LangChain(四)工具调用的底层原理!给大模型按上双手吧!(新手向)

背景

经过前面三篇的内容,我想大家对于大模型的构建、Langchain的优势、Chain的构建有了相当程度的理解(虽然只是最简单的示例,但是足够有代表性)。

后续Chain的使用将会更加丰富多彩,您会了解Langchain开发的大模型会有多么逆天的可扩展性。但今天我们先暂缓此部分,我们来讲讲Langchain里面最最最重要的功能:工具调用!

我们会先从Langchain规定的官方API开始构建,带着大家跑通一系列工具之后,从底层原理出发,为大家讲解大模型是怎么调用工具的(不必担心,非常浅显易懂,本栏目始终是新手向的)。

工具说明

在Langchain眼中,所谓的工具都只是函数而已,我们要做的就是把函数写好,并交给大模型去自主的调用。

Langchain给大模型调用的函数专门设定了一个函数装饰器: @tool

有关于函数装饰器,作为新手其实没必要理解太深刻,只需要理解装饰器给函数添加了一些功能和变量即可。而这个tool装饰器仅仅是给函数增加了几个变量而已,如下:

  • 工具名称:(tool.name)
  • 该工具是什么的描述(tool.description)
  • 输入内容的 JSON 格式 (tool.args)
  • 工具的结果是否应直接返回给用户(tool.return_direct)
@tool
def func(input:int):'''没用的函数'''return input print(func.name)
# 输出:funcprint(func.description)
# 输出:没用的函数print(func.args)
# 输出: {'input': {'title': 'Input', 'type': 'int'}}print(func.return_direct)
# 输出:false

在这个装饰器中最主要的必须知晓的就是tool.nametool. description。这个是后续工具调用的基础。

  • tool.name

若无特殊设定,默认为函数名。作为新手向,该变量就不要去有额外的操作。只需要知道 tool.name == 函数名 即可。(操作更多也不会有额外的效果,还增加理解难度)

  • tool.description

若无特殊设定,默认为函数的文档字符串(即函数下方的函数说明),有关文档字符串的内容可以参考下面的博客,简单清晰。该部分作为小白直接利用该部分特性即可。有更高要求的看客可以查阅有关BaseTool类的相关知识。

Python 文档字符串(DocStrings)是个啥??-CSDN博客

大模型的工具调用(直接使用API)

在之前的项目中我们编写了有关基础大模型的相关内容,开发了第一个问答大模型以及尝试了LLMchain的相关内容,我们将在此基础上继续往前!

LangChain(二)基础问答大模型,纯新手向-CSDN博客

LangChain(三)基础问答大模型,从LLMchain开始了解chain!纯新手向-CSDN博客

其实不看也可以啦,看了理解起来会更快而已……

step1:工具定义!

该部分我们先定义需要的工具,代码如下。@tool装饰器说明这是一个工具。工具名称为“multiply”,工具描述为“Multiply two integers together.”。

from langchain_core.tools import tool@tool
def multiply(first_int: int, second_int: int) -> int:"""Multiply two integers together."""return first_int * second_int

 这部分没啥难度,其实就是你自己设定一个函数,然后前面加上@tool,函数内部首行用""" """ 定义一下函数功能描述即可。

step2:大模型定义

该部分我们定义大模型,详细内容可以参考我之前的博客内容哦。使用百度的千帆大模型


import os
from langchain_community.chat_models import QianfanChatEndpoint# 设定百度千帆大模型的AK和SK-去百度千帆官网的控制台新建一个应用即可
os.environ["QIANFAN_AK"] = "your AK“"
os.environ["QIANFAN_SK"] = "your SK"#创建千帆LLM模型
qianfan_chat = QianfanChatEndpoint(model="ERNIE-3.5-8K",temperature=0.2,timeout=30,
)

这部分依旧没啥难度,按部就班走即可。 

 step3:工具设定与绑定!

该部分我们进行工具的设定和与大模型进行绑定!

tools = [multiply]
llm_with_tools = qianfan_chat.bind_tools(tools)
tool_map = {tool.name: tool for tool in tools}

