☰
semantic-router 决策目录 NOT 决策详解:用排除规则实现安全网关与高危流量拦截
2026/10/15 18:23:09 网站建设 项目流程
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

导读

本篇文章以 not.md 教程为骨架,深入讲解 semantic-router 决策目录(decision catalog)中最简单的排除规则NOT:如何在路由定义中显式声明"某个已知风险信号必须不存在"才能匹配。你会掌握 NOT 决策的适用场景、完整 YAML 配置写法、底层求值语义(严格一元运算符)、配置校验契约,以及如何将 NOT 与 AND / OR 嵌套组合,构建 jailbreak 拦截、PII 排除、Premium 路由隔离等真实安全网关。


一、什么是 NOT 决策

NOT是 semantic-router 决策目录(config/fragments/decision/)中最简单的排除型规则。它表达的含义是:

当且仅当某个"危险 / 不被允许"的信号不存在时,路由才应当匹配。

在仓库中,NOT 决策的官方片段位于config/fragments/decision/not/,与and/、or/、single/、composite/等片段并列,共同构成决策目录的算子集合。从源码常量看,规则树(RuleNode)支持的组合算子只有三个:AND、OR、NOT(见 validator_rule_operator.go),其中NOT是唯一的一元(unary)算子。

它解决什么问题

部分路由只有在某个已知风险信号缺失时才应该运行。如果这种排除是隐式的,审核者必须从下游行为反推"为什么这条路由不处理这类流量",代价高且易错。NOT把排除条件直接写进路由定义,让"被拒绝的信号"被显式命名,从而:

  • 让负向策略(negative policy)变得明确;
  • 天然适用于安全网关(safety gates)与 Premium 路由排除(premium-route exclusions);
  • 把排除逻辑保留在路由图中,而不是隐藏在下游代码里;
  • 因为被拒绝的信号被直接点名,审计(audit)变得更加容易。

二、何时使用 NOT

根据教程,以下场景适合使用not/:

  1. 已知 jailbreak 或携带 PII 的流量必须被排除:例如含提示注入(prompt injection)特征的请求不应进入普通服务路由;
  2. Premium 路由应远离不安全输入:高价值、高成本模型只服务"干净"请求,不承接有风险的内容;
  3. 升级(escalation)前必须确认某个冲突信号不存在:例如只有当"紧急"信号存在且"直接请求"信号缺席时,才进入多模型融合(fusion)等高级处理路径。

反过来说,教程也给出了一条重要提醒:NOT要克制使用,且被排除的信号必须保持显式,否则决策将变得难以审计。

三、NOT 决策完整配置解析

官方来源片段为 config/fragments/decision/not/exclude-jailbreak.yaml,完整内容如下:

routing: decisions: - name: safe_only_route description: Match only when the known prompt-injection signal is absent. priority: 70 rules: operator: NOT conditions: - type: jailbreak name: prompt_injection modelRefs: - model: qwen2.5:3b use_reasoning: false

字段逐项说明

字段取值(本例)含义
namesafe_only_route决策名,路由图中的唯一标识,也是审计追踪中的入口。
descriptionMatch only when the known prompt-injection signal is absent.人类可读的语义说明,应直接描述"什么信号被排除"。
priority70决策优先级,数值越高越先被评估;用于多个决策之间的排序与竞争。
rules.operatorNOT组合算子,表示对其唯一子条件取反。
rules.conditionstype: jailbreak, name: prompt_injection被取反的叶子条件:信号类型为jailbreak,信号名为prompt_injection。
modelRefsqwen2.5:3b+use_reasoning: false决策命中后路由到的模型引用;use_reasoning控制是否启用推理模式。

被排除的信号从哪来

prompt_injection并不是凭空捏造的字段名,它在信号目录中有明确定义。查看 config/fragments/signal/jailbreak/patterns.yaml:

routing: signals: jailbreak: - name: prompt_injection method: contrastive threshold: 0.8 include_history: true description: Detect common prompt-injection or jailbreak attempts. jailbreak_patterns: - ignore previous instructions - reveal the hidden prompt - jailbreak mode benign_patterns: - explain the policy - summarize the safety rules

也就是说:prompt_injection使用 contrastive(对比)检测方法,阈值0.8,开启历史上下文(include_history: true),通过危险模式(如 "ignore previous instructions")与良性模式(如 "explain the policy")的对比来判定注入特征。当该信号被判定为"存在"时,NOT决策即不匹配——这正是教程所说"已知 jailbreak 流量必须被排除"的落地实现。

四、底层实现:evalNOT 的求值语义

在决策引擎 engine.go 中,NOT被实现为严格一元操作符,对应函数evalNOT:

// evalNOT is a strictly unary operator: it negates the result of its single child. // Configuration errors (0 or 2+ children) are treated as non-matching. func (e *DecisionEngine) evalNOT(children []config.RuleNode, ...) (nodeEvaluation, *TraceNode) { trace := newTraceNode("NOT", withTrace) if len(children) != 1 { logging.Warnf("NOT operator requires exactly 1 child, got %d — treating as non-match", len(children)) evaluation := nodeEvaluation{state: evaluationFalse} trace.finish(evaluation) return evaluation, trace } childEvaluation, childTrace := e.evalNode(children[0], signals, policy, withTrace) trace.addChild(childTrace) switch childEvaluation.state { case evaluationFalse: evaluation = nodeEvaluation{state: evaluationTrue, confidence: 1, ...} case evaluationUnknown: evaluation = nodeEvaluation{state: evaluationUnknown} default: evaluation = nodeEvaluation{state: evaluationFalse, confidence: childEvaluation.confidence, ...} } ... }

