LangChain 入门学习第三篇-工具调用和 Agent

前两篇已经完成了基础模型调用、PromptTemplate 和结构化输出。

这一篇开始进入 LangChain 里非常关键的一块:工具调用和 Agent。

本文对应代码位于 code/langchain-demo/chapter03。这一章依然保持独立目录,避免后续代码改动影响前面文章的示例。

本篇目标

在开始之前,先明确一下这篇要解决的问题。

前两篇里,模型主要是在“回答问题”。即使第二篇加了结构化输出,本质上也还是让模型按照格式生成内容。

但真实应用里,经常希望模型可以做一些自己不擅长,或者不应该靠猜的事情,比如:

  1. 计算两个数字相乘。
  2. 统计文本长度。
  3. 查询数据库。
  4. 读取文件。
  5. 调用某个业务接口。

这里就需要工具调用。

第三篇先不接数据库、文件系统或者网络搜索,只定义几个结果确定的小工具。这样能把注意力放在 LangChain 的工具调用机制上。

本篇主要完成两件事:

  1. 使用 bind_tools 观察模型如何提出工具调用。
  2. 使用 create_agent 让 Agent 自动执行工具调用流程。

代码结构

第三篇的代码结构如下:

code/langchain-demo/
  chapter03/
    __init__.py
    config.py
    llm.py
    simple_tools.py
    tool_calling_demo.py
    agent_demo.py

其中:

  • config.py:读取 .env
  • llm.py:创建 ChatOpenAI 模型对象。
  • simple_tools.py:定义工具。
  • tool_calling_demo.py:演示 bind_tools 和手动执行工具。
  • agent_demo.py:演示 create_agent 自动执行工具调用流程。

config.pyllm.py 和前面章节差不多,这里就不重复展开了。重点看工具和调用流程。

定义工具

工具代码放在 chapter03/simple_tools.py

from langchain_core.tools import tool


@tool
def count_characters(text: str) -> int:
    """统计一段文本中的字符数量。"""

    return len(text)


@tool
def count_words(text: str) -> int:
    """统计英文文本中的单词数量。"""

    return len(text.split())


@tool
def multiply(a: int, b: int) -> int:
    """计算两个整数相乘的结果。"""

    return a * b


# 第三章只放几个结果确定的工具,方便观察模型有没有真的发起工具调用。
TOOLS = [count_characters, count_words, multiply]

这里用了 @tool 装饰器,把普通 Python 函数包装成 LangChain 可以识别的工具。

有几个点需要注意:

  1. 函数名会成为工具名。
  2. 函数参数会变成工具入参。
  3. docstring 会作为工具说明提供给模型。

也就是说,模型判断是否调用工具时,不只是看函数名,也会参考工具说明。

所以工具的 docstring 不要随便写,最好直接说明这个工具能做什么。

这一篇只定义了三个工具:

  • count_characters:统计字符数量。
  • count_words:统计英文单词数量。
  • multiply:计算两个整数相乘。

这些工具都非常简单,但结果确定,适合用来观察工具调用过程。

使用 bind_tools

接下来先看比较底层的工具调用方式。

文件是 chapter03/tool_calling_demo.py

import json
import sys

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage

from chapter03.config import load_environment
from chapter03.llm import create_chat_model
from chapter03.simple_tools import TOOLS


DEFAULT_QUESTION = (
    "请计算 23 乘以 19,"
    "再统计文本 LangChain tool calling 的字符数,最后汇总结果。"
)

这个 demo 的默认问题里有两个任务:

  1. 计算 23 * 19
  2. 统计 LangChain tool calling 的字符数。

这两个任务正好分别对应 multiplycount_characters 工具。

核心逻辑如下:

tools_by_name = {item.name: item for item in TOOLS}

# bind_tools 只是把“有哪些工具可以用”告诉模型。
# 这里还不会自动执行工具,模型会先返回 tool_calls。
model = create_chat_model(temperature=0).bind_tools(TOOLS)
messages = [
    SystemMessage(content="你是一个会使用工具解决问题的助手。"),
    HumanMessage(content=question),
]

