【LangChain】使用聊天模型调用工具

在 LangChain 中,使用聊天模型调用工具是一种强大的机制,允许语言模型(LLM)通过动态选择和执行外部工具来完成复杂任务。工具调用(Tool Calling)结合了聊天模型的自然语言理解能力和外部工具(如搜索、计算、API 调用等)的功能,使模型能够处理需要外部数据或特定操作的任务。这在代理(Agents)、问答系统或自动化工作流中尤为重要。

以下是对 LangChain 中使用聊天模型调用工具的详细介绍,涵盖其定义、工作原理、实现方式、应用场景、代码示例、优化建议以及与生态系统的结合。


1. 什么是聊天模型调用工具?

聊天模型调用工具是指利用支持工具调用功能的语言模型(如 OpenAI 的 GPT-4、Anthropic 的 Claude)动态选择并执行外部工具,以完成用户指定的任务。工具调用通常涉及:

  • 工具定义:指定工具的名称、描述和输入输出格式。
  • 模型推理:模型根据用户输入和工具描述决定是否调用工具、调用哪个工具以及传递什么参数。
  • 工具执行:模型生成工具调用指令,LangChain 执行工具并返回结果。
  • 结果整合:模型将工具结果整合到最终响应中。

工具调用的核心优势:

  • 动态性:模型根据任务上下文选择合适的工具。
  • 结构化交互:工具输入和输出通常为结构化格式(如 JSON),便于解析。
  • 扩展能力:通过工具弥补模型在实时数据、计算或领域知识上的不足。

2. 工具调用的工作原理

工具调用的工作流程如下:

  1. 定义工具
    • 使用 Tool 类、@tool 装饰器或 StructuredTool 定义工具,包括名称、描述和输入模式(通常基于 Pydantic 或 JSON Schema)。
  2. 绑定工具到模型
    • 使用 bind_tools 方法将工具注册到聊天模型,模型理解工具的功能。
  3. 用户输入
    • 用户提供查询,模型分析任务需求。
  4. 推理与工具选择
    • 模型根据提示和工具描述决定是否调用工具以及调用哪个工具。
    • 生成工具调用指令(通常为 JSON 格式,包含工具名称和参数)。
  5. 工具执行
    • LangChain 执行工具,传递模型提供的参数,获取结果。
  6. 结果处理
    • 工具结果反馈给模型,模型可能继续推理或直接生成最终输出。
  7. 输出返回
    • 模型整合工具结果,生成用户期望的响应。

关键组件:

  • 聊天模型:支持工具调用的模型(如 OpenAI、Anthropic)。
  • 工具:定义外部功能(如搜索、计算)。
  • 绑定机制bind_tools 或代理框架。
  • 解析器:处理工具调用和结果(如 JsonOutputParser)。

3. LangChain 中工具调用的实现方式

LangChain 提供了多种方式实现聊天模型调用工具,以下是主要方法的详细说明:

(1) 使用 bind_tools

  • 功能:直接将工具绑定到聊天模型,模型生成工具调用指令。
  • 特点
    • 适合简单工具调用场景。
    • 模型直接输出结构化工具调用(如 JSON)。
    • 需要手动处理工具执行和结果反馈。
  • 适用场景
    • 单次工具调用。
    • 结构化输出任务。
  • 示例
    from langchain_openai import ChatOpenAI
    from langchain_core.tools import tool
    from pydantic import BaseModel
    
    # 定义工具
    class CalcInput(BaseModel):
        a: float
        b: float
    
    @tool
    def add_numbers(input: CalcInput) -> float:
        """将两个数字相加"""
        return input.a + input.b
    
    # 初始化模型并绑定工具
    llm = ChatOpenAI(api_key="your-openai-key").bind_tools([add_numbers])
    
    # 调用模型
    response = llm.invoke("请将 5 和 3 相加")
    if response.tool_calls:
        tool_call = response.tool_calls[0]
        tool_name = tool_call["name"]
        tool_args = tool_call["args"]
        result = add_numbers.invoke(tool_args)
        print(f"工具 {tool_name} 结果:{result}")
    
  • 输出
    工具 add_numbers 结果:8.0
    
  • 注意
    • 模型需要支持工具调用(如 gpt-4-1106-preview 或更高版本)。
    • 需手动执行工具并处理结果。

