
工具调用根本作用是让大语言模型LLM具备与外部世界交互的能力。LLM 本身是一个封闭的知识系统其能力受限于其训练数据存在滞后性和内在的文本生成逻辑。它无法执行直接计算、查询实时信息、操作数据库或调用任何外部 API。工具调用打破了这层壁垒其作用具体体现在扩展能力边界模型可以借助工具完成它自身无法完成的任务如执行数学计算、搜索网络、查询数据库等。保证信息实时性通过调用搜索工具或数据库查询工具LLM 可以获取最新的、训练数据中不存在的信息避免回答过时或“一本正经地胡说八道”。处理复杂任务将一个复杂的用户请求如“分析我上个月的消费趋势”分解成多个步骤并依次调用不同的工具如“从数据库获取数据” - “用 Python 进行数据分析” - “生成图表”来协同完成。协调这件事这更体现在 Agent 智能体上。连接现有系统可以将企业内部已有的系统、API 和数据库封装成工具让 LLM 成为一个用自然语言驱动的统一接口极大地提升了自动化和集成能力。所以在 LangChain 中聊天模型害提供了工具调用的功能。1. 创建工具1.1. 方式一使用tool装饰器创建工具1.1.1 模式1常规用法使用tool和 python 函数来创建fromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:int,b:int)-int:returnabprint(add.invoke({a:1,b:2}))此时如果运行会出现报错报错信息如下那我们接下来就给它一个字符串文档fromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:int,b:int)-int:两数相加 Args: a第一个整数 b第二个整数 returnabprint(add.invoke({a:1,b:2}))此时再次进行运行结果正确结论函数名、字符串文档和类型提示都需要定义。类型提示参数的类型 返回值的类型。工具的属性工具名称函数名、工具描述文档字符串、工具参数类型提示。为什么需要定义这些呢这些信息都是传递给工具 schema的。1.1.2. 什么是 Schema?JSON 我们大家都很熟悉JSON schema 是用来描述整个 JSON结构的。有了JSON Schema之后就可以知道JSON长什么样用来做校验工作。那么同理工具也是有属性的工具 Schema 是用来描述工具结构的。fromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:int,b:int)-int:两数相加 Args: a第一个整数 b第二个整数 returnabprint(add.invoke({a:1,b:2}))# 获取工具名称、描述、参数print(add.name)print(add.description)print(add.args)运行结果1.1.3. 工具为什么需要这些属性属性工具名称、工具描述、工具参数。我们定义的这些工具是要提供给聊天模型调用的我们将提示词写的越标准聊天模型给我们的回复也会更高可用。对于工具来说1. 工具的名称可以让 LLM 知道有哪些工具可以调用哪些工具。2. 工具的描述实际上就是在写提示词告诉模型工具的能力让模型在执行任务的时候知道调用哪个工具。3. 工具的参数可以让模型知道怎么调用工具要传入什么参数传入的参数类型是什么。只要能把工具的这三个属性传递过去就能定义出来工具。1.1.4. 文档字符串推荐使用 Google 风格的文档字符串。deffetch_data(url,retries3):从给定的URL获取数据。 Args: url (str): 要从中获取数据的URL。 retries (int, optional): 失败时重试的次数。默认为3。 Returns: dict: 从URL解析的JSON响应。 # ... 函数实现 ...1.1.5. 模式2依赖 Pydantic 类在LangChain中可以使用Pydantic它提供了运行时数据的校验和类型检查。fromlangchain_core.toolsimporttoolfrompydanticimportBaseModel,Field# 定义参数add工具的输入classAddInput(BaseModel):两数相加a:intField(...,description第一个整数)# 三个点表示这个字段的必填的没有默认值b:intField(...,description第二个整数)# 将参数传递给tooltool(args_schemaAddInput)defadd(a:int,b:int)-int:returnabprint(add.invoke({a:1,b:2}))# 获取工具名称、描述、参数print(add.name)print(add.description)print(add.args)运行结果1.1.6. 模式2依赖 AnnotatedfromtypingimportAnnotatedfromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:Annotated[int,...,第一个整数],b:Annotated[int,...,第二个整数],)-int:两数相加 Args: a第一个整数 b第二个整数 returnabprint(add.invoke({a:1,b:2}))# 获取工具名称、描述、参数print(add.name)print(add.description)print(add.args)1.2. 方式二使用 StructuredTool 类提供的函数创建工具1.2.1. 常规用法# 使用StructuredTool类提供的函数创建工具fromlangchain_core.toolsimportStructuredTool# 定义方法defadd(a:int,b:int)-int:两数相加returnab# 定义工具add_toolStructuredTool.from_function(funcadd)# 执行工具print(add_tool.invoke({a:1,b:2}))1.2.2 加入配置依赖Pydantic类# 使用StructuredTool类提供的函数创建工具fromlangchain_core.toolsimportStructuredToolfrompydanticimportBaseModel,Field# 定义参数classAddInput(BaseModel):a:intField(description第一个整数)b:intField(description第二个整数)defadd(a:int,b:int)-int:returnab# 定义工具add_toolStructuredTool.from_function(funcadd,nameADD,# 工具名description两数相加,# 工具描述args_schemaAddInput,# 工具参数)# 执行工具print(add_tool.invoke({a:1,b:2}))print(add_tool.name)print(add_tool.description)print(add_tool.args)1.1.3. 加入 response_format 配置可以保留工具调用的过程。保留过程可以帮助我们在出现问题的时候分析问题。# 使用StructuredTool类提供的函数创建工具fromtypingimportTuple,Listfromlangchain_core.toolsimportStructuredToolfrompydanticimportBaseModel,Field# 定义方法classAddInput(BaseModel):a:intField(description第一个整数)b:intField(description第二个整数)# 方法返回一个元组 str是返回的结果List[int]是保留的过程defadd(a:int,b:int)-Tuple[str,List[int]]:nums[a,b]contentf{nums}两数相加的结果是{ab}returncontent,nums# 定义工具add_toolStructuredTool.from_function(funcadd,nameADD,# 工具名description两数相加,# 工具描述args_schemaAddInput,# 工具参数response_formatcontent_and_artifact# 大模型不知道工具返回的结果中哪部分是结果哪部分是过程加上这个参数才能让大模型区分开。)# 执行工具# print(add_tool.invoke({a: 1, b: 2})) # 这样调用的结果只会显示结果不会显示过程是因为这种调用方法是手动调用工具的写法需要模拟大模型调用工具的方式才能看到过程。# print(add_tool.name)# print(add_tool.description)# print(add_tool.args)# 模拟大模型调用工具print(add_tool.invoke({name:ADD,args:{a:1,b:2},type:tool_call,# 调用类型 必填id:111# id工具调用的关联标识符大模型要调用ADD工具需要用id将工具调用请求和工具调用结果关联起来必填}))输出结果对于聊天模型来说就可以通过字段content获取结果通过字段artifact获取过程。但是实际上聊天模型是获取content为主的。2. 工具绑定与调用fromtypingimportAnnotatedfromlangchain_deepseekimportChatDeepSeekfromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:Annotated[int,...,第一个整数],b:Annotated[int,...,第二个整数],)-int:两数相加returnabtooldefmultiply(a:Annotated[int,...,第一个整数],b:Annotated[int,...,第二个整数],)-int:两数相乘returna*b# 定义modelmodelChatDeepSeek(modeldeepseek-chat)# 绑定工具tools[add,multiply]# 返回了一个新的Runnable一个绑定了新的工具的modelmodel_with_toolsmodel.bind_tools(toolstools)# 调用工具print(model_with_tools.invoke(2乘3等于多少))输出结果content2乘3等于6。\n\n我们可以用工具计算一下additional_kwargs{refusal:None}response_metadata{token_usage:{completion_tokens:71,prompt_tokens:371,total_tokens:442,completion_tokens_details:None,prompt_tokens_details:{audio_tokens:None,cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:371},model_provider:deepseek,model_name:deepseek-v4-flash,system_fingerprint:fp_8b330d02d0_prod0820_fp8_kvcache_20260402,id:3fce2ee8-ac0a-4f1d-b67d-e6acec32d09c,finish_reason:tool_calls,logprobs:None}idlc_run--019e972c-35da-7101-884d-4e91e9d9f4c2-0tool_calls[{name:multiply,args:{a:2,b:3},id:call_00_5Lc30LrcTN4I4YZA49Jk2866,type:tool_call}]invalid_tool_calls[]usage_metadata{input_tokens:371,output_tokens:71,total_tokens:442,input_token_details:{cache_read:0},output_token_details:{}}对返回的结果格式化一下content2乘3等于6。\n\n我们可以用工具计算一下additional_kwargs{refusal:None}response_metadata{token_usage:{completion_tokens:71,prompt_tokens:371,total_tokens:442,completion_tokens_details:None,prompt_tokens_details:{audio_tokens:None,cached_tokens:0},prompt_cache_hit_tokens:0,prompt_cache_miss_tokens:371},model_provider:deepseek,model_name:deepseek-v4-flash,system_fingerprint:fp_8b330d02d0_prod0820_fp8_kvcache_20260402,id:3fce2ee8-ac0a-4f1d-b67d-e6acec32d09c,finish_reason:tool_calls,logprobs:None}idlc_run--019e972c-35da-7101-884d-4e91e9d9f4c2-0tool_calls[{name:multiply,args:{a:2,b:3},id:call_00_5Lc30LrcTN4I4YZA49Jk2866,type:tool_call}]invalid_tool_calls[]usage_metadata{input_tokens:371,output_tokens:71,total_tokens:442,input_token_details:{cache_read:0},output_token_details:{}}可以看到大模型在执行任务的时候根据我们的提示词语义选择并调用了我们给它绑定的工具。2.1 强制大模型调用工具大模型是否调用工具是根据我们的提示词语义和工具的相关性来决定的并不是说我们给它绑定了工具它就每次都调用工具但是我们可以强制大模型调用某个工具。只需要在绑定工具的时候加上一个参数并设置其值为anymodel_with_toolsmodel.bind_tools(toolstools,tool_choiceany)调用结果可以看到它确实调用了我们指定的工具但是content是空的。2.2 将工具输出传递给聊天模型上面我们调用工具虽然可以拿到计算结果但是它返回的结果不是我们想要的形式我们希望它返回2 * 3的结果为6而不是单单的一个6。上面我们调用大模型返回的结果中有一个tool_calls字段可以看到这个字段中的内容和我们上面模拟大模型调用姿势的时候是一模一样的。通过调试可以看到工具调用返回的是一个ToolMesage。那么我们就可以构造在一个messages消息列表将其传给大模型。fromtypingimportAnnotatedfromlangchain_core.messagesimportHumanMessagefromlangchain_deepseekimportChatDeepSeekfromlangchain_core.toolsimporttool# 定义工具tooldefadd(a:Annotated[int,...,第一个整数],b:Annotated[int,...,第二个整数],)-int:两数相加returnabtooldefmultiply(a:Annotated[int,...,第一个整数],b:Annotated[int,...,第二个整数],)-int:两数相乘returna*b# 定义modelmodelChatDeepSeek(modeldeepseek-chat)# 绑定工具tools[add,multiply]# 返回了一个新的Runnable一个绑定了新的工具的modelmodel_with_toolsmodel.bind_tools(toolstools,tool_choiceany)# 调用工具# tool_result multiply.invoke(model_with_tools.invoke(2*3等于多少).tool_calls[0])# print(tool_result)# 定义消息列表添加要传递给聊天模型的消息message[HumanMessage(2*3等于多少21等于多少),]ai_msgmodel_with_tools.invoke(message)message.append(ai_msg)# print(ai_msg)# 我们给了两个问题分别调用了两个工具tool_calls中也有两个元素。# tool_calls [# {name: multiply,args: {a: 2,b: 3},id: call_00_KQZFPPxU1AfuVwTZciOL0371,type: tool_call},# {name: add,args: {a: 2,b: 1},id: call_01_z1Zq9jSk9vlHmgU73uP40488,type: tool_call}# ]# 构建toolmessage,并添加到消息列表中fortoo_callinai_msg.tool_calls:# 定义一个查找的字典并通过tool_call里面的name忽略大小写去查找toolselected_tool{add:add,multiply:multiply}[too_call[name].lower()]tool_msgselected_tool.invoke(too_call)message.append(tool_msg)print(message)print(model.invoke(message).content)输出结果