LangGraph 自定义工具调用,大模型使用加法和乘法工具的工作流实现

摘要

文章提出了采用few-shot学习的方法,通过给大模型提供几个示例来激活其工具调用能力,而非进行复杂的微调。

文章通过构建工作流结构,包括llm节点(生成工具调用和结果输出)和action节点(运行工具调用并输出结果),展示了自动化实现工具调用和结果处理的流程。工作流的优势在于能够简化流程,自动处理大模型输出、工具调用及结果反馈的循环。

引言

背景:在用LangGraph实现工作流时,需要让 Agent 能够调用工具解决问题。

下述是大家常用的大模型调用工具的例子,只需要使用bind_tools绑定工具,让大模型自动调用工具。

llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)

大模型通过 bind_tools 可以看到每个绑定工具的功能介绍,args参数描述,所以大模型就能生成相应的 tool_calls

我们在使用搜索引擎工具时,发现大模型能够调用的很好。但我们发现大模型通常不能很有效地调用我们自定义的一些工具。为了应对这个挑战,当大模型不能按照用户意图执行操作的时候,要么微调、要么few-shot。本文不微调,而是选择few-shot,因为更简单一点。通过给大模型几个示例,激活大模型的工具调用能力。
如果要进一步提高 few-shot 的效果可以参考这篇文章:利用langchain 做大模型 Few-shot Learning 提示,包括固定和向量相似的动态样本筛选,本人亲测使用了样本筛选的few-shot,可以在文本分类任务上提高20%的准确率。提示样本筛选类似RAG技术,根据用户问题,在样本库中筛选出类似的例子提示,去激活大模型的能力。

本文的工具调用主要参考的是langchain的这篇文章,How to use few-shot prompting with tool calling, https://python.langchain.com/v0.2/docs/how_to/tools_few_shot/ 这篇文章的不足之处在于,只给出了根据文本生成工具调用的AIMessage,没有进一步完成工具调用的计算过程。本文增加了这部分的内容。

简单的工具调用:一次调用直接拿到结果,比如搜素引擎;
复杂的工具调用:下一个工具调用的参数,依赖于上一个工具的调用结果;一旦工具调用之间有嵌套和依赖关系,大模型的表现就会不好;

本文做的这个工作是一个复杂工具的调用,因为下一轮计算,依赖于上一轮计算的结果。

from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
    """Adds a and b.

    Args:
        a: first int
        b: second int
    """
    return a + b