(2) 使用 AgentExecutor

  • 功能:通过代理框架自动管理工具调用和推理循环。
  • 特点
    • 适合多步骤任务,代理自动选择和执行工具。
    • 支持 ReAct 或 OpenAI Functions 推理框架。
    • 自动处理工具结果和模型交互。
  • 适用场景
    • 复杂任务需要多次工具调用。
    • 动态决策场景。
  • 示例
    from langchain_openai import ChatOpenAI
    from langchain.agents import initialize_agent, AgentType
    from langchain_core.tools import Tool
    from langchain_community.tools import DuckDuckGoSearchRun
    
    # 定义计算工具
    def calculator(expression: str) -> str:
        return str(eval(expression))
    
    calc_tool = Tool(
        name="Calculator",
        func=calculator,
        description="执行数学计算,输入为数学表达(如 '2 + 2')。"
    )
    
    # 初始化搜索工具
    search_tool = DuckDuckGoSearchRun()
    
    # 工具列表
    tools = [calc_tool, search_tool]
    
    # 初始化代理
    llm = ChatOpenAI(api_key="your-openai-key")
    agent = initialize_agent(
        tools=tools,
        llm=llm,
        agent_type=AgentType.OPENAI_FUNCTIONS,
        verbose=True
    )
    
    # 执行任务
    result = agent.invoke("计算 5 + 3 并搜索量子计算的最新进展")
    print(result["output"])
    
  • 输出
    计算结果为 8,量子计算的最新进展包括...
    
  • 注意
    • 代理自动管理推理循环,适合复杂任务。
    • 需优化工具描述以提高选择准确性。

(3) 使用 StructuredTool

  • 功能:定义结构化工具,指定输入和输出格式。
  • 特点
    • 使用 Pydantic 模型定义输入,确保参数验证。
    • 适合需要严格输入格式的工具。
    • 支持代理和直接调用。
  • 适用场景
    • 复杂工具调用。
    • 需要结构化输入输出的场景。
  • 示例
    from langchain_openai import ChatOpenAI
    from langchain_core.tools import StructuredTool
    from pydantic import BaseModel
    
    # 定义输入和输出结构
    class CalcInput(BaseModel):
        a: float
        b: float
    
    def add_numbers(input: CalcInput) -> float:
        return input.a + input.b
    
    calc_tool = StructuredTool.from_function(
        func=add_numbers,
        name="Adder",
        description="将两个数字相加"
    )
    
    # 初始化模型并绑定工具
    llm = ChatOpenAI(api_key="your-openai-key").bind_tools([calc_tool])
    
    # 调用
    response = llm.invoke("请将 5 和 3 相加")
    if response.tool_calls:
        tool_call = response.tool_calls[0]
        result = calc_tool.invoke(tool_call["args"])
        print(f"结果:{result}")
    
  • 输出
    结果:8.0
    

(4) 使用 LangGraph 自定义工具调用

  • 功能:通过 LangGraph 构建自定义工具调用工作流。
  • 特点
    • 高度灵活,适合复杂逻辑。
    • 支持状态管理和多步骤推理。
    • 需要更多开发工作。
  • 适用场景
    • 自定义代理逻辑。
    • 复杂多工具交互。
  • 示例
    from langchain_openai import ChatOpenAI
    from langchain_core.tools import tool
    from langgraph.graph import StateGraph, END
    from typing import TypedDict, Annotated
    
    # 定义状态
    class AgentState(TypedDict):
        messages: list
    
    # 定义工具
    @tool
    def calculator(expression: str) -> str:
        """执行数学计算"""
        return str(eval(expression))
    
    # 初始化模型
    llm = ChatOpenAI(api_key="your-openai-key").bind_tools([calculator])
    
    # 定义节点
    def call_model(state: AgentState):
        response = llm.invoke(state["messages"])
        return {"messages": state["messages"] + [response]}
    
    def call_tool(state: AgentState):
        last_message = state["messages"][-1]
        if last_message.tool_calls:
            tool_call = last_message.tool_calls[0]
            result = calculator.invoke(tool_call["args"])
            return {"messages": state["messages"] + [{"content": str(result)}]}
        return {"messages": state["messages"]}
    
    # 构建图
    workflow = StateGraph(AgentState)
    workflow.add_node("model", call_model)
    workflow.add_node("tool", call_tool)
    workflow.set_entry_point("model")
    workflow.add_conditional_edges(
        "model",
        lambda state: "tool" if state["messages"][-1].tool_calls else END
    )
    workflow.add_edge("tool", END)
    graph = workflow.compile()
    
    # 调用
    result = graph.invoke({"messages": [{"role": "user", "content": "计算 2 + 2"}]})
    print(result["messages"][-1]["content"])
    
  • 输出
    4
    

4. 工具调用的应用场景

