agood_day_头像
关注
LangChain基础入门3(小白必看)封面图

LangChain基础入门3(小白必看)

LangChain 工具调用实战:从 @tool 定义到 bind_tools 与 tool_choice

大模型本身只能生成文字,不能直接访问天气接口、数据库或业务系统。工具(Tool)就是把一个外部函数描述给模型,让模型在需要时提出调用请求,再由程序真正执行函数并把结果交回模型。

完整链路可以概括为:

定义 Python 函数
      ↓
转换为模型可理解的工具 Schema
      ↓
model.bind_tools(tools)
      ↓
模型返回 tool_calls
      ↓
程序执行工具
      ↓
ToolMessage 回传模型
      ↓
模型生成最终回答

一、工具有两种调用方式

1. 直接调用工具

from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 晴天,温度 15°C"

result = get_weather.invoke({"city": "北京"})
print(result)

这里的 get_weather 已经不是普通函数,而是一个 LangChain StructuredTool。调用 .invoke() 时传入字典,字典的键名要和工具参数名一致。

2. 基于模型调用工具

model_with_tools = model.bind_tools([get_weather])
response = model_with_tools.invoke("北京天气如何?")

if response.tool_calls:
    print("AI 想调用工具:", response.tool_calls)
else:
    print("AI 直接回答:", response.content)

bind_tools() 的作用不是执行工具,而是把工具的名称、描述和参数 Schema 发送给模型。模型根据用户问题决定:直接回答,还是生成 tool_calls

二、不使用 @tool 时,工具描述是怎样生成的?

LangChain 最终需要把 Python 函数转换为模型能理解的 JSON Schema。可以直接查看转换结果:

from langchain_core.utils.function_calling import convert_to_openai_tool

def get_weather(city: str = "北京"):
    """
    查询城市的天气

    Args:
        city: 具体的城市
    """
    return f"{city}天气晴朗"

print(convert_to_openai_tool(get_weather))

转换后的描述大致包含:

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "查询城市的天气",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {"type": "string", "description": "具体的城市"}
      }
    }
  }
}

模型并不会读取 Python 函数体来理解工具,它主要依赖这份结构化描述。因此工具名称、函数注释、参数类型和默认值都很重要。

三、工具描述的四个关键部分

1. 工具名称

默认使用 Python 函数名:

def get_weather(city: str):
    ...

模型看到的工具名通常就是 get_weather。名称应简洁、明确,使用动词更容易理解,例如 search_newsquery_order

也可以自定义名称:

@tool(parse_docstring=True, name_or_callable="getWeather")
def get_weather(city: str):
    """获取城市天气"""
    return f"{city}天气晴朗"

实际开发通常建议直接使用清晰的函数名,避免函数名和工具名不一致导致调试困难。

2. description

工具必须有清楚的描述。使用 @tool 时,如果没有显式提供 description,函数一般需要写 docstring:

@tool
def get_weather(city: str):
    """查询指定城市当日天气"""
    return f"{city}天气晴朗"

描述应该说明“工具做什么”,不要只写“工具函数”。模型会依据描述判断什么时候该调用它。

3. 参数说明

推荐使用 Google 风格 docstring,并开启 parse_docstring=True

@tool(parse_docstring=True)
def get_weather(city: str) -> str:
    """
    查询指定城市当日天气

    Args:
        city: 城市名称,例如北京、上海
    """
    return f"{city}天气晴朗"

参数注释会进入 Schema,模型就能知道 city 应该传什么。

4. 类型和默认值

def get_weather(city: str, days: int = 1):
    ...

strintbool 等类型会被转换为 JSON Schema 类型。设置默认值后,该参数通常不会出现在 required 中,模型可以省略它。

def get_weather(city: str = "北京"):
    ...

没有默认值的参数通常是必填参数:

def get_weather(date: str, city: str = "北京"):
    ...

此时 date 必须提供,city 可以使用默认值。

四、使用 @tool 装饰器

@tool 会把普通函数包装成 LangChain 工具:

from langchain.tools import tool

@tool(parse_docstring=True)
def get_news(domain: str) -> str:
    """
    查询指定领域的热点新闻

    Args:
        domain: 新闻领域
    """
    return f"{domain}领域暂无新闻"

装饰前:

get_news 是普通 Python 函数

装饰后:

get_news 具有 name、description、args_schema 和 invoke() 等工具能力

五、使用 Pydantic 定义 args_schema

工具参数复杂时,可以使用 Pydantic 模型集中定义校验规则:

from pydantic import BaseModel, Field
from typing import Literal
from langchain.tools import tool

class WeatherInput(BaseModel):
    city: str = Field(default="北京", description="具体城市")
    unit: Literal["celsius", "fahrenheit"] = "celsius"
    include_forecast: bool = Field(
        default=False,
        description="是否包含未来五天预报",
    )

@tool(args_schema=WeatherInput)
def get_weather(
    city: str,
    unit: str = "celsius",
    include_forecast: bool = False,
) -> str:
    """获取城市天气"""
    return f"{city}天气晴朗"

