当前位置: 首页 > news >正文

langchain系列(终)- LangGraph 多智能体详解

目录

一、导读

二、概念原理

1、智能体

2、多智能体

3、智能体弊端

4、多智能体优点

5、多智能体架构

6、交接(Handoffs)

7、架构说明

(1)网络

(2)监督者

(3)监督者(工具调用)

(4)层级

(5)自定义

8、智能体通信

(1)state

(2)工具调用

(3)共享信息

三、Conmand示例

1、功能说明

2、添加工具

 3、添加智能体

 4、编译graph

5、完整代码

6、输出结果

7、分析

四、工具示例

1、功能说明

2、定义数学工具

3、定义子图

4、简单测试

5、输出内容

6、定义交接工具 

7、定义智能体

8、完整代码

9、输出结果

9、分析

五、React Agent

1、方法说明

2、代码变动

3、完整代码

4、输出结果

六、函数API

1、功能说明

2、定义工具

3、定义智能体

 4、定义入口

5、完整代码

6、输出结果 

7、分析

七、大结局


一、导读

环境:OpenEuler、Windows 11、WSL 2、Python 3.12.3 langchain 0.3 langgraph 0.2

背景:前期忙碌的开发阶段结束,需要沉淀自己的应用知识,过一遍LangGraph

时间:20250308

说明:技术梳理,LangGraph 智能体、多智能体概念、原理详解,代码实现

注意:模型不同,代码可能存在bug,执行如果出现异常,可以多测试几次

官方文档地址:多智能体概念  多智能体示例

二、概念原理

1、智能体

智能体在英文文档中一般使用agent表示,所以某些文章中的代理也是表示当前智能体的概念。

智能体(Agent)是指能够感知环境并采取行动以实现特定目标的代理体。它可以是软件、硬件或一个系统,具备自主性、适应性和交互能力。智能体通过感知环境中的变化(如通过传感器或数据输入),根据自身学习到的知识和算法进行判断和决策,进而执行动作以影响环境或达到预定的目标。智能体在人工智能领域广泛应用,常见于自动化系统、机器人、虚拟助手和游戏角色等,其核心在于能够自主学习和持续进化,以更好地完成任务和适应复杂环境。

在LangChain或是LangGraph中,最简单的一个对话系统也可以称之为智能体,所以,不用想的很复杂

2、多智能体

多智能体也可以看作是一个智能体,因为本质上它是一个系统,只是功能多而已。在LangGraph的文档中,使用Multi-agent表示,子图是一种另类的多智能体。

3、智能体弊端

复杂的系统可能会遇到以下问题:

  • 智能体拥有的工具太多,无法做出关于调用下一个工具的明智决策
  • 上下文变得过于复杂,单个智能体难以跟踪
  • 系统中需要多个专业领域(例如计划者、研究者、数学专家等)

为了解决这些问题,可以考虑将应用程序分解为多个较小的独立智能体,并将它们组合成一个多智能体系统。这些独立智能体可以简单到只是一个提示和一次LLM调用,也可以复杂到你想象不到的地步。

4、多智能体优点

模块化:独立的智能体使得开发、测试和维护系统变得更加容易。
专业化:你可以创建专注于特定领域的专家智能体,这有助于提高整个系统的性能。
控制:你可以明确控制智能体之间的通信方式(而不是依赖于函数调用)。

5、多智能体架构

在多智能体系统中,常见的连接方式有以下几种:

网络:每个智能体都可以与其他每个智能体通信。任何智能体都可以决定下一个调用哪个智能体。
监督者:每个智能体都与一个监督者智能体通信。监督者智能体决定下一个调用哪个智能体。
监督者(工具调用):这是监督者架构的一个特例。个体智能体可以表示为工具。在这种情况下,监督者智能体使用工具调用的LLM来决定调用哪个智能体工具,以及传递给这些智能体的参数。
层次化:你可以定义一个具有监督者监督者的多智能体系统。这是监督者架构的泛化,允许更复杂的控制流。
自定义多智能体工作流:每个智能体只与部分智能体通信。部分流程是确定性的,只有某些智能体可以决定调用下一个哪个智能体。

