优化llm分析逻辑
This commit is contained in:
parent
d3f5c82d98
commit
9e10bf11cf
@ -158,15 +158,7 @@ class OpenAICompatibleLlmClient:
|
||||
self.action_analysis_prompt,
|
||||
{
|
||||
"action": action,
|
||||
"result": {
|
||||
"backend": result.backend,
|
||||
"ok": result.ok,
|
||||
"exit_code": result.exit_code,
|
||||
"tool_name": result.tool_name,
|
||||
"values": _redact_sensitive(result.values),
|
||||
"stderr": _truncate_text(result.stderr),
|
||||
"error_summary": result.error_summary,
|
||||
},
|
||||
"result": _action_review_result_payload(action, result),
|
||||
"state_summary": _redact_sensitive(state_summary),
|
||||
},
|
||||
)
|
||||
@ -325,6 +317,45 @@ def _redact_sensitive(value: Any) -> Any:
|
||||
return value
|
||||
|
||||
|
||||
def _action_review_result_payload(action: str, result: ActionResult) -> dict[str, Any]:
|
||||
"""构造 action 审核输入,避免把正常脚本日志当作错误喂给 LLM。"""
|
||||
payload: dict[str, Any] = {
|
||||
"backend": result.backend,
|
||||
"ok": result.ok,
|
||||
"exit_code": result.exit_code,
|
||||
"tool_name": result.tool_name,
|
||||
"values": _redact_sensitive(result.values),
|
||||
"error_summary": result.error_summary,
|
||||
}
|
||||
if _needs_diagnostic_log(action, result):
|
||||
diagnostic = _diagnostic_log_text(result)
|
||||
if diagnostic:
|
||||
payload["diagnostic_log"] = diagnostic
|
||||
return payload
|
||||
|
||||
|
||||
def _needs_diagnostic_log(action: str, result: ActionResult) -> bool:
|
||||
"""仅在失败或业务异常时把少量诊断日志交给 LLM。"""
|
||||
if not result.ok or result.error_summary or result.values.get("PENDING_AGENT_CONFIRMATION"):
|
||||
return True
|
||||
if action == "verify-ip":
|
||||
success = result.values.get("SUCCESS")
|
||||
if success is not None and str(success).lower() not in ("true", "1", "yes"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _diagnostic_log_text(result: ActionResult) -> str:
|
||||
"""优先使用错误摘要;必要时取 stderr/stdout/raw_output 的尾部作为诊断上下文。"""
|
||||
if result.error_summary:
|
||||
return _truncate_text(result.error_summary)
|
||||
for text in (result.stderr, result.stdout, result.raw_output):
|
||||
stripped = text.strip()
|
||||
if stripped:
|
||||
return _tail_text(stripped)
|
||||
return ""
|
||||
|
||||
|
||||
def _truncate_text(value: str, limit: int = 1000) -> str:
|
||||
"""截断发送给 LLM 的长文本,避免传入完整日志。"""
|
||||
if len(value) <= limit:
|
||||
@ -332,6 +363,14 @@ def _truncate_text(value: str, limit: int = 1000) -> str:
|
||||
return value[:limit] + "...[已截断]"
|
||||
|
||||
|
||||
def _tail_text(value: str, limit: int = 1000) -> str:
|
||||
"""保留长诊断日志尾部,通常错误原因更靠近末尾。"""
|
||||
if len(value) <= limit:
|
||||
return value
|
||||
marker = "[已截断]..."
|
||||
return marker + value[-(limit - len(marker)) :]
|
||||
|
||||
|
||||
def _string(payload: dict[str, Any], key: str, default: str) -> str:
|
||||
"""安全读取字符串字段。"""
|
||||
value = payload.get(key, default)
|
||||
|
||||
@ -83,5 +83,7 @@ ACTION_ANALYSIS_PROMPT = """分析一次 PAM action 执行结果。
|
||||
要求:
|
||||
- 必须明确给出 `should_continue`:没有问题时为 true;存在需要人工判断的问题时为 false。
|
||||
- 如果 exit_code 非 0、ok=false、verify-ip SUCCESS=false、出现 pending_confirmation,应标记异常。
|
||||
- 主要依据结构化字段 `ok`、`exit_code`、`values`、`error_summary` 判断;只有输入里存在 `diagnostic_log` 时,才把它当作异常诊断上下文。
|
||||
- 脚本正常过程日志不会作为错误依据,不能因为日志来自 stderr 就判定异常。
|
||||
- 不要输出密钥、token、Authorization 或完整日志原文。
|
||||
"""
|
||||
|
||||
@ -179,7 +179,6 @@ class RuleBasedLlmClient:
|
||||
"exit_code": result.exit_code,
|
||||
"tool_name": result.tool_name,
|
||||
"values": result.values,
|
||||
"stderr": result.stderr,
|
||||
"error_summary": result.error_summary,
|
||||
},
|
||||
max_text_len=1000,
|
||||
@ -197,7 +196,7 @@ class RuleBasedLlmClient:
|
||||
if not result.ok:
|
||||
severity = "medium"
|
||||
possible_reason = result.error_summary or "action 返回失败状态。"
|
||||
suggested_action = "查看 action stderr/raw_output,确认参数、网络和目标服务状态。"
|
||||
suggested_action = "查看 action 诊断日志、参数、网络和目标服务状态。"
|
||||
notes.append("硬规则检测到 action 执行失败。")
|
||||
should_continue = False
|
||||
|
||||
|
||||
@ -15,4 +15,6 @@
|
||||
要求:
|
||||
- 必须明确给出 `should_continue`:没有问题时为 true;存在需要人工判断的问题时为 false。
|
||||
- 如果 exit_code 非 0、ok=false、verify-ip SUCCESS=false、出现 pending_confirmation,应标记异常。
|
||||
- 主要依据结构化字段 `ok`、`exit_code`、`values`、`error_summary` 判断;只有输入里存在 `diagnostic_log` 时,才把它当作异常诊断上下文。
|
||||
- 脚本正常过程日志不会作为错误依据,不能因为日志来自 stderr 就判定异常。
|
||||
- 不要输出密钥、token、Authorization 或完整日志原文。
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
from dataclasses import asdict
|
||||
import json
|
||||
|
||||
from pam_deploy_graph.agent import PamDeployAgent
|
||||
from pam_deploy_graph.checkpoint_store import redact_mapping
|
||||
@ -219,13 +220,66 @@ def test_openai_compatible_client_analyzes_action_result_with_redaction():
|
||||
ok=False,
|
||||
values={"CLIENT_SECRET": "real-secret", "SUCCESS": "false"},
|
||||
stderr="x" * 1200,
|
||||
error_summary="failed",
|
||||
),
|
||||
state_summary={"params": {"CLIENT_SECRET": "real-secret"}},
|
||||
)
|
||||
|
||||
serialized_prompt = str(calls[0])
|
||||
input_payload = _llm_input_payload(calls[0])
|
||||
assert analysis.has_anomaly is True
|
||||
assert analysis.severity == "high"
|
||||
assert "real-secret" not in serialized_prompt
|
||||
assert "[已截断]" in serialized_prompt
|
||||
assert input_payload["result"]["diagnostic_log"].startswith("[已截断]...")
|
||||
|
||||
|
||||
def test_openai_compatible_client_omits_success_script_logs_from_action_review():
|
||||
calls = []
|
||||
|
||||
def transport(url, headers, payload, timeout_sec):
|
||||
calls.append(payload)
|
||||
return {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"content": (
|
||||
'{"action":"get-online-ips","has_anomaly":false,"severity":"info",'
|
||||
'"possible_reason":"","suggested_action":"continue",'
|
||||
'"requires_confirmation":false,"should_continue":true}'
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
client = OpenAICompatibleLlmClient(
|
||||
base_url="https://llm.example/v1",
|
||||
api_key="secret-key",
|
||||
model="model-a",
|
||||
transport=transport,
|
||||
)
|
||||
|
||||
client.analyze_action_result(
|
||||
action="get-online-ips",
|
||||
result=ActionResult(
|
||||
action="get-online-ips",
|
||||
backend="script",
|
||||
ok=True,
|
||||
values={"ACTION": "get-online-ips", "COUNT": "1", "IP": ["10.4.1.1"]},
|
||||
stdout="ACTION=get-online-ips\nCOUNT=1\nIP=10.4.1.1\n",
|
||||
stderr="[INFO] [FLOW][START] get_token\n[INFO] [FLOW][DONE] get_online_ips\n",
|
||||
),
|
||||
state_summary={},
|
||||
)
|
||||
|
||||
input_payload = _llm_input_payload(calls[0])
|
||||
result_payload = input_payload["result"]
|
||||
assert "diagnostic_log" not in result_payload
|
||||
assert "stdout" not in result_payload
|
||||
assert "stderr" not in result_payload
|
||||
assert "[FLOW][START]" not in json.dumps(input_payload, ensure_ascii=False)
|
||||
|
||||
|
||||
def _llm_input_payload(request_payload):
|
||||
content = request_payload["messages"][1]["content"]
|
||||
_, _, raw_json = content.partition("输入 JSON:\n")
|
||||
return json.loads(raw_json)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user