智能体二次开发:langchain:核心组件详解_tools

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
智能体二次开发:langchain:核心组件详解_tools [2026/05/20 19:02] – 移除 - 外部编辑 (未知日期) 127.0.0.1智能体二次开发:langchain:核心组件详解_tools [2026/05/20 19:02] (当前版本) – ↷ 页面langchain二次开发:核心组件详解_tools被移动至智能体二次开发:langchain:核心组件详解_tools 张叶安
行 1: 行 1:
 +====== 第六章:核心组件详解 - Tools ======
 +
 +如果说模型负责“理解、推理和表达”,那么工具(Tools)负责“接触真实世界”。没有工具的模型,只能基于训练数据和当前上下文进行回答;有了工具,模型才能查天气、查订单、读数据库、检索知识库、发送草稿、访问内部接口。因此,工具是从“会聊天”走向“能办事”的关键桥梁。
 +
 +本章会从 Tool 的概念、定义方式、Schema 设计、返回值设计、状态访问、错误处理,到与 Agent 协同工作的方法,系统讲清楚如何构建真正“好用”的工具。
 +
 +===== 6.1 什么是 Tool =====
 +
 +==== 6.1.1 Tool 的本质 ====
 +
 +Tool 本质上是一段可被模型调用的函数能力。根据 LangChain 官方文档,一个工具通常包含三个关键元素:
 +  * 名称:模型看到的工具名;
 +  * 描述:模型判断“何时该用”的依据;
 +  * 参数 Schema:模型知道“怎么调用”的结构。
 +
 +最容易理解的比喻是:**Tool 就是给模型看的 API 文档。**
 +
 +如果你只给程序员看,它是函数;如果你既要给程序员看,也要给模型看,它就必须设计成 Tool。
 +
 +==== 6.1.2 为什么普通函数不够 ====
 +
 +普通函数通常只考虑:
 +  * 逻辑是否正确;
 +  * 参数是否完整;
 +  * 返回是否可用。
 +
 +而 Tool 还要额外考虑:
 +  * 模型能不能理解这个工具是干什么的;
 +  * 模型会不会误用它;
 +  * 模型能不能正确填参数;
 +  * 返回结果是否适合下一步推理。
 +
 +===== 6.2 使用 `@tool` 定义工具 =====
 +
 +==== 6.2.1 最小工具示例 ====
 +
 +<code python>
 +from langchain_core.tools import tool
 +
 +@tool
 +def get_weather(city: str) -> str:
 +    """查询指定城市天气。"""
 +    return f"{city} 今日晴,24 到 31 摄氏度。"
 +</code>
 +
 +这个工具已经具备:
 +  * 工具名:`get_weather`
 +  * 描述:来自 docstring
 +  * 参数:`city: str`
 +
 +==== 6.2.2 为什么 docstring 很重要 ====
 +
 +LangChain 官方文档明确强调,工具 docstring 应当简洁且信息充分,因为它直接帮助模型理解何时使用该工具。
 +
 +一个好的 docstring 应该回答:
 +  * 什么时候该用;
 +  * 参数分别表示什么;
 +  * 结果大致返回什么;
 +  * 是否有边界限制。
 +
 +坏例子:
 +  * “查询接口。”
 +
 +好例子:
 +  * “根据订单编号查询订单状态和物流信息,仅适用于已创建订单。”
 +
 +===== 6.3 参数 Schema 设计 =====
 +
 +==== 6.3.1 简单参数 ====
 +
 +如果参数很少,直接靠函数签名即可。
 +
 +<code python>
 +@tool
 +def exchange_rate(base_currency: str, quote_currency: str) -> str:
 +    """查询两种货币之间的汇率。"""
 +    return f"1 {base_currency} = 7.21 {quote_currency}"
 +</code>
 +
 +==== 6.3.2 使用 Pydantic 做复杂参数约束 ====
 +
 +复杂工具更推荐使用 Pydantic 定义 Schema,这样字段描述会更清晰。
 +
 +<code python>
 +from pydantic import BaseModel, Field
 +from typing import Literal
 +from langchain_core.tools import tool
 +
 +class WeatherInput(BaseModel):
 +    city: str = Field(description="城市名称,例如北京、上海")
 +    units: Literal["celsius", "fahrenheit"] = Field(
 +        default="celsius",
 +        description="温度单位"
 +    )
 +    include_forecast: bool = Field(
 +        default=False,
 +        description="是否返回未来 3 天预报"
 +    )
 +
 +@tool(args_schema=WeatherInput)
 +def get_weather(city: str, units: str = "celsius", include_forecast: bool = False) -> str:
 +    """查询城市天气,可选返回未来天气预报。"""
 +    temp = 26 if units == "celsius" else 78
 +    result = f"{city} 当前温度:{temp}°"
 +    if include_forecast:
 +        result += ";未来三天:多云、小雨、晴"
 +    return result
 +</code>
 +
 +==== 6.3.3 设计参数时的实践建议 ====
 +
 +  * 字段名应清晰,不要用 `x1`、`arg2` 这类无语义名字;
 +  * 能枚举就枚举,例如 `priority` 用 `low/medium/high`;
 +  * 描述要体现业务语义,不只是技术含义;
 +  * 尽量避免一个工具接受十几个松散参数;
 +  * 高风险参数要明确写明取值规则。
 +
 +===== 6.4 工具返回什么最合适 =====
 +
 +根据 LangChain 官方文档,工具返回值可以是:
 +  * 字符串;
 +  * 对象 / dict;
 +  * `Command`(用于更新状态)。
 +
 +==== 6.4.1 返回字符串 ====
 +
 +适合模型直接阅读的自然语言结果。
 +
 +<code python>
 +@tool
 +def get_inventory(sku: str) -> str:
 +    """查询商品库存。"""
 +    return f"SKU {sku} 当前库存 23 件,位于华东一号仓。"
 +</code>
 +
 +适合场景:
 +  * 返回结果本身就是一句清晰结论;
 +  * 模型只需继续基于文字推理;
 +  * 结果结构简单。
 +
 +==== 6.4.2 返回结构化对象 ====
 +
 +如果你希望模型能显式读取字段,返回 `dict` 更合适。
 +
 +<code python>
 +@tool
 +def get_inventory_data(sku: str) -> dict:
 +    """查询商品库存详情。"""
 +    return {
 +        "sku": sku,
 +        "available": 23,
 +        "reserved": 5,
 +        "warehouse": "east_01",
 +        "last_updated": "2026-04-03 10:30:00"
 +    }
 +</code>
 +
 +适合场景:
 +  * 下游模型需要基于多个字段推理;
 +  * 你希望结果更稳定可解析;
 +  * 后面可能会把同一个工具复用于 API 或审计系统。
 +
 +==== 6.4.3 返回 Command 更新状态 ====
 +
 +在 LangChain / LangGraph 体系中,某些工具不仅返回数据,还要直接修改 Agent 状态。这时可以返回 `Command`。
 +
 +<code python>
 +from langchain_core.tools import ToolRuntime, tool
 +from langchain_core.messages import ToolMessage
 +from langgraph.types import Command
 +
 +@tool
 +def set_language(language: str, runtime: ToolRuntime) -> Command:
 +    """设置用户偏好的回复语言。"""
 +    return Command(
 +        update={
 +            "preferred_language": language,
 +            "messages": [
 +                ToolMessage(
 +                    content=f"已将语言偏好设置为 {language}",
 +                    tool_call_id=runtime.tool_call_id,
 +                )
 +            ],
 +        }
 +    )
 +</code>
 +
 +这类工具适合:
 +  * 保存用户偏好;
 +  * 更新当前任务状态;
 +  * 写入工作流共享字段。
 +
 +===== 6.5 在工具中访问运行时状态 =====
 +
 +LangChain 官方文档提到,可以通过 `runtime: ToolRuntime` 访问会话状态,这个参数不会暴露给模型,只会在运行时自动注入。
 +
 +==== 6.5.1 读取最近一条用户消息 ====
 +
 +<code python>
 +from langchain_core.tools import tool, ToolRuntime
 +from langchain_core.messages import HumanMessage
 +
 +@tool
 +def get_last_user_message(runtime: ToolRuntime) -> str:
 +    """读取最近一条用户消息。"""
 +    messages = runtime.state["messages"]
 +    for message in reversed(messages):
 +        if isinstance(message, HumanMessage):
 +            return message.content
 +    return "未找到用户消息"
 +</code>
 +
 +==== 6.5.2 读取自定义状态 ====
 +
 +<code python>
 +@tool
 +def get_user_preference(pref_name: str, runtime: ToolRuntime) -> str:
 +    """读取用户偏好设置。"""
 +    preferences = runtime.state.get("user_preferences", {})
 +    return str(preferences.get(pref_name, "未设置"))
 +</code>
 +
 +这种方式的好处是:
 +  * 工具可以更“上下文化”;
 +  * 不必要求模型把所有状态字段都手工重复传参;
 +  * 对复杂 Agent 特别有价值。
 +
 +===== 6.6 把工具绑定给模型 =====
 +
 +==== 6.6.1 使用 `bind_tools()` ====
 +
 +<code python>
 +from langchain_openai import ChatOpenAI
 +
 +model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
 +model_with_tools = model.bind_tools([get_weather, get_inventory])
 +response = model_with_tools.invoke("查询 SKU A-100 库存,并顺便看看上海天气")
 +print(response.tool_calls)
 +</code>
 +
 +绑定之后,模型就具备了“选择工具”的能力。但你仍需要决定:
 +  * 是手动执行工具调用闭环;
 +  * 还是交给 Agent 框架自动完成。
 +
 +==== 6.6.2 手动执行工具调用 ====
 +
 +<code python>
 +response = model_with_tools.invoke("北京天气怎么样?")
 +for tool_call in response.tool_calls:
 +    print("tool:", tool_call["name"])
 +    print("args:", tool_call["args"])
 +</code>
 +
 +这个阶段很适合调试:
 +  * 模型到底会不会选对工具;
 +  * 参数有没有填错;
 +  * 工具描述是否足够清楚。
 +
 +===== 6.7 在 Agent 中使用工具 =====
 +
 +大多数真实项目不会手写每一轮工具循环,而是直接交给 `create_agent()`。
 +
 +<code python>
 +from langchain.agents import create_agent
 +
 +agent = create_agent(
 +    model="openai:gpt-4o-mini",
 +    tools=[get_weather, get_inventory],
 +    system_prompt="你是企业运营助手,涉及事实问题时优先调用工具核实。"
 +)
 +
 +result = agent.invoke({
 +    "messages": [{"role": "user", "content": "帮我查一下上海天气,并看看 SKU A-100 库存是否够发货。"}]
 +})
 +
 +print(result)
 +</code>
 +
 +此时 Agent 会:
 +  * 读取用户消息;
 +  * 判断是否要调工具;
 +  * 执行工具;
 +  * 将 ToolMessage 回传;
 +  * 再次调用模型汇总答案。
 +
 +===== 6.8 工具错误处理 =====
 +
 +工具连接的是外部世界,所以错误不是异常情况,而是常态:
 +  * 网络超时;
 +  * 参数错误;
 +  * 权限不足;
 +  * 数据为空;
 +  * 下游返回格式变化;
 +  * 第三方接口不可用。
 +
 +==== 6.8.1 在工具内部做基础兜底 ====
 +
 +<code python>
 +@tool
 +def safe_order_query(order_id: str) -> str:
 +    """查询订单状态。"""
 +    try:
 +        if not order_id.startswith("ORD"):
 +            return "订单号格式错误,请提供以 ORD 开头的订单编号。"
 +        return f"订单 {order_id} 当前状态:已出库,预计明日送达。"
 +    except TimeoutError:
 +        return "订单服务超时,请稍后重试。"
 +    except Exception as exc:
 +        return f"订单查询失败:{exc}"
 +</code>
 +
 +==== 6.8.2 使用中间件统一监控工具调用 ====
 +
 +LangChain 官方自定义中间件文档提供了 `@wrap_tool_call` 的模式,适合做日志、监控、统一错误包装。
 +
 +<code python>
 +from collections.abc import Callable
 +from langchain.agents.middleware import wrap_tool_call
 +from langchain.messages import ToolMessage
 +from langchain.tools.tool_node import ToolCallRequest
 +from langgraph.types import Command
 +
 +@wrap_tool_call
 +def monitor_tool(
 +    request: ToolCallRequest,
 +    handler: Callable[[ToolCallRequest], ToolMessage | Command],
 +) -> ToolMessage | Command:
 +    print(f"Executing tool: {request.tool_call['name']}")
 +    print(f"Arguments: {request.tool_call['args']}")
 +    try:
 +        result = handler(request)
 +        print("Tool completed successfully")
 +        return result
 +    except Exception as e:
 +        print(f"Tool failed: {e}")
 +        raise
 +</code>
 +
 +这类中间件适合做:
 +  * 调用日志;
 +  * 指标采集;
 +  * 错误监控;
 +  * 风险操作审计。
 +
 +===== 6.9 工具设计的工程原则 =====
 +
 +==== 6.9.1 一个工具只做一件事 ====
 +
 +坏工具:
 +  * `business_assistant()`:既查天气、又查订单、又发邮件。
 +
 +好工具:
 +  * `get_weather()`
 +  * `query_order_status()`
 +  * `draft_email()`
 +  * `create_approval_draft()`
 +
 +拆分的好处:
 +  * 便于模型选择;
 +  * 便于测试;
 +  * 便于权限控制;
 +  * 便于指标分析。
 +
 +==== 6.9.2 工具描述要写“使用时机” ====
 +
 +模型关心的不是你的内部接口路径,而是“我什么时候该调用这个工具”。
 +
 +例如:
 +  * 好描述:根据订单号查询订单当前状态和物流信息,仅适用于已创建订单。
 +  * 坏描述:调用 `/api/v3/order/detail` 接口,返回 OMS JSON。
 +
 +==== 6.9.3 输出要面向模型推理 ====
 +
 +不要机械返回原始 JSON。更好的方式通常是:
 +  * 过滤无关字段;
 +  * 统一单位和时间格式;
 +  * 标明数据更新时间;
 +  * 在必要时补充解释;
 +  * 对异常场景返回模型可理解的提示。
 +
 +===== 6.10 高风险工具的安全边界 =====
 +
 +不是所有工具都能直接暴露给 Agent。尤其是:
 +  * 写数据库;
 +  * 发正式邮件;
 +  * 退款;
 +  * 创建审批单;
 +  * 删除数据;
 +  * 执行系统命令。
 +
 +这类工具建议采取分层策略:
 +  * **只读工具**:可直接开放;
 +  * **草拟工具**:允许生成草稿,不允许最终提交;
 +  * **审批工具**:必须人工确认后执行;
 +  * **禁止暴露工具**:只允许后台系统调用。
 +
 +==== 6.10.1 邮件工具双阶段设计 ====
 +
 +<code python>
 +@tool
 +def draft_email(to: str, subject: str, body: str) -> dict:
 +    """生成待确认的邮件草稿,不会真正发送。"""
 +    return {
 +        "status": "draft",
 +        "to": to,
 +        "subject": subject,
 +        "body": body
 +    }
 +</code>
 +
 +真正发送邮件应由:
 +  * 用户在界面确认;
 +  * 或工作流走到人工审批节点后;
 +  * 再调用后端受控接口完成。
 +
 +===== 6.11 一个完整的多工具案例 =====
 +
 +下面给出一个更接近真实业务的例子:
 +
 +<code python>
 +from langchain.agents import create_agent
 +from langchain_core.tools import tool
 +
 +@tool
 +def query_order(order_id: str) -> dict:
 +    """根据订单号查询订单状态和物流信息。"""
 +    return {
 +        "order_id": order_id,
 +        "status": "已出库",
 +        "logistics_status": "运输中",
 +        "eta": "2026-04-04"
 +    }
 +
 +@tool
 +def query_inventory(sku: str) -> dict:
 +    """根据 SKU 查询当前可用库存。"""
 +    return {
 +        "sku": sku,
 +        "available": 23,
 +        "warehouse": "华东一号仓"
 +    }
 +
 +@tool
 +def draft_delay_notice(order_id: str, reason: str) -> str:
 +    """生成延迟发货通知草稿,不会真正发送。"""
 +    return f"订单 {order_id} 延迟通知草稿:由于 {reason},预计发货时间将顺延 1 天。"
 +
 +agent = create_agent(
 +    model="openai:gpt-4o-mini",
 +    tools=[query_order, query_inventory, draft_delay_notice],
 +    system_prompt="""
 +你是订单运营助手。
 +1. 涉及订单、库存等事实信息时,必须优先调用工具。
 +2. 不要编造库存数字或发货状态。
 +3. 涉及通知用户时,只生成草稿,不得声称已经发送。
 +"""
 +)
 +
 +result = agent.invoke({
 +    "messages": [{
 +        "role": "user",
 +        "content": "请帮我看订单 ORD20260403001 是否会延迟,如果延迟顺便生成一份通知草稿。"
 +    }]
 +})
 +
 +print(result)
 +</code>
 +
 +===== 6.12 本章小结 =====
 +
 +  * Tool 是模型接触真实世界的接口层;
 +  * 好工具不仅要功能正确,还要让模型容易理解和正确使用;
 +  * 参数 Schema、描述和返回格式会直接影响工具调用质量;
 +  * `ToolRuntime` 和 `Command` 让工具具备读取和更新状态的能力;
 +  * 高风险工具必须加人工确认或工作流控制;
 +  * 统一监控和错误处理,是工具工程化的关键。
 +
 +===== 练习 =====
 +
 +1. 实现 3 个工具:天气查询、订单查询、库存查询,并为每个工具编写清晰的 docstring 与参数 Schema。
 +
 +2. 设计一个“邮件发送”双阶段流程:Agent 只能生成草稿,用户确认后才能真正发送。
 +
 +3. 将一个原始 JSON 接口返回值,重构为更适合模型理解的文本或结构化对象输出。
 +
 +4. 使用 `@wrap_tool_call` 编写一个工具监控中间件,记录工具名、参数和执行时长。
 +
 +5. 为你的业务设计一个不超过 8 个工具的 Agent 工具集,并说明每个工具的边界。
 +
 +===== 参考资源 =====
 +
 +  * [[https://docs.langchain.com/oss/python/langchain/tools|LangChain 官方文档:Tools]]
 +  * [[https://docs.langchain.com/oss/python/langchain/agents|LangChain 官方文档:Agents]]
 +  * [[https://docs.langchain.com/oss/python/langchain/middleware/custom|LangChain 官方文档:Custom middleware]]
 +
  

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。