6、交接(Handoffs

交接指的是该智能体下一步是结束、另一个智能体还是本身。交接需要两个参数:目标智能体、负载。

一般交接均使用Command对象,例如:

在LangGraph中实现交接,智能体节点可以返回Command对象,该对象允许你结合控制流和状态更新:

def agent(state) -> Command[Literal["agent", "another_agent"]]:# 路由/停止的条件可以是任何东西,例如LLM工具调用/结构化输出等goto = get_next_agent(...)  # 'agent' / 'another_agent'return Command(# 指定调用下一个智能体goto=goto,# 更新图状态update={"my_state_key": "my_state_value"})

在更复杂的场景中,每个智能体节点本身就是一个图(即,一个子图),一个智能体子图中的节点可能希望导航到另一个智能体。例如,如果你有两个智能体,alicebob(父图中的子图节点),并且alice需要导航到bob,你可以在Command对象中设置graph=Command.PARENT

def some_node_inside_alice(state)return Command(goto="bob",update={"my_state_key": "my_state_value"},# 指定要导航到的图(默认为当前图)graph=Command.PARENT,)

最常见的智能体类型之一是ReAct风格的工具调用智能体。对于这种类型的智能体,常见的模式是将交接包装在一个工具调用中

def transfer_to_bob(state):"""转移到 bob."""return Command(goto="bob",update={"my_state_key": "my_state_value"},graph=Command.PARENT,)

7、架构说明

(1)网络

在这种架构中,智能体被定义为图节点。每个智能体可以与每个其他智能体通信(多对多连接),并可以决定调用下一个哪个智能体。这种架构适用于没有明确智能体层次结构或特定智能体调用顺序的问题

(2)监督者

在这种架构中,我们定义智能体为节点,并添加一个监督者节点(LLM),它决定调用哪个智能体节点。我们使用Command根据监督者的决定将执行路由到适当的智能体节点。这种架构也适用于并行运行多个智能体或使用map-reduce模式。

(3)监督者(工具调用)

在这种监督者架构的变体中,我们将个体智能体定义为工具,并在监督者节点中使用工具调用的LLM。这可以实现为一个ReAct-风格的智能体,具有两个节点——一个LLM节点(监督者)和一个执行工具的节点(在这种情况下为智能体)。

(4)层级

当你向系统添加更多智能体时,监督者可能难以管理所有智能体。监督者可能会开始做出关于调用哪个智能体的不良决策,上下文可能变得过于复杂,以至于单个监督者无法跟踪。换句话说,你最终会遇到最初促使多智能体架构的问题。

为了解决这个问题,你可以设计你的系统为层级式。例如,你可以创建由单独监督者管理的专用智能体团队,并由顶层监督者管理这些团队。

(5)自定义

在这种架构中,我们添加单独的智能体作为图节点,并预先定义智能体调用的顺序,形成一个自定义工作流。在LangGraph中,工作流可以通过两种方式定义:

显式控制流(普通边):LangGraph允许你显式定义应用程序的控制流(即智能体通信的顺序),通过普通图边。这是上述架构中最确定的变体——我们总是知道下一个调用哪个智能体。
动态控制流(Command):在LangGraph中,你可以允许LLM决定应用程序控制流的部分。这可以通过使用Command实现。这种架构的一个特例是监督者工具调用架构。在这种情况下,为监督者智能体提供动力的工具调用LLM将决定调用工具(智能体)的顺序。

8、智能体通信

(1)state

上述大多数架构中,智能体是通过state进行通信的

相同state

要通过图状态进行通信,需要将各个智能体定义为图节点。这些节点可以作为函数或整个子图添加。在图执行的每一步中,智能体节点都会接收当前的图状态,执行智能体代码,然后将更新后的状态传递给下一个节点。

不同state

一个智能体可能需要具有与其他智能体不同的状态模式。例如,搜索智能体可能只需要跟踪查询和检索到的文档。在LangGraph中,有两种方法可以实现这一点:

定义具有单独状态模式的子图智能体:如果子图和父图之间没有共享状态键(通道),则需要添加输入/输出转换,以便父图知道如何与子图通信。
定义具有私有输入状态模式的智能体节点函数:该模式与整体图状态模式不同。这允许传递仅用于执行特定智能体的信息。

(2)工具调用

在具有工具调用的监督者的情况下,负载是工具调用的参数。

(3)共享信息

智能体之间最常用的通信方式是通过共享state,通常是消息列表。这假设状态中始终至少有一个通道(键)被智能体共享。

共享完整的历史
智能体可以与所有其他智能体共享完整的思考过程历史(草稿)。这种“草稿”通常看起来像一个消息列表。共享完整思考过程的好处是,它可能帮助其他智能体做出更好的决策,并提高整个系统的推理能力。缺点是随着智能体数量和复杂性的增加,“草稿”会迅速增长,可能需要额外的策略来管理内存。

共享最终结果
智能体可以有自己的私有“草稿”,并仅与其他智能体共享最终结果。这种方法可能更适合具有许多智能体或更复杂智能体的系统。在这种情况下,你需要定义具有不同状态模式的智能体。

对于作为工具调用的智能体,监督者根据工具模式确定输入。此外,LangGraph允许传递state给工具,因此下属智能体可以在需要时访问父图state。

三、Conmand示例

此示例演示使用用Command进行交接

1、功能说明

此处实现两个智能体的系统:加法智能体、乘法智能体,计算功能交给大模型。两个智能体互相可以交接,返回结束或是Command对象,拓扑结构为网络架构。

大模型计算时若是返回带有tool_calls属性,则返回Command对象,否则直接返回消息并结束

2、添加工具

此处需要两个工具,该工具仅具备形式的含义,方便判断大模型返回时是否具有call_tools属性

@tool
def transfer_to_multiplication_expert():"""向乘法智能体寻求帮助。"""# 此工具不会返回任何内容:我们只是使用它作为一种方式,让语言模型(LLM)表明需要将任务移交给另一个智能体return@tool
def transfer_to_addition_expert():"""向加法智能体寻求帮助。"""return

 3、添加智能体

智能体有两个,分别时加法、乘法智能体。此时大模型先绑定工具再调用,通过大模型调用返回值来确认是结束还是路由到某个智能体,此逻辑均在智能体中实现,加法智能体如下

def addition_expert(state: MessagesState,
) -> Command[Literal["multiplication_expert", "__end__"]]:system_prompt = ("你是加法专家,当你需要进行乘法运算时可以向乘法专家寻求帮助。在交接前,务必先完成你自己部分的计算。")messages = [{"role": "system", "content": system_prompt}] + state["messages"]ai_msg = model.bind_tools([transfer_to_multiplication_expert]).invoke(messages)# 如果存在工具调用,LLM(语言模型)需要将任务移交给另一个智能体(agent)。if len(ai_msg.tool_calls) > 0:tool_call_id = ai_msg.tool_calls[-1]["id"]# 注意:在这里插入一个工具消息是很重要的,因为LLM提供商期望所有的AI消息后面都跟着一个相应的工具结果消息。tool_msg = {"role": "tool","content": "成功交接","tool_call_id": tool_call_id,}return Command(goto="multiplication_expert", update={"messages": [ai_msg, tool_msg]})return {"messages": [ai_msg]}

显然,当 len(ai_msg.tool_calls) > 0时,说明存在工具调用,直接返回Command对象,需要两个参数,目的地(goto)和信息(update)

乘法智能体如下

def multiplication_expert(state: MessagesState,
) -> Command[Literal["addition_expert", "__end__"]]:system_prompt = ("你是一个乘法专家,你可以向加法专家寻求加法方面的帮助。在交接之前,总是先完成你那部分的计算。")messages = [{"role": "system", "content": system_prompt}] + state["messages"]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 = {"role": "tool","content": "成功交接","tool_call_id": tool_call_id,}return Command(goto="addition_expert", update={"messages": [ai_msg, tool_msg]})return {"messages": [ai_msg]}

由于此处仅有两个智能体,所以目的地时互相指向的,看代码中goto的参数

 4、编译graph

由于两个智能体互相指向,所以此处添加两个节点随意指向一个起始节点即可,如下

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

5、格式化输出

为了能够更清晰的输出过程信息,此处定义一个输出函数

def pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# 在打印输出中跳过父图更新。if len(ns) == 0:returngraph_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")

5、完整代码


from typing_extensions import Literal
from langchain_core.tools import tool
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.types import Command
from langchain_openai import ChatOpenAI
from langchain_core.messages import convert_to_messages# 指定大模型的API Key 等相关信息
model = ChatOpenAI(base_url="https://lxxxxx.enovo.com/v1/", api_key="sxxxxxxxwW",model_name="qwen2.5-instruct")@tool
def transfer_to_multiplication_expert():"""向乘法智能体寻求帮助。"""# 此工具不会返回任何内容:我们只是使用它作为一种方式,让语言模型(LLM)表明需要将任务移交给另一个智能体return@tool
def transfer_to_addition_expert():"""向加法智能体寻求帮助。"""returndef addition_expert(state: MessagesState,
) -> Command[Literal["multiplication_expert", "__end__"]]:system_prompt = ("你是加法专家,当你需要进行乘法运算时可以向乘法专家寻求帮助。在交接前,务必先完成你自己部分的计算。")messages = [{"role": "system", "content": system_prompt}] + state["messages"]ai_msg = model.bind_tools([transfer_to_multiplication_expert]).invoke(messages)# 如果存在工具调用,LLM(语言模型)需要将任务移交给另一个智能体(agent)。if len(ai_msg.tool_calls) > 0:tool_call_id = ai_msg.tool_calls[-1]["id"]# 注意:在这里插入一个工具消息是很重要的,因为LLM提供商期望所有的AI消息后面都跟着一个相应的工具结果消息。tool_msg = {"role": "tool","content": "成功交接","tool_call_id": tool_call_id,}return Command(goto="multiplication_expert", update={"messages": [ai_msg, tool_msg]})return {"messages": [ai_msg]}def multiplication_expert(state: MessagesState,
) -> Command[Literal["addition_expert", "__end__"]]:system_prompt = ("你是一个乘法专家,你可以向加法专家寻求加法方面的帮助。在交接之前,总是先完成你那部分的计算。")messages = [{"role": "system", "content": system_prompt}] + state["messages"]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 = {"role": "tool","content": "成功交接","tool_call_id": tool_call_id,}return Command(goto="addition_expert", update={"messages": [ai_msg, tool_msg]})return {"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()def pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# 在打印输出中跳过父图更新。if len(ns) == 0:returngraph_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", "(3 + 5) * 12 等于多少")]}):pretty_print_messages(chunk)

6、输出结果

从节点addition_expert输出:================================== Ai Message ==================================
Tool Calls:transfer_to_multiplication_expert (call_fac397c6-27ac-40ca-9f4c-892a7d750419)Call ID: call_fac397c6-27ac-40ca-9f4c-892a7d750419Args:result_of_addition: 8multiplier: 12
================================= Tool Message =================================成功交接从节点multiplication_expert输出:================================== Ai Message ==================================计算 (3 + 5) * 12 的结果时,首先需要计算加法部分,即 3 + 5 = 8,然后我们将这个结果乘以 12,得到 8 * 12 = 96。因此,(3 + 5) * 12 等于 96。

