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
+49 -8
View File
@@ -27,8 +27,11 @@ class DiagnosticAgentTest(unittest.TestCase):
return await collect_events(run_diagnostic_agent("report", max_steps=3))
events = asyncio.run(run())
self.assertEqual([event["type"] for event in events], ["thinking", "command", "result", "thinking", "final"])
self.assertEqual(events[1]["mode"], "read")
self.assertEqual(
[event["type"] for event in events],
["command", "result", "thinking", "command", "result", "thinking", "final"],
)
self.assertEqual(events[3]["mode"], "read")
self.assertEqual(events[-1]["content"], "发现一个异常 Pod")
def test_pauses_mutating_command_for_human_approval(self):
@@ -63,13 +66,18 @@ class DiagnosticAgentTest(unittest.TestCase):
async def fake_completion(messages):
return '{"action":"command","command":"kubectl delete pod api-0 -n prod","reason":"删除故障 Pod"}'
async def run():
with patch("backend.ai_agent._complete_chat", fake_completion), patch("backend.ai_agent.execute_kubectl") as execute:
events = await collect_events(run_diagnostic_agent("report", max_steps=3, execution_mode="ai"))
return events, execute
executed = []
events, execute = asyncio.run(run())
execute.assert_not_called()
async def run():
async def fake_execute(command):
executed.append(command)
return {"command": command, "mode": "read", "returncode": 0, "stdout": "nodes ready", "stderr": ""}
with patch("backend.ai_agent._complete_chat", fake_completion), patch("backend.ai_agent.execute_kubectl", fake_execute):
return await collect_events(run_diagnostic_agent("report", max_steps=3, execution_mode="ai"))
events = asyncio.run(run())
self.assertEqual(executed, ["kubectl get nodes -o wide"])
self.assertEqual(events[-1]["type"], "approval_required")
def test_rejects_unknown_execution_mode(self):
@@ -80,6 +88,39 @@ class DiagnosticAgentTest(unittest.TestCase):
self.assertEqual(events[-1]["type"], "error")
self.assertIn("执行模式", events[-1]["message"])
def test_ai_mode_forces_initial_read_only_probe_before_model_planning(self):
responses = iter([
'{"action":"final","analysis":"根据采集结果完成分析"}',
])
async def fake_completion(messages):
return next(responses)
executed = []
async def fake_execute(command):
executed.append(command)
return {"command": command, "mode": "read", "returncode": 0, "stdout": "node-a Ready", "stderr": ""}
async def run():
with patch("backend.ai_agent._complete_chat", fake_completion), patch("backend.ai_agent.execute_kubectl", fake_execute):
return await collect_events(run_diagnostic_agent("report", max_steps=3, execution_mode="ai"))
events = asyncio.run(run())
self.assertEqual(executed, ["kubectl get nodes -o wide"])
self.assertEqual([event["type"] for event in events[:2]], ["command", "result"])
self.assertEqual(events[-1]["type"], "final")
def test_parse_agent_action_accepts_markdown_json_fence(self):
from backend.ai_agent import _parse_agent_action
action = _parse_agent_action(
'我将先检查 Pod。\n```json\n{"action":"command","command":"kubectl get pods -A","reason":"查看 Pod"}\n```'
)
self.assertEqual(action["action"], "command")
self.assertEqual(action["command"], "kubectl get pods -A")
def test_stops_when_model_returns_invalid_action(self):
async def fake_completion(messages):
return "not-json"