Tools

Tool Calling(工具调用) 或 Function Calling(函数调用),说的是:模型在回答过程中,不直接给最终自然语言,而是先输出“我想调用某个工具,并附带参数”这一结构化意图

  • 模型负责决定:要不要调用工具、调用哪个工具、传什么参数
  • 程序负责执行:真正去调用函数 / API / 数据库 / 业务服务,并把结果再送回模型

一句话定义: Tool 是外部能力本身,Tool Calling 是模型发起使用这项能力的过程。

概念 是什么 核心职责
Tool 一个被封装好的外部能力 负责“做事”
Tool Calling / Function Calling 模型输出结构化调用意图的机制 负责“发起调用请求”
Agent 会推理、会规划、会多步决策的智能体 负责“决定什么时候调用、调用几次、按什么顺序调用”

理解:

  • Tool 像工具箱里的“螺丝刀、扳手、计算器、天气接口”
  • Tool Calling 像“我现在决定要用哪把工具,并说出参数”
  • Agent 像“有判断能力的工人或调度者”,它会自己决定先用哪个工具、后用哪个工具、要不要继续调用

基本流程步骤:

  1. 用户提出问题
  2. 程序把“用户消息 + 工具定义”一起发给模型
  3. 模型判断是否需要调用工具
  4. 如果需要,模型返回 tool_calls
  5. 程序根据 tool_calls 真正执行工具
  6. 程序把工具结果再放回消息流
  7. 模型基于工具结果生成最终自然语言回复

工具定义方式:

1. 使用@tool装饰器

  • 把一个普通 Python 函数包装成 LangChain Tool
  • 让它具备 namedescriptionargs 等元信息
  • 让它能够被模型或 Agent 识别并调用

案例:

from langchain_core.tools import tool

# @tool 装饰器:不写参数时,工具名默认为函数名 add_number,description 取自下方 docstring
@tool
def add_number(a: int, b: int) -> int:
    """两个整数相加"""   # add_number.description
    return a + b

# 直接执行工具:invoke 接收参数字典,键为参数名,值为参数值(与函数签名对应)
result = add_number.invoke({"a": 1, "b": 12})
print(result)

print(f"{add_number.name=}\n{add_number.description=}\n{add_number.args=}")
属性 作用 初学阶段怎么理解
name 工具名 模型要调用谁,首先看这个名字
description 工具说明 模型判断“什么时候该用这个工具”的最重要依据
args 参数结构 模型知道“应该传什么参数、参数是什么类型”
return_direct 是否直接把结果返回给用户 主要在 Agent 场景更常见

从工程实践上说,定义 Tool 时最怕的不是“函数实现难”,而是:名字取得太随意,描述写得太模糊,参数定义不清楚

image

参数schema和Pydantic

有时候参数类型和格式不够清晰;参数校验不够严格。希望能表达

  • 这个参数具体是什么意思
  • 有没有取值范围
  • 是否允许为空
  • 参数传错时怎样更清晰地报错

这就是 args_schema 和 Pydantic 出场的原因

Pydantic定义

Pydantic 是 Python 里非常常用的数据校验库,本质是把“参数结构、参数类型、字段说明、校验规则”统一收敛到一个模型类里

入门阶段可先把握一点:Pydantic = 类型声明 + 自动校验 + 更清晰的参数说明

 

from langchain_core.tools import tool
from loguru import logger
from pydantic import BaseModel,Field

# Pydantic 模型:定义“工具参数接口”,字段 description 会进入工具参数 schema
class FieldInfo(BaseModel):
    """定义加法运算所需的参数结构"""
    a: int = Field(description="第1个参数")
    b: int = Field(description="第2个参数")

# args_schema=FieldInfo:把参数模型绑定到工具,模型会更清楚看到 a、b 的类型与说明
@tool(args_schema = FieldInfo)
def add_number(a: int, b: int) -> int
     """计算两个整数之和"""
    return a + b

# 打印工具属性:带 args_schema 时,args 中会包含 Field 的 description
logger.info(f"name = {add_number.name}")
logger.info(f"args = {add_number.args}")
logger.info(f"description = {add_number.description}")
logger.info(f"return_direct = {add_number.return_direct}")