ai_message = model.invoke(messages)

这里需要重点看 bind_tools

bind_tools 的作用是把工具列表绑定到模型上,让模型知道当前有哪些工具可以使用。

但是要注意:bind_tools 不等于自动执行工具。

调用模型后,模型可能返回 tool_calls,意思是模型觉得接下来应该调用哪些工具,以及工具参数是什么。

这一步可以理解成:

用户问题 -> 模型判断需要什么工具 -> 返回 tool_calls

查看 tool_calls

代码里会把 tool_calls 打印出来:

print("Tool calls:")
print(json.dumps(ai_message.tool_calls, ensure_ascii=False, indent=2))

运行后可以看到类似结果:

[
  {
    "name": "multiply",
    "args": {
      "a": 23,
      "b": 19
    },
    "id": "call_xxx",
    "type": "tool_call"
  },
  {
    "name": "count_characters",
    "args": {
      "text": "LangChain tool calling"
    },
    "id": "call_xxx",
    "type": "tool_call"
  }
]

可以看到,模型没有直接计算,而是返回了两个工具调用请求:

  1. 调用 multiply,参数是 a=23b=19
  2. 调用 count_characters,参数是 text="LangChain tool calling"

这时候工具还没有真正执行。

手动执行工具

接下来代码会手动执行这些工具:

tool_messages: list[ToolMessage] = []

# 手动执行模型请求的工具,这一步能帮助理解 Agent 背后的基本过程。
for tool_call in ai_message.tool_calls:
    tool_name = tool_call["name"]
    tool_args = tool_call["args"]
    selected_tool = tools_by_name[tool_name]
    tool_result = selected_tool.invoke(tool_args)

    print()
    print(f"Executed tool: {tool_name}")
    print(f"Args: {tool_args}")
    print(f"Result: {tool_result}")

    tool_messages.append(
        ToolMessage(
            content=str(tool_result),
            name=tool_name,
            tool_call_id=tool_call.get("id") or tool_name,
        )
    )

这里的流程很清楚:

  1. 根据工具名找到真实工具对象。
  2. 把模型给出的参数传给工具。
  3. 得到工具执行结果。
  4. 把结果包装成 ToolMessage

ToolMessage 的作用是把工具执行结果重新交回给模型。

最后再调用一次模型:

final_response = model.invoke([*messages, ai_message, *tool_messages])

这一次模型就可以根据工具结果生成最终回答。

完整流程就是:

用户问题
  -> 模型返回 tool_calls
  -> Python 执行工具
  -> 工具结果交回模型
  -> 模型总结最终答案

运行工具调用示例

code/langchain-demo 目录下运行:

uv run python -m chapter03.tool_calling_demo

正常输出类似这样:

Question:
请计算 23 乘以 19,再统计文本 LangChain tool calling 的字符数,最后汇总结果。

Tool calls:
[
  {
    "name": "multiply",
    "args": {
      "a": 23,
      "b": 19
    },
    "id": "call_xxx",
    "type": "tool_call"
  },
  {
    "name": "count_characters",
    "args": {
      "text": "LangChain tool calling"
    },
    "id": "call_xxx",
    "type": "tool_call"
  }
]

Executed tool: multiply
Args: {'a': 23, 'b': 19}
Result: 437

Executed tool: count_characters
Args: {'text': 'LangChain tool calling'}
Result: 22

Final answer:
汇总结果:

- 23 × 19 = 437
- 文本 `LangChain tool calling` 的字符数 = 22

通过这个 demo,可以比较直观看到模型和工具之间的关系。

模型负责判断“该调用什么工具、参数是什么”,Python 负责真正执行工具。

Agent 是什么

理解了 bind_tools 后,再看 Agent 就比较容易了。

前面的手动流程里,我们自己写了这些步骤:

  1. 调用模型。
  2. 读取 tool_calls
  3. 执行工具。
  4. 把工具结果传回模型。
  5. 再让模型生成最终回答。

Agent 做的事情,就是把这套流程自动化。

