1. 引言
在 LangChain 构建智能体的过程中,我们经常会遇到两种核心机制:工具(Tool) 和 中间件(Middleware)。它们看似都是对请求的某种处理,但触发时机、执行逻辑和应用场景截然不同。
本文将从一个 PDF 文件解析的实际需求出发,深入讲解如何编写符合规范的 LangChain 工具,并对比工具与中间件的关键区别,帮助你设计出更健壮、更智能的 LangChain 应用。
2. LangChain 工具(Tool)的核心要点
2.1 定义与作用
在 LangChain 中,工具 本质上就是一个供大语言模型(LLM)调用的普通 Python 函数。LLM 会根据用户提出的问题,分析工具函数的描述(docstring)和参数定义,自主判断是否需要调用该工具以及如何传递参数。
简单来说,工具赋予了 LLM 连接外部世界、执行具体操作的能力,比如查询数据库、调用 API、解析文件等。
2.2 编写规范
编写一个能被 LangChain 智能体正确识别的工具,必须遵守以下三条规则:
- 明确的类型提示:函数参数和返回值必须添加 Python 类型注解。
- 详细的函数描述 :函数的
docstring是 LLM 理解工具用途的唯一途径,若缺失将直接导致智能体报错或无法调用。 - 优先使用函数注释而非复杂装饰器 :推荐使用
@tool装饰器配合清晰的 docstring,或者直接使用StructuredTool.from_function()来让代码更简洁、可维护。
示例代码:
python
from langchain_core.tools import tool
@tool
def parse_pdf_file(file_path: str) -> str:
"""
解析本地的 PDF 文件,提取其中的文本内容。
适用于需要读取 PDF 中的文字信息的场景。
参数:
file_path: PDF 文件的本地路径或可访问的 URL。
返回:
提取出的文本字符串。
"""
# 实际解析逻辑,此处以伪代码示意
text = f"已解析文件 {file_path} 的文本内容..."
return text
最佳实践:docstring 中应包含工具功能的简要说明、参数解释和返回值描述,LLM 会据此生成正确的调用参数。
2.3 调用流程
当用户提出一个与工具能力相关的问题时,整个调用流程大致如下:
- 用户发送消息,例如:"请帮我解析
report.pdf这个文件"。 - LangChain 智能体将用户输入、可用工具列表及各自描述组合成 Prompt,发送给 LLM。
- LLM 根据 Prompt 理解意图,判断需要调用
parse_pdf_file工具,并生成调用参数file_path="report.pdf"。 - 智能体执行该工具函数,获取返回结果。
- 将工具返回的结果再次反馈给 LLM,由 LLM 组织成自然语言回复用户。
#mermaid-svg-V1Mz28SIl78Okz1Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V1Mz28SIl78Okz1Y .error-icon{fill:#552222;}#mermaid-svg-V1Mz28SIl78Okz1Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V1Mz28SIl78Okz1Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V1Mz28SIl78Okz1Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V1Mz28SIl78Okz1Y .marker.cross{stroke:#333333;}#mermaid-svg-V1Mz28SIl78Okz1Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V1Mz28SIl78Okz1Y p{margin:0;}#mermaid-svg-V1Mz28SIl78Okz1Y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster-label text{fill:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster-label span{color:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster-label span p{background-color:transparent;}#mermaid-svg-V1Mz28SIl78Okz1Y .label text,#mermaid-svg-V1Mz28SIl78Okz1Y span{fill:#333;color:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y .node rect,#mermaid-svg-V1Mz28SIl78Okz1Y .node circle,#mermaid-svg-V1Mz28SIl78Okz1Y .node ellipse,#mermaid-svg-V1Mz28SIl78Okz1Y .node polygon,#mermaid-svg-V1Mz28SIl78Okz1Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V1Mz28SIl78Okz1Y .rough-node .label text,#mermaid-svg-V1Mz28SIl78Okz1Y .node .label text,#mermaid-svg-V1Mz28SIl78Okz1Y .image-shape .label,#mermaid-svg-V1Mz28SIl78Okz1Y .icon-shape .label{text-anchor:middle;}#mermaid-svg-V1Mz28SIl78Okz1Y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V1Mz28SIl78Okz1Y .rough-node .label,#mermaid-svg-V1Mz28SIl78Okz1Y .node .label,#mermaid-svg-V1Mz28SIl78Okz1Y .image-shape .label,#mermaid-svg-V1Mz28SIl78Okz1Y .icon-shape .label{text-align:center;}#mermaid-svg-V1Mz28SIl78Okz1Y .node.clickable{cursor:pointer;}#mermaid-svg-V1Mz28SIl78Okz1Y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V1Mz28SIl78Okz1Y .arrowheadPath{fill:#333333;}#mermaid-svg-V1Mz28SIl78Okz1Y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V1Mz28SIl78Okz1Y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V1Mz28SIl78Okz1Y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V1Mz28SIl78Okz1Y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V1Mz28SIl78Okz1Y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V1Mz28SIl78Okz1Y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster text{fill:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y .cluster span{color:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V1Mz28SIl78Okz1Y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V1Mz28SIl78Okz1Y rect.text{fill:none;stroke-width:0;}#mermaid-svg-V1Mz28SIl78Okz1Y .icon-shape,#mermaid-svg-V1Mz28SIl78Okz1Y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V1Mz28SIl78Okz1Y .icon-shape p,#mermaid-svg-V1Mz28SIl78Okz1Y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V1Mz28SIl78Okz1Y .icon-shape .label rect,#mermaid-svg-V1Mz28SIl78Okz1Y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V1Mz28SIl78Okz1Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V1Mz28SIl78Okz1Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V1Mz28SIl78Okz1Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
用户提问:解析 report.pdf
智能体组装 Prompt
LLM 分析工具描述
是否需要调用工具?
生成工具调用参数
执行工具函数
工具返回结果
LLM 结合结果生成回复
最终输出给用户
LLM 直接回答
3. 工具(Tool)与中间件(Middleware)的核心区别
虽然工具和中间件都可能用于处理请求或数据,但它们在 LangChain(或更广泛的 AI 应用框架)中扮演着完全不同的角色。
3.1 触发时机
- 中间件:在请求到达 LLM 之前就会被触发。它像一道"安检门",无论用户提什么问题,中间件都会先执行。
- 工具:只有 LLM 在理解用户意图后,判断当前提问需要调用该工具时,才会被触发。
3.2 执行逻辑
- 中间件 是被动拦截节点:它对所有请求一视同仁,按固定逻辑执行,不依赖 LLM 的理解和判断。一定会被执行。
- 工具 是主动调用节点:它完全由 LLM 驱动,LLM 根据对话上下文决定"是否调用、何时调用、如何调用"。只有当 LLM 认为必要时才会执行。
3.3 适用场景
- 中间件 适合处理与业务逻辑无关的、全局性的预处理工作。例如:
- 将用户上传的非文本文件(PDF、Word)预先转成文本。
- 对用户输入做敏感词过滤或数据清洗。
- 统一记录日志、注入上下文变量。
- 工具 适合需要 LLM 深度参与决策、动态执行的具体操作。例如:
- 根据用户需求调用不同的 API 获取天气、股票信息。
- 根据问题动态选择要读取的文件路径。
- 执行数据库查询或修改操作。
用一个形象的比喻:中间件是餐厅门口的迎宾员,负责引导顾客、检查着装(预处理);工具则是顾客点餐后,后厨按需制作的菜品(LLM 决策后执行)。
4. 总结
- LangChain 工具 是大模型的手和脚,通过清晰的类型注解和文档字符串,让 LLM 能够自主调用外部函数,实现复杂操作。
- 中间件 作为请求的守门人,在大模型介入前完成统一的预处理工作,确保输入干净、格式正确。
- 两者核心区别在于触发时机和执行逻辑 :中间件被动、必然执行 ,工具主动、按需调用。
- 在实际项目(如 PDF 解析)中,可结合中间件与工具,让系统自动完成"文件接收→格式识别→内容提取→LLM 处理"的闭环,最终打造一个通用的智能文件解析平台。