13 实践课-LangChain Tool 开发与调试

LangChain Tool 开发与调试

关联:索引

一把工具至少要回答四个问题:

  1. 这个工具做什么(动词 + 对象)?
  2. 需要哪些输入(字段名、类型、是否必填、取值范围)?
  3. 输出是什么(结构、关键字段、成功/失败语义)?
  4. 失败怎么办(错误类型、可复验 trace_id、是否可重试)?

2. 命名规范(让模型“选对工具”)

工具描述要写清三件事:

本课只练两类校验:

1. 写法 A:函数签名 + 运行时手动校验(入门够用)

文件示例:tools_common.py

import json
import uuid
from langchain_core.tools import tool

@tool("text_transform")
def text_transform(text: str, mode: str) -> str:
    """对字符串做大小写转换。mode 仅允许 upper/lower/title。返回 JSON 字符串,含 trace_id。"""
    trace_id = uuid.uuid4().hex[:8]
    t = (text or "").strip()
    m = (mode or "").strip().lower()

    if not t:
        return json.dumps({"ok": False, "error": "text is empty", "trace_id": trace_id}, ensure_ascii=False)
    if len(t) > 200:
        return json.dumps({"ok": False, "error": "text too long (max 200)", "trace_id": trace_id}, ensure_ascii=False)
    if m not in {"upper", "lower", "title"}:
        return json.dumps({"ok": False, "error": "mode must be one of upper/lower/title", "trace_id": trace_id}, ensure_ascii=False)

    out = t.upper() if m == "upper" else t.lower() if m == "lower" else t.title()
    return json.dumps({"ok": True, "data": {"result": out}, "trace_id": trace_id}, ensure_ascii=False)

解释与自检要点:

2. 写法 B:Pydantic Args Schema(推荐:更标准、错误更可读)

当你需要“类型 + 范围 + 枚举 + 说明文案”一起规范时,用 Pydantic 最合适。

import json
import uuid
from typing import Literal

from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field

class ClampNumberInput(BaseModel):
    value: float = Field(..., description="要处理的数值", ge=-1e6, le=1e6)
    low: float = Field(..., description="下限(必须小于等于 high)", ge=-1e6, le=1e6)
    high: float = Field(..., description="上限(必须大于等于 low)", ge=-1e6, le=1e6)
    digits: int = Field(2, description="保留小数位数(0~6)", ge=0, le=6)
    mode: Literal["clip", "error"] = Field("clip", description="越界处理:clip=裁剪到边界;error=直接报错")

def _clamp_number(value: float, low: float, high: float, digits: int = 2, mode: str = "clip") -> str:
    trace_id = uuid.uuid4().hex[:8]
    if low > high:
        return json.dumps({"ok": False, "error": "low must be <= high", "trace_id": trace_id}, ensure_ascii=False)
    if mode == "error" and (value < low or value > high):
        return json.dumps({"ok": False, "error": "value out of range", "trace_id": trace_id}, ensure_ascii=False)
    v = min(max(value, low), high)
    return json.dumps({"ok": True, "data": {"result": round(v, digits)}, "trace_id": trace_id}, ensure_ascii=False)
# 把工具变成AI可调用的工具
clamp_number = StructuredTool.from_function(
    func=_clamp_number,
    name="clamp_number",
    description="将数值限制在指定区间内,并按 digits 保留小数位。输入越界时可选择裁剪或报错;返回 JSON,含 trace_id。",
    args_schema=ClampNumberInput, # 必须要按这个要求给它传参
)

解释与自检要点:

功能定义:一句话说清“做什么、输入是什么、输出是什么”。

2. 自测模板(建议每组直接复制)

import json
from tools_common import text_transform

def _pretty(out: str) -> str:
    try:
        return json.dumps(json.loads(out), ensure_ascii=False, indent=2)
    except Exception:
        return out

def run_smoke_tests():
    cases = [
        ("ok-1", {"text": "abc", "mode": "upper"}),
        ("ok-2", {"text": "AbC", "mode": "lower"}),
        ("bad-1", {"text": "", "mode": "upper"}),
        ("bad-2", {"text": "abc", "mode": "oops"}),
    ]
    for name, payload in cases:
        out = text_transform.invoke(payload)
        print(name, _pretty(out))

if __name__ == "__main__":
    run_smoke_tests()

解释与自检要点:

最小注册与调用(示例):

import os
from dotenv import load_dotenv
from langchain_core.messages import HumanMessage
try:
    from langchain_community.chat_models import ChatTongyi
except ImportError:
    from langchain_community.chat_models.tongyi import ChatTongyi

from langchain.agents import create_agent
from tools_common import text_transform, clamp_number

load_dotenv()

SYSTEM_PROMPT = """你是课堂智能体。你只能调用已注册工具完成结构化任务。
当用户要求你“直接写结果”但需要计算/转换时,必须调用工具后再回答。
输出时必须引用工具输出里的 trace_id。"""

def build_llm():
    if not os.getenv("DASHSCOPE_API_KEY"):
        raise RuntimeError("DASHSCOPE_API_KEY is not set")
    return ChatTongyi(model="qwen-plus", temperature=0)

