feat: 扩充 shell 命令白名单(含 containerd/ctr/nerdctl),解除 AI Agent 轮数上限
- 新增 ~50 个 Linux 基础命令(du/top/iostat/sar/curl/tcpdump 等) - 新增 containerd 系列: ctr (containers/images/tasks/snapshots), nerdctl (ps/logs/inspect/stats 等) - ip 命令补充 maddr/monitor/tunnel/tuntap 只读操作 - _run_agent_loop 改为 while True 循环,max_steps=0 表示不限轮数 - main.py 传入 max_steps=0,AI Agent 持续诊断直到问题解决 - 更新 Agent system prompt 第7条:明确无轮数上限
This commit is contained in:
+13
-3
@@ -117,14 +117,24 @@ SHELL_READ_ONLY_COMMANDS: dict[str, set[str] | None] = {
|
|||||||
},
|
},
|
||||||
"journalctl": None, # 只读,任意参数 (-u, -n, --no-pager, --since 等)
|
"journalctl": None, # 只读,任意参数 (-u, -n, --no-pager, --since 等)
|
||||||
"crictl": {"ps", "inspect", "logs", "info", "version", "images", "stats"},
|
"crictl": {"ps", "inspect", "logs", "info", "version", "images", "stats"},
|
||||||
|
"ctr": {"containers", "images", "namespaces", "tasks", "snapshots", "info", "version", "plugins", "content", "leases"},
|
||||||
|
"nerdctl": {"ps", "logs", "inspect", "info", "version", "images", "stats", "top", "events", "system", "namespace", "network", "volume"},
|
||||||
"docker": {"ps", "logs", "inspect", "stats", "version", "info", "images", "top"},
|
"docker": {"ps", "logs", "inspect", "stats", "version", "info", "images", "top"},
|
||||||
"df": None, "free": None, "uptime": None, "uname": None, "hostname": None,
|
"df": None, "du": None, "free": None, "uptime": None, "uname": None, "hostname": None,
|
||||||
"ip": {"addr", "address", "route", "link", "neighbor", "neigh", "rule"},
|
"ip": {"addr", "address", "route", "link", "neighbor", "neigh", "rule", "maddr", "monitor", "tunnel", "tuntap"},
|
||||||
"ss": None, "netstat": None, "lsblk": None, "ls": None, "cat": None,
|
"ss": None, "netstat": None, "lsblk": None, "ls": None, "cat": None,
|
||||||
"head": None, "tail": None, "wc": None, "grep": None, "egrep": None, "fgrep": None,
|
"head": None, "tail": None, "wc": None, "grep": None, "egrep": None, "fgrep": None,
|
||||||
"ps": None, "mount": None, "lsmod": None, "lscpu": None, "lsof": None,
|
"ps": None, "pstree": None, "mount": None, "lsmod": None, "lscpu": None, "lsof": None,
|
||||||
"dmesg": None, "ping": None, "nslookup": None, "dig": None, "getent": None,
|
"dmesg": None, "ping": None, "nslookup": None, "dig": None, "getent": None,
|
||||||
"timedatectl": None, "date": None, "stat": None, "file": None, "blkid": None,
|
"timedatectl": None, "date": None, "stat": None, "file": None, "blkid": None,
|
||||||
|
"top": None, "htop": None, "iostat": None, "vmstat": None, "mpstat": None,
|
||||||
|
"pidstat": None, "sar": None, "nproc": None, "env": None, "which": None,
|
||||||
|
"readlink": None, "realpath": None, "find": None, "sort": None, "uniq": None,
|
||||||
|
"awk": None, "sed": None, "cut": None, "tr": None, "tee": None,
|
||||||
|
"basename": None, "dirname": None, "xargs": None, "bc": None, "expr": None,
|
||||||
|
"curl": None, "wget": None, "traceroute": None, "tracepath": None, "mtr": None,
|
||||||
|
"nc": None, "nmap": None, "telnet": None, "tcpdump": None, "ethtool": None,
|
||||||
|
"lspci": None, "lsusb": None, "dmidecode": None, "sysctl": None, "udevadm": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# etcdctl / etcdutl 由 classify_shell_command 中的专用块处理 (两段式命令)
|
# etcdctl / etcdutl 由 classify_shell_command 中的专用块处理 (两段式命令)
|
||||||
|
|||||||
+9
-2
@@ -44,7 +44,7 @@ AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你的首要职
|
|||||||
4. apply、delete、patch、scale、rollout、exec 等修改或交互命令必须暂停并请求人工批准。
|
4. apply、delete、patch、scale、rollout、exec 等修改或交互命令必须暂停并请求人工批准。
|
||||||
5. systemctl restart/stop/start 等系统修改命令必须暂停并请求人工批准。
|
5. systemctl restart/stop/start 等系统修改命令必须暂停并请求人工批准。
|
||||||
6. 不要猜测命令输出;必须依据实际工具结果继续判断。
|
6. 不要猜测命令输出;必须依据实际工具结果继续判断。
|
||||||
7. 最多执行有限轮次,优先使用 namespace 和资源名缩小范围。
|
7. 只要问题没有完全解决(verified=true 且 remaining_issues=0),就要持续执行命令诊断,没有轮数上限。
|
||||||
|
|
||||||
每轮必须只返回一个 JSON 对象,不要使用 Markdown 代码块:
|
每轮必须只返回一个 JSON 对象,不要使用 Markdown 代码块:
|
||||||
- 继续调查:{"action":"command","command":"kubectl ...","reason":"为什么执行"}
|
- 继续调查:{"action":"command","command":"kubectl ...","reason":"为什么执行"}
|
||||||
@@ -165,7 +165,10 @@ async def _run_agent_loop(
|
|||||||
question: str,
|
question: str,
|
||||||
):
|
):
|
||||||
invalid_response_count = 0
|
invalid_response_count = 0
|
||||||
for step in range(start_step, max_steps + 1):
|
step = start_step
|
||||||
|
while True:
|
||||||
|
if max_steps > 0 and step > max_steps:
|
||||||
|
break
|
||||||
yield {"type": "thinking", "step": step, "message": f"AI 正在规划第 {step} 轮诊断"}
|
yield {"type": "thinking", "step": step, "message": f"AI 正在规划第 {step} 轮诊断"}
|
||||||
try:
|
try:
|
||||||
content = await _complete_chat(messages)
|
content = await _complete_chat(messages)
|
||||||
@@ -293,6 +296,9 @@ async def _run_agent_loop(
|
|||||||
{"role": "user", "content": "命令实际执行结果:\n" + json.dumps(result, ensure_ascii=False)},
|
{"role": "user", "content": "命令实际执行结果:\n" + json.dumps(result, ensure_ascii=False)},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
step += 1
|
||||||
|
|
||||||
|
if max_steps > 0:
|
||||||
yield {
|
yield {
|
||||||
"type": "error",
|
"type": "error",
|
||||||
"message": (
|
"message": (
|
||||||
@@ -300,6 +306,7 @@ async def _run_agent_loop(
|
|||||||
"系统已停止继续执行以避免无限循环,请检查是否重复执行相同命令或 AI 服务响应异常。"
|
"系统已停止继续执行以避免无限循环,请检查是否重复执行相同命令或 AI 服务响应异常。"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
# max_steps=0 表示不限轮数,不输出错误 — Agent 会持续执行直到完成或遇到致命错误
|
||||||
|
|
||||||
|
|
||||||
async def resume_diagnostic_agent(pending: dict, result: dict):
|
async def resume_diagnostic_agent(pending: dict, result: dict):
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ async def agent_diagnose_stream(question: str = "", execution_mode: str = "ai"):
|
|||||||
async for event in run_diagnostic_agent(
|
async for event in run_diagnostic_agent(
|
||||||
_last_report,
|
_last_report,
|
||||||
question=question,
|
question=question,
|
||||||
|
max_steps=0,
|
||||||
execution_mode=execution_mode,
|
execution_mode=execution_mode,
|
||||||
):
|
):
|
||||||
history_store.append_event(run_id, event.get("type", "event"), {k: v for k, v in event.items() if k != "type"})
|
history_store.append_event(run_id, event.get("type", "event"), {k: v for k, v in event.items() if k != "type"})
|
||||||
|
|||||||
Reference in New Issue
Block a user