@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b.

    Args:
        a: first int
        b: second int
    """
    return a * b


tools = [add, multiply]
# 使用模型绑定工具
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)

给了几个例子,用于激活大模型的工具调用能力:

examples = [
    HumanMessage("317253 x 128472 + 4", name="example_user"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[
            {"name": "multiply", "args": {"x": 317253, "y": 128472}, "id": "1"}
        ],
    ),
    ToolMessage("40758127416", tool_call_id="1"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[{"name": "add", "args": {"x": "40758127416", "y": 4}, "id": "2"}],
    ),
    ToolMessage("40758127420", tool_call_id="2"),
    AIMessage(
        "317253 x 128472 + 4 = 40758127420",
        name="example_assistant",
    ),
    # 下一个例子
    
    HumanMessage("2 x 3 + 7", name="example_user"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[{"name": "multiply", "args": {"x": 2, "y": 3}, "id": "1"}],
    ),
    ToolMessage("6", tool_call_id="1"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[{"name": "add", "args": {"x": "6", "y": 7}, "id": "2"}],
    ),
    ToolMessage("13", tool_call_id="2"),
    AIMessage(
        "2 x 3 + 7 = 13",
        name="example_assistant",
    ),
    # 下一个例子
    HumanMessage("(6 + 7) x 9", name="example_user"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[{"name": "add", "args": {"x": 6, "y": 7}, "id": "1"}],
    ),
    ToolMessage("13", tool_call_id="1"),
    AIMessage(
        "",
        name="example_assistant",
        tool_calls=[{"name": "multiply", "args": {"x": 13, "y": 9}, "id": "2"}],
    ),
    ToolMessage("117", tool_call_id="2"),
    AIMessage(
        "(6 + 7) x 9 = 117",
        name="example_assistant",
    ),
]

# 在系统提示词中,暗示大模型它的数据能力不行,要求它使用工具完成计算。
# 不然,它就直接算出结果,本实验就没有意义了。
system = """You are bad at math but are an expert at using a calculator. 
Use past tool usage as an example of how to correctly use the tools.
You can only call a tool at a time.
"""

LangGraph

import os
from typing import TypedDict, Annotated
import operator
from IPython.display import Image

from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langgraph.graph import StateGraph, END


class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]

CalcuAgent 是参考吴恩达的LangGraph系列教程里面改写的。

CalcuAgent 基本是完全参考吴恩达编写的那个类,额外增加了

  1. checkpointer: memory = MemorySaver(),保存state的中间过程,不然只会保留最后一个state。
  2. examples: few-shot 提示的例子;
class CalcuAgent:
    def __init__(self, model, tools, system="", checkpointer=None, examples=[]):
        self.system = system
        self.examples = examples
        graph = StateGraph(AgentState)
        graph.add_node("llm", self.call_openai)
        graph.set_entry_point("llm")
        graph.add_node("action", self.take_action)
        graph.add_conditional_edges(
            "llm", self.exists_action, {True: "action", False: END}
        )
        graph.add_edge("action", "llm")
        self.graph = graph.compile(checkpointer=checkpointer)

        self.tools = {t.name: t for t in tools}
        self.model = model.bind_tools(tools)

    def exists_action(self, state: AgentState):
        result = state["messages"][-1]
        return len(result.tool_calls) > 0

    def call_openai(self, state: AgentState):
        messages = [SystemMessage(self.system)] + self.examples + state["messages"]
        message = self.model.invoke(messages)
        return {"messages": [message]}

    def take_action(self, state: AgentState):
        tool_calls = state["messages"][-1].tool_calls
        results = []
        print(f"take_action called with tool_calls: {tool_calls}")
        for t in tool_calls:
            print(f"Calling: {t}")
            if not t["name"] in self.tools:  # check for bad tool name from LLM
                print("\n ....bad tool name....")
                result = "bad tool name, retry"  # instruct LLM to retry if bad
            else:
                result = self.tools[t["name"]].invoke(t["args"])
                # print(f"action {t['name']}, result: {result}")
            results.append(
                ToolMessage(tool_call_id=t["id"], name=t["name"], content=str(result))
            )
        print("Back to the model!")
        return {"messages": results}

    def draw_graph(self):
        return Image(self.graph.get_graph().draw_png())
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()

agent = CalcuAgent(
    llm, tools=tools, system=system, examples=examples, checkpointer=memory
)
workflow.draw_graph()

工作流的结构如下:
在这里插入图片描述

上图是本文工作流结构,结合上图,分析一下这个流程。

  • llm 节点:生成工具调用和结果输出;
  • action 节点: 运行工具调用,输出工具调用结果的 ToolMessage
user_input = "5 x 8 + 100 + (4 + 2) x 10"
config = {"configurable": {"thread_id": "1"}}
# The config is the **second positional argument** to stream() or invoke()!
events = workflow.graph.stream(
    {"messages": [HumanMessage(user_input)]}, config, stream_mode="values"
)
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()

下述是工作流的逐步输出结果:
Output:

================================ Human Message =================================

5 x 8 + 100 + (4 + 2) x 10
================================== Ai Message ==================================
Tool Calls:
  multiply (call_7EmU4pqnS3FnlWwOP1ez90uF)
 Call ID: call_7EmU4pqnS3FnlWwOP1ez90uF
  Args:
    a: 5
    b: 8
  add (call_tIqKNCDWBc7KomPujVNd3Hx1)
 Call ID: call_tIqKNCDWBc7KomPujVNd3Hx1
  Args:
    a: 4
    b: 2
  multiply (call_O9hwCevnRu6lmKWoxQJ4ZdmN)
 Call ID: call_O9hwCevnRu6lmKWoxQJ4ZdmN
  Args:
    a: 6
    b: 10
take_action called with tool_calls: [{'name': 'multiply', 'args': {'a': 5, 'b': 8}, 'id': 'call_7EmU4pqnS3FnlWwOP1ez90uF', 'type': 'tool_call'}, {'name': 'add', 'args': {'a': 4, 'b': 2}, 'id': 'call_tIqKNCDWBc7KomPujVNd3Hx1', 'type': 'tool_call'}, {'name': 'multiply', 'args': {'a': 6, 'b': 10}, 'id': 'call_O9hwCevnRu6lmKWoxQJ4ZdmN', 'type': 'tool_call'}]
Calling: {'name': 'multiply', 'args': {'a': 5, 'b': 8}, 'id': 'call_7EmU4pqnS3FnlWwOP1ez90uF', 'type': 'tool_call'}
Calling: {'name': 'add', 'args': {'a': 4, 'b': 2}, 'id': 'call_tIqKNCDWBc7KomPujVNd3Hx1', 'type': 'tool_call'}
Calling: {'name': 'multiply', 'args': {'a': 6, 'b': 10}, 'id': 'call_O9hwCevnRu6lmKWoxQJ4ZdmN', 'type': 'tool_call'}
Back to the model!
================================= Tool Message =================================
Name: multiply

60
================================== Ai Message ==================================
Tool Calls:
  add (call_EIxrJe9Scq2YxE61hrT0OxRV)
 Call ID: call_EIxrJe9Scq2YxE61hrT0OxRV
  Args:
    a: 40
    b: 100
take_action called with tool_calls: [{'name': 'add', 'args': {'a': 40, 'b': 100}, 'id': 'call_EIxrJe9Scq2YxE61hrT0OxRV', 'type': 'tool_call'}]
Calling: {'name': 'add', 'args': {'a': 40, 'b': 100}, 'id': 'call_EIxrJe9Scq2YxE61hrT0OxRV', 'type': 'tool_call'}
Back to the model!
================================= Tool Message =================================
Name: add

140
================================== Ai Message ==================================
Tool Calls:
  add (call_UAHSSIoa7iK5WUwEx1MslysO)
 Call ID: call_UAHSSIoa7iK5WUwEx1MslysO
  Args:
    a: 140
    b: 60
take_action called with tool_calls: [{'name': 'add', 'args': {'a': 140, 'b': 60}, 'id': 'call_UAHSSIoa7iK5WUwEx1MslysO', 'type': 'tool_call'}]
Calling: {'name': 'add', 'args': {'a': 140, 'b': 60}, 'id': 'call_UAHSSIoa7iK5WUwEx1MslysO', 'type': 'tool_call'}
Back to the model!
================================= Tool Message =================================
Name: add

200
================================== Ai Message ==================================

5 x 8 + 100 + (4 + 2) x 10 = 200

工作流输出了正确的答案。

Q:这个工作为什么要用工作流实现?
A:因为工作流实现更简单。不然设想一下如果不使用工作流的话。首先大模型可以根据用户输入得到工具的调用结果,但是大模型不能运行工具。这一块得想办法根据大模型输出的运行工具,然后拿到工具运行的结果。再把工具输出的结果再输入给大模型,再等待大模型是选择继续调用工具还是总结输出。而使用工作流这一块就可以自动化地完成这个流程。

参考资料

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieshenai

为了遇见更好的文章

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值