12 KiB
@dm/xllm — 面向大模型 / Agent 的使用说明
本文档供 大模型、代码助手、自动化 Agent 在编写或修改调用代码时作为单一事实来源。API 以当前包导出为准(入口:packages/xllm/src/index.ts)。
1. 包与运行时
- 包名:
@dm/xllm - 模块:ESM(
"type": "module") - 运行环境:需存在全局
fetch(现代 Node、浏览器、Bun 等)。无fetch时抛XllmError(NETWORK_ERROR)。 - 安装(monorepo):在依赖包中
bun add @dm/xllm@workspace:*或等价 workspace 引用。
2. 入口与导出
2.1 运行时导出
import { createXllm, XllmError } from "@dm/xllm";
2.2 类型导出(export type)
XClientOptions, XRequest, XResponse, XStreamEvent, XMessage, XContentPart, XProviderName, XThinkingMode, XReasoningEffortInput, XToolDefinition, XToolCall, XToolChoice, XToolExecutor, XToolExecutorMap, XToolErrorStrategy, XChatWithToolsOptions, XChatWithToolsResult, XUsage
3. 客户端:createXllm 与 XllmClient
const xllm = createXllm(options?: XClientOptions): XllmClient;
3.1 XllmClient 方法
| 方法 | 签名 | 用途 |
|---|---|---|
generate |
(request: XRequest) => Promise<XResponse> |
非流式,一次返回完整结构 |
stream |
(request: XRequest) => AsyncIterable<XStreamEvent> |
流式 SSE,用 for await 消费 |
chatWithTools |
(request: XChatWithToolsOptions, executors: XToolExecutorMap) => Promise<XChatWithToolsResult> |
非流式 + 自动工具闭环 |
streamWithTools |
同上,返回 AsyncIterable<XStreamEvent> |
流式 + 自动工具闭环 |
with |
(overrides: Partial<XClientOptions>) => XllmClient |
复制客户端并合并默认配置 |
4. 配置合并与环境变量
4.1 优先级(从高到低)
- 单次请求
XRequest/XChatWithToolsOptions上的字段(provider,model,apiKey,baseURL等) createXllm传入的XClientOptions- 环境变量(仅 Node 等存在
process.env时)
实现位置:packages/xllm/src/runtime/config.ts。
4.2 环境变量
| 变量 | 作用 |
|---|---|
XLLM_API_KEY |
通用 API Key 兜底 |
OPENAI_API_KEY |
provider === "openai-compatible" 时兜底 |
DEEPSEEK_API_KEY |
provider === "deepseek" 时兜底 |
XLLM_BASE_URL |
通用 base URL 兜底 |
4.3 默认 provider / model / baseURL
未指定时:
provider默认为"openai-compatible"model:openai-compatible→gpt-4o-mini,deepseek→deepseek-chatbaseURL:https://api.openai.com/v1或https://api.deepseek.com
5. 供应商(XProviderName)
当前内置:
"openai-compatible":OpenAI Chat Completions 兼容 HTTP 形态(/chat/completions)"deepseek":在兼容层之上设置 DeepSeek 默认baseURL,其余映射与 openai-compatible 一致
接入其它「OpenAI 兼容」网关:不必新增枚举;使用 provider: "openai-compatible",并设置 baseURL(及可选 headers)指向该网关。
非兼容协议:须在库内新增 ProviderAdapter 并注册(见第 11 节)。
5.1 思考模式(Thinking / CoT)— 非全供应商通用
thinking / reasoningEffort 不是抽象意义上的「全市场通用配置」:它们对应 OpenAI Chat Completions 兼容 JSON 里部分厂商采用的字段名(DeepSeek 官方文档中的 OpenAI 形态即如此)。其它供应商若使用不同字段名、不同嵌套路径或不同 API,有两种做法:
providerExtras(推荐先做):在XRequest.providerExtras里写入任意键值,库会在构建完标准字段后Object.assign合并进请求体根对象(后合并者可覆盖同名标准字段)。用于传入厂商文档要求的专有参数,而无需立刻新增适配器。- 新
ProviderAdapter:协议差异大(路径、鉴权、流式格式都不同)时,在packages/xllm内实现并注册适配器。
与 DeepSeek 文档一致的字段映射如下(兼容体):
XRequest 字段 |
请求体字段 | 说明 |
|---|---|---|
thinking?: { type: "enabled" | "disabled" } |
thinking |
思考开关;不传则由服务端默认(一般为 enabled) |
reasoningEffort?: "low" | "medium" | "high" | "max" | "xhigh" |
reasoning_effort |
仅发送 high 或 max:low / medium → high,xhigh → max |
流式:若供应商在 SSE 的 choices[].delta 中返回 reasoning_content(或 reasoning 字符串),库会发出 XStreamEvent:{ type: "reasoning.delta", text: string },与 { type: "text.delta", ... } 区分。
非流式:若 message 中含 reasoning_content 或 reasoning,会填入 XResponse.reasoning;正文仍在 XResponse.text。
provider: "deepseek" 与 provider: "openai-compatible" 均走同一适配器请求体逻辑;其它 同形态 网关若支持相同字段,亦可使用 thinking / reasoningEffort;否则请用 providerExtras。
6. 请求与消息模型
6.1 XRequest(核心字段)
messages: XMessage[](必填)tools?: XToolDefinition[]toolChoice?: "auto" | "none" | { name: string }temperature?,topP?,maxTokens?,metadata?- 可覆盖:
provider?,model?,apiKey?,baseURL? thinking?,reasoningEffort?:见 §5.1(非通用,部分兼容厂商)providerExtras?:合并进 Chat Completions 请求 JSON 根对象(见 §5.1)stream?:由generate/stream内部控制,调用方一般不必依赖此字段语义
6.2 XMessage
system|user|assistant:content: XContentPart[];assistant可选toolCalls?: XToolCall[](协议要求:在role: "tool"之前,assistant 需带对应tool_calls;使用chatWithTools/streamWithTools时由库自动写入);assistant还可选reasoningContent?: string,序列化为reasoning_content(思考模式 + 工具调用时,部分供应商要求后续轮完整回传)tool:必须包含toolCallId: string,且与上一轮 assistant 的tool_calls[].id对应
6.3 XContentPart
{ type: "text"; text: string }{ type: "image"; image: { url: string } }(http(s) 或 data URL;模型是否支持多模态由供应商决定,库只做字段映射)
6.4 XToolDefinition
name, 可选description,parameters(通常为 JSON Schema 风格对象,会原样进入供应商请求体)
7. 响应与流式事件
7.1 XResponse
text,toolCalls,usage?,provider,model,finishReason?,raw?
7.2 XStreamEvent(判别联合,按 event.type 收窄)
type |
含义 |
|---|---|
response.start |
流开始,含 provider, model, requestId? |
text.delta |
文本增量 |
reasoning.delta |
思考链增量(如 reasoning_content) |
tool_call.delta |
工具参数增量 |
tool_call.done |
单个工具调用片段结束 |
response.usage |
用量 |
response.done |
本轮流结束;finishReason 可能为空(例如仅 [DONE] 收尾时) |
消费建议:直出场景主要处理 text.delta;工具场景监听 tool_call.done;结束可用 response.done(注意可能与适配器在 finish_reason 与 [DONE] 路径上的去重逻辑配合,避免重复收尾业务状态)。
8. 工具调用
8.1 仅单次模型调用(手动处理工具)
generate/stream带tools+toolChoice: "auto"- 从
XResponse.toolCalls或流式tool_call.done取id,name,arguments(arguments为 JSON 字符串) - 本地执行后,追加
role: "tool"且toolCallId对齐,再发下一轮请求;assistant 侧需带toolCalls(推荐直接用chatWithTools/streamWithTools避免协议错误)
8.2 chatWithTools(非流式闭环)
- 额外选项:
maxRounds?(默认5),toolErrorStrategy?(默认"throw") - 返回:
{ response, rounds, toolCallsExecuted } - 行为:每轮
generate;若有toolCalls,执行executors[name],将结果写入 tool 消息并追加历史,直到无工具调用或超出maxRounds - 思考模式 + 工具调用(DeepSeek):若本轮
XResponse.reasoning有值(来自message.reasoning_content),库会把同一全文写入历史 assistant 的reasoningContent,下一轮请求序列化为reasoning_content,满足「后续请求须完整回传」的要求。
8.3 streamWithTools(流式闭环)
- 每轮消费完整
stream;收集tool_call.done与拼接的assistantText;同时拼接本轮全部reasoning.delta为reasoningContent;执行工具后追加assistant(含toolCalls与可选reasoningContent)与tool消息,再进入下一轮 - 事件仍原样
yield给调用方
8.4 XToolExecutor 与 XToolExecutorMap
type XToolExecutor = (args: unknown, toolCall: XToolCall) => Promise<unknown> | unknown;
type XToolExecutorMap = Record<string, XToolExecutor>;
args由库对toolCall.arguments做JSON.parse失败时回退为{ raw: string }
8.5 toolErrorStrategy
| 值 | 行为 |
|---|---|
throw |
工具不存在或执行抛错 → 抛 XllmError |
return_tool_error_message |
将错误封装为 tool 消息内容(JSON),让模型继续 |
skip |
跳过该工具,不追加 tool 消息 |
9. 错误:XllmError
code:AUTH_ERROR|RATE_LIMIT|NETWORK_ERROR|INVALID_REQUEST|PROVIDER_ERRORprovider,message, 可选statusCode,requestId,raw- HTTP 非 2xx 时由适配器
normalizeError映射
10. 自定义 fetch 与测试
const xllm = createXllm({
apiKey: "test",
fetch: mockFetch as typeof fetch,
});
用于单测或代理;generate/stream 使用合并后的 fetchImpl。
11. 新增内置供应商(需改库)
- 扩展
XProviderName(packages/xllm/src/core/types.ts) - 实现
ProviderAdapter(packages/xllm/src/providers/types.ts) - 在
registry.ts注册 - 在
runtime/config.ts补充默认model/baseURL/resolveApiKey分支 - 增加
index.test.ts用 mockfetch覆盖
OpenAI 兼容第三方:通常只需 openai-compatible + baseURL,无需新枚举。
12. 实现文件索引(供维护者 / Agent 定位)
| 职责 | 路径 |
|---|---|
| 对外导出 | src/index.ts |
| 客户端 | src/client/create-xllm.ts, generate.ts, stream.ts, chat-with-tools.ts, stream-with-tools.ts, tool-loop-shared.ts |
| 类型 | src/core/types.ts, src/core/errors.ts |
| 配置 | src/runtime/config.ts, http.ts, sse.ts |
| 适配器 | src/providers/openai-compatible.adapter.ts, deepseek.adapter.ts, registry.ts |
13. 生成代码时的检查清单(给大模型)
- 是否设置了
apiKey或对应环境变量? - 兼容网关是否使用
openai-compatible+ 正确baseURL? - 流式是否用
for await遍历XStreamEvent并收窄type? - 工具闭环是否优先使用
chatWithTools/streamWithTools,避免手写tool_calls顺序错误? - 多模态是否确认目标模型支持,否则仅发
text? - 工具失败策略是否为生产环境显式选择
toolErrorStrategy? - 思考类参数是否确认与供应商文档一致?否则是否改用
providerExtras?
文档与 packages/xllm 源码同步维护;若行为与代码不一致,以源码为准。