Add Funccall notebook and document (#1110)

* add funccall example and doc

* revise to comments

* Update website/docs/Use-Cases/Auto-Generation.md

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* revise

* update

* minor update

* add test notebook

* update

---------

Co-authored-by: Chi Wang <wang.chi@microsoft.com>
This commit is contained in:
Yiran Wu 2023-07-10 12:56:07 +08:00 committed by GitHub
parent 5eece5c748
commit 255faf97e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 499 additions and 3 deletions

View File

@ -1 +1 @@
__version__ = "2.0.0rc2"
__version__ = "2.0.0rc3"

View File

@ -0,0 +1,428 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "ae1f50ec",
"metadata": {},
"source": [
"<a href=\"https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/autogen_agent_function_call.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "9a71fa36",
"metadata": {},
"source": [
"# Interative LLM Agent with Function Calls\n",
"\n",
"FLAML offers an experimental feature of interactive LLM agents, which can be used to solve various tasks with human or automatic feedback, including tasks that require using tools via code. We now support the new feature of OpenAI models to make function calls to pre-defined functions. \n",
"\n",
"In this notebook, we demonstrate how to use `AssistantAgent` and `UserProxyAgent` to make function calls (For details of the two agents please check out [autogen_agent_auto_feedback_from_code_execution.ipynb](https://github.com/microsoft/FLAML/blob/main/notebook/autogen_agent_auto_feedback_from_code_execution.ipynb)). A specified prompt and function configs need to be passed to `AssistantAgent` to initialize the agent. The corresponding functions need to be passed to `UserProxyAgent`, which will be responsible for executing any function calls made by `AssistantAgent`. Besides this requirement of matching descriptions with functions, we recommend checking the system prompt to make sure the instructions align with the function call descriptions.\n",
"\n",
"## Requirements\n",
"\n",
"FLAML requires `Python>=3.8`. To run this notebook example, please install flaml with the [mathchat] option since we will import functions from `MathUserProxyAgent`:\n",
"```bash\n",
"pip install flaml[mathchat]\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2b803c17",
"metadata": {},
"outputs": [],
"source": [
"# %pip install flaml[mathchat]==2.0.0rc3"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "5ebd2397",
"metadata": {},
"source": [
"## Set your API Endpoint\n",
"\n",
"The [`config_list_from_models`](https://microsoft.github.io/FLAML/docs/reference/autogen/oai/openai_utils#config_list_from_models) function tries to create a list of configurations using Azure OpenAI endpoints and OpenAI endpoints for the provided list of models. It assumes the api keys and api bases are stored in the corresponding environment variables or local txt files:\n",
"\n",
"- OpenAI API key: os.environ[\"OPENAI_API_KEY\"] or `openai_api_key_file=\"key_openai.txt\"`.\n",
"- Azure OpenAI API key: os.environ[\"AZURE_OPENAI_API_KEY\"] or `aoai_api_key_file=\"key_aoai.txt\"`. Multiple keys can be stored, one per line.\n",
"- Azure OpenAI API base: os.environ[\"AZURE_OPENAI_API_BASE\"] or `aoai_api_base_file=\"base_aoai.txt\"`. Multiple bases can be stored, one per line.\n",
"\n",
"It's OK to have only the OpenAI API key, or only the Azure OpenAI API key + base.\n",
"\n",
"If you open this notebook in google colab, you can upload your files by click the file icon on the left panel and then choose \"upload file\" icon.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "dca301a4",
"metadata": {},
"outputs": [],
"source": [
"from flaml import oai\n",
"\n",
"config_list = oai.config_list_from_models(model_list=[\"gpt-4-0613\"])"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "2b9526e7",
"metadata": {},
"source": [
"## Making Function Calls\n",
"\n",
"In this example, we demonstrate function call execution with `AssistantAgent` and `UserProxyAgent`. With the default system prompt of `AssistantAgent`, we allow the LLM assistant to perform tasks with code, and the `UserProxyAgent` would extract code blocks from the LLM response and execute them. With the new \"function_call\" feature, we define a new function using the pre-defined `_execute_code` from `UserProxyAgent` and specify the description of the function in the OpenAI config. \n",
"\n",
"Then, the model has two paths to execute code:\n",
"1. Put the code blocks in the response. `UserProxyAgent` will extract and execute the code through `_execute_code` method in the class.\n",
"2. As we put a function description to OpenAI config and passed a function `execute_code_function` to `UserProxyAgent`, the model can also make function calls (will be put in `function_call` field of the API reply). `UserProxyAgent` will execute the function call through a `_execute_function` method."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "9fb85afb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user (to chatbot):\n",
"\n",
"Draw a rocket and save to a file named 'rocket.svg'\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"You can use the `svgwrite` library for Python to draw images into SVG format. Before we draw the rocket, you need to install the library. Use the code below to install it.\n",
"\n",
"```sh\n",
"pip install svgwrite\n",
"```\n",
"\n",
"After installing the library, here is the python code you can use to draw a rocket and save it to a file named 'rocket.svg':\n",
"\n",
"```python\n",
"# filename: draw_rocket.py\n",
"\n",
"import svgwrite\n",
"\n",
"def draw_rocket():\n",
" dwg = svgwrite.Drawing('rocket.svg', profile='tiny')\n",
"\n",
" # Draw rocket body\n",
" dwg.add(dwg.rect((50, 20), (20, 40), fill='grey'))\n",
"\n",
" # Draw top of rocket\n",
" dwg.add(dwg.polygon(points=[(50, 20), (60, 0), (70, 20)], fill='red'))\n",
"\n",
" # Draw bottom of rocket\n",
" dwg.add(dwg.polygon(points=[(50, 60), (60, 80), (70, 60)], fill='red'))\n",
"\n",
" # Draw rocket window\n",
" dwg.add(dwg.circle(center=(60, 40), r=5, fill='blue'))\n",
"\n",
" dwg.save()\n",
"\n",
"draw_rocket()\n",
"```\n",
"You can run this code using Python by calling `python draw_rocket.py`. After running this script, you will have a file named `rocket.svg` in your current directory. The SVG picture represents a simple rocket with a gray body, red top and bottom, and a blue window. \n",
"\n",
"Please replace the `draw_rocket.py` with your actual python filename when you execute the script.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\n",
">>>>>>>> NO HUMAN INPUT RECEIVED. USING AUTO REPLY FOR THE USER...\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"user (to chatbot):\n",
"\n",
"exitcode: 0 (execution succeeded)\n",
"Code output: \n",
"Collecting svgwrite\n",
" Downloading svgwrite-1.4.3-py3-none-any.whl (67 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67.1/67.1 kB 3.2 MB/s eta 0:00:00\n",
"Installing collected packages: svgwrite\n",
"Successfully installed svgwrite-1.4.3\n",
"WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\n",
"\n",
"\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"***** Suggested function Call: execute_code *****\n",
"Arguments: \n",
"{\n",
" \"code_type\": \"python\",\n",
" \"code\": \"import svgwrite\\n\\ndef draw_rocket():\\n dwg = svgwrite.Drawing('rocket.svg', profile='tiny')\\n\\n # Draw rocket body\\n dwg.add(dwg.rect((50, 20), (20, 40), fill='grey'))\\n\\n # Draw top of rocket\\n dwg.add(dwg.polygon(points=[(50, 20), (60, 0), (70, 20)], fill='red'))\\n\\n # Draw bottom of rocket\\n dwg.add(dwg.polygon(points=[(50, 60), (60, 80), (70, 60)], fill='red'))\\n\\n # Draw rocket window\\n dwg.add(dwg.circle(center=(60, 40), r=5, fill='blue'))\\n\\n dwg.save()\\n\\ndraw_rocket()\"\n",
"}\n",
"*************************************************\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\n",
">>>>>>>> NO HUMAN INPUT RECEIVED. USING AUTO REPLY FOR THE USER...\n",
"user (to chatbot):\n",
"\n",
"***** Response from calling function \"execute_code\" *****\n",
"(0, '\\n')\n",
"*********************************************************\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"Great! The code to draw the rocket and save it to 'rocket.svg' should have successfully executed. \n",
"\n",
"You should now be able to find the file 'rocket.svg' in your current directory and open it with an application that can handle SVG files (a web browser, for instance).\n",
"\n",
"Is there anything else you need help with? If not, please reply 'TERMINATE'.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\n",
">>>>>>>> NO HUMAN INPUT RECEIVED. USING AUTO REPLY FOR THE USER...\n",
"user (to chatbot):\n",
"\n",
"\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"TERMINATE\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"from flaml.autogen.agent import AssistantAgent, UserProxyAgent\n",
"\n",
"oai_config = {\n",
" \"functions\": [\n",
" {\n",
" \"name\": \"execute_code\",\n",
" \"description\": \"Receive a list of python code or shell script and return the execution result.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"code_type\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Code type, 'python' or 'sh'.\",\n",
" },\n",
" \"code\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Valid Python code to execute.\",\n",
" }\n",
" },\n",
" \"required\": [\"code_type\", \"code\"],\n",
" },\n",
" }\n",
" ],\n",
" \"function_call\": \"auto\",\n",
"}\n",
"chatbot = AssistantAgent(\"chatbot\", config_list=config_list, **oai_config)\n",
"\n",
"# use pre-defined execute_code function from a UserProxyAgent instance\n",
"# for simplicity, we don't pass in `exec_func` directly to UserProxyAgent because it requires a list of tuple as parameter\n",
"# instead, we define a wrapper function to call `exec_func`\n",
"exec_func = UserProxyAgent(name=\"execute_code\", work_dir=\"coding\", use_docker=False)._execute_code\n",
"def execute_code(code_type, code):\n",
" return exec_func([(code_type, code)])\n",
"\n",
"user = UserProxyAgent(\n",
" \"user\",\n",
" human_input_mode=\"NEVER\",\n",
" function_map={\"execute_code\": execute_code},\n",
")\n",
"\n",
"# start the conversation\n",
"chatbot.receive(\n",
" \"Draw a rocket and save to a file named 'rocket.svg'\",\n",
" user,\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "42cee331",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:ev=\"http://www.w3.org/2001/xml-events\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" baseProfile=\"tiny\" height=\"100%\" version=\"1.2\" width=\"100%\"><defs/><rect fill=\"grey\" height=\"40\" width=\"20\" x=\"50\" y=\"20\"/><polygon fill=\"red\" points=\"50,20 60,0 70,20\"/><polygon fill=\"red\" points=\"50,60 60,80 70,60\"/><circle cx=\"60\" cy=\"40\" fill=\"blue\" r=\"5\"/></svg>"
],
"text/plain": [
"<IPython.core.display.SVG object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# uncomment the following to render the svg file\n",
"# from IPython.display import SVG, display\n",
"\n",
"# display(SVG(\"coding/rocket.svg\"))"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "e9531d55",
"metadata": {},
"source": [
"## Another example with Wolfram Alpha API\n",
"\n",
"We give another example of query Wolfram Alpha API to solve math problem. We use the predefined function from `MathUserProxyAgent()`, we directly pass the class method, `MathUserProxyAgent()._execute_one_wolfram_query`, as the function to be called."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4a917492",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user (to chatbot):\n",
"\n",
"Problem: Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"***** Suggested function Call: query_wolfram *****\n",
"Arguments: \n",
"{\n",
" \"query\": \"solve (2x+10)(x+3) < (3x+9)(x+8) for x\"\n",
"}\n",
"**************************************************\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\n",
">>>>>>>> NO HUMAN INPUT RECEIVED. USING AUTO REPLY FOR THE USER...\n",
"user (to chatbot):\n",
"\n",
"***** Response from calling function \"query_wolfram\" *****\n",
"('Assumption: solve (2 x + 10) (x + 3)<(3 x + 9) (x + 8) for x \\nAnswer: ans 0: x<-14\\nans 1: x>-3\\n', True)\n",
"**********************************************************\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"The solution to the inequality $(2x+10)(x+3)<(3x+9)(x+8)$ is $x \\in (-\\infty, -14) \\cup (-3, +\\infty)$.\n",
"\n",
"To express in interval notation, the answer is $(-\\infty, -14) \\cup (-3, \\infty)$. \n",
"\n",
"TERMINATE.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\n",
">>>>>>>> NO HUMAN INPUT RECEIVED. USING AUTO REPLY FOR THE USER...\n",
"user (to chatbot):\n",
"\n",
"\n",
"\n",
"--------------------------------------------------------------------------------\n",
"chatbot (to user):\n",
"\n",
"TERMINATE\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"import os\n",
"from flaml.autogen.agent import AssistantAgent, UserProxyAgent, MathUserProxyAgent\n",
"\n",
"# you need to provide a wolfram alpha appid to run this example\n",
"if not os.environ.get(\"WOLFRAM_ALPHA_APPID\"):\n",
" os.environ[\"WOLFRAM_ALPHA_APPID\"] = open(\"wolfram.txt\").read().strip()\n",
"\n",
"\n",
"sys_prompt = \"\"\"You are an advanced AI with the capability to solve complex math problems.\n",
"Wolfram alpha is provided as an external service to help you solve math problems.\n",
"\n",
"When the user gives a math problem, please use the most efficient way to solve the problem.\n",
"You are encouraged to use Wolfram alpha whenever it is possible during the solving process. For example, simplications, calculations, equation solving, etc.\n",
"However, if the operation requires little computation (very simple calculations, etc), you can also solve it directly.\n",
"Reply \"TERMINATE\" in the end when everything is done.\n",
"\"\"\"\n",
"oai_config = {\n",
" \"model\": \"gpt-4-0613\",\n",
" \"functions\": [\n",
" {\n",
" \"name\": \"query_wolfram\",\n",
" \"description\": \"Return the API query result from the Wolfram Alpha. the ruturn is a tuple of (result, is_success).\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"query\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The Wolfram Alpha code to be executed.\",\n",
" }\n",
" },\n",
" \"required\": [\"query\"],\n",
" },\n",
" }\n",
" ],\n",
" \"function_call\": \"auto\",\n",
"}\n",
"chatbot = AssistantAgent(\"chatbot\", sys_prompt, config_list=config_list, **oai_config)\n",
"\n",
"\n",
"# the key in `function_map` should match the function name passed to OpenAI\n",
"# we pass a class instance directly\n",
"user = UserProxyAgent(\n",
" \"user\",\n",
" human_input_mode=\"NEVER\",\n",
" function_map={\"query_wolfram\": MathUserProxyAgent()._execute_one_wolfram_query},\n",
")\n",
"\n",
"# start the conversation\n",
"chatbot.receive(\n",
" \"Problem: Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n",
" user,\n",
")\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "flaml_dev",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.16"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -59,6 +59,14 @@ def test_autogen_openai_completion(save=False):
run_notebook("autogen_openai_completion.ipynb", save=save)
@pytest.mark.skipif(
skip or not sys.version.startswith("3.10"),
reason="do not run if openai is not installed or py!=3.10",
)
def test_autogen_agent_function_call(save=False):
run_notebook("autogen_agent_function_call.ipynb", save=save)
@pytest.mark.skipif(
skip or not sys.version.startswith("3.10"),
reason="do not run if openai is not installed or py!=3.10",
@ -79,3 +87,4 @@ if __name__ == "__main__":
test_autogen_chatgpt_gpt4(save=True)
test_autogen_openai_completion(save=True)
test_autogen_agent_MathChat(save=True)
test_autogen_agent_function_call(save=True)

View File

@ -24,7 +24,7 @@ We have designed different classes of Agents that are capable of communicating w
### `UserProxyAgent`
`UserProxyAgent` is an Agent class that serves as a proxy for the human user. Upon receiving a message, the UserProxyAgent will either solicit the human user's input or prepare an automatically generated reply. The chosen action depends on the settings of the `human_input_mode` and `max_consecutive_auto_reply` when the `UserProxyAgent` instance is constructed, and whether a human user input is available.
Currently, the automatically generated reply is crafted based on automatic code execution. The `UserProxyAgent` triggers code execution automatically when it detects an executable code block in the received message and no human user input is provided. We plan to add more capabilities in `UserProxyAgent` beyond code execution. One can also easily extend it by overriding the `auto_reply` function of the `UserProxyAgent` to add or modify responses to the `AssistantAgent`'s specific type of message. For example, one can easily extend it to execute function calls to external API, which is especially useful with the newly added [function calling capability of OpenAI's Chat Completions API](https://openai.com/blog/function-calling-and-other-api-updates?ref=upstract.com). This auto-reply capability allows for more autonomous user-agent communication while retaining the possibility of human intervention.
Currently, the automatically generated reply is crafted based on automatic code execution. The `UserProxyAgent` triggers code execution automatically when it detects an executable code block in the received message and no human user input is provided. We plan to add more capabilities in `UserProxyAgent` beyond code execution. One can also easily extend it by overriding the `auto_reply` function of the `UserProxyAgent` to add or modify responses to the `AssistantAgent`'s specific type of message. This auto-reply capability allows for more autonomous user-agent communication while retaining the possibility of human intervention.
Example usage of the agents to solve a task with code:
```python
@ -57,11 +57,68 @@ In the example above, we create an AssistantAgent named "assistant" to serve as
Please find a visual illustration of how UserProxyAgent and AssistantAgent collaboratively solve the above task below:
![Agent Example](images/agent_example.png)
Notes:
#### Human Input Mode
The `human_input_mode` parameter of `UserProxyAgent` controls the behavior of the agent when it receives a message. It can be set to `"NEVER"`, `"ALWAYS"`, or `"TERMINATE"`.
- Under the mode `human_input_mode="NEVER"`, the multi-turn conversation between the assistant and the user_proxy stops when the number of auto-reply reaches the upper limit specified by `max_consecutive_auto_reply` or the received message is a termination message according to `is_termination_msg`.
- When `human_input_mode` is set to `"ALWAYS"`, the user proxy agent solicits human input every time a message is received; and the conversation stops when the human input is "exit", or when the received message is a termination message and no human input is provided.
- When `human_input_mode` is set to `"TERMINATE"`, the user proxy agent solicits human input only when a termination message is received or the number of auto reply reaches `max_consecutive_auto_reply`.
#### Function Calling
To leverage [function calling capability of OpenAI's Chat Completions API](https://openai.com/blog/function-calling-and-other-api-updates?ref=upstract.com), one can pass in a list of callable functions or class methods to `UserProxyAgent`, which corresponds to the description of functions passed to OpenAI's API.
Example usage of the agents to solve a task with function calling feature:
```python
from flaml.autogen.agent import AssistantAgent, UserProxyAgent
# put the descriptions of functions in config to be passed to OpenAI's API
oai_config = {
"model": "gpt-4-0613",
"functions": [
{
"name": "execute_code",
"description": "Receive a list of python code or shell script and return the execution result.",
"parameters": {
"type": "object",
"properties": {
"code_type": {
"type": "string",
"description": "Code type, 'python' or 'sh'.",
},
"code": {
"type": "string",
"description": "Valid Python code to execute.",
}
},
"required": ["code_type", "code"],
},
}
],
"function_call": "auto",
}
# create an AssistantAgent instance named "assistant"
chatbot = AssistantAgent("assistant", config_list=config_list, **oai_config)
# define your own function. Here we use a pre-defined '_execute_code' function from a UserProxyAgent instance
# we define a wrapper function to call `exec_func`
exec_func = UserProxyAgent(name="execute_code", work_dir="coding", use_docker=False)._execute_code
def execute_code(code_type, code):
return exec_func([(code_type, code)])
# create a UserProxyAgent instance named "user", the execute_code_function is passed
user = UserProxyAgent(
"user",
human_input_mode="NEVER",
function_map={"execute_code": execute_code},
)
# start the conversation
chatbot.receive(
"Draw a rocket and save to a file named 'rocket.svg'",
user,
)
```
*Interested in trying it yourself? Please check the following notebook examples:*
* [Interactive LLM Agent with Auto Feedback from Code Execution](https://github.com/microsoft/FLAML/blob/main/notebook/autogen_agent_auto_feedback_from_code_execution.ipynb)
@ -71,6 +128,8 @@ Notes:
* [Using MathChat to Solve Math Problems](https://github.com/microsoft/FLAML/blob/main/notebook/autogen_agent_MathChat.ipynb)
* [Interactive LLM Agent with Function Calls](https://github.com/microsoft/FLAML/blob/main/notebook/autogen_agent_function_call.ipynb)
* [Multi-Agent Communication and Planning](https://github.com/microsoft/FLAML/blob/main/notebook/autogen_agent_planning.ipynb)
## Enhanced Inference