langchain的Tools教程(一)

langchain的Tools教程(一)
1、Tools概述1.1 工具的重要性要构建更强大的AI工程应用只有生成文本这样的“纸上谈兵”能力自然是不够的。工具是赋予大语言模型与外部世界交互能力的关键组件从而能让智能体执行搜索、计算、数据库查询、邮件发送或调用第三方API等进而构建功能强大的AI应用。借助工具大模型才能从“认识世界”走向“改变世界”。举例工具是构建智能体的核心要素之一1.2 工具调用的方式在LangChain中工具Tools实际上是指明确定义了输入和输出的可调用函数。因此工具调用(Tool Calling)也被称为函数调用(Function Calling)。具体有两种调用方式方式1直接调用这种方式适合测试时使用。from langchain_core.tools import tool tool def get_weather(city: str) - str: 获取指定城市的天气信息 参数: city: 城市名称如北京、上海 返回: 天气信息字符串 # 你的实现 return city 晴天温度 15°C # 使用 .invoke() 方法 res get_weather.invoke({city:上海}) print(res)输出上海晴天温度 15°C方式2绑定到模型主流这种方式让AI来调用开发中使用。from langchain.chat_models import init_chat_model from dotenv import load_dotenv import os from langchain_core.tools import tool # 从.env文件中加载环境变量 load_dotenv(overrideTrue) QWEN_API_KEY os.getenv(QWEN_API_KEY) QWEN_BASE_URL os.getenv(QWEN_BASE_URL) model init_chat_model( modelqwen-vl-plus, model_provideropenai, api_keyQWEN_API_KEY, base_urlQWEN_BASE_URL, ) # 定义工具 tool def get_weather(city: str) - str: 获取指定城市的天气 # 你的实现 return 晴天温度 15°C # 绑定工具 model_with_tools model.bind_tools([get_weather]) # AI 可以决定是否调用工具 response model_with_tools.invoke(我想去深圳玩但不知道天气如何) # response model_with_tools.invoke(2 3 ) # 检查 AI 是否要调用工具 if response.tool_calls: print(AI 想调用工具, response.tool_calls) else: print(AI 直接回答, response.content)输出AI 想调用工具 [{name: get_weather, args: {city: 深圳}, id: call_91508f46951b455e89b981, type: tool_call}]查找到的tool_calls是response对象的实例属性里面是大模型想调用的工具大模型会根据输入来判断要查找的天气如果只输出response model_with_tools.invoke(我想去深圳玩)AI 直接回答 深圳是一个非常现代化的城市有很多好玩的地方可以去。以下是一些推荐的景点和活动1. **深圳欢乐谷**这是一个大型的主题公园适合家庭游玩有各种刺激的游乐设施和表演。2. **东部华侨城**这里有美丽的自然风光和丰富的娱乐项目包括茶溪谷、大侠谷等区域。等等的这些回复不会提到天气。1.3 工具调用的整体流程大模型能根据对话上下文决定何时调用工具以及传递哪些参数。经典流程如下我们现在编写的LangChain应用对应上图中的AI助手或应用。现在搞完了第三步还没有开始调用工具返回结果1.4 从Message流转看工具的调用前提模型的初始化参考2使用tool修饰from langchain.chat_models import init_chat_model from dotenv import load_dotenv import os # 从.env文件中加载环境变量 load_dotenv(overrideTrue) QWEN_API_KEY os.getenv(QWEN_API_KEY) QWEN_BASE_URL os.getenv(QWEN_BASE_URL) model init_chat_model( modelqwen-vl-plus, model_provideropenai, api_keyQWEN_API_KEY, base_urlQWEN_BASE_URL, ) from langchain.messages import HumanMessage, ToolMessage tool def get_weather(city: str): 获取天气的工具 return f{city}天气晴朗~ # 将模型和工具绑定 model_with_tools model.bind_tools([get_weather]) messages [ HumanMessage(今天北京天气如何) ] # 模型生成调用工具请求 response model_with_tools.invoke(messages) # 添加AIMessage messages.append(response) tool_calls response.tool_calls for tool_call in tool_calls: if tool_call[name] get_weather: # 返回的是ToolMessage类型消息 tool_response get_weather.invoke(tool_call) print(type(tool_response)) # 返回的是ToolMessage类型消息 messages.append(tool_response) print( messages ) for msg in messages: msg.pretty_print() print( messages ) final_response model_with_tools.invoke(messages) print(ffinal_response: \n{final_response})输出class langchain_core.messages.tool.ToolMessage messages Human Message 今天北京天气如何 Ai Message Tool Calls:get_weather (call_4278cd8b979b404994f66a)Call ID: call_4278cd8b979b404994f66aArgs:city: 北京 Tool Message Name: get_weather北京天气晴朗~ messages final_response:content今天北京天气晴朗非常适合外出活动记得带上太阳镜和防晒霜享受美好的一天吧如果有其他问题随时告诉我哦~ additional_kwargs{refusal: None} response_metadata{token_usage: {completion_tokens: 34, prompt_tokens: 161, total_tokens: 195, completion_tokens_details: {accepted_prediction_tokens: None, audio_tokens: None, reasoning_tokens: None, rejected_prediction_tokens: None, text_tokens: 34}, prompt_tokens_details: {audio_tokens: None, cached_tokens: None, text_tokens: 161}}, model_provider: openai, model_name: qwen-vl-plus, system_fingerprint: None, id: chatcmpl-cae91143-e7bf-9d77-9653-4826cec36d1c, finish_reason: stop, logprobs: None} idlc_run--019f50c8-bdce-7361-948c-cacaced18e31-0 tool_calls[] invalid_tool_calls[] usage_metadata{input_tokens: 161, output_tokens: 34, total_tokens: 195, input_token_details: {}, output_token_details: {}}分三层讲清楚为什么工具可以.invoke(tool_call)以及底层原理1.先纠正一个关键细节get_weather不是普通函数是 LangChainTool对象tool def get_weather(city: str): 获取天气的工具 return f{city}天气晴朗~tool装饰器会把原生 Python 函数包装成一个StructuredTool工具实例。 这个工具对象属于Runnable可运行接口所有 Runnable 都自带.invoke()方法这是 LangChain 统一规范。2.tool.invoke()接收参数的两种合法形式工具的invoke支持两种入参格式都能正常运行方式 1直接传关键字参数字典最基础# tool_call[args] {city: 北京} result get_weather.invoke({city: 北京})方式 2直接传入完整tool_call字典你代码用的方式tool_call { name: get_weather, args: {city: 北京}, id: call_xxxx, type: tool_call } tool_response get_weather.invoke(tool_call)3.拆解get_weather.invoke(tool你传入完整tool_call字典{name,args,id,type}工具内部判断输入是 tool_call 结构自动取出tool_call[args]作为函数入参执行原函数get_weather(city北京)拿到函数返回文本北京天气晴朗~自动用 tool_call 里的id封装成ToolMessage对象返回这就是你打印type(tool_response)是 ToolMessage 的原因ToolMessage(content北京天气晴朗~, tool_call_idcall_xxxx)对比手动写法等价更繁琐# 等价手动实现 args tool_call[args] content get_weather(**args) tool_response ToolMessage(contentcontent, tool_call_idtool_call[id])流程图1.5 工具调用流程总结所以如果真正要大模型根据工具调用结果进行回复完整的调用流程包括如下四个步骤步骤1模型绑定工具通过model.bind_tools([...])绑定一个或者多个工具。步骤2模型生成工具调用请求用户输入问题调用模型比如invoke()。如果需要调用工具模型返回包含工具调用信息如工具名称和参数的AIMessage。步骤3开发者手动执行工具用户从响应中提取工具调用信息并手动调用对应的工具比如工具.invoke()。步骤4将工具执行结果ToolMessage传递给模型生成最终结果将之前用户提问内容和手动执行工具结果ToolMessage返回模型模型最终生成回复。特别注意大模型调用工具是单次推理直接响应需要开发者手动执行工具并管理循环适合简单、确定的任务。2、工具的定义方式1不使用tool2.1 模型绑定工具并发送请求from langchain.chat_models import init_chat_model from dotenv import load_dotenv import os # 从.env文件中加载环境变量 load_dotenv(overrideTrue) QWEN_API_KEY os.getenv(QWEN_API_KEY) QWEN_BASE_URL os.getenv(QWEN_BASE_URL) model init_chat_model( modelqwen-vl-plus, model_provideropenai, api_keyQWEN_API_KEY, base_urlQWEN_BASE_URL, ) from rich import print as rprint # 定义工具 def get_weather(city: str): return f{city}天气晴朗 # 将模型和工具绑定 model_with_tools model.bind_tools([get_weather]) response model_with_tools.invoke( 今天北京天气如何 ) rprint(response) # print(response.tool_calls)2.2 工具描述的各部分详解2.2.1 了解convert_to_openai_tool执行 model.bind_tools([get_weather])底层最终会调用 convert_to_openai_tool 生成工具描述。所以我们可以直接调用后者查看解析后的工具描述。from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city: str): return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出如下{type: function,function: {name: get_weather,description: ,parameters: {properties: {city: {type: string}},required: [city],type: object}}}结果字段说明(1) type: 定义当前数据节点必须是什么数据类型。常见类型有 string, number, integer, boolean, object, array, null。object即是json对象。(2) properties用于定义JSON对象Object中可以包含哪些属性键以及每个属性对应的值类型和说明。(3) required: 当 type为 object时使用是一个数组列出了对象中必须存在的属性名。问题为什么不使用tool装饰器修饰的函数也可以理解为工具呢查看 convert_to_openai_tool 底层源码:elif isinstance(function, langchain_core.tools.base.BaseTool): oai_function cast(dict, _format_tool_to_openai_function(function)) elif callable(function): oai_function cast( dict, _convert_python_function_to_openai_function(function) )相当于加了tool修饰的函数走上面的分支没有加tool修饰的函数走下面的分支后者会基于函数定义和docstring生成pydantic模式的描述然后转换为规范的tool_schema。2.2.2 description说明convert_to_openai_tool 会从 docstring(文档字符串) 加载工具的描述信息上面的案例中docstring 为空所以抽取的 description 为空。docstring文档字符串使用三个双引号表示开始和结束。from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city: str): 天气查询工具 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出{ type: function, function: { name: get_weather, description: 查询城市天气, parameters: {properties: {city: {type: string}}, required: [city], type: object} } }2.2.3 参数说明convert\_to\_openai\_tool 会从 docstring 加载参数说明这里的 docstring 必须遵循 Google 风格。Google 风格 docstring 说明https://google.github.io/styleguide/pyguide.htmlGoogle 风格 docstring 示例: https://www.sphinx-doc.org/en/master/usage/extensions/example_google.htmlPython docstring 通用约定:https://peps.python.org/pep-0257/不推荐推荐Google的基础用法不必完整阅读规范只需要按照下面的示例仿写即可。from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city: str): 天气查询工具 Args: city: 城市名称 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))使用 Args:、Returns:、Raises: 等关键字这种方式可读性强。Agent通过工具的这些注释来理解工具的用途和调用时机因此清晰、准确的文档字符串是工具能被正确调用的前提。输出如下{ type: function, function: { name: get_weather, description: 查询城市天气, parameters: {properties: {city: {type: string}}, required: [city], type: object} } }添加描述from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city: str): 查询城市天气 Args: city:具体的城市 Returns: 返回城市的天气 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出{ type: function, function: { name: get_weather, description: 查询城市天气, parameters: { properties: {city: {description: 具体的城市, type: string}}, required: [city], type: object } } }注意要记得换行否则会看成工具的描述输入法格式必须是英文而且不能写小写args否则会出现以下后果错误输出{ type: function, function: { name: get_weather, description: 查询城市天气\nArgs:\n city:具体的城市, parameters: {properties: {city: {type: string}}, required: [city], type: object} } }AI 依赖 docstring 来理解工具。# ✗ 不好太模糊 tool def tool1(x: str) - str: 做一些事情 ...# 好清晰明确 tool def search_products(query: str) - str: 在产品数据库中搜索产品 Args: query: 搜索关键词如笔记本电脑、手机 Returns: 产品列表的 JSON 字符串 ...2.2.4 参数类型说明参数类型来源于函数的类型注解。from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city): 天气查询工具 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出{ type: function, function: { name: get_weather, description: 天气查询工具, parameters: {properties: {city: {}}, required: [city], type: object} } }删除了参数类型注解则工具描述中不包含参数类型说明注意如果docstring中包含参数说明则对应的参数必须有类型注解否则报错from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city): 天气查询工具 Args: city: 城市名称 print(天气晴朗) rprint(convert_to_openai_tool(get_weather))报错如下1 | Traceback...2 | ValueError: Arg city in docstring not found in function signature.2.2.5 参数默认值说明如果参数没有默认值则会包含在required对应的列表中。反之则参数的描述信息会包含 default 字段并且不会出现在required列表中。from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(city: str北京): 天气查询工具 Args: city: 城市名称 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出如下{ type: function, function: { name: get_weather, description: 天气查询工具, parameters: { properties: {city: {default: 北京, description: 城市名称, type: string}}, type: object } } }目前只有一个参数并且有默认值所以required字段被移除了。举例2:from langchain_core.utils.function_calling import convert_to_openai_tool from rich import print as rprint def get_weather(dt: str, city: str北京): 天气查询工具 Args: dt: 日期 city: 城市名称 return f{city}天气晴朗 rprint(convert_to_openai_tool(get_weather))输出如下{ type: function, function: { name: get_weather, description: 天气查询工具, parameters: { properties: { dt: {description: 日期, type: string}, city: {default: 北京, description: 城市名称, type: string} }, required: [dt], type: object } } }注该教程由尚硅谷所作本人进行学习并补充说明