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:
+51
-1
@@ -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 多轮对话 (非流式,兼容旧接口)"""
|
||||
|
||||
Reference in New Issue
Block a user