chore: rename multiple files to improve importability and module structure
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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
|
||||
|
||||
# LangSmith调试
|
||||
os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能
|
||||
os.environ["LANGCHAIN_PROJECT"] = "graph_as_tool" # 自定义项目名
|
||||
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"
|
||||
)
|
||||
|
||||
# 构建子工作流
|
||||
# 1.子任务状态
|
||||
class RetryState(TypedDict):
|
||||
query: str
|
||||
attempt: int
|
||||
result: str
|
||||
|
||||
# 2.子图逻辑 -- 模拟一个可能失败,需重试的API调用
|
||||
def call_unstable_api(state:RetryState):
|
||||
"""模拟偶发性的外部服务,偶发失败"""
|
||||
attempt = state["attempt"]
|
||||
if attempt == 1:
|
||||
# 第一次故意失败
|
||||
return {"result":"ERROR:服务暂时不可用","attempt":attempt+1}
|
||||
else:
|
||||
# 第二次成功
|
||||
return {"result":f"SUCCESS:成功处理请求:{state['query']}","attempt":attempt+1}
|
||||
|
||||
def should_retry(state:RetryState):
|
||||
if "ERROR" in state["result"] and state["attempt"] <= 2: # 出现报错且重试次数小于2,重连
|
||||
return "call_api"
|
||||
return END
|
||||
|
||||
# 3.构建子图工作流
|
||||
retry_workflow = StateGraph(RetryState)
|
||||
retry_workflow.add_node("call_api",call_unstable_api)
|
||||
retry_workflow.add_edge(START,"call_api")
|
||||
retry_workflow.add_conditional_edges(
|
||||
"call_api",
|
||||
should_retry,
|
||||
{"call_api":"call_api",END:END}
|
||||
)
|
||||
retry_app = retry_workflow.compile()
|
||||
|
||||
# 4.封装为tool(Graph-as-a-Tool)
|
||||
@tool
|
||||
def create_order(query:str) -> str:
|
||||
"""创建新订单,自动重试保障成功率"""
|
||||
result = retry_app.invoke({"query":query,"attempt":1,"result":""})
|
||||
return result["result"]
|
||||
|
||||
|
||||
# 5.主graph
|
||||
tools = [create_order]
|
||||
llm_with_tools = llm.bind_tools(tools)
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
def agent_node(state:MessagesState):
|
||||
response = llm_with_tools.invoke(state["messages"])
|
||||
return {"messages":[response]}
|
||||
|
||||
def should_continue(state:MessagesState):
|
||||
last_msg = state["messages"][-1]
|
||||
if hasattr(last_msg,"tool_calls") and last_msg.tool_calls:
|
||||
return "tools"
|
||||
return END
|
||||
|
||||
# 构建主工作流
|
||||
workflow = StateGraph(MessagesState)
|
||||
workflow.add_node("agent",agent_node)
|
||||
workflow.add_node("tools",tool_node)
|
||||
workflow.add_edge(START,"agent")
|
||||
workflow.add_conditional_edges(
|
||||
"agent",
|
||||
should_continue,
|
||||
{
|
||||
"tools": "tools",
|
||||
END: END
|
||||
}
|
||||
)
|
||||
workflow.add_edge("tools","agent")
|
||||
|
||||
app = workflow.compile()
|
||||
|
||||
|
||||
# 运行
|
||||
if __name__ == '__main__':
|
||||
user_input = "请创建一个新订单:购买三本书"
|
||||
print('用户输入:',user_input)
|
||||
|
||||
inputs = {"messages":[
|
||||
SystemMessage(content="你是一个任务执行助手。当用户提出任何需要处理、操作或执行的请求时,必须调用 create_order 工具来完成,不要自行回答细节"),
|
||||
HumanMessage(content=user_input)
|
||||
]}
|
||||
result = app.invoke(inputs)
|
||||
|
||||
tool_result = None
|
||||
# 在主工作流的消息历史中,查找最近的工具执行结果
|
||||
for msg in reversed(result["messages"]):
|
||||
if msg.type == "tool": # 找到ToolMessage类型消息
|
||||
tool_result = msg.content
|
||||
break
|
||||
if tool_result:
|
||||
print(f"\n✅ 直接获取子图返回值:\n{tool_result}")
|
||||
else:
|
||||
print("\n❌ 未执行任何工具")
|
||||
final_reply = result["messages"][-1]
|
||||
print(f'\n最终回复:\n{final_reply}')
|
||||
@@ -0,0 +1,118 @@
|
||||
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
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
# LangSmith调试
|
||||
os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能
|
||||
os.environ["LANGCHAIN_PROJECT"] = "human_approval" # 自定义项目名
|
||||
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 send_email(to, content):
|
||||
"""模拟发送邮件"""
|
||||
return f'邮件已发送至{to},内容为:{content}'
|
||||
|
||||
|
||||
# 工具绑定到llm
|
||||
tools = [send_email]
|
||||
llm_with_tools = llm.bind_tools(tools)
|
||||
|
||||
# Node函数与Edge节点
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = llm_with_tools.invoke(state['messages'])
|
||||
return {"messages":[response]}
|
||||
|
||||
|
||||
def should_continue(state: MessagesState):
|
||||
last_msg = state['messages'][-1]
|
||||
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
|
||||
return "tools"
|
||||
return END
|
||||
|
||||
|
||||
|
||||
# 构建基础ReAct图
|
||||
workflow = StateGraph(MessagesState)
|
||||
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("tools", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
workflow.add_conditional_edges(
|
||||
"agent",
|
||||
should_continue,
|
||||
{
|
||||
"tools": "tools",
|
||||
END: END
|
||||
}
|
||||
)
|
||||
|
||||
workflow.add_edge("tools", "agent")
|
||||
|
||||
app = workflow.compile(
|
||||
# 在内存里做状态持久化
|
||||
checkpointer=MemorySaver(),
|
||||
interrupt_before=["tools"] # 选择要人工审批的节点 -- 负责在哪里停,之后的代码负责停了之后怎么办
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config = {
|
||||
"configurable":{"thread_id":"user123"}
|
||||
}
|
||||
user_input = "请帮我给 boss@example.com 发一封邮件,内容是:会议推迟到明天下午3点。"
|
||||
|
||||
print("用户输入:",user_input)
|
||||
print("\nAgent正在思考...\n")
|
||||
|
||||
# 初识输入
|
||||
inputs = {"messages":[HumanMessage(content=user_input)]}
|
||||
|
||||
while 1:
|
||||
# 触发工作流执行,推进到下一个中断点或自然结束
|
||||
# inputs注入事件;config确定回话id;"values":完整记录每步结果
|
||||
for _ in app.stream(inputs,config,stream_mode="values"): # 流式执行
|
||||
pass # 必须迭代生成器,才能实际执行工作流
|
||||
|
||||
# 获取当前状态
|
||||
snapshot = app.get_state(config)
|
||||
next_tasks = snapshot.next # 返回下一步要执行的节点名列表
|
||||
|
||||
# 如果没有下一步,说明工作流已结束
|
||||
if not next_tasks:
|
||||
final_msg = snapshot.values['messages'][-1]
|
||||
print(f'\n最终回复:{final_msg.content}')
|
||||
break
|
||||
|
||||
# 如果下一步是需要审批的节点
|
||||
if "tools" in next_tasks:
|
||||
last_msg = snapshot.values['messages'][-1]
|
||||
tool_call = last_msg.tool_calls[0]
|
||||
print(f'\n⚠️ Agent准备执行操作:')
|
||||
print(f' 工具名称:{tool_call["name"]}')
|
||||
print(f' 参数:{tool_call["args"]}')
|
||||
|
||||
approval = input("\n✅ 是否批准执行?(输入 'yes' 继续,其他取消): ").strip().lower()
|
||||
if approval == "yes":
|
||||
print('\n 继续执行...')
|
||||
inputs = None # 表示从断点继续,无新输入
|
||||
else:
|
||||
print("\n❌ 操作已取消,流程终止")
|
||||
break
|
||||
@@ -0,0 +1,167 @@
|
||||
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, 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}")
|
||||
@@ -0,0 +1,226 @@
|
||||
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,START,END
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from typing import TypedDict,Annotated
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
# LangSmith调试
|
||||
os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能
|
||||
os.environ["LANGCHAIN_PROJECT"] = "supervisor_agent_ops_system" # 自定义项目名
|
||||
os.environ["LANGCHAIN_API_KEY"] = LANGCHAIN_API_KEY
|
||||
|
||||
llm = ChatOpenAI(
|
||||
model="deepseek-chat",
|
||||
api_key=OPENAI_API_KEY,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
|
||||
# === 一、Graph-as-a-Tool ===
|
||||
# === 模拟一个不稳定的SSH日志查询过程 ===
|
||||
|
||||
class SSHState(TypedDict):
|
||||
target_ip: str
|
||||
attempt: int
|
||||
logs: str
|
||||
|
||||
def connect_ssh(state:SSHState):
|
||||
"""模拟SSH连接,第一次连接必定超时"""
|
||||
print(f' [子图]正在尝试连接服务器{state["target_ip"]}(第{state["attempt"]}次)')
|
||||
if state['attempt'] == 1:
|
||||
return {'logs':'ERROR:Connection Timed Out','attempt':state['attempt']+1}
|
||||
return {'logs':'CONNECTED','attempt':state['attempt']+1}
|
||||
|
||||
def grep_system_logs(state:SSHState):
|
||||
"""连接成功后读取日志"""
|
||||
if state["logs"] == "CONNECTED":
|
||||
# 打印查到的结果
|
||||
return {'logs':f'SUCCESS: Retrieved logs from {state["target_ip"]}:[ERROR: OutOfMemory at line 4032]'}
|
||||
return {'logs':state['logs']} # 保持错误状态
|
||||
|
||||
def ssh_routing(state:SSHState):
|
||||
"""路由逻辑:如果连接失败且尝试次数少于3,重试"""
|
||||
if "ERROR" in state['logs'] and state["attempt"] <= 2:
|
||||
return "connect"
|
||||
return "grep"
|
||||
|
||||
# 构建子图
|
||||
ssh_workflow = StateGraph(SSHState)
|
||||
ssh_workflow.add_node("connect",connect_ssh)
|
||||
ssh_workflow.add_node("grep",grep_system_logs)
|
||||
|
||||
ssh_workflow.add_edge(START,"connect")
|
||||
ssh_workflow.add_conditional_edges("connect",ssh_routing,{"connect":"connect","grep":"grep"})
|
||||
ssh_workflow.add_edge("grep",END)
|
||||
|
||||
ssh_app = ssh_workflow.compile()
|
||||
|
||||
# 将子图封装为工具
|
||||
@tool
|
||||
def analyze_server_logs(ip_address:str):
|
||||
"""使用SSH连接服务器并分析最近的错误日志(内含自动重连机制)"""
|
||||
result = ssh_app.invoke({"target_ip":ip_address,"attempt":1,"log":""})
|
||||
return result['logs']
|
||||
|
||||
|
||||
|
||||
# === 二、Human-in-the-Loop ===
|
||||
# === 重启服务,高危操作,需要审批 ===
|
||||
|
||||
@tool
|
||||
def restart_service(service_name:str):
|
||||
"""重启指定的服务器服务"""
|
||||
return f"服务[{service_name}]已成功重启,系统负载已恢复正常"
|
||||
|
||||
|
||||
|
||||
# === 三、Multi-Agent 编排 ===
|
||||
# === 总控调度 + 专家分工 ===
|
||||
|
||||
# 1. 共享状态
|
||||
class AgentState(TypedDict):
|
||||
messages:Annotated[list,add_messages]
|
||||
next_speaker:str
|
||||
|
||||
# 2. 专家节点
|
||||
def log_expert(state:AgentState):
|
||||
prompt = "你是日志分析专家,使用工具分析服务器日志,找出报错原因。回答需简洁。"
|
||||
messages = [SystemMessage(content=prompt)] + state['messages']
|
||||
# 绑定子图工具
|
||||
tools = [analyze_server_logs]
|
||||
response = llm.bind_tools(tools).invoke(messages)
|
||||
return {"messages":[response]}
|
||||
|
||||
def ops_expert(state:AgentState):
|
||||
prompt = "你是运维专家。当收到修复指令时,请立即调用 'restart_service' 工具进行修复,不要输出任何额外的解释文本。"
|
||||
messages = [SystemMessage(content=prompt)] + state['messages']
|
||||
tools = [restart_service]
|
||||
# 绑定敏感工具
|
||||
response = llm.bind_tools(tools).invoke(messages)
|
||||
return {"messages":[response]}
|
||||
|
||||
# 3. 总控节点(supervisor)
|
||||
def supervisor(state:AgentState):
|
||||
prompt = """
|
||||
你是 IT 运维总指挥。
|
||||
专家列表:
|
||||
- log_expert
|
||||
- ops_expert
|
||||
|
||||
决策逻辑:
|
||||
1. 未知原因 -> log_expert
|
||||
2. 已知原因(如OOM、报错) -> ops_expert
|
||||
3. 修复完成 -> FINISH
|
||||
|
||||
【输出约束】
|
||||
仅输出下一个专家的名字(如 log_expert),不要包含任何其他字符或标点。
|
||||
"""
|
||||
messages = [SystemMessage(content=prompt)] + state['messages']
|
||||
response = llm.invoke(messages)
|
||||
return {"next_speaker":response.content.strip()}
|
||||
|
||||
# 4. 路由逻辑
|
||||
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):
|
||||
return state["next_speaker"]
|
||||
|
||||
|
||||
# === 四、构建主图与集成 ===
|
||||
|
||||
# 工具集合
|
||||
all_tools = [analyze_server_logs,restart_service]
|
||||
tool_node = ToolNode(all_tools)
|
||||
|
||||
# 构建主图
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
workflow.add_node("supervisor",supervisor)
|
||||
workflow.add_node("log_expert",log_expert)
|
||||
workflow.add_node("ops_expert",ops_expert)
|
||||
workflow.add_node("tools",tool_node)
|
||||
|
||||
workflow.add_edge(START,"supervisor")
|
||||
workflow.add_conditional_edges("supervisor",route_supervisor)
|
||||
for member in ["log_expert","ops_expert"]:
|
||||
workflow.add_conditional_edges(
|
||||
member,
|
||||
should_continue,
|
||||
{"tools":"tools","supervisor":"supervisor"}
|
||||
)
|
||||
workflow.add_conditional_edges("tools",route_after_tool)
|
||||
|
||||
# 编译图:加入记忆与中断机制(Human-in-the-Loop)
|
||||
# 注: 我们在所有工具执行前都暂停,但在运行时进行逻辑判断
|
||||
app = workflow.compile(
|
||||
checkpointer=MemorySaver(),
|
||||
interrupt_before=["tools"]
|
||||
)
|
||||
|
||||
# === 五、运行时逻辑(模拟生产环境的交互) ===
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 模拟一次完整的故障处理流程
|
||||
user_input = "服务器 192.168.1.100 报警,响应极慢,请处理。"
|
||||
config = {
|
||||
"configurable": {"thread_id": "incident_001"}
|
||||
}
|
||||
|
||||
print(f'收到报警 : {user_input}')
|
||||
inputs = {"messages":[HumanMessage(content=user_input)]}
|
||||
|
||||
# 循环执行,直到任务结束
|
||||
while 1:
|
||||
# 1. 执行图直到中断或结束
|
||||
for _ in app.stream(inputs,config,stream_mode="values"):
|
||||
pass
|
||||
|
||||
# 2. 检查当前状态
|
||||
snapshot = app.get_state(config)
|
||||
next_tasks = snapshot.next
|
||||
|
||||
# 没有下一步,任务结束
|
||||
if not next_tasks:
|
||||
print(f" 最终报告:{snapshot.values['messages'][-1].content}")
|
||||
break
|
||||
|
||||
# 3. 处理中断:判断是哪个工具被调用
|
||||
if "tools" in next_tasks:
|
||||
last_msg = snapshot.values['messages'][-1]
|
||||
tool_call = last_msg.tool_calls[0]
|
||||
tool_name = tool_call["name"]
|
||||
|
||||
print(f'\n[系统暂停] 请求调用工具:{tool_name}')
|
||||
|
||||
# 策略A: 自动放行安全工具(Graph-as-a-Tool)
|
||||
if tool_name == "analyze_server_logs":
|
||||
print(' -> 这是一个查询类工具,系统自行批准。')
|
||||
inputs = None # 继续执行
|
||||
continue
|
||||
|
||||
# 策略B: 拦截高危工具(Human-in-the-Loop)
|
||||
elif tool_name == "restart_service":
|
||||
print(" -> ⚠️ 警告: 这是一个高危操作!")
|
||||
user_approval = input(" -> 请人工审批 (输入 'yes' 允许重启):")
|
||||
|
||||
if user_approval == "yes":
|
||||
print(' -> ✅ 审批通过,正在执行...')
|
||||
inputs = None # 继续执行
|
||||
else:
|
||||
print(' -> ❌️ 审批拒绝,任务终止!')
|
||||
# 实际系统中,应通过 ToolMessage 反馈人工拒绝,使 LLM 能继续响应;
|
||||
# 当前demo为简化,直接退出
|
||||
break
|
||||
Reference in New Issue
Block a user