diff --git a/.gitignore b/.gitignore index efa407c..2d19f40 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +# ignore .DS_Store file on macOS +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 9aaffdb..054d5ee 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,23 @@ To run this sample code follow these steps: 2. **Run the script** - run the `python main.py` 3. **Command line argument** - Optionally you can add `--input "new input here"` to overide the default user input text. +### Chatbot UI +To run the chatbot interface, follow these steps: + +1. Start the chatbot interface by running the following command: + ```bash + python chatbot_interface.py + ``` + You should see output similar to: + ``` + Running on local URL: http://127.0.0.1:7860 + + To create a public link, set `share=True` in `launch()`. + ``` +2. Open the provided local URL in your web browser to start chatting. + +3. The UI will look like the following: ![Chatbot UI](images/chatbot_ui.png) + ### Example **Default prompt**: "What is the current stock price of amazon stock in pounds? diff --git a/chatbot_interface.py b/chatbot_interface.py new file mode 100644 index 0000000..67202f8 --- /dev/null +++ b/chatbot_interface.py @@ -0,0 +1,107 @@ +import sys +from loguru import logger +from pkg.ask import generate_text +from tools.ticker import parse_and_run_get_stock_price +from tools.currency import parse_and_run_convert_currency +import argparse +import json +import gradio as gr + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "input", + type=str, + nargs="?", + help="Input text for the LLM", + default="What is the current stock price of amazon stock in pounds?", + ) + parser.add_argument( + "--log-level", + type=str, + default="INFO", + choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + help="Set the logging level", + ) + return parser.parse_known_args() + + +def build_message(input_text: str) -> list[dict]: + """ + Build the message to send to the LLM from the input test + :param input_text: the input text to send to the LLM + :return: a list containing containing the role, content and user input as text + """ + return [{"role": "user", "content": [{"text": input_text}]}] + + +def chatbot(input_text: str) -> str: + logger.info("Starting") + msg = build_message(input_text) + logger.info(f"input text: {input_text}") + + stop_reason: str = None + answer: str = None + + # Run until the model end_turn + while stop_reason != "end_turn": + stop_reason, tools_requested, messages = generate_text(msg) + logger.debug(f"stop reason is {stop_reason}, continue work till final answer") + + # Amazon Bedrock LLM ended turn and responded the final answer + if stop_reason == "end_turn": + logger.info("The question asked the LLM ended turn and this is the answer") + answers = messages.get("content", {}) + # iterate over the returned answers from Amazon Bedrock LLM + answers_text = [a.get("text", "\n") for a in answers] + answer = "".join(answers_text) + break + + if stop_reason == "tool_use": + # find from the content returned form tools_requested the tool to use + for content in tools_requested: + if "toolUse" in content: + tool_use_id = content.get("toolUse", {}).get("toolUseId") + tool_use_name = content.get("toolUse", {}).get("name") + tool_use_input = content.get("toolUse", {}).get("input") + logger.info( + f"tool use id is {tool_use_id}, tool use name is {tool_use_name}" + ) + # stock price tool + if tool_use_name == "get_stock_price": + message = parse_and_run_get_stock_price( + tool_use_id, tool_use_input + ) + messages.append(message) + + # currency conversion tool + if tool_use_name == "convert_currency": + message = parse_and_run_convert_currency( + tool_use_id, tool_use_input + ) + messages.append(message) + + # See the messages appended that are being built for the LLM, this will allow the Bedrock LLM to provide the final answer. + logger.debug(f"messages:\n{json.dumps(messages)}") + + else: + # Stop reasons can be: 'end_turn'|'tool_use'|'max_tokens'|'stop_sequence'|'guardrail_intervened'|'content_filtered' + # This code sample only covers end_turn, and tool_use, you may need to implement additional code to cover all the rest of the responses. + logger.warning( + f"llm didn't end_turn, or asked to use a tool, he asked to {stop_reason}" + ) + return "An error occurred." + + # Printing the final response from the model + logger.info(answer) + return answer + + +if __name__ == "__main__": + args, _ = parse_args() + logger.remove() # Remove the default logger + logger.add(sys.stderr, level=args.log_level.upper()) + + iface = gr.Interface(fn=chatbot, inputs="text", outputs="text", title="Chatbot") + iface.launch() diff --git a/images/chatbot_ui.png b/images/chatbot_ui.png new file mode 100644 index 0000000..c7f15fa Binary files /dev/null and b/images/chatbot_ui.png differ diff --git a/requirements.txt b/requirements.txt index beddf9a..2c5277f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ boto3>=1.34.141 loguru>=0.7.2 yfinance==0.2.40 -currencyconverter==0.17.25 \ No newline at end of file +currencyconverter==0.17.25 +gradio==4.44.1 \ No newline at end of file