7、分析

显然,先调用了加法智能体,将( 3 + 5 )的结果计算了出来,之后调用了乘法智能体,此时已经计算完毕,所以直接输出了消息

四、工具示例

此示例演示使用工具进行交接

1、功能说明

创建交接工具,工具直接返回Command对象。当智能体调用工具时,工具会路由到指定的智能体。其主体功能与上述示例一样

注意:与前面的例子不同,调用工具的智能体不是一个单独的节点,而是一个可以作为子图节点添加到多智能体图中的另一个图。

由于每个智能体都是另一个图中的一个__子图__节点,并且工具将在智能体子图节点之一中被调用(例如工具执行器),因此需要在Command中指定graph=Command.PARENT,这样LangGraph就知道要在智能体子图之外导航。

我们可以选择指定一个状态更新,该更新将在下一个智能体被调用之前应用于父图的状态。

这些状态更新可用于控制目标智能体可以看到的聊天消息历史记录的多少。例如,你可能选择只共享当前智能体的最后一条AI消息,或者其完整的内部聊天历史记录等。在下面的例子中,我们将共享完整的内部聊天历史记录。
我们可以选择向工具提供以下内容(在工具函数签名中):

图状态(使用InjectedState)
图长期记忆(使用InjectedStore)
当前工具调用ID(使用InjectedToolCallId)
这些不是必需的,但对创建传递给下一个智能体的状态更新很有用。

2、定义数学工具

定义两个工具,加法与乘法

@tool
def add(a: int, b: int) -> int:"""两数相加"""return a + b@tool
def multiply(a: int, b: int) -> int:"""两数相乘"""return a * b

3、定义子图

该函数返回一个graph对象,其内封装了访问工具和访问大模型的功能

def make_agent(model, tools, system_prompt=None):model_with_tools = model.bind_tools(tools)tools_by_name = {tool.name: tool for tool in tools}def call_model(state: MessagesState) -> Command[Literal["call_tools", "__end__"]]:messages = state["messages"]if system_prompt:messages = [{"role": "system", "content": system_prompt}] + messagesresponse = model_with_tools.invoke(messages)if len(response.tool_calls) > 0:return Command(goto="call_tools", update={"messages": [response]})return {"messages": [response]}# 注意:这是预构建的ToolNode的一个简化版本def call_tools(state: MessagesState) -> Command[Literal["call_model"]]:tool_calls = state["messages"][-1].tool_callsresults = []for tool_call in tool_calls:tool_ = tools_by_name[tool_call["name"]]tool_input_fields = tool_.get_input_schema().model_json_schema()["properties"]# 这是为了演示目的而简化的,并且与ToolNode的实际实现不同if "state" in tool_input_fields:tool_call = {**tool_call, "args": {**tool_call["args"], "state": state}}tool_response = tool_.invoke(tool_call)if isinstance(tool_response, ToolMessage):results.append(Command(update={"messages": [tool_response]}))# 处理直接返回命令的工具elif isinstance(tool_response, Command):results.append(tool_response)# 在LangGraph中,节点允许您返回更新列表,包括命令对象return resultsgraph = StateGraph(MessagesState)graph.add_node(call_model)graph.add_node(call_tools)graph.add_edge(START, "call_model")graph.add_edge("call_tools", "call_model")return graph.compile()

4、简单测试

整体功能与Command示例一样,仅仅交接方式不同

from typing import Annotated
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langchain_openai import ChatOpenAIfrom typing_extensions import Literal
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.types import Command
from langchain_core.messages import convert_to_messages# 指定大模型的API Key 等相关信息
model = ChatOpenAI(base_url="https://lxxxxx.enovo.com/v1/", api_key="sxxxxxxxwW",model_name="qwen2.5-instruct")def make_agent(model, tools, system_prompt=None):model_with_tools = model.bind_tools(tools)tools_by_name = {tool.name: tool for tool in tools}def call_model(state: MessagesState) -> Command[Literal["call_tools", "__end__"]]:messages = state["messages"]if system_prompt:messages = [{"role": "system", "content": system_prompt}] + messagesresponse = model_with_tools.invoke(messages)if len(response.tool_calls) > 0:return Command(goto="call_tools", update={"messages": [response]})return {"messages": [response]}# 注意:这是预构建的ToolNode的一个简化版本def call_tools(state: MessagesState) -> Command[Literal["call_model"]]:tool_calls = state["messages"][-1].tool_callsresults = []for tool_call in tool_calls:tool_ = tools_by_name[tool_call["name"]]tool_input_fields = tool_.get_input_schema().model_json_schema()["properties"]# 这是为了演示目的而简化的,并且与ToolNode的实际实现不同if "state" in tool_input_fields:tool_call = {**tool_call, "args": {**tool_call["args"], "state": state}}tool_response = tool_.invoke(tool_call)if isinstance(tool_response, ToolMessage):results.append(Command(update={"messages": [tool_response]}))# 处理直接返回命令的工具elif isinstance(tool_response, Command):results.append(tool_response)# 在LangGraph中,节点允许您返回更新列表,包括命令对象return resultsgraph = StateGraph(MessagesState)graph.add_node(call_model)graph.add_node(call_tools)graph.add_edge(START, "call_model")graph.add_edge("call_tools", "call_model")return graph.compile()@tool
def add(a: int, b: int) -> int:"""两数相加"""return a + b@tool
def multiply(a: int, b: int) -> int:"""两数相乘"""return a * bagent = make_agent(model, [add, multiply])def pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# skip parent graph updates in the printoutsif len(ns) == 0:returngraph_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")if isinstance(node_update, list):node_update = node_update[-1]for m in convert_to_messages(node_update["messages"]):m.pretty_print()print("\n")for chunk in agent.stream({"messages": [("user", "(3 + 5) * 12 等于多少")]}):pretty_print_messages(chunk)

5、输出内容

从节点call_model输出:================================== Ai Message ==================================
Tool Calls:add (call_e5ac406e-310f-4964-8ac1-8fcdd1dbe1d6)Call ID: call_e5ac406e-310f-4964-8ac1-8fcdd1dbe1d6Args:a: 3b: 5multiply (call_e5ac406e-310f-4964-8ac1-8fcdd1dbe1d6)Call ID: call_e5ac406e-310f-4964-8ac1-8fcdd1dbe1d6Args:a: 8b: 12从节点call_tools输出:================================= Tool Message =================================
Name: multiply96从节点call_model输出:================================== Ai Message ==================================计算 (3 + 5) * 12 的结果是 96。

由此可见,可以正常输出 

6、定义交接工具 

该工具的作用仅仅为路由到指定工具的作用,可以认为是中介或是中间件的作用

