文章目录

定位是:学习者 → 进阶者,覆盖 Tool 全链路知识点(“架构理解图”)。

Image

1. LangChain中定义 Tool 的三种方式

在 LangChain 的 Agent 体系中,Tool 是最容易“会写但不理解”的模块之一
很多学习者可以很快写出一个 Tool,却无法解释:

  • Tool 是如何被 Agent 选中的?
  • 为什么有的 Tool 永远不会被调用?
  • description、name、参数 schema 各自起什么作用?
  • @toolStructuredToolBaseTool 到底差在哪?

本文试图给出一篇**“一次讲清楚”的完整答案**。


一、什么是 Tool(先建立正确的抽象)

在 LangChain / Tool Calling 体系中:

Tool ≠ 函数
Tool = 给 LLM 阅读并做决策用的“函数说明书”

一个 Tool 至少包含三类信息:

类别 作用
name 工具的唯一标识
description 给 LLM 做语义判断用
args schema 参数结构与约束

⚠️ 关键点:

LLM 永远不会读你的函数实现代码
它只能“看到” Tool 的元信息


二、定义 Tool 的三种方式(完整对比)

方式一:StructuredTool.from_function(最推荐)

from langchain.tools import StructuredTool

def calculate(a: int, b: int, operation: str) -> float:
    if operation == "add":
        return a + b
    if operation == "subtract":
        return a - b
    if operation == "multiply":
        return a * b
    if operation == "divide":
        return a / b
    raise ValueError("invalid operation")

calculate_tool = StructuredTool.from_function(
    func=calculate,
    name="math_calculator",
    description=(
        "当用户需要进行明确的数学计算时使用,"
        "例如加法、减法、乘法或除法。"
        "适用于“帮我算一下”“结果是多少”等请求。"
    ),
)

特点

  • 强制结构完整
  • schema 自动生成
  • 几乎不踩坑
  • 正式项目首选

方式二:@tool 装饰器(最容易出问题)

from langchain.tools import tool

@tool("math_calculator")
def calculate(a: int, b: int, operation: str) -> float:
    """
    用于执行明确的数学运算。
    当用户请求计算、求和、相减、相乘、相除时使用。
    operation 支持 add / subtract / multiply / divide。
    """
    ...

⚠️ 关键规则(必须记住)

  • description 通常来自 docstring

  • 没有 docstring → 很多版本会:

    • 报错
    • 或 Tool 无法被 Agent 正确使用

方式三:继承 BaseTool(高级 / 框架级)

from langchain.tools import BaseTool

class MathCalculatorTool(BaseTool):
    name = "math_calculator"
    description = "当用户需要进行数学计算时使用"

    def _run(self, a: int, b: int, operation: str) -> float:
        return a + b

适用场景

  • 高度定制
  • 框架 / 中间层开发
  • 一般业务开发不推荐

2.从 tool到 LangChain / LangGraph


一、从 Python 最基础开始:docstring 到底是什么

1️⃣ 最原始的 Python 函数

def add(a: int, b: int) -> int:
    """
    返回 a 和 b 的和
    """
    return a + b

你需要明确的 3 件事

  1. """ """docstring
  2. docstring 是 运行时可读取的
  3. Python 把它当成 数据,不是注释
print(add.__doc__)

输出:

返回 a 和 b 的和

⚠️ 到这里为止,一切都还只是 Python。


二、LangChain 做的第一件事:把 docstring 交给 LLM

核心思想(非常重要)

LangChain 并没有发明新概念
它只是:
👉 把 Python 的“可读元信息”喂给了 LLM


三、Tool 的本质结构(脑子里必须有这个模型)

┌─────────────┐
│   Tool      │
├─────────────┤
│ name        │  ← 给 LLM 看
│ description │  ← docstring → 给 LLM 看
│ args schema │  ← 给 LLM + 程序看
│ function    │  ← 只给程序跑
└─────────────┘

⚠️ LLM 永远不会看到 function 的实现代码。


四、第一种方式:@tool(最直观)

示例:一个“数学计算 Tool”

from langchain.tools import tool

@tool("math_calculator")
def calculate(a: int, b: int, operation: str) -> float:
    """
    用于执行明确的数学运算。
    当用户请求计算、求和、相减、相乘、相除时使用。
    
    参数说明:
    - a: 第一个数字
    - b: 第二个数字
    - operation: 运算类型,可选 add / subtract / multiply / divide
    """
    if operation == "add":
        return a + b
    if operation == "subtract":
        return a - b
    if operation == "multiply":
        return a * b
    if operation == "divide":
        return a / b
    raise ValueError("不支持的 operation")

逐行解释(非常关键)

@tool("math_calculator")
  • 注册一个 Tool
  • "math_calculator" 会注入成为 tool name,不写的话,默认就是函数名字calculate
def calculate(...)
  • 函数签名 → 自动生成 args schema
"""
用于执行明确的数学运算...
"""

👉 这段 docstring 会直接进入 prompt,给 LLM 读


