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、代码实现

from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(base_url="https://llm.xxx.xxxx.com/v1/",openai_api_key="sk-xxxxxxxxxx",model_name="qwen2.5-instruct")

@tool
def get_weather(location: str):
    """获取当前城市天气。"""
    if location in ["上海", "北京"]:
        return f"当前{location}天气晴朗,温度为21℃"
    else:
        return "该城市未知,不在地球上"

@tool
def get_coolest_cities():
    """获取中国最冷的城市"""
    return "黑龙江漠河气温在-30℃"

tools = [get_weather, get_coolest_cities]

llm_with_tools = llm.bind_tools(tools)

messages = ["北京的天气怎么样?", "哈尔滨的天气怎么样?", "中国最冷的城市?", "唐朝持续了多少年?"]

for i in messages:
    ai_msg = llm_with_tools.invoke([HumanMessage(i)])
    if not ai_msg.tool_calls:
        print(ai_msg.content, "--未调用工具")
    for tool_call in ai_msg.tool_calls:
        selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
        tool_msg = selected_tool.invoke(tool_call)
        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装饰器

代码实现
from langchain_core.tools import tool

@tool
def multiply(a: int, b: int):
    """multiply two numbers"""
    return a * b

@tool
async def amultiply(a: int, b: int):
    """amultiply two numbers"""
    return a * b

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、结构化工具

代码实现
from langchain_core.tools import StructuredTool

def multiply(a: int, b: int):
    """multiply two numbers"""
    return a * b

async def amultiply(a: int, b: int):
    """amultiply two numbers"""
    return a * b

calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)

print(calculator.name, "|",calculator.description, "|",calculator.args)
输出

