把「封禁」这个动作从模型输出里拿掉,是这类接入最先要定的边界:Gemini 3.5 Flash Cyber 只负责判断和给建议,执行由人确认后触发。这样做的直接好处是误判不会立刻打到业务上,代价是多了一步人工确认,所以配套要解决三件事——建议长什么样、建议怎么进人的待办、人处理完之后留下什么记录。下面按这三件事的顺序给结构和可替换的实现骨架,接口细节按你实际的模型调用方式调整。
适用场景:账号风控、登录异常、临时限流这类误伤成本高、回滚麻烦的处置。操作动作:模型输出只保留建议类型、涉及对象、证据引用、建议动作,ID 与有效期由服务端补齐,解析层不接收任何执行指令字段;建议先进人工待办,人工确认后再落执行。验证方式:构造一条带执行字段的输出,看解析层是否拒收;抽查待办条目是否留下采纳或驳回记录。风险边界:这套做法不降低误判率本身,只是把误判成本从线上挪到人工确认环节;待办长期积压时建议会过期失效,需要有人值守。
把执行动作从模型输出里拿掉
最容易出事的不是模型判断错,而是判断和执行的链路是通的。结构上要保证:模型返回的内容即使完全跑偏,也没有任何一条路径能直接封号。做法是让模型的输出方案只允许四个语义区块——建议类型、涉及对象、依据、建议动作,且建议动作里只写动作名和参数,不写命令、脚本、接口地址。
提示词层面先把边界写死,下面这段是通用骨架,可直接替换成你链路上的系统提示词:
你是安全处置建议生成器,不执行任何操作。
只输出一个 JSON 对象,字段限定为:
suggestion_type / target / evidence / suggested_action / note
规则:
1. 不得输出命令、脚本、接口调用、URL 或任何可直接执行的字符串;
2. evidence 中每一项必须引用输入数据里出现过的 log_id、rule_id 或事件 ID;
3. 证据不足时 suggestion_type 固定为 manual_review,并在 note 里写清缺什么;
4. 不要输出建议编号、时间戳、有效期,这些字段由服务端生成。
解析层再做一次白名单校验,比在提示词里反复强调更可靠。下面这段是校验骨架,键名按你实际字段替换:
ALLOWED = {"suggestion_type", "target", "evidence", "suggested_action", "note"}
FORBIDDEN = {"execute", "command", "shell", "api_call", "auto_ban", "payload"}
def check(obj):
keys = set(obj.keys())
if keys & FORBIDDEN or keys - ALLOWED:
return "reject_schema" # 不进待办,落一条解析失败日志
if not obj.get("target") or not obj.get("evidence"):
return "insufficient" # 降级为待补充,不进执行队列
return "ok"
关键是执行服务那一侧不接模型原始返回。执行接口的入参是待办条目的 ID 加操作员身份,模型输出只能通过「被采纳」这个动作间接影响执行内容。校验失败的返回要单独记一条日志,方便回看提示词是不是被绕过了。
设计一条建议的最小字段
字段太少,人看不懂;字段太多,模型填不稳。最小集建议是:对象、证据引用、建议动作、有效期,外加一个由服务端生成的建议编号。对象要带类型(账号、IP、会话、订单等)和 ID,避免只有一串裸标识;证据引用指向已有日志或规则命中记录,不要写成自然语言描述;建议动作给动作名和必要参数,例如限流范围、是否可回滚;有效期决定这条建议什么时候自动失效。
{
"suggestion_id": "sg-0007", // 服务端生成
"suggestion_type": "manual_review",
"target": {"kind": "account", "id": "u_8812"},
"evidence": [{"type": "log", "ref": "auth_log:88231", "window": "10m"}],
"suggested_action": {"action": "rate_limit", "params": {"scope": "login"}},
"expires_at": "服务端按类型默认有效期写入",
"note": "同一账号短时多次失败登录,未命中已有规则"
}
缺失处理要提前定好,不能等到线上遇到再临时判断。对象缺失直接丢弃,因为没有对象就没法进待办;证据引用缺失或无法在日志里解析到,降级为「待补充」状态,可查但不进优先级队列;建议动作缺失,按类型给一个最保守的默认值(例如只生成人工复核待办);有效期缺失由服务端按建议类型写入配置里的默认值,不要依赖模型自己算时间。这几条规则写进解析层,就不会出现「建议在库里但没人知道该拿它怎么办」的情况。
让建议进入人的待办而不是留在日志里
建议只写进日志,等于没提。生成待办的门槛可以定成三条同时满足:通过字段校验、证据引用能在日志系统里查到、对象在业务库中存在。三条缺一条就留在观测表里,不打扰值班的人。
优先级排序建议以可核对的事实为主:涉及对象是否落在核心业务范围、证据引用的日志条数和规则命中数、距离过期的剩余时间。模型自报的置信度可以作为次级参考,但不要当成唯一排序依据,否则模型越自信的条目越靠前,人工的注意力反而被带偏。
重复建议必须合并,否则同一个账号刷十条待办,人很快就麻木了。合并键用「对象 + 建议类型」,在一个滑动时间窗内只保留一条:新证据追加到证据列表,命中次数加一,最后出现时间更新,优先级取窗口内的最高值。下面这段是合并后看待办的查询样例,表名和字段按实际环境替换:
SELECT target_kind, target_id, suggestion_type,
COUNT(*) AS hit_count,
MAX(priority) AS priority,
MAX(last_seen_at) AS last_seen_at
FROM security_suggestions
WHERE status = 'pending' AND expires_at > now()
GROUP BY target_kind, target_id, suggestion_type
ORDER BY priority DESC, last_seen_at DESC;
待办页面至少要给出三样东西:对象是什么、证据点开能跳到原始日志、建议动作和有效期。人做判断时不需要再回模型对话里翻上下文。
记录采纳与驳回的理由
只记「采纳/驳回」两个状态,后面没法评估建议质量。建议记这些字段:建议编号、决定(采纳或驳回)、操作人、决定时间、实际执行动作(允许与建议不同,比如建议封禁但人只做了限流)、驳回原因码、备注。实际执行动作和建议动作分开记,是判断「模型建议是否过重」的关键依据。
驳回原因用固定枚举,不要用自由文本,否则统计不出来。可以先定这几类:证据不足、对象误判(正常业务行为被当成异常)、动作过重、动作过轻、已有其他处置方式、重复建议、超过有效期未处理。枚举项不必一开始就齐全,但要能覆盖你当前最常见的驳回场景,后续可以增补,不要改已有编码的含义。
这些记录在下一次调整中这样用:驳回集中在「证据不足」,说明提示词里对证据引用的约束不够,或输入给模型的上下文太薄;集中在「动作过重」,说明建议动作的档位需要重新划,或者应该在提示词里明确「能复核就不要直接封」;集中在「重复建议」,说明合并窗口太短;「超过有效期未处理」占比高,则是值守人力或待办提醒的问题,跟模型无关,别去改提示词。
观察建议质量的两三个指标
采纳率的统计口径要先定清楚,不同口径算出来的数字差很多。建议口径是:统计周期内被采纳数 ÷(被采纳数 + 被驳回数),过期未处理的条目不计入分母,单独作为积压指标看;同一条建议被合并后多次命中,仍按一条计。这个口径能反映「人看了之后认不认」,但会被待办积压稀释,所以要和积压量一起看。
SELECT
SUM(CASE WHEN status = 'adopted' THEN 1 ELSE 0 END) AS adopted,
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) AS rejected,
SUM(CASE WHEN status = 'adopted' THEN 1 ELSE 0 END) * 1.0
/ NULLIF(SUM(CASE WHEN status IN ('adopted','rejected') THEN 1 ELSE 0 END), 0) AS adopt_rate
FROM security_suggestions
WHERE created_at >= now() - interval '7 days';
驳回原因分布按原因码分组看趋势,而不是只看总量。某一类原因短期内明显上涨,通常对应输入侧的变化(新增了某类日志、上游规则改了阈值),先去核对输入,再考虑改提示词。
过期未处理数量用「状态仍是 pending 且 expires_at 早于当前时间」计数,按建议类型分组,每天跑一次落到小表里看趋势。这个数字持续走高,说明建议产出量超过了人工处理能力,此时应该收紧生成条件(提高证据门槛、只对核心对象生成待办),而不是把有效期调长来让数字变好看——后者只是把问题藏起来。