拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【三个月 AI Agent 实战学习】Day 23 详细展开:LangChain 的 Tool 定义 —— 让模型看懂你的函数

【三个月 AI Agent 实战学习】Day 23 详细展开:LangChain 的 Tool 定义 —— 让模型看懂你的函数

Day 23 详细展开:LangChain 的 Tool 定义 —— 让模型看懂你的函数

欢迎来到第二十三天!昨天我们从理论层面深入理解了 ReAct 范式,今天开始进入 LangChain 的 Agent 实战。在构建 Agent 时,工具(Tool)是模型与外部世界交互的桥梁。LangChain 提供了一套简洁的工具定义方式,让普通的 Python 函数能够被大模型理解、调用。今天我们将掌握如何用@tool装饰器包装函数,如何编写高质量的工具描述和参数说明,并通过一个小实验验证模型能否正确识别并调用我们定义的工具。


一、今日学习目标

  1. 理解 LangChain 中 Tool 的概念:它是将 Python 函数封装为模型可识别、可调用的接口。
  2. 掌握使用@tool装饰器定义工具的方法,包括函数文档字符串(docstring)对工具描述的重要作用。
  3. 学会定义带有参数的工具,并指定参数的类型和描述。
  4. 通过实验:创建一个计算器工具,让模型在对话中主动调用它,观察模型的调用流程。
  5. 了解工具定义的最佳实践:清晰描述、参数约束、错误处理。

二、详细实现步骤

步骤 1:环境准备

确保已安装 LangChain 相关库。如果尚未安装,执行:

pipinstalllangchain langchain-openai

同时,我们需要初始化 ChatOpenAI 以使用 DeepSeek。

新建langchain_tool_demo.py,导入所需模块:

importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttool load_dotenv()# 初始化模型llm=ChatOpenAI(model="deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepseek.com",temperature=0.1)
步骤 2:使用@tool装饰器定义工具

LangChain 提供了一个@tool装饰器,可以将一个函数转换为Tool对象。关键点:

  • 函数名:默认作为工具名称。
  • 文档字符串(docstring):作为工具的描述,模型依靠它来决定何时调用该工具。因此 docstring 必须清晰、明确。
  • 参数类型注解:模型通过函数的签名了解参数类型(如str、int)。你也可以使用Annotated提供更具体的参数描述。
  • 返回值:可以是字符串或任何可序列化的对象,推荐返回字符串,便于模型理解。

我们来定义一个简单的计算器工具:

@tooldefcalculator(expression:str)->str:"""计算数学表达式,支持加减乘除和括号。输入为字符串格式的数学表达式,返回计算结果。"""try:# 限制表达式仅包含数字和运算符,避免安全问题allowed=set("0123456789+-*/(). ")ifnotset(expression).issubset(allowed):return"错误:表达式包含非法字符"result=eval(expression)returnstr(result)exceptExceptionase:returnf"错误:{e}"

说明:

  • @tool装饰器会自动将函数转换为Tool对象。
  • 函数名calculator将成为工具名称。
  • docstring 详细描述了工具的功能、输入格式和输出格式,这直接影响模型是否能够正确调用。
  • 参数expression有类型注解str,模型知道要提供一个字符串。

我们可以打印工具的信息来查看:

print(calculator.name)# calculatorprint(calculator.description)# 计算数学表达式,支持加减乘除和括号。输入为字符串格式的数学表达式,返回计算结果。print(calculator.args)# 返回参数 schema
步骤 3:定义带多个参数的工具

有时候工具需要多个参数。我们可以使用Annotated为每个参数提供描述,增强模型的意图理解。

例如,定义一个查询天气的工具:

fromtypingimportAnnotated@tooldefget_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:查看工具转换为模型可识别的 schema

LangChain 内部会将工具转换为 OpenAI 函数调用的格式。我们可以通过convert_to_openai_function或直接查看.args来了解:

fromlangchain_core.utils.function_callingimportconvert_to_openai_function openai_function=convert_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_tools=llm.bind_tools([calculator,get_weather])# 用户提问user_message="请帮我计算 (15 + 7) * 3 的结果。"messages=[{"role":"user","content":user_message}]# 模型回应response=llm_with_tools.invoke(messages)print("模型响应:")print(response)

运行脚本,你会看到模型返回了一个AIMessage,其中包含tool_calls字段,表示它请求调用calculator工具,并提供了参数。

观察:

  • response.content可能为空。
  • response.tool_calls包含工具调用的详细信息,如name和args(参数已解析为字典)。
步骤 6:执行工具并反馈结果

我们需要手动执行工具,并将结果作为ToolMessage发送回模型,让模型生成最终回答。

importjsonfromlangchain_core.messagesimportToolMessage# 执行工具调用tool_call=response.tool_calls[0]function_name=tool_call["name"]args=tool_call["args"]print(f"模型请求调用工具:{function_name},参数:{args}")# 根据函数名执行对应的函数iffunction_name=="calculator":observation=calculator.invoke(args)# 注意:Tool 对象有 invoke 方法eliffunction_name=="get_weather":observation=get_weather.invoke(args)else:observation="未知工具"print(f"工具返回:{observation}")# 将工具结果添加到消息历史messages.append(response)# 添加 assistant 消息(包含 tool_calls)messages.append(ToolMessage(content=observation,tool_call_id=tool_call["id"]))# 添加工具结果# 再次调用模型生成最终回答final_response=llm_with_tools.invoke(messages)print("\n最终回答:")print(final_response.content)

运行后,你将看到模型最终回复类似:“计算结果为 66”。

步骤 7:整合完整流程

我们可以将上述过程封装成一个函数,方便复用。

defrun_with_tools(user_input:str):messages=[{"role":"user","content":user_input}]# 第一次调用response=llm_with_tools.invoke(messages)# 检查是否有工具调用ifresponse.tool_calls:messages.append(response)fortool_callinresponse.tool_calls:function_name=tool_call["name"]args=tool_call["args"]iffunction_name=="calculator":result=calculator.invoke(args)eliffunction_name=="get_weather":result=get_weather.invoke(args)else:result="未知工具"# 追加工具结果messages.append(ToolMessage(content=result,tool_call_id=tool_call["id"]))# 再次调用模型final_response=llm_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:多个工具时,模型选择了错误的工具。
→ 确保每个工具的描述足够独特,避免功能重叠。可以在描述中说明适用场景和限制。

Q5:tool_call["args"]是字典还是字符串?
→ 在 LangChain 的AIMessage中,tool_calls列表中的每个元素是一个字典,其中args已经是解析后的字典(如果参数是 JSON 对象)。你不需要再json.loads。

Q6:如何安全地执行 eval?
→ 生产环境中应避免使用eval,可以使用numexpr等安全库,或自己解析表达式。我们这里仅作为演示,加入了字符白名单限制。


四、今日总结与作业

今天你完成了:

  • ✅ 理解了 LangChain 中 Tool 的定义方式。
  • ✅ 使用@tool装饰器创建了计算器和天气查询工具。
  • ✅ 学习了如何编写清晰的工具描述和参数注解。
  • ✅ 通过手动循环验证了模型能够正确识别并调用工具。
  • ✅ 为明天使用create_react_agent构建完整 Agent 打下了基础。

今日作业(必做):

  1. 自己定义两个新工具:get_time(获取当前时间,无参数)和translate_text(翻译文本,参数为text和target_language)。使用@tool装饰器,确保描述清晰。
  2. 将这两个工具绑定到 LLM,测试用户提问“现在几点了?”和“把‘你好’翻译成英文”,观察模型是否能正确调用。
  3. 思考:工具的描述如何影响模型的调用决策?如果描述写得很模糊(比如只写“一个工具”),会发生什么?尝试修改描述,观察模型行为变化。

明日预告:我们将使用 LangChain 的create_react_agent构建第一个真正的 Agent,让它自动处理工具调用循环,无需手动编写 ReAct 循环。你将体会到框架带来的巨大便利。

有任何问题欢迎随时提问!

返回列表