LangChain(五)工具调用的底层原理进阶!依旧纯新手向~

系列文章目录

LangChain(一)构建本地数据检索问答Agent,新手向-CSDN博客

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

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

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


文章目录

前言

一、工具调用回顾

二、自己构建工具调用!

1.构建工具函数

2.定义大模型

3.设定工具选择模块

4.  构建chain以及最后格式化输出

5. 函数调用!最终步骤!

老规矩,全部代码展示!

总结


前言

随着第三篇的内容,我们跟进到了大模型调用工具的开发与原理,并浅尝辄止了一下大模型调用工具的原理!

本篇我们上接上文,继续此部分的内容,我们主要讲解有关大模型调用工具的原理!我们会尽可能的把LangChain高度抽象的代码还原,把每一个步骤都尽可能的说清楚!大家感兴趣的话可以看一下第四篇的内容~

其实不看也没事啦,看了理解起来会更快而已。本篇最大的用途是,对于一些不支持LangChain工具调用的大模型,我们依旧可以使用本方案工具调用!

一、工具调用回顾

        在上一篇的内容中我们开发了一个基础的工具调用代码,I know大家对于代码更感兴趣,所以我把代码先粘过来~

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_tools
 
print(chain.invoke("What's 25 times 11?")[0]["output"])

总而言之,上诉的代码主要经过了以下几个步骤:

初始化内容

  • 定义工具函数与大模型初始化
  • 工具函数与大模型绑定构建函数选择模块:llm_with_tools
  • 构建工具调用模块:call_tools

 chain实际运行的数据流

  • 用户输入给到函数选择模块
  • 函数选择模块输出需要的函数和对应的参数
  • 工具调用模块进行工具调用并返回

二、自己构建工具调用!

不必担心,依旧很简单~

1.构建工具函数

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
def add(first_int: int, second_int: int) -> int:
    """add two integers together."""
    return first_int + second_int

我们使用下面的代码查看一下具体的内容,这些内容就是真正需要给到大模型参考的函数描述 

print(multiply.name)
print(multiply.description)
print(multiply.args)

 函数输出如下:

multiply
multiply(first_int: int, second_int: int) -> int - Multiply two integers together.
{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}

 可以查看一下第三行这是之前没有讲到的部分,此部分定义了函数输入参数的规格,用以提醒大模型。

2.定义大模型

直接用LangChain集成好的,依旧是千帆大模型~

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,
)

3.设定工具选择模块

这部分是本篇精华,本篇和上一篇最大的区别就在于函数选择器,LangChain帮助我们把此部分高度抽象成了 llm_with_tools 。我们本篇将其拓写!

①构建函数描述模块!

from langchain.tools.render import render_text_description

# 构建工具条件和调用描述
rendered_tools = render_text_description([multiply, add])
print("rendered_tools = ", rendered_tools)

 此部分需要好好讲解一下这是工具选择功能实现的核心

此部分render_text_description函数的作用是构建函数描述符,返回函数的头以及参数规格和函数的功能描述。其对应的输出如下:

rendered_tools =  

multiply(first_int: int, second_int: int) -> int - Multiply two integers together.
add(first_int: int, second_int: int) -> int - add two integers together.

有关该函数,详情可见如下: 

LangChain中的render_text_description究竟做了什么操作?-CSDN博客 

②构建工具选择输入prompt模板!

from langchain_core.prompts import ChatPromptTemplate

# 构建工具选择器的系统人设
system_prompt = f"""You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:

{rendered_tools}

# 构建输入的模板
Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys."""

prompt = ChatPromptTemplate.from_messages(
    [("system", system_prompt), ("user", "{input}")]
)

        通过上诉文本的阅读,我想看客们此时应该已经反应过来了,大模型工具选择的原理就是根据之前传入的函数描述符和用户的输入,返回需要的函数名称和参数。实在是非常粗暴。很多看客想必此时心中一万匹草泥马奔腾而过……。 

4.  构建chain以及最后格式化输出

# 构建chain以及最后格式化输出
chain = prompt | qianfan_chat
print(chain.invoke({"input": "what's thirteen times 4"}))

构建链的过程应该无需我过多解释,若不明白的看客可以查看一下上一篇的内容~。

此时的输出如下:

content='```json\n{\n    
"name": "multiply",\n    
"arguments": {\n        
"first_int": 13,\n        
"second_int": 4\n    
}\n}\n
```' 
additional_kwargs={
'finish_reason': 'normal', 
'request_id': 'as-mzhzznjcan', 
'object': 'chat.completion', 
'search_info': []} 
response_metadata={
'token_usage': {'prompt_tokens': 114, 'completion_tokens': 45, 'total_tokens': 159}, 'model_name': 'ERNIE-3.5-8K', 
'finish_reason': 'normal', 
'id': 'as-mzhzznjcan', 
'object': 'chat.completion', 
'created': 1720599923, 
'result': '```json\n{\n    
    "name": "multiply",\n    
    "arguments": {\n        
        "first_int": 13,\n        
        "second_int": 4\n    }\n}\n```', 
'is_truncated': False, 
'need_clear_history': False, 
'usage': {'prompt_tokens': 114, 'completion_tokens': 45, 'total_tokens': 159}} 

id='run-b141fa71-065b-41c0-baa1-6f6797e3ae0f-0'

太复杂了对不对?我们可以对结果进行处理下,LangChain很贴心的提供了相关的内容: JsonOutputParser() !我们在链的末尾增加该函数!

# 构建chain以及最后格式化输出
chain = prompt | qianfan_chat | JsonOutputParser()
print(chain.invoke({"input": "what's thirteen times 4"}))

 此时输出如下:

{'name': 'multiply', 'arguments': [13, 4]}

完美!

5. 函数调用!最终步骤!

tools = [add, multiply]
tool_map = {tool.name: tool for tool in tools}

def tools_call(model_output):
    chosen_tool = tool_map[model_output["name"]]
    return chosen_tool.invoke(model_output["arguments"])


chain = prompt | qianfan_chat | JsonOutputParser() | tools_call
print(chain.invoke({"input": "what's thirteen times 4"}))

 如果理解了上一篇的内容,这部分毫无难度~。详情可以移步上一篇哦~


老规矩,全部代码展示!

from langchain_core.tools import tool
import os
from langchain_community.chat_models import QianfanChatEndpoint
#创建工具调用
from langchain.tools.render import render_text_description
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from operator import itemgetter

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

@tool
def add(first_int: int, second_int: int) -> int:
    """add two integers together."""
    return first_int + second_int
    
tools = [add, multiply]
tool_map = {tool.name: tool for tool in tools}
 
# 设定百度千帆大模型的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,
)

# 构建工具条件和调用描述
rendered_tools = render_text_description([multiply, add])
print("rendered_tools = ", rendered_tools)

# 构建工具选择器的系统人设
system_prompt = f"""You are an assistant that has access to the following set of tools. Here are the names and descriptions for each tool:

{rendered_tools}

Given the user input, return the name and input of the tool to use. Return your response as a JSON blob with 'name' and 'arguments' keys."""

# 构建输入的模板
prompt = ChatPromptTemplate.from_messages(
    [("system", system_prompt), ("user", "{input}")]
)

# 构建chain以及最后格式化输出
chain = prompt | qianfan_chat 
print(chain.invoke({"input": "what's thirteen times 4"}))

def tools_call(model_output):
    chosen_tool = tool_map[model_output["name"]]
    return chosen_tool.invoke(model_output["arguments"])


chain = prompt | qianfan_chat | JsonOutputParser() | tools_call
print(chain.invoke({"input": "what's thirteen times 4"}))

总结

本篇我们更深一层的剥开抽象的LangChain.对于LangChain的工具调用原理有了更深入的了解。

下一篇我们将继续我们的Chain之路~构建和尝试更多的Chain!

有何问题和想要了解的内容敬请留言哦

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数调用是编程中的一个基本操作,它允许程序在不同的代码块之间进行切换,并传递参数和执行不同的任务。函数调用的底层实现原理涉及到许多复杂的计算机科学概念,包括内存管理、栈、寄存器、调用约定等。 在底层实现中,函数调用通常涉及以下几个步骤: 1. **参数传递**:当一个函数被调用时,它的参数会被压入调用函数的栈帧中。这些参数包括输入和输出参数,以及局部变量。 2. **代码执行**:当函数开始执行时,控制权会转移到该函数的代码上。这个过程通常涉及到将程序的执行上下文(包括寄存器的内容、内存中的数据等)保存到栈帧中,以便函数执行完毕后可以恢复这些信息。 3. **返回地址保存**:当函数执行完毕并准备返回时,它会将程序计数器的当前值(即下一条要执行的指令的地址)保存到一个特殊的寄存器(通常是EIP)中,以便函数可以返回调用它的代码。 4. **返回**:函数执行完毕后,会从栈帧中取出返回地址(通常是EIP),然后跳转到这个地址继续执行程序。此时,函数调用就完成了。 这个过程在许多不同的编程语言中都是相似的,但是实现方式可能会有所不同。具体实现会取决于所使用的编程语言和操作系统,以及硬件架构(如x86、ARM等)。此外,不同的编译器和运行时环境可能会有不同的调用约定,这也会影响函数调用的底层实现。 值得注意的是,函数调用的底层实现通常涉及到许多底层的细节和复杂性,对于大多数编程任务来说并不需要了解这些细节。如果你对这方面的知识感兴趣,可以进一步学习计算机体系结构和操作系统课程,以了解更多关于函数调用和程序执行的基础知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值