五、为什么 description(docstring)决定 Tool 能否被选中

Agent 实际看到的是类似这样的 prompt(简化)

你可以使用以下工具:

Tool name: math_calculator
Description:
用于执行明确的数学运算。
当用户请求计算、求和、相减、相乘、相除时使用。

然后用户说:

帮我算一下 3 乘以 5

LLM 会做的事是:

“用户在请求 乘法 → description 命中 → 使用 tool”


六、StructuredTool:工程上更安全的方式

from langchain.tools import StructuredTool

def calculate(
    a: int,
    b: int,
    operation: str
) -> float:
    """
    当用户需要进行数学计算时使用。
    适用于加减乘除等明确计算场景。
    """
    if operation == "add":
        return a + b
    if operation == "subtract":
        return a - b
    if operation == "multiply":
        return a * b
    if operation == "divide":
        return a / b
    raise ValueError("invalid operation")
calculate_tool = StructuredTool.from_function(
    func=calculate,
    name="math_calculator",
    description=calculate.__doc__,  # 显式传入
)

为什么更推荐它?

  • 强制你思考 description
  • schema 更严格
  • 在大型系统中更稳定

七、Agent:Tool 的“使用者”,不是执行者

Agent 的真实职责

Agent 不干活,只做决策


一个最小 Agent 示例(简化)

from langchain.agents import initialize_agent
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI()

agent = initialize_agent(
    tools=[calculate_tool],
    llm=llm,
    agent="zero-shot-react-description",
    verbose=True,
)

发生了什么?

  1. Tool 的 description 被塞进 prompt
  2. LLM 决定是否调用 Tool
  3. Agent 只是“中转站”

八、为什么 LangGraph 会出现(这是关键跃迁)

LangChain Agent 的问题

  • 思考过程隐式
  • 多步骤流程难以控制
  • 调试困难

Image

Image

九、LangGraph:把“想法”变成结构

LangGraph 的核心概念

State  → 保存信息
Node   → 执行一步逻辑
Edge   → 决定下一步走哪

一个最小 LangGraph 示例(重点)

1️⃣ 定义 State

from typing import TypedDict

class AgentState(TypedDict):
    user_input: str
    result: str

2️⃣ 定义 Node(这里仍然可以用 Tool)

def decide_and_calculate(state: AgentState) -> AgentState:
    """
    根据用户输入决定是否进行计算,
    并返回计算结果。
    """
    text = state["user_input"]

    if "乘" in text:
        result = 3 * 5  # 示例
        return {
            "user_input": text,
            "result": str(result)
        }

    return {
        "user_input": text,
        "result": "未执行计算"
    }

👉 注意:Node 的 docstring 也是“行为说明”


3️⃣ 构建 Graph

from langgraph.graph import StateGraph

graph = StateGraph(AgentState)

graph.add_node("calc", decide_and_calculate)
graph.set_entry_point("calc")

app = graph.compile()

4️⃣ 执行

output = app.invoke({
    "user_input": "帮我算一下 3 乘 5"
})

print(output["result"])

十、把所有东西“穿起来”的最终视图

Python Function
   ↓
docstring(语义说明)
   ↓
Tool(能力描述)
   ↓
Prompt(LLM 可读)
   ↓
Agent / LangGraph Node(决策)
   ↓
Tool Execution
   ↓
State 更新

十一、总结

我们现在的阶段,能理解:

  • docstring 是 prompt
  • Tool 是“能力边界”
  • Agent 是“决策循环”
  • LangGraph 是“显式流程控制”

已经:

从“用框架”升级到了“理解框架设计的人”


Image

Image

Image

3. 生产级 Agent 从 0 到 1

——目录结构、Memory、RAG 的完整工程实践

在真实工程中,我们构建的不是一个 Demo Agent,而是一个具备以下特征的系统:

  • 能长期对话(Memory)
  • 能访问外部知识(RAG)
  • 能稳定决策与回退(LangGraph)
  • 能扩展、维护、调试(工程结构)

整体架构 → 目录设计 → Memory → RAG → LangGraph 串联


一、先给出最终工程目录(全局视角)

这是一个生产级最小可扩展结构

agent_app/
├─ app/
│  ├─ main.py              # 程序入口
│  ├─ config.py            # 模型 / 路径 / 参数配置
│
│  ├─ llm/
│  │  └─ model.py          # LLM 初始化
│
│  ├─ memory/
│  │  ├─ base.py           # Memory 抽象
│  │  ├─ conversation.py  # 对话记忆
│  │  └─ summary.py       # 压缩记忆
│
│  ├─ rag/
│  │  ├─ retriever.py      # 检索
│  │  ├─ index.py          # 向量索引
│  │  └─ formatter.py     # 文档整理
│
│  ├─ tools/
│  │  ├─ calculator.py
│  │  └─ search.py
│
│  ├─ graph/
│  │  ├─ state.py          # LangGraph State
│  │  ├─ nodes.py          # Graph Nodes
│  │  └─ build.py          # Graph 构建
│
│  └─ prompts/
│     ├─ system.txt
│     └─ answer.txt

