Skip to content

sunwu51/nanollm

Repository files navigation

nanollm

一个类似litellm的llm模型代理服务,主打一个轻量和本地化,适合个人本地聚合多个模型的场景。

支持的功能:

  • 1 可以配置chat/completions(下面称chat), responsesmessages三种接口(暂不支持google接口)的模型供应商,并且同时对外暴露这三种接口,带/v1前缀。
  • 2 可以配置修改请求中的headersbody,传自定义数据,其中body支持深度合并。
  • 3 可以配置兜底方案,设置兜底分组,如果调用的模型下游接口失败,并且在某个分组中,则会尝试分组其他模型。
  • 4 支持配置文件热更新和本地管理页:modelsfallbackserver.ttfb_timeoutrecord.max_size 保存后立即生效,server.port 写回后需重启进程。

Configure

Example:

server:
  port: 3000 # default 3000
  ttfb_timeout: 5000 # optional, upstream first-byte timeout in ms

record:
  max_size: 100 # optional, default 10

models:
  - name: gpt-5.4-a
    # responses规范
    provider: openai-responses
    base_url: https://example.com/v1
    api_key: YOUR_KEY1
    model: openai/gpt-5.4

  - name: gpt-5.4-b
    # responses规范
    provider: openai-responses
    base_url: https://example.com/v1
    api_key: YOUR_KEY1
    model: openai/gpt-5.4
      
  - name: glm5.1
    # chat/completions规范
    provider: openai-chat
    base_url: https://example.com/v1
    api_key: YOUR_KEY2
    model: glm5.1
    image: true # optional, default true; only effective for openai-chat provider
    ttfb_timeout: 3000 # optional, overrides server.ttfb_timeout
    proxy: http://127.0.0.1:7890 # optional, overrides HTTPS_PROXY/HTTP_PROXY for this model
    headers:
      user-agent: nanollm
    body:
      temperature: 1
      store: false
      text: '{"verbosity":"high"}'
    bodyExpression: |
      ({
        ...body,
        messages: body.messages?.map((message) => ({
          ...message,
          updatedAt: Date.now()
        }))
      })
  
  - name: claude-sonnet-4-6
    # messages规范
    provider: anthropic
    base_url: https://example.com/v1
    api_key: ${YOUR_KEY3_FROM_ENV_VAR}
    model: claude-sonnet-4-6
    ignore_invalid_history: true # optional, default true; Anthropic转换时丢弃空signature的thinking历史

fallback:
  gpt-5.4:
    - gpt-5.4-a
    - gpt-5.4-b
    - glm5.1

Run the proxy server:

npx nanollm --config /path/to/config.yaml

对外提供的模型为所有models[i].namefallback.[group_name]例如上面demo配置就提供了

gpt-5.4-a
gpt-5.4-b
glm5.1
claude-sonnet-4-6
gpt-5.4

这样5个模型,其中gpt-5.4是兜底分组名,当使用这个模型的时候,会在下属列表的模型中寻找可用的模型,尝试顺序为按max(0, 最近5min失败次数-1)升序;如果分数相同,则保持配置里的原始顺序。

动态请求体表达式

models[*].bodyExpression 可以在请求发往上游前动态改写最终 request body。表达式运行时会拿到变量 body,并且必须同步返回新的 body;执行顺序是先应用 body 深度合并,再执行 bodyExpression

models:
  - name: gpt-5.4-a
    provider: openai-chat
    base_url: https://example.com/v1
    api_key: YOUR_KEY1
    model: openai/gpt-5.4
    bodyExpression: |
      ({
        ...body,
        messages: body.messages?.map((message, index) => ({
          ...message,
          content: index === 0 ? `${message.content}\nextra prompt` : message.content
        }))
      })

Anthropic 历史 thinking 签名

models[*].ignore_invalid_history 目前只影响 provider: anthropic 的协议转换,默认值为 true。当 OpenAI Chat/Responses 历史消息里的明文 reasoning 被转换到 Anthropic Messages 时,如果对应 thinking block 没有 signaturesignature 为空字符串,默认会丢弃该 thinking block,避免 Anthropic 上游校验空签名时报错。

如果需要保留旧行为,可以显式设置为 false,这时无签名 thinking 会继续带着空字符串 signature 发往 Anthropic 上游:

models:
  - name: claude-sonnet
    provider: anthropic
    base_url: https://example.com/v1
    api_key: YOUR_KEY
    model: claude-sonnet-4-6
    ignore_invalid_history: false

模型级 HTTP proxy

models[*].proxy 可以为单个模型配置请求下游供应商时使用的 HTTP proxy URL:

models:
  - name: claude-sonnet
    provider: anthropic
    base_url: https://example.com/v1
    api_key: YOUR_KEY
    model: claude-sonnet-4-6
    proxy: http://127.0.0.1:7890

代理优先级为:

  1. models[*].proxy
  2. HTTPS_PROXY
  3. HTTP_PROXY
  4. 直连

proxy 为空字符串或未配置时,会继续回退到环境变量;当前支持 http://https:// 代理 URL。

模型名通配符 *

models[*].name 支持后缀通配写法,可以把一类未显式配置的模型名路由到同一个上游配置:

models:
  - name: gpt-*
    provider: openai-chat
    base_url: https://example.com/v1
    api_key: YOUR_KEY
    model: openai/gpt-*

  - name: gpt-5.5-a
    provider: openai-chat
    base_url: https://example.com/v1
    api_key: YOUR_KEY
    model: openai/gpt-5.5

  - name: "*"
    provider: openai-chat
    base_url: https://example.com/v1
    api_key: YOUR_KEY
    model: fallback-model

fallback:
  gpt-5.5:
    - gpt-5.5-a

规则:

  • * 必须只出现一次,并且只能放在结尾。合法例子:gpt-*claude-**;非法例子:gpt-*-xg*p*tgpt**
  • 匹配优先级是:精确 fallback 分组名 > 精确 model 名 > 通配 model 名。
  • 如果多个通配 model 都能匹配,选择 * 前缀最长的那个;前缀长度相同则按 models 配置顺序。
  • 单独的 * 可以匹配任意请求模型名,适合作为最后兜底。
  • /v1/models 会直接展示配置中的通配名称,例如 gpt-**

以上面配置为例:

  • 请求 gpt-5.5:优先命中 fallback 分组 gpt-5.5
  • 请求 gpt-5.5-a:命中同名 model gpt-5.5-a
  • 请求 gpt-5.6:没有同名分组或同名 model,于是命中 gpt-*
  • 请求 llama-4:命中最后的 *

通配命中时,下游 model 字段里的 * 会被替换为请求中被 models[*].name 捕获的部分。例如:

  • name: gpt-*
  • 请求模型名:gpt-5.6
  • 捕获部分:5.6
  • model: openai/gpt-*
  • 实际发给上游的 modelopenai/gpt-5.6

如果下游 model 中没有 *,则始终使用固定模型名;如果下游 model 中有多个 *,会全部替换为同一个捕获部分。

openai-chat 的图片兼容选项

models[*].image 目前只对 provider: openai-chat 生效,主要用于兼容不同 OpenAI-compatible chat 服务对图片输入的支持差异。默认值为 true

  • image: true(默认):如果请求中包含图片,转为 chat 接口时保留 OpenAI chat 多模态 content 数组,例如:
{
  "role": "user",
  "content": [
    { "type": "text", "text": "请解释这张图" },
    { "type": "image_url", "image_url": { "url": "https://example.com/cat.png" } }
  ]
}
  • image: false:用于 DeepSeek 等只接受 content: string 的 chat 上游;图片、文件、音频等非文本内容会降级为字符串描述,文本内容用换行拼接,例如:
{
  "role": "user",
  "content": "请解释这张图\nAttached image: https://example.com/cat.png"
}

注意:image: false 当前不影响 provider: openai-responsesprovider: anthropic,这两类上游仍按各自协议保留图片结构。

也可以指定配置文件运行:

npx nanollm --config /path/to/config.yaml

如果希望 /status/record 跨进程重启保留最近数据,可以启用 SQLite 存储:

npx nanollm --config /path/to/config.yaml --storage sqlite

不传 --storage 时默认使用 memory,行为与旧版本一致。SQLite 文件固定保存在 ~/.nanollm/nanollm.sqlite3

如果当前目录就有 config.yaml,也可以直接运行:

npx nanollm

注意:npm 发布包不会包含作者本地的 config.yaml,需要你自己准备配置文件。

Config Admin

提供了 http://localhost:3000/admin 的本地配置管理页。

  • 页面使用表单方式编辑常用配置项:全局设置、模型列表和 fallback 分组;server.port 仅展示当前运行值,不提供页面编辑。
  • 常见使用方式是:先在 /admin 中新增或修改模型,再调整 fallback 分组成员顺序,最后点击“保存并应用”立即生效。
  • 页面内提供跳转到 /status/record 的快捷入口,方便保存后继续查看当前模型状态和最近请求记录。
  • 如果只是想放弃当前改动,可以点击“撤销未保存修改”;如果配置文件已被外部改动,可以点击“从服务端刷新”重新加载最新内容。
  • 点击保存后会先把表单数据转换成 YAML、校验配置,再原子写回配置文件。
  • modelsfallbackserver.ttfb_timeoutrecord.max_size 会立即热更新到新请求。
  • server.port 会写回文件,但需要重启进程后才会真正生效。
  • 已有模型上未在表单中展开的高级字段会在保存时自动保留。
  • 如果你在外部手动修改 config.yaml,服务也会自动检测并加载新配置;若新内容非法,则继续保留上一份有效配置并在管理页显示错误。

注意:/admin/config 设计目标是本机单用户管理,不建议暴露到局域网或公网。

Monitor

提供了http://localhost:3000/status的监控页面,可以查看模型健康状态。

提供了http://localhost:3000/record的采样记录页面,可以查看请求记录,对debug非常有用(默认只保留最新10次请求,可通过record.max_size配置修改)。

默认情况下,上述数据都只存在内存中,进程结束即消失。使用 --storage sqlite 启动后,/status 会在 SQLite 中保留最近 1 个月的稀疏 5 分钟统计 bucket(页面仍只展示最近 6 小时),/record 会持久化最近 record.max_size 条请求记录。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors