fix: 实现 Agent 修复验证闭环

This commit is contained in:
cnbugs
2026-07-25 13:10:21 +08:00
parent 47abfef238
commit f2254646c1
6 changed files with 264 additions and 30 deletions
+79 -3
View File
@@ -3,6 +3,7 @@ import json
import re
import uuid
from collections import Counter
from typing import Optional
import httpx
from backend.config import AI_API_BASE, AI_API_KEY, AI_MODEL
from backend.agent_tools import classify_kubectl_command, execute_kubectl
@@ -40,7 +41,9 @@ AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你的首要职
每轮必须只返回一个 JSON 对象,不要使用 Markdown 代码块:
- 继续调查:{"action":"command","command":"kubectl ...","reason":"为什么执行"}
- 完成分析:{"action":"final","analysis":"中文 Markdown 分析结论"}
- 完成分析:{"action":"final","analysis":"中文 Markdown 分析结论","verified":true,"remaining_issues":0}
只有完成修复后的全量复检,并确认 remaining_issues 为 0 时才能返回 final。只定位根因、执行命令成功或部分资源恢复都不能结束。
"""
_PENDING_APPROVALS: dict[str, dict] = {}
@@ -72,6 +75,9 @@ def _parse_agent_action(content: str) -> dict:
raise ValueError("AI 命令动作缺少 command")
if action["action"] == "final" and not action.get("analysis"):
raise ValueError("AI 最终动作缺少 analysis")
if action["action"] == "final":
if action.get("verified") is not True or action.get("remaining_issues") != 0:
raise ValueError("AI 最终动作必须确认 verified=true 且 remaining_issues=0")
return action
@@ -130,17 +136,58 @@ async def run_diagnostic_agent(
"content": "AI 执行模式已自动采集第一份现场证据:\n" + json.dumps(initial_result, ensure_ascii=False),
})
for step in range(1, max_steps + 1):
async for event in _run_agent_loop(
messages=messages,
execution_mode=execution_mode,
max_steps=max_steps,
start_step=1,
command_counts=command_counts,
report=report,
question=question,
):
yield event
async def _run_agent_loop(
messages: list[dict],
execution_mode: str,
max_steps: int,
start_step: int,
command_counts: Counter,
report: str,
question: str,
):
for step in range(start_step, max_steps + 1):
yield {"type": "thinking", "step": step, "message": f"AI 正在规划第 {step} 轮诊断"}
try:
content = await _complete_chat(messages)
action = _parse_agent_action(content)
except ValueError as exc:
if "最终动作必须确认" in str(exc):
messages.extend([
{"role": "assistant", "content": content},
{
"role": "user",
"content": (
"不能结束诊断:尚未通过修复后的全量复检确认所有问题清零。"
"请继续返回 command,检查节点、Pod、Deployment、事件以及原故障资源;"
"只有 verified=true 且 remaining_issues=0 时才可返回 final。"
),
},
])
yield {"type": "verification_required", "message": "尚未确认所有问题清零,Agent 继续执行诊断验证"}
continue
yield {"type": "error", "message": str(exc)}
return
except Exception as exc:
yield {"type": "error", "message": str(exc)}
return
if action["action"] == "final":
yield {"type": "final", "content": action["analysis"], "model": AI_MODEL}
yield {
"type": "final", "content": action["analysis"], "model": AI_MODEL,
"verified": True, "remaining_issues": 0,
}
return
command = action["command"]
@@ -191,6 +238,13 @@ async def run_diagnostic_agent(
"reason": reason,
"created_by": AI_MODEL,
"run_id": "",
"report": report,
"question": question,
"execution_mode": execution_mode,
"messages": messages + [{"role": "assistant", "content": content}],
"next_step": step + 1,
"max_steps": max_steps,
"command_counts": dict(command_counts),
}
yield {
"type": "approval_required", "approval_id": approval_id,
@@ -214,6 +268,28 @@ async def run_diagnostic_agent(
}
async def resume_diagnostic_agent(pending: dict, result: dict):
"""审批命令执行后,把真实结果交回原 Agent 上下文并继续诊断。"""
messages = list(pending.get("messages") or [])
messages.append({
"role": "user",
"content": (
"用户已批准命令,以下是实际执行结果。请继续自主诊断并验证问题是否解决:\n"
+ json.dumps(result, ensure_ascii=False)
),
})
async for event in _run_agent_loop(
messages=messages,
execution_mode=pending.get("execution_mode", "ai"),
max_steps=int(pending.get("max_steps", 30)),
start_step=int(pending.get("next_step", 1)),
command_counts=Counter(pending.get("command_counts") or {}),
report=pending.get("report", ""),
question=pending.get("question", ""),
):
yield event
def take_pending_approval(approval_id: str) -> dict | None:
return _PENDING_APPROVALS.pop(approval_id, None)