feat: AI 诊断 Agent 支持执行系统诊断 shell 命令
扩展 Agent 命令工具,除 kubectl 外新增节点宿主机系统诊断命令支持:
- agent_tools.py: 新增 shell 命令白名单分类器 (classify_shell_command)
- 只读白名单: systemctl status/is-active、journalctl、crictl ps/logs、
docker ps/logs、df、free、ss、ip addr、ps、mount、lsmod、dmesg、
ping、nslookup、cat (限 /proc /sys /etc/kubernetes 等安全路径) 等 30+ 命令
- 写白名单: systemctl restart/stop/start/reload 等需人工审批
- 统一分发器 classify_agent_command 按命令前缀路由
- 统一执行入口 execute_command 走 subprocess_exec (无 shell 注入风险)
- ai_agent.py: 更新 AGENT_SYSTEM_PROMPT 告知 AI 两类可用工具及安全规则
- 三个调用点 (初始探测/分类/执行) 切换到统一接口
- main.py: 审批执行处改用 execute_command
- tests: 新增 19 个用例覆盖 shell 命令分类、混合执行、白名单拒绝
- 全部 44 个测试通过
- 前端/README: 文案与文档补充 shell 命令支持说明
安全策略不变: 禁止 shell 操作符/管道/重定向/多命令拼接,
非白名单命令 (rm/vi/apt 等) 直接拒绝。
This commit is contained in:
+48
-15
@@ -6,7 +6,7 @@ from collections import Counter
|
||||
from typing import Optional
|
||||
import httpx
|
||||
from backend.config import AI_API_BASE, AI_API_KEY, AI_MODEL
|
||||
from backend.agent_tools import classify_kubectl_command, execute_kubectl
|
||||
from backend.agent_tools import classify_agent_command, execute_command
|
||||
|
||||
SYSTEM_PROMPT = """你是一位资深的 Kubernetes 运维专家和 SRE 工程师。你的任务是分析 K8S 集群诊断报告,找出根本原因并给出修复建议。
|
||||
|
||||
@@ -25,19 +25,25 @@ _HEADERS = {
|
||||
}
|
||||
|
||||
|
||||
AGENT_SYSTEM_PROMPT = """你是 Kubernetes 自主诊断 Agent。你的首要职责是主动调用 kubectl 获取现场证据,而不是只给用户一段脚本或操作建议。
|
||||
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
|
||||
|
||||
行为要求:
|
||||
1. 如果证据不足,必须返回 command 动作并逐条调查;至少检查与故障资源直接相关的状态、describe、events 或 logs。
|
||||
2. 禁止在 final 中仅提供“请执行以下命令”的脚本。只有在已经依据真实命令输出形成结论时才能返回 final。
|
||||
3. 每次只提出一条 kubectl 命令,服务端会执行并把真实输出返回给你,然后你继续下一轮判断。
|
||||
2. 禁止在 final 中仅提供"请执行以下命令"的脚本。只有在已经依据真实命令输出形成结论时才能返回 final。
|
||||
3. 每次只提出一条命令,服务端会执行并把真实输出返回给你,然后你继续下一轮判断。
|
||||
|
||||
安全规则:
|
||||
1. 只允许提出单条 kubectl 命令,不得使用 shell、管道、重定向或命令连接符。
|
||||
2. get、describe、logs、top 等只读命令可以自动执行。
|
||||
3. apply、delete、patch、scale、rollout、exec 等修改或交互命令必须暂停并请求人工批准。
|
||||
4. 不要猜测命令输出;必须依据实际工具结果继续判断。
|
||||
5. 最多执行有限轮次,优先使用 namespace 和资源名缩小范围。
|
||||
1. 每次只返回一条命令,不得使用 shell、管道、重定向或命令连接符 (; | && > < 等)。
|
||||
2. kubectl 的 get、describe、logs、top 等只读命令可以自动执行。
|
||||
3. 系统诊断只读命令 (systemctl status、journalctl、crictl ps、df、free、ss、ip addr 等) 可以自动执行。
|
||||
4. apply、delete、patch、scale、rollout、exec 等修改或交互命令必须暂停并请求人工批准。
|
||||
5. systemctl restart/stop/start 等系统修改命令必须暂停并请求人工批准。
|
||||
6. 不要猜测命令输出;必须依据实际工具结果继续判断。
|
||||
7. 最多执行有限轮次,优先使用 namespace 和资源名缩小范围。
|
||||
|
||||
每轮必须只返回一个 JSON 对象,不要使用 Markdown 代码块:
|
||||
- 继续调查:{"action":"command","command":"kubectl ...","reason":"为什么执行"}
|
||||
@@ -129,7 +135,7 @@ async def run_diagnostic_agent(
|
||||
"reason": "建立集群节点状态基线,确保 AI 执行模式实际采集现场证据",
|
||||
"mode": "read",
|
||||
}
|
||||
initial_result = await execute_kubectl(initial_command)
|
||||
initial_result = await execute_command(initial_command)
|
||||
yield {"type": "result", **initial_result}
|
||||
messages.append({
|
||||
"role": "user",
|
||||
@@ -157,6 +163,7 @@ async def _run_agent_loop(
|
||||
report: str,
|
||||
question: str,
|
||||
):
|
||||
invalid_response_count = 0
|
||||
for step in range(start_step, max_steps + 1):
|
||||
yield {"type": "thinking", "step": step, "message": f"AI 正在规划第 {step} 轮诊断"}
|
||||
try:
|
||||
@@ -164,6 +171,7 @@ async def _run_agent_loop(
|
||||
action = _parse_agent_action(content)
|
||||
except ValueError as exc:
|
||||
if "最终动作必须确认" in str(exc):
|
||||
invalid_response_count = 0
|
||||
messages.extend([
|
||||
{"role": "assistant", "content": content},
|
||||
{
|
||||
@@ -177,13 +185,37 @@ async def _run_agent_loop(
|
||||
])
|
||||
yield {"type": "verification_required", "message": "尚未确认所有问题清零,Agent 继续执行诊断验证"}
|
||||
continue
|
||||
yield {"type": "error", "message": str(exc)}
|
||||
return
|
||||
invalid_response_count += 1
|
||||
if invalid_response_count >= 3:
|
||||
yield {
|
||||
"type": "error",
|
||||
"message": "AI 连续 3 次未返回合法 JSON 动作,已停止本次诊断以避免无效循环",
|
||||
}
|
||||
return
|
||||
messages.extend([
|
||||
{"role": "assistant", "content": content},
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"上一次响应无法解析:{exc}。请不要输出解释、脚本或多条命令,"
|
||||
"严格只返回一个 JSON 对象:"
|
||||
'{"action":"command","command":"kubectl ...","reason":"..."} '
|
||||
"或在全量复检问题清零后返回 final。"
|
||||
),
|
||||
},
|
||||
])
|
||||
yield {
|
||||
"type": "response_rejected",
|
||||
"message": f"AI 响应格式无效,正在自动纠正并重试({invalid_response_count}/3)",
|
||||
"reason": str(exc),
|
||||
}
|
||||
continue
|
||||
except Exception as exc:
|
||||
yield {"type": "error", "message": str(exc)}
|
||||
return
|
||||
|
||||
if action["action"] == "final":
|
||||
invalid_response_count = 0
|
||||
yield {
|
||||
"type": "final", "content": action["analysis"], "model": AI_MODEL,
|
||||
"verified": True, "remaining_issues": 0,
|
||||
@@ -191,6 +223,7 @@ async def _run_agent_loop(
|
||||
return
|
||||
|
||||
command = action["command"]
|
||||
invalid_response_count = 0
|
||||
reason = action.get("reason", "AI 需要更多集群证据")
|
||||
command_counts[command] += 1
|
||||
if command_counts[command] > 2:
|
||||
@@ -210,11 +243,11 @@ async def _run_agent_loop(
|
||||
}
|
||||
continue
|
||||
try:
|
||||
decision = classify_kubectl_command(command)
|
||||
decision = classify_agent_command(command)
|
||||
except ValueError as exc:
|
||||
messages.extend([
|
||||
{"role": "assistant", "content": content},
|
||||
{"role": "user", "content": f"命令被安全策略拒绝:{exc}。请改用单条合法 kubectl 命令。"},
|
||||
{"role": "user", "content": f"命令被安全策略拒绝:{exc}。请改用单条合法 kubectl 或系统诊断命令。"},
|
||||
])
|
||||
yield {"type": "command_rejected", "command": command, "reason": str(exc)}
|
||||
continue
|
||||
@@ -252,7 +285,7 @@ async def _run_agent_loop(
|
||||
}
|
||||
return
|
||||
|
||||
result = await execute_kubectl(command)
|
||||
result = await execute_command(command)
|
||||
yield {"type": "result", **result}
|
||||
messages.extend([
|
||||
{"role": "assistant", "content": content},
|
||||
|
||||
Reference in New Issue
Block a user