feat: 持久化诊断历史并修复 Agent 命令执行

This commit is contained in:
cnbugs
2026-07-25 12:40:53 +08:00
parent 4f3a7833a0
commit fdac1eaffc
10 changed files with 650 additions and 45 deletions
+41 -5
View File
@@ -1,5 +1,6 @@
"""AI Agent 分析模块 - 调用 OpenAI 兼容接口分析诊断结果 (支持流式输出)"""
import json
import re
import uuid
import httpx
from backend.config import AI_API_BASE, AI_API_KEY, AI_MODEL
@@ -22,7 +23,12 @@ _HEADERS = {
}
AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你可以根据诊断报告决定执行 kubectl 命令获取更多证据,并在证据充分后给出结论
AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你的首要职责是主动调用 kubectl 获取现场证据,而不是只给用户一段脚本或操作建议
行为要求:
1. 如果证据不足,必须返回 command 动作并逐条调查;至少检查与故障资源直接相关的状态、describe、events 或 logs。
2. 禁止在 final 中仅提供“请执行以下命令”的脚本。只有在已经依据真实命令输出形成结论时才能返回 final。
3. 每次只提出一条 kubectl 命令,服务端会执行并把真实输出返回给你,然后你继续下一轮判断。
安全规则:
1. 只允许提出单条 kubectl 命令,不得使用 shell、管道、重定向或命令连接符。
@@ -40,10 +46,25 @@ _PENDING_APPROVALS: dict[str, dict] = {}
def _parse_agent_action(content: str) -> dict:
try:
action = json.loads(content.strip())
except json.JSONDecodeError as exc:
raise ValueError("AI 未返回合法 JSON 动作") from exc
text = content.strip()
candidates = [text]
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL | re.IGNORECASE)
if fenced:
candidates.insert(0, fenced.group(1))
first_brace = text.find("{")
last_brace = text.rfind("}")
if first_brace >= 0 and last_brace > first_brace:
candidates.append(text[first_brace:last_brace + 1])
action = None
for candidate in candidates:
try:
action = json.loads(candidate)
break
except json.JSONDecodeError:
continue
if action is None:
raise ValueError("AI 未返回合法 JSON 动作")
if not isinstance(action, dict) or action.get("action") not in {"command", "final"}:
raise ValueError("AI 返回了不支持的动作")
if action["action"] == "command" and not action.get("command"):
@@ -91,6 +112,20 @@ async def run_diagnostic_agent(
{"role": "user", "content": user_content},
]
if execution_mode == "ai":
initial_command = "kubectl get nodes -o wide"
yield {
"type": "command", "step": 0, "command": initial_command,
"reason": "建立集群节点状态基线,确保 AI 执行模式实际采集现场证据",
"mode": "read",
}
initial_result = await execute_kubectl(initial_command)
yield {"type": "result", **initial_result}
messages.append({
"role": "user",
"content": "AI 执行模式已自动采集第一份现场证据:\n" + json.dumps(initial_result, ensure_ascii=False),
})
for step in range(1, max_steps + 1):
yield {"type": "thinking", "step": step, "message": f"AI 正在规划第 {step} 轮诊断"}
try:
@@ -134,6 +169,7 @@ async def run_diagnostic_agent(
"command": command,
"reason": reason,
"created_by": AI_MODEL,
"run_id": "",
}
yield {
"type": "approval_required", "approval_id": approval_id,