singularity-forge/docs/zh-CN/user-docs/custom-models.md
ace-pm b29c12d5e5 refactor(native): rename gsd_parser.rs to forge_parser.rs
Final rebrand: rename remaining Rust source file to complete the gsd → forge
transition. All parser references already use forge_parser after earlier commits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:58:21 +02:00

378 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 自定义模型
通过 `~/.sf/agent/models.json` 添加自定义 providers 和 modelsOllama、vLLM、LM Studio、代理等
## 目录
- [最小示例](#minimal-example)
- [完整示例](#full-example)
- [支持的 API](#supported-apis)
- [Provider 配置](#provider-configuration)
- [Model 配置](#model-configuration)
- [覆盖内置 Providers](#overriding-built-in-providers)
- [按 model 覆盖](#per-model-overrides)
- [OpenAI 兼容性](#openai-compatibility)
<a id="minimal-example"></a>
## 最小示例
对于本地 modelsOllama、LM Studio、vLLM每个 model 只要求提供 `id`
```json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{ "id": "llama3.1:8b" },
{ "id": "qwen2.5-coder:7b" }
]
}
}
}
```
`apiKey` 在 schema 中是必填,但 Ollama 会忽略它,因此任意值都可以。
有些 OpenAI-compatible server 不支持推理模型使用的 `developer` role。对于这类 provider需要把 `compat.supportsDeveloperRole` 设为 `false`,这样 SF 会改用 `system` message 发送 system prompt。如果该 server 同时也不支持 `reasoning_effort`,还应把 `compat.supportsReasoningEffort` 也设为 `false`
你可以在 provider 级别设置 `compat`,让它应用到该 provider 下的所有 models也可以在 model 级别单独覆盖某个 model。这个设置常见于 Ollama、vLLM、SGLang 以及类似的 OpenAI-compatible server。
```json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "gpt-oss:20b",
"reasoning": true
}
]
}
}
}
```
<a id="full-example"></a>
## 完整示例
当你需要显式覆盖默认值时,可以写成更完整的配置:
```json
{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{
"id": "llama3.1:8b",
"name": "Llama 3.1 8B (Local)",
"reasoning": false,
"input": ["text"],
"contextWindow": 128000,
"maxTokens": 32000,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
```
每次打开 `/model` 时,这个文件都会重新加载。可以在会话过程中直接编辑,无需重启。
<a id="supported-apis"></a>
## 支持的 API
| API | 说明 |
|-----|------|
| `openai-completions` | OpenAI Chat Completions兼容性最好 |
| `openai-responses` | OpenAI Responses API |
| `anthropic-messages` | Anthropic Messages API |
| `google-generative-ai` | Google Generative AI |
`api` 可以设置在 provider 级别(作为该 provider 下所有 models 的默认值),也可以设置在 model 级别(覆盖单个 model
<a id="provider-configuration"></a>
## Provider 配置
| 字段 | 说明 |
|------|------|
| `baseUrl` | API endpoint URL |
| `api` | API 类型(见上) |
| `apiKey` | API key见下方值解析 |
| `headers` | 自定义请求头(见下方值解析) |
| `authHeader` | 设为 `true` 时,自动添加 `Authorization: Bearer <apiKey>` |
| `models` | model 配置数组 |
| `modelOverrides` | 针对该 provider 的内置 models 做按 model 覆盖 |
<a id="value-resolution"></a>
### 值解析
`apiKey``headers` 支持三种写法:
- **Shell 命令:** `"!command"`,执行后读取 stdout
```json
"apiKey": "!security find-generic-password -ws 'anthropic'"
"apiKey": "!op read 'op://vault/item/credential'"
```
- **环境变量:** 取对应环境变量的值
```json
"apiKey": "MY_API_KEY"
```
- **字面量:** 直接使用
```json
"apiKey": "sk-..."
```
<a id="command-allowlist"></a>
#### 命令允许列表
Shell 命令(`!command`)只能执行一组已知的凭据工具。只有以下前缀开头的命令才会被允许:
`pass`、`op`、`aws`、`gcloud`、`vault`、`security`、`gpg`、`bw`、`gopass`、`lpass`
不在列表中的命令会被阻止,最终该值会解析为 `undefined`。同时会向 stderr 输出一条警告。
为了防止注入,命令参数中的 shell 操作符(`;`、`|`、`&`、`` ` ``、`$`、`>`、`<`)同样会被阻止。
**自定义允许列表:**
如果你使用的凭据工具不在默认列表中,可以在全局设置(`~/.sf/agent/settings.json`)里覆盖:
```json
{
"allowedCommandPrefixes": ["pass", "op", "sops", "doppler", "mycli"]
}
```
这会完全替换默认列表,因此如果你还想保留默认命令,需要一起写进去。
你也可以设置 `SF_ALLOWED_COMMAND_PREFIXES` 环境变量(逗号分隔)。环境变量优先级高于 settings.json
```bash
export SF_ALLOWED_COMMAND_PREFIXES="pass,op,sops,doppler"
```
> **注意:** 这是一个仅全局生效的设置。项目级 settings.json`<project>/.sf/settings.json`)不能覆盖命令 allowlist以防克隆下来的仓库提升命令执行权限。
### 自定义 Headers
```json
{
"providers": {
"custom-proxy": {
"baseUrl": "https://proxy.example.com/v1",
"apiKey": "MY_API_KEY",
"api": "anthropic-messages",
"headers": {
"x-portkey-api-key": "PORTKEY_API_KEY",
"x-secret": "!op read 'op://vault/item/secret'"
},
"models": [...]
}
}
}
```
<a id="model-configuration"></a>
## Model 配置
| 字段 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `id` | 是 | — | Model 标识符(会原样传给 API |
| `name` | 否 | `id` | 可读的 model 标签,用于匹配(例如 `--model` 模糊匹配)并显示在详情 / 状态文字里 |
| `api` | 否 | provider 的 `api` | 为这个 model 覆盖 provider 的 API 类型 |
| `reasoning` | 否 | `false` | 是否支持扩展 thinking |
| `input` | 否 | `["text"]` | 输入类型:`["text"]` 或 `["text", "image"]` |
| `contextWindow` | 否 | `128000` | 上下文窗口大小tokens |
| `maxTokens` | 否 | `16384` | 最大输出 tokens |
| `cost` | 否 | 全为 0 | `{"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}`(每百万 tokens |
| `compat` | 否 | provider 的 `compat` | OpenAI 兼容性覆盖项。如果 provider 和 model 两边都配置了,会合并 |
当前行为:
- `/model` 与 `--list-models` 都是按 model `id` 列出条目
- 配置里的 `name` 会用于 model 匹配,以及详情 / 状态文本展示
<a id="overriding-built-in-providers"></a>
## 覆盖内置 Providers
如果你想把某个内置 provider 经由代理路由出去,但又不想重新定义全部 models可以这样写
```json
{
"providers": {
"anthropic": {
"baseUrl": "https://my-proxy.example.com/v1"
}
}
}
```
这样所有内置 Anthropic models 仍然可用。已有的 OAuth 或 API key 认证也会继续生效。
如果你想把自定义 models 合并进某个内置 provider就同时提供 `models` 数组:
```json
{
"providers": {
"anthropic": {
"baseUrl": "https://my-proxy.example.com/v1",
"apiKey": "ANTHROPIC_API_KEY",
"api": "anthropic-messages",
"models": [...]
}
}
}
```
合并规则如下:
- 内置 models 会保留
- 自定义 models 会按 `id` 在该 provider 下执行 upsert
- 如果某个自定义 model 的 `id` 与内置 model 相同,自定义 model 会替换那个内置 model
- 如果某个自定义 model 的 `id` 是新的,它会作为新增条目并列出现
<a id="per-model-overrides"></a>
## 按 model 覆盖
如果你只想修改某些特定的内置 model而不想替换整个 provider 的 model 列表,可以使用 `modelOverrides`。
```json
{
"providers": {
"openrouter": {
"modelOverrides": {
"anthropic/claude-sonnet-4": {
"name": "Claude Sonnet 4 (Bedrock Route)",
"compat": {
"openRouterRouting": {
"only": ["amazon-bedrock"]
}
}
}
}
}
}
}
```
`modelOverrides` 支持的字段包括:`name`、`reasoning`、`input`、`cost`(可部分覆盖)、`contextWindow`、`maxTokens`、`headers`、`compat`。
行为说明:
- `modelOverrides` 只会应用到内置 provider 的 models 上
- 未知的 model ID 会被忽略
- 可以把 provider 级别的 `baseUrl` / `headers` 与 `modelOverrides` 组合使用
- 如果某个 provider 同时定义了 `models`,那么自定义 models 会在应用完内置覆盖后再合并;如果它的 `id` 与已覆盖的内置 model 相同,最终会以自定义 model 为准
<a id="openai-compatibility"></a>
## OpenAI 兼容性
对于只部分兼容 OpenAI 的 providers可通过 `compat` 字段修正行为。
- provider 级别的 `compat` 会作为该 provider 下所有 models 的默认值
- model 级别的 `compat` 会覆盖该 model 的 provider 级别设置
```json
{
"providers": {
"local-llm": {
"baseUrl": "http://localhost:8080/v1",
"api": "openai-completions",
"compat": {
"supportsUsageInStreaming": false,
"maxTokensField": "max_tokens"
},
"models": [...]
}
}
}
```
| 字段 | 说明 |
|------|------|
| `supportsStore` | Provider 是否支持 `store` 字段 |
| `supportsDeveloperRole` | 是否使用 `developer` 而非 `system` role |
| `supportsReasoningEffort` | 是否支持 `reasoning_effort` 参数 |
| `reasoningEffortMap` | 把 SF 的 thinking levels 映射到 provider 专属 `reasoning_effort` 值 |
| `supportsUsageInStreaming` | 是否支持 `stream_options: { include_usage: true }`(默认 `true` |
| `maxTokensField` | 使用 `max_completion_tokens` 还是 `max_tokens` |
| `requiresToolResultName` | tool result message 中是否必须包含 `name` |
| `requiresAssistantAfterToolResult` | tool result 之后、user message 之前是否需要插入 assistant message |
| `requiresThinkingAsText` | 是否把 thinking block 转成纯文本 |
| `thinkingFormat` | 使用 `reasoning_effort`、`zai`、`qwen` 或 `qwen-chat-template` 的 thinking 参数格式 |
| `supportsStrictMode` | 是否在 tool definitions 中包含 `strict` 字段 |
| `openRouterRouting` | 传给 OpenRouter 的路由配置,用于 model/provider 选择 |
| `vercelGatewayRouting` | Vercel AI Gateway 的路由配置,用于 provider 选择(`only`、`order` |
`qwen` 使用顶层 `enable_thinking`。对于要求 `chat_template_kwargs.enable_thinking` 的本地 Qwen-compatible server请使用 `qwen-chat-template`。
示例:
```json
{
"providers": {
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "OPENROUTER_API_KEY",
"api": "openai-completions",
"models": [
{
"id": "openrouter/anthropic/claude-3.5-sonnet",
"name": "OpenRouter Claude 3.5 Sonnet",
"compat": {
"openRouterRouting": {
"order": ["anthropic"],
"fallbacks": ["openai"]
}
}
}
]
}
}
}
```
Vercel AI Gateway 示例:
```json
{
"providers": {
"vercel-ai-gateway": {
"baseUrl": "https://ai-gateway.vercel.sh/v1",
"apiKey": "AI_GATEWAY_API_KEY",
"api": "openai-completions",
"models": [
{
"id": "moonshotai/kimi-k2.5",
"name": "Kimi K2.5 (Fireworks via Vercel)",
"reasoning": true,
"input": ["text", "image"],
"cost": { "input": 0.6, "output": 3, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 262144,
"maxTokens": 262144,
"compat": {
"vercelGatewayRouting": {
"only": ["fireworks", "novita"],
"order": ["fireworks", "novita"]
}
}
}
]
}
}
}
```