
Day 23 详细展开LangChain 的 Tool 定义 —— 让模型看懂你的函数欢迎来到第二十三天昨天我们从理论层面深入理解了 ReAct 范式今天开始进入 LangChain 的 Agent 实战。在构建 Agent 时工具Tool是模型与外部世界交互的桥梁。LangChain 提供了一套简洁的工具定义方式让普通的 Python 函数能够被大模型理解、调用。今天我们将掌握如何用tool装饰器包装函数如何编写高质量的工具描述和参数说明并通过一个小实验验证模型能否正确识别并调用我们定义的工具。一、今日学习目标理解 LangChain 中 Tool 的概念它是将 Python 函数封装为模型可识别、可调用的接口。掌握使用tool装饰器定义工具的方法包括函数文档字符串docstring对工具描述的重要作用。学会定义带有参数的工具并指定参数的类型和描述。通过实验创建一个计算器工具让模型在对话中主动调用它观察模型的调用流程。了解工具定义的最佳实践清晰描述、参数约束、错误处理。二、详细实现步骤步骤 1环境准备确保已安装 LangChain 相关库。如果尚未安装执行pipinstalllangchain langchain-openai同时我们需要初始化 ChatOpenAI 以使用 DeepSeek。新建langchain_tool_demo.py导入所需模块importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttool load_dotenv()# 初始化模型llmChatOpenAI(modeldeepseek-chat,api_keyos.getenv(DEEPSEEK_API_KEY),base_urlhttps://api.deepseek.com,temperature0.1)步骤 2使用tool装饰器定义工具LangChain 提供了一个tool装饰器可以将一个函数转换为Tool对象。关键点函数名默认作为工具名称。文档字符串docstring作为工具的描述模型依靠它来决定何时调用该工具。因此 docstring 必须清晰、明确。参数类型注解模型通过函数的签名了解参数类型如str、int。你也可以使用Annotated提供更具体的参数描述。返回值可以是字符串或任何可序列化的对象推荐返回字符串便于模型理解。我们来定义一个简单的计算器工具tooldefcalculator(expression:str)-str:计算数学表达式支持加减乘除和括号。输入为字符串格式的数学表达式返回计算结果。try:# 限制表达式仅包含数字和运算符避免安全问题allowedset(0123456789-*/(). )ifnotset(expression).issubset(allowed):return错误表达式包含非法字符resulteval(expression)returnstr(result)exceptExceptionase:returnf错误{e}说明tool装饰器会自动将函数转换为Tool对象。函数名calculator将成为工具名称。docstring 详细描述了工具的功能、输入格式和输出格式这直接影响模型是否能够正确调用。参数expression有类型注解str模型知道要提供一个字符串。我们可以打印工具的信息来查看print(calculator.name)# calculatorprint(calculator.description)# 计算数学表达式支持加减乘除和括号。输入为字符串格式的数学表达式返回计算结果。print(calculator.args)# 返回参数 schema步骤 3定义带多个参数的工具有时候工具需要多个参数。我们可以使用Annotated为每个参数提供描述增强模型的意图理解。例如定义一个查询天气的工具fromtypingimportAnnotatedtooldefget_weather(city:Annotated[str,城市名称例如北京、上海],unit:Annotated[str,温度单位可选 celsius 或 fahrenheit]celsius)-str:查询指定城市的当前天气情况返回天气描述。# 模拟天气数据weather_data{北京:{celsius:晴26°C,fahrenheit:晴79°F},上海:{celsius:多云28°C,fahrenheit:多云82°F}}ifcityinweather_data:returnweather_data[city].get(unit,未知单位)else:returnf未找到{city}的天气信息注意使用Annotated为参数添加了描述这些描述会出现在工具 schema 中帮助模型理解参数含义。unit参数有默认值模型可以省略。步骤 4查看工具转换为模型可识别的 schemaLangChain 内部会将工具转换为 OpenAI 函数调用的格式。我们可以通过convert_to_openai_function或直接查看.args来了解fromlangchain_core.utils.function_callingimportconvert_to_openai_function openai_functionconvert_to_openai_function(calculator)print(openai_function)你会看到类似下面的 JSON schema{name:calculator,description:计算数学表达式支持加减乘除和括号。输入为字符串格式的数学表达式返回计算结果。,parameters:{type:object,properties:{expression:{type:string}},required:[expression]}}这就是模型在调用 Function Calling 时看到的工具描述。步骤 5绑定工具到模型并进行对话现在我们将工具绑定到 LLM 上并模拟一个对话观察模型是否会调用工具。# 将工具绑定到模型llm_with_toolsllm.bind_tools([calculator,get_weather])# 用户提问user_message请帮我计算 (15 7) * 3 的结果。messages[{role:user,content:user_message}]# 模型回应responsellm_with_tools.invoke(messages)print(模型响应)print(response)运行脚本你会看到模型返回了一个AIMessage其中包含tool_calls字段表示它请求调用calculator工具并提供了参数。观察response.content可能为空。response.tool_calls包含工具调用的详细信息如name和args参数已解析为字典。步骤 6执行工具并反馈结果我们需要手动执行工具并将结果作为ToolMessage发送回模型让模型生成最终回答。importjsonfromlangchain_core.messagesimportToolMessage# 执行工具调用tool_callresponse.tool_calls[0]function_nametool_call[name]argstool_call[args]print(f模型请求调用工具{function_name}参数{args})# 根据函数名执行对应的函数iffunction_namecalculator:observationcalculator.invoke(args)# 注意Tool 对象有 invoke 方法eliffunction_nameget_weather:observationget_weather.invoke(args)else:observation未知工具print(f工具返回{observation})# 将工具结果添加到消息历史messages.append(response)# 添加 assistant 消息包含 tool_callsmessages.append(ToolMessage(contentobservation,tool_call_idtool_call[id]))# 添加工具结果# 再次调用模型生成最终回答final_responsellm_with_tools.invoke(messages)print(\n最终回答)print(final_response.content)运行后你将看到模型最终回复类似“计算结果为 66”。步骤 7整合完整流程我们可以将上述过程封装成一个函数方便复用。defrun_with_tools(user_input:str):messages[{role:user,content:user_input}]# 第一次调用responsellm_with_tools.invoke(messages)# 检查是否有工具调用ifresponse.tool_calls:messages.append(response)fortool_callinresponse.tool_calls:function_nametool_call[name]argstool_call[args]iffunction_namecalculator:resultcalculator.invoke(args)eliffunction_nameget_weather:resultget_weather.invoke(args)else:result未知工具# 追加工具结果messages.append(ToolMessage(contentresult,tool_call_idtool_call[id]))# 再次调用模型final_responsellm_with_tools.invoke(messages)returnfinal_response.contentelse:returnresponse.content# 测试print(run_with_tools(请计算 25 * 4))print(run_with_tools(北京今天天气怎么样))步骤 8测试不同输入数学计算应该调用calculator。天气查询应该调用get_weather。闲聊“你好吗”应该不调用工具直接回答。观察模型的决策是否正确。三、常见问题与调试Q1模型没有调用工具而是直接回答。→ 可能原因工具描述不够清晰模型认为不需要工具。用户问题不明确比如“北京天气”没有明确指出要查询天气模型可能直接回答。可以尝试在系统提示中鼓励模型使用工具后续 Agent 中会处理。确保bind_tools正确执行且工具确实被传递。Q2模型调用工具时参数错误例如少了参数或格式不对。→ 增强工具 docstring 和参数描述使用Annotated提供具体说明。另外可以在工具内部做参数校验返回错误信息让模型修正。Q3工具返回的结果不是字符串模型无法理解→ 建议工具返回字符串。如果必须返回其他类型可以使用return_direct或手动转换为字符串。Q4多个工具时模型选择了错误的工具。→ 确保每个工具的描述足够独特避免功能重叠。可以在描述中说明适用场景和限制。Q5tool_call[args]是字典还是字符串→ 在 LangChain 的AIMessage中tool_calls列表中的每个元素是一个字典其中args已经是解析后的字典如果参数是 JSON 对象。你不需要再json.loads。Q6如何安全地执行 eval→ 生产环境中应避免使用eval可以使用numexpr等安全库或自己解析表达式。我们这里仅作为演示加入了字符白名单限制。四、今日总结与作业今天你完成了✅ 理解了 LangChain 中 Tool 的定义方式。✅ 使用tool装饰器创建了计算器和天气查询工具。✅ 学习了如何编写清晰的工具描述和参数注解。✅ 通过手动循环验证了模型能够正确识别并调用工具。✅ 为明天使用create_react_agent构建完整 Agent 打下了基础。今日作业必做自己定义两个新工具get_time获取当前时间无参数和translate_text翻译文本参数为text和target_language。使用tool装饰器确保描述清晰。将这两个工具绑定到 LLM测试用户提问“现在几点了”和“把‘你好’翻译成英文”观察模型是否能正确调用。思考工具的描述如何影响模型的调用决策如果描述写得很模糊比如只写“一个工具”会发生什么尝试修改描述观察模型行为变化。明日预告我们将使用 LangChain 的create_react_agent构建第一个真正的 Agent让它自动处理工具调用循环无需手动编写 ReAct 循环。你将体会到框架带来的巨大便利。有任何问题欢迎随时提问