feat: 移除 etcdutl 引用,增加 ctr/nerdctl 示例,实现 Agent 断点续传

- AI Agent Prompt 移除不存在的 etcdutl 命令引用
- Prompt 增加 containerd(ctr/nerdctl) 和 Linux 基础调优命令示例
- processing_runs 表新增 context_json 字段,保存 Agent 执行上下文
- 每次命令执行后自动保存上下文到 DB
This commit is contained in:
Your Name
2026-07-25 14:34:05 +08:00
parent a5399bd69d
commit b19975c369
3 changed files with 169 additions and 4 deletions
+51 -1
View File
@@ -22,7 +22,8 @@ from backend.diagnosis import (
from backend.ai_agent import (
analyze_with_ai, chat_with_ai,
analyze_with_ai_stream, chat_with_ai_stream,
run_diagnostic_agent, resume_diagnostic_agent, take_pending_approval,
run_diagnostic_agent, resume_diagnostic_agent, resume_from_context,
take_pending_approval,
)
from backend.agent_tools import execute_command
from backend.history_store import HistoryStore
@@ -329,6 +330,8 @@ async def agent_diagnose_stream(question: str = "", execution_mode: str = "ai"):
question=question,
max_steps=0,
execution_mode=execution_mode,
run_id=run_id,
history_store=history_store,
):
history_store.append_event(run_id, event.get("type", "event"), {k: v for k, v in event.items() if k != "type"})
if event.get("type") == "final":
@@ -409,6 +412,53 @@ async def execute_approved_agent_command(req: AgentApprovalRequest):
)
@app.get("/api/agent/resume-stream/{run_id}")
async def agent_resume_stream(run_id: str):
"""从 DB 上下文恢复 Agent 诊断,不重跑历史命令,直接继续诊断。"""
ctx = history_store.get_context(run_id)
if ctx is None:
raise HTTPException(status_code=404, detail="该运行记录不存在或不支持断点续传")
ctx.pop("report", None) # get_context 已经注入了 report
async def event_generator():
yield f"data: {json.dumps({'type': 'start', 'mode': ctx.get('execution_mode', 'ai'), 'run_id': run_id, 'resumed': True}, ensure_ascii=False)}\n\n"
final_status = "completed"
summary = ""
try:
async for event in resume_from_context(
ctx=ctx,
run_id=run_id,
history_store=history_store,
):
history_store.append_event(run_id, event.get("type", "event"), {k: v for k, v in event.items() if k != "type"})
if event.get("type") == "final":
summary = event.get("content", "")
elif event.get("type") == "error":
final_status = "failed"
summary = event.get("message", "")
elif event.get("type") in {"manual_command", "approval_required"}:
final_status = "waiting"
if event.get("type") == "approval_required":
from backend.ai_agent import _PENDING_APPROVALS
pending = _PENDING_APPROVALS.get(event.get("approval_id"))
if pending is not None:
pending["run_id"] = run_id
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
history_store.finish_run(run_id, final_status, summary)
yield f"data: {json.dumps({'type': 'done', 'run_id': run_id}, ensure_ascii=False)}\n\n"
except Exception as exc:
message = f"断点续传诊断失败: {exc}"
history_store.append_event(run_id, "error", {"message": message})
history_store.finish_run(run_id, "failed", message)
yield f"data: {json.dumps({'type': 'error', 'message': message}, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
@app.post("/api/chat")
async def chat(req: ChatRequest):
"""AI 多轮对话 (非流式,兼容旧接口)"""