mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
fix(zcode): 日期块对齐参考的两条规则(幂等 + 只看第一条),修 off-by-one
通读参考 zcode-identity.ts 的 withContextPrefix 后发现我的实现漏了两条官方行为, 且其中一条藏着真实 bug: ## ① ★ 幂等判断 off-by-one(真 bug) `"<system-reminder>"` 是 **17** 个字符,而我写的是 `c:sub(1, 16) == SR` —— 判断**永远为假** ⇒ 幂等完全失效:同一请求重试/多次 transform 时日期块 会叠加成多层,改变 content 的前缀结构(上游检查的正是这个结构)。 修法:用 `#SR` 算长度,不写死数字。 ## ② 只看**第一条**消息,且它必须 role == "user" 我原来实现成「往后扫第一个 user 消息」。官方行为是:只处理 `messages[0]`, 且若它不是 user 就**完全不动** —— 不是去找后面那条 user。已对齐。 ## ③ 其它类型兜底 参考对非字符串/非数组的 content 也会置为 `[prefix]`,照做。 判据 2 条(新增): - TestZcodeContextPrefixIsIdempotent 已含日期块时不重复插 - TestZcodeContextPrefixOnlyTouchesFirstMessage 首条非 user 时不动、首条 user 时插入 变异 2/2 咬住:把 #SR 改回写死的 16 → 报红;去掉幂等判断 → 报红。 ★ 我自己在写这条判据时又犯了一个错并当场自查出来:断言「文本 content 应保持 字符串」,而 anthropic 基座把文本转成块数组是**正常**的协议转换行为 —— 差点把 正确的实现判成错。判据要验的是「**日期块**有没有被插进来」这个事实,不是 content 的容器类型。 验证:internal/core 335s + internal/gateway 207s 全绿。
This commit is contained in:
@ -9250,18 +9250,40 @@ function adapter.transform_request(raw_body)
|
||||
|
||||
req.system = buildSystemBlocks(caller, opts)
|
||||
|
||||
-- 日期块:插到**第一条** user 消息的 content 数组最前(官方形态)。
|
||||
-- ★ 日期块:只处理**第一条**消息,且它必须是 role == "user"(官方行为)。
|
||||
--
|
||||
-- 两条容易漏掉的规则(照参考 withContextPrefix,「优化」它们会偏离官方):
|
||||
-- ① **幂等**:已以 <system-reminder> 开头则不重复插。不判的话,同一请求
|
||||
-- 在重试/多次 transform 时会把日期块叠成好几层,改变前缀结构;
|
||||
-- ② **只看第一条**:若首条不是 user(例如 assistant 开场),**不动** ——
|
||||
-- 不是"往后找一个 user 消息"。我第一版就是往后找,与官方行为不符。
|
||||
local msgs = req.messages
|
||||
if type(msgs) == "table" then
|
||||
for _, m in ipairs(msgs) do
|
||||
if type(m) == "table" and m.role == "user" then
|
||||
local c = m.content
|
||||
local first = msgs[1]
|
||||
if type(first) == "table" and first.role == "user" then
|
||||
local c = first.content
|
||||
-- 已插过(字符串或块数组两种形态都要判)→ 不重复插。
|
||||
-- ⚠ 长度必须用 #SR 算,不能写死 —— 我第一版写 `sub(1, 16)` 而
|
||||
-- "<system-reminder>" 是 **17** 字符,判断**永远为假** ⇒ 幂等失效、
|
||||
-- 重试时日期块叠加(判据当场抓到)。
|
||||
local SR = "<system-reminder>"
|
||||
local already = false
|
||||
if type(c) == "string" then
|
||||
already = c:sub(1, #SR) == SR
|
||||
elseif type(c) == "table" and type(c[1]) == "table" then
|
||||
local t = c[1].text
|
||||
already = type(t) == "string" and t:sub(1, #SR) == SR
|
||||
end
|
||||
if not already then
|
||||
local blk = contextPrefixBlock()
|
||||
if type(c) == "table" then
|
||||
table.insert(c, 1, contextPrefixBlock())
|
||||
table.insert(c, 1, blk)
|
||||
elseif type(c) == "string" then
|
||||
m.content = { contextPrefixBlock(), { type = "text", text = c } }
|
||||
-- 纯文本 content 转成块数组(官方就是数组形态)
|
||||
first.content = { blk, { type = "text", text = c } }
|
||||
else
|
||||
first.content = { blk }
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user