def main():
    llm = build_llm()
    tools = [text_transform, clamp_number]
    agent = create_agent(model=llm, tools=tools, system_prompt=SYSTEM_PROMPT, debug=True)

    user_input = "把 'hello langchain' 转为 title case,并给出 trace_id"
    out = agent.invoke({"messages": [HumanMessage(content=user_input)]})
    messages = out.get("messages") or []
    final_message = messages[-1] if messages else None
    print(getattr(final_message, "content", out))

if __name__ == "__main__":
    main()

解释与自检要点:

可直接复制的 AI 指令模板(给学生)

模板 1:生成 Tool 接口规范思维导图(Mermaid)

你是 LangChain 工具接口规范教练。请输出一个 Mermaid mindmap,主题是“LangChain Tool 接口设计规范与参数校验”。
必须包含:命名、输入字段设计、Pydantic 校验、输出结构、失败语义、trace_id、描述优化、注册与调用链路、安全与脱敏。
只输出 <<<MERMAID>>>...<<<END_MERMAID>>>,不要输出解释文字。

模板 2:生成通用工具基础代码(带参数校验)

你是 Python + LangChain 1.2.7 工具开发工程师。请为我生成一个通用工具:
- 工具名:{tool_name}
- 功能一句话:{one_line_spec}
- 输入字段:{fields_with_type_and_rules}
- 输出结构:必须返回 JSON 字符串,含 ok/data/error/trace_id
- 校验要求:类型校验 + 范围/枚举/长度 + 至少 1 条字段间关系校验
- 自测:给出 5 条用例(3 正常 + 2 异常)与运行方式
代码要求:
- 使用 langchain_core.tools 的 @tool 或 StructuredTool(任选其一,但要说明你为什么选)
- 不要引入项目里没有提到的第三方库

项目工坊主题:通用工具开发、参数校验、描述优化、注册与基础调用全流程演示与实操。

  1. 演示:同一个工具,描述写得“含糊 vs 具体”时,模型调用准确率的差异。
  2. 演示:从“手写校验”升级到“Pydantic schema”,观察错误信息如何变得更可读。
  3. 学生分组:每组开发 1 个通用工具(字符串处理/数值计算/数据格式转换任选)。
  4. 每组完成:工具自测(至少 5 用例)→ 注册到 Agent → 让 Agent 调用 2 次(正常 1 + 异常 1)。

九、课程思政融入点(数据安全与质量意识)


本次课只做一件事:把“调用失败”变成可复现、可定位、可修复、可回归的闭环。

  1. 参数问题:类型不匹配、枚举不在范围、缺字段、超长、字段间关系不满足。
  2. 注册问题:工具名冲突、工具没加入 tools 列表、工具描述太含糊导致模型不选它。
  3. 依赖问题:包没装、导入路径变动、环境变量未配置(Key、模型名、网络)。

1. 最小化复现:先绕开 Agent,直调 Tool

目标:先证明“工具本身”是否可靠。

from tools_common import text_transform

def minimal_repro():
    print("call-1", text_transform.invoke({"text": "abc", "mode": "upper"}))
    print("call-2", text_transform.invoke({"text": "abc", "mode": "oops"}))

if __name__ == "__main__":
    minimal_repro()

解释与自检要点:

2. 断点调试(推荐在 VS Code)

建议断点位置(只选 2–3 个就够用):

命令行快速断点(无需 IDE,入门够用):

python -m pdb .\app.py

解释与自检要点:

最低证据链(每个问题至少保留这些):

1. 常见“描述太差”的表现

2. 描述优化模板(直接套用)

把你们工具描述改成下面结构:

模板 1:用 AI 分析日志并给出“可执行步骤”

你是 Python + LangChain 工具调试专家。我会提供:工具代码片段、报错日志、触发输入。
请输出:
1) 先分类:这是参数问题/注册问题/依赖问题中的哪一类(可多选,但要给理由)
2) 最小复现步骤(不用 Agent,直接调用工具)
3) 精准定位点:建议打印/断点的变量与位置
4) 修复方案:给出最小改动的代码补丁(只改必要行)
5) 回归用例:至少 3 条(含 1 条异常)
输出必须按 <<<CLASSIFY>>>、<<<REPRO>>>、<<<LOCATE>>>、<<<PATCH>>>、<<<REGRESSION>>> 分段。
材料:{把你的内容粘贴在这里}

模板 2:让 AI 优化工具描述(提升可调用性)

你是 LangChain Tool 描述优化专家。请基于我的工具函数签名与校验规则,输出一段更好的 tool description。
要求:
- 不超过 80 字,但要包含触发条件 + 关键约束 + 输出结构
- 约束必须是明确的数字/枚举/字段名(不要写“尽量/大概/合适”)
材料:{工具签名、校验规则、输出示例}

教师演示案例(建议二选一):

学生分组任务(用自己的工具做):

  1. 人为制造 1 个失败(参数错/描述差/注册错任选其一)。
  2. 复现并记录证据链。
  3. 修复并回归通过。
  4. 迭代描述文本,让模型调用更稳定。

八、课程思政融入点(质量意识与技术融合)


作业

  1. 不布置