diff --git a/ROADMAP.md b/ROADMAP.md index cacae509..99f3bb4a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,7 +8,8 @@ Core features improve the library itself to cater wider range of functionalities | Name | Description | Status | Release version | |------|-------------|--------|-----------------| -| Full composability | Right now teams can only be combined with teams and agents with agents. We want to extend this to team + agent composibility | Yet to start | 0.0.4 | +| Full composability | Right now teams can only be combined with teams and agents with agents. We want to extend this to team + agent composibility | In progress | 0.0.4 | +| Error handling | Ability to handle errors autonomously | In Progress | 0.0.4| |Output formatter| Ability to templatize output format using pydantic| Yet to start| 0.0.4| |LLM Extensibilty| Ability to different LLMs across different agents and teams| Yet to start| 0.0.4| |Auto-Query RAG| Ability to make metadata query within the agentic, which can automatically add metadata while rag query runs, like timestamp or other data|Yet to start|TBD| diff --git a/examples/email_reply_agent.ipynb b/examples/email_reply_agent.ipynb index f7f4f40b..92d9f7a7 100644 --- a/examples/email_reply_agent.ipynb +++ b/examples/email_reply_agent.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -11,7 +11,7 @@ "True" ] }, - "execution_count": 1, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -50,8 +50,8 @@ " email: str = Field(description=\"The email text to be sent\")\n", "\n", "class SendEmailTool(BaseTool):\n", - " name = \"email_triage\"\n", - " description = \"useful for when you need to send an email to someone\"\n", + " name: str = \"email_triage\"\n", + " description: str = \"useful for when you need to send an email to someone\"\n", " args_schema: Type[BaseModel] = SendEmailInput\n", "\n", " def _run(\n", @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -92,8 +92,8 @@ " reference_number: str = Field(description=\"The transaction reference number\")\n", "\n", "class FetchTransactionTool(BaseTool):\n", - " name = \"fetch_transactions\"\n", - " description = \"useful for when you want to fetch the transaction details given reference number\"\n", + " name: str = \"fetch_transactions\"\n", + " description: str = \"useful for when you want to fetch the transaction details given reference number\"\n", " args_schema: Type[BaseModel] = FetchTrxInput\n", "\n", " def _run(\n", @@ -116,28 +116,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 13, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/vizsatiz/Documents/hub/flo/.venv/lib/python3.11/site-packages/sentence_transformers/cross_encoder/CrossEncoder.py:11: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from tqdm.autonotebook import tqdm, trange\n", - "/Users/vizsatiz/Documents/hub/flo/.venv/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:1601: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted in transformers v4.45, and will be then set to `False` by default. For more details check this issue: https://github.com/huggingface/transformers/issues/31884\n", - " warnings.warn(\n" - ] - } - ], + "outputs": [], "source": [ "# import\n", "from langchain_chroma import Chroma\n", "from langchain_openai import OpenAIEmbeddings\n", "from langchain_community.document_loaders import TextLoader\n", - "from langchain_community.embeddings.sentence_transformer import (\n", - " SentenceTransformerEmbeddings,\n", - ")\n", "from langchain_text_splitters import CharacterTextSplitter\n", "\n", "# load the document and split it into chunks\n", @@ -157,16 +143,31 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 14, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-11-06 10:24:00,066 - SESSION - INFO - New FloSession created with ID: eMUGLvOrp7dclQJZ\n", + "2024-11-06 10:24:00,148 - SESSION - INFO - Tool 'HousingLoanTool' registered for session eMUGLvOrp7dclQJZ\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "**********\n" + ] + }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -197,16 +198,24 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 15, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-11-06 10:24:03,385 - SESSION - INFO - Tool 'SendEmailTool' registered for session eMUGLvOrp7dclQJZ\n", + "2024-11-06 10:24:03,386 - SESSION - INFO - Tool 'FetchTransactionTool' registered for session eMUGLvOrp7dclQJZ\n" + ] + }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 6, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -230,7 +239,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -271,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -311,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -341,37 +350,59 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", - "To disable this warning, you can either:\n", - "\t- Avoid using `tokenizers` before the fork if possible\n", - "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", - "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", - "To disable this warning, you can either:\n", - "\t- Avoid using `tokenizers` before the fork if possible\n", - "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" + "2024-11-06 10:24:15,166 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-11-06 10:24:15,192 - COMMON - INFO - Flo instance created for session eMUGLvOrp7dclQJZ\n", + "2024-11-06 10:24:15,193 - COMMON - INFO - Streaming query for session eMUGLvOrp7dclQJZ: \n", + "You are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\n", + "\n", + "1. Reply asking for more information if there is missing information in the email\n", + "2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\n", + "3. If are not able to come up with a answer just reply I dont know over the email\n", + "\n", + "Email:\n", + "\n", + "\n", + "Hi Tom,\n", + "\n", + "We have a failed transaction. Can you tell me more about why this failed.\n", + "\n", + "Transaction ID: 12123123432\n", + "\n", + "Thanks\n", + "Vishnu\n", + "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "{'SupportSupervisor-pwZRF': {'next': 'TransactionFetcher-lpv5Y'}}\n", + "----->\n", + "{'messages': [{'content': 'You are a supervisor tasked with managing a conversation between the following team members: EmailSender, TransactionFetcher, HousingLoanTeamLead. Given the following user request, respond with the worker to act next. Each worker will perform a task and respond with their results and status. When the users question is answered or the assigned task is finished, respond with FINISH. ', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': 'Given the conversation above, who should act next? Or should we FINISH if the task is already answered, Select one of: [\\'EmailSender\\', \\'TransactionFetcher\\', \\'HousingLoanTeamLead\\', \\'FINISH\\'] \\n The output should be formatted as a JSON instance that conforms to the JSON schema below.\\n\\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\\n\\nHere is the output schema:\\n```\\n{\"properties\": {\"next\": {\"title\": \"Next\", \"description\": \"Name of the next member to be called\", \"type\": \"string\"}}, \"required\": [\"next\"]}\\n```', 'role': 'system'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': False, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': NOT_GIVEN, 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", + "{'SupportSupervisor': {'next': 'TransactionFetcher'}}\n", "----\n", "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "----->\n", + "{'messages': [{'content': 'You are capable of fetching any kind of transactions from the database given transaction reference id', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': True, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': [{'type': 'function', 'function': {'name': 'fetch_transactions', 'description': 'useful for when you want to fetch the transaction details given reference number', 'parameters': {'properties': {'reference_number': {'description': 'The transaction reference number', 'type': 'string'}}, 'required': ['reference_number'], 'type': 'object'}}}], 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", "\u001b[32;1m\u001b[1;3m\n", "Invoking: `fetch_transactions` with `{'reference_number': '12123123432'}`\n", "\n", "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3mThe transaction happened on 23/07/2024 IST and it failed because there was not enough balance in the account\u001b[0m\u001b[32;1m\u001b[1;3mSubject: Re: Failed Transaction Inquiry\n", + "\u001b[0m\u001b[36;1m\u001b[1;3mThe transaction happened on 23/07/2024 IST and it failed because there was not enough balance in the account\u001b[0m----->\n", + "{'messages': [{'content': 'You are capable of fetching any kind of transactions from the database given transaction reference id', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': None, 'role': 'assistant', 'tool_calls': [{'type': 'function', 'id': 'call_gz8RGLLuebKNGo8t29nWXhPr', 'function': {'name': 'fetch_transactions', 'arguments': '{\"reference_number\": \"12123123432\"}'}}]}, {'content': 'The transaction happened on 23/07/2024 IST and it failed because there was not enough balance in the account', 'role': 'tool', 'tool_call_id': 'call_gz8RGLLuebKNGo8t29nWXhPr'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': True, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': [{'type': 'function', 'function': {'name': 'fetch_transactions', 'description': 'useful for when you want to fetch the transaction details given reference number', 'parameters': {'properties': {'reference_number': {'description': 'The transaction reference number', 'type': 'string'}}, 'required': ['reference_number'], 'type': 'object'}}}], 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", + "\u001b[32;1m\u001b[1;3mSubject: Re: Failed Transaction Inquiry\n", "\n", "Hi Vishnu,\n", "\n", @@ -385,20 +416,24 @@ "Tom\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n", - "{'TransactionFetcher-lpv5Y': {'messages': [HumanMessage(content='Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', name='TransactionFetcher-lpv5Y')]}}\n", + "{'TransactionFetcher': {'messages': [HumanMessage(content='Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', additional_kwargs={}, response_metadata={}, name='TransactionFetcher')]}}\n", "----\n", - "{'SupportSupervisor-pwZRF': {'next': 'EmailSender-Bn2zS'}}\n", + "----->\n", + "{'messages': [{'content': 'You are a supervisor tasked with managing a conversation between the following team members: EmailSender, TransactionFetcher, HousingLoanTeamLead. Given the following user request, respond with the worker to act next. Each worker will perform a task and respond with their results and status. When the users question is answered or the assigned task is finished, respond with FINISH. ', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': 'Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', 'name': 'TransactionFetcher', 'role': 'user'}, {'content': 'Given the conversation above, who should act next? Or should we FINISH if the task is already answered, Select one of: [\\'EmailSender\\', \\'TransactionFetcher\\', \\'HousingLoanTeamLead\\', \\'FINISH\\'] \\n The output should be formatted as a JSON instance that conforms to the JSON schema below.\\n\\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\\n\\nHere is the output schema:\\n```\\n{\"properties\": {\"next\": {\"title\": \"Next\", \"description\": \"Name of the next member to be called\", \"type\": \"string\"}}, \"required\": [\"next\"]}\\n```', 'role': 'system'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': False, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': NOT_GIVEN, 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", + "{'SupportSupervisor': {'next': 'EmailSender'}}\n", "----\n", "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "----->\n", + "{'messages': [{'content': 'You are capable of sending the reply email but constructing a apt response', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': 'Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', 'name': 'TransactionFetcher', 'role': 'user'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': True, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': [{'type': 'function', 'function': {'name': 'email_triage', 'description': 'useful for when you need to send an email to someone', 'parameters': {'properties': {'email': {'description': 'The email text to be sent', 'type': 'string'}}, 'required': ['email'], 'type': 'object'}}}], 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `email_triage` with `{'email': 'Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards,\\nTom'}`\n", + "Invoking: `email_triage` with `{'email': 'Hi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards,\\nTom'}`\n", "\n", "\n", - "\u001b[0mSubject: Re: Failed Transaction Inquiry\n", - "\n", - "Hi Vishnu,\n", + "\u001b[0mHi Vishnu,\n", "\n", "Thank you for reaching out regarding the failed transaction.\n", "\n", @@ -408,12 +443,18 @@ "\n", "Best regards,\n", "Tom\n", - "\u001b[36;1m\u001b[1;3mEmail sent successfully\u001b[0m\u001b[32;1m\u001b[1;3mI have sent the response to Vishnu regarding the failed transaction. If you need any further assistance, feel free to ask!\u001b[0m\n", + "\u001b[36;1m\u001b[1;3mEmail sent successfully\u001b[0m----->\n", + "{'messages': [{'content': 'You are capable of sending the reply email but constructing a apt response', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': 'Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', 'name': 'TransactionFetcher', 'role': 'user'}, {'content': None, 'role': 'assistant', 'tool_calls': [{'type': 'function', 'id': 'call_tRgauP6bKTaqWhtyZBx6RHVM', 'function': {'name': 'email_triage', 'arguments': '{\"email\": \"Hi Vishnu,\\\\n\\\\nThank you for reaching out regarding the failed transaction.\\\\n\\\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\\\n\\\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\\\n\\\\nBest regards,\\\\nTom\"}'}}]}, {'content': 'Email sent successfully', 'role': 'tool', 'tool_call_id': 'call_tRgauP6bKTaqWhtyZBx6RHVM'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': True, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': [{'type': 'function', 'function': {'name': 'email_triage', 'description': 'useful for when you need to send an email to someone', 'parameters': {'properties': {'email': {'description': 'The email text to be sent', 'type': 'string'}}, 'required': ['email'], 'type': 'object'}}}], 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", + "\u001b[32;1m\u001b[1;3mI have sent the response to Vishnu regarding the failed transaction. If you need any further assistance, feel free to ask!\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n", - "{'EmailSender-Bn2zS': {'messages': [HumanMessage(content='I have sent the response to Vishnu regarding the failed transaction. If you need any further assistance, feel free to ask!', name='EmailSender-Bn2zS')]}}\n", + "{'EmailSender': {'messages': [HumanMessage(content='I have sent the response to Vishnu regarding the failed transaction. If you need any further assistance, feel free to ask!', additional_kwargs={}, response_metadata={}, name='EmailSender')]}}\n", "----\n", - "{'SupportSupervisor-pwZRF': {'next': 'FINISH'}}\n", + "----->\n", + "{'messages': [{'content': 'You are a supervisor tasked with managing a conversation between the following team members: EmailSender, TransactionFetcher, HousingLoanTeamLead. Given the following user request, respond with the worker to act next. Each worker will perform a task and respond with their results and status. When the users question is answered or the assigned task is finished, respond with FINISH. ', 'role': 'system'}, {'content': '\\nYou are a support ticket handling agent. You are expected to understand the email, and perform the following steps:\\n\\n1. Reply asking for more information if there is missing information in the email\\n2. Perform the neccessary steps to required to answer the users question and reply the user query as email.\\n3. If are not able to come up with a answer just reply I dont know over the email\\n\\nEmail:\\n\\n\\nHi Tom,\\n\\nWe have a failed transaction. Can you tell me more about why this failed.\\n\\nTransaction ID: 12123123432\\n\\nThanks\\nVishnu\\n', 'role': 'user'}, {'content': 'Subject: Re: Failed Transaction Inquiry\\n\\nHi Vishnu,\\n\\nThank you for reaching out regarding the failed transaction.\\n\\nThe transaction with ID 12123123432 occurred on 23/07/2024 IST and unfortunately failed due to insufficient balance in the account.\\n\\nIf you have any further questions or need assistance with anything else, feel free to ask!\\n\\nBest regards, \\nTom', 'name': 'TransactionFetcher', 'role': 'user'}, {'content': 'I have sent the response to Vishnu regarding the failed transaction. If you need any further assistance, feel free to ask!', 'name': 'EmailSender', 'role': 'user'}, {'content': 'Given the conversation above, who should act next? Or should we FINISH if the task is already answered, Select one of: [\\'EmailSender\\', \\'TransactionFetcher\\', \\'HousingLoanTeamLead\\', \\'FINISH\\'] \\n The output should be formatted as a JSON instance that conforms to the JSON schema below.\\n\\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\\n\\nHere is the output schema:\\n```\\n{\"properties\": {\"next\": {\"title\": \"Next\", \"description\": \"Name of the next member to be called\", \"type\": \"string\"}}, \"required\": [\"next\"]}\\n```', 'role': 'system'}], 'model': 'gpt-4o-mini', 'frequency_penalty': NOT_GIVEN, 'function_call': NOT_GIVEN, 'functions': NOT_GIVEN, 'logit_bias': NOT_GIVEN, 'logprobs': NOT_GIVEN, 'max_completion_tokens': NOT_GIVEN, 'max_tokens': NOT_GIVEN, 'metadata': NOT_GIVEN, 'n': 1, 'parallel_tool_calls': NOT_GIVEN, 'presence_penalty': NOT_GIVEN, 'response_format': NOT_GIVEN, 'seed': NOT_GIVEN, 'service_tier': NOT_GIVEN, 'stop': NOT_GIVEN, 'store': NOT_GIVEN, 'stream': False, 'stream_options': NOT_GIVEN, 'temperature': 0.0, 'tool_choice': NOT_GIVEN, 'tools': NOT_GIVEN, 'top_logprobs': NOT_GIVEN, 'top_p': NOT_GIVEN, 'user': NOT_GIVEN}\n", + "----->\n", + "{'SupportSupervisor': {'next': 'FINISH'}}\n", "----\n" ] } diff --git a/examples/tool_error_handling.py b/examples/tool_error_handling.py new file mode 100644 index 00000000..73d53495 --- /dev/null +++ b/examples/tool_error_handling.py @@ -0,0 +1,82 @@ +from flo_ai import Flo +from flo_ai import FloSession +from langchain_openai import ChatOpenAI +from langchain.tools import BaseTool +from pydantic import BaseModel, Field +from typing import List +from dotenv import load_dotenv +from flo_ai.tools.flo_tool import flotool + +load_dotenv() + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') + +session = FloSession( + llm, + log_level="ERROR" +) + +class AdditionToolInput(BaseModel): + numbers: List[int] = Field(..., description="List of numbers to add") + +import asyncio +# Use flotool to define the tool function +@flotool(name="AdditionTool", description="Tool to add numbers") +async def addition_tool(numbers: List[int]) -> str: + result = sum(numbers) + await asyncio.sleep(1) + return f"The sum is {result}" + +@flotool(name="MultiplicationTool", description="Tool to multiply numbers to get product of numbers") +def mul_tool(numbers: List[int]) -> str: + result = sum(numbers) + # await asyncio.sleep(1) + return f"The product is {result}" + +session.register_tool( + name="Adder", + tool=addition_tool +).register_tool( + name="Multiplier", + tool=mul_tool +) + +simple_weather_checking_agent = """ +apiVersion: flo/alpha-v1 +kind: FloAgent +name: weather-assistant +agent: + name: SummationHelper + kind: agentic + job: > + Add or multiply numbers. Always answer based on what the tool says + tools: + - name: Adder + - name: Multiplier +""" + +from IPython.display import Image, display +flo = Flo.build(session, simple_weather_checking_agent, log_level="ERROR") + +import asyncio + +# Assuming flo.ainvoke is your async method for invoking the tool or chain +async def invoke_main(): + result = await flo.async_invoke("Whats the sum of 1, 3, 4, 5 and 6, and their product") + print(result) + +asyncio.run(invoke_main()) + +# import asyncio + +# async def stream_main(): +# # Use 'async for' to iterate over the asynchronous generator +# async for s in flo.async_stream("Whats the sum of 1, 3, 4, 5 and 6, and their product"): +# if "__end__" not in s: +# print(s) +# print("----") + +# asyncio.run(stream_main()) + + + diff --git a/features/steps/flo_name_steps.py b/features/steps/flo_name_steps.py index 81f74ea2..7bb9c9a6 100644 --- a/features/steps/flo_name_steps.py +++ b/features/steps/flo_name_steps.py @@ -1,4 +1,4 @@ -from behave import given, when, then, step +from behave import when, then from flo_ai.yaml.validators import raise_for_name_error @when('use the name, {flo_name}') diff --git a/flo_ai/builders/yaml_builder.py b/flo_ai/builders/yaml_builder.py index 15dd0aea..6a72d19e 100644 --- a/flo_ai/builders/yaml_builder.py +++ b/flo_ai/builders/yaml_builder.py @@ -4,7 +4,8 @@ from flo_ai.state.flo_session import FloSession from flo_ai.router.flo_router_factory import FloRouterFactory from flo_ai.factory.agent_factory import AgentFactory -from flo_ai.yaml.validators import raise_for_name_error, DuplicateStringError +from flo_ai.error.flo_exception import FloException +from flo_ai.yaml.validators import raise_for_name_error from flo_ai.common.flo_logger import builder_logger def build_supervised_team(session: FloSession) -> ExecutableFlo: @@ -45,5 +46,5 @@ def validate_names(name_set: set, name): raise_for_name_error(name) if name in name_set: builder_logger.error(f"Duplicate name found: '{name}'") - raise DuplicateStringError(f"The name '{name}' is already in the set.") + raise FloException(f"The name '{name}' is already in the set.") name_set.add(name) \ No newline at end of file diff --git a/flo_ai/core.py b/flo_ai/core.py index 801d1c9d..0d78d33b 100644 --- a/flo_ai/core.py +++ b/flo_ai/core.py @@ -1,8 +1,10 @@ +import asyncio from flo_ai.yaml.config import to_supervised_team from flo_ai.builders.yaml_builder import build_supervised_team, FloRoutedTeamConfig from typing import Any, Iterator, Union from flo_ai.state.flo_session import FloSession from flo_ai.models.flo_executable import ExecutableFlo +from flo_ai.error.flo_exception import FloException from flo_ai.common.flo_logger import common_logger, builder_logger, set_global_log_level class Flo: @@ -25,6 +27,10 @@ def stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: self.logger.info(f"Streaming query for session {self.session.session_id}: {query}") return self.runnable.stream(query, config) + def async_stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: + self.logger.info(f"Streaming query for session {self.session.session_id}: {query}") + return self.runnable.astream(query, config) + def invoke(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: config = { 'callbacks' : [self.session.langchain_logger] @@ -32,6 +38,13 @@ def invoke(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: self.logger.info(f"Invoking query for session {self.session.session_id}: {query}") return self.runnable.invoke(query, config) + def async_invoke(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: + config = { + 'callbacks' : [self.session.langchain_logger] + } + self.logger.info(f"Invoking query for session {self.session.session_id}: {query}") + return self.runnable.ainvoke(query, config) + @staticmethod def build(session: FloSession, yaml: str, log_level: str = "INFO"): set_global_log_level(log_level) diff --git a/flo_ai/error/flo_exception.py b/flo_ai/error/flo_exception.py new file mode 100644 index 00000000..06a2c405 --- /dev/null +++ b/flo_ai/error/flo_exception.py @@ -0,0 +1,2 @@ +class FloException(Exception): + pass \ No newline at end of file diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 1820015e..77dd2dcd 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -43,7 +43,8 @@ def __create_agentic_agent(session: FloSession, agent: AgentConfig, tool_map) -> flo_agent: FloAgent = FloAgent.Builder( session, agent, - tools + tools, + on_error=session.on_agent_error ).build() return flo_agent diff --git a/flo_ai/models/flo_agent.py b/flo_ai/models/flo_agent.py index f1bfa3e9..98881051 100644 --- a/flo_ai/models/flo_agent.py +++ b/flo_ai/models/flo_agent.py @@ -6,7 +6,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from flo_ai.models.flo_executable import ExecutableFlo from flo_ai.state.flo_session import FloSession -from typing import Union, Optional +from typing import Union, Optional, Callable from flo_ai.yaml.config import AgentConfig from flo_ai.models.flo_executable import ExecutableType @@ -28,14 +28,12 @@ def __init__(self, verbose: bool = True, role: Optional[str] = None, llm: Union[BaseLanguageModel, None] = None, - return_intermediate_steps: bool = False, - handle_parsing_errors: bool = True) -> None: - prompt: Union[ChatPromptTemplate, str] = config.job + on_error: Union[str, Callable] = True) -> None: + prompt: Union[ChatPromptTemplate, str] = config.job self.name: str = config.name self.llm = llm if llm is not None else session.llm self.config = config - # TODO improve to add more context of what other agents are available system_prompts = [("system", "You are a {}".format(role)), ("system", prompt)] if role is not None else [("system", prompt)] system_prompts.append(MessagesPlaceholder(variable_name="messages")) system_prompts.append(MessagesPlaceholder(variable_name="agent_scratchpad")) @@ -44,8 +42,7 @@ def __init__(self, ) if isinstance(prompt, str) else prompt self.tools: list[BaseTool] = tools self.verbose = verbose - self.return_intermediate_steps = return_intermediate_steps - self.handle_parsing_errors = handle_parsing_errors + self.on_error = on_error def build(self) -> AgentExecutor: @@ -53,6 +50,6 @@ def build(self) -> AgentExecutor: executor = AgentExecutor(agent=agent, tools=self.tools, verbose=self.verbose, - return_intermediate_steps=self.return_intermediate_steps, - handle_parsing_errors=self.handle_parsing_errors) + return_intermediate_steps=True, + handle_parsing_errors=self.on_error) return FloAgent(agent, executor, self.config) diff --git a/flo_ai/models/flo_executable.py b/flo_ai/models/flo_executable.py index f5a76e65..1dd73060 100644 --- a/flo_ai/models/flo_executable.py +++ b/flo_ai/models/flo_executable.py @@ -39,6 +39,13 @@ def stream(self, work, config = None): ] }, config) + def astream(self, work, config = None): + return self.runnable.astream({ + STATE_NAME_MESSAGES: [ + HumanMessage(content=work) + ] + }, config) + def invoke(self, work, config = None): return self.runnable.invoke({ STATE_NAME_MESSAGES: [ @@ -46,6 +53,13 @@ def invoke(self, work, config = None): ], }, config) + def ainvoke(self, work, config = None): + return self.runnable.ainvoke({ + STATE_NAME_MESSAGES: [ + HumanMessage(content=work) + ], + }, config) + def draw(self, xray=True): return self.runnable.get_graph().draw_mermaid_png() \ No newline at end of file diff --git a/flo_ai/router/flo_llm_router.py b/flo_ai/router/flo_llm_router.py index ffcdb58d..054f3cb2 100644 --- a/flo_ai/router/flo_llm_router.py +++ b/flo_ai/router/flo_llm_router.py @@ -11,20 +11,11 @@ from flo_ai.state.flo_state import TeamFloAgentState from flo_ai.yaml.config import TeamConfig from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field +from pydantic import BaseModel, Field class NextAgent(BaseModel): next: str = Field(description="Name of the next member to be called") -class StateUpdateComponent: - def __init__(self, name: str, session: FloSession) -> None: - self.name = name - self.inner_session = session - - def __call__(self, input): - self.inner_session.append(self.name) - return input - class FloLLMRouter(FloRouter): def __init__(self, diff --git a/flo_ai/router/flo_supervisor.py b/flo_ai/router/flo_supervisor.py index 2260dd16..a16ba2b3 100644 --- a/flo_ai/router/flo_supervisor.py +++ b/flo_ai/router/flo_supervisor.py @@ -4,7 +4,7 @@ from langchain_core.runnables import Runnable from flo_ai.state.flo_session import FloSession from flo_ai.constants.prompt_constants import FLO_FINISH -from flo_ai.router.flo_llm_router import FloLLMRouter, StateUpdateComponent +from flo_ai.router.flo_llm_router import FloLLMRouter from flo_ai.models.flo_team import FloTeam from flo_ai.yaml.config import TeamConfig from langchain_core.output_parsers import JsonOutputParser diff --git a/flo_ai/state/flo_session.py b/flo_ai/state/flo_session.py index 280270cc..8a6dcd38 100644 --- a/flo_ai/state/flo_session.py +++ b/flo_ai/state/flo_session.py @@ -1,5 +1,4 @@ -import uuid -from typing import Union +from typing import Union, Dict from langchain_core.language_models import BaseLanguageModel from langchain_core.tools import BaseTool from flo_ai.common.flo_logger import session_logger, FloLogger @@ -9,6 +8,13 @@ from typing import Optional +def _handle_agent_error(error) -> str: + error_message = str(error)[:50] + return f""" + Following error happened while agent execution, please retry with the fix for the same: + {error_message} + """ + class FloSession: def __init__(self, @@ -16,16 +22,18 @@ def __init__(self, loop_size: int = 2, max_loop: int = 3, log_level: Optional[str] = "INFO", - custom_langchainlog_handler: Optional[FloLangchainLogger] = None) -> None: + custom_langchainlog_handler: Optional[FloLangchainLogger] = None, + on_agent_error=_handle_agent_error) -> None: self.session_id = str(random_str(16)) self.llm = llm - self.tools = dict() + self.tools: Dict[str, BaseTool] = dict() self.counter = dict() self.navigation: list[str] = list() self.pattern_series = dict() self.loop_size: int = loop_size self.max_loop: int = max_loop + self.on_agent_error = on_agent_error self.init_logger(log_level) self.logger = session_logger diff --git a/flo_ai/tools/flo_tool.py b/flo_ai/tools/flo_tool.py new file mode 100644 index 00000000..97e44d2e --- /dev/null +++ b/flo_ai/tools/flo_tool.py @@ -0,0 +1,28 @@ +import asyncio +from langchain.tools import tool +from functools import wraps +from typing import Optional + +def flotool( + name: str, + description: Optional[str] = None, + argument_contract: Optional[type] = None, + unsafe: bool = False): + + def decorator(func): + func.__doc__ = func.__doc__ or description + + @tool(name, args_schema=argument_contract) + @wraps(func) + async def wrapper(*args, **kwargs): + try: + if asyncio.iscoroutinefunction(func): + return await func(*args, **kwargs) + return func(*args, **kwargs) + except Exception as e: + if unsafe: + raise e + return f"An error occurred while executing the tool: {str(e)}, please retry with the corresponding fix" + return wrapper + + return decorator diff --git a/flo_ai/yaml/validators.py b/flo_ai/yaml/validators.py index 9974b5f3..a0d318ab 100644 --- a/flo_ai/yaml/validators.py +++ b/flo_ai/yaml/validators.py @@ -1,13 +1,8 @@ import re +from flo_ai.error.flo_exception import FloException name_regex = r'^[a-zA-Z0-9-_]+$' -class DuplicateStringError(Exception): - pass - -class InvalidStringError(Exception): - pass - def raise_for_name_error(string): if not re.match(name_regex, string): - raise InvalidStringError("Name must contain only alphanumeric characters and hyphens.") \ No newline at end of file + raise FloException("Name must contain only alphanumeric characters and hyphens.") \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 6880bd1c..1424bd6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -332,6 +332,27 @@ files = [ tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "behave" version = "1.2.6" @@ -4016,6 +4037,17 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + [[package]] name = "sqlalchemy" version = "2.0.36" @@ -4777,6 +4809,20 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] +[[package]] +name = "wikipedia" +version = "1.4.0" +description = "Wikipedia API for Python" +optional = false +python-versions = "*" +files = [ + {file = "wikipedia-1.4.0.tar.gz", hash = "sha256:db0fad1829fdd441b1852306e9856398204dc0786d2996dd2e0c8bb8e26133b2"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +requests = ">=2.0.0,<3.0.0" + [[package]] name = "wrapt" version = "1.16.0" @@ -4974,4 +5020,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "d96d7a3919b34d16e06ca0d53e2fbdd0f576448387cacb6c867e13f3e1103ed3" +content-hash = "ef9f7664b9fc1d0b09720b05568c4965c8dc6ca2153f79563fdadbf1d0171623" diff --git a/pyproject.toml b/pyproject.toml index 46825421..bd3ebffa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ db-sqlite3 = "^0.0.1" peewee = "^3.17.6" python-dotenv = "^1.0.1" behave = "^1.2.6" +wikipedia = "^1.4.0" [build-system] requires = ["poetry-core"]