def make_handoff_tool(*, agent_name: str):"""创建一个能够通过命令返回交接信息的工具。"""tool_name = f"transfer_to_{agent_name}"@tool(tool_name)def handoff_to_agent(# 可选地将当前图的状态传递给工具(该状态将被语言模型忽略)state: Annotated[dict, InjectedState],# 可选地传递当前的工具调用ID(该ID将被语言模型忽略)tool_call_id: Annotated[str, InjectedToolCallId],):"""Ask another agent for help."""tool_message = {"role": "tool","content": f"Successfully transferred to {agent_name}","name": tool_name,"tool_call_id": tool_call_id,}return Command(# # 在父图中导航到另一个代理节点goto=agent_name,graph=Command.PARENT,# 这是智能体 `agent_name` 在被调用时将看到的状态更新。# 我们传递了代理的全部内部消息历史,并添加了一个工具消息,以确保最终的聊天记录是有效的update={"messages": state["messages"] + [tool_message]},)return handoff_to_agent

7、定义智能体

定义加法、乘法智能体,指定大模型、工具、交接工具以及提示词

addition_expert = make_agent(model,[add, make_handoff_tool(agent_name="multiplication_expert")],system_prompt="您是加法专家,您可以向乘法专家寻求乘法方面的帮助。",
)
multiplication_expert = make_agent(model,[multiply, make_handoff_tool(agent_name="addition_expert")],system_prompt="您是乘法专家,您可以向加法专家寻求加法方面的帮助。",
)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()

8、完整代码

此次运行,会调用定义的交接工具,实现演示目的

from typing import Annotated
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langchain_openai import ChatOpenAIfrom typing_extensions import Literal
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.types import Command
from langchain_core.messages import convert_to_messages# 指定大模型的API Key 等相关信息
model = ChatOpenAI(base_url="https://lxxxxx.enovo.com/v1/", api_key="sxxxxxxxwW",model_name="qwen2.5-instruct")def make_handoff_tool(*, agent_name: str):"""创建一个能够通过命令返回交接信息的工具。"""tool_name = f"transfer_to_{agent_name}"@tool(tool_name)def handoff_to_agent(# 可选地将当前图的状态传递给工具(该状态将被语言模型忽略)state: Annotated[dict, InjectedState],# 可选地传递当前的工具调用ID(该ID将被语言模型忽略)tool_call_id: Annotated[str, InjectedToolCallId],):"""Ask another agent for help."""tool_message = {"role": "tool","content": f"成功交接到 {agent_name}","name": tool_name,"tool_call_id": tool_call_id,}return Command(# # 在父图中导航到另一个代理节点goto=agent_name,graph=Command.PARENT,# 这是智能体 `agent_name` 在被调用时将看到的状态更新。# 我们传递了代理的全部内部消息历史,并添加了一个工具消息,以确保最终的聊天记录是有效的update={"messages": state["messages"] + [tool_message]},)return handoff_to_agentdef make_agent(model, tools, system_prompt=None):model_with_tools = model.bind_tools(tools)tools_by_name = {tool.name: tool for tool in tools}def call_model(state: MessagesState) -> Command[Literal["call_tools", "__end__"]]:messages = state["messages"]if system_prompt:messages = [{"role": "system", "content": system_prompt}] + messagesresponse = model_with_tools.invoke(messages)if len(response.tool_calls) > 0:return Command(goto="call_tools", update={"messages": [response]})return {"messages": [response]}# 注意:这是预构建的ToolNode的一个简化版本def call_tools(state: MessagesState) -> Command[Literal["call_model"]]:tool_calls = state["messages"][-1].tool_callsresults = []for tool_call in tool_calls:tool_ = tools_by_name[tool_call["name"]]tool_input_fields = tool_.get_input_schema().model_json_schema()["properties"]# 这是为了演示目的而简化的,并且与ToolNode的实际实现不同if "state" in tool_input_fields:tool_call = {**tool_call, "args": {**tool_call["args"], "state": state}}tool_response = tool_.invoke(tool_call)if isinstance(tool_response, ToolMessage):results.append(Command(update={"messages": [tool_response]}))# 处理直接返回命令的工具elif isinstance(tool_response, Command):results.append(tool_response)# 在LangGraph中,节点允许您返回更新列表,包括命令对象return resultsgraph = StateGraph(MessagesState)graph.add_node(call_model)graph.add_node(call_tools)graph.add_edge(START, "call_model")graph.add_edge("call_tools", "call_model")return graph.compile()@tool
def add(a: int, b: int) -> int:"""两数相加"""return a + b@tool
def multiply(a: int, b: int) -> int:"""两数相乘"""return a * baddition_expert = make_agent(model,[add, make_handoff_tool(agent_name="multiplication_expert")],system_prompt="您是加法专家,您可以向乘法专家寻求乘法方面的帮助。",
)
multiplication_expert = make_agent(model,[multiply, make_handoff_tool(agent_name="addition_expert")],system_prompt="您是乘法专家,您可以向加法专家寻求加法方面的帮助。",
)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()def pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# 在打印输出中跳过父图更新。if len(ns) == 0:returngraph_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")if isinstance(node_update, list):node_update = node_update[-1]for m in convert_to_messages(node_update["messages"]):m.pretty_print()print("\n")for chunk in graph.stream({"messages": [("user", "(3 + 5) * 12 等于多少")]}):pretty_print_messages(chunk)

9、输出结果

从节点addition_expert输出:================================ Human Message =================================(3 + 5) * 12 等于多少
================================== Ai Message ==================================
Tool Calls:add (call_2436b363-357f-4775-a58a-6adfe6189a3a)Call ID: call_2436b363-357f-4775-a58a-6adfe6189a3aArgs:a: 3b: 5
================================= Tool Message =================================
Name: add8
================================== Ai Message ==================================
Tool Calls:transfer_to_multiplication_expert (call_9286e818-9bcf-48ba-9e14-d29ca6218aec)Call ID: call_9286e818-9bcf-48ba-9e14-d29ca6218aecArgs:
================================= Tool Message =================================
Name: transfer_to_multiplication_expert成功交接到 multiplication_expert从节点multiplication_expert输出:================================ Human Message =================================(3 + 5) * 12 等于多少
================================== Ai Message ==================================
Tool Calls:add (call_2436b363-357f-4775-a58a-6adfe6189a3a)Call ID: call_2436b363-357f-4775-a58a-6adfe6189a3aArgs:a: 3b: 5
================================= Tool Message =================================
Name: add8
================================== Ai Message ==================================
Tool Calls:transfer_to_multiplication_expert (call_9286e818-9bcf-48ba-9e14-d29ca6218aec)Call ID: call_9286e818-9bcf-48ba-9e14-d29ca6218aecArgs:
================================= Tool Message =================================
Name: transfer_to_multiplication_expert成功交接到 multiplication_expert
================================== Ai Message ==================================
Tool Calls:multiply (call_917e4493-2d45-44d9-be56-603df36a36f4)Call ID: call_917e4493-2d45-44d9-be56-603df36a36f4Args:a: 8b: 12
================================= Tool Message =================================
Name: multiply96
================================== Ai Message ==================================(3 + 5) * 12 等于 96。

9、分析

计算结果是工具计算,而非大模型计算出来的;有专门的工具进行交接,而非由智能体直接路由

五、React Agent

1、方法说明

如果你不需要额外的自定义功能,可以使用预构建的create_react_agent,它通过ToolNode内置了交接工具的支持。也就是说,使用create_react_agent代替了make_agent即可

2、代码变动

使用create_react_agent代替了make_agent

from langgraph.prebuilt import create_react_agentaddition_expert = create_react_agent(model,[add, make_handoff_tool(agent_name="multiplication_expert")],prompt="您是加法专家,您可以向乘法专家寻求乘法方面的帮助。",
)
multiplication_expert = create_react_agent(model,[multiply, make_handoff_tool(agent_name="addition_expert")],prompt="您是乘法专家,您可以向加法专家寻求加法方面的帮助。",
)

3、完整代码

from typing import Annotated
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.types import Command
from langchain_core.messages import convert_to_messages# 指定大模型的API Key 等相关信息
model = ChatOpenAI(base_url="https://lxxxxx.enovo.com/v1/", api_key="sxxxxxxxwW",model_name="qwen2.5-instruct")def make_handoff_tool(*, agent_name: str):"""创建一个能够通过命令返回交接信息的工具。"""tool_name = f"transfer_to_{agent_name}"@tool(tool_name)def handoff_to_agent(# 可选地将当前图的状态传递给工具(该状态将被语言模型忽略)state: Annotated[dict, InjectedState],# 可选地传递当前的工具调用ID(该ID将被语言模型忽略)tool_call_id: Annotated[str, InjectedToolCallId],):"""Ask another agent for help."""tool_message = {"role": "tool","content": f"成功交接到 {agent_name}","name": tool_name,"tool_call_id": tool_call_id,}return Command(# # 在父图中导航到另一个代理节点goto=agent_name,graph=Command.PARENT,# 这是智能体 `agent_name` 在被调用时将看到的状态更新。# 我们传递了代理的全部内部消息历史,并添加了一个工具消息,以确保最终的聊天记录是有效的update={"messages": state["messages"] + [tool_message]},)return handoff_to_agent@tool
def add(a: int, b: int) -> int:"""两数相加"""return a + b@tool
def multiply(a: int, b: int) -> int:"""两数相乘"""return a * baddition_expert = create_react_agent(model,[add, make_handoff_tool(agent_name="multiplication_expert")],prompt="您是加法专家,您可以向乘法专家寻求乘法方面的帮助。",
)
multiplication_expert = create_react_agent(model,[multiply, make_handoff_tool(agent_name="addition_expert")],prompt="您是乘法专家,您可以向加法专家寻求加法方面的帮助。",
)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()def pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# 在打印输出中跳过父图更新。if len(ns) == 0:returngraph_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")if isinstance(node_update, list):node_update = node_update[-1]for m in convert_to_messages(node_update["messages"]):m.pretty_print()print("\n")for chunk in graph.stream({"messages": [("user", "(3 + 5) * 12 等于多少")]}):pretty_print_messages(chunk)

4、输出结果

从节点addition_expert输出:================================ Human Message =================================(3 + 5) * 12 等于多少
================================== Ai Message ==================================
Tool Calls:add (call_01a46d37-188b-473d-b5f3-994d9beafe94)Call ID: call_01a46d37-188b-473d-b5f3-994d9beafe94Args:a: 3b: 5
================================= Tool Message =================================
Name: add8
================================== Ai Message ==================================
Tool Calls:transfer_to_multiplication_expert (call_3aeea06b-8b6b-4df0-9d12-0676a2cb3b07)Call ID: call_3aeea06b-8b6b-4df0-9d12-0676a2cb3b07Args:
================================= Tool Message =================================
Name: transfer_to_multiplication_expert成功交接到 multiplication_expert从节点multiplication_expert输出:================================ Human Message =================================(3 + 5) * 12 等于多少
================================== Ai Message ==================================
Tool Calls:add (call_01a46d37-188b-473d-b5f3-994d9beafe94)Call ID: call_01a46d37-188b-473d-b5f3-994d9beafe94Args:a: 3b: 5
================================= Tool Message =================================
Name: add8
================================== Ai Message ==================================
Tool Calls:transfer_to_multiplication_expert (call_3aeea06b-8b6b-4df0-9d12-0676a2cb3b07)Call ID: call_3aeea06b-8b6b-4df0-9d12-0676a2cb3b07Args:
================================= Tool Message =================================
Name: transfer_to_multiplication_expert成功交接到 multiplication_expert
================================== Ai Message ==================================
Tool Calls:multiply (call_ebca3e9c-8c7d-43c6-832d-1202117468b8)Call ID: call_ebca3e9c-8c7d-43c6-832d-1202117468b8Args:a: 8b: 12
================================= Tool Message =================================
Name: multiply96
================================== Ai Message ==================================(3 + 5) * 12 等于 96。

六、函数API

本小节使用函数API演示如何实现一个网络架构,其中每个智能体都可以与其他所有智能体通信(多对多连接),并且可以决定下一个调用哪个代理。

1、功能说明

建立一个旅行助手智能体团队,这些智能体能够相互交流。

我们将创建两个智能体:

travel_advisor:可以提供旅行目的地推荐。可以向hotel_advisor寻求帮助。
hotel_advisor:可以提供酒店推荐。可以向travel_advisor寻求帮助。
这是一个完全连接的网络——每个智能体都可以与其他任何智能体通信。

本节未使用常规的节点、边、状态的构建方法,使用了LangGraph的API形式

2、定义工具

添加酒店、城市工具,以及交接工具

@tool
def get_travel_recommendations():"""获取旅行目的地推荐"""return random.choice(["哈尔滨", "沈阳"])@tool
def get_hotel_recommendations(location: Literal["哈尔滨", "沈阳"]):"""获取给定目的地的酒店推荐。"""return {"哈尔滨": ["格林豪泰酒店", "万达酒店"],"沈阳": ["七天酒店", "白玉兰酒店"]}[location]@tool(return_direct=True)
def transfer_to_hotel_advisor():"""向酒店顾问代理寻求帮助。"""return "成功交接到hotel advisor"@tool(return_direct=True)
def transfer_to_travel_advisor():"""向旅行顾问代理寻求帮助。"""return "成功交接到travel advisor"

注意:传输工具中使用了@tool(return_direct=True)。这样做的目的是为了让单个代理(例如,travel_advisor)在调用这些工具时能够提前退出ReAct循环。这是期望的行为,因为我们希望在代理调用此工具时立即检测到并立即将控制权移交给另一个代理。 

3、定义智能体

定义两个智能体,酒店和城市推荐智能体

# 定义城市推荐
travel_advisor_tools = [get_travel_recommendations,transfer_to_hotel_advisor
]
travel_advisor = create_react_agent(model,travel_advisor_tools,state_modifier=("你是一个通用的旅行专家,可以推荐旅行目的地(如国家、城市等)。""如果你需要酒店推荐,请向‘hotel_advisor’寻求帮助。""在转接给其他代理之前,你必须提供易于人类阅读的回复。"),
)@task
def call_travel_advisor(messages):# 您还可以添加额外的逻辑,例如更改 agent 的输入 / agent 的输出等。# 注意:我们正在调用 ReAct 代理,其中包含该状态中消息的完整历史记录response = travel_advisor.invoke({"messages": messages})return response["messages"]# 定义酒店推荐
hotel_advisor_tools = [get_hotel_recommendations, transfer_to_travel_advisor]
hotel_advisor = create_react_agent(model,hotel_advisor_tools,state_modifier=("你是一个酒店专家,可以为指定的目的地提供酒店推荐。""如果你需要帮助选择旅行目的地,请向‘travel_advisor’寻求帮助。""在转接给其他代理之前,你必须提供易于人类阅读的回复。"),
)@task
def call_hotel_advisor(messages):response = hotel_advisor.invoke({"messages": messages})return response["messages"]

 4、定义入口

使用LangGraph的Func,实现功能入口流程

# 程序入口
@entrypoint()
def workflow(messages):messages = add_messages([], messages)call_active_agent = call_travel_advisorwhile True:agent_messages = call_active_agent(messages).result()messages = add_messages(messages, agent_messages)ai_msg = next(m for m in reversed(agent_messages) if isinstance(m, AIMessage))if not ai_msg.tool_calls:breaktool_call = ai_msg.tool_calls[-1]if tool_call["name"] == "transfer_to_travel_advisor":call_active_agent = call_travel_advisorelif tool_call["name"] == "transfer_to_hotel_advisor":call_active_agent = call_hotel_advisorelse:raise ValueError(f"期望交接工具, 得到了 '{tool_call['name']}'")return messages

使用了 entrypoint和task方式,实现了图的流转

5、完整代码

from langchain_openai import ChatOpenAIfrom langchain_core.messages import AIMessage
from langgraph.prebuilt import create_react_agent
from langgraph.graph import add_messages
from langgraph.func import entrypoint, task
import randomfrom typing_extensions import Literal
from langchain_core.tools import tool
from langchain_core.messages import convert_to_messages# 指定大模型的API Key 等相关信息
model = ChatOpenAI(base_url="https://lxxxxx.enovo.com/v1/", api_key="sxxxxxxxwW",model_name="qwen2.5-instruct")@tool
def get_travel_recommendations():"""获取旅行目的地推荐"""return random.choice(["哈尔滨", "沈阳"])@tool
def get_hotel_recommendations(location: Literal["哈尔滨", "沈阳"]):"""获取给定目的地的酒店推荐。"""return {"哈尔滨": ["格林豪泰酒店", "万达酒店"],"沈阳": ["七天酒店", "白玉兰酒店"]}[location]@tool(return_direct=True)
def transfer_to_hotel_advisor():"""向酒店顾问代理寻求帮助。"""return "成功交接到hotel advisor"@tool(return_direct=True)
def transfer_to_travel_advisor():"""向旅行顾问代理寻求帮助。"""return "成功交接到travel advisor"# 定义城市推荐
travel_advisor_tools = [get_travel_recommendations,transfer_to_hotel_advisor
]
travel_advisor = create_react_agent(model,travel_advisor_tools,state_modifier=("你是一个通用的旅行专家,可以推荐旅行目的地(如国家、城市等)。""如果你需要酒店推荐,请向‘hotel_advisor’寻求帮助。""在转接给其他代理之前,你必须提供易于人类阅读的回复。"),
)@task
def call_travel_advisor(messages):# 您还可以添加额外的逻辑,例如更改 agent 的输入 / agent 的输出等。# 注意:我们正在调用 ReAct 代理,其中包含该状态中消息的完整历史记录response = travel_advisor.invoke({"messages": messages})return response["messages"]# 定义酒店推荐
hotel_advisor_tools = [get_hotel_recommendations, transfer_to_travel_advisor]
hotel_advisor = create_react_agent(model,hotel_advisor_tools,state_modifier=("你是一个酒店专家,可以为指定的目的地提供酒店推荐。""如果你需要帮助选择旅行目的地,请向‘travel_advisor’寻求帮助。""在转接给其他代理之前,你必须提供易于人类阅读的回复。"),
)@task
def call_hotel_advisor(messages):response = hotel_advisor.invoke({"messages": messages})return response["messages"]# 程序入口
@entrypoint()
def workflow(messages):messages = add_messages([], messages)call_active_agent = call_travel_advisorwhile True:agent_messages = call_active_agent(messages).result()messages = add_messages(messages, agent_messages)ai_msg = next(m for m in reversed(agent_messages) if isinstance(m, AIMessage))if not ai_msg.tool_calls:breaktool_call = ai_msg.tool_calls[-1]if tool_call["name"] == "transfer_to_travel_advisor":call_active_agent = call_travel_advisorelif tool_call["name"] == "transfer_to_hotel_advisor":call_active_agent = call_hotel_advisorelse:raise ValueError(f"期望交接工具, 得到了 '{tool_call['name']}'")return messagesdef pretty_print_messages(update):if isinstance(update, tuple):ns, update = update# 在打印输出中跳过父图更新。if len(ns) == 0:returngraph_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 workflow.stream([{"role": "user","content": "我想去中国寒冷的地方。选择一个目的地并给我推荐酒店",}],subgraphs=True,
):pretty_print_messages(chunk)

6、输出结果 

从子图更新 call_travel_advisor:从节点更新 agent:================================== Ai Message ==================================
Tool Calls:get_travel_recommendations (call_cf9d6fba-b6fa-451c-818c-a4a26554eb1b)Call ID: call_cf9d6fba-b6fa-451c-818c-a4a26554eb1bArgs:location_preference: coldcountry: China从子图更新 call_travel_advisor:从节点更新 tools:================================= Tool Message =================================
Name: get_travel_recommendations哈尔滨从子图更新 call_travel_advisor:从节点更新 agent:================================== Ai Message ==================================
Tool Calls:transfer_to_hotel_advisor (call_95329200-b9a4-4fb6-87ab-825c5184853d)Call ID: call_95329200-b9a4-4fb6-87ab-825c5184853dArgs:destination: 哈尔滨从子图更新 call_travel_advisor:从节点更新 tools:================================= Tool Message =================================
Name: transfer_to_hotel_advisor成功交接到hotel advisor从子图更新 call_hotel_advisor:从节点更新 agent:================================== Ai Message ==================================
Tool Calls:get_hotel_recommendations (call_5a0c9b35-6fc8-490a-87ee-f435cb3ca35e)Call ID: call_5a0c9b35-6fc8-490a-87ee-f435cb3ca35eArgs:location: 哈尔滨从子图更新 call_hotel_advisor:从节点更新 tools:================================= Tool Message =================================
Name: get_hotel_recommendations["格林豪泰酒店", "万达酒店"]从子图更新 call_hotel_advisor:从节点更新 agent:================================== Ai Message ==================================我为您选择了中国的寒冷目的地:哈尔滨。以下是为您推荐的酒店:格林豪泰酒店 和 万达酒店。希望您会喜欢!

7、分析

整体实现了效果,不过代码并非100%运行成功,偶尔会出现异常,推测和使用的模型相关,一般会在response = hotel_advisor.invoke({"messages": messages})出现问题,经测试,保证100%运行成功可以,有些复杂,此处不再赘述。

本小节实现了网络架构的流程,从选择目的地到推荐酒店。里面使用了之前未曾使用的方法,便于理解多种方式的使用

七、大结局

LangChain系列到此结束,由于写的匆忙,有一些bug可能存在于文章中,欢迎指正 

相关文章:

langchain系列(终)- LangGraph 多智能体详解

目录 一、导读 二、概念原理 1、智能体 2、多智能体 3、智能体弊端 4、多智能体优点 5、多智能体架构 6、交接(Handoffs) 7、架构说明 (1)网络 (2)监督者 (3)监督者&…...

springboot旅游管理系统设计与实现(代码+数据库+LW)

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本旅游管理系统就是在这样的大环境下诞生,其可以帮助使用者在短时间内处理完毕庞大的数据信息&a…...

【前端跨域】WebSocket如何实现跨域通信?原理、实践与安全指南

在实时通信场景(如在线聊天、实时数据推送)中,WebSocket因其高效的双向通信能力成为首选技术 然而,当客户端与服务器部署在不同源时,跨域问题同样可能阻碍WebSocket的连接 一、WebSocket与跨域的关系 WebSocket的跨…...

Go红队开发—格式导出

文章目录 输出功能CSV输出CSV 转 结构体结构体 转 CSV端口扫描结果使用CSV格式导出 HTML输出Sqlite输出nmap扫描 JSONmap转json结构体转jsonjson写入文件json编解码json转结构体json转mapjson转string练习:nmap扫描结果导出json格式 输出功能 在我们使用安全工具的…...

Sharp 存在任意文件读取漏洞( DVB-2025-8923)

免责声明 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x01…...

C++数组,链表,二叉树的内存排列是什么样的,结构体占多大内存如何计算,类占多大内存如何计算,空类的空间是多少,为什么?

C数组是连续存储的,C数组元素依次存放在相邻的内存地址之中,并且内存大小相同。 C链表是离散存储的,C链表是由节点构成的,每个节点之中存在节点的值以及指向下一个节点的指针,每个节点是动态分配的。 C二叉树也是离散…...

【vLLM 教程】使用 TPU 安装

vLLM 是一款专为大语言模型推理加速而设计的框架,实现了 KV 缓存内存几乎零浪费,解决了内存管理瓶颈问题。 更多 vLLM 中文文档及教程可访问 →https://vllm.hyper.ai/ vLLM 使用 PyTorch XLA 支持 Google Cloud TPU。 依赖环境​ Google Cloud TPU …...

【RAG】基于向量检索的 RAG (BGE示例)

RAG机器人 结构体 文本向量化: 使用 BGE 模型将文档和查询编码为向量。 (BGE 是专为检索任务优化的开源 Embedding 模型,除了本文API调用,也可以通过Hugging Face 本地部署BGE 开源模型) 向量检索: 从数据库中找到与查询相关的文…...

【RAG】RAG 系统的基本搭建流程(ES关键词检索示例)

RAG 系统的基本搭建流程 搭建过程: 文档加载,并按一定条件切割成片段将切割的文本片段灌入检索引擎封装检索接口构建调用流程:Query -> 检索 -> Prompt -> LLM -> 回复 1. 文档的加载与切割 # !pip install --upgrade openai…...

PSIM积累经验

1、三极管的部署报错。 出错信息: 元件: R 名称: R2 Error: The RLC branch R2 is connected to the gate node of the switch Q1. The gate node should be connected to an On-Off Controller output. Refer to the switch Help p…...

C++之vector类(超详解)

这节我们来学习一下,C中一个重要的工具——STL,这是C中自带的一个标准库,我们可以直接调用这个库中的函数或者容器,可以使效率大大提升。这节我们介绍STL中的vector。 文章目录 前言 一、标准库类型vector 二、vector的使用 2.…...

Go学习笔记

<!-- 注意* --> 初始化工程 go mod init GoDemo 结构体&#xff0c;接口 type i struct{} type i interface{} 条件&#xff0c;选择 循环 键值对 make(map[string]int) 切片&#xff0c;集合 make([]int,10) 函数 通道 Channel make(chan int) ​ ch <- v…...

前端杂的学习笔记

什么是nginx Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 Nginx是一款轻量级的Web 服务器/反向代理服务器&#xff0c;处理高并发能力是十分强大的&#xff0c;并且支持热部署&#xff0c;启动简单&#xff0c;可以做到7*24不间断运行 正代和反代 学习nginx&a…...

痉挛性斜颈护理:全方位呵护,重燃生活希望

痉挛性斜颈是一种以颈部肌肉不自主收缩导致头部向一侧扭转或倾斜为特征的疾病。对于痉挛性斜颈患者而言&#xff0c;科学有效的护理能够显著提升其生活质量&#xff0c;辅助病情的改善。 生活护理&#xff1a;在生活环境布置上&#xff0c;要充分考虑患者行动的便利性。确保室内…...

MySQL的安装以及数据库的基本配置

MySQL的安装及配置 MySQL的下载 选择想要安装的版本&#xff0c;点击Download下载 Mysql官网下载地址&#xff1a;​ ​https://downloads.mysql.com/archives/installer/​​ MySQL的安装 选择是自定义安装&#xff0c;所以直接选择“Custom”&#xff0c;点击“Next”​ …...

WangEditor快速实现版

WangEditor快速实现版 效果 案例代码 后端 package com.diy.springboot.controller;import cn.hutool.core.util.IdUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiImplicitParam; import org.sp…...

LeetCode Hot100刷题——反转链表(迭代+递归)

206.反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#…...

10.2 继承与多态

文章目录 继承多态 继承 继承的作用是代码复用。派生类自动获得基类的除私有成员外的一切。基类描述一般特性&#xff0c;派生类提供更丰富的属性和行为。在构造派生类时&#xff0c;其基类构造函数先被调用&#xff0c;然后是派生类构造函数。在析构时顺序刚好相反。 // 基类…...

java项目之基于ssm的智能训练管理平台(源码+文档)

项目简介 智能训练管理平台实现了以下功能&#xff1a; 系统可以提供信息显示和相应服务&#xff0c;其管理员增删改查课程信息和课程信息资料&#xff0c;审核课程信息预订订单&#xff0c;查看订单评价和评分&#xff0c;通过留言功能回复用户提问。 &#x1f495;&#x1…...

29-验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否则&#xf…...

(57)[HGAME 2023 week1]easyasm

nss&#xff1a;3477 [HGAME 2023 week1]easyasm 关于这个题吧&#xff0c;我还是和上一个题一样&#xff0c;我观察到了异或0x33 所以我就把result的结果跟0x33异或&#xff0c;然后我就就这样&#xff0c;做出来了...

FY-3D MWRI亮温绘制

1、FY-3D MWRI介绍 风云三号气象卫星&#xff08;FY-3&#xff09;是我国自行研制的第二代极轨气象卫星&#xff0c;其有效载荷覆 盖了紫外、可见光、红外、微波等频段&#xff0c;其目标是实现全球全天候、多光谱、三维定量 探测&#xff0c;为中期数值天气预报提供卫星观测数…...

Java集合面试题

引言 Java集合框架是Java编程中不可或缺的一部分&#xff0c;它提供了一系列用于存储和操作对象的接口和类。在Java面试中&#xff0c;集合框架的相关知识往往是必考的内容。本文将汇总一系列关于Java集合的面试题&#xff0c;帮助求职者更好地准备面试。 一、Java集合框架概…...

知识蒸馏综述Knowledge Distillation: A Survey解读

论文链接&#xff1a;Knowledge Distillation: A Survey 摘要&#xff1a;近年来&#xff0c;深度神经网络在工业界和学术界都取得了成功&#xff0c;尤其是在计算机视觉任务方面。深度学习的巨大成功主要归功于它能够扩展以对大规模数据进行编码&#xff0c;并且能够处理数十…...

ES映射知识

映射 映射类似于关系型数据库的Schema&#xff08;模式&#xff09;。 映射来定义字段列和存储的类型等基础信息。 {"mappings": {"properties": {"username": {"type": "keyword","ignore_above": 256 // 忽略…...

Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南

Spring Boot拦截器&#xff08;Interceptor&#xff09;与过滤器&#xff08;Filter&#xff09;深度解析&#xff1a;区别、实现与实战指南 一、核心概念对比 1. 本质区别 维度过滤器&#xff08;Filter&#xff09;拦截器&#xff08;Interceptor&#xff09;规范层级Serv…...

Debian二次开发一体化工作站:提升科研效率的智能工具

在科研领域&#xff0c;数据处理是实验成功的关键环节之一。随着实验数据的复杂性和规模不断增加&#xff0c;传统的数据处理方法已经难以满足科研人员的需求。这时&#xff0c;一体化工作站应运而生&#xff0c;成为科研实验数据处理的 “智能大脑”。 一体化工作站&#xff…...

swift-5-汇编分析闭包本质

一、枚举、结构体、类都定义方法 方法占用对象的内存么&#xff1f; 不占用 方法的本质就是函数 方法、函数都存放在代码段&#xff0c;因为方法都是公共的&#xff0c;不管 对象一还是对对象二调用都是一样的&#xff0c;所以放在代码段&#xff0c;但是每个对象的成员不一样所…...

Linux安装升级docker

Linux 安装升级docker Linux 安装升级docker背景升级停止docker服务备份原docker数据目录移除旧版本docker安装docker ce恢复数据目录启动docker参考 安装找到docker官网找到docker文档删除旧版本docker配置docker yum源参考官网继续安装docker设置开机自启配置加速测试 Linux …...

小程序事件系统 —— 33 事件传参 - data-*自定义数据

事件传参&#xff1a;在触发事件时&#xff0c;将一些数据作为参数传递给事件处理函数的过程&#xff0c;就是事件传参&#xff1b; 在微信小程序中&#xff0c;我们经常会在组件上添加一些自定义数据&#xff0c;然后在事件处理函数中获取这些自定义数据&#xff0c;从而完成…...

推荐一些免费开源支持Vue3甘特图组件

文章目录 前言一、dhtmlxGantt二、frappe-gantt三、vue-ganttastic四、gantt-elastic五、v-gantt六、vue-gantt-schedule-timeline-calendar七、vue-gantt八、总结 前言 在现代项目管理和任务调度中&#xff0c;甘特图是一种非常实用的工具。它能够直观地展示任务的时间安排、…...

Dify 本地部署教程

目录 一、下载安装包 二、修改配置 三、启动容器 四、访问 Dify 五、总结 本篇文章主要记录 Dify 本地部署过程&#xff0c;有问题欢迎交流~ 一、下载安装包 从 Github 仓库下载最新稳定版软件包&#xff0c;点击下载~&#xff0c;当然也可以克隆仓库或者从仓库里直接下…...

nlp培训重点-5

1. LoRA微调 loader&#xff1a; # -*- coding: utf-8 -*-import json import re import os import torch import numpy as np from torch.utils.data import Dataset, DataLoader from transformers import BertTokenizer """ 数据加载 """cl…...

XWiki使用war部署在tomcat9

xwiki部署 官方文档&#xff0c;比较详细。 https://www.xwiki.org/xwiki/bin/view/Documentation/AdminGuide/Installation/InstallationWAR/ xwiki是基于java的开源知识库&#xff0c;可以替代Confluence。有多种部署方式&#xff0c;本文使用war方式部署在tomca下&#x…...

CTA策略【量化理论】

CTA策略演变史 全称&#xff1a;Commodity Trading Advisor &#xff08;商品交易顾问&#xff09; CTA最开始是指通过为客户提供期权、期货方面的交易建议&#xff0c;或者直接通过受管理的期货账户参与实际交易&#xff0c;来获得收益的机构或个人。 随着市场的发展&#…...

旋转编码器原理与应用详解:从结构到实战 | 零基础入门STM32第四十七步

主题内容教学目的/扩展视频旋转编码器电路原理&#xff0c;跳线设置&#xff0c;结构分析。驱动程序与调用。熟悉电路和驱动程序。 师从洋桃电子&#xff0c;杜洋老师 &#x1f4d1;文章目录 一、旋转编码器是什么&#xff1f;二、内部结构揭秘2.1 机械组件解剖2.2 核心部件说明…...

计算机视觉cv2入门之图像的读取,显示,与保存

在计算机视觉领域&#xff0c;Python的cv2库是一个不可或缺的工具&#xff0c;它提供了丰富的图像处理功能。作为OpenCV的Python接口&#xff0c;cv2使得图像处理的实现变得简单而高效。 示例图片 目录 opencv获取方式 图像基本知识 颜色空间 RGB HSV 图像格式 BMP格式 …...

基于Canvas和和原生JS实现俄罗斯方块小游戏

这里是一个完整的H5俄罗斯方块游戏&#xff0c;使用了 HTML CSS JavaScript (原生) 实现&#xff0c;支持基本的俄罗斯方块玩法&#xff0c;如&#xff1a; ✅ 方块自动下落 ✅ 方向键控制移动、旋转、加速下落 ✅ 方块堆叠、消行 ✅ 计分系统 在 canvas 上绘制游戏&#x…...

阿里云 QwQ-32B 模型调研文档

阿里云 QwQ-32B 模型调研文档 ——技术解析、部署实践与微调指南 一、模型概述 QwQ-32B 是阿里云开源的轻量化大语言模型,以 320 亿参数 实现与 DeepSeek-R1(6710 亿参数)相当的推理性能。其核心优势包括: 参数效率:1/20 参数量达成竞品性能,显存需求降低 70%部署灵活性…...

【玩转23种Java设计模式】结构型模式篇:组合模式

软件设计模式&#xff08;Design pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...

Eolink:专为开发者设计的API协作平台

Eolink Apikit 是一款集 API 设计、管理、自动化测试、Mock 和异常监控于一体的全生命周期智能协作平台&#xff0c;旨在提升 API 研发和管理的效率。以下是对其功能和特点的详细介绍&#xff1a; 核心功能&#xff1a; API 设计与文档管理&#xff1a;Apikit 提供了强大的 API…...

【Python】为什么要写__init__.py

文章目录 PackageA(__init__特性)应该往__init__.py里放什么东西&#xff1f;1、包的初始化2、管理包的公共接口3、包的信息 正常我们直接导入就可以执行&#xff0c;但是在package的时候&#xff0c;有一种__init__.py的特殊存在 引入moduleA.py&#xff0c;执行main.py&…...

golang 从零单排 (一) 安装环境

1.下载安装 打开网址The Go Programming Language 直接点击下载go1.24.1.windows-amd64.msi 下载完成 直接双击下一步 下一步 安装完成 环境变量自动设置不必配置 2.验证 win r 输入cmd 打开命令行 输入go version...

30-判断子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#…...

AI 驱动的软件测试革命:从自动化到智能化的进阶之路

&#x1f680;引言&#xff1a;软件测试的智能化转型浪潮 在数字化转型加速的今天&#xff0c;软件产品的迭代速度与复杂度呈指数级增长。传统软件测试依赖人工编写用例、执行测试的模式&#xff0c;已难以应对快速交付与高质量要求的双重挑战。人工智能技术的突破为测试领域注…...

深度相机进行目标物体的空间姿态(位姿)估计

利用深度相机&#xff08;如Kinect、Intel Realsense、Zed相机等&#xff09;进行目标物体的空间姿态&#xff08;位姿&#xff09;估计&#xff0c;通常结合了3D点云处理、目标识别和位姿优化算法。以下是完整的实现流程、算法选择及注意事项&#xff1a; 一、实现流程 1. 目…...

3月8日实验

拓扑&#xff1a; 需求&#xff1a; 1.学校内部的HTTP客户端可以正常通过域名www.baidu.com访问到白度网络中的HTTP服务器 2.学校网络内部网段基于192.168.1.0/24划分&#xff0c;PC1可以正常访问3.3.3.0/24网段&#xff0c;但是PC2不允许 3.学校内部路由使用静态路由&#…...

GO语言学习笔记

一、viper笔记【七米】 https://liwenzhou.com/posts/Go/viper/ 二、优雅关机和平滑重启 https://liwenzhou.com/posts/Go/graceful-shutdown/ 三、gin使用zap https://liwenzhou.com/posts/Go/zap-in-gin/ 四、flag 用于命令行传参 https://liwenzhou.com/posts/Go/flag/ 五、…...

Autosar技术栈总目录

总目录 Autosar架构理解Autosar Mcal配置开发&#xff08;TC3xx系列 基于EB&#xff09;Autosar Mcal配置开发&#xff08;S32K3xx系列 基于EB&#xff09;Autosar BSW服务开发&#xff08;基于Davinci CFG &Dev&#xff09;Makefile编译自动化脚本 持续更新中… Autosar架…...

开发指南107-谷歌内核浏览器滚动条设置

平台上统一制定了滚动条样式(仅限于webkit内核)&#xff1a;/* ------美化谷歌浏览器滚动条 开始-----------*/ ::-webkit-scrollbar{width:12px;height:12px;background-color: #E1E1E1;} ::-webkit-scrollbar-button:single-button { background-color:#E1E1E1; display: …...