🎯 课程主题
不使用 @tool 装饰器,通过原生 Python 函数 + 文档字符串的方式定义 Tool,并理解底层 convert_to_openai_tool 的转换机制。
📝 核心知识点
1. 普通函数绑定为工具
- 概念说明:普通的 Python 函数通过
model.bind_tools()绑定后,底层会自动调用convert_to_openai_tool()将其转换为工具。 - 关键细节:
- 绑定后返回新对象,需用变量接收(
model_with_tools = model.bind_tools([func]))。 - 不指定任何描述信息时,函数也能被模型识别并调用,但缺少描述会影响复杂场景的识别准确度。
- 绑定后返回新对象,需用变量接收(
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
def get_weather(city: str) -> str:
return f"{city}天气晴朗"
model_with_tools = model.bind_tools([get_weather])
response = model_with_tools.invoke("北京天气怎么样?")
2. 底层转换机制:convert_to_openai_tool
- 概念说明:
bind_tools()底层会调用convert_to_openai_tool()将普通函数转换为符合 OpenAI 规范的工具描述。 - 关键细节:
- 可通过该函数直接查看工具转换后的 schema 结构(name、description、parameters 等)。
- 无论是否使用
@tool装饰器,底层都会走转换逻辑,保证了兼容性。
from langchain_core.utils.function_calling import convert_to_openai_tool
def get_weather(city: str) -> str:
return f"{city}天气晴朗"
# 查看转换后的工具描述
tool_schema = convert_to_openai_tool(get_weather)
print(tool_schema)
# 输出: {"type": "function", "name": "get_weather", "description": "", "parameters": {...}}
3. 工具描述(docstring / description)
- 概念说明:通过函数的文档字符串(三引号
"""...""")为工具添加描述,帮助模型在多个工具中正确识别。 - 关键细节:
- 必须添加描述:无描述的工具在复杂场景下可能导致模型无法正确选择工具。
- 描述应清晰说明工具的用途,不能写模糊内容如"做一些事情"。
def get_weather(city: str) -> str:
"""查询指定城市的天气"""
return f"{city}天气晴朗"
4. 参数描述(Google 风格 docstring)
- 概念说明:在 docstring 中使用
Args:块为每个参数添加描述。 - 关键细节:
- 必须严格遵循格式:
Args:后换行,每个参数一行,param_name: 描述。 Args:中的A必须大写,冒号必须英文格式,必须有换行。- 不规范的格式会导致参数描述被当作工具整体描述,甚至报错。
- 必须严格遵循格式:
正确写法:
def get_weather(city: str) -> str:
"""查询指定城市的天气
Args:
city: 具体的城市名称
"""
return f"{city}天气晴朗"
错误写法示例:
# 错误1:不换行
"""查询指定城市的天气
args: city: 城市名称"""
# 错误2:Args 小写
"""查询指定城市的天气
args:
city: 城市名称"""
# 错误3:中文冒号
"""查询指定城市的天气
Args:
city:城市名称"""
5. 参数类型说明
- 概念说明:在函数签名中声明参数类型(类型注解)。
- 关键细节:
- 关键规则:若在 docstring 中声明了参数描述,则函数签名处必须指明该参数的类型,否则运行报错。
- 若 docstring 中没有参数描述,不声明类型不会报错(但不推荐)。
# 正确:docstring 有参数描述 → 签名必须声明类型
def get_weather(city: str) -> str:
"""查询指定城市的天气
Args:
city: 具体的城市名称
"""
return f"{city}天气晴朗"
# 错误:docstring 有参数描述,但签名无类型 → 报错
def get_weather(city) -> str:
"""查询指定城市的天气
Args:
city: 具体的城市名称
"""
return f"{city}天气晴朗"
6. 参数默认值
- 概念说明:可为参数设置默认值,设置默认值后该参数不再是必填项。
- 关键细节:
- 有默认值的参数不会出现在
required字段中。 - 无默认值的参数会出现在
required字段中。
- 有默认值的参数不会出现在
def get_weather(city: str = "北京", date: str) -> str:
"""查询指定城市的天气
Args:
city: 具体的城市名称
date: 查询的日期
"""
return f"{city}天气晴朗"
# city 有默认值 → required 中只有 date
# required: ["date"]
7. 返回值描述(Returns)
- 概念说明:在 docstring 中添加
Returns:块描述返回值。 - 关键细节:格式与
Args:类似,属于可选项。
def get_weather(city: str) -> str:
"""查询指定城市的天气
Args:
city: 具体的城市名称
Returns:
该城市的天气情况描述
"""
return f"{city}天气晴朗"
🏗️ 架构与工作流
Python 函数(类型注解 + docstring)
│
▼
convert_to_openai_tool()
│
▼
OpenAI 格式工具描述(name, description, parameters)
│
▼
model.bind_tools() 绑定
│
▼
模型调用时依据描述匹配工具
底层源码逻辑:bind_tools 内部会判断函数是否为 Tool 实例;若是则直接格式化,若不是(普通函数)则走 convert_to_openai_tool 转换——保证了无论是否使用 @tool 装饰器,函数都能被当作工具使用。
💻 代码实战
见各知识点的代码示例。
⚠️ 常见问题与避坑指南
- docstring 中声明参数描述,必须同时声明类型注解:否则运行时报错,提示参数在 docstring 中定义了但在函数签名中找不到。
Args:格式必须严格:A大写、英文冒号、必须换行,三者缺一不可。- 不要写模糊的 docstring:如"做一些事情"这类描述无法帮助模型正确选择工具。
- 查看工具 schema 用
convert_to_openai_tool:开发时可用此函数校验工具定义是否符合预期。
💡 个人总结与延伸
不使用 @tool 装饰器定义工具的核心在于 Python 类型注解 + Google 风格 docstring。这种方式的优点是与 Python 原生语法高度兼容,缺点是格式要求严格、易出错。实际开发中,推荐使用下节课的 @tool 装饰器方式,但理解本节的底层机制有助于调试和排错。