ai-llm-service
v1.0.4
Published
A lightweight isomorphic LLM service class with streaming, retry, fallback, and multi-provider support
Downloads
615
Readme
AiLLMService
轻量同构 LLM 调用服务类,基于 fetch 同时运行在浏览器、Node.js、小程序、React Native 端,统一各厂商 API 差异。
✨ 特性
- 🎯 类型安全 - 完整的 TypeScript 类型推导,编译时捕获错误
- 🔀 多模型/多 Provider - 统一接口适配 OpenAI、Anthropic、DeepSeek、Moonshot 等厂商
- 🤖 Anthropic 原生支持 - 适配 Claude Messages API,自动转换消息/工具/流式格式
- 🔄 自动主备切换 - 主模型失败自动降级到备用模型,支持多级 fallback 链
- 🔁 重试机制 - 可配置重试次数、延迟、退避策略(固定/指数),自动识别可重试错误(429/5xx)
- 🌊 流式封装 -
onStream回调自动拼接 content / reasoning / toolCalls,无需手动处理 SSE - 🛠 Tools Use - 声明式 tools 定义 +
onToolCall回调,支持自动多轮 tool 调用,内置循环保护 - 📦 JSON 容错解析 - 集成
json-repair-js+best-effort-json-parser,三层级降级解析 LLM 输出的破损/不完整 JSON - 📝 HTML/Markdown 提取 -
responseFormat: 'html'/'markdown'自动去掉 code block 包裹 - 🧠 思维链支持 -
enableThinking自动映射为各厂商格式 - ⚙️ 默认参数 -
defaultParams避免每次重复传参 - 🪵 调试日志 - 内置 Logger 中间件,
debug: true一键开启请求/响应/错误日志 - 🔌 中间件机制 - 请求/响应管道,可插入自定义逻辑(鉴权、限流、缓存等)
- ⏱ 超时控制 - 全局 + 单次请求超时,基于 AbortController
- 🛑 请求中止 - 传入
signal: AbortSignal随时中止请求 - 📊 用量追踪 - 返回
usage(prompt/completion/total tokens) - 🔄 同构兼容 - 基于
fetch实现,可运行在浏览器、Node.js、小程序、Cloudflare Worker 等环境
📦 安装
npm install ai-llm-serviceNode.js 版本要求
| Node.js | 说明 |
| ------- | ------------------------------------------- |
| ≥ 18 | 内置 fetch,开箱即用 |
| 16 ~ 17 | 需安装 node-fetch 并通过 fetch 配置传入 |
// Node.js 16/17 兼容方式
import fetch from 'node-fetch'
const llm = new AiLLMService({
providers: [...],
defaultModel: 'gpt-4o',
fetch,
})🚀 快速开始
import { AiLLMService } from 'ai-llm-service'
const llm = new AiLLMService({
providers: [
{
name: 'openai',
baseURL: 'https://api.openai.com/v1',
apiKey: 'sk-xxx',
models: ['gpt-4o', 'gpt-4o-mini'],
},
{
name: 'anthropic',
providerType: 'anthropic',
baseURL: 'https://api.anthropic.com',
apiKey: 'sk-ant-xxx',
models: ['claude-sonnet-4-5'],
},
{
name: 'deepseek',
baseURL: 'https://api.deepseek.com/v1',
apiKey: 'sk-xxx',
models: ['deepseek-chat', 'deepseek-reasoner'],
},
],
defaultModel: 'gpt-4o',
debug: true,
})基础调用
const res = await llm.completion({
messages: [{ role: 'user', content: '你好' }],
})
// res.content → "你好!有什么可以帮你的?"
// res.model → "gpt-4o"
// res.provider → "openai"
// res.usage → { prompt_tokens: 10, completion_tokens: 15, total_tokens: 25 }流式调用
const res = await llm.completion({
messages: [{ role: 'user', content: '写一首诗' }],
stream: true,
onStream: (chunk, { delta, reasoningDelta, content, reasoning, toolCalls, done }) => {
process.stdout.write(delta) // 增量输出
},
})
// 流结束后 res.content 是拼接好的完整内容
// res.reasoning → 思维链完整内容
// res.toolCalls → 累计的 tool 调用JSON 解析
// responseFormat: 'json' — 自动从 content 中解析 JSON(支持 markdown code block)
const res = await llm.completion({
messages: [{ role: 'user', content: '返回一个包含 name 和 age 的 JSON' }],
responseFormat: 'json',
})
// res.content → { name: "张三", age: 25 } (object)
// responseFormat: 'json_object' — 同时发送 API 的 response_format + 自动解析
const res = await llm.completion({
messages: [...],
responseFormat: 'json_object',
})HTML / Markdown 提取
// responseFormat: 'html' — 自动去掉 ```html ... ``` 包裹
const res = await llm.completion({
messages: [{ role: 'user', content: '生成一个登录页面的 HTML' }],
responseFormat: 'html',
})
// res.content → "<div class=\"login\">...</div>" (纯 HTML,无 code block)
// responseFormat: 'markdown' — 自动去掉 ```markdown / ```md ... ``` 包裹
const res = await llm.completion({
messages: [{ role: 'user', content: '生成一份 Markdown 格式的周报' }],
responseFormat: 'markdown',
})Tools Use
const res = await llm.completion({
messages: [{ role: 'user', content: '北京今天天气怎么样?' }],
tools: [
{
type: 'function',
function: {
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名称' },
},
required: ['city'],
},
},
},
],
onToolCall: async toolCall => {
const { city } = toolCall.function.args // 自动解析
const result = await getWeather(city)
return JSON.stringify(result) // 自动回填到 messages 继续对话
},
})默认参数
const llm = new AiLLMService({
providers: [...],
defaultModel: 'gpt-4o',
defaultParams: {
temperature: 0.7,
maxTokens: 4096,
responseFormat: 'json',
},
})
// 自动继承 defaultParams,无需重复传参
const res = await llm.completion({
messages: [{ role: 'user', content: '你好' }],
})
// 单次调用可覆盖
const res2 = await llm.completion({
messages: [...],
temperature: 0.1,
})主备切换
const llm = new AiLLMService({
providers: [
{ name: 'openai', baseURL: '...', apiKey: 'sk-xxx', models: ['gpt-4o'] },
{
name: 'anthropic',
providerType: 'anthropic',
baseURL: '...',
apiKey: 'sk-ant-xxx',
models: ['claude-sonnet-4-5'],
},
{ name: 'deepseek', baseURL: '...', apiKey: 'sk-xxx', models: ['deepseek-chat'] },
],
defaultModel: 'gpt-4o',
fallbacks: {
'gpt-4o': ['claude-sonnet-4-5', 'deepseek-chat'], // 多级降级
},
})重试机制
const llm = new AiLLMService({
providers: [...],
defaultModel: 'gpt-4o',
retry: {
maxRetries: 3, // 最大重试次数
delay: 1000, // 初始延迟 ms
backoff: 'exponential', // 'fixed' | 'exponential'
},
})请求中止
const controller = new AbortController()
llm.completion({ messages: [...], signal: controller.signal })
controller.abort() // 随时中止自定义中间件
llm.use({
name: 'auth',
beforeRequest: (ctx, request) => {
request.headers['X-Custom-Auth'] = 'token'
return request
},
afterResponse: (ctx, result) => {
console.log('cost:', result.usage)
return result
},
})自定义 Provider
import { BaseProvider, registerProvider } from 'ai-llm-service'
class MyProvider extends BaseProvider {
buildRequest(params, model) {
/* ... */
}
parseResponse(data) {
/* ... */
}
parseStreamChunk(data) {
/* ... */
}
}
registerProvider('my-provider', MyProvider)
const llm = new AiLLMService({
providers: [{ name: 'my-provider', baseURL: '...', apiKey: 'xxx' }],
defaultModel: 'my-model',
})📖 API
new AiLLMService(config)
| 参数 | 类型 | 必填 | 说明 |
| --------------- | -------------------------- | ---- | ------------------------------ |
| providers | ProviderConfig[] | ✅ | Provider 列表 |
| defaultModel | string | ✅ | 默认模型 |
| fallbacks | Record<string, string[]> | | 模型主备映射 |
| defaultParams | DefaultableParams | | 默认参数,每次 completion 合并 |
| retry | Partial<RetryConfig> | | 重试配置 |
| timeout | number | | 全局超时 ms,默认 60000 |
| maxToolRounds | number | | Tool 调用最大轮数,默认 20 |
| debug | boolean | | 调试模式,默认 false |
| logger | Logger | | 自定义 logger |
| fetch | typeof fetch | | 自定义 fetch(Node < 18 兼容) |
ProviderConfig
| 参数 | 类型 | 必填 | 说明 |
| -------------- | ------------------------- | ---- | ----------------------------- |
| name | string | ✅ | Provider 名称标识 |
| baseURL | string | ✅ | API 基础地址 |
| apiKey | string | ✅ | API Key |
| providerType | 'openai' \| 'anthropic' | | API 格式类型,默认 'openai' |
| models | string[] | | 支持的模型列表,用于自动路由 |
| headers | Record<string, string> | | 额外自定义请求头 |
| fetch | typeof fetch | | 自定义 fetch |
CompletionResult
| 字段 | 类型 | 说明 |
| ----------- | ------------------ | ----------------------- |
| content | string \| object | 文本内容或解析后的 JSON |
| reasoning | string | 思维链完整内容 |
| toolCalls | ToolCall[] | Tool 调用列表 |
| usage | Usage | 用量统计 |
| model | string | 实际使用的模型 |
| provider | string | 实际使用的 provider |
StreamParsedData(onStream 第二个参数)
| 字段 | 类型 | 说明 |
| ---------------- | ------------ | ---------------------- |
| delta | string | 本次增量文本 |
| reasoningDelta | string | 本次增量思维链 |
| content | string | 累计文本(拼接好的) |
| reasoning | string | 累计思维链(拼接好的) |
| toolCalls | ToolCall[] | 累计 tool 调用 |
| done | boolean | 是否结束 |
📚 文档
🔧 构建
npm run build # father 构建 esm + cjs
npm run typecheck # TypeScript 类型检查
npm test # 运行集成测试📄 License
MIT © lizhooh