这里:

  • Field(description=...) 给模型提供参数含义;
  • Literal[...] 限制参数只能取指定值;
  • 默认值让参数变成可选;
  • args_schema 把这套规则绑定到工具上。

六、从 Message 流转看完整工具调用

模型不会直接运行 Python 函数。程序必须负责中间的执行步骤:

from langchain.messages import HumanMessage

@tool
def get_weather(city: str) -> str:
    """获取天气"""
    return f"{city}天气晴朗"

model_with_tools = model.bind_tools([get_weather])
messages = [HumanMessage("今天北京天气如何?")]

# 第一次调用:模型只提出工具请求
response = model_with_tools.invoke(messages)
messages.append(response)

for tool_call in response.tool_calls:
    if tool_call["name"] == "get_weather":
        tool_response = get_weather.invoke(tool_call)
        messages.append(tool_response)

# 第二次调用:模型读取 ToolMessage,组织最终回答
final_response = model_with_tools.invoke(messages)
print(final_response.content)

运行顺序是:

1. HumanMessage 提出问题
2. model_with_tools 返回带 tool_calls 的 AIMessage
3. 程序读取 tool_calls
4. get_weather.invoke(tool_call) 执行真实函数
5. 生成 ToolMessage 并追加到 messages
6. 再次调用模型
7. 模型根据工具结果返回自然语言答案

这也是大模型和 Agent 的一个重要区别:单独的大模型通常只会提出工具调用请求,Agent 或程序代码负责真正执行工具并继续循环。

七、多工具调用

可以一次绑定多个工具:

@tool(parse_docstring=True)
def get_weather(city: str) -> str:
    """获取当日天气"""
    return f"{city}当天晴朗"

@tool(parse_docstring=True)
def get_news() -> str:
    """获取当日新闻"""
    return "今天有新的科技新闻"

model_with_tools = model.bind_tools([get_weather, get_news])
response = model_with_tools.invoke(
    "今天杭州天气如何?今天有什么新闻?"
)

模型可能返回一个或多个 tool_calls。程序应遍历全部调用,并根据 tool_call["name"] 分发给对应工具:

for tool_call in response.tool_calls:
    if tool_call["name"] == "get_weather":
        messages.append(get_weather.invoke(tool_call))
    elif tool_call["name"] == "get_news":
        messages.append(get_news.invoke(tool_call))

工具名称必须和绑定时的名称一致。代码中如果把 get_weather 错写成 get_weacher_and_forecast,就不会进入正确的分支。

八、tool_choice:控制模型是否调用工具

1. tool_choice="none"

禁止调用工具:

model_with_tools = model.bind_tools(
    [get_weather],
    tool_choice="none",
)

即使用户问天气,模型也只能直接生成文本答案。

2. tool_choice="auto"

由模型自行判断:

model_with_tools = model.bind_tools(
    [get_weather],
    tool_choice="auto",
)

用户问天气时可能调用工具;用户问“2 + 3 = ?”时可能直接回答。它是最常见、最自然的模式。

3. tool_choice="required"

要求模型必须调用工具:

model_with_tools = model.bind_tools(
    [get_weather],
    tool_choice="required",
)

即使用户问一个简单数学问题,模型也会被要求从工具集合中选一个调用。工具本身是否适合这个问题,需要开发者谨慎设计。

4. 强制调用指定工具

绑定多个工具时,可以强制指定一个:

model_with_tools = model.bind_tools(
    [get_weather1, get_weather2],
    tool_choice="get_weather2",
)

此时模型必须调用名为 get_weather2 的工具。

九、工具设计的实践建议

1. 工具描述要面向模型

不要只写:

"""工具函数"""

应该写清楚:

"""
查询指定城市当天的天气。

Args:
    city: 城市名称,例如北京或上海。
"""

2. 参数尽量少而明确

工具参数越多,模型越容易填错。可以把复杂参数放进 Pydantic Schema,并给枚举字段和默认值。

3. 工具返回结果要便于模型阅读

推荐返回结构清晰的字符串或字典,而不是大段无关日志。

4. 工具执行必须有异常处理

真实 API 可能超时、鉴权失败或返回空数据。生产环境应把异常转换为模型可理解的工具错误消息,并决定是否重试。

5. 不要把 API Key 写进工具代码

使用 .envload_dotenv(override=True) 加载密钥,并通过环境变量读取。

十、总结

@tool:把普通函数包装成 LangChain 工具
docstring:提供工具和参数说明
args_schema:用 Pydantic 约束复杂参数
bind_tools:把工具描述交给模型,不会自动执行工具
tool_calls:模型提出的工具调用请求
ToolMessage:程序执行工具后返回给模型的结果
tool_choice:控制不调用、自动调用、必须调用或指定调用

真正完整的工具调用不是一次 API 请求,而是一个消息循环:模型提出请求,程序执行工具,工具结果回到模型,模型再生成最终答案。理解这条链路后,就能进一步使用 LangGraph 构建带工具的 Agent。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/agood_day_/article/details/163763096

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--