mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-09-03 00:25:17 +03:00
- Move AI chat API design document to gns3-copilot directory - Add comprehensive HITL implementation plan with architecture and workflow - Introduce human-in-the-loop confirmation for dangerous configuration commands - Include state management, API endpoints, and frontend integration details - Provide testing plan and deployment steps for the new feature
1403 lines
40 KiB
Markdown
1403 lines
40 KiB
Markdown
# GNS3 Copilot HITL 功能实现方案
|
||
|
||
## 概述
|
||
|
||
本文档详细描述了 GNS3 Copilot 中 Human-in-the-Loop (HITL) 功能的完整实现方案。HITL 功能允许在执行敏感操作(如设备配置)前要求用户确认,提高系统安全性。
|
||
|
||
## 目标
|
||
|
||
- 在执行配置工具前要求用户确认
|
||
- 支持单个和批量工具确认
|
||
- 提供清晰的工具执行预览
|
||
- 保持现有功能完全兼容
|
||
|
||
## 核心设计
|
||
|
||
### 流程图
|
||
|
||
```
|
||
用户消息 → LLM → 工具调用决策
|
||
↓
|
||
检测是否需要确认
|
||
↙ ↘
|
||
需要 HITL 直接执行
|
||
↓ ↓
|
||
暂停,等待前端 执行工具
|
||
↓ ↓
|
||
用户确认/拒绝 返回结果
|
||
↓
|
||
执行已确认的工具
|
||
↓
|
||
返回结果
|
||
```
|
||
|
||
### 架构分层
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Frontend (Web UI) │
|
||
│ - 显示确认对话框 │
|
||
│ - 列出待确认的工具 │
|
||
│ - 处理用户确认/拒绝操作 │
|
||
└─────────────────────────────────────────┘
|
||
↕ SSE/HTTP
|
||
┌─────────────────────────────────────────┐
|
||
│ API Layer (FastAPI) │
|
||
│ - /hitl/status: 获取待确认工具 │
|
||
│ - /hitl/confirm: 确认执行 │
|
||
│ - /hitl/reject: 拒绝执行 │
|
||
└─────────────────────────────────────────┘
|
||
↕
|
||
┌─────────────────────────────────────────┐
|
||
│ AgentService (状态管理) │
|
||
│ - 管理 HITL 状态 │
|
||
│ - 处理确认/拒绝逻辑 │
|
||
│ - checkpoint 持久化 │
|
||
└─────────────────────────────────────────┘
|
||
↕
|
||
┌─────────────────────────────────────────┐
|
||
│ LangGraph Agent (工作流) │
|
||
│ - llm_call: LLM 调用 │
|
||
│ - check_hitl: 检查是否需要确认 │
|
||
│ - hitl_confirmation: 等待确认 │
|
||
│ - conditional_execution: 条件执行 │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
## 实现改动
|
||
|
||
### 1. LangGraph Agent 层
|
||
|
||
#### 文件:`gns3server/agent/gns3_copilot/agent/gns3_copilot.py`
|
||
|
||
##### 改动 1.1:扩展 MessagesState
|
||
|
||
**位置**:第 98-124 行
|
||
|
||
```python
|
||
# 扩展状态定义,添加 HITL 相关字段
|
||
class MessagesState(TypedDict):
|
||
"""GNS3-Copilot 对话状态管理类"""
|
||
|
||
messages: Annotated[list[AnyMessage], operator.add]
|
||
llm_calls: int
|
||
remaining_steps: RemainingSteps
|
||
conversation_title: str | None
|
||
topology_info: dict | None
|
||
|
||
# 新增:HITL 相关字段
|
||
pending_tool_calls: list[dict] # 等待确认的工具调用列表
|
||
hitl_confirmation_required: bool # 是否需要用户确认
|
||
hitl_session_id: str | None # HITL 会话唯一标识
|
||
confirmed_tool_calls: list[dict] # 用户已确认的工具调用
|
||
rejected_tool_calls: list[dict] # 用户拒绝的工具调用
|
||
```
|
||
|
||
**改动影响**:
|
||
- checkpoint 数据库中新增 5 个字段
|
||
- 现有状态读取代码需要兼容新字段
|
||
|
||
##### 改动 1.2:添加 HITL 检测节点
|
||
|
||
**位置**:在 `generate_title` 函数后添加(约第 310 行)
|
||
|
||
```python
|
||
# 需要确认的工具列表
|
||
HITL_TOOLS = {
|
||
"execute_multiple_device_config_commands",
|
||
# 可根据需要添加:
|
||
# "delete_node",
|
||
# "start_gns3_node",
|
||
}
|
||
|
||
DANGEROUS_PATTERNS = [
|
||
"reload", "reboot", "write erase", "erase startup-config",
|
||
"factory reset", "format", "delete", "no ip routing"
|
||
]
|
||
|
||
|
||
def _is_dangerous_config(tool_name: str, tool_args: dict) -> bool:
|
||
"""检测配置是否包含危险命令"""
|
||
if tool_name == "execute_multiple_device_config_commands":
|
||
device_configs = tool_args.get("device_configs", [])
|
||
for device in device_configs:
|
||
commands = device.get("config_commands", [])
|
||
for cmd in commands:
|
||
cmd_lower = cmd.lower()
|
||
if any(pattern in cmd_lower for pattern in DANGEROUS_PATTERNS):
|
||
return True
|
||
return False
|
||
|
||
|
||
def check_hitl_requirement(state: MessagesState) -> dict:
|
||
"""
|
||
检查工具调用是否需要用户确认 (HITL)
|
||
|
||
Returns:
|
||
dict: 包含 pending_tool_calls 和 hitl_confirmation_required 的状态更新
|
||
"""
|
||
last_message = state["messages"][-1]
|
||
|
||
# 如果最后一条消息没有工具调用,直接返回
|
||
if not hasattr(last_message, 'tool_calls') or not last_message.tool_calls:
|
||
return {"hitl_confirmation_required": False}
|
||
|
||
pending_tools = []
|
||
|
||
for tool_call in last_message.tool_calls:
|
||
tool_name = tool_call["name"]
|
||
tool_args = tool_call["args"]
|
||
|
||
# 只处理需要 HITL 的工具
|
||
if tool_name in HITL_TOOLS:
|
||
# 检查是否为危险操作
|
||
is_dangerous = _is_dangerous_config(tool_name, tool_args)
|
||
|
||
tool_info = {
|
||
"tool_call_id": tool_call["id"],
|
||
"tool_name": tool_name,
|
||
"tool_args": tool_args,
|
||
"danger_level": "high" if is_dangerous else "medium"
|
||
}
|
||
|
||
# 添加描述信息
|
||
if tool_name == "execute_multiple_device_config_commands":
|
||
device_count = len(tool_args.get("device_configs", []))
|
||
tool_info["description"] = f"配置 {device_count} 个设备"
|
||
|
||
pending_tools.append(tool_info)
|
||
logger.info("HITL: Tool '%s' requires confirmation (danger_level=%s)",
|
||
tool_name, tool_info["danger_level"])
|
||
|
||
if pending_tools:
|
||
logger.info("HITL: %d tools require confirmation", len(pending_tools))
|
||
return {
|
||
"pending_tool_calls": pending_tools,
|
||
"hitl_confirmation_required": True,
|
||
"hitl_session_id": str(uuid4())
|
||
}
|
||
|
||
return {"hitl_confirmation_required": False}
|
||
```
|
||
|
||
##### 改动 1.3:修改 should_continue 路由
|
||
|
||
**位置**:第 337-370 行
|
||
|
||
```python
|
||
def should_continue(
|
||
state: MessagesState,
|
||
) -> Literal["conditional_tool_execution", "hitl_confirmation", "title_generator_node", END]:
|
||
"""
|
||
LLM 响应后的路由决策
|
||
|
||
Returns:
|
||
Literal: 路由到下一个节点
|
||
"""
|
||
last_message = state["messages"][-1]
|
||
current_title = state.get("conversation_title")
|
||
|
||
# LLM 请求工具调用
|
||
if last_message.tool_calls:
|
||
# 检查是否需要 HITL 确认
|
||
if state.get("hitl_confirmation_required"):
|
||
logger.info("Routing to hitl_confirmation node")
|
||
return "hitl_confirmation"
|
||
|
||
logger.info("Routing to conditional_tool_execution node")
|
||
return "conditional_tool_execution"
|
||
|
||
# 首次交互完成,生成标题
|
||
if current_title in [None, "New Conversation"]:
|
||
return "title_generator_node"
|
||
|
||
return END
|
||
```
|
||
|
||
**关键变更**:
|
||
- 原路由 `"tool_node"` 改为 `"conditional_tool_execution"`
|
||
- 新增 `"hitl_confirmation"` 路由
|
||
- 路由名称变更,需要同步更新所有引用
|
||
|
||
##### 改动 1.4:添加 HITL 确认等待节点
|
||
|
||
**位置**:在 `should_continue` 函数后添加
|
||
|
||
```python
|
||
def hitl_confirmation_node(state: MessagesState) -> dict:
|
||
"""
|
||
HITL 确认节点 - 暂停执行,等待用户确认
|
||
|
||
这是一个特殊的节点,它不执行任何操作,只是保持状态。
|
||
实际的确认流程由 API 层处理。
|
||
|
||
工作原理:
|
||
1. 节点被调用时,状态中包含 pending_tool_calls
|
||
2. LangGraph 保存状态到 checkpoint
|
||
3. 执行流程暂停,等待外部状态更新
|
||
4. 用户通过 API 确认/拒绝后,状态被更新
|
||
5. 从 checkpoint 恢复并继续执行
|
||
|
||
Returns:
|
||
dict: 空字典,保持状态不变
|
||
"""
|
||
logger.info("HITL: Pausing for user confirmation (session_id=%s)",
|
||
state.get("hitl_session_id"))
|
||
|
||
# 返回空字典,保持状态不变
|
||
# 状态将在用户确认后通过 API 更新
|
||
return {}
|
||
```
|
||
|
||
##### 改动 1.5:添加条件执行节点
|
||
|
||
**位置**:在 `hitl_confirmation_node` 函数后添加
|
||
|
||
```python
|
||
def conditional_tool_execution(state: MessagesState) -> dict:
|
||
"""
|
||
条件工具执行节点 - 根据用户确认决定是否执行工具
|
||
|
||
这个节点替代原来的 tool_node,增加了:
|
||
1. 只执行用户确认的工具
|
||
2. 处理用户拒绝的工具
|
||
3. 更新 HITL 状态
|
||
|
||
Returns:
|
||
dict: 包含执行结果和状态更新的字典
|
||
"""
|
||
confirmed_calls = state.get("confirmed_tool_calls", [])
|
||
rejected_calls = state.get("rejected_tool_calls", [])
|
||
|
||
logger.info("HITL: Executing %d confirmed tools, %d rejected",
|
||
len(confirmed_calls), len(rejected_calls))
|
||
|
||
# 处理用户拒绝的工具
|
||
if rejected_calls:
|
||
rejected_names = [c["tool_name"] for c in rejected_calls]
|
||
rejection_msg = (
|
||
f"用户拒绝了以下操作: {', '.join(rejected_names)}。\n"
|
||
f"请提供替代方案或解释不执行这些操作的原因。"
|
||
)
|
||
|
||
# 添加系统消息通知 LLM
|
||
return {
|
||
"messages": [SystemMessage(content=rejection_msg)],
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False,
|
||
"confirmed_tool_calls": [],
|
||
"rejected_tool_calls": []
|
||
}
|
||
|
||
# 执行已确认的工具
|
||
if not confirmed_calls:
|
||
logger.warning("HITL: No confirmed tools to execute")
|
||
return {
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False
|
||
}
|
||
|
||
results = []
|
||
for tool_call_dict in confirmed_calls:
|
||
tool_call_id = tool_call_dict["tool_call_id"]
|
||
tool_name = tool_call_dict["tool_name"]
|
||
tool_args = tool_call_dict["tool_args"]
|
||
|
||
logger.info("Executing confirmed tool: %s", tool_name)
|
||
|
||
tool = tools_by_name[tool_name]
|
||
try:
|
||
observation = tool.invoke(tool_args)
|
||
results.append(ToolMessage(
|
||
content=observation,
|
||
tool_call_id=tool_call_id,
|
||
name=tool_name
|
||
))
|
||
logger.debug("Tool %s completed successfully", tool_name)
|
||
except Exception as e:
|
||
logger.error("Tool %s failed: %s", tool_name, e, exc_info=True)
|
||
results.append(ToolMessage(
|
||
content=f"Error: {str(e)}",
|
||
tool_call_id=tool_call_id,
|
||
name=tool_name
|
||
))
|
||
|
||
# 清理 HITL 状态
|
||
return {
|
||
"messages": results,
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False,
|
||
"confirmed_tool_calls": [],
|
||
"rejected_tool_calls": []
|
||
}
|
||
```
|
||
|
||
**关键变更**:
|
||
- 替代原 `tool_node` 函数
|
||
- 增加确认逻辑处理
|
||
- 支持部分确认、部分拒绝
|
||
|
||
##### 改动 1.6:更新 LangGraph 构建流程
|
||
|
||
**位置**:第 393-427 行
|
||
|
||
```python
|
||
# 构建工作流
|
||
agent_builder = StateGraph(MessagesState)
|
||
|
||
# 添加节点
|
||
agent_builder.add_node("llm_call", llm_call)
|
||
agent_builder.add_node("hitl_confirmation", hitl_confirmation_node)
|
||
agent_builder.add_node("conditional_tool_execution", conditional_tool_execution)
|
||
agent_builder.add_node("title_generator_node", generate_title)
|
||
|
||
# 添加边:START → llm_call
|
||
agent_builder.add_edge(START, "llm_call")
|
||
|
||
# 添加边:LLM 后的条件路由
|
||
agent_builder.add_conditional_edges(
|
||
"llm_call",
|
||
should_continue,
|
||
{
|
||
"hitl_confirmation": "hitl_confirmation", # 需要 HITL 确认
|
||
"conditional_tool_execution": "conditional_tool_execution", # 直接执行
|
||
"title_generator_node": "title_generator_node", # 生成标题
|
||
END: END # 结束对话
|
||
},
|
||
)
|
||
|
||
# HITL 确认节点是特殊的,它需要等待外部状态更新
|
||
# 状态更新后,下一轮调用将从 checkpoint 恢复并继续
|
||
# 这个循环通过 API 触发新的 graph.ainvoke() 调用完成
|
||
|
||
# 添加边:条件执行后继续 LLM 调用
|
||
agent_builder.add_conditional_edges(
|
||
"conditional_tool_execution",
|
||
recursion_limit_continue,
|
||
{
|
||
"llm_call": "llm_call",
|
||
END: END
|
||
},
|
||
)
|
||
|
||
# 添加边:标题生成后结束
|
||
agent_builder.add_edge("title_generator_node", END)
|
||
```
|
||
|
||
**工作流图**:
|
||
```
|
||
START → llm_call → should_continue
|
||
↓
|
||
┌────────────┼────────────┐
|
||
↓ ↓ ↓
|
||
hitl_confirmation conditional title_generator
|
||
(等待 API) _execution ↓
|
||
└────────────┴────────────→ END
|
||
```
|
||
|
||
#### 文件:`gns3server/agent/gns3_copilot/agent_service.py`
|
||
|
||
##### 改动 2.1:添加 HITL 事件处理
|
||
|
||
**位置**:第 333-376 行的 `_convert_event_to_chunk` 函数
|
||
|
||
```python
|
||
def _convert_event_to_chunk(self, event: Dict[str, Any], session_id: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
转换 LangGraph 事件为 API 响应块
|
||
|
||
支持 HITL 事件类型
|
||
"""
|
||
event_type = event.get("event", "")
|
||
data = event.get("data", {})
|
||
|
||
if event_type == "on_chat_model_stream":
|
||
chunk = data.get("chunk", {})
|
||
content = getattr(chunk, "content", "")
|
||
if content:
|
||
return {"type": "content", "content": content}
|
||
|
||
elif event_type == "on_tool_start":
|
||
return {
|
||
"type": "tool_start",
|
||
"tool_name": event.get("name", ""),
|
||
"session_id": session_id
|
||
}
|
||
|
||
elif event_type == "on_tool_end":
|
||
output = data.get("output", "")
|
||
if not isinstance(output, str):
|
||
output = str(output)
|
||
return {
|
||
"type": "tool_end",
|
||
"tool_name": event.get("name", ""),
|
||
"tool_output": output,
|
||
"session_id": session_id
|
||
}
|
||
|
||
# 新增:HITL 确认要求事件
|
||
elif event_type == "hitl_required":
|
||
return {
|
||
"type": "hitl_required",
|
||
"pending_tools": data.get("pending_tool_calls", []),
|
||
"hitl_session_id": data.get("hitl_session_id"),
|
||
"timeout": 300, # 5分钟超时
|
||
"session_id": session_id
|
||
}
|
||
|
||
return None
|
||
```
|
||
|
||
**注意**:实际实现中,HITL 事件不是通过 LangGraph 的 `astream_events` 触发的,而是通过状态查询实现的。因此,此函数主要用于处理工具执行事件。
|
||
|
||
---
|
||
|
||
### 2. API 层
|
||
|
||
#### 文件:`gns3server/api/routes/controller/chat.py`
|
||
|
||
##### 改动 2.1:添加 HITL 端点
|
||
|
||
**位置**:在文件末尾添加(约第 325 行后)
|
||
|
||
```python
|
||
from gns3server import schemas
|
||
from typing import List
|
||
|
||
|
||
# =============================================================================
|
||
# HITL (Human-in-the-Loop) 端点
|
||
# =============================================================================
|
||
|
||
@router.get(
|
||
"/sessions/{session_id}/hitl-status",
|
||
response_model=schemas.HITLStatusResponse,
|
||
summary="获取 HITL 状态",
|
||
description="获取当前会话中待确认的工具列表"
|
||
)
|
||
async def get_hitl_status(
|
||
session_id: str,
|
||
project: Project = Depends(dep_project),
|
||
current_user: schemas.User = Depends(get_current_active_user),
|
||
) -> schemas.HITLStatusResponse:
|
||
"""
|
||
获取 HITL 状态
|
||
|
||
返回当前会话中等待用户确认的工具调用列表。
|
||
前端应定期轮询此端点以检查是否有新的待确认工具。
|
||
"""
|
||
if project.status != "opened":
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail=f"Project must be opened. Current status: {project.status}"
|
||
)
|
||
|
||
agent_manager = await get_project_agent_manager()
|
||
agent_service = await agent_manager.get_agent(str(project.id), project.path)
|
||
|
||
# 从 checkpoint 获取状态
|
||
config = {"configurable": {"thread_id": session_id}}
|
||
state = await agent_service._graph.aget_state(config)
|
||
|
||
if not state or not state.values:
|
||
return schemas.HITLStatusResponse(
|
||
status="idle",
|
||
pending_tools=[],
|
||
hitl_session_id=None,
|
||
session_id=session_id
|
||
)
|
||
|
||
values = state.values
|
||
pending_tools = values.get("pending_tool_calls", [])
|
||
hitl_session_id = values.get("hitl_session_id")
|
||
|
||
# 转换为 Schema 格式
|
||
pending_tool_schemas = []
|
||
for tool in pending_tools:
|
||
pending_tool_schemas.append(schemas.PendingTool(
|
||
tool_call_id=tool["tool_call_id"],
|
||
tool_name=tool["tool_name"],
|
||
tool_args=tool["tool_args"],
|
||
danger_level=tool.get("danger_level", "medium"),
|
||
description=tool.get("description", "")
|
||
))
|
||
|
||
return schemas.HITLStatusResponse(
|
||
status="waiting" if pending_tool_schemas else "idle",
|
||
pending_tools=pending_tool_schemas,
|
||
hitl_session_id=hitl_session_id,
|
||
session_id=session_id
|
||
)
|
||
|
||
|
||
@router.post(
|
||
"/sessions/{session_id}/hitl/confirm",
|
||
response_model=schemas.HITLConfirmationResponse,
|
||
summary="确认执行工具",
|
||
description="用户确认执行一个或多个待确认的工具"
|
||
)
|
||
async def confirm_tool_execution(
|
||
session_id: str,
|
||
request: schemas.HITLConfirmationRequest,
|
||
project: Project = Depends(dep_project),
|
||
current_user: schemas.User = Depends(get_current_active_user),
|
||
) -> schemas.HITLConfirmationResponse:
|
||
"""
|
||
确认执行工具
|
||
|
||
用户可以选择:
|
||
- confirm_all=true: 确认所有待确认的工具
|
||
- tool_call_ids=[...]: 确认指定的工具
|
||
|
||
确认后,工具将被执行,结果将通过 SSE 流返回。
|
||
"""
|
||
if project.status != "opened":
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail=f"Project must be opened. Current status: {project.status}"
|
||
)
|
||
|
||
agent_manager = await get_project_agent_manager()
|
||
agent_service = await agent_manager.get_agent(str(project.id), project.path)
|
||
|
||
config = {"configurable": {"thread_id": session_id}}
|
||
state = await agent_service._graph.aget_state(config)
|
||
|
||
if not state or not state.values:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Session '{session_id}' not found"
|
||
)
|
||
|
||
values = state.values
|
||
pending_tools = values.get("pending_tool_calls", [])
|
||
|
||
if not pending_tools:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="No pending tools to confirm"
|
||
)
|
||
|
||
# 根据请求选择要确认的工具
|
||
confirmed_tools = []
|
||
if request.confirm_all:
|
||
confirmed_tools = pending_tools
|
||
elif request.tool_call_ids:
|
||
confirmed_tools = [t for t in pending_tools if t["tool_call_id"] in request.tool_call_ids]
|
||
else:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="Must specify either confirm_all=true or tool_call_ids"
|
||
)
|
||
|
||
if not confirmed_tools:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="No tools matched the confirmation criteria"
|
||
)
|
||
|
||
# 更新状态:标记为已确认
|
||
await agent_service._graph.aupdate_state(
|
||
config,
|
||
{
|
||
"confirmed_tool_calls": confirmed_tools,
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False
|
||
}
|
||
)
|
||
|
||
# 继续执行流程
|
||
try:
|
||
new_state = await agent_service._graph.ainvoke(None, config)
|
||
except Exception as e:
|
||
logger.error("Error continuing graph execution: %s", e, exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Error executing tools: {str(e)}"
|
||
)
|
||
|
||
return schemas.HITLConfirmationResponse(
|
||
status="confirmed",
|
||
confirmed_count=len(confirmed_tools),
|
||
message=f"已确认 {len(confirmed_tools)} 个工具执行"
|
||
)
|
||
|
||
|
||
@router.post(
|
||
"/sessions/{session_id}/hitl/reject",
|
||
response_model=schemas.HITLConfirmationResponse,
|
||
summary="拒绝执行工具",
|
||
description="用户拒绝执行一个或多个待确认的工具"
|
||
)
|
||
async def reject_tool_execution(
|
||
session_id: str,
|
||
request: schemas.HITLRejectionRequest,
|
||
project: Project = Depends(dep_project),
|
||
current_user: schemas.User = Depends(get_current_active_user),
|
||
) -> schemas.HITLConfirmationResponse:
|
||
"""
|
||
拒绝执行工具
|
||
|
||
用户可以选择:
|
||
- reject_all=true: 拒绝所有待确认的工具
|
||
- tool_call_ids=[...]: 拒绝指定的工具
|
||
- reason: 拒绝原因(将反馈给 LLM)
|
||
|
||
拒绝后,LLM 将收到通知并可以提供替代方案。
|
||
"""
|
||
if project.status != "opened":
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail=f"Project must be opened. Current status: {project.status}"
|
||
)
|
||
|
||
agent_manager = await get_project_agent_manager()
|
||
agent_service = await agent_manager.get_agent(str(project.id), project.path)
|
||
|
||
config = {"configurable": {"thread_id": session_id}}
|
||
|
||
rejected_tools = []
|
||
if request.reject_all:
|
||
state = await agent_service._graph.aget_state(config)
|
||
if state and state.values:
|
||
rejected_tools = state.values.get("pending_tool_calls", [])
|
||
elif request.tool_call_ids:
|
||
state = await agent_service._graph.aget_state(config)
|
||
if state and state.values:
|
||
pending = state.values.get("pending_tool_calls", [])
|
||
rejected_tools = [t for t in pending if t["tool_call_id"] in request.tool_call_ids]
|
||
|
||
if not rejected_tools:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="No pending tools to reject"
|
||
)
|
||
|
||
# 更新状态:标记为已拒绝
|
||
await agent_service._graph.aupdate_state(
|
||
config,
|
||
{
|
||
"rejected_tool_calls": rejected_tools,
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False
|
||
}
|
||
)
|
||
|
||
# 继续执行流程,LLM 将收到拒绝通知
|
||
try:
|
||
new_state = await agent_service._graph.ainvoke(None, config)
|
||
except Exception as e:
|
||
logger.error("Error continuing graph execution: %s", e, exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Error processing rejection: {str(e)}"
|
||
)
|
||
|
||
rejected_names = [t["tool_name"] for t in rejected_tools]
|
||
return schemas.HITLConfirmationResponse(
|
||
status="rejected",
|
||
confirmed_count=0,
|
||
message=f"已拒绝 {len(rejected_tools)} 个工具执行: {', '.join(rejected_names)}"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Schema 层
|
||
|
||
#### 文件:`gns3server/schemas/controller/chat.py`
|
||
|
||
##### 改动 3.1:添加 HITL 相关的 Pydantic 模型
|
||
|
||
**位置**:在文件末尾添加(约第 112 行后)
|
||
|
||
```python
|
||
class PendingTool(BaseModel):
|
||
"""待确认的工具信息"""
|
||
tool_call_id: str = Field(..., description="工具调用的唯一 ID")
|
||
tool_name: str = Field(..., description="工具名称")
|
||
tool_args: Dict[str, Any] = Field(..., description="工具参数")
|
||
danger_level: Literal["low", "medium", "high"] = Field(
|
||
default="medium",
|
||
description="危险等级"
|
||
)
|
||
description: Optional[str] = Field(None, description="工具执行的描述")
|
||
|
||
|
||
class HITLStatusResponse(BaseModel):
|
||
"""HITL 状态响应"""
|
||
status: Literal["idle", "waiting", "confirmed", "rejected"] = Field(
|
||
...,
|
||
description="当前状态"
|
||
)
|
||
pending_tools: List[PendingTool] = Field(
|
||
default_factory=list,
|
||
description="待确认的工具列表"
|
||
)
|
||
hitl_session_id: Optional[str] = Field(
|
||
None,
|
||
description="HITL 会话 ID"
|
||
)
|
||
session_id: str = Field(..., description="聊天会话 ID")
|
||
|
||
|
||
class HITLConfirmationRequest(BaseModel):
|
||
"""HITL 确认请求"""
|
||
confirm_all: bool = Field(
|
||
default=False,
|
||
description="是否确认所有待确认的工具"
|
||
)
|
||
tool_call_ids: Optional[List[str]] = Field(
|
||
None,
|
||
description="要确认的工具调用 ID 列表"
|
||
)
|
||
|
||
|
||
class HITLRejectionRequest(BaseModel):
|
||
"""HITL 拒绝请求"""
|
||
reject_all: bool = Field(
|
||
default=False,
|
||
description="是否拒绝所有待确认的工具"
|
||
)
|
||
tool_call_ids: Optional[List[str]] = Field(
|
||
None,
|
||
description="要拒绝的工具调用 ID 列表"
|
||
)
|
||
reason: Optional[str] = Field(
|
||
None,
|
||
description="拒绝原因(将反馈给 LLM)"
|
||
)
|
||
|
||
|
||
class HITLConfirmationResponse(BaseModel):
|
||
"""HITL 确认响应"""
|
||
status: Literal["confirmed", "rejected"] = Field(
|
||
...,
|
||
description="操作状态"
|
||
)
|
||
confirmed_count: int = Field(
|
||
...,
|
||
description="已确认的工具数量"
|
||
)
|
||
message: str = Field(..., description="响应消息")
|
||
```
|
||
|
||
**同时更新 `__init__.py` 导出**:
|
||
|
||
```python
|
||
# 文件:gns3server/schemas/__init__.py
|
||
|
||
# 添加到导入列表
|
||
from .controller.chat import (
|
||
# ... 现有导入 ...
|
||
PendingTool,
|
||
HITLStatusResponse,
|
||
HITLConfirmationRequest,
|
||
HITLRejectionRequest,
|
||
HITLConfirmationResponse,
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 文件改动汇总
|
||
|
||
| 文件路径 | 改动类型 | 行数变化 | 风险等级 |
|
||
|---------|---------|----------|----------|
|
||
| `gns3_copilot.py` | 修改/新增 | +200 行 | 🟡 中 |
|
||
| `agent_service.py` | 修改 | +10 行 | 🟢 低 |
|
||
| `chat.py` (API) | 新增 | +150 行 | 🟢 低 |
|
||
| `chat.py` (Schema) | 新增 | +50 行 | 🟢 低 |
|
||
| **总计** | - | **+410 行** | - |
|
||
|
||
---
|
||
|
||
## 数据库变更
|
||
|
||
### Checkpoint 表结构
|
||
|
||
**表名**:`checkpoints` (LangGraph 自动管理)
|
||
|
||
**新增字段**(通过 MessagesState 扩展自动添加):
|
||
|
||
| 字段名 | 类型 | 说明 |
|
||
|--------|------|------|
|
||
| `pending_tool_calls` | TEXT (JSON) | 待确认的工具调用列表 |
|
||
| `hitl_confirmation_required` | BOOLEAN | 是否需要 HITL 确认 |
|
||
| `hitl_session_id` | TEXT | HITL 会话 ID |
|
||
| `confirmed_tool_calls` | TEXT (JSON) | 已确认的工具调用 |
|
||
| `rejected_tool_calls` | TEXT (JSON) | 已拒绝的工具调用 |
|
||
|
||
**迁移说明**:
|
||
- LangGraph 自动处理新的 state 字段
|
||
- 无需手动执行数据库迁移
|
||
- 现有 checkpoint 向后兼容
|
||
|
||
---
|
||
|
||
## API 规范
|
||
|
||
### 1. 获取 HITL 状态
|
||
|
||
**端点**:`GET /v3/projects/{project_id}/chat/sessions/{session_id}/hitl-status`
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"status": "waiting",
|
||
"pending_tools": [
|
||
{
|
||
"tool_call_id": "call_abc123",
|
||
"tool_name": "execute_multiple_device_config_commands",
|
||
"tool_args": {
|
||
"project_id": "xxx",
|
||
"device_configs": [
|
||
{
|
||
"device_name": "R1",
|
||
"config_commands": ["interface gig0/0", "ip address 10.0.0.1/24"]
|
||
}
|
||
]
|
||
},
|
||
"danger_level": "medium",
|
||
"description": "配置 1 个设备"
|
||
}
|
||
],
|
||
"hitl_session_id": "hitl_12345",
|
||
"session_id": "chat_session_id"
|
||
}
|
||
```
|
||
|
||
### 2. 确认执行
|
||
|
||
**端点**:`POST /v3/projects/{project_id}/chat/sessions/{session_id}/hitl/confirm`
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"confirm_all": true,
|
||
"tool_call_ids": null
|
||
}
|
||
```
|
||
|
||
**或指定工具**:
|
||
```json
|
||
{
|
||
"confirm_all": false,
|
||
"tool_call_ids": ["call_abc123", "call_def456"]
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"status": "confirmed",
|
||
"confirmed_count": 2,
|
||
"message": "已确认 2 个工具执行"
|
||
}
|
||
```
|
||
|
||
### 3. 拒绝执行
|
||
|
||
**端点**:`POST /v3/projects/{project_id}/chat/sessions/{session_id}/hitl/reject`
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"reject_all": true,
|
||
"reason": "配置有误,需要重新规划"
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"status": "rejected",
|
||
"confirmed_count": 0,
|
||
"message": "已拒绝 1 个工具执行: execute_multiple_device_config_commands"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 前端集成指南
|
||
|
||
### 1. 检测 HITL 状态
|
||
|
||
```javascript
|
||
// 定期轮询 HITL 状态
|
||
async function pollHITLStatus(sessionId) {
|
||
const response = await fetch(
|
||
`/api/v3/projects/${projectId}/chat/sessions/${sessionId}/hitl-status`
|
||
);
|
||
const status = await response.json();
|
||
|
||
if (status.status === 'waiting' && status.pending_tools.length > 0) {
|
||
showConfirmationDialog(status);
|
||
}
|
||
}
|
||
|
||
// 每 2 秒轮询一次
|
||
setInterval(() => pollHITLStatus(sessionId), 2000);
|
||
```
|
||
|
||
### 2. 显示确认对话框
|
||
|
||
```javascript
|
||
function showConfirmationDialog(hitlStatus) {
|
||
const { pending_tools, hitl_session_id } = hitlStatus;
|
||
|
||
const dialog = document.createElement('div');
|
||
dialog.className = 'hitl-confirmation-dialog';
|
||
|
||
let html = `
|
||
<h3>⚠️ 需要确认以下操作</h3>
|
||
<div class="pending-tools">
|
||
`;
|
||
|
||
pending_tools.forEach(tool => {
|
||
const icon = tool.danger_level === 'high' ? '🔴' : '🟡';
|
||
html += `
|
||
<div class="tool-item">
|
||
<span class="danger-icon">${icon}</span>
|
||
<span class="tool-name">${tool.tool_name}</span>
|
||
<span class="tool-description">${tool.description}</span>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
html += `
|
||
</div>
|
||
<div class="actions">
|
||
<button onclick="confirmAll('${hitl_session_id}')">全部确认</button>
|
||
<button onclick="rejectAll('${hitl_session_id}')">全部拒绝</button>
|
||
</div>
|
||
`;
|
||
|
||
dialog.innerHTML = html;
|
||
document.body.appendChild(dialog);
|
||
}
|
||
|
||
async function confirmAll(sessionId) {
|
||
const response = await fetch(
|
||
`/api/v3/projects/${projectId}/chat/sessions/${sessionId}/hitl/confirm`,
|
||
{
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ confirm_all: true })
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
closeDialog();
|
||
// 继续监听 SSE 流以获取执行结果
|
||
}
|
||
}
|
||
|
||
async function rejectAll(sessionId) {
|
||
const response = await fetch(
|
||
`/api/v3/projects/${projectId}/chat/sessions/${sessionId}/hitl/reject`,
|
||
{
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ reject_all: true, reason: '用户取消操作' })
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
closeDialog();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 测试计划
|
||
|
||
### 单元测试
|
||
|
||
```python
|
||
import pytest
|
||
from gns3server.agent.gns3_copilot.agent.gns3_copilot import (
|
||
check_hitl_requirement,
|
||
_is_dangerous_config
|
||
)
|
||
|
||
def test_dangerous_config_detection():
|
||
"""测试危险命令检测"""
|
||
tool_args = {
|
||
"device_configs": [{
|
||
"config_commands": ["reload", "write erase"]
|
||
}]
|
||
}
|
||
|
||
assert _is_dangerous_config("execute_multiple_device_config_commands", tool_args) == True
|
||
|
||
def test_safe_config_detection():
|
||
"""测试安全命令检测"""
|
||
tool_args = {
|
||
"device_configs": [{
|
||
"config_commands": ["interface gig0/0", "ip address 10.0.0.1/24"]
|
||
}]
|
||
}
|
||
|
||
assert _is_dangerous_config("execute_multiple_device_config_commands", tool_args) == False
|
||
|
||
def test_hitl_requirement_check():
|
||
"""测试 HITL 需求检查"""
|
||
state = {
|
||
"messages": [
|
||
HumanMessage(content="配置路由器"),
|
||
AIMessage(
|
||
content="",
|
||
tool_calls=[{
|
||
"id": "call_123",
|
||
"name": "execute_multiple_device_config_commands",
|
||
"args": {"project_id": "test"}
|
||
}]
|
||
)
|
||
]
|
||
}
|
||
|
||
result = check_hitl_requirement(state)
|
||
|
||
assert result["hitl_confirmation_required"] == True
|
||
assert len(result["pending_tool_calls"]) == 1
|
||
```
|
||
|
||
### 集成测试
|
||
|
||
```python
|
||
import pytest
|
||
from fastapi.testclient import TestClient
|
||
|
||
def test_hitl_flow():
|
||
"""测试完整的 HITL 流程"""
|
||
client = TestClient(app)
|
||
|
||
# 1. 开始聊天
|
||
response = client.post(
|
||
f"/v3/projects/{project_id}/chat/stream",
|
||
json={"message": "配置所有路由器"}
|
||
)
|
||
|
||
# 2. 检查 HITL 状态
|
||
status = client.get(f"/v3/projects/{project_id}/chat/sessions/{session_id}/hitl-status")
|
||
assert status.json()["status"] == "waiting"
|
||
assert len(status.json()["pending_tools"]) > 0
|
||
|
||
# 3. 确认执行
|
||
confirm = client.post(
|
||
f"/v3/projects/{project_id}/chat/sessions/{session_id}/hitl/confirm",
|
||
json={"confirm_all": True}
|
||
)
|
||
assert confirm.json()["status"] == "confirmed"
|
||
```
|
||
|
||
---
|
||
|
||
## 部署步骤
|
||
|
||
### 第一阶段:基础架构(1-2 天)
|
||
|
||
1. ✅ 扩展 MessagesState
|
||
2. ✅ 实现 check_hitl_requirement 节点
|
||
3. ✅ 实现 hitl_confirmation_node 和 conditional_tool_execution
|
||
4. ✅ 更新 LangGraph 工作流
|
||
5. ✅ 单元测试
|
||
|
||
**验证**:现有功能不受影响
|
||
|
||
### 第二阶段:API 端点(1 天)
|
||
|
||
1. ✅ 添加 HITL 状态查询端点
|
||
2. ✅ 添加确认/拒绝端点
|
||
3. ✅ 添加 Schema 定义
|
||
4. ✅ API 测试
|
||
|
||
**验证**:API 可正常调用
|
||
|
||
### 第三阶段:前端集成(2-3 天)
|
||
|
||
1. ✅ 实现轮询逻辑
|
||
2. ✅ 显示确认对话框
|
||
3. ✅ 处理确认/拒绝操作
|
||
4. ✅ 显示执行结果
|
||
|
||
**验证**:端到端流程可用
|
||
|
||
### 第四阶段:优化和增强(1-2 天)
|
||
|
||
1. ✅ 添加危险命令分级
|
||
2. ✅ 实现超时处理
|
||
3. ✅ 添加操作日志
|
||
4. ✅ 性能优化
|
||
|
||
**验证**:生产就绪
|
||
|
||
---
|
||
|
||
## 参数修改功能(扩展)
|
||
|
||
### 功能概述
|
||
|
||
除了"确认/拒绝"外,HITL 还支持用户修改即将执行的命令参数,然后将修改反馈给 LLM,由 LLM 理解并按新参数执行。
|
||
|
||
### 完整流程
|
||
|
||
```
|
||
LLM 生成命令 A
|
||
↓
|
||
HITL 确认暂停
|
||
↓
|
||
显示给用户
|
||
↓
|
||
┌─────────┼─────────┐
|
||
│ │ │
|
||
直接确认 拒绝 修改为B
|
||
│ │ │
|
||
执行A 反馈LLM 反馈(A→B)给LLM
|
||
↓
|
||
LLM理解修改
|
||
↓
|
||
生成新tool call
|
||
↓
|
||
执行B
|
||
```
|
||
|
||
### 对话示例
|
||
|
||
```
|
||
用户: 配置 R1 的 gig0/0 接口为 10.0.0.1/24
|
||
|
||
LLM: 我将为您配置 R1 的接口。
|
||
|
||
HITL: ⚠️ 需要确认以下操作
|
||
工具: execute_multiple_device_config_commands
|
||
参数: {
|
||
"device_configs": [{
|
||
"device_name": "R1",
|
||
"config_commands": [
|
||
"interface gig0/0",
|
||
"ip address 10.0.0.1 255.255.255.0"
|
||
]
|
||
}]
|
||
}
|
||
|
||
用户: [修改参数]
|
||
ip address 10.0.0.1 255.255.255.0
|
||
→ ip address 192.168.1.1 255.255.255.0
|
||
[保存并执行]
|
||
|
||
系统反馈给 LLM:
|
||
用户修改了即将执行的命令参数:
|
||
工具: execute_multiple_device_config_commands
|
||
R1:
|
||
原命令: ['interface gig0/0', 'ip address 10.0.0.1 255.255.255.0']
|
||
修改为: ['interface gig0/0', 'ip address 192.168.1.1 255.255.255.0']
|
||
请按照修改后的参数执行。
|
||
|
||
LLM: 明白,我将使用修改后的 IP 地址 192.168.1.1/24 来配置 R1 的 gig0/0 接口。
|
||
|
||
[工具执行 execute_multiple_device_config_commands with modified args]
|
||
|
||
LLM: 已完成配置,R1 的 gig0/0 接口已配置为 192.168.1.1/24。
|
||
```
|
||
|
||
### 实现要点
|
||
|
||
#### 1. 状态扩展
|
||
|
||
在 `MessagesState` 中添加:
|
||
|
||
```python
|
||
# 用户修改的字段
|
||
user_modified_args: dict | None # 结构:
|
||
# {
|
||
# "tool_call_id": str,
|
||
# "original_args": dict,
|
||
# "modified_args": dict
|
||
# }
|
||
```
|
||
|
||
#### 2. API 扩展
|
||
|
||
新增端点:`POST /v3/projects/{project_id}/chat/sessions/{session_id}/hitl/modify`
|
||
|
||
**请求体**:
|
||
```python
|
||
{
|
||
"tool_call_id": "call_abc123",
|
||
"modified_args": {
|
||
# 修改后的完整参数
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应**:
|
||
```python
|
||
{
|
||
"status": "modified",
|
||
"modification_summary": "显示参数差异",
|
||
"message": "已将修改反馈给 AI"
|
||
}
|
||
```
|
||
|
||
#### 3. 后端处理流程
|
||
|
||
1. 接收修改后的参数
|
||
2. 生成参数差异摘要
|
||
3. 添加 HumanMessage 到对话,说明用户的修改
|
||
4. 清除 `pending_tool_calls` 和 `hitl_confirmation_required`
|
||
5. 设置 `user_modified_args`(用于触发 LLM 重新生成)
|
||
6. 继续执行,LLM 看到用户的修改后,生成新的 tool call
|
||
7. 执行新的 tool call
|
||
|
||
#### 4. 前端实现要点
|
||
|
||
**界面组件**:
|
||
- 显示原始参数和编辑区域
|
||
- 提供参数差异高亮(原值 vs 新值)
|
||
- 支持 JSON 格式验证
|
||
- 保存修改并执行按钮
|
||
|
||
**交互流程**:
|
||
1. 用户点击"修改参数"按钮
|
||
2. 展开参数编辑区域,显示原始 JSON
|
||
3. 用户在文本框中编辑 JSON
|
||
4. 实时验证 JSON 格式
|
||
5. 点击"保存并执行"提交修改
|
||
6. 系统反馈修改摘要并继续执行
|
||
|
||
**用户体验**:
|
||
- 对于配置工具,可以提供更友好的命令行界面而非纯 JSON
|
||
- 高亮显示修改的部分(红色删除线、绿色新增)
|
||
- 提供参数预设模板
|
||
- 显示修改前后的对比视图
|
||
|
||
### 关键代码位置
|
||
|
||
**文件**:`gns3_copilot.py`
|
||
|
||
增强 `conditional_tool_execution` 函数,检测 `user_modified_args`:
|
||
|
||
```python
|
||
def conditional_tool_execution(state: MessagesState) -> dict:
|
||
"""条件工具执行节点(支持用户修改)"""
|
||
|
||
# 处理用户修改的情况
|
||
if state.get("user_modified_args"):
|
||
# LLM 已通过 HumanMessage 收到用户修改
|
||
# 清除标记,让 LLM 重新生成 tool call
|
||
return {
|
||
"user_modified_args": None,
|
||
"pending_tool_calls": [],
|
||
"hitl_confirmation_required": False
|
||
}
|
||
|
||
# ... 其他处理逻辑
|
||
```
|
||
|
||
**参数差异生成**:
|
||
|
||
```python
|
||
def _generate_modification_summary(tool_name: str, original: dict, modified: dict) -> str:
|
||
"""生成参数修改摘要"""
|
||
|
||
if tool_name == "execute_multiple_device_config_commands":
|
||
# 特殊处理配置工具,逐命令对比
|
||
orig_devices = original.get("device_configs", [])
|
||
mod_devices = modified.get("device_configs", [])
|
||
|
||
summary = []
|
||
for orig, mod in zip(orig_devices, mod_devices):
|
||
device = orig.get("device_name", "")
|
||
orig_cmds = orig.get("config_commands", [])
|
||
mod_cmds = mod.get("config_commands", [])
|
||
|
||
if orig_cmds != mod_cmds:
|
||
summary.append(f"\n{device}:")
|
||
for oc, mc in zip(orig_cmds, mod_cmds):
|
||
if oc != mc:
|
||
summary.append(f" - {oc}")
|
||
summary.append(f" + {mc}")
|
||
|
||
return "\n".join(summary) if summary else "无修改"
|
||
|
||
# 其他工具的通用处理
|
||
# ...
|
||
```
|
||
|
||
### 安全考虑
|
||
|
||
#### 参数验证
|
||
|
||
- 验证修改后的参数结构是否完整
|
||
- 检查必填字段是否存在
|
||
- 验证参数值是否在合法范围内
|
||
|
||
#### 危险命令二次确认
|
||
|
||
即使修改后,某些命令仍需二次确认:
|
||
- `erase startup-config`
|
||
- `reload`
|
||
- `format flash:`
|
||
|
||
### 测试用例
|
||
|
||
**场景**:用户修改配置命令
|
||
|
||
1. LLM 生成配置命令:`ip address 10.0.0.1 255.255.255.0`
|
||
2. 用户修改为:`ip address 192.168.1.1 255.255.255.0`
|
||
3. 系统反馈修改摘要
|
||
4. LLM 理解并确认使用新 IP
|
||
5. 执行工具,使用修改后的参数
|
||
6. 验证配置结果
|
||
|
||
---
|
||
|
||
## 风险评估
|
||
|
||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||
|------|------|------|----------|
|
||
| 破坏现有功能 | 低 | 高 | 完整的回归测试 |
|
||
| 状态不一致 | 中 | 中 | checkpoint 验证 |
|
||
| 性能影响 | 低 | 低 | 异步处理 |
|
||
| 前端集成问题 | 中 | 中 | 详细的前端文档 |
|
||
|
||
---
|
||
|
||
## 回滚计划
|
||
|
||
如需回滚:
|
||
|
||
1. 移除 HITL 相关节点
|
||
2. 恢复原始 `should_continue` 和 `tool_node`
|
||
3. 删除新增的 API 端点
|
||
4. checkpoint 中的新字段会被自动忽略
|
||
|
||
**回滚时间**:约 30 分钟
|
||
|
||
---
|
||
|
||
## 后续增强
|
||
|
||
1. **批量操作优化**:支持选择性确认部分工具
|
||
2. **操作历史**:记录所有 HITL 操作
|
||
3. **自动审批**:对低风险操作设置自动审批规则
|
||
4. **多用户协作**:支持多人审批流程
|
||
5. **模板管理**:保存常用配置为模板
|
||
|
||
---
|
||
|
||
## 参考文档
|
||
|
||
- [LangGraph Interrupts](https://langchain-ai.github.io/langgraph/concepts/low_level/#interruption)
|
||
- [GNS3 Copilot 架构](./ai-chat-api-design.md)
|
||
- [工具响应格式标准](./tool-response-format-standard.md)
|
||
|
||
---
|
||
|
||
**文档版本**:v1.0
|
||
**创建日期**:2026-03-04
|
||
**作者**:GNS3 Development Team
|