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:
+79
-3
@@ -29,8 +29,10 @@ AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你的首要职
|
||||
|
||||
可用工具:
|
||||
1. kubectl 命令 - 查询集群内资源状态。例如: kubectl get pods -A, kubectl describe node <name>, kubectl logs <pod> -n <ns>
|
||||
2. 系统诊断 shell 命令 - 查询节点宿主机状态。例如: systemctl status kubelet, journalctl -u kubelet -n 100 --no-pager, crictl ps, df -h, free -m, ss -tlnp, ip addr
|
||||
3. etcd 诊断命令 - 检查 etcd 集群健康与数据。例如: etcdctl endpoint status, etcdctl member list, etcdctl alarm list, etcdctl get /kubernetes --prefix, etcdutl snapshot status <file>
|
||||
2. 系统诊断 shell 命令 - 查询节点宿主机状态。例如: systemctl status kubelet, journalctl -u kubelet -n 100 --no-pager, crictl ps, df -h, free -m, ss -tlnp, ip addr, top, du -sh, curl, tcpdump
|
||||
3. containerd 命令 - 查询容器运行时状态。例如: ctr containers list, ctr images list, ctr tasks ls, nerdctl ps -a, nerdctl logs <container>, nerdctl stats
|
||||
4. etcd 诊断命令 - 检查 etcd 集群健康与数据。例如: etcdctl endpoint status, etcdctl endpoint health, etcdctl member list, etcdctl alarm list, etcdctl get /kubernetes --prefix
|
||||
5. Linux 基础调优命令 - 例如: iostat -xz 1 3, vmstat 1 3, mpstat -P ALL 1 3, pidstat -d 1 3, lsof -i :<port>, ethtool <iface>, tcpdump -i any -c 10 -nn
|
||||
|
||||
行为要求:
|
||||
1. 如果证据不足,必须返回 command 动作并逐条调查;至少检查与故障资源直接相关的状态、describe、events 或 logs。
|
||||
@@ -112,6 +114,8 @@ async def run_diagnostic_agent(
|
||||
question: str = "",
|
||||
max_steps: int = 30,
|
||||
execution_mode: str = "ai",
|
||||
run_id: str = "",
|
||||
history_store=None,
|
||||
):
|
||||
"""自主诊断循环:AI 模式执行只读命令,手动模式等待用户执行。"""
|
||||
if execution_mode not in {"ai", "manual"}:
|
||||
@@ -151,6 +155,8 @@ async def run_diagnostic_agent(
|
||||
command_counts=command_counts,
|
||||
report=report,
|
||||
question=question,
|
||||
run_id=run_id,
|
||||
history_store=history_store,
|
||||
):
|
||||
yield event
|
||||
|
||||
@@ -163,6 +169,8 @@ async def _run_agent_loop(
|
||||
command_counts: Counter,
|
||||
report: str,
|
||||
question: str,
|
||||
run_id: str = "",
|
||||
history_store=None,
|
||||
):
|
||||
invalid_response_count = 0
|
||||
step = start_step
|
||||
@@ -266,6 +274,9 @@ async def _run_agent_loop(
|
||||
"command": command, "reason": reason, "mode": decision.mode,
|
||||
"message": "请在终端手动执行命令,并根据结果继续排查",
|
||||
}
|
||||
# 手动模式也保存上下文,支持断点续传
|
||||
self_save_context(run_id, history_store, messages, command_counts, step, step + 1,
|
||||
max_steps, report, question, execution_mode, content)
|
||||
return
|
||||
|
||||
if decision.requires_approval:
|
||||
@@ -274,15 +285,19 @@ async def _run_agent_loop(
|
||||
"command": command,
|
||||
"reason": reason,
|
||||
"created_by": AI_MODEL,
|
||||
"run_id": "",
|
||||
"run_id": run_id,
|
||||
"report": report,
|
||||
"question": question,
|
||||
"execution_mode": execution_mode,
|
||||
"history_store": history_store,
|
||||
"messages": messages + [{"role": "assistant", "content": content}],
|
||||
"next_step": step + 1,
|
||||
"max_steps": max_steps,
|
||||
"command_counts": dict(command_counts),
|
||||
}
|
||||
# 保存上下文到 DB,避免浏览器关闭/刷新丢失进度
|
||||
self_save_context(run_id, history_store, messages, command_counts, step, step + 1,
|
||||
max_steps, report, question, execution_mode, content)
|
||||
yield {
|
||||
"type": "approval_required", "approval_id": approval_id,
|
||||
"command": command, "reason": reason,
|
||||
@@ -296,6 +311,10 @@ async def _run_agent_loop(
|
||||
{"role": "user", "content": "命令实际执行结果:\n" + json.dumps(result, ensure_ascii=False)},
|
||||
])
|
||||
|
||||
# 每次执行后保存上下文,支持断点续传
|
||||
self_save_context(run_id, history_store, messages, command_counts, step, step + 1,
|
||||
max_steps, report, question, execution_mode)
|
||||
|
||||
step += 1
|
||||
|
||||
if max_steps > 0:
|
||||
@@ -319,6 +338,8 @@ async def resume_diagnostic_agent(pending: dict, result: dict):
|
||||
+ json.dumps(result, ensure_ascii=False)
|
||||
),
|
||||
})
|
||||
run_id = pending.get("run_id", "")
|
||||
history_store = pending.get("history_store")
|
||||
async for event in _run_agent_loop(
|
||||
messages=messages,
|
||||
execution_mode=pending.get("execution_mode", "ai"),
|
||||
@@ -327,6 +348,8 @@ async def resume_diagnostic_agent(pending: dict, result: dict):
|
||||
command_counts=Counter(pending.get("command_counts") or {}),
|
||||
report=pending.get("report", ""),
|
||||
question=pending.get("question", ""),
|
||||
run_id=run_id,
|
||||
history_store=history_store,
|
||||
):
|
||||
yield event
|
||||
|
||||
@@ -335,6 +358,59 @@ def take_pending_approval(approval_id: str) -> dict | None:
|
||||
return _PENDING_APPROVALS.pop(approval_id, None)
|
||||
|
||||
|
||||
def self_save_context(
|
||||
run_id: str, history_store,
|
||||
messages: list, command_counts: Counter,
|
||||
step: int, next_step: int,
|
||||
max_steps: int, report: str, question: str,
|
||||
execution_mode: str,
|
||||
last_response_content: str = "",
|
||||
):
|
||||
"""保存 Agent 执行上下文到 DB,支持断点续传。"""
|
||||
if not run_id or not history_store:
|
||||
return
|
||||
context = {
|
||||
"messages": messages,
|
||||
"command_counts": dict(command_counts),
|
||||
"step": step,
|
||||
"next_step": next_step,
|
||||
"max_steps": max_steps,
|
||||
"report": report,
|
||||
"question": question,
|
||||
"execution_mode": execution_mode,
|
||||
"last_response": last_response_content,
|
||||
}
|
||||
history_store.update_context(run_id, context)
|
||||
|
||||
|
||||
async def resume_from_context(
|
||||
ctx: dict,
|
||||
run_id: str = "",
|
||||
history_store=None,
|
||||
):
|
||||
"""从 DB 保存的上下文恢复 Agent 诊断,不重跑历史命令,直接进入下一轮。"""
|
||||
messages = list(ctx.get("messages") or [])
|
||||
command_counts = Counter(ctx.get("command_counts") or {})
|
||||
next_step = int(ctx.get("next_step", 1))
|
||||
max_steps = int(ctx.get("max_steps", 0))
|
||||
report = ctx.get("report", "")
|
||||
question = ctx.get("question", "")
|
||||
execution_mode = ctx.get("execution_mode", "ai")
|
||||
|
||||
async for event in _run_agent_loop(
|
||||
messages=messages,
|
||||
execution_mode=execution_mode,
|
||||
max_steps=max_steps,
|
||||
start_step=next_step,
|
||||
command_counts=command_counts,
|
||||
report=report,
|
||||
question=question,
|
||||
run_id=run_id,
|
||||
history_store=history_store,
|
||||
):
|
||||
yield event
|
||||
|
||||
|
||||
async def _stream_chat(messages: list[dict]):
|
||||
"""通用流式请求 - yield 文本 chunk"""
|
||||
# connect=10s 快速失败; read=90s 防止 AI 服务无响应时前端无限卡死
|
||||
|
||||
Reference in New Issue
Block a user