Files
Cyrene_v2/internal/config/guardrail.go
T
AskaEth 137b3e9eaf
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(guardrail): 输入上限与附件注入预算统一换算(多附件不再被拦)
- 残留缺口:护栏量整段装配请求(字符),注入预算是单份 ⇒ 1M 窗口下 2×349525 就顶到旧上限 699050、5 份必被拦。根因是两套换算(上限按 1char=1token,注入按 loop 的 4char=1token)。

- 修法:上限 = max(262144, 推导预算 tokens × loop.CharsPerToken) ⇒ 1M 窗口 2,796,200 字符(5 份满额注入 1,747,625 有余量);不变量升级为「N 份」(maxChatDocumentsPerTurn=5,带出处注释);WARN 文案升级为多附件口径(max_documents/document_inject_chars/document_inject_total_chars/guardrail_max_input_chars/hint)。

- 同一换算常量复用:在 loop/compactor.go 的既有无导出常量旁加导出别名 loop.CharsPerToken = charsPerToken(2 行、零行为改动、不重命名)—— 这是本切片唯一触及「不要碰」清单的改动,已由主会话复核接受(替代方案是复制一个字面量 4,会重新造出两套换算)。

- 同步陈旧注释(config/guardrail.go、wire_runtime.go 里的旧公式)与 .env.example(新公式 + 多附件口径 + 1M 算例 2796200/1747625),并有「模板 ↔ 代码现算值」的一致性用例。

- 主会话独立验收:隔离 worktree(G2b 的 7 文件,排除 B1/ST1 在途)build/vet 绿、全树 59 包 0 FAIL、护栏用例 17 PASS、双平台交叉编译绿。

- 残留(未修,需裁决):预算关闭或落在 (0,81920) token 的部署,派生态上限 = 262144 而 5×64K = 327680 ⇒ 满载附件回合仍可能被拦(启动会记 WARN,不静默);真正的下限应取 max(262144, maxDocs×每份注入)。
2026-10-04 19:41:44 +08:00

49 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
// 本文件承载内容护栏里**由部署方声明**的那一部分(design/04 §5)。
//
// 敏感信息阻断档是代码内定的安全默认值(见组合根的 buildGuardrailPipeline),
// 不对外开放配置:它不该被一次误配置关掉。这里放的是两项**与部署环境相关**的
// 取值 —— 内容策略词表,以及请求侧字符上限的显式覆盖。
//
// 请求侧字符上限为什么必须能配:它的默认值由**推导出的上下文预算**派生
// (预算被抬高到 1M 窗口能吃的量级后,硬编码的 256 Ki 字符反而成了瓶颈,
// 会把「分享大文件」整条主线打成 guardrail_input 结束,见
// cmd/cyrene/wire_guardrail.go 的说明)。派生值已经能自适应窗口,本键只是
// 给需要收紧或放宽的部署留一个显式落点。
// GuardrailMaxInputCharsEnv 是请求侧字符上限的配置键名。
//
// 它必须与 GuardrailConfig.MaxInputChars 的 env 标签**逐字一致**:WARN 文案要能
// 直接点出「改哪个键」,因此键名只能有一处出处(一致性由 guardrail_test.go 钉住)。
const GuardrailMaxInputCharsEnv = "GUARDRAIL_MAX_INPUT_CHARS"
// GuardrailConfig 承载内容护栏里由部署方声明的取值(design/04 §5)。
type GuardrailConfig struct {
// Keywords 是内容策略关键词表(GUARDRAIL_KEYWORDS,逗号分隔)。
//
// **为空时不挂关键词护栏**:项目绝不硬编一张词表 —— 凭空猜出来的词
// 只会误伤正常对话,且没有任何部署方能对它的命中负责。只有显式声明过
// 的词才会被拦(大小写不敏感的子串匹配)。
Keywords []string `env:"GUARDRAIL_KEYWORDS"`
// MaxInputChars 是**请求侧**字符上限的显式覆盖(GUARDRAIL_MAX_INPUT_CHARS)。
// 三态:
//
// - 未设置 / 0(默认)= 按推导出的上下文预算派生:
// 输入上限 = max(256 Ki, 推导预算 token 数 × CharsPerToken)
// (换算必须与附件注入预算同一个常量 loop.CharsPerToken,理由见
// cmd/cyrene/wire_guardrail.go 的说明);
// - > 0 = 显式覆盖(原样使用);
// - < 0 = 非法:记一条 WARN 并回落派生值,绝不静默生效(AGENTS §10)。
//
// 派生态下「单回合全部附件的注入预算合计 ≤ 输入上限」这条关系不变量恒成立
// (1M 窗口 ⇒ 5 份 × 349525 = 1747625 字符 ≤ 上限 2796200 字符);显式覆盖把它
// 压到合计之下时,组合根记一条 WARN 明说「多附件回合会被护栏拦下」,而不是等到
// 用户上传多个大文件时才以一个没有上下文的 input_length_exceeded 表现出来。
//
// 它只动**输入**侧:输出上限(65536)防的是模型输出失控,与上下文预算无关,
// 因此不随本键变化、也不开放配置。
MaxInputChars int `env:"GUARDRAIL_MAX_INPUT_CHARS"`
}