核心原则
👉 Memory、RAG、Tool、Graph 必须物理分离


二、Memory:不是“记住一切”,而是“可控记忆”

1. Memory 的工程定义

在生产环境中:

Memory = 跨调用保留的信息 + 可控策略

Memory 不是聊天记录,而是状态管理系统


2. Memory 的三种典型形态

类型 作用 适合
原始对话 保留上下文 短对话
Summary 压缩历史 长对话
结构化状态 决策依赖 LangGraph

3. 对话 Memory(conversation)

# app/memory/conversation.py
class ConversationMemory:
    """
    保存最近 N 轮对话,用于上下文连贯。
    """

    def __init__(self, max_turns: int = 5):
        self.max_turns = max_turns
        self.buffer: list[tuple[str, str]] = []

    def add(self, user: str, assistant: str):
        self.buffer.append((user, assistant))
        self.buffer = self.buffer[-self.max_turns:]

    def load(self) -> str:
        """
        返回可注入 prompt 的文本
        """
        return "\n".join(
            f"User: {u}\nAssistant: {a}"
            for u, a in self.buffer
        )

👉 只保留“近期上下文”,避免 prompt 爆炸


4. Summary Memory(长期对话的关键)

# app/memory/summary.py
class SummaryMemory:
    """
    使用 LLM 对历史对话进行语义压缩。
    """

    def __init__(self):
        self.summary = ""

    def update(self, new_dialogue: str, llm):
        prompt = f"""
        请将以下对话压缩为事实摘要:
        {self.summary}
        {new_dialogue}
        """
        self.summary = llm.invoke(prompt)

    def load(self) -> str:
        return self.summary

👉 Summary 是“长期记忆”,不是聊天记录


三、RAG:外部知识系统,而不是 Tool

Image

Image

1. RAG 的工程定位

RAG = 可查询的外部事实系统

它只做三件事:

  1. 检索
  2. 过滤
  3. 格式化

2. Retriever(检索层)

# app/rag/retriever.py
class Retriever:
    """
    根据 query 返回相关文档。
    """

    def __init__(self, vector_store):
        self.vector_store = vector_store

    def retrieve(self, query: str) -> list[str]:
        docs = self.vector_store.similarity_search(query, k=5)
        return [d.page_content for d in docs]

3. Formatter(为 LLM 整理材料)

# app/rag/formatter.py
def format_docs(docs: list[str]) -> str:
    """
    将检索结果整理为 prompt 可读格式。
    """
    return "\n\n".join(
        f"[资料]\n{doc}" for doc in docs
    )

👉 RAG 的输出是“材料”,不是答案


四、LangGraph:把 Memory 与 RAG 串成可控流程

1. State:统一上下文

# app/graph/state.py
from typing import TypedDict, Optional

class AgentState(TypedDict):
    question: str
    memory: str
    docs: Optional[str]
    answer: Optional[str]
    error: Optional[str]

2. Node:每个 Node 只做一件事

判断是否需要 RAG

def decide_need_rag(state: AgentState) -> str:
    """
    决定是否依赖外部知识。
    """
    if "公司" in state["question"]:
        return "rag"
    return "direct"

RAG Node(可失败)

def rag_node(state: AgentState, retriever) -> AgentState:
    try:
        docs = retriever.retrieve(state["question"])
        return {**state, "docs": format_docs(docs)}
    except Exception as e:
        return {**state, "error": str(e)}

回退 Node(工程必备)

def fallback_node(state: AgentState) -> AgentState:
    return {
        **state,
        "answer": "当前无法访问外部知识,请稍后再试。"
    }

五、最终 Graph 构建(完整闭环)

# app/graph/build.py
from langgraph.graph import StateGraph

def build_graph():
    graph = StateGraph(AgentState)

    graph.add_node("rag", rag_node)
    graph.add_node("fallback", fallback_node)

    graph.add_conditional_edges(
        "rag",
        lambda s: "fallback" if s.get("error") else "direct"
    )

    graph.set_entry_point("rag")
    return graph.compile()

六、主程序:把一切接起来

# app/main.py
def run(question: str):
    memory_text = conversation_memory.load()
    state = {
        "question": question,
        "memory": memory_text,
        "docs": None,
        "answer": None,
        "error": None,
    }
    result = app.invoke(state)
    return result["answer"]

七、最终工程认知(非常重要)

Memory、RAG、Tool、Graph 的正确分工

模块 职责
Memory 状态延续
RAG 提供事实
Tool 执行动作
LangGraph 决策与回退

最终结论(工程级)

1️⃣ Memory 是状态,不是聊天记录
2️⃣ RAG 是知识系统,不是 Tool
3️⃣ LangGraph 是控制器,不是 Agent 包装
4️⃣ 失败路径必须被显式建模

如果我们按这套结构构建 Agent,那么系统将具备:

可预测、可扩展、可维护、可演进


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