@jsonstudio/llms
v0.6.1643
Published
> 自 0.4.3 起,原 `rcc-llmswitch-core` npm 包已迁移并更名为 `@jsonstudio/llms`。开发 / 发布流程保持不变,仅需更新 package 依赖与文档引用。
Readme
@jsonstudio/llms(RouteCodex LLM Switch 核心)
自 0.4.3 起,原
rcc-llmswitch-corenpm 包已迁移并更名为@jsonstudio/llms。开发 / 发布流程保持不变,仅需更新 package 依赖与文档引用。
本包是 RouteCodex V2 的“工具治理唯一入口 + 协议转换核心”,实现严格的“前半段/后半段”双向流水线,并通过 defaultSseCodecRegistry 统一治理所有 SSE ↔ JSON 转换。核心事实:
- 唯一入口:所有 HTTP 请求(Chat/Responses/Anthropic/Gemini)必须先进入前半段 Conversion,SSE 输入一律由
SSEInputNode→defaultSseCodecRegistry→ 对应协议 JSON,再映射为标准 Chat 请求。 - 唯一治理点:后半段 Chat Pipeline 是唯一可修改工具行为的位置;其他节点仅做形状转换或流式编解码。
- 唯一出站规则:响应阶段的 output 格式 (JSON/SSE) 只看原入口端点与入站 streaming 标记;providerType 只能决定 inbound converter 与 provider 调用,不得改写 outbound 协议。
- 前半段(Front-Half,Conversion):按端点最小映射为 OpenAI Chat 标准形状(不做工具治理、不兜底)。
- 后半段(Back-Half,Chat Pipeline):统一工具治理、参数修复、MCP 两步暴露、流/非流一致化与最终响应组装。
遵循 AGENTS.md 的 9 大架构原则:职责单一、最小兼容、Fail Fast、无兜底、配置驱动、统一出口/入口。
版本提示:
- 从 0.2.95 起,Responses→Chat 的映射严格“只做形状转换”,不再注入兜底文本或工具;所有治理只在后半段进行。
- 从 0.3.20 起,Anthropic/Chat/Responses 三端在 V2 前后半段上实现双向闭环:请求侧统一 Anth/Chat/Responses→Chat→providerProtocol,响应侧统一 providerProtocol→Chat→入口协议,所有工具调用/结果在 Anth↔Chat↔Anth 与 Responses↔Chat↔Responses 流程中保持字段/分组不变。
- 自 0.4.2 起,Responses ↔ Anthropic 走完整的 Chat 桥后可直接互通:Responses upstream SSE → Chat canonical → Anthropic outbound,以及 Anthropic inbound → Chat canonical → Responses outbound,工具调用/函数输出在双向往返中保持 shape,不再需要任何协议猜测或字段兜底。
- 规划中:Gemini/Chat 双向闭环(
gemini-messages协议)将按与 Anthropic 完全平行的方式接入,仅在前半段/后半段增加独立 codec,不影响现有 Chat/Responses/Anthropic 行为。
👉 Hub Pipeline 是当前唯一入口。 如果 Host 需要直接编排 Virtual Router,请参见 docs/HUB_PIPELINE_USAGE.md,了解如何通过 bootstrapVirtualRouterConfig + HubPipeline 完成初始化与热更新。
👉 ServerTool 是所有 server-side 工具的唯一统一框架。 web_search / vision followup 等服务端工具均在 llmswitch-core 内部通过 ServerTool 执行,Host 只需提供 providerInvoker/reenterPipeline 即可。设计与接入方式详见 docs/SERVERTOOL_DESIGN.md。
总览
入站请求(任意端点)
├─ Chat (/v1/chat) ┐
├─ Responses (/v1/responses) ├─ 前半段 Conversion → 规范化为 Chat 标准 JSON(非流)
└─ Anthropic (/v1/messages) ┘ - 仅做字段/形状映射;不做工具治理/文本收割
- 统一关闭上游直通,前半段需要时将 SSE 合成为非流 JSON
▼
Chat Back-Half(唯一治理入口)
- 工具治理(canonicalize/repair/去重/ID配对)
- MCP 两步暴露(列表→读取/模板)
- reasoning/think 标准化(按端点策略保留或过滤)
- finalize:确保 tool_calls / tool role、finish_reason、content 形状一致
▼
Provider(HTTP 通信,仅转发)
▼
出站响应(统一从 Chat 反向映射)
- Chat:直接输出标准 OpenAI Chat 形状
- Responses:从 Chat 还原 required_action / output / items
- Anthropic:映射为 Anthropic 支持的形状要点:
- “后半段唯一治理点”:三端(Chat/Responses/Anthropic)最终都走同一套 Chat 后半段。
- Responses↔Anthropic 互通:Responses provider 输出先回落到 Chat,再由入口端点(例如
/v1/messages)决定重新构建 Anthropic wire 形状;反向同理,Anthropic 输入先 canonical 化为 Chat 后再落地 Responses 请求,由 outbound streaming 配置决定最终 SSE/JSON 行为。 - “前半段最小映射”:只做协议字段/工具形状转换,不做文本工具收割/兜底/治理。
- “流式一致”:默认不上游直通(upstream SSE OFF),前半段将流合成为非流 JSON,再走后半段,保证一致。
Conversion V3 节点架构与工具治理约束
- 唯一入口:RouteCodex 主包只能通过
src/modules/llmswitch/bridge.ts→dist/bridge/routecodex-adapter.js调用 conversion v3;禁止旁路 import。 - 配置驱动管线:
config/llmswitch/pipeline-config.json(或通过LLMSWITCH_PIPELINE_CONFIG指定)声明每条入/出站线路的节点序列:SSE Input → Provider Input → Chat Process → Provider Output → SSE Output,Responses/Anthropic 线路结构一致。 - 节点职责
nodes/sse/*:入站 SSE 正规化、出站 SSE 序列化以及纯透传,占位但不改写业务数据。nodes/input/*:OpenAI Chat / Responses / Anthropic 请求解析器,校验model/messages,输出 canonicalstandardizedRequest。nodes/process/chat-process-node:唯一的工具治理点,负责 tool_calls 修复、MCP 两步暴露、上下文与 streaming 策略、passthrough 判定等。nodes/output/*:基于processedRequest生成 Provider 协议响应(choices、usage、content blocks)。nodes/response/*:出站方向入口,把 Provider 响应重新映射为 canonical,再交由 output/sse 节点返回。
- 工具治理原则
- 除 Compatibility 层为了满足 Provider/OpenAI 形状所做的最小字段修剪外,任何模块都不得修改工具语义。
- Input/Output/SSE/Response 节点只做格式转换或序列化;所有工具解析/修复/透传/执行判定必须发生在
process链路。 - 如需新增与工具相关的功能,必须在
chat-process-node中实现或扩展新的 process 节点,并由 pipeline 配置显式启用。
流水线节点与职能(入站→出站)
- HTTP Server 入站(端点路由)
- 接收 /v1/chat、/v1/responses、/v1/messages 的原始请求体
- 写入 http-request 快照(可选:.parsed 摘要)
- 前半段 Conversion(本包 v2/conversion/...)
- Chat:校验/轻量规范(保持 OpenAI Chat 标准)
- Responses:instructions + input 映射为 Chat.messages;function_call/tool_result → assistant.tool_calls / role='tool'(只形状,不治理)
- Anthropic:Claude 消息/工具映射为 OpenAI Chat(仅形状)
- SSE:如入站为 SSE,先在前半段合成为非流 JSON(默认),确保后续统一路径
- 后半段 Chat Pipeline(本包 v2/conversion/openai-chat/...)
- request-shape:
- 统一 messages.content 为 string
- 删除不支持字段(如 stream)
- request-tools-stage:
- canonicalizeChatResponseTools:不变式(content=null,finish_reason=tool_calls)
- JSON/JSON5 风格参数修复,失败回退 "{}"
- 工具 ID 生成/去重
- MCP 两步暴露(仅在后半段)
- provider 调用(Provider 层):HTTP 转发 + 快照;不做工具处理
- response-shape:
- 统一 Chat 响应形状、finish_reason、content
- reasoning/think 清理或保留(按端点策略)
- response-tools-stage:
- 工具结果配对(role='tool' 与 tool_calls.id 对齐)
- Responses 反向桥接(仅映射,不治理):required_action + items/output 还原
- 出站响应
- Chat:OpenAI Chat
- Responses:OpenAI Responses
- Anthropic:Anthropic Messages
三端前半段:具体滤波器与映射(伪代码)
Chat 前半段
function chatFrontHalf(payload):
assert(Array.isArray(payload.messages))
drop(payload.stream) // 统一非流
ensureOpenAIChatShape(payload)
return payloadResponses 前半段(严格不兜底)
function responsesFrontHalf(payload):
ctx = captureResponsesContext(payload)
tools = normalizeTools(payload.tools)
msgs = []
if ctx.instructions: msgs.push({role:'system', content:trim(ctx.instructions)})
for entry in payload.input:
switch(entry.type):
case 'function_call'|'tool_call':
name = entry.name || entry.function?.name
args = parseArguments(entry.arguments || entry.function?.arguments)
msgs.push({role:'assistant', tool_calls:[{id:genId(entry), type:'function', function:{name, arguments:stringify(args)}}]})
case 'function_call_output'|'tool_result'|'tool_message':
id = entry.tool_call_id || entry.call_id || entry.tool_use_id || entry.id
out = normalizeToolOutput(entry.output)
msgs.push({role:'tool', tool_call_id:id, content:String(out ?? '')})
default:
// 优先 entry.message.content[]
if entry.message?.content: text = collectText(entry.message.content)
else if entry.content: text = collectText(entry.content)
else if entry.text: text = entry.text
if text: msgs.push({role:normalizeRole(entry.role), content:text})
return { model, messages: msgs, tools: tools, tool_choice: payload.tool_choice }注意:不注入“伪 user”,不做文本工具收割/治理,严格只做形状转换。
Anthropic 前半段
function anthropicFrontHalf(payload):
// Claude → OpenAI Chat 映射
for m in payload.messages:
map role/parts → Chat message
map tools → OpenAI function tools
drop(stream)
return chatPayloadGemini 前半段(规划中)
Gemini REST 规范使用 contents[].role + parts[] + tools.functionDeclarations 的结构,与 Anthropic 的 Messages 协议类似,本包会以完全平行的方式接入一个 gemini-messages 协议线:
目标:
- 仅在前半段/后半段增加
gemini-messages的 codec,既有 Chat/Responses/Anthropic 行为不受影响。 - 非 passthrough 流水线继续遵循统一约束:
entryProtocol → Chat → providerProtocol(请求)与providerProtocol → Chat → entryProtocol(响应),中段只认 Chat。
计划中的 main codec 文件:
src/conversion/codecs/gemini-openai-codec.tsbuildOpenAIChatFromGeminiRequest(gReq):Gemini 请求 → OpenAI Chat 请求buildGeminiRequestFromOpenAIChat(chatReq):OpenAI Chat 请求 → Gemini 请求buildOpenAIChatFromGeminiResponse(gResp):Gemini 响应 → OpenAI Chat completionbuildGeminiFromOpenAIChat(chatResp):OpenAI Chat completion → Gemini 响应(用于“入口/出口都是 Gemini”的回环测试与/v1/gemini出口)
请求侧(Gemini → Chat)映射要点:
contents: Content[]Content.role:"user"→ Chatrole: "user""model"→ Chatrole: "assistant""system"→ Chatrole: "system"(或汇总进顶层 systemInstruction,再统一收敛为 Chat system 消息)"tool"→ Chatrole: "tool"(对应 functionResponse)
Content.parts: Part[]:{ text }→ Chat message.content 文本(可继续使用 text-markup-normalizer 做轻量标准化,不做治理){ functionCall: { name, args } }→- 映射为 Chat
assistant.tool_calls[]:type: "function"function.name = namefunction.arguments = JSON 字符串(通过现有 jsonish.repairArgumentsToString 进行安全修复)
- 映射为 Chat
{ functionResponse: { name, response } }→- 映射为 Chat
role: "tool"消息:tool_call_id与前一轮tool_calls对齐(ID 生成与去重仍由后半段工具治理负责)content为字符串化后的结果(不丢失结构,可放入 metadata 保留原始 JSON)
- 映射为 Chat
- 其它多模态部件(
inlineData/fileData/...):- 按“不丢失信息”原则,先透传到 Chat 消息的
metadata.vendor.gemini.parts[],后续再根据需要扩展。
- 按“不丢失信息”原则,先透传到 Chat 消息的
systemInstruction?: Content:- 统一转换为 Chat 顶层 system 消息(或合并进 messages 中的 system 段),保持与 Anthropic 一致的“所有系统指令在 Chat 段收敛”策略。
tools.functionDeclarations:- 映射为 Chat
tools[].function,字段基本一一对应(name/description/parameters)。
- 映射为 Chat
- 采样/安全配置:
generationConfig.temperature/topP/topK/maxOutputTokens/stopSequences映射为 Chat 顶层采样参数;safetySettings透传到metadata.vendor.gemini.safetySettings,保证信息不丢。
响应侧(Gemini → Chat)映射要点:
candidates[0].content.parts[]:- text 部分 → Chat
choices[0].message.content(多段合并) - functionCall 部分 → Chat
choices[0].message.tool_calls[](与请求侧规则对应)
- text 部分 → Chat
finishReason→ Chatfinish_reason(STOP → "stop"、MAX_TOKENS → "length" 等)usageMetadata→ Chatusage(prompt_tokens/output_tokens/total)
Chat → Gemini(请求与响应):
- 与 Anthropic 完全平行,在
buildGeminiRequestFromOpenAIChat/buildGeminiFromOpenAIChat内按 Chat canonical 重新投影回 Gemini wire 形状:- system →
systemInstruction或系统 Content; - assistant + tool_calls →
role: "model", parts:[{ functionCall... }]; role: "tool"→role: "tool", parts:[{ functionResponse... }];- tools →
functionDeclarations; - 采样/安全配置 →
generationConfig/safetySettings。
- system →
与现有协议的隔离性:
gemini-messages只在 providerProtocol 显式配置为"gemini-messages"时参与路由,且 codec 文件和分支独立存在:- 不修改
openai-chat、openai-responses、anthropic-messages的现有逻辑; - 中段 Chat 工具治理/最终器不依赖具体 provider 协议,只认 canonical Chat completion。
- 不修改
- RouteCodex 主包侧仅需:
providerType: "gemini"→providerProtocol: "gemini-messages";- Provider 继续复用 OpenAIStandard,仅负责 HTTP 通信。
测试与回环(规划):
- 类似 Anthropic/Responses,将新增:
gemini-request-closed-loop.ts:Gemini 请求 → Chat 请求 → Gemini 请求',检查字段/工具/role 数量与顺序一致。gemini-in-out-closed-loop.ts:通过 routecodex-adapter 的processIncoming/processOutgoing对真实 codex-samples 做闭环。- 如有需要,扩展 streaming 测试,验证
streamGenerateContent→ Chat SSE delta → Responses/Anthropic SSE 的一致性。
后半段:主要处理点与职责
- request-shape(v2/conversion/openai-chat/request-shape.ts)
- 统一 messages.content 为 string
- 删除不支持字段(如 stream)
- request-tools-stage(v2/conversion/openai-chat/request-tools-stage.ts)
- canonicalizeChatResponseTools:不变式(content=null,finish_reason=tool_calls)
- jsonish.repairArgumentsToString:把任意形态 arguments 修复为安全 JSON 字符串
- 工具 ID 生成/去重
- MCP 两步暴露(仅在后半段)
- response-shape(v2/conversion/openai-chat/response-shape.ts)
- Chat 响应标准化;think/推理文本按端点策略保留/清理
- response-tools-stage(v2/conversion/openai-chat/response-tools-stage.ts)
- role='tool' 与 tool_calls.id 配对
- Responses 反向桥接(仅映射,不治理):required_action + items/output 还原
流式(SSE)策略
- 默认不上游直通(provider 配置未显式允许时)。
- 前半段把 SSE 合成为非流 JSON;后半段统一处理,再需要时用本包 streaming 模块合成 Responses SSE。
快照与排错
- 快照目录:
~/.routecodex/codex-samples/<endpoint>/*_http-request.json/*_http-request.parsed.json*_pipeline.llmswitch.request.post.json(进入后半段前的 Chat 形状)*_pipeline.provider.request.pre.json(上游请求体,顶层仅 Chat 字段)
- 常见 1214 根因:
- 缺失用户消息(Responses 输入未包含 user 文本,且前半段不兜底)
- 顶层出现 data/metadata/stream 等额外键(应移除“添加逻辑”,而非末端清理)
设计原则与边界
- 工具治理唯一在后半段;前半段绝不进行文本工具收割/参数修复
- 兼容层只做 provider 特定最小映射;Provider 只做 HTTP 通信
- Fail Fast:形状不合规直接报错,不做隐藏兜底
版本与构建
- 构建:
npm run build - 打包:
npm pack - 发布:
npm publish