工具调用在以下场景中广泛应用:

  1. 实时信息检索
    • 使用搜索工具(如 DuckDuckGoSearchRun)获取最新数据。
    • 示例:回答“今天的新闻”。
  2. 数学与计算
    • 使用计算工具(如 PythonREPLTool)解决数学问题。
    • 示例:计算复杂公式。
  3. 数据库查询
    • 使用数据库工具查询结构化数据。
    • 示例:从 SQL 数据库提取销售数据。
  4. API 集成
    • 调用外部 API 执行操作。
    • 示例:查询天气 API。
  5. 自动化工作流
    • 触发外部动作(如发送邮件)。
    • 示例:结合 ZapierToolkit
  6. 结构化数据处理
    • 生成或解析 JSON 数据。
    • 示例:提取用户输入中的实体。

5. 工具调用的优化建议

(1) 提高工具选择准确性

  • 清晰的工具描述
    • 描述应明确工具功能和输入格式。
    • 示例:description="执行数学计算,输入为字符串表达式,如 '2 + 2'。"
  • 结构化输入
    • 使用 Pydantic 模型定义输入格式。
    class SearchInput(BaseModel):
        query: str
    
  • 限制工具数量
    • 通常少于 10 个工具,避免模型选择困难。

(2) 提高性能

  • 缓存工具结果
    • 使用 LangChain 的缓存机制。
    from langchain.globals import set_llm_cache
    from langchain.cache import SQLiteCache
    
    set_llm_cache(SQLiteCache(database_path="cache.db"))
    
  • 异步工具
    • 实现异步工具以支持高并发。
    @tool
    async def async_search(query: str) -> str:
        import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.get(f"https://api.example.com?q={query}") as resp:
                return await resp.text()
    
  • 批量处理
    • 批量调用工具,减少开销。
    results = agent.batch([{"input": "q1"}, {"input": "q2"}])
    

(3) 错误处理

  • 回退机制
    • 为工具配置回退。
    calc_tool_with_fallback = calc_tool.with_fallbacks([backup_calc_tool])
    
  • 异常捕获
    • 在工具中处理异常。
    @tool
    def safe_calculator(expression: str) -> str:
        try:
            return str(eval(expression))
        except Exception as e:
            return f"计算错误:{e}"
    
  • 验证输入
    • 使用 Pydantic 验证工具输入。
    class CalcInput(BaseModel):
        a: float
        b: float
    

(4) 监控与调试

  • 回调
    • 使用回调记录工具调用。
    from langchain_core.callbacks import BaseCallbackHandler
    
    class ToolCallback(BaseCallbackHandler):
        def on_tool_start(self, serialized, input_str, **kwargs):
            print(f"工具 {serialized['name']} 开始,输入:{input_str}")
    
  • LangSmith
    • 分析工具调用性能和准确性。
    from langsmith import Client
    agent.invoke(input, config={"callbacks": [Client(api_key="your-langsmith-key")]})
    

(5) 工具输出处理

  • 结构化输出
    • 确保工具返回结构化数据。
    @tool
    def search(query: str) -> dict:
        return {"query": query, "results": ["result1", "result2"]}
    
  • 后处理
    • 使用解析器规范化工具输出。
    from langchain_core.output_parsers import JsonOutputParser
    
    parser = JsonOutputParser()
    chain = llm | parser
    

6. 注意事项

  • 模型支持
    • 工具调用需要模型支持(如 OpenAI 的 gpt-4 或 Anthropic 的 Claude)。
    • 检查模型文档是否支持 tool_calls
  • 工具描述
    • 不清晰的描述可能导致模型误选工具。
    • 使用具体、简洁的描述。
  • 性能
    • 多次工具调用可能增加延迟,需优化工具执行。
    • 设置 max_iterations 限制代理循环。
    agent = initialize_agent(tools, llm, max_iterations=5)
    
  • 安全性
    • 避免执行不安全的工具(如未沙箱化的 eval)。
    • 验证工具输入和输出,防止注入攻击。
  • 成本
    • 工具调用和模型推理可能产生费用。
    • 使用缓存和低成本工具优化。

7. 与其他模块的结合

  • 代理(Agents)
    • 工具调用是代理的核心,AgentExecutor 自动管理。
    agent = initialize_agent(tools, llm, agent_type=AgentType.OPENAI_FUNCTIONS)
    
  • 结构化输出
    • 工具调用通常生成结构化输出。
    llm.with_structured_output(schema=Answer)
    
  • 回调(Callbacks)
    • 监控工具调用过程。
    agent.invoke(input, config={"callbacks": [ToolCallback()]})
    
  • 缓存(Cache)
    • 缓存工具结果,减少重复调用。
    set_llm_cache(SQLiteCache(database_path="cache.db"))
    
  • 检索器(Retrievers)
    • 结合检索器为工具提供上下文。
    from langchain.chains import ConversationalRetrievalChain
    qa_chain = ConversationalRetrievalChain.from_llm(llm, retriever, tools=tools)
    
  • LangSmith
    • 分析工具调用性能和准确性。

8. 综合示例:多工具代理