可以简单理解为:

Agent = 模型 + 工具 + 执行循环

当然,真实 Agent 能做的事情会更多,但入门时先这样理解就够了。

使用 create_agent

Agent 示例放在 chapter03/agent_demo.py

import sys

from langchain.agents import create_agent

from chapter03.config import load_environment
from chapter03.llm import create_chat_model
from chapter03.simple_tools import TOOLS


DEFAULT_QUESTION = (
    "请计算 23 乘以 19,"
    "再统计文本 LangChain tool calling 的字符数,最后给出简洁答案。"
)

核心代码如下:

# Agent 会在模型和工具之间做执行循环:
# 模型决定调用哪个工具,工具返回结果后,Agent 再把结果交给模型总结。
agent = create_agent(
    model=create_chat_model(temperature=0),
    tools=TOOLS,
    system_prompt="你是一个会优先使用工具解决计算和统计问题的助手。",
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": question,
            }
        ]
    }
)

这里使用的是 create_agent

bind_tools 相比,create_agent 更进一步,它会自动完成工具调用循环。

也就是说,你不用自己遍历 tool_calls,也不用自己创建 ToolMessage,Agent 会帮你处理。

为了看清楚 Agent 中间做了什么,代码里把返回的消息列表都打印出来:

for message in result["messages"]:
    message_type = getattr(message, "type", message.__class__.__name__)
    content = getattr(message, "content", "")
    tool_calls = getattr(message, "tool_calls", None)

    print(f"- {message_type}: {content}")
    if tool_calls:
        print(f"  tool_calls: {tool_calls}")

这样可以看到人类消息、模型消息、工具消息和最终回答。

运行 Agent 示例

code/langchain-demo 目录下运行:

uv run python -m chapter03.agent_demo

正常输出类似这样:

Question:
请计算 23 乘以 19,再统计文本 LangChain tool calling 的字符数,最后给出简洁答案。

Messages:
- human: 请计算 23 乘以 19,再统计文本 LangChain tool calling 的字符数,最后给出简洁答案。
- ai:
  tool_calls: [{'name': 'multiply', 'args': {'a': 23, 'b': 19}, ...}]
- tool: 437
- tool: 22
- ai: 23 × 19 = 437;文本字符数 = 22。

Final answer:
23 × 19 = 437;文本字符数 = 22。

这个结果和手动工具调用示例做的是同一件事,只是执行流程交给 Agent 处理了。

bind_tools 和 Agent 的区别

这里简单总结一下两者区别。

bind_tools 更底层:

  1. 把工具能力告诉模型。
  2. 模型返回 tool_calls
  3. 工具执行需要自己写代码。
  4. 更适合学习和调试工具调用流程。

create_agent 更完整:

  1. 把模型和工具组合起来。
  2. 自动执行工具调用循环。
  3. 自动把工具结果交回模型。
  4. 更适合写真正的业务流程。

所以这一篇先写 tool_calling_demo.py,再写 agent_demo.py

只有先看过手动工具调用过程,Agent 做了什么才不会显得太神秘。

常见问题

模型不调用工具怎么办

工具调用是模型自己决定的。

如果模型没有返回 tool_calls,可以从几个方向排查:

  1. 问题是否真的需要工具。
  2. 工具 docstring 是否描述清楚。
  3. system prompt 是否明确要求优先使用工具。
  4. 当前模型服务是否支持 tool calling。

本篇示例里使用的是计算和字符统计,目的就是让模型更容易判断应该调用工具。

为什么不直接让模型计算

简单数字模型也可能直接算对,但这不是重点。

工具调用的核心价值是:把确定性的事情交给确定性的工具。

比如计算、查数据库、调接口,这些事情不应该靠模型猜,而应该由程序执行。

模型更适合做的是理解用户意图、选择工具、组织最终回答。

第三章为什么不用真实外部工具

这一篇没有接文件、数据库和网络搜索,是刻意的。

因为第三章要先讲清楚工具调用机制本身。