从源码可以提炼出三条关键语义(engine.go#L816-L850):

  1. 严格一元:NOT只接受恰好 1 个子条件。子条件个数为 0 或 ≥2 时,引擎会记录告警并直接判定为不匹配(evaluationFalse),而不是猜测意图;
  2. 真值表:子条件为假 →NOT为真(confidence 取 1);子条件为真 →NOT为假;子条件状态为 unknown(信号不可知/未扫描)→NOT保持 unknown,不强行给出结论;
  3. 置信度传播:取反成功时置信度固定为结构常量 1.0,而取反失败时继承子条件的置信度。这一点与源码注释相互印证:keyword rules, NOT guards and predicates在 OR 分支竞争中都携带结构化的 1.0 分值(见 engine.go#L803-L805),因此"额外多一个 NOT 守卫不会剥离决策原本已经报告的证据"。

同时,evalNode的调度逻辑保证了只有经过规范化的规则树才会进入NOT分支(engine.go#L369-L379):合法加载的配置只携带AND、OR、NOT三种算子,default 分支仅供编程方式构造的树兜底。

五、配置校验:NOT 的 Arity 契约

在配置进入引擎之前,validator_rule_operator.go 中的NormalizeRuleOperator会递归地规范化并校验整棵规则树。与 NOT 直接相关的契约有两个:

  1. 运算符归一化:normalizeCombinationOperator会把operator值 trim 空白、转大写,并将省略的 operator 默认为AND(对携带子节点的节点而言),只允许AND/OR/NOT三种值,其他值(如XOR)直接报错并指明出错节点路径;
  2. Arity 校验:validateCombinationArity强制NOT必须有恰好 1 个子条件:
if op == RuleOperatorNot && children != 1 { return fmt.Errorf("%s: NOT requires exactly one child condition, got %d", path, children) }

也就是说,即使引擎对畸形 NOT 做了防御(返回不匹配),正常加载路径下畸形配置在校验阶段就会被拒绝,引擎根本不会见到它。

六、规则树形状测试:NOT 的真值表验证

决策包中的 rule_tree_shape_test.go 用一张"形状语料"表同时约束了校验器与求值器,其中 NOT 相关的用例包括:

  • NOT of matched leaf:对已匹配叶子取反 → 期望evaluationFalse;
  • NOR via nesting:NOT(OR(c, a))→ 期望evaluationFalse(德摩根式嵌套);
  • NAND via nesting:NOT(AND(c, a))→ 期望evaluationTrue;
  • padded not:" not "带空格 → 归一化后为合法NOT,期望evaluationTrue;
  • 被拒绝的树:multi-child NOT never matches(2 个子节点)、childless NOT never matches(0 个子节点)都会被校验器拒绝,引擎永远碰不到。

这组测试印证了一个设计原则:校验器接受什么形状,求值器就能给出确定真值;校验器拒绝什么形状,正是求值器会误读的形状(多子 NOT 会被"永久判负",见 rule_tree_shape_test.go#L9-L15)。

七、组合使用:AND + OR + NOT 构建多信号路由

NOT 的实战价值在于与其他算子组合。决策目录中的组合片段 priority-safe-escalation.yaml 给出了一个"AND 包 OR、AND 包 NOT"的真实结构:

routing: decisions: - name: priority_safe_escalation_route description: Combine AND, OR, and NOT for a realistic multi-signal routing case. priority: 160 rules: operator: AND conditions: - type: domain name: business - operator: OR conditions: - type: keyword name: urgent_keywords - type: complexity name: needs_reasoning:hard - operator: NOT conditions: - type: jailbreak name: prompt_injection modelRefs: - model: qwen2.5:3b use_reasoning: true

这条决策的语义是:(业务域信号存在)AND(紧急关键词 OR 高复杂度推理需求)AND(NOT 提示注入信号)才路由到qwen2.5:3b并启用推理。可以看出:

  • NOT作为"安全闸门"被嵌在 AND 组合中,与业务条件同层级并列;
  • 它把"危险信号缺席"提升为与"业务条件满足"同权的前置条件,而不是事后在下游处理;
  • 这正是教程所说"排除逻辑保留在路由图中"的组合形态。

类似地,官方accuracy配方(config/recipes/accuracy/config.yaml)中的accuracy_deliberation决策也使用了嵌套 NOT——当accuracy_deliberation_request关键词命中但accuracy_direct_request关键词缺席时才进入多模型融合(fusion)路径,展示了"升级前确认冲突信号不存在"的实践(见 config.yaml#L255-L266)。

八、审计与可观测性

NOT 决策在可审计性上的优势来自两点:

  1. 配置层:被排除的信号(如jailbreak/prompt_injection)在路由定义中被显式命名,审计者无需从下游行为反推排除逻辑;
  2. 运行时:决策引擎在withTrace模式下会为 NOT 节点生成类型为NOT的追踪节点(TraceNode),记录子节点评估结果与最终状态(engine.go#L824-L849),使"该请求为什么没有命中某路由"在追踪链路上可查、可复现。

九、最佳实践小结

综合教程与源码,使用 NOT 决策时建议遵循以下实践:

实践说明
只带一个子条件NOT严格一元,多个条件请先在外层用 AND / OR 组合成一个子节点;畸形 NOT 要么被校验器拒绝,要么被引擎判为不匹配。
显式命名被排除的信号保持description与信号名(如prompt_injection)对应,便于审计。
克制使用NOT 过多会降低规则树可读性,优先考虑把排除条件收敛到共享的信号定义中。
与安全信号目录配套被排除的信号应在 config/fragments/signal/ 中有明确定义(检测方法、阈值、模式),避免"裸引用"未定义的信号名。
用于安全网关与 Premium 隔离在 AND 组合中把 NOT 作为独立子节点,使"危险信号缺席"成为路由匹配的前置条件。

如需查看 NOT 之外的算子与更多决策形态,可继续阅读 config/fragments/decision/ 下的and/、or/、single/、composite/目录;决策求值与校验的完整实现见 engine.go 与 validator_rule_operator.go,真值表契约测试见 rule_tree_shape_test.go。

  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询