Files
agent-craft/09_langgraph_advanced/03_multi_agent_orchestration.py
T

167 lines
6.2 KiB
Python

import os
from config import OPENAI_API_KEY,LANGCHAIN_API_KEY
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode
from typing import TypedDict # 定义数据类型
from typing import Annotated # 注释说明细节
from langgraph.graph.message import add_messages
# LangSmith调试
os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能
os.environ["LANGCHAIN_PROJECT"] = "multi_agent_orchestration" # 自定义项目名
os.environ["LANGCHAIN_API_KEY"] = LANGCHAIN_API_KEY
# llm配置
llm = ChatOpenAI(
model="deepseek-chat",
api_key=OPENAI_API_KEY,
base_url="https://api.deepseek.com"
)
# 模拟工具
@tool
def search_internal_docs(query:str):
"""搜索公司内部文档获取政策信息"""
return "根据公司手册,年假为15天"
@tool
def search_web(query:str):
"""通过搜索引擎获取最新的公开信息"""
return "据TechCrunch报道,LangGraph 0.6已支持持久化记忆"
@tool
def generate_code(requirement:str):
"""根据需求生成可运行的Python代码"""
return "python\nprint('Hello from Code writer!')"
# 共享状态定义
class AgentState(TypedDict):
messages: Annotated[list,add_messages] # 自动累积对话历史
next_speaker: str
# 专家节点
def rag_expert(state:AgentState):
prompt = "你是公司知识库专家,只基于内部文档回答问题。回答应简洁明了,直接给出最终结论。请在回答的最后一行加上:(任务已完成)"
messages = [SystemMessage(content=prompt)]+state['messages']
tools = [search_internal_docs]
response = llm.bind_tools(tools).invoke(messages)
return {'messages':[response]}
def web_research(state:AgentState):
prompt = "你是互联网研究员,擅长用搜索引擎获取最新公开信息。回答应简洁明了,直接给出最终结论。请在回答的最后一行加上:(任务已完成)"
messages = [SystemMessage(content=prompt)]+state['messages']
tools = [search_web]
response = llm.bind_tools(tools).invoke(messages)
return {'messages':[response]}
def code_writer(state:AgentState):
prompt = "你是python工程师,只生成可运行代码,不解释。回答应简洁明了,直接给出最终结论。请在回答的最后一行加上:(任务已完成)"
messages = [SystemMessage(content=prompt)]+state['messages']
tools = [generate_code]
response = llm.bind_tools(tools).invoke(messages)
return {'messages':[response]}
# 总控节点
def supervisor(state:AgentState):
supervisor_prompt = """
你是一个任务协调员。你的目标是管理专家来解决用户的问题。
当前对话需要以下专家参与:
- rag_expert:涉及公司政策、内部流程
- web_research:涉及外部新闻、公开数据
- code_writer:需要生成代码
【决策逻辑】
1. **检查历史记录**:先看上一个回复是否已经完整回答了用户的初始问题。
2. **如果已经回答完毕**:必须输出 'FINISH'。
3. **如果尚未回答或需要补充**:根据当前缺少的步骤,选择下一个最合适的专家。
请只输出专家名字或 'FINISH',不要输出任何其他解释。
"""
messages = [SystemMessage(content=supervisor_prompt)]+state['messages']
response = llm.invoke(messages)
next_speaker = response.content.strip()
return {"next_speaker":next_speaker}
# 路由函数定义
def route_supervisor(state:AgentState):
if state["next_speaker"]=="FINISH":
return END
return state["next_speaker"]
def should_continue(state:AgentState):
last_msg = state["messages"][-1]
if hasattr(last_msg,"tool_calls") and last_msg.tool_calls:
return "tools"
return "supervisor"
def route_after_tool(state:AgentState):
# 工具执行完后,通过next_speaker知道是谁调用的,路由回去
return state["next_speaker"]
# 添加工具节点
tools = [search_internal_docs,search_web,generate_code]
tool_node = ToolNode(tools)
# 构建协作图
workflow = StateGraph(AgentState)
# 1. 添加节点
workflow.add_node("supervisor",supervisor)
workflow.add_node("rag_expert",rag_expert)
workflow.add_node("web_research",web_research)
workflow.add_node("code_writer",code_writer)
workflow.add_node("tools",tool_node)
# 2. 总控回路
workflow.add_edge(START,"supervisor")
workflow.add_conditional_edges("supervisor",route_supervisor)
# 3. 专家节点的ReAct循环
for member in ["rag_expert","web_research","code_writer"]: # 为每个专家添加条件边:决定是去执行工具还是回总控
workflow.add_conditional_edges(
member,
should_continue,
{"tools":"tools","supervisor":"supervisor"}
)
# 4.工具节点闭环
workflow.add_conditional_edges( # 工具执行完,根据next_speaker路由回原来的专家
"tools",
route_after_tool
)
app = workflow.compile()
# 测试运行
if __name__ == '__main__':
user_input = "公司年假多少天"
print("用户提问:",user_input)
print('\n开始多智能体协作...\n')
inputs = {"messages":[HumanMessage(content=user_input)]}
# app是编译好的图,stream()会让图开始运转,并返回一个生成器
# 图每执行完一个节点,就会产出一个step字典
for step in app.stream(inputs):
# 因为step是个字典,所以需要拆包拿到 节点名(Node) 与 输出内容(output)
for node,output in step.items():
# 有工具/专家回复
if "messages" in output:
msg = output["messages"][-1]
if hasattr(msg,"tool_calls") and msg.tool_calls:
call = msg.tool_calls[0]
print(f"【{node}】调用工具 {call['name']}({call['args']})")
else:
print(f"【{node}】回复:{msg.content}")
# supervisor刚做完决策,确定下个发言人
elif "next_speaker" in output:
speaker = output["next_speaker"]
print(f"【Supervisor】指定下一位发言人:{speaker}")