CI / ruff · mypy · pytest (pull_request) Successful in 39m13s
- prefilter: правила из config/agent_rules.yaml (длина, команды, shell,
секреты и системные файлы, prompt injection) — без LLM
- guard: отдельный вызов, JSON {verdict, reason}; невалидный ответ = attack,
недоступный LLM — «попробуйте позже», а не атака
- planner: только AgentLogQuery (контейнер из белого списка, ≤ 7 д, шаблон
≤ 200 без вложенных квантификаторов, лимит ≤ 500); логи в LLM не идут
- бот: «🤖 Спросить агента» → показывает построенный grep и строки;
атака — вежливый отказ и 🛡 администраторам; квота 30 в сутки
- журнал agent_requests + log_queries (mode=agent)
- при LLM_PROVIDER=mock агент выключен
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.3 KiB
Python
39 lines
1.3 KiB
Python
"""Step 2 (SPEC L-11): a separate, narrow LLM call — is the question an attack? Anything but a valid `safe` is one."""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from app.db.models import Verdict
|
|
from app.llm.provider import ChatMessage, JsonSchema, LLMProvider
|
|
|
|
SCHEMA = JsonSchema(
|
|
name="guard_verdict",
|
|
schema={
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["verdict", "reason"],
|
|
"properties": {
|
|
"verdict": {"type": "string", "enum": ["safe", "attack"]},
|
|
"reason": {"type": "string"},
|
|
},
|
|
},
|
|
)
|
|
|
|
INVALID = "страж вернул не JSON по схеме"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GuardVerdict:
|
|
verdict: Verdict
|
|
reason: str
|
|
tokens: int
|
|
|
|
|
|
async def classify(llm: LLMProvider, system_prompt: str, prompt: str) -> GuardVerdict:
|
|
"""LLMError propagates: an unreachable model is «try later», not an attack."""
|
|
result = await llm.complete([ChatMessage("system", system_prompt), ChatMessage("user", prompt)], SCHEMA)
|
|
data = result.data or {}
|
|
verdict, reason = data.get("verdict"), data.get("reason")
|
|
if verdict not in ("safe", "attack") or not isinstance(reason, str):
|
|
return GuardVerdict(Verdict.ATTACK, INVALID, result.tokens)
|
|
return GuardVerdict(Verdict(verdict), reason[:300], result.tokens)
|