multiply | multiply two numbers | {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

3、Runnables方法

代码实现
from langchain_core.language_models import GenericFakeChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(
    [("human", "Hello. Please respond in the style of {answer_style}.")]
)

llm = GenericFakeChatModel(messages=iter(["hello matey"]))

chain = prompt | llm | StrOutputParser()

as_tool = chain.as_tool(
    name="Style responder", description="Description of when to use tool."
)

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子类

这是最灵活的方法,它提供了最大的控制程度,但需要更多的代码。

代码实现
from typing import Optional, Type
from langchain_core.callbacks import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field

class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

class CustomCalculatorTool(BaseTool):
    name: str = "Calculator"
    description: str = "useful for when you need to answer questions about math"
    args_schema: Type[BaseModel] = CalculatorInput
    return_direct: bool = True

    def _run(
        self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool."""
        return a * b

    async def _arun(
        self,
        a: int,
        b: int,
        run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool asynchronously."""
        return self._run(a, b, run_manager=run_manager.get_sync())
    
tool_case = CustomCalculatorTool()
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'}}

分析

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

五、多工具调用

文章开篇使用遍历的方式实现工具调用的详细说明,实际上工具是可以连续调用的

代码实现

from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    base_url="https://lxxxxx.enovo.com/v1/", 
    api_key="sxxxxxxxwW",
    model_name="qwen2.5-instruct"
    )

@tool
def get_weather(location: str):
    """获取当前城市天气。"""
    if location in ["上海", "北京"]:
        return f"当前{location}天气晴朗,温度为21℃"
    else:
        return "该城市未知,不在地球上"

@tool
def get_coolest_cities():
    """获取中国最冷的城市"""
    return "黑龙江漠河气温在-30℃"

tools = [get_weather, get_coolest_cities]

llm_with_tools = llm.bind_tools(tools)

messages = "北京的天气怎么样?哈尔滨的天气怎么样?中国最冷的城市?唐朝持续了多少年?"

ai_msg = llm_with_tools.invoke([HumanMessage(messages)])
if ai_msg.content:
    print(ai_msg.content, "--未调用工具")
for tool_call in ai_msg.tool_calls:
    selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    print(f"{tool_msg.content:<30} {tool_call.get("name")}")

输出

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

 分析

一般而言,工具调用后,即执行如下代码后:

llm_with_tools.invoke([HumanMessage(messages)])

返回值为空,即content='',而tool_calls属性中存在tool的name、arguments等信息,如下:

所以,直接产生内容,则说明没有调用工具。工具产生的内容都是在二次调用,如下:

selected_tool = {"get_weather": get_weather, "get_coolest_cities": get_coolest_cities}[tool_call["name"].lower()]
tool_msg = selected_tool.invoke(tool_call)

只有工具在二次调用后,才会生成工具返回的信息

 代码中唐朝持续了多少年?这个问题与工具无关,其余三个问题均与工具相关(即这三个问题都会调用工具),所以当四个问题为合并为一个字符串输入到大模型时,大模型的响应中content会有唐朝相关的内容,而工具中会有三个,如下图:

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的用法,不在过多叙述

### LangChain Agent 的概述 LangChain 是一种用于构建基于大型语言模型的应用程序的框架,而其中的核心组件之一就是 **Agent**。Agent 能够根据输入的任务动态调用不同的工具集,并通过迭代的方式完成复杂任务[^1]。 #### 什么是 LangChain Agent? LangChain 中的 Agent 是指能够自主决策并执行操作的一种机制。它可以根据用户的请求自动选择合适的工具(Tools),并通过多次交互逐步解决问题。这种能力使得 Agent 成为了处理多步推理和复杂查询的理想解决方案[^3]。 --- ### LangChain Agent 的工作原理 Agent 的核心功能在于其能够解析用户输入、选择适当的工具以及生成最终响应。以下是它的主要流程: 1. 用户提供自然语言指令。 2. Agent 解析该指令并将其转化为可执行的操作序列。 3. 基于这些操作,Agent 动态调用预定义的一组工具(如搜索引擎、数据库接口或其他外部服务)。 4. 工具返回的结果被反馈到 Agent,后者会继续优化后续动作直至目标达成[^5]。 例如,在 OpenAI 提供的支持函数调用的功能中,默认提示可以通过如下方式加载: ```python from langchain.agents import load_tools, initialize_agent from langchain.llms import OpenAI llm = OpenAI(temperature=0) tools = load_tools(["serpapi", "llm-math"], llm=llm) agent_chain = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True) ``` 上述代码片段展示了如何初始化一个简单的零样本反应描述型代理 (Zero-Shot React Description),此类型的代理无需任何训练即可运行[^2]。 --- ### 示例代码:创建自定义 LangChain Agent 下面是一个完整的例子,演示了如何利用 SerpAPI 和 LLM 数学计算工具来解决涉及网络搜索算术运算的问题。 ```python import os from langchain.agents import Tool, initialize_agent, AgentType from langchain.tools import BaseTool from langchain.utilities import SerpAPIWrapper from langchain.prompts import PromptTemplate from langchain.chains import LLMChain from langchain.llms import OpenAI class CustomSearchTool(BaseTool): name = "custom_search" description = "A tool that uses a custom search engine to find information." def _run(self, query: str) -> str: serp_api_key = os.getenv('SERP_API_KEY') search = SerpAPIWrapper(serpapi_api_key=serp_api_key) result = search.run(query) return f"Searched for {query}. Found: {result}" async def _arun(self, query: str) -> str: raise NotImplementedError() # 初始化LLM实例 llm = OpenAI(model_name="text-davinci-003") # 加载工具 search_tool = CustomSearchTool() tools = [ Tool( name=search_tool.name, func=search_tool._run, description=search_tool.description ) ] # 设置Prompt模板 prompt_template = """Use the following pieces of context to answer the question at the end. {context} Question: {question} Answer:""" prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"]) # 构建链式结构 chain = LLMChain(llm=llm, prompt=prompt) # 初始化Agent agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) # 测试Agent response = agent.run("What is the capital city of France and what's its population?") print(response) ``` 在这个脚本里,我们首先定义了一个名为 `CustomSearchTool` 的类继承自 `BaseTool` 接口;接着配置好所需的参数之后便可以轻松实现跨平台数据抓取等功能[^4]。 --- ### 总结 综上所述,LangChain Agents 不仅提供了强大的灵活性还简化了许多繁琐的手动编码环节,让开发者得以专注于业务逻辑本身而非底层细节。无论是初学者还是资深工程师都能从中受益匪浅。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值