【零基础入门 LLM 开发 · Day 15】LangChain 注册工具——告别手写 JSON Schema场景Day 8-9 用原生 SDK 手写工具说明书一个get_weather就写了二十来行 JSON Schema。工具一多手写说明书又臭又长还容易写错字段名。今天用 LangChain 把这件事变省力三种姿势逐级进化手写字典 → Pydantic 模型 → tool 装饰器。起步bind_tools 一行顶一坨工具说明书还是 Day 8 那套字典但绑定和调用交给 LangChainimportosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_model load_dotenv()modelinit_chat_model(modelos.getenv(YUMC_MODEL_NAME),model_provideropenai,api_keyos.getenv(YUMC_MODEL_API_KEY),base_urlos.getenv(YUMC_MODEL_BASE_URL),)# 用字典描述工具和 Day 8 手写的一样tools[{type:function,function:{name:get_weather,description:查询指定城市的天气,parameters:{type:object,properties:{city:{type:string,description:城市名称,如 杭州、北京}},required:[city],},},}]# bind_tools() 将工具绑定到模型model_with_toolsmodel.bind_tools(tools)responsemodel_with_tools.invoke(杭州今天天气怎么样)ifresponse.tool_calls:fortcinresponse.tool_calls:print(f工具名称{tc[name]})print(f参数{tc[args]})print(f调用ID{tc[id]})response.tool_calls是个列表——AI 觉得需要的话会一次请求多个工具调用别只取第一个。进阶Pydantic 模型直接当工具手写字典的问题字段名、类型、描述全靠人肉对齐。换成 Pydantic 模型LangChain 自动帮你生成说明书frompydanticimportBaseModel,FieldclassWeatherInput(BaseModel):查询指定城市的天气情况city:strField(description城市名称如 杭州、北京)unit:strField(defaultcelsius,description温度单位celsius摄氏度或 fahrenheit华氏度,)classCalculatorInput(BaseModel):执行数学计算expression:strField(description要计算的数学表达式,如(35)*2)# 传入 Pydantic 模型LangChain 自动转换为工具描述model_with_toolsmodel.bind_tools([WeatherInput,CalculatorInput])responsemodel_with_tools.invoke(北京今天多少度顺便帮我算一下123*456)print(f模型请求了{len(response.tool_calls)}个工具调用)fortcinresponse.tool_calls:print(f{tc[name]}({tc[args]}))为什么 Pydantic 模型能当工具LangChain 拿到对象后自动提取三样东西——和 Day 8 手写的 JSON Schema 是同一个东西只是框架替你生成Pydantic 里的变成说明书里的类名function.namedocstring查询指定城市的天气情况function.description字段类型 Field(description...)parameters的 JSON Schema更简tool 装饰器普通函数也能注册——函数名、docstring、类型注解就是现成的说明书fromlangchain_core.toolsimporttooltooldefget_location(location:str)-str:获取指定位置的经纬度returnf获取{location}的经纬度# 和 Pydantic 模型混着用也没问题model_with_toolsmodel.bind_tools([WeatherInput,CalculatorInput,get_location])responsemodel_with_tools.invoke(北京今天多少度顺便帮我算一下123*456经纬度)三种方式对比方式工具名描述参数 schema适用场景bind_tools(字典)手写手写手写和 Day 8 等价没必要再用bind_tools(Pydantic)类名docstring字段定义参数结构复杂、需要校验/复用tool函数名docstring类型注解最常用一行搞定tool的本质LangChain 根据函数签名现场生成一个 Pydantic 模型get_location.args_schema可以看所以后两种方式殊途同归。和 Day 13 的血缘with_structured_output(methodfunction_calling)的底层就是 bind_toolsPydantic 模型 → JSON Schema → 发给模型。注册工具模型主动申请调用你执行后把结果喂回去Day 8 流程结构化输出强制模型把你的 Pydantic 模型当工具调用一次直接拿到参数当结果。同一套机制两个用途。学会一个另一个就是白送。运行效果模型请求了3个工具调用 WeatherInput ({city: 北京, unit: celsius}) CalculatorInput ({expression: 123*456}) get_location ({location: 北京})一个问题触发三个工具调用——AI 自己决定要不要用、用哪几个、传什么参数。今日踩坑点tools写成装饰器from langchain import tools导入的是模块模块不能调用报TypeError: module object is not callable。要用from langchain_core.tools import tool然后tool或者tools.tool。Field(decription...)拼错Pydantic 不认识的关键字被当额外参数只给一条 DeprecationWarning不报错——结果描述丢了模型只能瞎猜参数含义工具质量悄悄下降。警告也要看f-string 括号位置f{tc[name] ({tc[args]})}会把tc[name]当函数调用name 后面跟了个括号报str object is not callable。正确写法f{tc[name]} ({tc[args]})。Pydantic 类名直接变工具名WeatherInput这种名字出现在给模型看的说明书里很怪。tool用函数名更自然坚持用 Pydantic 就把类名起得像工具名。今日小练习必做用tool写一个get_current_time()跑通现在几点了——对比 Day 8 的必做题感受少写了多少行。进阶print(get_current_time.args_schema)看看 tool 现场生成的 Pydantic 模型长什么样。思考把 Day 8 手写说明书的工具全部换成 tool 版本模型的工具选择行为有变化吗明日预告Day 15工具又多又杂时AI 容易选错、token 也贵。学公司设前台——主 Agent 只管转接子 Agent 各管一摊。附录完整练习代码langchainTest4-tools.py字典版 bind_toolsimportosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_model load_dotenv()modelinit_chat_model(modelos.getenv(YUMC_MODEL_NAME),model_provideropenai,api_keyos.getenv(YUMC_MODEL_API_KEY),base_urlos.getenv(YUMC_MODEL_BASE_URL),)# 用字典描述工具tools[{type:function,function:{name:get_weather,description:查询指定城市的天气,parameters:{type:object,properties:{city:{type:string,description:城市名称,如 杭州、北京}},required:[city],},},}]# bind_tools() 将工具绑定到模型model_with_toolsmodel.bind_tools(tools)responsemodel_with_tools.invoke(杭州今天天气怎么样)ifresponse.tool_calls:print(模型调用了以下工具)fortcinresponse.tool_calls:print(f工具名称{tc[name]})print(f参数{tc[args]})print(f调用ID{tc[id]})else:print(f模型直接回复:{response.content})langchainTest4-tools2.pyPydantic 模型版importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_modelfrompydanticimportBaseModel,Field load_dotenv()modelinit_chat_model(modelos.getenv(YUMC_MODEL_NAME),model_provideropenai,temperature0,api_keyos.getenv(YUMC_MODEL_API_KEY),base_urlos.getenv(YUMC_MODEL_BASE_URL),)classWeatherInput(BaseModel):查询指定城市的天气情况city:strField(description城市名称如 杭州、北京)unit:strField(defaultcelsius,description温度单位celsius摄氏度或 fahrenheit华氏度,)classCalculatorInput(BaseModel):执行数学计算expression:strField(description要计算的数学表达式,如(35)*2)# 传入pydantic 模型LangChain 自动转换为工具描述model_with_toolsmodel.bind_tools([WeatherInput,CalculatorInput])responsemodel_with_tools.invoke(北京今天多少度顺便帮我算一下123*456)print(f模型请求了{len(response.tool_calls)}个工具调用)fortcinresponse.tool_calls:print(f{tc[name]}({tc[args]}))langchainTest4-tools3.pytool 装饰器版importosfromdotenvimportload_dotenvfromlangchain_core.toolsimporttoolfromlangchain.chat_modelsimportinit_chat_modelfrompydanticimportBaseModel,Field load_dotenv()modelinit_chat_model(modelos.getenv(YUMC_MODEL_NAME),model_provideropenai,temperature0,api_keyos.getenv(YUMC_MODEL_API_KEY),base_urlos.getenv(YUMC_MODEL_BASE_URL),)classWeatherInput(BaseModel):查询指定城市的天气情况city:strField(description城市名称如 杭州、北京)unit:strField(defaultcelsius,description温度单位celsius摄氏度或 fahrenheit华氏度,)classCalculatorInput(BaseModel):执行数学计算expression:strField(description要计算的数学表达式,如(35)*2)tooldefget_location(location:str)-str:获取指定位置的经纬度returnf获取{location}的经纬度# 传入pydantic 模型LangChain 自动转换为工具描述model_with_toolsmodel.bind_tools([WeatherInput,CalculatorInput,get_location])responsemodel_with_tools.invoke(北京今天多少度顺便帮我算一下123*456经纬度)print(f模型请求了{len(response.tool_calls)}个工具调用)fortcinresponse.tool_calls:print(f{tc[name]}({tc[args]}))