Merge remote-tracking branch 'origin/master' into feat/account-provider-signout
This commit is contained in:
commit
4dae756ced
7 files changed
+56
-44
No files matched your search
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/user/guide/providers.md
|
||||
providers.md: 81ccba8b0665fa1f7666b5724aab4a8b3d183bbf
|
||||
providers.zh.md: 1afb7e7bf616206290e5cc4a90ef45b4250a9596
|
||||
providers.md: 773ff6722a8558720a458f7574940972cf976958
|
||||
providers.zh.md: 6d618ed1e9eec17e8c4a000d00cc6987d0a6af14
|
||||
@@ -20,7 +20,7 @@ Providers that sign in with OAuth, such as Codex, are not supported here yet.
|
||||
|
||||
## Add a custom model API
|
||||
|
||||
Switch the card to **Custom model API** for a relay, a company gateway, a self-hosted server, or any provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model. The **API protocol** must be the one your gateway speaks, and the picker offers three: OpenAI Chat Completions, OpenAI Responses, and Anthropic Messages, stored in `settings.yaml` as `openai-completions`, `openai-responses`, and `anthropic-messages`. A provider speaks one protocol, so a gateway that serves two needs two providers.
|
||||
Switch the card to **Custom model API** for a relay, a company gateway, a self-hosted server, or any provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model. The **API protocol** must be the one your gateway speaks, and the picker offers three: OpenAI Chat Completions, OpenAI Responses, and Anthropic Messages, stored in the active profile's `cordis.patch.yml` as `openai-completions`, `openai-responses`, and `anthropic-messages`. A provider speaks one protocol, so a gateway that serves two needs two providers.
|
||||
|
||||

|
||||
|
||||
@@ -44,6 +44,8 @@ The generated [plugin configuration catalog](../../config-catalog.md) lists ever
|
||||
|
||||
::: tip Additional settings
|
||||
The Models page exposes the API key, display name, base URL, API protocol, and each model's id, display name, context window, max output tokens, and input types. Configure reasoning effort levels, request-compatibility switches, headers, timeouts, and retry policy in `$DSH_HOME/profiles/<profile>/cordis.patch.yml`, the same document the page writes. Edit it directly, or, when the browser runs on the same machine as the server, open it with **Open configuration file** in the Settings header; the adapters re-read it on the next request, so nothing needs a restart. The subsections below cover the fields most gateways need.
|
||||
|
||||
For the standard Web UI launch with `dsh web`, `<profile>` is `web`, so the path is `$DSH_HOME/profiles/web/cordis.patch.yml`. If you launch a custom profile, use the name selected at startup instead.
|
||||
:::
|
||||
|
||||
### Image input
|
||||
@@ -132,14 +134,14 @@ Each key is a level the menu offers, and its value is the spelling sent on the w
|
||||
An `off` left empty sends nothing, which only stops a model that thinks on request; an `off` given a value sends that value as `reasoning_effort` instead. A model that thinks unless told not to — DeepSeek V4 behind an OpenAI-compatible gateway, for example — needs `compat.thinkingFormat: deepseek`, which makes `off` send `thinking: {type: disabled}` and every other level send `thinking: {type: enabled}` beside the effort:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- id: deepseek-v4-pro
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
reasoningEfforts:
|
||||
off:
|
||||
high: high
|
||||
max: max
|
||||
models:
|
||||
- id: deepseek-v4-pro
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
reasoningEfforts:
|
||||
off:
|
||||
high: high
|
||||
max: max
|
||||
```
|
||||
|
||||
A built-in provider's model whose gateway does not reason loses its levels with `reasoningEfforts: false` under `modelOverrides`; selecting an effort for it is then refused as `UNSUPPORTED_REASONING_EFFORT`. DeepSeek's own route needs none of this: its models already offer `off`, `low`, `high`, and `max`, and `llm-deepseek.reasoningEffort` sets the default the picker starts from:
|
||||
@@ -174,11 +176,11 @@ Two account for most of it. A model that declares reasoning has its system promp
|
||||
A route's `compat` is the default for its models, and a model's own wins field by field, so one model can be corrected without restating the route:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- id: my-model
|
||||
- id: my-reasoner
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
models:
|
||||
- id: my-model
|
||||
- id: my-reasoner
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
```
|
||||
|
||||
What neither sets keeps the installed catalog's value for that model, and what the catalog does not describe falls to pi-ai's detection. Give every switch you name a value: a key left empty (`supportsDeveloperRole:`) is refused rather than ignored, because an empty value would erase what the catalog knows while saying nothing in its place. A name no protocol accepts is refused too, and the message lists the ones that are available.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
## 添加自定义模型 API
|
||||
|
||||
对于中转站、公司网关、自建服务器或已安装目录中不存在的提供商,把卡片切换到**自定义模型 API**。提供小写 Provider ID、基础 URL、API 协议、凭据和至少一个模型。**API 协议**必须选网关实际使用的那一种,选择框提供三种:OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages,在 `settings.yaml` 中分别存为 `openai-completions`、`openai-responses` 和 `anthropic-messages`。一个提供商只使用一种协议,网关同时提供两种时需要建两个提供商。
|
||||
对于中转站、公司网关、自建服务器或已安装目录中不存在的提供商,把卡片切换到**自定义模型 API**。提供小写 Provider ID、基础 URL、API 协议、凭据和至少一个模型。**API 协议**必须选网关实际使用的那一种,选择框提供三种:OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages,在当前 profile 的 `cordis.patch.yml` 中分别存为 `openai-completions`、`openai-responses` 和 `anthropic-messages`。一个提供商只使用一种协议,网关同时提供两种时需要建两个提供商。
|
||||
|
||||

