{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 工具\n\n工具是可由代理执行以完成操作的代码。一个工具可以是一个简单的函数(如计算器),也可以是对第三方服务的API调用(如股票价格查询或天气预报)。在AI代理的上下文中,工具被设计为由代理执行,以响应模型生成的函数调用。\n\nAutoGen提供了{py:mod}`autogen_core.tools`模块,其中包含一套内置工具和实用程序,用于创建和运行自定义工具。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 内置工具\n\n其中一个内置工具是{py:class}`~autogen_ext.tools.code_execution.PythonCodeExecutionTool`,它允许代理执行Python代码片段。\n\n以下是创建和使用该工具的方法。\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, world!\n", "\n" ] } ], "source": [ "from autogen_core import CancellationToken\n", "from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor\n", "from autogen_ext.tools.code_execution import PythonCodeExecutionTool\n", "\n", "# 创建工具。\n", "code_executor = DockerCommandLineCodeExecutor()\n", "await code_executor.start()\n", "code_execution_tool = PythonCodeExecutionTool(code_executor)\n", "cancellation_token = CancellationToken()\n", "\n", "# 不通过代理直接使用工具。\n", "code = \"print('Hello, world!')\"\n", "result = await code_execution_tool.run_json({\"code\": code}, cancellation_token)\n", "print(code_execution_tool.return_value_as_string(result))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "{py:class}`~autogen_ext.code_executors.docker.DockerCommandLineCodeExecutor`类是一个内置代码执行器,它在Docker容器的命令行环境中以子进程方式运行Python代码片段。\n{py:class}`~autogen_ext.tools.code_execution.PythonCodeExecutionTool`类封装了代码执行器,并提供了执行Python代码片段的简单接口。\n\n其他内置工具示例:\n- {py:class}`~autogen_ext.tools.graphrag.LocalSearchTool`和{py:class}`~autogen_ext.tools.graphrag.GlobalSearchTool`用于使用[GraphRAG](https://github.com/microsoft/graphrag)。\n- {py:class}`~autogen_ext.tools.mcp.mcp_server_tools`用于将[模型上下文协议(MCP)](https://modelcontextprotocol.io/introduction)服务器作为工具使用。\n- {py:class}`~autogen_ext.tools.http.HttpTool`用于向REST API发起HTTP请求。\n- {py:class}`~autogen_ext.tools.langchain.LangChainToolAdapter`用于使用LangChain工具。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 自定义函数工具\n\n工具也可以是一个执行特定操作的简单 Python 函数。\n要创建自定义函数工具,您只需创建一个 Python 函数\n并使用 {py:class}`~autogen_core.tools.FunctionTool` 类来包装它。\n\n{py:class}`~autogen_core.tools.FunctionTool` 类利用描述和类型注解\n来告知大语言模型何时以及如何使用给定函数。描述提供了\n关于函数用途和预期使用场景的上下文,而类型注解则告知大语言模型\n预期的参数和返回类型。\n\n例如,一个获取公司股价的简单工具可能如下所示:\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "143.83831971965762\n" ] } ], "source": [ "import random\n", "\n", "from autogen_core import CancellationToken\n", "from autogen_core.tools import FunctionTool\n", "from typing_extensions import Annotated\n", "\n", "\n", "async def get_stock_price(ticker: str, date: Annotated[str, \"Date in YYYY/MM/DD\"]) -> float:\n", " # 返回随机股价用于演示目的。\n", " return random.uniform(10, 200)\n", "\n", "\n", "# 创建函数工具。\n", "stock_price_tool = FunctionTool(get_stock_price, description=\"Get the stock price.\")\n", "\n", "# 运行工具。\n", "cancellation_token = CancellationToken()\n", "result = await stock_price_tool.run_json({\"ticker\": \"AAPL\", \"date\": \"2021/01/01\"}, cancellation_token)\n", "\n", "# 打印结果。\n", "print(stock_price_tool.return_value_as_string(result))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 通过模型客户端调用工具\n\n在 AutoGen 中,每个工具都是 {py:class}`~autogen_core.tools.BaseTool` 的子类,\n它会自动生成工具的 JSON 模式。\n例如,要获取 `stock_price_tool` 的 JSON 模式,我们可以使用\n{py:attr}`~autogen_core.tools.BaseTool.schema` 属性。\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'name': 'get_stock_price',\n", " 'description': 'Get the stock price.',\n", " 'parameters': {'type': 'object',\n", " 'properties': {'ticker': {'description': 'ticker',\n", " 'title': 'Ticker',\n", " 'type': 'string'},\n", " 'date': {'description': 'Date in YYYY/MM/DD',\n", " 'title': 'Date',\n", " 'type': 'string'}},\n", " 'required': ['ticker', 'date'],\n", " 'additionalProperties': False},\n", " 'strict': False}" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stock_price_tool.schema" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "模型客户端利用工具的 JSON 模式来生成工具调用。\n\n以下示例展示了如何将 {py:class}`~autogen_core.tools.FunctionTool` 类\n与 {py:class}`~autogen_ext.models.openai.OpenAIChatCompletionClient` 结合使用。\n其他模型客户端类的使用方式类似。详见[模型客户端](./model-clients.ipynb)\n获取更多信息。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[FunctionCall(id='call_tpJ5J1Xoxi84Sw4v0scH0qBM', arguments='{\"ticker\":\"AAPL\",\"date\":\"2021/01/01\"}', name='get_stock_price')]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import json\n", "\n", "from autogen_core.models import AssistantMessage, FunctionExecutionResult, FunctionExecutionResultMessage, UserMessage\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "# 创建 OpenAI 聊天补全客户端,使用环境变量中的 OPENAI_API_KEY\n", "model_client = OpenAIChatCompletionClient(model=\"gpt-4o-mini\")\n", "\n", "# 创建用户消息\n", "user_message = UserMessage(content=\"What is the stock price of AAPL on 2021/01/01?\", source=\"user\")\n", "\n", "# 使用上面定义的 stock_price_tool 运行聊天补全\n", "cancellation_token = CancellationToken()\n", "create_result = await model_client.create(\n", " messages=[user_message], tools=[stock_price_tool], cancellation_token=cancellation_token\n", ")\n", "create_result.content" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "调用 `{py:class}`~autogen_ext.models.openai.BaseOpenAIChatCompletionClient.create` 方法时,底层实际发生了什么?模型客户端会获取工具列表并为每个工具的参数生成 JSON 模式。然后,它会向模型 API 发起请求,携带工具的 JSON 模式和其他消息以获取结果。\n\n许多模型(如 OpenAI 的 GPT-4o 和 Llama-3.2)经过训练后,能够生成符合工具 JSON 模式的结构化 JSON 字符串形式的工具调用。AutoGen 的模型客户端随后会解析模型的响应,并从 JSON 字符串中提取工具调用。\n\n最终结果是生成一个 `{py:class}`~autogen_core.FunctionCall` 对象列表,这些对象可用于运行对应的工具。\n\n我们使用 `json.loads` 将 `{py:class}`~autogen_core.FunctionCall.arguments` 字段中的 JSON 字符串解析为 Python 字典。`{py:meth}`~autogen_core.tools.BaseTool.run_json` 方法会接收该字典并使用提供的参数运行工具。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'32.381250753393104'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "assert isinstance(create_result.content, list)\n", "arguments = json.loads(create_result.content[0].arguments) # type: ignore\n", "tool_result = await stock_price_tool.run_json(arguments, cancellation_token)\n", "tool_result_str = stock_price_tool.return_value_as_string(tool_result)\n", "tool_result_str" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "现在你可以发起另一个模型客户端调用,让模型对工具执行结果进行反思。\n\n工具调用的结果会被包装在 `{py:class}`~autogen_core.models.FunctionExecutionResult` 对象中,该对象包含工具执行结果和被调用工具的 ID。模型客户端可以利用这些信息生成对工具执行结果的反思。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The stock price of AAPL (Apple Inc.) on January 1, 2021, was approximately $32.38.\n" ] } ], "source": [ "# 创建函数执行结果\n", "exec_result = FunctionExecutionResult(\n", " call_id=create_result.content[0].id, # type: ignore\n", " content=tool_result_str,\n", " is_error=False,\n", " name=stock_price_tool.name,\n", ")\n", "\n", "# 使用历史记录和函数执行结果消息进行另一次聊天完成\n", "messages = [\n", " user_message,\n", " AssistantMessage(content=create_result.content, source=\"assistant\"), # 带有工具调用的助手消息\n", " FunctionExecutionResultMessage(content=[exec_result]), # 函数执行结果消息\n", "]\n", "create_result = await model_client.create(messages=messages, cancellation_token=cancellation_token) # type: ignore\n", "print(create_result.content)\n", "await model_client.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 配备工具的智能体\n\n将模型客户端与工具结合起来,您可以创建一个配备工具的智能体\n它能够使用工具执行操作,并反思这些操作的结果。\n\n```{note}\n核心API设计为最小化,您需要围绕模型客户端和工具构建自己的智能体逻辑。\n关于可以使用工具的\"预构建\"智能体,请参考[AgentChat API](../../agentchat-user-guide/index.md)。\n```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "import json\n", "from dataclasses import dataclass\n", "from typing import List\n", "\n", "from autogen_core import (\n", " AgentId,\n", " FunctionCall,\n", " MessageContext,\n", " RoutedAgent,\n", " SingleThreadedAgentRuntime,\n", " message_handler,\n", ")\n", "from autogen_core.models import (\n", " ChatCompletionClient,\n", " LLMMessage,\n", " SystemMessage,\n", " UserMessage,\n", ")\n", "from autogen_core.tools import FunctionTool, Tool\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "\n", "@dataclass\n", "class Message:\n", " content: str\n", "\n", "\n", "class ToolUseAgent(RoutedAgent):\n", " def __init__(self, model_client: ChatCompletionClient, tool_schema: List[Tool]) -> None:\n", " super().__init__(\"An agent with tools\")\n", " self._system_messages: List[LLMMessage] = [SystemMessage(content=\"You are a helpful AI assistant.\")]\n", " self._model_client = model_client\n", " self._tools = tool_schema\n", "\n", " @message_handler\n", " async def handle_user_message(self, message: Message, ctx: MessageContext) -> Message:\n", " # 创建消息会话\n", " session: List[LLMMessage] = self._system_messages + [UserMessage(content=message.content, source=\"user\")]\n", "\n", " # 使用工具运行聊天补全\n", " create_result = await self._model_client.create(\n", " messages=session,\n", " tools=self._tools,\n", " cancellation_token=ctx.cancellation_token,\n", " )\n", "\n", " # 如果没有工具调用,返回结果\n", " if isinstance(create_result.content, str):\n", " return Message(content=create_result.content)\n", " assert isinstance(create_result.content, list) and all(\n", " isinstance(call, FunctionCall) for call in create_result.content\n", " )\n", "\n", " # 将首个模型创建结果添加到会话中\n", " session.append(AssistantMessage(content=create_result.content, source=\"assistant\"))\n", "\n", " # 执行工具调用\n", " results = await asyncio.gather(\n", " *[self._execute_tool_call(call, ctx.cancellation_token) for call in create_result.content]\n", " )\n", "\n", " # 将函数执行结果添加到会话中。\n", " session.append(FunctionExecutionResultMessage(content=results))\n", "\n", " # 再次运行聊天补全以反映历史记录和函数执行结果。\n", " create_result = await self._model_client.create(\n", " messages=session,\n", " cancellation_token=ctx.cancellation_token,\n", " )\n", " assert isinstance(create_result.content, str)\n", "\n", " # 将结果作为消息返回。\n", " return Message(content=create_result.content)\n", "\n", " async def _execute_tool_call(\n", " self, call: FunctionCall, cancellation_token: CancellationToken\n", " ) -> FunctionExecutionResult:\n", " # 按名称查找工具。\n", " tool = next((tool for tool in self._tools if tool.name == call.name), None)\n", " assert tool is not None\n", "\n", " # 运行工具并捕获结果。\n", " try:\n", " arguments = json.loads(call.arguments)\n", " result = await tool.run_json(arguments, cancellation_token)\n", " return FunctionExecutionResult(\n", " call_id=call.id, content=tool.return_value_as_string(result), is_error=False, name=tool.name\n", " )\n", " except Exception as e:\n", " return FunctionExecutionResult(call_id=call.id, content=str(e), is_error=True, name=tool.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在处理用户消息时,`ToolUseAgent` 类首先使用模型客户端\n生成一系列工具函数调用,然后运行这些工具\n并对工具执行结果进行反思。\n随后将反思结果作为代理的响应返回给用户。\n\n要运行该代理,我们需要创建一个运行时环境并将代理注册到运行时中。\n" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AgentType(type='tool_use_agent')" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 创建模型客户端。\n", "model_client = OpenAIChatCompletionClient(model=\"gpt-4o-mini\")\n", "# 创建运行时环境。\n", "runtime = SingleThreadedAgentRuntime()\n", "# 创建工具集。\n", "tools: List[Tool] = [FunctionTool(get_stock_price, description=\"Get the stock price.\")]\n", "# 注册代理程序。\n", "await ToolUseAgent.register(\n", " runtime,\n", " \"tool_use_agent\",\n", " lambda: ToolUseAgent(\n", " model_client=model_client,\n", " tool_schema=tools,\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "本示例使用 {py:class}`~autogen_ext.models.openai.OpenAIChatCompletionClient`,\n关于Azure OpenAI和其他客户端,请参阅[模型客户端](./model-clients.ipynb)。\n让我们用一个关于股票价格的问题来测试这个智能体。\n" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The stock price of NVIDIA (NVDA) on June 1, 2024, was approximately $140.05.\n" ] } ], "source": [ "# 开始处理消息。\n", "runtime.start()\n", "# 向工具智能体发送直接消息。\n", "tool_use_agent = AgentId(\"tool_use_agent\", \"default\")\n", "response = await runtime.send_message(Message(\"What is the stock price of NVDA on 2024/06/01?\"), tool_use_agent)\n", "print(response.content)\n", "# 停止处理消息。\n", "await runtime.stop()\n", "await model_client.close()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 2 }