AI创想

标题: langchain系列(五)- LangChain 的tool原理与代码实现 [打印本页]

作者: yitiaocong    时间: 2025-9-7 23:36
标题: langchain系列(五)- LangChain 的tool原理与代码实现
目录
一、导读
二、代码实战
1、代码实现
2、输出结果
3、分析
三、tool简介
四、创建tools
1、tool装饰器
代码实现
输出
分析
2、结构化工具
代码实现
输出
3、Runnables方法
代码实现
输出
分析
4、BaseTool子类
代码实现
输出
分析
五、多工具调用
代码实现
输出
分析
6、总结

一、导读

环境:OpenEuler、Windows 11、WSL 2、Python 3.12.3 langchain 0.3
背景:前期忙碌的开发阶段结束,需要沉淀自己的应用知识,过一遍LangChain
时间:20250225
说明:技术梳理,使用LangChain实现tool,tool一般用于智能体(agent、代理)。而当前智能体基本均使用langgraph实现,故而此处仅仅作为示例而已。由于langchain的理念变化,所有都在向langgraph倾向,所以此篇也计划是最后一篇langchain的文章,后面也会使用langgraph实现rag、tool这些。
二、代码实战

以下代码实现两个工具,获取北京、上海天气和获取最冷城市的工具,可以通过不同的问题来分析tool实现原理
1、代码实现
  1. from langchain_core.messages import HumanMessage
  2. from langchain_core.tools import tool
  3. from langchain_openai import ChatOpenAI
  4. llm = ChatOpenAI(base_url="https://llm.xxx.xxxx.com/v1/",openai_api_key="sk-xxxxxxxxxx",model_name="qwen2.5-instruct")
  5. @tool
  6. def get_weather(location: str):
  7.     """获取当前城市天气。"""
  8.     if location in ["上海", "北京"]:
  9.         return f"当前{location}天气晴朗,温度为21℃"
  10.     else:
  11.         return "该城市未知,不在地球上"
  12. @tool
  13. def get_coolest_cities():
  14.     """获取中国最冷的城市"""
  15.     return "黑龙江漠河气温在-30℃"
  16. tools = [get_weather, get_coolest_cities]
  17. llm_with_tools = llm.bind_tools(tools)
  18. messages = ["北京的天气怎么样?", "哈尔滨的天气怎么样?", "中国最冷的城市?", "唐朝持续了多少年?"]
  19. for i in messages:
  20.     ai_msg = llm_with_tools.invoke([HumanMessage(i)])
  21.     if not ai_msg.tool_calls:
  22.         print(ai_msg.content, "--未调用工具")
  23.     for tool_call in ai_msg.tool_calls:
  24.         selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
  25.         tool_msg = selected_tool.invoke(tool_call)
  26.         print(f"{tool_msg.content:<30} {tool_call.get("name")}")
复制代码
2、输出结果

当前北京天气晴朗,温度为21℃                get_weather
该城市未知,不在地球上                    get_weather
黑龙江漠河气温在-30℃                   get_coolest_cities
唐朝从618年建立,到907年灭亡,一共持续了289年。这段历史可以分为初唐、盛唐、中唐和晚唐四个阶段。如果您需要更详细的信息,请告诉我! --未调用工具
3、分析

第一个问题调用了get_weather方法,第二个问题相同,第三个问题调用get_coolest_cities,第四个问题未调用工具。由此可见,大模型会根据问题自动选择相应的工具。具体选用哪个工具需要查看ai_msg的tool_calls属性。你想要实现你的工具的功能,只需在相应的函数内部实现你的功能即可。
注:该示例出自langgraph官方文档 更改的demo
三、tool简介

LangChain的tool功能,一般用于智能体,可以传递给支持工具调用的聊天模型,允许模型请求执行具有特定输入的特定功能。其常见属性有:name、description、args,可以分析代码,以便于更好的理解tool,LangChain也提供了很多的默认工具,地址:LangChain第三方工具集合
四、创建tools

