LangChain 聊天模型中工具的调用详解----《Hello LangChain LangGraph!》(3)
文章目录
- LangChain 聊天模型中工具的调用详解
- 聊天模型中工具的调用
- 创建工具
- 绑定工具
- 工具的调用
LangChain 聊天模型中工具的调用详解
聊天模型中工具的调用
调用工具的一般步骤:
定义工具(创建工具)->绑定工具->调用工具
LangChain提供的现成的工具和工具包:(工具一般都不需要自己手搓)
LangChain Python integrations - Docs by LangChain可以在这里面获取
创建工具
1.在 LangChain 中,实现了一个
@tool装饰器来创建工具,@tool装饰器是自定义工具的最简单方法。# 定义工具@tooldefadd(a:int,b:int)->int:# 函数定义"""两数相加 Args: a: 第一个整数 b: 第二个整数 """returna+b# 函数体一共就是4个部分:
@tool装饰器:把普通函数标记为「LangChain 工具」函数定义:工具的名称、参数、返回值
文档字符串(Docstring):给模型看的「工具说明书」
函数体:工具的底层执行逻辑
–
LangChain 用 @tool 装饰器时,会自动根据你的函数生成 Schema(工具名默认用函数名;工具描述默认用函数的文档字符串)–若是简单定义工具,如上述示例,工具 schema 需要解析 Google 风格的文档字符串去获取参数描述
除了上面这种方式,还有其他方式可以让工具 schema,获取相关工具声明需要的内容:
1.模式1:依赖Pydantic类
# pydantic 数据验证frompydanticimportBaseModel,FieldclassAddInput(BaseModel):"""Add two integers."""a:int=Field(...,description="First integer")b:int=Field(...,description="Second integer")# 除非提供默认值,不然这里所有的字段在调用工具时都是必填的# 定义工具fromlangchain_core.toolsimporttool@tool(args_schema=AddInput)defadd(a:int,b:int)->int:returna+b2.模式2:依赖Annotated类
fromlangchain_core.toolsimporttoolfromtyping_extensionsimportAnnotated@tooldefadd(a:Annotated[int,...,"First integer"],b:Annotated[int,...,"Second integer"])->int:"""Add two integers."""returna+b
2.使用StructuredTool类提供的函数创建工具:
这个类其中的
from_function类方法通过给定的函数来创建并返回一个工具from_function类方法的定义:@classmethoddeffrom_function(# 1. 同步函数:工具的核心执行逻辑(比如加法、查询天气)func:Callable|None=None,# 2. 异步协程函数:如果工具是异步执行的,传这个参数coroutine:Callable[[...],Awaitable[Any]]|None=None,# 3. 工具名称:AI 调用工具时用的名字,默认取函数名name:str|None=None,# 4. 工具描述:给 AI 看的说明书,AI 靠它判断是否调用该工具description:str|None=None,# 5. 是否直接返回结果:True=工具结果直接给用户,不经过AI整理;False=AI会二次加工回答return_direct:bool=False,# 6. 参数校验模板:Pydantic 的 BaseModel,定义工具必填/选填参数、类型args_schema:type[BaseModel]|dict[str,Any]|None=None,# 7. 是否自动推断参数Schema:自动从函数注解、文档字符串生成参数规则infer_schema:bool=True,# 强制关键字参数分隔符:后面的参数必须用 key=value 的形式传参*,# 8. 响应格式:content=只返回纯文本给AI;content_and_artifact=返回文本+原始数据(你刚学的)response_format:Literal['content','content_and_artifact']='content',# 9. 是否自动解析文档字符串:自动从函数 """注释""" 提取描述、参数说明parse_docstring:bool=False,# 10. 文档字符串不合法时是否报错:开启后,注释格式错了直接抛错error_on_invalid_docstring:bool=False,# 11. 额外参数:接收所有未定义的关键字参数**kwargs:Any,# 方法返回值:一个标准的 LangChain 结构化工具(StructuredTool)对象)->StructuredTool:其的官方文档介绍:from_function | langchain_core | LangChain Reference
用法介绍:
用法一:fromlangchain_core.toolsimportStructuredTooldefmultiply(a:int,b:int)->int:"""Multiply two numbers."""returna*b calculator_tool=StructuredTool.from_function(func=multiply)print(calculator_tool.invoke({"a":2,"b":3}))# 输出: 6用法二:使用 args_schema 参数,依赖 Pydantic 类定义并提供工具输入参数的 schema 属性 使用 description 参数,替代文档字符串中对于工具描述的 schema 属性fromlangchain_core.toolsimportStructuredToolfrompydanticimportBaseModel,FieldclassCalculatorInput(BaseModel):a:int=Field(description="first number")b:int=Field(description="second number")defmultiply(a:int,b:int)->int:returna*b calculator_tool=StructuredTool.from_function(func=multiply,name="Calculator",description="两数相乘",args_schema=CalculatorInput,)print(calculator_tool.invoke({"a":2,"b":3}))# 输出: 6print(calculator_tool.name)# 输出: Calculator用法三:classAddInput(BaseModel):a:int=Field(description="第一个整数")b:int=Field(description="第二个整数")#a:int和b:int是类型注解,被当作"类型提示"传递给工具schemadefadd(a:int,b:int)->Tuple[str,List[int]]:nums=[a,b]content=f"{nums}相加的结果是{a+b}"returncontent,nums add_tool=StructuredTool.from_function(func=add,name="ADD",# 工具名description="两数相加",# 工具描述args_schema=AddInput,# 工具参数response_format="content_and_artifact")# 模拟大模型调用姿势 --这样才能返回看到大模型分析的过程和结果print(add_tool.invoke({"name":"ADD","args":{"a":3,"b":4},"type":"tool_call",# 必填"id":"111",# 必填, 用来将工具调用请求和结果关联起来。}))# 手动调用# print(add_tool.invoke({"a": 2, "b": 5}))
绑定工具
使用聊天模型里面的
.bind_tools()方法–这里只介绍常见参数和用法,至于详细的参数介绍,可以去官方文档了解
假如已经定义好了两个工具:add和multiply# 收集工具tools=[add,multiply]# 强制绑定工具model_with_tools=model.bind_tools(tools=tools)# 会返回一个Runnable实例
工具的调用
普通调用:
result=model_with_tools.invoke("9乘6等于多少?")#返回的是AIMessageprint(result)但是模型hi根据输入的相关性据欸的那个何时使用工具,如果给定的是一个不相关输入的话,模型都不会去调用这个工具
强制模型调用工具:
model_with_tools=model.bind_tools(tools,tool_choice="any")# 要求模型至少调用其中的一个工具result=model_with_tools.invoke("hello world!")print(result)
如果调用了工具的话,
result将具有一个tool_calls属性,这个属性里面包括了执行这跟工具所需的一切(比如工具名称和输入参数)
result.content是给大模型看的文本结果
result.artifact是给后续组件用的原始数据
如果只像上面那样调用的话,只会看到模型要求调用工具的指令(也就是第一次
AIMessage),此时并没有得到最终结果后续应该本地代码接管这个流程然后调用工具,把结果构造成一个
ToolMessage发送给模型,然后模型总结(此时模型收到了前面的完整的对话:HumanMessage[用户询问]、AIMessage(模型请求调用工具)、ToolMessage(工具返回的结果))并输出最终答案(AIMessage的形式传回本地)完整的工具调用应该是:# 定义消息列表,添加要传递给聊天模型的消息message=[HumanMessage("2乘3等于多少?6加6等于多少?")]ai_msg=model_with_tools.invoke(message)message.append(ai_msg)# 把AIMessage追加到message后面# 构造ToolMessage, 并添加到消息列表中去fortool_callinai_msg.tool_calls:selected_tool={"add":add,"multiply":multiply}[tool_call["name"].lower()]# 创建字典+从字典中选择tool_msg=selected_tool.invoke(tool_call)message.append(tool_msg)# 追加ToolMessageprint(model_with_tools.invoke(message).content)#这才是最终结果