精通LangGraph-多agent-01

文摘   2025-01-21 16:00   四川  
代理是一种使用 LLM 来决定应用程序控制流的系统。随着您开发这些系统,它们可能会随着时间的推移变得更加复杂,从而更难管理和扩展。例如,我们可能会遇到以下问题:
代理可以使用的工具太多,无法正确决定下一步调用哪个工具环境变得过于复杂,单个代理无法跟踪系统中需要多个专业领域(例如规划师、研究员、数学专家等)
为了解决这些问题,我们可以考虑将应用程序分解为多个较小的独立代理,并将它们组合成一个多代理系统。这些独立代理可以像提示和 LLM 调用一样简单,也可以像ReAct代理一样复杂(甚至更多!)。
使用多代理系统的主要好处是:
  • 模块化:独立的代理使开发、测试和维护代理系统变得更加容易。
  • 专业化:可以创建专注于特定领域的专家代理,这有助于提高整体系统的性能。
  • 控制:可以明确控制代理如何通信(而不是依赖于函数调用)。
可以做的架构如下图所示

在多代理系统中,有多种连接代理的方法:

  • network:每个代理都可以与其它代理通信,任何代理都可以决定接下来要呼叫哪个代理。
  • supervisor:每个代理与单个supervisor agent通信。主管代理决定下一步应呼叫哪个代理。
  • supervisor(工具调用):这是supervisor架构的一个特例。单个代理可以表示为工具。在这种情况下,主管代理使用工具调用 LLM 来决定调用哪些代理工具,以及要传递给这些代理的参数。
  • 层次结构:我们可以定义一个具有多个supervisor的多代理系统。这是supervisor架构的泛化,允许更复杂的控制流。
  • 自定义多代理工作流:每个代理仅与一部分代理进行通信。流程的某些部分是确定性的,只有部分代理可以决定接下来要调用哪些其他代理。
接下来,我们先来看一下所有架构的基础:如何在多代理之间流转:
我们的示例是这样的:我们定义两个node,模拟agent,一个加法专家,一个乘法专家,当接受用户输入的时候,会先走加法专家,然后根据LLM的决定,是否再去走乘法专家完成最终用户的问题。so easy!
from langgraph.constants import ENDfrom typing_extensions import Literalfrom langchain_core.tools import toolfrom langgraph.graph import MessagesState, StateGraph, STARTfrom langgraph.types import Commandfrom langchain_core.messages import SystemMessage,ToolMessagefrom langchain_ollama import ChatOllamaimport base_conf
model = ChatOllama(base_url=base_conf.base_url,model=base_conf.model_name,temperature=0)
# 下面的两个tool 不返回任何内容:# 我们只是用它作为一种提示:让LLM知道它需要交给另一个代理@tooldef transfer_to_multiplication_expert():    """请求乘法专家的帮助。"""    return@tooldef transfer_to_addition_expert():    """请求加法专家的帮助。"""    return
# 下面的两个节点分别代表加法专家和乘法专家def addition_expert(state: MessagesState) -> Command[Literal["multiplication_expert", END]]:    system_prompt = (        "你是一个加法专家,你可以请求乘法专家帮助进行乘法。"        "在交接之前总是先完成你那部分的计算。"    )    messages = [SystemMessage(content=system_prompt)] + state["messages"]    ai_msg = model.bind_tools([transfer_to_multiplication_expert,transfer_to_addition_expert]).invoke(messages)    # 首先先走到了加法这里,这里的ai_msg返回了一个tool_call        # 如果有工具调用,LLM需要交给另一个代理    if len(ai_msg.tool_calls) > 0:        # 这里为什么官方教程总是用-1呢?因为这里的tool_call_id是一个list,所以-1是最后一个        # 那有没有可能list中有多个呢?有可能,如果修改prompt,并且传入多个tool那么ai_msg是会返回多个tool_call的        # 因此实际开发中,要注意这个问题        tool_call_id = ai_msg.tool_calls[-1]["id"]        # 注意:在这里插入ToolMessage很重要,因为LLM提供者期望所有AI message 后面都有相应的工具结果消息        tool_msg = ToolMessage(content="成功转移", tool_call_id=tool_call_id)        return Command(            # 经过上面的处理,到达multiplication_expert的时候,入参已经变成了加法之后乘法需要的入参,            # 也就是原来加法部分已经被计算了:8 * 12            goto="multiplication_expert", update={"messages": [ai_msg, tool_msg]}        )    # 如果专家有答案,直接返回给用户    return Command(goto=END, update={"messages": [ai_msg]})
def multiplication_expert(    state: MessagesState,) -> Command[Literal["addition_expert", END]]:    system_prompt = (        "你是一个乘法专家,你可以请求加法专家帮助进行加法。"        "在交接之前总是先完成你那部分的计算。"    )    messages = [SystemMessage(content=system_prompt)] + state["messages"]        # 当这里拿着 8*12 的 AI message的时候,ai_msg最终的tool_call里面    # 是没有transfer_to_multiplication_expert的    # 它已经给出了结果,所以这里的tool_call是空的    ai_msg = model.bind_tools([transfer_to_addition_expert]).invoke(messages)    if len(ai_msg.tool_calls) > 0:        tool_call_id = ai_msg.tool_calls[-1]["id"]        tool_msg = ToolMessage(content="成功转移", tool_call_id=tool_call_id)        return Command(goto="addition_expert", update={"messages": [ai_msg, tool_msg]})    # 这里就直接返回了结果    return Command(goto=END, update={"messages": [ai_msg]})
builder = StateGraph(MessagesState)builder.add_node("addition_expert", addition_expert)builder.add_node("multiplication_expert", multiplication_expert)
# 我们总是从加法专家开始builder.add_edge(START, "addition_expert")graph = builder.compile()from langchain_core.messages import convert_to_messages
def pretty_print_messages(update):    if isinstance(update, tuple):        ns, update = update        # 在打印输出中跳过父图更新        if len(ns) == 0:            return        graph_id = ns[-1].split(":")[0]        print(f"来自子图 {graph_id} 的更新:")        print("\n")    for node_name, node_update in update.items():        print(f"来自节点 {node_name} 的更新:")        print("\n")        for m in convert_to_messages(node_update["messages"]):            m.pretty_print()        print("\n")
# 让我们运行图for chunk in graph.stream(    {"messages": [("user""what's (3 + 5) * 12")]},):    pretty_print_messages(chunk)
来自节点 addition_expert 的更新:

================================== Ai Message ==================================Tool Calls:  transfer_to_multiplication_expert (3e43e7c6-5ca4-433e-9a0e-e3a6f7913109) Call ID: 3e43e7c6-5ca4-433e-9a0e-e3a6f7913109  Args:    expression: 8 * 12================================= Tool Message =================================
成功转移

来自节点 multiplication_expert 的更新:

================================== Ai Message ==================================
The result of (3 + 5* 12 is 96.
这里代理流转的基本思路,我们主要使用了Commond进行node路由。
代码细节比较多,关键地方已经写了注释,建议读者亲自断点调试看一下每个地方的具体值是如何流转。

半夏决明
读书,摄影,随笔
 最新文章