如果一上来就接很多外部系统,读者很容易把注意力放在文件路径、接口鉴权、网络问题上,反而忽略了 LangChain 里的工具调用流程。

等工具调用理解清楚后,后面再接 RAG 或博客文章检索,会顺很多。

小结

这一篇完成了 LangChain 工具调用和 Agent 的入门实践:

  1. 使用 @tool 把普通 Python 函数包装成工具。
  2. 使用 bind_tools 把工具绑定到模型。
  3. 观察模型返回的 tool_calls
  4. 手动执行工具,并通过 ToolMessage 把结果交回模型。
  5. 使用 create_agent 自动完成工具调用流程。

到这里,模型就不只是生成文本了,它已经可以通过工具完成一些确定性的任务。

下一篇可以继续往 RAG 方向走,尝试把博客文章作为知识库,让模型基于本地文章内容回答问题。

参考

  1. https://docs.langchain.com/oss/python/langchain/overview
  2. https://docs.langchain.com/oss/python/langchain/agents
  3. https://python.langchain.com/api_reference/core/tools/langchain_core.tools.convert.tool.html
  4. https://python.langchain.com/api_reference/core/messages/langchain_core.messages.tool.ToolMessage.html
打开链接下载源码: https://pan.quark.cn/s/05da658a2377 在信息技术领域中,输入法作为操作系统的一个核心构成部分,赋予了用户利用键盘输入多语种文字的能力。"ime-日语输入法安装必须文件"这一资源是一套为日语输入法部署而设计、包含全部必要元素的集成包,对于那些需要在个人计算机上执行日语文字输入的操作者而言具有不可替代的作用。接下来将深入剖析其中所包含的核心概念。 IME(Input Method Editor,输入法编辑器)是操作系统内的一种软件支持服务,其功能在于为非拉丁字符环境提供文字输入方案,例如中文、日文、韩文等文字系统。在日本地区,IME通常被用来将罗马字(罗马拼音)形式的输入转换为平假名、片假名乃至汉字。此压缩文件内含的日语IME文件夹即为执行这一转换功能的关键要素。 kbdjpn.dll被视为一个关键的系统性文件,其意指“Japanese Keyboard Layout”(日语键盘布局)。该动态链接库文件负责设定日语键盘的排列方式及快捷操作组合,使用户能够借助常规的QWERTY键盘输入日语文字。倘若缺少这一文件,即便已经安装了日语输入法,依然无法正常显示及输入日语字符。 另外,imjp81k.dll同样是一个重要的系统性构成,它属于日语IME的范畴,全称为“Input Method Japanese for Windows 8.1 and later, Katakana mode”(适用于Windows 8.1及更新版本的日语输入法,片假名模式)。该文件支持日语的片假名输入,是处理日语输入的核心组成部分。在安装或升级日语输入法的过程中,保证imjp81k.dll的准确性与完整性显得尤为关键。 压缩包所含的"Window...
内容概要:本文研究了基于DPWMA调制与正负序分离的ANPC三电平并网逆变器前馈控制策略,旨在解决传统三电平逆变器在谐波抑制、电网不平衡适应性及动态响应方面的技术瓶颈。通过构建融合双极性倍频脉宽调制(DPWMA)、正负序分离锁相控制与电网电压前馈的一体化控制体系,全面优化逆变器的输出波形质量、相位同步精度与抗扰能力。文章深入分析了ANPC三电平拓扑的结构优势,如开关损耗均衡、中点电位可控性强电压利用率高等特点,并设计了包含信号采集、核心控制与调制驱动三层架构的完整控制系统。通过Simulink仿真平台对稳态运行、电网不平衡及动态扰动等多种工况进行验证,结果表明该策略显著降低了总谐波畸变率,提升了锁相精度与系统动态稳定性,有效增强了逆变器在复杂电网环境下的适应能力运行可靠性。; 适合人群:具备电力电子、自动控制及新能源并网相关基础知识,从事新能源发电、微电网、电力系统仿真等领域的科研人员与工程技术人员,特别适合研究生及以上层次的研究者。; 使用场景及目标:①用于提升大功率并网逆变器在电网电压不平衡、谐波干扰动态扰动等复杂工况下的运行性能;②为高电能质量要求的应用场景提供先进控制解决方案;③支持科研仿真、论文复现与实际工程项目中的高性能并网控制系统设计与优化。; 阅读建议:建议结合提供的Simulink仿真模型进行实践操作,重点理解DPWMA调制机制、正负序分离锁相算法与电网电压前馈控制之间的协同作用,按照文档结构系统学习,并与传统控制策略进行对比分析,以深入掌握改进策略的技术优势与实现细节。
代码下载链接: https://pan.quark.cn/s/d9794888cbc0 ### G代码经典解释程序知识点详解 #### 一、引言 随着数控技术的持续进步,尤其是开放式数控系统的广泛应用,软件层面的设计在数控领域占据了核心地位。G代码作为数控机床编程的基础语言,在自动化生产流程中发挥着不可或缺的作用。本文的核心内容是关于一个基于Linux平台、采用C语言开发的G代码解释程序的设计思路及其具体实现。 #### 二、G代码解释器概述 **1. 设计背景** - 当前数控技术发展的主要方向是开放式数控系统,这类系统具备出色的可扩展能力、良好的移植性、高度的互换性以及优异的互操作性等优势。 - 计算机硬件技术的快速发展使得在PC平台上构建数控系统成为可能,进而推动了全软件式数控系统的普及。 **2. G代码解释器的重要性** - G代码解释器在全软件式数控系统中是至关重要的组成部分,其主要职责是将G代码转化为数控系统能够识别的数据格式。 - 为了提升数控系统的开放程度,G代码解释器的设计必须兼顾开放性灵活性。 #### 三、G代码解释器设计与实现 **1. 总体结构设计** - G代码解释器主要由两个核心部分构成:G代码关键字函数表(GKFT)G代码分组(GG)。 - GKFT用于解析G代码中的关键字,它是解释器的核心骨架;而GG则是语法检查的基础框架。 **2. G代码关键字函数表(GKFT)** - GKFT是一种专门用于存储G代码关键字及其关联处理函数的数据结构。 - 解释器通过查询GKFT,能够根据特定的G代码关键字调用相应的处理函数,从而完成对G代码的有效解析。 - 此种设计方法不仅简化了解释器的构建过程,同时也增强了其可扩展性,因为新增功能...
已经博主授权,源码转载自 https://pan.quark.cn/s/458849d2eac8 Microblaze代表由Xilinx公司研发的一款软核处理器,其核心特性在于使用户能够针对FPGA(Field Programmable Gate Array)平台进行嵌入式系统的个性化构建。此“Xinlin中Microblaze的培训教程”致力于辅助学习人员深入理解熟练掌握Microblaze在Xilinx开发环境中的实际应用。 一、Microblaze基础 Microblaze作为一款可配置的32位RISC处理器,具备高度适应性,允许在设计中根据具体需求对性能、功耗及面积进行灵活调整。Microblaze支持多种指令集架构(ISA),涵盖Xtensa-likeClassic两种模式,并且与包括UART、SPI、I2C在内的多种外设接口标准保持兼容。 二、Xilinx ISE与Vivado工具 Xilinx ISE(Integrated Software Environment)是一个用于FPGA系统设计、实现调试的集成开发平台,而Vivado则是一款功能更为先进且全面的工具套件。在本次教程中,学员将学会如何在上述工具中配置执行Microblaze处理器,以及如何开发相关的硬件描述语言(HDL)代码。 三、Microblaze硬件设计 在Xinlin提供的教程里,学员将学习如何在Xilinx FPGA中部署Microblaze处理器。这涉及到选择合适的处理器配置参数,如时钟频率、缓存容量外设接口设置。此外,学员还将接触到创建连接内存模块、中断控制器以及其他必需硬件组件的方法。 四、软件开发 Microblaze的软件开发通常涉及嵌入式编程,采用C或C++语言来...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

音视频开发进阶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值