虽然基础模型(Foundation Models)非常擅长进行长时间的聊天,但工具才是赋予 AI 智能体(AI Agents)能力的基石,使其能够检索额外信息和上下文、执行任务,并以有意义的方式与环境进行交互。在 AI 的语境下,工具可以定义为智能体为达到预期结果而执行的一项特定能力或一组动作。这些工具的范围很广,从简单的单步任务,到需要高级推理和解决问题能力的复杂多步操作。特别是如果希望智能体能够产生实际的变更,而不仅仅是搜索和提供信息,那么工具就是执行这些变更的方式。
工具在 AI 智能体中的重要性,类似于专业技能对人类专家的重要性。就像医生需要一套多样化的工具来诊断和治疗病人一样,AI 智能体也需要一套工具库来有效地处理各种任务。本章旨在全面介绍 AI 智能体中的工具,探讨它们的设计、开发和部署。
AI 智能体的核心是旨在与环境交互、处理信息并自主执行任务的复杂系统。为了高效地做到这一点,它们依赖于一套结构化的工具。这些工具是模块化的组件,可以独立地进行开发、测试和优化,然后集成在一起,形成一个能够执行复杂操作的聚合系统。
从现实角度来看,工具既可以是简单的图像物体识别,也可以是复杂的从初步接触到最终解决的客户支持工单管理。这些工具的设计和实现对于 AI 智能体的整体功能和有效性至关重要。我们将从 LangChain 的一些基础知识开始,然后涵盖可以提供给自主智能体的不同类型的工具,我们将按顺序介绍:本地工具(Local Tools)、基于 API 的工具(API-based Tools)和 MCP 工具。
本书代码请见:https://github.com/alanhou/ai-agent。
LangChain 基础 (LangChain Fundamentals)
在深入探讨工具选择和编排之前,了解一些 LangChain 的核心概念会有所帮助。LangChain 的核心是基础模型和聊天模型,它们处理提示词(Prompts)并生成响应。例如,ChatOpenAI 是一个包装类,提供了一个简单的接口来与 OpenAI 的基于聊天的模型(如 GPT-5)进行交互。可以使用模型名称等参数对其进行初始化,以指定要使用的模型:
|
1 2 |
from langchain_openai import ChatOpenAI llm = ChatOpenAI(model_name="gpt-4o") |
LangChain 将交互构建为消息,以维护对话上下文。两种主要的消息类型是
HumanMessage(代表用户输入)和 AIMessage(代表模型的响应):|
1 2 |
from langchain_core.messages import HumanMessage messages = [HumanMessage("What is the weather today?")] |
与此同时,工具是你的模型可以调用以扩展其文本生成以外能力的外部函数——例如,调用 API、检索数据库条目或执行计算。在 LangChain 中,你使用 @tool 装饰器定义工具,它会注册该函数并自动生成描述其输入和输出的模式(Schema):
|
1 2 3 4 5 6 |
from langchain_core.tools import tool @tool def add_numbers(x: int, y: int) -> int: """Adds two numbers and returns the sum.(将两个数字相加并返回和)""" return x + y |
定义好工具后,需要使用 .bind_tools() 将它们绑定到模型,这使得模型能够根据用户输入选择并调用这些工具。使用 .invoke() 方法与模型交互,并向其提供代表当前对话的消息列表。如果模型决定调用某个工具,它将输出一个工具调用(Tool Call),然后通过调用相应的函数并将结果追加回对话中来执行该调用,最后生成最终响应:
|
1 2 3 4 |
llm_with_tools = llm.bind_tools([add_numbers]) ai_msg = llm_with_tools.invoke(messages) for tool_call in ai_msg.tool_calls: tool_response = add_numbers.invoke(tool_call) |
这些积木——聊天模型、消息、工具和工具调用——构成了基于 LangChain 的系统的基础。理解它们如何组合在一起,将有助于理解本章中的示例,并构建能够将语言理解与现实世界行动无缝集成的智能体。
本地工具 (Local Tools)
这些工具被设计为在本地运行。它们通常基于预定义的规则和逻辑,针对特定任务量身定制。这些本地工具易于构建和修改,并与智能体共同部署。它们可以弥补语言模型的弱点,而在这些方面传统编程技术表现更好,例如算术、时区转换、日历操作或地图交互。这些本地工具提供精确性、可预测性和简单性。由于逻辑是显式定义的,本地工具往往是可预测且可靠的。
元数据——工具的名称、描述和模式——与其逻辑一样关键。模型使用该元数据来决定调用哪个工具。因此,以下几点很重要:
-
选择精确、范围狭窄的名称。 如果名称太宽泛,LLM 可能会在不需要时调用它。
-
编写清晰、独特的描述。 多个工具之间描述过于宽泛或重叠,肯定会导致混淆和性能不佳。
-
定义严格的输入/输出模式。 显式的模式有助于基础模型确切地理解何时以及如何使用该工具,从而减少误操作。
尽管有这些好处,本地工具也有一些重要的缺点:
-
可扩展性 (Scalability): 设计、构建和部署本地工具可能既繁琐又耗时,且极具挑战性,而且本地工具很难在不同的用例之间共享。虽然工具可以作为库公开并在多个智能体用例中共享,但在实践和规模化操作中这具有挑战性。
-
重复性 (Duplication): 每个想要使用本地工具的团队或智能体部署都需要部署相同的库及其智能体服务,并且推送这些工具的更改需要协调部署到使用这些工具的每个智能体服务。实际上,许多团队只是独立地重新实现相同的工具,以避免协调开销。
-
维护 (Maintenance): 随着环境或需求的变化,手工制作的工具可能需要频繁的更新和调整。这种持续的维护可能是资源密集型的,通常需要重新部署你的智能体服务。
尽管有这些缺点,手工制作的工具在解决基础模型的传统弱点方面特别有用。简单的数学运算就是一个很好的例子。单位转换、计算器操作、日历更改、日期和时间操作以及地图和图形操作,都是手工制作的工具可以显著提高智能体系统效率的领域。
让我们看一个注册计算器工具的例子。首先,我们定义简单的计算器函数:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from langchain_core.runnables import ConfigurableField from langchain_core.tools import tool from langchain_openai import ChatOpenAI # 使用简洁的函数定义来定义工具 @tool def multiply(x: float, y: float) -> float: """Multiply 'x' times 'y'. (计算 'x' 乘以 'y')""" return x * y @tool def exponentiate(x: float, y: float) -> float: """Raise 'x' to the 'y'. (计算 'x' 的 'y' 次幂)""" return x**y @tool def add(x: float, y: float) -> float: """Add 'x' and 'y'. (计算 'x' 加 'y')""" return x + y |
LangChain 通过
@tool装饰器实现了工具定义的声明式封装,将业务逻辑与提示词(docstring)通过元编程无缝结合。由于 Go 语言不支持 Python 式的装饰器语法糖及动态类型推导,Eino 在设计上有所不同,但它通过utils.InferTool这一工具函数,利用反射(Reflection)技术弥补了语言特性的差异,实现了同等效能的工具Schema推断与绑定。
1 multiplyTool, _ := utils.InferTool("multiply", "Multiply x * y (计算 x 乘以 y)", multiply)
然后,我们将工具与 LangChain 中的基础模型绑定:
|
1 2 3 4 |
tools = [multiply, exponentiate, add] # 使用 GPT-4o 初始化 LLM 并绑定工具 llm = ChatOpenAI(model_name="gpt-4o", temperature=0) llm_with_tools = llm.bind_tools(tools) |
这种“绑定”操作注册了工具。在底层,LangChain 现在将检查基础模型的响应是否包含任何调用工具的请求。现在我们绑定了工具,我们可以向基础模型提问,如果工具对回答问题有帮助,基础模型将选择工具,为这些工具选择参数,并调用这些函数:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
query = "What is 393 * 12.25? Also, what is 11 + 49?" messages = [HumanMessage(query)] ai_msg = llm_with_tools.invoke(messages) messages.append(ai_msg) for tool_call in ai_msg.tool_calls: selected_tool = {"add": add, "multiply": multiply, "exponentiate": exponentiate}[tool_call["name"].lower()] tool_msg = selected_tool.invoke(tool_call) print(f"{tool_msg.name} {tool_call['args']} {tool_msg.content}") messages.append(tool_msg) final_response = llm_with_tools.invoke(messages) print(final_response.content) |
通过添加打印语句以提升可见性,可以看到基础模型调用了两个函数——multiply 和 add 各一次:
|
1 2 |
multiply {'x': 393, 'y': 12.25} Result: 4814.25 add {'x': 11, 'y': 49} 60.0 |
然后,模型会将工具调用的结果包含在生成的最终响应中,产生如下结果:
393 times 12.25 is 4814.25, and 11 + 49 is 60.
虽然这种效果看起来很简单,但其意义深远。基础模型现在能够执行我们与其绑定的计算机程序。这是一个简单的例子,但我们可以将任意有用且重要的程序绑定到基础模型,并且我们现在依赖基础模型来选择执行哪些程序以及使用哪些参数。只绑定那些基础模型以利大于弊的方式执行的工具,是构建智能体和智能体系统的开发者的首要责任。
基于 API 的工具 (API-Based Tools)
基于 API 的工具使自主智能体能够与外部服务交互,通过访问额外信息、处理数据和执行本地无法完成的操作来增强其能力。这些工具利用应用程序编程接口 (API) 与公共或私有服务进行通信,提供了一种动态且可扩展的方式来扩展智能体的功能。
基于 API 的工具在智能体需要与各种外部系统集成、检索实时数据或执行过于消耗资源而无法在内部处理的复杂计算的场景中特别有价值。通过连接到 API,智能体可以访问大量服务,例如天气信息、股票市场数据、翻译服务等,使其能够对用户查询提供更丰富、更准确的响应。这些基于 API 的工具有多种好处。
通过利用外部服务,这些工具可以极大地扩展智能体可以执行的任务范围。例如,智能体可以使用天气 API 提供当前天气状况和预报,使用金融 API 获取股票价格,或使用翻译 API 提供多语言支持。这种集成多样化外部服务的能力极大地拓宽了智能体的功能,而无需重新训练模型。
实时数据访问是基于 API 的工具的另一个主要好处。API 使智能体能够从外部来源访问最新信息,确保其响应和行动基于最新数据。这对于依赖及时准确信息的应用程序至关重要,例如金融交易或应急响应系统,在这些系统中,必须根据最新的可用数据快速做出决策。
为了说明基于 API 的工具的实现,让我们从让你的智能体浏览开放网络以获取更多信息开始。在这个代码片段中,我们注册了一个从维基百科检索信息的工具,这是迈向全功能网页浏览智能体的一步:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from langchain_openai import ChatOpenAI from langchain_community.tools import WikipediaQueryRun from langchain_community.utilities import WikipediaAPIWrapper from langchain_core.messages import HumanMessage api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=300) tool = WikipediaQueryRun(api_wrapper=api_wrapper) # 使用 GPT-4o 初始化 LLM 并绑定工具 llm = ChatOpenAI(model_name="gpt-4o", temperature=0) llm_with_tools = llm.bind_tools([tool]) messages = [HumanMessage("What was the most impressive thing" + "about Buzz Aldrin?")] ai_msg = llm_with_tools.invoke(messages) messages.append(ai_msg) for tool_call in ai_msg.tool_calls: tool_msg = tool.invoke(tool_call) print(tool_msg.name) print(tool_call['args']) print(tool_msg.content) messages.append(tool_msg) print() final_response = llm_with_tools.invoke(messages) print(final_response.content) |
基础模型识别查询中的感兴趣对象,并在维基百科中搜索该术语。然后,它在回答问题时使用这些额外信息生成最终答案:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{'query': 'Buzz Aldrin'} Page: Buzz Aldrin Summary: Buzz Aldrin (born Edwin Eugene Aldrin Jr. January 20, 1930) is an American former astronaut, engineer and fighter pilot. He made three spacewalks as pilot of the 1966 Gemini 12 mission, and was the Lunar Module Eagle pilot on the 1969 Apollo 11 mission. One of the most impressive things about Buzz Aldrin is that he was the Lunar Module Eagle pilot on the 1969 Apollo 11 mission, making him one of the first two humans to land on the Moon. This historic event marked a significant achievement in space exploration and human history. Additionally, Aldrin made three spacewalks as pilot of the 1966 Gemini 12 mission, showcasing his tools and contributions to advancing space travel. |
现在让我们看第二个例子,一个旨在获取和显示股票市场数据的智能体。这个过程包括定义 API 交互、处理响应以及将工具集成到智能体的工作流中。通过遵循这种方法,智能体可以无缝集成外部数据源,从而提高其整体功能和有效性。
首先,我们定义与股票市场 API 交互的函数。然后,我们将此函数注册为智能体的工具,之后我们就可以像以前的工具一样调用它:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain_community.tools import WikipediaQueryRun from langchain_community.utilities import WikipediaAPIWrapper from langchain_core.messages import HumanMessage import requests @tool def get_stock_price(ticker: str) -> float: """Get the stock price for the stock exchange ticker for the company. (获取公司股票交易所代码的股票价格)""" api_url = f"https://api.example.com/stocks/{ticker}" response = requests.get(api_url) if response.status_code == 200: data = response.json() return data["price"] else: raise ValueError(f"Failed to fetch stock price for {ticker}") # 使用 GPT-4o 初始化 LLM 并绑定工具 llm = ChatOpenAI(model_name="gpt-4o", temperature=0) llm_with_tools = llm.bind_tools([get_stock_price]) messages = [HumanMessage("What is the stock price of Apple?")] ai_msg = llm_with_tools.invoke(messages) messages.append(ai_msg) for tool_call in ai_msg.tool_calls: tool_msg = get_stock_price.invoke(tool_call) print(tool_msg.name) print(tool_call['args']) print(tool_msg.content) messages.append(tool_msg) print() final_response = llm_with_tools.invoke(messages) print(final_response.content) |
可以创建类似的工具来搜索团队或公司特定的信息。通过为你的智能体提供访问处理任务所需信息所必需的工具,以及对这些信息进行操作的特定工具,你可以显著扩展可以自动化的任务的范围和复杂性。
在为智能体设计 API 工具时,重点关注可靠性、安全性和故障的优雅处理。外部服务可能会停机,因此智能体需要后备方案或清晰的错误消息。使用 HTTPS 和强身份验证来保护所有通信,特别是对于敏感数据。
注意 API 速率限制以避免中断,并确保遵守数据隐私法律——在需要时匿名化或混淆用户数据。稳健地处理错误,以便智能体可以从网络问题或无效响应中恢复,而不会破坏用户体验。如果可能,考虑替代方案和多个提供商,以便在任何给定提供商降级时获得更高的可靠性。
API 赋予智能体实时数据、繁重的计算以及它们无法单独执行的外部操作,使它们更加能干和有效。
插件工具 (Plug-In Tools)
这些工具是模块化的,只需极少的自定义即可集成到 AI 智能体的框架中。它们利用现有的库、API 和第三方服务来扩展智能体的能力,而无需大量的开发工作。插件工具能够快速部署和扩展智能体的功能。这些工具是预先设计的模块,可以毫不费力地集成到 AI 系统中,利用现有的库、API 和第三方服务。插件工具的集成已成为 OpenAI、Anthropic 的 Claude、Google 的 Gemini 和 Microsoft 的 Phi 等领先平台以及不断增长的开源社区的标准产品。插件工具提供了强大的工具来扩展 AI 智能体的能力,而无需大量的定制开发。
-
OpenAI 的插件生态系统提供了强大的扩展功能——从实时网络搜索到专门的代码生成器——但它们仅在 ChatGPT 产品内部可用,而不是公共 API。你不能通过标准的 OpenAI Completions 或 Chat 端点调用 Expedia、Zapier 或任何第一方 ChatGPT 插件。要在自己的应用程序中复制类似的行为,你必须构建自定义的函数调用层(例如,通过 LangChain)来近似插件功能。
-
Anthropic 的 Claude 相比之下,直接通过 Anthropic Messages API(以及 Amazon Bedrock 或 Google Cloud Vertex AI 等平台)公开其完整的“工具使用”能力。只需注册自定义工具(或使用 Anthropic 提供的工具),Claude 就可以在推理时调用它们——不需要单独的 UI。这种 API 优先的方法使得将内容审核、偏差检测或特定领域的服务集成到任何由 Claude 驱动的工作流中变得直观。
-
Google 的 Gemini 模型支持通过 Vertex AI API 进行函数调用,通过在
FunctionCallingConfig中声明工具,并让 Gemini 将它们作为结构化调用来调用。无论是需要自然语言理解、图像识别还是数据库查找,都可以预先定义函数并在代码中处理返回的参数——没有专有的 UI 层阻隔在应用程序和模型之间。 -
Microsoft 的 Phi 模型通过 Azure AI Foundry 提供,它们与 Azure 的其他服务(如认知搜索、文档处理和数据可视化 API)无缝集成——通过你用于其他 Azure AI 模型的相同公共端点。虽然没有标记为“插件”,但 Phi 与 Azure 生产力和分析工具的紧密结合提供了类似的流畅体验:你调用模型,接收结构化输出,并将它们直接输入到现有的 Azure 工作流中,而无需切换上下文。
插件工具的一个显著优势是它们在模型执行层的集成。这意味着可以将这些工具添加到 AI 模型中,而对现有工作流的干扰最小。开发人员可以简单地将这些模块插入到他们的 AI 系统中,从而立即增强其能力,而无需大量的自定义或开发工作。这种集成的易用性使插件工具成为在 AI 应用程序中快速部署新功能的有吸引力的选择。然而,这种易用性也带来了一定的局限性。虽然插件工具功能强大,但它们提供的可定制性和适应性不如可以在本地或远程服务的自定义开发工具。它们被设计为通用工具,可以解决广泛的任务,但可能无法针对每个应用程序的特定需求和细微差别进行定制。这种集成易用性与可定制性之间的权衡是开发人员在选择插件工具还是定制开发时的重要考虑因素。
尽管目前存在局限性,但领先平台提供的插件工具目录正在迅速增长。随着这些目录的扩展,通过插件工具可用的功能广度将增加,为开发人员提供更多工具来增强其 AI 智能体。这种增长是由 AI 研究的不断进步以及新技术和新技术的开发推动的。在不久的将来,我们可以预期这些插件工具目录将包含更专业和更高级的功能,以满足更广泛的应用程序和行业的需求。这种扩展将通过为开发人员提供现成的工具来解决复杂多样的任务,从而促进智能体的开发。不断增长的插件工具生态系统将使 AI 智能体能够执行越来越复杂的功能,使其在各个领域更加通用和有效。
除了主要平台的产品外,还有一个快速增长的工具生态系统,可以整合到开源基础模型中。该生态系统为希望通过高级功能增强其 AI 智能体的开发人员提供了丰富的资源。开源社区积极致力于插件工具的开发,创造了一个促进创新和知识共享的协作环境。一个著名的例子是 Hugging Face Transformers 库,它为自然语言处理任务提供了广泛的预训练模型和插件工具。这些工具可以轻松集成到开源基础模型中,实现文本生成、情感分析和语言翻译等功能。该库的开源性质使开发人员能够自定义和扩展这些工具以满足其特定需求。这些框架的灵活性意味着开发人员可以将插件工具与自定义开发相结合,创建强大且适应性强的 AI 系统。在研究人员、开发人员和爱好者的共同努力下,开源 AI 社区不断贡献新的插件工具和增强功能。像 Glama.ai 和 mcp.so 这样的平台聚合了大量的 MCP 服务,使它们可搜索和可发现,范围从简单的实用程序到复杂的有状态服务。这些贡献丰富了生态系统,并为希望利用 AI 最新进展的开发人员提供了宝贵的资源。
插件工具的实际应用非常广泛和多样,涵盖了多个行业和用例。通过集成插件工具,开发人员可以创建高效执行各种任务的 AI 智能体。在客户支持中,插件工具可以使 AI 智能体处理查询、提供解决方案并管理支持工单。像自然语言理解和情感分析这样的工具可以帮助 AI 智能体理解客户问题并做出适当的响应,从而提高客户满意度并缩短响应时间。在医疗保健领域,插件工具可以协助 AI 智能体执行医学图像分析、患者分诊和数据管理等任务。利用计算机视觉的工具可以帮助识别医学图像中的异常,而自然语言处理工具可以协助管理患者记录并从医学文献中提取相关信息,向量搜索工具可以提供相关文档的基础以解决当前的查询。在金融行业,插件工具可以增强 AI 智能体分析市场趋势、检测欺诈活动和管理投资组合的能力。像异常检测和预测分析这样的工具可以提供有价值的见解并改进决策过程。在教育领域,插件工具可以支持 AI 智能体进行个性化学习、自动评分和内容推荐。
AI 开发中插件工具的未来看起来很有希望,随着不断的进步和各行各业的日益普及。随着插件工具功能的扩展,我们可以预期 AI 智能体将变得更加能干和多才多艺。领先平台和开源社区正在进行的研究和开发工作将推动创新,从而为 AI 开发带来更强大、更复杂的工具。未来的一个重点领域是插件工具的互操作性和标准化。为插件工具建立通用标准和协议将促进不同 AI 平台和系统之间的无缝集成和互操作性。这将使开发人员能够利用来自各种来源的插件工具,创建更灵活和适应性更强的 AI 解决方案。人们还在努力增强插件工具的定制化和适应性。未来的插件工具可能会提供更多可配置选项,使开发人员能够根据特定用例和要求对其进行定制。这将弥合集成易用性与定制解决方案需求之间的差距,提供两全其美的方案。
模型上下文协议 (Model Context Protocol, MCP)
随着 AI 生态系统的成熟,智能体不再生活在孤岛中。它们需要从云存储读取文档,将数据推送到业务应用程序,调用内部 API,并与其他智能体协调。自定义集成——即为每个数据源或服务编写定制的适配器——是脆弱且难以扩展的。这就引入了模型上下文协议 (MCP):这是一个由 Anthropic 推出(并已被 OpenAI、Google DeepMind 和 Microsoft 等主要参与者采用)的开放标准,它提供了一种统一的、与模型无关的方式将 LLM 连接到外部系统。可以将 MCP 视为“AI 的 USB-C 接口”——任何数据源或工具都可以公开这单一的、定义明确的接口,任何智能体都可以使用它,而无需专门的胶水代码。从核心上看,MCP 定义了两个角色:
- MCP 服务端 (MCP Server)这是一个通过标准化的 JSON-RPC 2.0 接口公开数据或服务的 Web 服务器。服务端可以包装任何东西——云对象存储、SQL 数据库、企业客户关系管理、专有业务逻辑——只要它实现了 MCP 规范。
- MCP 客户端 (MCP Client)这是任何“讲” MCP 语言的智能体或 LLM 应用程序。客户端发送 JSON-RPC 请求(例如,“列出此 Salesforce 文件夹中的所有文件”或“使用 customerId=1234 执行函数 ‘getCustomerBalance’”)并接收结构化的 JSON 响应。由于协议是统一的,智能体开发人员不需要知道服务器的内部结构——只需要知道它公开的方法。
在底层,MCP 使用基于 HTTPS 或 WebSocket 的 JSON-RPC 2.0。服务端公开其可用的方法(例如,listFiles, getRecord, runAnalysis)及其输入/输出模式。客户端获取服务器的“方法目录”,允许 LLM 推理调用哪个方法以及使用什么参数。一旦选择了工具调用,MCP 客户端将该调用包装成 JSON-RPC 有效负载,将其发送到相应的服务器,并等待响应。因为两端都讲同一种语言,构建跨平台互操作性变得直观。
在 MCP 之前,开发人员为每个目标系统编写自定义适配器——直接在智能体代码中硬编码 REST 调用或 SDK 使用。随着数据源数量的增加,这些定制集成成倍增加,导致代码脆弱、容易出错,且难以维护或扩展。
尽管有这些优势,但人们提出了一些安全问题,并且尚未完全解决——特别是围绕身份验证、访问控制以及多个智能体共享 MCP 端点时的潜在攻击媒介。确保只有授权的智能体调用特定方法,维护对敏感数据的基于角色的访问控制,防止恶意负载注入,以及维护审计日志,仍然是研究和工程的活跃领域。一些组织仍然依赖额外的网络策略或代理层来减轻这些风险,但核心 MCP 规范尚未强制要求单一的标准化安全解决方案。然而,MCP 解决了跨多个智能体复用工具的关键挑战:一旦通过 MCP 公开服务,任意数量的智能体都可以发现并调用其方法,而无需为每个智能体重写自定义适配器。这大大减少了开发工作量,并鼓励模块化、可重用的架构。
为了看到 MCP 的实际应用,我们将通过一个包含以下内容的自包含 Python 示例:
-
启动一个本地“算术” MCP 服务器(通过子进程)。
-
连接到一个运行在
localhost:8000/mcp上的远程“天气” MCP 服务器。 -
实现一个异步智能体循环,检查用户的最后一条消息,并决定是调用“数学”工具(用于算术表达式)还是“天气”工具(用于天气查询)。
-
演示智能体如何解析工具的输出并返回最终的助手响应。
以下是演示这些步骤的完整 Python 实现:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
class AgentState(TypedDict): messages: Sequence[Any] # BaseMessage/HumanMessage/... 的列表 mcp_client = MultiServerMCPClient( { "math": { "command": "python3", "args": ["src/common/mcp/MCP_weather_server.py"], "transport": "stdio", # 子进程 -> STDIO JSON-RPC }, "weather": { # 假设一个单独的 MCP 服务器已经在端口 8000 上运行 "url": "http://localhost:8000/mcp", "transport": "streamable_http", # HTTP -> JSON-RPC over WebSocket/stream }, }) async def get_mcp_tools() -> list[Tool]: return await mcp_client.get_tools() async def call_mcp_tools(state: AgentState) -> dict[str, Any]: messages = state["messages"] last_msg = messages[-1].content.lower() # 在第一次调用时获取并缓存 MCP 工具 global MCP_TOOLS if "MCP_TOOLS" not in globals(): MCP_TOOLS = await mcp_client.get_tools() # 简单的启发式方法:如果出现任何数字运算符标记,选择 "math" if any(token in last_msg for token in ["+", "-", "*", "/", "(", ")"]): tool_name = "math" elif "weather" in last_msg: tool_name = "weather" else: # 无匹配 -> 直接响应 return { "messages": [ { "role": "assistant", "Sorry, I can only answer math" + " or weather queries." } ] } tool_obj = next(t for t in MCP_TOOLS if t.name == tool_name) user_input = messages[-1].content mcp_result: str = await tool_obj.arun(user_input) return { "messages": [ {"role": "assistant", "content": mcp_result} ] } |
-
"math"条目使用command+args衍生一个运行MCP_weather_server.py的子进程。在底层,此脚本必须符合 MCP(即,通过 STDIO 提供 JSON-RPC 服务)。 -
"weather"条目指向已经在http://localhost:8000/mcp运行的 HTTP MCP 服务器。streamable_http传输允许通过 HTTP/WebSocket 进行双工 JSON-RPC 通信。我们的测试代码可以在项目根目录中执行python python/src/common/mcp/MCP_weather_server.py来启动服务端(Go语言版本则执行go run go/cmd/mcp_servers/weather/main.go)。
MCP 代表了我们在规模化设计、部署和维护 AI 智能体方式上的重大进步。通过定义一个单一的、标准化的 JSON-RPC 接口来公开和使用方法,MCP 将服务实现与智能体逻辑解耦,使任意数量的智能体能够重用相同的工具,而无需定制集成。在实践中,这意味着随着新数据源、微服务或遗留系统的出现,开发人员只需实现一次符合 MCP 的服务器——任何具备 MCP 能力的智能体都可以立即发现并调用其方法。
虽然强大的身份验证、细粒度的访问控制和负载验证等安全问题仍然是发展的活跃领域,但 MCP 的核心承诺——无缝互操作性和模块化工具重用——已经在领先组织的生产系统中实现。展望未来,我们预计 MCP 的安全最佳实践将不断完善,标准化方法目录将被更广泛地采用,以及公共和私有 MCP 端点生态系统将不断增长。总而言之,MCP 解决了智能体系统设计中最持久的挑战之一——如何快速可靠地集成各种服务——同时为更加灵活、可维护和分布式的 AI 架构奠定了基础。
有状态工具 (Stateful Tools)
有状态工具涵盖了本地脚本、外部 API 和部署了 MCP 的服务,但它们都有一个共同的风险:当你赋予基础模型对持久状态的直接控制权时,也赋予了它犯下破坏性错误或被坏人利用的能力。在一个真实案例中,AI 智能体通过删除生产表中的一半数据来“优化”数据库性能,在此过程中删除了关键记录。即使没有恶意,基础模型也可能误解用户的意图,将本应无害的查询变成破坏性的命令。对于有状态工具来说,这种风险尤为严重,因为它们与内容随时间变化的实时数据存储进行交互。
为了缓解这些危险,只将限定范围的操作注册为工具,而不是公开“执行任意 SQL”的端点。例如,定义一个 get_user_profile(user_id) 工具或 add_new_customer(record) 工具,每个工具都封装了一个单一的、经过充分测试的查询或过程。只需要读取访问权限的智能体绝不应获得删除或修改数据的权利。通过在注册层限制工具功能,你可以大幅减少攻击面并限制潜在错误的范围。
如果你的用例绝对需要自由形式的查询,必须实施严格的清理和访问控制。OWASP 的 GenAI Security Project 警告说,提示注入(Prompt Injections)可能会将危险的子句(如 DROP 或 ALTER)塞入原本良性的请求中,因此输入验证必须拒绝任何包含这些模式的语句。始终绑定参数或使用预处理语句以防止 SQL 注入,并确保智能体使用的数据库帐户仅拥有执行允许查询所需的最低权限。
除了清理之外,强烈建议记录每个工具的调用,以检测异常行为并支持取证分析。结合对可疑模式(例如异常大的删除或更改架构的命令)的实时警报,可以在小错误级联成重大事故之前迅速干预。
归根结底,最小权限原则应指导我们的设计:只给模型它严格需要的工具,并用精确的边界和监督来保护每个操作。无论你的工具是在本地运行、调用外部 API 还是在 MCP 服务器上执行,同样的保障措施都适用——限制能力、清理输入、强制执行最小权限并保持完全的可观察性。通过以这种纪律水平对待有状态工具,你确保你的 AI 智能体仍然是强大的协作者,而不是不受控制的数据库管理员。
自动化工具开发 (Automated Tool Development)
代码生成是一种 AI 智能体自主编写代码的技术,显著减少了创建和维护软件应用程序所需的时间和精力。这个过程包括在大量代码数据上训练模型,使其能够理解编程语言、编码模式和最佳实践。
代码生成代表了 AI 能力的变革性飞跃,特别是当智能体为了解决任务或与新 API 交互而实时编写自己的工具时。这种动态方法使 AI 智能体能够适应并扩展其功能,显着增强其多功能性和解决问题的能力。
基础模型成为工具制造者
基模不再仅仅消费工具——它们还构建工具。通过向 LLM 提供 API 规范或示例输入,可以让它生成初始包装器、辅助函数或更高级别的“原子”操作。让模型编写代码段,在安全的沙箱中执行,然后审视其自己的输出:“该端点返回了 400——调整查询参数。”经过几次快速迭代,最终会得到一套经过充分测试、限定范围的工具,智能体可以直接调用这些工具,而无需手工编写每个包装器。
在与庞大的 API 环境作斗争时,这种方法会大放异彩。与其手动编写数十个微服务客户端,不如将模型指向你的 OpenAPI 规范(或代码示例),并让它编写每个函数的初版。然后,人工审核人员在生成的代码进入持续集成/持续部署 (CI/CD) 管道之前对其进行验证和收紧,以确保安全性和正确性。随着 API 的发展,你只需重新运行相同的生成和完善循环即可使工具保持同步——节省数周的样板工作,并避免脆弱的手写胶水代码。
虽然基础模型驱动的工具创建大幅缩短了开发时间并轻松扩展,但它仍然需要清晰的验证标准(测试、响应检查、模式执行)和开发人员的监督。模型的自然语言审查使我们可以轻松理解建议修复,但你最终要负责定位边缘情况,防范安全漏洞并确认业务逻辑的一致性。如果操作得当,这种 AI 创造力与人工审核的混合体将把混乱的 API 生态系统转变为精简的、即用型的智能体工具包——在你的组织中解锁快速、可靠的自动化。
实时代码生成
实时代码生成涉及 AI 智能体在其运行期间根据需要编写和执行代码。这种能力使智能体能够创建新工具或修改现有工具以解决特定任务,从而使其具有高度的适应性。例如,如果 AI 智能体遇到新的 API 或不熟悉的问题,它可以实时生成代码以与 API 交互或开发问题的解决方案。
该过程始于智能体分析手头的任务并确定完成任务所需的步骤。基于其理解,智能体编写代码片段,然后尝试执行这些代码片段。如果代码未按预期执行,智能体会迭代修改它,从每次尝试中学习,直到达到预期的结果。这种反复试验的迭代过程使智能体能够不断改进其工具,自主提高其性能并扩展其能力。
实时代码生成提供了几个引人注目的优势,特别是在适应性和效率方面。能够即时生成代码使 AI 智能体可以快速适应新任务和环境。这种适应性对于需要动态解决问题和灵活性的应用程序至关重要,例如实时数据分析和复杂的软件集成任务。通过实时生成代码,AI 智能体可以解决即时需求,而无需等待人工干预,从而显着加快流程,减少宕机时间并提高整体效率。
然而,实时代码生成也带来了一些挑战和风险。质量控制是一个主要问题,因为确保自主生成的代码的质量和安全性至关重要。质量低劣的代码可能导致系统故障、安全漏洞和其他重大问题。安全风险是另一个主要挑战,因为允许 AI 智能体执行自行生成的代码会引入恶意人员利用该能力注入有害代码的可能性,从而导致数据泄露、未经授权的访问或系统损坏。实施强有力的安全措施和监督对于减轻这些风险至关重要。
一个不太明显但至关重要的缺点是可重复性。当你的智能体每次都从头开始重新创建工具时,就失去了可预测性——一次调用的成功并不能保证下一次调用的成功。性能可能会剧烈波动,提示词或模型更新的细微变化可能会导致完全不同的代码路径。这种不稳定性使调试、测试和合规性变得复杂,难以证明智能体将始终按预期运行。
资源消耗也是一个关键的考虑因素,因为实时代码生成和执行可能是资源密集型的,需要大量的计算能力和内存,特别是当编写和执行不成熟或低效的解决方案时。在系统性能的多个方面设置安全护栏有助于减轻这些风险。
工具使用配置 (Tool Use Configuration)
来自 OpenAI、Anthropic、Gemini 等的基础模型 API 允许通过 tool_choice 参数显式控制模型的工具使用——从灵活的基础模型驱动的调用转变为确定性行为。在auto模式下,模型根据上下文决定是否调用工具;这适用于一般用途。相比之下,any/required强制模型调用至少一个工具,这在工具输出至关重要时非常理想。将这些参数设置为none会阻止所有工具调用——这对于受控输出或测试环境很有用。有些接口甚至允许固化指定的工具,确保可预测、可重复的流程。通过选择适当的模式,可以决定是让基础模型灵活地管理任务,还是强加结构——在灵活性、可靠性和可预测性之间取得平衡。
即使是最好的智能体也会失误——跳过必要的工具调用、输出无效的 JSON 或运行出错的工具——因此需要适当的可靠回退和后处理机制。在每个模型响应之后,检查它是否调用了正确的工具,生成了有效的 JSON,并且没有运行时错误地成功执行。如果出现任何问题,请以纠正流程进行响应:
-
首先使用你的模式进行验证(例如,通过 jsonschema 或 Pydantic)。这会捕获缺失的字段或错误的结构。如果跳过了工具,请自动触发它;如果 JSON 无效,请提示模型更正它。
-
智能重试,使用结构化逻辑,例如针对瞬时故障的指数退避,或仅重新生成有问题的部分而不是重新启动整个交换。
-
当重试失败时优雅地回退。选项包括切换到备份模型或服务,要求用户澄清,使用缓存数据或返回安全的默认值。
-
记录一切——提示词、工具调用、验证错误、重试、回退——以便进行可观察性、调试和持续改进。
通过验证输出、战略性重试和优雅回退——同时记录每一步——你将随机故障转化为可管理的、可预测的行为。这种转变对于交付健壮的、生产级的智能体至关重要。
结论 (Conclusion)
工具使 AI 智能体能够有效地执行任务、做出决策并与环境交互。这些任务范围从简单到需要高级推理的复杂任务。由开发人员手动设计的手工制作工具提供了精确性,但维护起来可能很耗时。由 OpenAI 和 Google 的 Gemini 等平台提供的插件工具实现了快速集成和可扩展性,但缺乏可定制性。
自动化工具开发,包括实时代码生成、模仿学习和强化学习,允许 AI 智能体动态适应和完善其能力。这增强了它们的多功能性和解决问题的能力,实现了工具的持续改进和自主扩展。为智能体构建和管理工具包是赋予智能体在手头任务中取得成功的能力的最关键方法之一。
现在我们知道了如何构建和策划我们提供给智能体的一组工具,我们将继续考虑如何使智能体制定计划、选择和参数化工具,并将这些部分组合在一起以执行有用的工作。在下一章中,我们将讨论如何组织一系列工具来执行复杂任务,这一过程我们称为编排 (Orchestration)。
翻译整理自Building Applications with AI Agents一书,仅供学习交流使用