该部分必须要好好解释一下。不然大家初看之下可能会一头雾水。

tools = [multiply]

        由于在实际开发过程中,不可能只有一个工具,我们常常会调用多个工具,那么和大模型进行绑定难道要每个工具函数都绑定一次吗?咋可能对不对。这部分就是把所有需要调用的函数打造成一个列表,列表内保存的是各个函数(不是函数名!函数名是string,函数就是函数,本质上是个对象,这里理解不了跳过即可,我还记得这是个新手向的博客~~~)。

llm_with_tools = qianfan_chat.bind_tools(tools)

        这一行是把大模型和工具进行一个绑定,构建一个工具选择模块(一个 agent)大模型就是通过该模块进行的工具选择,具体的原理在下一篇博客会详细讲解,此部分我们先暂缓跳过~。

tool_map = {tool.name: tool for tool in tools}

        这一行是把函数名称(string)和函数(对象)作为一个字典保存。

        key:函数名称,value:函数

        这个变量大家先留意一下,现在可能看不出用途,后面就有用了。

step4:实际运行!

接下来我们把后面的代码一次性和盘托出! 

def call_tools(msg: AIMessage) -> Runnable:"""Simple sequential tool calling helper."""tool_calls = msg.tool_calls.copy()for tool_call in tool_calls:tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])return tool_callschain = llm_with_tools | call_toolschain.invoke("What's 25 times 11?"
)

I know,I know,突然信息量就上来了对不对。没事,我们一个一个来! 我们先跳过call_tools的函数定义,我们先看下面:

  • chain = llm_with_tools | call_tools

        对于chain还不理解的同学可以先看我之前的博客,链接在上面!看了我之前博客的同学想必依旧有疑惑,我们只是使用过LLMchain,怎么就变成这样了?

        实际上Langchain确实有很多已经定义好的chain,只需要调用即可,但是在实际开发中,最实用的依旧是自己定义的chain,个性化的定义才能满足个性化的需求嘛。

        Langchain官方自然有可以让我们自己个性化定义chain的方式。该处就是一个典型。

        该处的chain是如何工作的呢?作为小白我们不需要去理解源码。从高维去俯瞰它。步骤如下:

  • 用户输入给到 llm_with_tools(该部分有大模型)
  • llm_with_tools 获取用户输入和函数名称与描述,大模型进行处理并返回需要的函数名和对应的输入变量,记为“AIMessage”(这就是上面call_tools的参数哦~)。
  • call_tools获取上一个步骤输出的参数,并帮助大模型调用对应的函数,并返回结果。

llm_with_tools 的实际输出!

我们运行下面的代码:

query = "25 * 11 = ?"messages = [HumanMessage(query)]print("messages1 = ", messages)ai_msg = llm_with_tools.invoke(messages)print("ai_msg = ", ai_msg)

可得输出如下(手动标准格式了下):

messages1 =  [HumanMessage(content='25 * 11 = ?')]
ai_msg =  
content='' 
additional_kwargs={'finish_reason': 'function_call', 'request_id': 'as-y3xqr3j5b5', 'object': 'chat.completion', 'search_info': [], 'function_call': {'name': 'Multiply', 'arguments': '{"a":25,"b":11}'}, 'tool_calls': [{'type': 'function', 'function': {'name': 'Multiply', 'arguments': '{"a":25,"b":11}'}, 'id': '07eeb8f7-56b0-42f0-b828-4c7d4b17c850'}]} 
response_metadata={'token_usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75},  'model_name': 'ERNIE-3.5-8K', 'finish_reason': 'function_call', 'id': 'as-y3xqr3j5b5', 'object': 'chat.completion', 'created': 1720421110, 'result': '', 'is_truncated': False, 'need_clear_history': False, 'function_call': {'name': 'Multiply', 'thoughts': '用户需要进行乘法运算,我可以使用工具Multiply来完成这个任务。', 'arguments': '{"a":25,"b":11}'}, 'usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75}}     
id='run-082b9676-4902-4bf6-af1b-545f4a095001-0' 
tool_calls=[{'name': 'Multiply', 'args': {'a': 25, 'b': 11}, 'id': '07eeb8f7-56b0-42f0-b828-4c7d4b17c850'}]