# 调用工具:传入字典,Pydantic 会做类型校验与转换
res = add_number.invoke({"a": 1, "b": 2})
logger.info(res)

"""
name = add_number
args = {'a': {'description': '第1个参数', 'title': 'A', 'type': 'integer'}, 'b': {'description': '第2个参数', 'title': 'B', 'type': 'integer'}}
description = 计算两个整数之和
return_direct = False
3
"""
    
    

 

  • 用 BaseModel 定义参数结构
  • 用 Field(description=...) 给参数写说明
  • 用 @tool(args_schema=...) 把参数模型绑定给工具

定义天气查询工具

准备工作:

openwearth中申请api key.然后填到.env中

OPENWEATHER_API_KEY=你的密钥

定义好 Tool 后,还需要把它交给模型,这通常通过 bind_tools(...) 完成, 只有先把工具绑定给模型,后续模型调用时才有机会返回 tool_calls也就是说,bind_tools([get_weather]) 的含义不是“现在就执行工具”,而是:

把 get_weather 这项能力声明给模型,告诉它:你之后如果判断有必要,可以调用这个工具

完整链路代码

from dotenv impot load_dotenv
load_dotenv(encoding="utf-8")

import os
from langchain_core.output_parsers import JsonOutputKeyToolsParser,StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from loguru import logger
from QueryWeatherTool import get_weather

# 初始化大模型(教程 5.4:需可调用工具的大模型)
llm = ChatOpenAI(
    model="qwen-plus",
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 将工具绑定到模型:请求时会把 get_weather 的名称、描述、参数 schema 发给模型,模型随后可能返回 tool_calls
llm_with_tools = llm.bind_tools([get_weather])

# 解析器:从模型输出中提取“命中的天气工具参数”,得到可直接传给 get_weather 的入参
parser - JsonOutputKeyToolsParser(key_name=getweather.name, first_tool_only=True)

# 天气查询链:用户问题 → 模型(可能返回 tool_calls)→ 解析出参数 → 执行 get_weather → 得到天气 JSON 字符串
get_weather_chain = llm_with_tools | parser | get_weather

# 输出链:把天气 JSON 塞进提示词,由模型转成更适合用户阅读的自然语言描述
output_prompt = PromptTemplate.from_template(
    """你将收到一段 JSON 格式的天气数据{weather_json},请用简洁自然的方式将其转述给用户。
    以下是天气 JSON 数据:
    请将其转换为中文天气描述,例如:
    "北京现在天气:多云,气温 28℃,体感有点闷热(约 32℃),湿度 75%,微风(东南风 2 米/秒),
    能见度很好,大约 10 公里。建议穿短袖短裤。适合做户外运动。"
    """
)
output_parser = StrOutputParser()
output_chain = output_prompt | llm | output_parser

# 完整链:先拿到天气 JSON,再包装成 {"weather_json": x} 送入输出链,得到最终中文描述
full_chain = get_weather_chain | (lambda x: {"weather_json": x}) | output_chain

result = full_chain.invoke("请问北京今天的天气如何?")
logger.info(result)

image

组织方式:

  • tool.py / tools/:定义给模型看的工具入口
  • service.py / services/:写真实业务逻辑
  • client.py / adapters/:封装第三方 API 或内部接口访问
  • prompt / chain / agent 层:决定什么时候调用工具

 

tool设计原则:

  • 职责单一:一个 Tool 最好只做一件清晰的事
  • 输入明确:参数含义、类型、是否必填要说清楚
  • 输出稳定:返回结构尽量稳定,不要时而返回字符串、时而返回复杂嵌套对象
  • 异常可控:报错要能被程序捕捉和处理,不要直接把底层异常糊给用户
  • 幂等与副作用隔离:能做查询就不要顺手做写入;涉及状态变更时,尽量拆成“先确认、再执行”的显式步骤
  • 超时、重试与限流:外部 API 类 Tool 要设置超时、重试上限和频控,避免 Agent 在异常场景下反复调用
  • 权限受控:涉及写操作、支付、删除、外呼等工具,一定要额外设防
  • 可观测:最好能记录调用日志、参数、耗时、错误信息,便于排障

 

posted @ 2026-05-06 09:29  幻影之舞  阅读(2)  评论(0)    收藏  举报