简介 大模型不仅可以生成文本,还能根据需要调用外部的工具来完成一些特定的任务。例如,模型可能需要从数据库中获取数据、在互联网上搜索信息,或者运行一段代码来实现某个功能。通过调用这些工具,模型能够更有效地处理复杂的任务,扩展自身的功能边界,从而更好地满足用户的需求。
组件 :代理调用以执行动作的组件
桥梁作用 :连接模型与外部系统(API、数据库、文件系统)
结构化接口 :通过明确定义的输入输出与世界互动
能力扩展 :扩展模型能力,使其能够执行具体任务
工具调用流程:
graph LR
A["用户输入"] --> B["模型决策"]
B --> C["工具调用"]
C --> D["执行动作"]
D --> E["返回结果"]
工具的组成:
模式(Schema) :模式是工具的一个重要组成部分,它定义了工具的基本信息和使用规范。具体包括:
工具名称(Name of the tool) :用于唯一标识这个工具,方便模型在调用时能够明确指定要使用的是哪一个工具。
描述(Description) :对工具的功能和用途进行简要说明,帮助模型理解这个工具是做什么的,以便在合适的情况下调用它。
参数定义(Argument definitions) :详细说明了调用该工具时需要提供哪些参数,以及这些参数的类型、格式和含义等。通常会采用JSON模式(JSON schema)来定义参数,这样可以清晰地描述参数的结构和约束条件,确保模型在调用工具时能够提供正确和完整的参数信息。
执行函数或协程(Function or coroutine to execute) :这是工具的核心部分,它是一个具体的函数或者协程,用于实际执行工具所定义的任务。当模型调用工具时,就会触发这个函数或协程的执行,从而完成相应的操作,比如查询数据库、搜索网络或者运行代码等。函数或协程的实现会根据工具的具体功能而有所不同,它们是工具能够完成任务的关键所在。
以官网的示例进行说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from langchain.tools import tool@tool def get_weather (locations: list [str ] ) -> str : """Get the weather at multiple locations.""" results = [] for location in locations: if location == 'SF' : results.append('SF: 72°F sunny' ) elif location == 'NYC' : results.append('NYC: 68°F cloudy' ) else : results.append(f'{location} : Weather not available' ) return ", " .join(results) model_with_tools = model.bind_tools([get_weather]) response = model_with_tools.invoke("What's the weather in SF and NYC?" ) for tool_call in response.tool_calls: print (f"Tool: {tool_call['name' ]} " ) print (f"Args: {tool_call['args' ]} " )
用户和模型之间的基本工具调用流程
sequenceDiagram
participant User as 用户
participant Model as AI模型
participant Tools as 外部工具(天气查询)
%% 用户发起天气查询请求
User->>Model: What's the weather in SF and NYC?
note over Model: 分析请求,确定需调用天气查询工具
%% 模型并行调用工具(SF和NYC天气查询)
Model->>Tools: 并行调用 get_weather("San Francisco")
Model->>Tools: 并行调用 get_weather("New York")
note over Tools: 分别获取两地天气数据
%% 工具返回结果给模型
Tools-->>Model: 返回旧金山天气数据(72°F sunny)
Tools-->>Model: 返回纽约天气数据(68°F cloudy)
note over Model: 整理工具返回结果,生成自然语言回复
%% 模型向用户反馈最终结果
Model-->>User: SF: 72°F sunny, NYC: 68°F cloudy
核心价值
能力扩展 :让模型可以访问外部 API、数据库、文件系统等,获取实时信息或执行操作,例如查询天气、查询数据库、读写文件等。
结构化交互 :通过明确的输入输出模式(如函数定义、参数规范),确保模型与外部系统交互的准确性,避免因理解偏差导致的调用失败。
自主决策 :让模型能够根据任务需求,自主决定何时调用工具、调用哪个工具,以及如何解析工具返回的结果,实现更智能的任务处理。
任务自动化 :使模型能够完成复杂的端到端任务,而不仅仅是生成文本。例如,自动规划行程、处理订单、分析数据等。
创建基本工具 使用 @tool 装饰器
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 import pymysqlfrom langchain.agents import create_agentfrom langchain.tools import toolfrom src.core.llms import model_client@tool('数据库操作' , description='连接数据库,执行sql语句,并返回执行的结果' ) def mysql_executor (sql: str ): """ 连接数据库,执行sql语句 需要先安装pymysql :param sql: :return: """ try : connect = pymysql.connect(host='127.0.0.1' , port=3306 , user='root' , password='mysql' , cursorclass=pymysql.cursors.DictCursor, db='agent' , autocommit=True , ) cursor = connect.cursor() cursor.execute(sql) connect.commit() except pymysql.err.OperationalError as e: return 0 result = cursor.fetchall() cursor.close() connect.cursor() return result agent = create_agent( model=model_client, tools=[mysql_executor], system_prompt=""" 您是一个数据库操作助手 您可以: 1. 执行SQL创建、查询、更新和删除操作 2. 提供详细的执行过程反馈 请根据用户需求执行相应的数据库操作。 """ )
重要说明:
类型提示必需 :定义工具的输入模式
文档字符串重要 :帮助模型理解工具用途
简洁明了 :文档字符串应具备信息性且简洁
自定义工具属性 自定义工具名称 1 2 3 4 5 6 @tool("web_search" ) def search (query: str ) -> str : """在网络上搜索信息。""" return f"搜索结果: {query} " print (search.name)
自定义工具描述 1 2 3 4 5 6 @tool("web_search" ) def search (query: str ,description="用于网络搜索" ) -> str : """在网络上搜索信息。""" return f"搜索结果: {query} " print (search.name)
工具参数的声明 使用 Pydantic 模型定义复杂输入
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import pymysqlfrom langchain.agents import create_agentfrom langchain.tools import toolfrom pydantic import BaseModel, Fieldfrom src.core.llms import model_clientclass DataBaseConfig (BaseModel ): host: str = Field(..., description="数据库地址" ) port: int = Field(..., description="数据库端口" ) user: str = Field(..., description="数据库用户名" ) password: str = Field(..., description="数据库密码" ) db: str = Field(..., description="数据库名称" ) @tool('数据库操作' , description='连接数据库,执行sql语句,并返回执行的结果' ) def mysql_executor (sql: str , data_config: DataBaseConfig ): """ 连接数据库,执行sql语句 需要先安装pymysql :param sql: :return: """ try : connect = pymysql.connect(host=data_config.host, port=data_config.port, user=data_config.user, password=data_config.password, cursorclass=pymysql.cursors.DictCursor, db=data_config.db, autocommit=True , ) cursor = connect.cursor() cursor.execute(sql) connect.commit() except pymysql.err.OperationalError as e: return 0 result = cursor.fetchall() cursor.close() connect.cursor() return result @tool('获取数据库连接配置' , description='获取数据库的连接参数' ) def get_database_config () -> DataBaseConfig: """ 获取数据库连接参数 :return: """ return DataBaseConfig( host="127.0.0.1" , port=3306 , user="root" , password="12345678" , db="test" ) agent = create_agent( model=model_client, tools=[get_database_config, mysql_executor], system_prompt=""" 您是一个数据库操作助手 您可以: 1. 执行SQL创建、查询、更新和删除操作 2. 提供详细的执行过程反馈 请根据用户需求执行相应的数据库操作。 """ )
注意 :
自定义工具参数的名称,以下参数名称是保留的,不能用作工具参数:
参数名称
用途
config
保留给内部工具 RunnableConfig
runtime
保留给参数 ToolRuntime
要访问运行时信息,请使用 ToolRuntime 参数。
访问运行时上下文 当工具 能够访问代理状态、运行时上下文和长期记忆时,它们最为强大。这使得工具能够做出基于上下文的决策、个性化响应,并在对话之间保持信息。
运行时上下文提供了一种方式,可以在运行时将依赖项(如数据库连接、用户ID或配置)注入到工具中,这使得工具更具可测试性和可重用性。
工具可以通过ToolRuntime参数访问运行时信息,它提供以下内容:
State(状态) :在执行过程中流动的可变数据(例如消息、计数器、自定义字段)。
Context(上下文) :像用户ID、会话详情或应用程序特定配置这样的不可变配置。
Store(存储) :跨对话的持久长期记忆。
Stream Writer(流式写入器) :在工具执行时流式传输自定义更新。
Config(配置) :执行的RunnableConfig。
Tool Call ID(工具调用ID) :当前工具调用的ID。这段内容主要介绍了LangChain中工具(Tools)如何通过ToolRuntime参数访问运行时上下文信息,以增强工具的功能性和灵活性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from langchain.tools import tool, ToolRuntime@tool def summarize_conversation (runtime: ToolRuntime ) -> str : """总结当前对话历史。""" messages = runtime.state["messages" ] human_msgs = sum (1 for m in messages if m.__class__.__name__ == "HumanMessage" ) ai_msgs = sum (1 for m in messages if m.__class__.__name__ == "AIMessage" ) tool_msgs = sum (1 for m in messages if m.__class__.__name__ == "ToolMessage" ) return f"对话包含{human_msgs} 条用户消息,{ai_msgs} 条AI回复,{tool_msgs} 条工具结果"
工具访问运行时上下文的重要性
增强工具能力 :当工具能够访问代理状态、运行时上下文和长期记忆时,它们可以基于上下文做出更智能的决策,提供个性化的响应,并在多次对话中保持信息的连贯性。
提高可测试性和可重用性 :运行时上下文允许在运行时将依赖项(如数据库连接、用户ID或配置)注入到工具中,这使得工具在不同环境下更容易被测试和重用。
flowchart LR
%% 定义容器(子图)
subgraph ToolRuntimeContext [工具运行时上下文]
direction LR
ToolCall[工具调用] --> ToolRuntime[工具运行时]
ToolRuntime --> ContextAccess[上下文访问]
ToolRuntime --> StateAccess[状态访问]
ToolRuntime --> StoreAccess[存储访问]
ToolRuntime --> StreamWriter[流写入器]
end
subgraph AvailableResources [可用资源]
SessionInfo[会话信息]
UserID[用户ID]
Messages[消息记录]
CustomState[自定义状态]
UserPreferences[用户偏好]
LongtermMemory[长期记忆]
end
subgraph EnhancedToolCapabilities [增强的工具能力]
ContextAwareTools[上下文感知工具]
StatefulTools[有状态工具]
MemoryEnabledTools[记忆赋能工具]
StreamingTools[流式工具]
end
%% 连接各模块
ContextAccess --> SessionInfo
ContextAccess --> UserID
StateAccess --> Messages
StateAccess --> CustomState
StoreAccess --> UserPreferences
StoreAccess --> LongtermMemory
SessionInfo --> ContextAwareTools
UserID --> ContextAwareTools
Messages --> ContextAwareTools
CustomState --> StatefulTools
UserPreferences --> MemoryEnabledTools
LongtermMemory --> MemoryEnabledTools
StreamWriter --> StreamingTools
%% 样式优化(提升视觉辨识度)
style ToolRuntimeContext fill:#fff8e1,stroke:#333,stroke-width:1px
style AvailableResources fill:#fff8e1,stroke:#333,stroke-width:1px
style EnhancedToolCapabilities fill:#fff8e1,stroke:#333,stroke-width:1px
classDef boxStyle fill:#e3eafd,stroke:#333,stroke-width:1px
class ToolCall,ToolRuntime,ContextAccess,StateAccess,StoreAccess,StreamWriter,SessionInfo,UserID,Messages,CustomState,UserPreferences,LongtermMemory,ContextAwareTools,StatefulTools,MemoryEnabledTools,StreamingTools boxStyle
上下文数据 ToolRuntime :这是一个工具运行时的上下文环境或参数,它为工具提供了一个统一的方式来访问所有运行时的信息。
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 from dataclasses import dataclassimport pymysqlfrom langchain.agents import create_agentfrom langchain.tools import tool, ToolRuntimefrom pydantic import BaseModel, Fieldfrom src.core.llms import model_clientclass DataBaseConfig (BaseModel ): host: str = Field(..., description="数据库地址" ) port: int = Field(..., description="数据库端口" ) user: str = Field(..., description="数据库用户名" ) password: str = Field(..., description="数据库密码" ) db: str = Field(..., description="数据库名称" ) @tool('数据库操作' , description='连接数据库,执行sql语句,并返回执行的结果' ) def mysql_executor (sql: str , data_config: DataBaseConfig ): """ 连接数据库,执行sql语句 需要先安装pymysql :param sql: :return: """ try : connect = pymysql.connect(host=data_config.host, port=data_config.port, user=data_config.user, password=data_config.password, cursorclass=pymysql.cursors.DictCursor, db=data_config.db, autocommit=True , ) cursor = connect.cursor() cursor.execute(sql) connect.commit() except pymysql.err.OperationalError as e: return 0 result = cursor.fetchall() cursor.close() connect.cursor() return result @dataclass class AgentRunContext : user_id: str @tool('获取数据库连接配置' , description='获取数据库的连接参数' ) def get_database_config (runtime: ToolRuntime[AgentRunContext] ) -> DataBaseConfig: """ 获取数据库连接参数 :return: """ print ("user_id" , runtime.context.user_id) return DataBaseConfig( host="127.0.0.1" , port=3306 , user="root" , password="12345678" , db="test" ) agent = create_agent( model=model_client, tools=[get_database_config, mysql_executor], context_schema=AgentRunContext, system_prompt=""" 您是一个数据库操作助手 您可以: 1. 执行SQL创建、查询、更新和删除操作 2. 提供详细的执行过程反馈 请根据用户需求执行相应的数据库操作。 """ ) result = agent.stream( {"messages" : [{"role" : "user" , "content" : "用户获取数据的连接时什么?" }]}, context=AgentRunContext(user_id="user123" ) )
ToolRuntime的核心工作原理:
类型化上下文 :通过 @dataclass 定义结构化数据模型,避免无类型的字典传递。
泛型声明 :工具函数使用 ToolRuntime[ContextType] 声明所需上下文类型,实现编译期类型检查。
自动注入 :运行时自动注入正确类型的上下文对象,通过 runtime.context 安全访问,无需手动传递。
Agent 集成 :创建 Agent 时指定 context_schema,调用时传入具体实例,实现上下文与工具的无缝衔接。
存储访问 通过记忆可以访问跨对话的持久数据。存储可通过以下方式访问,并允许您保存和检索用户特定或应用特定数据。runtime.store
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 from dataclasses import dataclassimport pymysqlfrom langchain.agents import create_agentfrom langchain.tools import tool, ToolRuntimefrom langgraph.store.memory import InMemoryStorefrom pydantic import BaseModel, Fieldfrom src.core.llms import model_clientclass DataBaseConfig (BaseModel ): host: str = Field(..., description="数据库地址" ) port: int = Field(..., description="数据库端口" ) user: str = Field(..., description="数据库用户名" ) password: str = Field(..., description="数据库密码" ) db: str = Field(..., description="数据库名称" ) @tool('数据库操作' , description='连接数据库,执行sql语句,并返回执行的结果' ) def mysql_executor (sql: str , data_config: DataBaseConfig ): """ 连接数据库,执行sql语句 需要先安装pymysql :param sql: :return: """ try : connect = pymysql.connect(host=data_config.host, port=data_config.port, user=data_config.user, password=data_config.password, cursorclass=pymysql.cursors.DictCursor, db=data_config.db, autocommit=True , ) cursor = connect.cursor() cursor.execute(sql) connect.commit() except pymysql.err.OperationalError as e: return 0 result = cursor.fetchall() cursor.close() connect.cursor() return result @tool('获取数据库连接配置' , description='获取数据库的连接参数' ) def get_database_config () -> DataBaseConfig: """ 获取数据库连接参数 :return: """ print ("user_id" , runtime.context.user_id) return DataBaseConfig( host="127.0.0.1" , port=3306 , user="root" , password="12345678" , db="test" ) @dataclass class AgentRunContext : user_id: str @tool('获取当前用户详情' , description='获取当前的用户id' ) def get_user_id (runtime: ToolRuntime[AgentRunContext] ) -> str : """ 获取当前用户id :return: """ return runtime.context.user_id @tool("存储对话的记录" , description="保存一个agent对话的记录" ) def save_conversation (runtime: ToolRuntime[AgentRunContext] ): store = runtime.store user_id = runtime.context.user_id message = runtime.state['message' ] store.put(('user_id' , user_id), user_id, message) return "保存成功" @tool("读取历史对话记录" , description="读取历史对话记录" ) def read_history_conversation (runtime: ToolRuntime[AgentRunContext] ): store = runtime.store user_id = runtime.context.user_id info_list = store.get(('user_id' , user_id), user_id) return info_list store = InMemoryStore() agent = create_agent( model=model_client, tools=[get_database_config, mysql_executor, get_user_id, save_conversation, read_history_conversation], context_schema=AgentRunContext, store=store, system_prompt=""" 您是一个数据库操作助手 您可以: 1. 执行SQL创建、查询、更新和删除操作 2. 提供详细的执行过程反馈 3. 保存和读取对话记录 请根据用户需求执行相应的数据库操作。 """ ) result = agent.stream( {"messages" : [{"role" : "user" , "content" : "查询users表中的详情" }]}, context=AgentRunContext(user_id="user123" ) )
流式写入器 当工具运行时,使用“runtime.stream_writer”来流式传输来自工具的自定义更新。这有助于向用户实时反馈工具正在执行的操作。
1 2 3 4 5 6 7 8 9 10 11 12 from langchain.tools import tool, ToolRuntime@tool def get_weather (city: str , runtime: ToolRuntime ) -> str : """获取指定城市的天气。""" writer = runtime.stream_writer writer(f"正在查找城市数据: {city} " ) writer(f"已获取城市数据: {city} " ) return f"{city} 的天气总是晴朗的!"
更新会话状态
Command 对象 :来自 langgraph.types,是一种特殊的返回类型,用于向执行引擎(如 LangGraph)发出指令,以更新状态或控制流程。
工具返回 Command :当工具函数返回 Command 时,它不再只是返回文本,而是告诉系统 “执行以下操作”。
1 2 3 4 5 6 7 8 from langgraph.types import Commandfrom langchain.tools import tool, ToolRuntime@tool def update_user_name (new_name: str , runtime: ToolRuntime ) -> Command: """更新用户的名称。""" return Command(update={"user_name" : new_name})
工具接收 new_name 参数。
返回 Command(update={"user_name": new_name}),让系统直接更新会话状态中的 user_name 字段。
1 2 3 4 5 6 7 8 9 10 11 12 @tool def clear_conversation () -> Command: """清除对话历史。""" from langchain.messages import RemoveMessage from langchain.graph.message import REMOVE_ALL_MESSAGES return Command( update={ "messages" : [RemoveMessage(id =REMOVE_ALL_MESSAGES)], } )
使用 RemoveMessage 和 REMOVE_ALL_MESSAGES 常量。
返回 Command,让系统清空对话历史(messages 列表)。
工具的最佳实战 1. 工具描述最佳实践 核心目标是让模型和开发者都能清晰理解工具的用途和用法:
编写详细清晰的函数文档字符串 :用自然语言描述工具的功能,让模型能准确判断何时调用。
为每个参数添加准确的描述 :明确参数的含义、类型和约束,避免模型生成无效参数。
说明工具的预期用途和返回值 :让模型知道调用后能得到什么,从而更好地规划下一步。
包含示例输入和输出 :提供具体示例,帮助模型理解正确的调用格式和结果格式。
2. 安全性考虑 核心目标是防止工具被滥用或恶意利用:
对工具输入进行验证和清理 :校验参数格式,过滤危险字符,防止注入攻击。
设置合理的权限和限制 :限制工具可执行的操作范围,例如只读操作或特定用户数据访问。
避免在工具中执行未经过滤的用户输入 :不直接执行用户提供的代码或命令,防止远程代码执行。
实施访问控制和审计日志 :记录工具调用的用户、时间和参数,便于事后审计和问题追溯。
3. 错误处理 核心目标是让工具在失败时也能稳定、可调试:
捕获并优雅地处理异常 :避免工具崩溃导致整个 Agent 流程中断。
提供有用的错误消息 :返回清晰的错误信息,帮助模型理解失败原因并尝试修复。
实现超时机制 :防止长时间运行的操作阻塞对话,提升用户体验。
记录详细的错误日志 :记录完整的错误栈和上下文,便于开发者排查问题。
4. 性能优化 核心目标是让工具高效、可扩展:
缓存频繁使用的工具结果 :对重复查询(如天气、股票)进行缓存,减少重复调用。
异步执行长时间运行的操作 :将耗时任务(如文件处理、数据库查询)转为异步,避免阻塞对话。
优化工具的执行路径 :减少不必要的步骤,提升单次调用的响应速度。
实现批量处理机制 :支持批量操作,减少网络或 IO 开销。