大家先别慌!别着急!重点其实很少~。听我细细说来。

第一行的 messages1中的HumanMessage,仅仅只是告诉大模型这是用户发出的信息而已,至少在现在这个阶段不是重点,不用管他!

最主要的是下面的ai_msg,有三个重要模块

  • “additional_kwargs”:额外信息,对新手没啥用
  • “response_metadata”:正式的响应信息,一堆没啥用的信息之外,thoughts该字段反映了大模型是如何思考的。并且格式化返回了需要调用的相关函数名称(string)和函数的参数。
  • “tool_calls”:最重要的信息,单独提出来单纯只是降低层级而已,你可以看到上面几个字段都有一样的信息。

总而言之,在本篇工具调用栏目看来,最重要的就是tool_calls字段,其他直接忽略。函数选择器的详细原理将会放置下一篇博客详细讲解,本文仅说Langchain的API调用的步骤和思路。毕竟是新手向嘛~

综上 函数选择器 的功能输出正式讲解完毕,其实大家只需要知道函数选择器就是用来选择函数的,最重要的功能就是输出的tool_calls字段,其中保存大模型想要调用的函数名称(string)和对应的参数。

call_tools函数的原理和操作!

以防大家往上翻太烦,再粘贴一次。这个函数的主要作用就是获取AI想要调用的工具,并帮AI调用该工具。

def call_tools(msg: AIMessage) -> Runnable:"""Simple sequential tool calling helper."""tool_calls = msg.tool_calls.copy()for tool_call in tool_calls:tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])return tool_calls

该部分的输入参数就是上一步函数选择器的输出:AImessage(不知道大家有没有注意到,这和HumanMessage正好是对应关系,其实就是一个是用户的信息,一个是AI的信息而已,仅仅是对信息做一个标识,其实没啥用) 

后面的 --> Runnable 请忽略,新手直接跳过即可,想了解可以自行了解。 接下来让我们分行说明!

  • tool_calls = msg.tool_calls.copy()

        养成好习惯,直接copy,解耦互不影响,尤其在流式场景下。

  • for tool_call in tool_calls

        因为AI可能需要调用多个函数,所以对每一个AI想要调用的函数都需要处理。我不知道为什么我要解释这个……

  • tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])

        最重要的是这一行,不知道大家还记不记得 tool_map 这个变量,在上文提过,截图如下。这一行我们慢慢来,对于当前需要调用的tool_call,有一个字段“name”保存着需要调用函数的函数名称。用tool_map访问该名称,返回该函数名称(string)对应的函数(对象)!这下大家终于理解为什么我需要强调这多次了吧~。即:

  • tool_map[tool_call["name"]] == multiply
  • tool_call["args"] == {'a': 25, 'b': 11}

这一行 == multiply.invoke({'a': 25, 'b': 11}) == multiply('a': 25, 'b': 11) == 275,此时tool_call多了一个字段“output”,value = 275

到此就结束了,我们终于实现了大模型调用工具的基础操作。大家安心,上面代码好像很多,好像很复杂,我们最后复习一下,看一下全部的代码,你会发现没什么难的其实。

import os
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_core.tools import tool
from langchain_core.messages import AIMessage
from langchain_core.runnables import Runnable# 设定百度千帆大模型的AK和SK
os.environ["QIANFAN_AK"] = " your AK"
os.environ["QIANFAN_SK"] = " your SK"# 定义千帆大模型
qianfan_chat = QianfanChatEndpoint(model="ERNIE-3.5-8K",temperature=0.2,timeout=30,
)# 设定两个函数,记得描述函数功能,这很重要
@tool
def func1():''' useless function '''return 0@tool
def Multiply(a: int, b: int) -> int:"""Multiplies a and b."""return a * b# 工具集合
tools = [Multiply, func1]
# 工具与大模型绑定,构建函数选择模块
llm_with_tools = qianfan_chat.bind_tools(tools)
# 构建一一对应的map
tool_map = {tool.name: tool for tool in tools}# 工具函数执行
def call_tools(msg: AIMessage) -> Runnable:"""Simple sequential tool calling helper."""tool_calls = msg.tool_calls.copy()for tool_call in tool_calls:tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])return tool_calls# 构建链
chain = llm_with_tools | call_toolsprint(chain.invoke("What's 25 times 11?")[0]["output"])

 输出:275

