尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LangChain 应用开发(七):Tools 工具定义与核心机制

LangChain 应用开发(七):Tools 工具定义与核心机制 目录一、认识 Tools1. 什么是 Tool2. Tool 能解决大模型的哪些局限二、Tool Calling 的基本原理1. 模型是否真的执行了函数2. Tool Calling 的整体流程3. 一次完整工具调用中的消息变化三、Tool 是如何描述给模型的1. Tool 的抽象结构2. 构成 Tool 描述的核心要素四、不使用 tool 定义工具1. 普通函数如何包含工具信息2. convert_to_openai_tool五、使用 tool 装饰器1. 为什么推荐使用 tool2. tool 基本使用3. 查看 Tool 内部自动生成的 Schema 信息4. 本地执行被 tool 装饰的工具六、自定义 Tool 描述1. 工具描述本质上就是 Prompt 工程2. 自定义描述工具七、自定义工具参数 Schema1. 使用 Pydantic 定义2. 使用 JSON Schema 定义总结一、认识 Tools在前面的章节中我们深入探讨了消息类型、模型接口以及提示词的构建与组装。然而仅仅依靠这些大语言模型依然无法落地要让大型语言模型从简单的聊天交互进化为能够自主处理复杂业务的智能体AI Agent核心在于——工具Tool能力1. 什么是 Tool在 LangChain 的语境下Tool工具是模型与真实世界进行交互的桥梁从本质上看Tool 就是一个被赋予了 结构化自我描述 的可执行函数。它不仅包含一段能够实际运行的代码逻辑如 Python 函数、数据库查询、API 请求还包含一套专门给大模型阅读的说明书描述工具的功能、输入参数格式与规则Tool 是构建 Agent 的核心要素在专栏的第一篇博客中我们曾给出了构建通用 AI Agent 的标准公式在这个公式中LLM是负责思考与决策的大脑Planning是大脑分解目标的思维路线Memory是保证上下文连贯的海马体Tools则充当了 Agent 的双手、双眼与感官如果没有 ToolsAgent 的 Action 能力就是一片空白。只有赋予了模型调用工具的能力它才能将思考真正转化为对外部世界的操作2. Tool 能解决大模型的哪些局限即使是最顶尖的大语言模型如 GPT-4o其自身能力也存在不可逾越的短板。Tool 的出现正是为了修复这些局限大模型的天然短板Tool 带来的解决方案知识时效性缺失知识库存在截止日期无法获知实时信息实时搜索工具或新闻 API计算与精确逻辑缺陷做复杂数学计算时极易出现幻觉Python 代码执行器或计算器工具无法感知私有数据不了解企业内部数据与数据库SQL 查询工具或向量数据库检索器缺乏行动能力无法对外部世界产生实际改变邮件发送、支付扣款、日程创建等 API 工具LLM 的决策路径引入 Tool 后LLM 在接收到用户请求时的交互逻辑发生了改变。它不再是机械地从记忆库里检索文本回答而是演变成了一个路由决策中心Tool 是大模型的能力扩展。在后续的开发中我们不再试图让 LLM 本身学会所有知识而是通过为它配备高效的 Tools让它学会在合适的时间选择并使用合适的工具去解决问题二、Tool Calling 的基本原理在正式上手用代码写 Tool 之前我们需要先抹平一个关于工具调用Tool Calling最普遍的认知误区。理解模型是如何做出工具调用决策的是后续掌握 LangChain 工具链与复杂 Agent 开发的核心1. 模型是否真的执行了函数大多数刚接触 AI 开发的同学都会有一个误解以为给大模型传了一个 Python 函数大模型就会在其云端服务器上运行这段 Python 代码大语言模型本身绝对不会执行任何 Python 函数或代码代码行大模型归根结底是一个文本与 Token 的预测器。在工具调用的整个生命周期中模型只做了一件事在收到请求后判断当前问题需要调用哪个工具并生成一段符合特定格式的 工具调用请求结构化 JSON 文本真正去找到本地 Python 函数、执行代码并拿到返回结果的是运行在你自己服务器上的应用程序2. Tool Calling 的整体流程一次完整的工具调用过程在底层数据流中需要经过2 次大模型请求和1 次本地工具执行。让我们通过一个经典的案例——“今天天气怎么样”来看清整个交互流程与消息角色的变化3. 一次完整工具调用中的消息变化为了在代码层面上看清这个过程我们可以将这套交互链路解构为四个步骤中的消息状态1. 用户提出请求 (HumanMessage)用户发送普通的自然语言提示词此时框架会将已注册的工具清单如 get_weather 的说明隐式或显式地作为系统上下文提交给 LLMHumanMessage(content东京现在天气怎么样)2. 模型做出工具调用决策 (AIMessage 带有 tool_calls)模型经过思考发现自己不知道实时天气但命中了一个名为 get_weather 的 Tool于是它停止生成常规文本转而返回一个带有 tool_calls 属性的 AIMessageAIMessage( content, # 注意此时 content 通常为空 tool_calls[{ name: get_weather, args: {city: 北京, unit: celsuis}, id: call_abc123 # 唯一的调用 ID用于关联后续结果 }] )3. 宿主程序执行 Tool 并返回结果 (ToolMessage)LangChain 框架捕获到上述 AIMessage 中的 tool_calls 列表自动在本地找到并运行 get_weather。函数执行完成后框架将其输出包装为ToolMessageToolMessage( content晴朗28°C, tool_call_idcall_abc123 # 必须与上一条 AIMessage 的 call ID 完全一致 )4. 模型整理最终回答 (AIMessage)框架将包含历史上下文的完整消息列表整体追加后重新请求 LLMHumanMessage: 东京现在天气怎么样AIMessage: tool_calls[...]ToolMessage: 晴朗25°C模型在读懂了 ToolMessage 里的数据后终于得到了答题所需的全部拼图输出最终给用户的文本AIMessage(content北京今天晴, 气温 28°C。)三、Tool 是如何描述给模型的大模型之所以能够精准决定调用哪个工具、并生成合规的参数是因为它在对话开始前就已经拿到了一份所有可用工具的完整说明这就引出了一个非常关键的思想Tool 不仅仅是一个 Python 函数它的本质是给模型看的结构化描述Schema 在本地运行的可执行代码Callable如果只有代码而没有结构化描述模型就像拿着一份没有文字的菜单根本不知道哪个函数能用来做什么1. Tool 的抽象结构在 LangChain 框架中一个完整的 Tool 可以被解构为以下两大模块2. 构成 Tool 描述的核心要素1. 工具名称 (name)作用工具的唯一标识符类似于函数的 ID重要性模型在返回 tool_calls 时就是通过这个 name 来指定要调用的工具。名称应当具有明确的语义避免使用 func_1 或 tool_a 这种无意义的命名2. 工具描述 (description)作用向模型解释这个工具能干什么以及应该在什么情况下调用重要性description 实际上就是针对工具调用的专有 Prompt。模型的路由决策极度依赖这段文字。如果描述过于模糊模型就很容易漏选或误选工具3. 参数字典与 Schema (args)模型决定调用某个工具后需要为其填充具体的输入参数。为了让模型填对参数必须将每一个参数的元数据清晰展示参数名称如 city、start_date数据类型如 string、integer、boolean、array。模型会严格按照数据类型生成 JSON参数描述告诉模型这个参数代表什么例如 city 的描述是“需要查询天气的城市名称如北京、东京”默认与必填指示模型哪些参数是必须提供的哪些可以省略或使用默认值4. 可执行函数 (callable)作用承载真正的 Python 逻辑如发起 HTTP 请求、执行 SQL 语句隐蔽性这部分逻辑保存在本地服务端对大模型是完全不可见且未暴露的在后面的章节中无论我们是用简单的高阶函数、tool 装饰器还是通过 Pydantic 来自定义高级 Schema其底层目的只有一个将普通的 Python 函数封装转译为大模型 API 所能理解的标准 JSON Schema模型只有事先明确知道了这个工具叫什么 (name)它能用来解决什么问题 (description)调用它需要按什么格式传参 (args)它才能在用户提问时做出精准的决策返回符合预期的工具调用指令四、不使用 tool 定义工具在正式学习 LangChain 提供的各种快捷工具如 tool 装饰器之前我们先来思考如果我们什么装饰器都不用直接写一个普通 Python 函数LangChain 是如何将它翻译给大模型的1. 普通函数如何包含工具信息假设我们编写了一个标准的 Python 函数用来查询城市天气。在这个函数中我们遵守了规范的 编码习惯包含了类型注解和Google 风格的文档字符串def get_weather(city: str, unit: str celsius) - str: 获取指定城市的天气信息。 Args: city: 城市名称 unit: 温度单位默认为摄氏度 (celsius) Returns: 天气信息描述字符串 return f{city}当前天气晴朗气温 25 {unit}。观察这个普通的 Python 函数你会发现它其实已经包含了构造工具所需的全部元数据函数签名对应工具的 name类型注解 (city: str, unit: str celsius)声明了参数的数据类型与默认值Docstring 主体说明描述了函数的核心功能对应工具的 descriptionDocstring 中的 Args: 规范详细解释了每一个参数的语义对应每个参数的 description2. convert_to_openai_tool在 LangChain 底层提供了一个内置的转换工具函数 convert_to_openai_tool()。我们无需去死记硬背这个 API在这里引入它只是为了观察普通函数最终是如何转译成提供给 LLM 的 Schema JSON 的from langchain_core.utils.function_calling import convert_to_openai_tool tool_schema convert_to_openai_tool(get_weather) import json print(json.dumps(tool_schema, indent2, ensure_asciiFalse))运行上述代码你会看到输出了一段符合 OpenAI Tool Specification 要求的结构化 JSON Schema从原生代码到 LLM 说明书对比上面的 Python 函数与生成的 JSON我们可以清晰地画出这套自动映射路径Python 原生函数 提供给大模型的 Tool Schema ────────────────── ─────────────────────────── def get_weather(...) ────── name: get_weather Docstring 首行 ────── description: 获取指定城市的天气信息 city: str ────── city: { type: string } Args 中的 city 描述 ────── city: { description: 城市名称 } unit: str celsius ────── unit: { default: celsius }在 AI 应用工程中规范的 Docstring 和类型注解直接充当了针对大模型的 Prompt如果不写类型注解LangChain 将无法推断参数类型转换出的 Schema 会将参数盲目标记为通用类型甚至缺失导致大模型传入错误的数据格式如果不写 Docstring生成的 description 将会为空大模型就像拿到了一张没有标注功能的按钮图纸根本不知道什么时候该去调用这个函数因此养成良好的 Python 编码习惯是在 LangChain 中快速构建高质量工具的第一步五、使用 tool 装饰器普通 Python 函数可以通过类型注解和 Docstring 解析出模型所需的 JSON Schema。然而原生函数本身仍然只是一段普通代码。要让模型或 Agent 能够统一调度、校验输入、处理异步调用或绑定到 Chain 中函数需要被包装成标准的BaseTool对象为了让这个转化过程尽可能优雅LangChain 提供了官方推荐的快捷解法——tool 装饰器1. 为什么推荐使用 tool如果不用 tool我们需要手动继承 BaseTool 类并覆写 _run、args_schema 等多个方法和属性这在编写简单的业务工具时会产生大量的样板代码tool 装饰器的伟大之处在于它把这种繁琐的包装过程自动化了使用 tool 后的函数既保留了原函数的本地执行能力通过 .invoke()又自动具备了向 LLM 暴露 Schema 的全套属性2. tool 基本使用使用 tool 非常简单只需从 langchain_core.tools 导入它并装饰在定义好的函数上方即可from langchain_core.tools import tool # 1. 使用 tool 装饰器声明工具 tool def get_weather(city: str, unit: str celsius) - str: 获取指定城市的天气信息。 Args: city: 城市名称。 unit: 温度单位默认为摄氏度 (celsius)。 return f{city}当前天气晴朗气温 25 {unit}。 # 2. 此时 get_weather 已经不再是普通的 function而是一个 StructuredTool 对象 print(工具类型:, type(get_weather))输出结果3. 查看 Tool 内部自动生成的 Schema 信息被 tool 装饰后你可以像操作对象一样直接调取该工具暴露给 LangChain 和大模型的各个核心元数据字段# 1. 查看工具名称 (默认取函数名) print(Name:, get_weather.name) # 2. 查看工具描述 (默认取 Docstring 首行/主体) print(Description:, get_weather.description) # 3. 查看工具参数 Schema (自动解析类型注解与 Args 描述) import json print(Args Schema:\n, json.dumps(get_weather.args, indent2, ensure_asciiFalse))输出结果4. 本地执行被 tool 装饰的工具被 tool 装饰后的对象建议使用 LangChain 标准的 .invoke() 方法来调用这能够确保参数在执行前经过 LangChain 的 Schema 校验# 使用标准的 .invoke() 方法传入参数字典进行调用 result get_weather.invoke({city: 北京, unit: celsius}) print(执行结果:, result)输出结果在绝大多数日常开发场景中tool 装饰器 规范的 Docstring是构建 LangChain 工具最常用、最首选的方式六、自定义 Tool 描述在前面的学习中我们看到 tool 默认会提取 Python 函数的名称作为工具名提取Docstring作为工具描述但在实际开发中仅靠函数的默认名称和简单的注释往往是不够的。我们需要对工具描述进行更精细的控制与定制1. 工具描述本质上就是 Prompt 工程许多开发者在写 Tool 时容易掉入一个误区觉得只要 Python 函数代码能跑通、不出错Tool 的定义就完成了Tool 的 name 和 description本质上是注入给大模型系统提示词System Prompt的一部分当大模型面临多个工具选择时它是完全根据 description 中的文字来猜测和评估 哪个工具最适合解决当前用户的提问。如果工具描述含糊不清模型的选择准确率就会大幅下滑2. 自定义描述工具LangChain 的 tool 装饰器提供了非常灵活的参数支持允许我们重写工具名称、自定义描述字符串以及开启增强解析1. 直接传入字符串重命名工具当装饰器直接接收一个位置参数时该参数会被作为工具的 name即 name_or_callabletool(custom_product_search) def search(query: str) - str: 搜索商品库。 return ... print(search.name)输出结果2. 显式指定 description 参数如果不想使用函数的 Docstring例如 Docstring 写了大量面向开发者的内部代码注释不适合暴露给 LLM可以通过 description 参数显式覆盖tool( search_user, description专门用于根据用户 ID 或手机号查询用户基础档案信息。请勿用于查询商品或订单。 ) def fetch_user_info(user_id: str) - str: # 这里是写给程序员看的内部实现注释 # Step 1: Connect to MySQL... # Step 2: Query user_table... return ... print(fetch_user_info.description)输出结果3. 显式开启 Docstring 解析 (parse_docstring)默认情况下tool 会将 Docstring 的第一行解析为 description。如果你的 Docstring 遵循了标准的 Google 风格可以设置 parse_docstringTrue这样 LangChain 会自动从 Docstring 的 Args: 块中提取每个参数的具体说明并填充到 Schema 中tool(parse_docstringTrue) def calculate_tax(income: float, location: str BJ) - float: 计算个人所得税。 Args: income: 税前月收入单位元人民币。 location: 城市缩写例如 BJ (北京), SH (上海)。 return income * 0.2高质量 Tool 描述的设计法则为了让大模型准确理解并正确调用你的工具编写 description 时建议包含以下三个核心要素要素维度解决的问题示例描述触发时机 (When)明确告诉模型在什么场景下应该使用以及不应该使用该工具当用户询问订单状态、快递物流或退货进度时调用。功能范围 (What)清晰说明工具会处理什么业务范围在哪里仅查询近 90 天内的电商订单记录无法查询历史归档订单。输出特性 (Output)告诉模型执行后能拿到什么数据是否有副作用返回 JSON 格式的物流轨迹与最新配送员电话。此操作只读不会发起退款。把工具描述当成针对 LLM 的微型 Prompt 来精雕细琢这是提高 Agent 决策成功率成本最低、效果最立竿见影的手段七、自定义工具参数 Schema在前面的章节中我们通过 类型注解 Docstring 让 tool 自动提取生成参数 Schema。这在应对简单入参如几个基础字符串或数字时十分高效但在真实场景中大模型需要调用的业务接口往往要复杂得多当出现以下需求时自动提取逻辑就会显得力不从心需要对参数值进行严格约束与校验如限定数字范围、正则表达式匹配、枚举选项需要传入复杂数据结构如嵌套的字典、对象列表某个参数的说明非常长放在函数的 Docstring 中会导致代码结构臃肿难读为了应对这些需求LangChain 允许我们显式地为工具配置args_schema参数 Schema1. 使用 Pydantic 定义Pydantic是 Python 中用于接口校验和 Schema 生成的标准库也是 LangChain 底层最核心的依赖之一通过定义继承自 BaseModel 的类并结合 Field 描述符我们可以对工具入参进行精准的控制与注释from pydantic import BaseModel, Field from typing import Literal class CreateTicketInput(BaseModel): user_id: str Field( description提出工单的用户唯一 ID例如 USR10023 ) priority: Literal[low, medium, high, urgent] Field( defaultmedium, description工单优先级必须是 low/medium/high/urgent 之一 ) title: str Field( description工单简短标题建议 5 到 20 个字 ) description: str Field( description工单的详细故障描述 ) # 2. 在 tool 中通过 args_schema 指定上面定义的 Pydantic 类 tool(args_schemaCreateTicketInput) def create_support_ticket(user_id: str, title: str, description: str, priority: str medium) - str: 在内部客服系统中创建新的技术支持工单。 return f成功为用户 {user_id} 创建【{priority}】工单单号: TICKET-8892 # 3. 观察生成的工具参数定义 import json print(json.dumps(create_support_ticket.args, indent2, ensure_asciiFalse))输出结果片段Pydantic Field 的核心增强能力枚举校验 (Literal)直接限制 LLM 只能选择我们列出的固定的几个选项彻底规避模型瞎编参数的问题范围约束 (ge, le)例如 Field(ge1, le100) 限制数字必须在 1 到 100 之间参数注释参数的描述从函数的 Docstring 中解耦出来代码结构更清晰大模型读取到的参数定义也更精准2. 使用 JSON Schema 定义在少数情况下比如工具参数是从外部配置文件加载的或者使用非 Python 语言的 Schema 定义直接编写类代码可能不够灵活。此时我们可以直接传入底层的JSON Schema 字典weather_schema { type: object, properties: { location: {type: string}, units: {type: string}, include_forecast: {type: boolean} }, required: [location, units, include_forecast] } tool(args_schemaweather_schema) def get_weather(city: str, unit: str celsius, include_forecast: bool False) - str: 获取当日天气 pass输出结果至此我们已经掌握了 LangChain 中定义 Tool 的全部方式。根据业务的复杂度我们可以将 Tool 的定义整理为三个清晰的演进层级简单 Tool直接写 def func(a: str): doc 并加上 tool 即可零成本快速验证需要详细参数约束使用Pydantic定义 args_schema这是生产环境下构建稳定 Agent 最推荐的标准范式特殊场景通过 tool(args_schemajson_schema_dict) 将符合 JSON Schema 标准的字典与工具函数关联总结本章围绕 LangChain 的Tools 工具定义与核心机制展开首先了解了 Tool 在大模型应用和 Agent 中的作用并梳理了从模型生成工具调用请求到程序执行工具并将结果返回给模型的基本流程随后我们学习了 Tool 的名称、描述、参数以及 Schema 等核心组成并掌握了通过普通 Python 函数和 tool 装饰器定义工具的方法。同时还进一步学习了 description、parse_docstring、args_schema 等配置方式以及使用 Pydantic 和 JSON Schema 对工具参数进行更加精确的描述与约束通过本章的学习我已经能够完成一个规范 LangChain Tool 的定义。下一篇将进一步进入Tools 的实际调用与应用学习如何将工具绑定到模型、获取并执行 tool_calls、构造 ToolMessage最终完成一次完整的模型工具调用流程
返回列表