(tools, options = {})
| 79 | // 两个头都认:方括号是规范形式,尖括号是模型 RL 惯性下仍可能吐出的旧原生形式。 |
| 80 | // 旧形式被平台拦截时我们本来就收不到;漏网的那些照旧回收。 |
| 81 | // |
| 82 | // 这里**不**用否定环视去甩掉 `[tool calls](url)` 这类 Markdown 链接:环视要往后看几十个字符, |
| 83 | // 而流式解析器在 chunk 边界上看到的是半截 `[tool calls]`('(' 还没到),环视据此提前放行, |
| 84 | // 于是整段和流式两条路径对同一输入给出不同结果 —— 分歧比误报本身更糟。改为在**定界点**判断 |
| 85 | // (isMarkdownLinkTail),那时两条路径都已经拿到了触发器到负载之间的完整 tail。 |
| 86 | const TOOL_CALL_TRIGGER_RE = /<[ \t]{0,4}tool_calls?|\[[ \t]{0,4}tool[ \t_-]{1,2}calls?/i; |
| 87 | |
| 88 | // 触发器到负载之间允许的最大间隔。中位数 3、最大 49、128 上界 —— 这些数字全部量自 |
| 89 | // **尖括号**语料(149 段抓包),方括号形式还没有对应的语料。沿用是合理默认:方括号是 |
| 90 | // 我们自己教给模型、要求写干净的形式,装饰理应更少而不是更多。真要偏离,得先抓一批 |
| 91 | // [TOOL CALL] 的真实输出再调,别凭感觉动这个 128。 |
| 92 | const TOOL_CALL_PAYLOAD_WINDOW = 128; |
| 93 | |
| 94 | /** 触发器能匹配到的最长文本,用作 chunk 边界暂存区的上界。取两种形式里更长的那个。 */ |
| 95 | const TOOL_CALL_TRIGGER_MAX = Math.max( |
| 96 | '< tool_calls'.length, |
| 97 | '[ tool calls'.length |
| 98 | ); |
| 99 | |
| 100 | /** |
| 101 | * 闭标签同样会被写坏(`</tool_call">`、`</tool_call result>`),而且常和开标签不对称。 |
| 102 | * 它不携带任何信息,唯一的用处是别把它当正文吐出去,所以只用来**吞掉**,并且有上界。 |
| 103 | */ |
| 104 | // 闭标签也会被写坏:`</tool_call">`、`</tool_call\n>`、`</tool_call result>`、 |
| 105 | // `</tool_call_id_1>`、`</tool_call>`(中文输入法的全角尖括号)。 |
| 106 | // |
| 107 | // 尾巴必须收得很紧。之前用 `[^<>>]{0,64}` 太松:`</tool_call and then 5 > 3` 里那个 '>' |
| 108 | // 让它一口吞掉 24 个字符的**真实回答**。现在只允许「一段不含空白的碎片 + 至多一个单词」, |
| 109 | // 多词散文因此匹配不上,宁可让闭标签泄漏,也绝不吃掉模型的回答。 |
| 110 | const TOOL_CALL_CLOSE_RE = |
| 111 | /^<[ \t]{0,4}\/[ \t]{0,4}tool_calls?[^\s<>>]{0,16}[ \t\r\n]{0,4}(?:[A-Za-z_][\w-]{0,15})?[ \t\r\n]{0,4}[>>]/i; |
| 112 | const TOOL_CALL_CLOSE_BARE_RE = /^<[ \t]{0,4}\/[ \t]{0,4}tool_calls?/i; |
| 113 | // 方括号闭标记:`[END TOOL CALL]` 是规范形式,`[/TOOL CALL]` 是可预期的变体。 |
| 114 | // 与尖括号闭标签同一条纪律:只用来吞掉、有上界、多词散文匹配不上。 |
| 115 | // `[TOOL RESULT: …]` 既没有 END 也没有 '/',按构造匹配不上 —— 模型伪造的结果块 |
| 116 | // 不会被当成闭标记吃掉。 |
| 117 | // 装饰段同时排除 '[' 和 ']':consumeTrailingCloser 的 grow 判据把内部的 '[' |
| 118 | // 当成"这段永远成不了闭标记"的证据(`!slice.includes('[', 1)`),正则这一半也必须认同, |
| 119 | // 否则 `[END TOOL CALL[[[]` 在正则里算闭标记、在 grow 判据里不算,两半自相矛盾。 |
| 120 | // |
| 121 | // **序号臂**(`#3`)。foldToolMessages 把历史里的调用块写成 `[TOOL CALL #n]`,模型每一轮 |
| 122 | // 都读得到它,而这个文件的开头就写着"模型几乎每次都把标签写坏"、以及当年它正是从读到的 |
| 123 | // 形状里学会了 `<tool_call_id_1>` 那一族。镜像回一个 `[END TOOL CALL #3]` 是最自然的模仿, |
| 124 | // 而装饰段 `[^\s[\]]{0,16}` **排除空白**,跨不过 '#' 前面那个空格:实测 `[END TOOL CALL#7]` |
| 125 | // 认得出来,`[END TOOL CALL #7]`(正是我们教出去的那个空格)认不出来 —— 闭标记原样交付给 |
| 126 | // 客户端,stripToolCallResidue 没有 span 可删(交付层绝无第二套扫描), |
| 127 | // containsOrphanProtocolResidue 也返回 false,连 malformed_protocol 重试都不会触发。 |
| 128 | // 所以这里只放宽到**一段数字**:空白 + '#' + 一串数字,绝不认单词。放数字进来不会咬到 |
| 129 | // 回答(散文里不会出现 `[END TOOL CALL #12]`;`[END TOOL CALL #3 我的回答]` 里序号之后 |
| 130 | // 不是 ']',整条仍然匹配不上,回答完好),放单词进来会(见 :103-105 的纪律:宁可漏出 |
| 131 | // 闭标记,也绝不吃掉模型的回答)。位数取 6 而不是 4:这个上界唯一的失效方式是 |
| 132 | // foldToolMessages 的序号涨过它 —— 那正是本次修的这个静默泄漏,宁可给足余量;而多几位 |
| 133 | // 数字对"吃掉回答"的风险恰好是零。序号里既没有 '[' 也没有 ']',与上面 grow 判据的那条 |
| 134 | // 约定仍然一致。裸臂必须**同步**放宽:流尾停在 `[END TOOL CALL #3`(少一个 ']')时, |
no outgoing calls
no test coverage detected