总结

有一说一,工具调用会了,世界上还有什么功能实现不了?

但是本篇博客是从API的角度出发为大家构建一个工具调用的操作,下一篇博客我们将从原理出发,直接手撸工具调用!放宽心,依旧是新手向~

由于小博主依旧是个卑微的打工人,只能上班摸鱼的时候写写博客,后续的博客将保持一周一篇的频率~ 敬请期待~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/41847.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

14-31 剑和诗人5 - 使用 AirLLM 和分层推理在单个 4GB GPU 上运行 LLama 3 70B

利用分层推理实现大模型语言(LLM) 大型语言模型 (LLM) 领域最近取得了显著进展,LLaMa 3 70B 等模型突破了之前认为可能实现的极限。然而,这些模型的庞大规模给其部署和实际使用带来了巨大挑战,尤其是在资源受限的设备上,例如内存…

怎么压缩pdf文件的大小?减小PDF文件大小的四种方法

怎么压缩pdf文件的大小?文件大小不仅影响传输速度,还可能涉及存储空间的管理。当处理大型PDF文件时,可能会面临电子邮件附件限制或云存储容量不足的问题。此外,过大的文件在浏览和加载时也会导致延迟,影响阅读体验。这…

3款自己电脑就可以运行AI LLM的项目

AnythingLLM、LocalGPT和PrivateGPT都是与大语言模型(LLM)相关的项目,它们允许用户在本地环境中与文档进行交互,但它们在实现方式和特点上存在一些差异。AnythingLLM使用Pinecone和ChromaDB来处理矢量嵌入,并使用OpenA…

【C语言】return 关键字详解

在C语言中,return是一个关键字,用于从函数中返回值或者结束函数的执行。它是函数的重要组成部分,负责将函数的计算结果返回给调用者,并可以提前终止函数的执行。 主要用途和原理: 返回值给调用者: 当函数执…

【论文阅读】-- Visual Traffic Jam Analysis Based on Trajectory Data

基于轨迹数据的可视化交通拥堵分析 摘要1 引言2 相关工作2.1 交通事件检测2.2 交通可视化2.3 传播图可视化 3 概述3.1 设计要求3.2 输入数据说明3.3 交通拥堵数据模型3.4 工作流程 4 预处理4.1 路网处理4.2 GPS数据清理4.3 地图匹配4.4 道路速度计算4.5 交通拥堵检测4.6 传播图…

掌握【Python异常处理】:打造健壮代码的现代编程指南

目录 ​编辑 1. 什么是异常? 知识点 示例 小李的理解 2. 常见的内置异常类型 知识点 示例 小李的理解 3. 异常机制的意义 知识点 示例 小李的理解 4. 如何处理异常 知识点 示例 小李的理解 5. 抛出异常 知识点 示例 小李的理解 6. Python内置…

Springboot整合Jsch-Sftp

背景 开发一个基于jsch的sftp工具类&#xff0c;方便在以后的项目中使用。写代码的过程记录下来&#xff0c;作为备忘录。。。 Maven依赖 springboot依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-par…

codeforces 1633A

文章目录 1. 题目链接2. 题目代码正确代码错误代码 3. 题目总结 1. 题目链接 Div. 7 2. 题目代码 正确代码 #include<iostream> using namespace std; int main(){int testCase;cin >> testCase;while(testCase --){int ingeter;cin >> ingeter;if(!(inget…

SpringBoot彩蛋之定制启动画面

写在前面 在日常开发中&#xff0c;我们经常会看到各种各样的启动画面。例如以下几种 ① spring项目启动画面 ② mybatisplus启动画面 ③若依项目启动画面 还有很多各式各样好看的启动画面&#xff0c;那么怎么定制这些启动画面呢&#xff1f; 一、小试牛刀 ① 新建一个Spr…

SQL 之 concat_ws和concat的区别