官方文档提供了三种创建方式
1、tool装饰器

代码实现
  1. from langchain_core.tools import tool
  2. @tool
  3. def multiply(a: int, b: int):
  4.     """multiply two numbers"""
  5.     return a * b
  6. @tool
  7. async def amultiply(a: int, b: int):
  8.     """amultiply two numbers"""
  9.     return a * b
  10. print(amultiply.name, "|",amultiply.description, "|", amultiply.args)
复制代码
输出

amultiply | amultiply two numbers | {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
分析

该方法是通过tool装饰器实现,name即为工具名称,description为工具说明(注释的内容),args为工具接收的参数,两个工具功能相同,仅为异步和同步的差别。该方法为LangChain稳定创建tool的方法,第三种为beta版本,后期可能发生变化。
2、结构化工具

代码实现
  1. from langchain_core.tools import StructuredTool
  2. def multiply(a: int, b: int):
  3.     """multiply two numbers"""
  4.     return a * b
  5. async def amultiply(a: int, b: int):
  6.     """amultiply two numbers"""
  7.     return a * b
  8. calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)
  9. print(calculator.name, "|",calculator.description, "|",calculator.args)
复制代码
输出

multiply | multiply two numbers | {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
3、Runnables方法

代码实现
  1. from langchain_core.language_models import GenericFakeChatModel
  2. from langchain_core.output_parsers import StrOutputParser
  3. from langchain_core.prompts import ChatPromptTemplate
  4. prompt = ChatPromptTemplate.from_messages(
  5.     [("human", "Hello. Please respond in the style of {answer_style}.")]
  6. )
  7. llm = GenericFakeChatModel(messages=iter(["hello matey"]))
  8. chain = prompt | llm | StrOutputParser()
  9. as_tool = chain.as_tool(
  10.     name="Style responder", description="Description of when to use tool."
  11. )
  12. print(as_tool.name, "|",as_tool.description,"|", as_tool.args)
复制代码
输出

/tools/create_tools.py:41: LangChainBetaWarning: This API is in beta and may change in the future.
   as_tool = chain.as_tool(
Style responder | Description of when to use tool. | {'answer_style': {'title': 'Answer Style', 'type': 'string'}}
分析

显然,提示了警告,说明了该API在将来可能发生变化
4、BaseTool子类

这是最灵活的方法,它提供了最大的控制程度,但需要更多的代码。
代码实现
  1. from typing import Optional, Type
  2. from langchain_core.callbacks import (
  3.     AsyncCallbackManagerForToolRun,
  4.     CallbackManagerForToolRun,
  5. )
  6. from langchain_core.tools import BaseTool
  7. from pydantic import BaseModel, Field
  8. class CalculatorInput(BaseModel):
  9.     a: int = Field(description="first number")
  10.     b: int = Field(description="second number")
  11. class CustomCalculatorTool(BaseTool):
  12.     name: str = "Calculator"
  13.     description: str = "useful for when you need to answer questions about math"
  14.     args_schema: Type[BaseModel] = CalculatorInput
  15.     return_direct: bool = True
  16.     def _run(
  17.         self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None
  18.     ) -> str:
  19.         """Use the tool."""
  20.         return a * b
  21.     async def _arun(
  22.         self,
  23.         a: int,
  24.         b: int,
  25.         run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
  26.     ) -> str:
  27.         """Use the tool asynchronously."""
  28.         return self._run(a, b, run_manager=run_manager.get_sync())
  29.    
  30. tool_case = CustomCalculatorTool()
  31. print(tool_case.name, tool_case.description, tool_case.args)
复制代码
输出

Calculator useful for when you need to answer questions about math {'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
分析

该方法为创建工具中最为灵活的方式,当然,比其他方式也更复杂,根据自己的需求选择合适的创建方式
五、多工具调用

文章开篇使用遍历的方式实现工具调用的详细说明,实际上工具是可以连续调用的
代码实现
  1. from langchain_core.messages import HumanMessage
  2. from langchain_core.tools import tool
  3. from langchain_openai import ChatOpenAI
  4. llm = ChatOpenAI(
  5.     base_url="https://lxxxxx.enovo.com/v1/",
  6.     api_key="sxxxxxxxwW",
  7.     model_name="qwen2.5-instruct"
  8.     )
  9. @tool
  10. def get_weather(location: str):
  11.     """获取当前城市天气。"""
  12.     if location in ["上海", "北京"]:
  13.         return f"当前{location}天气晴朗,温度为21℃"
  14.     else:
  15.         return "该城市未知,不在地球上"
  16. @tool
  17. def get_coolest_cities():
  18.     """获取中国最冷的城市"""
  19.     return "黑龙江漠河气温在-30℃"
  20. tools = [get_weather, get_coolest_cities]
  21. llm_with_tools = llm.bind_tools(tools)
  22. messages = "北京的天气怎么样?哈尔滨的天气怎么样?中国最冷的城市?唐朝持续了多少年?"
  23. ai_msg = llm_with_tools.invoke([HumanMessage(messages)])
  24. if ai_msg.content:
  25.     print(ai_msg.content, "--未调用工具")
  26. for tool_call in ai_msg.tool_calls:
  27.     selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
  28.     tool_msg = selected_tool.invoke(tool_call)
  29.     print(f"{tool_msg.content:<30} {tool_call.get("name")}")
复制代码
输出

对于唐朝持续了多少年的问题,唐朝从618年到907年,共持续了289年。 --未调用工具
当前北京天气晴朗,温度为21℃                get_weather
该城市未知,不在地球上                    get_weather
黑龙江漠河气温在-30℃                   get_coolest_cities
分析

一般而言,工具调用后,即执行如下代码后:
  1. llm_with_tools.invoke([HumanMessage(messages)])
复制代码
返回值为空,即content='',而tool_calls属性中存在tool的name、arguments等信息,如下:


所以,直接产生内容,则说明没有调用工具。工具产生的内容都是在二次调用,如下:
  1. selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
  2. tool_msg = selected_tool.invoke(tool_call)
复制代码
只有工具在二次调用后,才会生成工具返回的信息
代码中唐朝持续了多少年?这个问题与工具无关,其余三个问题均与工具相关(即这三个问题都会调用工具),所以当四个问题为合并为一个字符串输入到大模型时,大模型的响应中content会有唐朝相关的内容,而工具中会有三个,如下图:
  1. AIMessage(content='对于唐朝持续了多少年的提问,我需要直接回答,不需要调用工具。唐朝从公元618年到公元907年,持续了大约290年。请继续您的其他查询。', additional_kwargs={'tool_calls': [{'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'function': {'arguments': '{"location": "北京"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'function': {'arguments': '{"location": "哈尔滨"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'function': {'arguments': '{}', 'name': 'get_coolest_cities'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 105, 'prompt_tokens': 301, 'total_tokens': 406, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen2.5-instruct', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-4c888156-f1a5-48fd-bf2d-806c0e1b501c-0', tool_calls=[{'name': 'get_weather', 'args': {'location': '北京'}, 'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'location': '哈尔滨'}, 'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'type': 'tool_call'}, {'name': 'get_coolest_cities', 'args': {}, 'id': 'call_9e32c132-3855-4b77-b5bb-018ea22ca1c1', 'type': 'tool_call'}], usage_metadata={'input_tokens': 301, 'output_tokens': 105, 'total_tokens': 406, 'input_token_details': {}, 'output_token_details': {}})
复制代码


6、总结

工具也有流式、异步等方式,工具的回调,langchain也给出了处理方案。此处仅为展示tool的用法,不在过多叙述




欢迎光临 AI创想 (http://llms-ai.com/) Powered by Discuz! X3.4