以下是一个结合搜索和计算工具的代理示例:

from langchain_openai import ChatOpenAI
from langchain.agents import initialize_agent, AgentType
from langchain_core.tools import Tool, StructuredTool
from langchain_community.tools import DuckDuckGoSearchRun
from pydantic import BaseModel
from langchain_core.callbacks import StdOutCallbackHandler

# 定义计算工具
class CalcInput(BaseModel):
    a: float
    b: float

def add_numbers(input: CalcInput) -> float:
    return input.a + input.b

calc_tool = StructuredTool.from_function(
    func=add_numbers,
    name="Adder",
    description="将两个数字相加"
)

# 初始化搜索工具
search_tool = DuckDuckGoSearchRun()

# 工具列表
tools = [calc_tool, search_tool]

# 初始化代理
llm = ChatOpenAI(api_key="your-openai-key")
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.OPENAI_FUNCTIONS,
    verbose=True,
    callbacks=[StdOutCallbackHandler()]
)

# 执行任务
result = agent.invoke("计算 5 + 3 并搜索量子计算的最新进展")
print(result["output"])

输出

[AgentExecutor] 正在执行...
[Tool: Adder] 输入:{'a': 5, 'b': 3}
[Tool Output] 8.0
[Tool: DuckDuckGoSearch] 输入:量子计算的最新进展
[Tool Output] 量子计算的最新研究...
[Final Answer] 计算结果为 8,量子计算的最新进展包括...

9. 学习资源


10. 总结

  • 定义:聊天模型调用工具通过动态选择和执行外部工具扩展模型能力。
  • 实现方式
    • bind_tools:直接绑定工具。
    • AgentExecutor:自动管理工具调用。
    • StructuredTool:定义结构化工具。
    • LangGraph:自定义工作流。
  • 工作原理:定义工具 → 绑定模型 → 推理选择 → 执行工具 → 整合结果。
  • 应用场景:信息检索、计算、数据库查询、API 集成、自动化、数据处理。
  • 优化点:工具选择、性能、错误处理、监控、输出处理。
  • 注意事项:模型支持、工具描述、性能、安全性、成本。
### LangChain 函数调用工具调用 #### 函数调用LangChain框架内,函数调用指的是直接集成特定功能到LLM(大型语言模型)的工作流程中。当用户发出请求时,系统能够解析该请求并自动执行相应的预定义函数[^1]。例如,在Llama 2环境中设置了一个用于获取当前时间戳的简单Python函数: ```python from datetime import datetime def get_current_timestamp(): """返回当前的时间戳""" now = datetime.now() timestamp = now.strftime("%Y-%m-%d %H:%M:%S") return {"timestamp": timestamp} ``` 这种类型的函数可以直接被聊天机器人调用来响应用户的即时需求。 #### 工具调用 相比之下,工具调用涉及更复杂的过程,通常是为了实现某些高级特性而设计的功能模块集合。这些工具可能包括但不限于搜索引擎、数据库查询接口或其他第三方服务API。通过配置`render_text_description`这样的组件,开发者可以让代理理解如何解释来自不同源的数据描述,并据此采取行动[^2]。 以Tavily搜索为例,这是一个具体的外部资源检索插件,允许代理根据输入的问题去互联网上查找相关信息。为了使这一过程更加智能化,可以通过编写自定义逻辑来指导何时以及怎样触发此类操作[^3]。 #### 实际应用对比 对于简单的计算任务或是内部状态更新来说,采用内置函数可能是最简便的选择。然而,如果目标是增强机器人的交互能力,使其能处理更为广泛的任务,则应该考虑引入更多的专用工具支持。下面是一个展示两者差异的小例子: ##### 内置函数示例 假设有一个场景是要让AI助手报告今天的日期: ```python import langchain as lc # 定义一个简单的函数 def today_date(): from datetime import date return str(date.today()) lc.register_function(today_date, "today_date") response = lc.call_function("what's the date today?", ["today_date"]) print(response) ``` ##### 外部工具示例 再看另一个情况,即利用Wikipedia API来进行知识型问答: ```python import wikipediaapi wiki_wiki = wikipediaapi.Wikipedia('en') def search_wikipedia(query): page_py = wiki_wiki.page(query) if not page_py.exists(): return f"No results found for '{query}'" summary = page_py.summary[0:240] + '...' url = page_py.fullurl result = { "summary": summary, "link": url } return result lc.add_tool(search_wikipedia) question = "Tell me about Python programming language." answer = lc.use_tool(question, tool="search_wikipedia") print(f"{answer['summary']}\nRead more at {answer['link']}") ``` 在这个案例里,不仅实现了信息的获取,还提供了额外的学习材料链接给用户。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彬彬侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值