concat_ws和concat都是用于连接字符串的函数&#xff0c;但它们在使用上有一些区别&#xff1a; 一、concat、concat_ws函数格式&#xff1a; concat格式&#xff1a; concat&#xff08;参数1,参数2,…参数n&#xff09;&#xff0c;如果要加’分隔符’直接写在 各参数中间就…

关于微信支付-商户平台:查询订单提示“查询失败:操作失败,请稍候重试”的分析

目录 引子 分析 应对 小结 引子 在开发和实施微信 JSAPI 支付的应用后&#xff0c;我们遇到了一些问题&#xff0c;订单的状态更新不正常&#xff0c;当然我们首先需要从自身寻找原因和完善解决问题的办法和方案。在支付的过程中&#xff0c;客户会给我们一些反馈&#xf…

Open-Sora1.2环境搭建推理测试

引子 前阵子写了一篇Open-Sora1.0环境搭建&推理测试&#xff08;Open-Sora1.0环境搭建&推理测试_自己搭建sora服务-CSDN博客&#xff0c;感兴趣的童鞋&#xff0c;请移步&#xff09;。Open-Sora1.1发布的时候&#xff0c;撇了一眼新闻。后面一转头&#xff0c;忘记这…

ARL联动AWVS实现自动化漏洞扫描

0x01 前言 很多场景下需要大范围的扫描漏洞和快速排查互联网暴露面的漏洞&#xff0c;需要使用这种自动化的手段&#xff0c;常规渗透测试的找互联网暴露面是&#xff0c;域名>子域名>IP>C段>端口&#xff0c;可以手动收集&#xff0c;也可以借助一些网络搜索引擎…

卡尔曼滤波Q和R怎么调

卡尔曼滤波器是一种有效的估计算法&#xff0c;主要用于在存在噪声的环境中估计动态系统的状态。它通过结合预测模型&#xff08;系统动态&#xff09;和观测数据&#xff08;包括噪声&#xff09;来实现这一点。在卡尔曼滤波中&#xff0c;调整过程噪声协方差矩阵 ( Q ) 和测量…

Kubernetes运维工程师必备:K8s 基础面试题精编(一)

Kubernetes运维工程师必备:K8s 基础面试题精编(一) 1. 什么是Kubernetes?2. Kubernetes如何实现容器编排?3. 说出k8s的常见资源对象?4. 什么是pod?5. Deployment介绍及使用?6. statefulesets介绍及使用?7. statefulesets和deployment区别?8. 什么是调度器(Scheduler…

The First项目报告:NvirWorld与区块链游戏的未来

根据官方公告&#xff0c;The Fisrt现货区将于2024年7月2日16:00上架NVIR/USDT交易对&#xff0c;NVIR是NvirWorld平台的原生代币。作为一个去中心化解决方案&#xff0c;NvirWorld为开发者提供了一个简化且适应性强的环境&#xff0c;旨在通过优化的扩展解决方案来降低交易成本…

docker 本地部署大模型(ollama)

docker 安装 ollama docker search ollama docker pull ollama/ollama###docker下载ollama部署 docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama### 下载模型 docker exec -it ollama ollama pull llama3### 交互式运行模型docker exec -i…

算法 —— 二分查找

目录 二分查找 在排序数组中查找元素的第一个和最后一个位置 搜索插入位置 x的平方根 山峰数组的峰顶索引 寻找峰值 搜索旋转排序数组中的最⼩值 点名 二分查找模板分为三种&#xff1a;1、朴素的二分模板 2、查找左边界的二分模板 3、查找右边界的二分模板&#xf…

【基于R语言群体遗传学】-12-超显性与次显性

欢迎先看前面的博客&#xff0c;再继续进行后面的内容&#xff1a; 群体遗传学_tRNA做科研的博客-CSDN博客 当杂合子的适应度超出纯合子的范围时&#xff0c;二倍体能够展现出更多令人着迷的选择实例。这种形式的一种是杂合子优势&#xff0c;或称为“超显性”&#xff0c;其…

【包邮送书】AIGC时代程序员的跃迁——编程高手的密码武器

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…