|
||||
|
||||
@@ -44,6 +44,8 @@ Provider ID 是永久的,因为请求、已保存会话、模型默认值和
|
||||
|
||||
::: tip 其他设置
|
||||
模型页提供 API 密钥、显示名称、API 地址、API 协议,以及每个模型的 ID、显示名称、上下文窗口、最大输出 token 数和输入类型。推理等级、请求兼容性开关、请求头、超时和重试策略在 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 中设置,也就是模型页写入的同一份文档。可以直接编辑它;浏览器与服务器在同一台机器时,也可以点击设置页顶部的**打开配置文件**打开它。适配器会在下一次请求时重新读取,无需重启任何东西。下面各小节介绍多数网关会用到的字段。
|
||||
|
||||
按常规方式通过 `dsh web` 启动 Web UI 时,`<profile>` 就是 `web`,完整路径为 `$DSH_HOME/profiles/web/cordis.patch.yml`。如果使用自定义 profile,请替换为启动时指定的名称。
|
||||
:::
|
||||
|
||||
### 图片输入
|
||||
@@ -132,14 +134,14 @@ DeepSeek 将省略的 `inputModalities` 视为纯文本,并拒绝空列表。
|
||||
留空的 `off` 什么都不发送,这只能让「按请求才思考」的模型停下来;给 `off` 一个值,则会把该值作为 `reasoning_effort` 发送。对于「不明确关闭就会思考」的模型——例如 OpenAI 兼容网关后面的 DeepSeek V4——需要 `compat.thinkingFormat: deepseek`:它让 `off` 发送 `thinking: {type: disabled}`,其他每个等级则在 effort 之外再发送 `thinking: {type: enabled}`:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- id: deepseek-v4-pro
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
reasoningEfforts:
|
||||
off:
|
||||
high: high
|
||||
max: max
|
||||
models:
|
||||
- id: deepseek-v4-pro
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
reasoningEfforts:
|
||||
off:
|
||||
high: high
|
||||
max: max
|
||||
```
|
||||
|
||||
网关并不提供推理能力的内置提供商模型,可在 `modelOverrides` 下用 `reasoningEfforts: false` 去掉其等级;之后再为它选择等级会被拒绝并报 `UNSUPPORTED_REASONING_EFFORT`。DeepSeek 自身的路由不需要以上任何配置:其模型已经提供 `off`、`low`、`high` 和 `max`,`llm-deepseek.reasoningEffort` 设置选择器的起始默认值:
|
||||
@@ -174,11 +176,11 @@ DeepSeek 将省略的 `inputModalities` 视为纯文本,并拒绝空列表。
|
||||
路由的 `compat` 是其模型的默认值,模型自身的则逐字段胜出,因此更正某一个模型无需重述整条路由:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- id: my-model
|
||||
- id: my-reasoner
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
models:
|
||||
- id: my-model
|
||||
- id: my-reasoner
|
||||
compat:
|
||||
thinkingFormat: deepseek
|
||||
```
|
||||
|
||||
两者都未设置的字段,沿用已安装 catalog 为该模型记录的值;catalog 也未描述的,落到 pi-ai 的检测。凡是写下的开关都要给值:冒号后留空的键(`supportsDeveloperRole:`)会被拒绝而不是被忽略,因为空值会抹掉 catalog 已知的信息,却又没有给出任何替代。任何协议都不接受的名字同样会被拒绝,报错会列出可用的那些。
|
||||
|
||||
@@ -295,18 +295,26 @@ it('reclaims the process range after an unexpected idle exit before replacing th
|
||||
it('retains a worker whose idle cleanup cannot observe exit until that range is joined', async () => {
|
||||
const { worker, spawn, ctx } = await fixture({ idleTimeoutMs: 100 })
|
||||
await prepare(worker)
|
||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
||||
await worker.transcribe({ audio, language: 'en' }, signal())
|
||||
const handle = spawn.mock.results[0]!.value as SubprocessHandle
|
||||
const joined = vi.spyOn(handle, 'waitForExit').mockRejectedValueOnce(new Error('range observation failed'))
|
||||
await vi.waitFor(() => { expect(joined).toHaveBeenCalledOnce() }, { timeout: 10000 })
|
||||
expect(warn).toHaveBeenCalledWith('Speech worker idle cleanup failed', expect.any(Error))
|
||||
await expect(worker.transcribe({ audio, language: 'zh' }, signal())).rejects.toThrow('range observation failed')
|
||||
expect(spawn).toHaveBeenCalledOnce()
|
||||
expect((await worker.transcribe({ audio, language: 'zh' }, signal())).text).toBe('zh')
|
||||
expect(await joined.mock.results.at(-1)!.value).toBe(true)
|
||||
expect(spawn).toHaveBeenCalledTimes(2)
|
||||
warn.mockRestore()
|
||||
const failure = new Error('range observation failed')
|
||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
||||
const joined = vi.spyOn(handle, 'waitForExit').mockRejectedValueOnce(failure)
|
||||
try {
|
||||
// Subprocess ownership also calls waitForExit after the direct process exits.
|
||||
await handle.done
|
||||
await vi.waitFor(() => {
|
||||
expect(warn).toHaveBeenCalledWith('Speech worker idle cleanup failed', failure)
|
||||
}, { timeout: 10000 })
|
||||
await expect(worker.transcribe({ audio, language: 'zh' }, signal())).rejects.toBe(failure)
|
||||
expect(spawn).toHaveBeenCalledOnce()
|
||||
expect((await worker.transcribe({ audio, language: 'zh' }, signal())).text).toBe('zh')
|
||||
expect(await joined.mock.results.at(-1)!.value).toBe(true)
|
||||
expect(spawn).toHaveBeenCalledTimes(2)
|
||||
} finally {
|
||||
joined.mockRestore()
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('retains completed preparation steps when cancellation settles the active step', async () => {
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/llm/llm-deepseek/README.md
|
||||
README.md: 1434358d6a4954135340f5726b7b424169b91a23
|
||||
README.zh.md: d975b6c6e51229a87e56f2df65305726ce224e7e
|
||||
README.md: 6a3e89788a5a23a56727fb41615898993939ef8c
|
||||
README.zh.md: db364b968f33838d3f8360ec54b2cf929866be67
|
||||
@@ -110,7 +110,7 @@ When `ctx.deepseekLlmApiExtensions` is present, the adapter prepares its registe
|
||||
|
||||
### Failures and recovery
|
||||
|
||||
Configuration accepts Messages only and has no `protocol` field. If resolution reports `protocol is not configurable`, remove `protocol` from the `llm-deepseek` section in `<harness home>/settings.yaml` and from any matching Cordis entry or overlay. Keep the intended `baseURL`, `apiKeyEnv`, and `models` fields. An invalid live section keeps the complete last good configuration; saving other fields in the Models card does not remove an unknown property. Edit the configuration file, then let settings reload or restart the profile; composition edits require a profile restart.
|
||||
Configuration accepts Messages only and has no `protocol` field. If resolution reports `protocol is not configurable`, remove `protocol` from the `config` of the `llm-deepseek` entry in `$DSH_HOME/profiles/<profile>/cordis.patch.yml` and from any overriding home patch or command-line overlay. Keep the intended `baseURL`, `apiKeyEnv`, and `models` fields. A stored configuration rejected by adapter validation makes subsequent requests fail until corrected; saving other fields in the Models card does not remove an unknown property. Edit the configuration file, then let the profile reload it through HMR or restart the profile if HMR is disabled.
|
||||
|
||||
Successful Files responses must contain valid JSON. JSON decoding failures from upload, list, retrieve, and delete throw `INVALID_RESPONSE` with the operation and HTTP status in the message, the status in `LlmError.failure`, and the original parser error as `cause`. Body-read transport and cancellation errors retain their identity.
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ Files 模式通过 `maxRequestFilesBytes` 与 `maxImagesPerRequest` 限制保留
|
||||
|
||||
### 失败与恢复
|
||||
|
||||
配置仅接受 Messages,不提供 `protocol` 字段。若解析报告 `protocol is not configurable`,请从 `<harness home>/settings.yaml` 的 `llm-deepseek` 分节以及对应 Cordis 条目或 overlay 中删除 `protocol`,保留需要的 `baseURL`、`apiKeyEnv` 和 `models` 字段。无效的实时分节会继续使用完整的最后有效配置;在模型设置卡中保存其他字段不会移除未知属性。请编辑配置文件,等待设置重新加载或重启 profile;组装配置变更需要重启 profile。
|
||||
配置仅接受 Messages,不提供 `protocol` 字段。若解析报告 `protocol is not configurable`,请从 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 中 `llm-deepseek` 条目的 `config` 以及覆盖它的 home patch 或命令行 overlay 中删除 `protocol`,保留需要的 `baseURL`、`apiKeyEnv` 和 `models` 字段。已存储的配置若被适配器校验拒绝,后续请求会持续失败,直到配置修正;在模型设置卡中保存其他字段不会移除未知属性。请编辑配置文件,等待 profile 通过 HMR(热模块替换)重新加载;若未启用 HMR,则重启 profile。
|
||||
|
||||
成功的 Files 响应必须包含有效 JSON。上传、列举、获取和删除操作的 JSON 解码失败抛出 `INVALID_RESPONSE`,消息包含操作名称与 HTTP 状态,`LlmError.failure` 保留该状态,`cause` 保留原始解析错误。读取响应体时的传输和取消错误保留其原有身份。
|
||||
|
||||
|
||||
Reference in new issue
Block a user