mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
feat(llm-deepseek): per-model output cap in the advisory catalog
A catalog entry may now carry its own `maxTokens`, matching the shape the pi-ai adapter already exposes. Exact-model resolution prefers it over the profile value, so capping one model no longer means capping the route. Defaults are unchanged: an entry without a cap, and any unlisted pass-through id, still resolve to the profile `maxTokens` (256,000), and the shipped catalog keeps its context windows.
This commit is contained in:
@@ -638,7 +638,7 @@ export interface Config {
|
||||
thinking?: 'enabled' | 'disabled'
|
||||
/** Default thinking effort (default `high`); `off` disables thinking per request. */
|
||||
reasoningEffort?: 'off' | 'high' | 'max'
|
||||
/** Default per-request output cap (default 256,000); explicit request values win. */
|
||||
/** Default per-request output cap (default 256,000); a model's own cap and explicit request values win. */
|
||||
maxTokens?: number
|
||||
/** Positive context capacity used when the selected model has no exact value (default 1,000,000). */
|
||||
defaultContextWindow?: number
|
||||
@@ -660,6 +660,8 @@ export interface DeepSeekCatalogModel {
|
||||
description?: string
|
||||
/** Known combined request/response context capacity; omitted when deployment metadata is unavailable. */
|
||||
contextWindow?: number
|
||||
/** Per-request output cap for this model; omission falls back to the profile's {@link DeepSeekConnectionOptions.maxTokens}. */
|
||||
maxTokens?: number
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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: 020aa65073495526be3f32912b7cd06667c52a2e
|
||||
README.zh.md: 4c655e90ba00340c056f6ac16159621f7a8c1ddb
|
||||
README.md: 72b04f5982ec7fdef024835ab23e7a9f0005f84c
|
||||
README.zh.md: 33eb1986a90079a2b8d6768368287b121e2fbd6b
|
||||
|
||||
@@ -40,7 +40,7 @@ The plugin registers the single provider route `deepseek-official` together with
|
||||
|
||||
`contextWindow` is optional per configured model and is not exposed through the advisory catalog. `ctx.llm.resolveModelInfo('deepseek-official', model).context` returns an exact model value first, then `defaultContextWindow` for an entry without capacity or an unlisted pass-through id. The adapter default is 1,000,000; pressure-sensitive plugins therefore get deployment-owned capacity without treating the model selector as authoritative. Registering another adapter for `deepseek-official` throws `LlmError('DUPLICATE_ADAPTER')`.
|
||||
|
||||
`maxTokens` is the adapter-configured output cap for conversation requests and defaults to 256,000. Exact-model resolution exposes it as `defaultMaxTokens`; `LlmService` materializes that value into `GenerateOptions.maxTokens` before the agent loop writes `request/header`, so the wire request remains reconstructable. An explicit request or `AgentOptions.maxTokens` value wins and is serialized as `max_tokens`. The adapter does not clamp this request budget against `contextWindow`; deployments with a smaller context or provider output limit must configure a compatible `maxTokens`.
|
||||
`maxTokens` is the adapter-configured output cap for conversation requests and defaults to 256,000. A catalog entry may carry its own `maxTokens`, which wins for that model; an entry without one, and any unlisted pass-through id, resolve to the profile value, so adding a per-model cap changes one model rather than the route. Exact-model resolution exposes the winner as `defaultMaxTokens`; `LlmService` materializes that value into `GenerateOptions.maxTokens` before the agent loop writes `request/header`, so the wire request remains reconstructable. An explicit request or `AgentOptions.maxTokens` value wins and is serialized as `max_tokens`. The adapter does not clamp this request budget against `contextWindow`; deployments with a smaller context or provider output limit must configure a compatible `maxTokens`.
|
||||
|
||||
The same exact-model result exposes ordered `off`, `high`, and `max` efforts under `reasoning` for every pass-through model when deployment policy permits thinking. `reasoningEffort` selects the deployment default and falls back to `high` when omitted. `agent/request` can replace it on each conversation step; the resolved value is logged in `request/header`. `high` and `max` enable thinking and serialize as the official top-level `reasoning_effort`; adapter-owned `off` instead serializes `thinking.type: disabled` and omits `reasoning_effort`. An unsupported value fails with `UNSUPPORTED_REASONING_EFFORT` before network I/O.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ harness LLM(大语言模型)seam 的 DeepSeek chat-completions 适配器:
|
||||
|
||||
`contextWindow` 对每个已配置模型都可选,不会通过建议 catalog 公开。`ctx.llm.resolveModelInfo('deepseek-official', model).context` 先返回精确模型值,再对不含容量的配置项或未列出原样传递 id 返回 `defaultContextWindow`。适配器默认值为 1,000,000;因此,压力敏感插件可以获得由部署决定的容量,不会将模型 selector 视为权威。为 `deepseek-official` 注册另一个适配器会抛出 `LlmError('DUPLICATE_ADAPTER')`。
|
||||
|
||||
`maxTokens` 是适配器为对话请求配置的输出上限,默认值为 256,000。确切模型解析会将其公开为 `defaultMaxTokens`;`LlmService` 会在 agent loop(智能体循环)写入 `request/header` 前,将该值填入 `GenerateOptions.maxTokens`,从而仍可根据持久记录重建协议请求。显式的请求值或 `AgentOptions.maxTokens` 值优先,并会序列化为 `max_tokens`。适配器不会根据 `contextWindow` 自动调低该请求预算;上下文或提供方输出上限较小的部署必须配置与其相容的 `maxTokens`。
|
||||
`maxTokens` 是适配器为对话请求配置的输出上限,默认值为 256,000。Catalog 配置项可以自带 `maxTokens`,它对该模型胜出;不含该上限的配置项以及任何未列出原样传递 id 都解析为 profile 值,因此新增按模型的上限只改变一个模型,而非整条路由。确切模型解析会将胜出值公开为 `defaultMaxTokens`;`LlmService` 会在 agent loop(智能体循环)写入 `request/header` 前,将该值填入 `GenerateOptions.maxTokens`,从而仍可根据持久记录重建协议请求。显式的请求值或 `AgentOptions.maxTokens` 值优先,并会序列化为 `max_tokens`。适配器不会根据 `contextWindow` 自动调低该请求预算;上下文或提供方输出上限较小的部署必须配置与其相容的 `maxTokens`。
|
||||
|
||||
同一确切模型结果会在部署策略允许思考时,为每个原样传递模型在 `reasoning` 下公开有序的 `off`、`high` 和 `max` 推理(reasoning)强度。`reasoningEffort` 选择部署默认值,省略时回退为 `high`。`agent/request` 可以在每个会话步骤替换它;解析后的值会记录在 `request/header`。`high` 和 `max` 会启用思考,并序列化为官方顶层 `reasoning_effort`;适配器持有的 `off` 则序列化为 `thinking.type: disabled`,且省略 `reasoning_effort`。不支持的值会在网络 I/O 前以 `UNSUPPORTED_REASONING_EFFORT` 失败。
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface DeepSeekCatalogModel {
|
||||
description?: string
|
||||
/** Known combined request/response context capacity; omitted when deployment metadata is unavailable. */
|
||||
contextWindow?: number
|
||||
/** Per-request output cap for this model; omission falls back to the profile's {@link DeepSeekConnectionOptions.maxTokens}. */
|
||||
maxTokens?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +183,7 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
? { provider, id: model, name: model }
|
||||
: modelInfo(provider, configured),
|
||||
context: { contextWindow },
|
||||
defaultMaxTokens: connection.maxTokens,
|
||||
defaultMaxTokens: configured?.maxTokens ?? connection.maxTokens,
|
||||
...connection.defaults.thinking === 'disabled'
|
||||
? {
|
||||
reasoning: {
|
||||
|
||||
@@ -68,7 +68,7 @@ export interface Config {
|
||||
thinking?: 'enabled' | 'disabled'
|
||||
/** Default thinking effort (default `high`); `off` disables thinking per request. */
|
||||
reasoningEffort?: 'off' | 'high' | 'max'
|
||||
/** Default per-request output cap (default 256,000); explicit request values win. */
|
||||
/** Default per-request output cap (default 256,000); a model's own cap and explicit request values win. */
|
||||
maxTokens?: number
|
||||
/** Positive context capacity used when the selected model has no exact value (default 1,000,000). */
|
||||
defaultContextWindow?: number
|
||||
@@ -85,6 +85,7 @@ const catalogModel: z<DeepSeekCatalogModel> = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
contextWindow: z.number().step(1).min(1),
|
||||
maxTokens: z.number().step(1).min(1),
|
||||
})
|
||||
|
||||
export const Config: z<Config> = z.object({
|
||||
@@ -125,6 +126,12 @@ function resolveModels(models: readonly DeepSeekCatalogModel[] | undefined): Dee
|
||||
`llm-deepseek: catalog model "${model.id}" contextWindow must be a positive integer`,
|
||||
)
|
||||
}
|
||||
if (model.maxTokens !== undefined
|
||||
&& (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) {
|
||||
throw new Error(
|
||||
`llm-deepseek: catalog model "${model.id}" maxTokens must be a positive integer`,
|
||||
)
|
||||
}
|
||||
if (seen.has(model.id)) throw new Error(`llm-deepseek: duplicate catalog model "${model.id}"`)
|
||||
seen.add(model.id)
|
||||
return {
|
||||
@@ -132,6 +139,7 @@ function resolveModels(models: readonly DeepSeekCatalogModel[] | undefined): Dee
|
||||
...model.name === undefined ? {} : { name: model.name },
|
||||
...model.description === undefined ? {} : { description: model.description },
|
||||
...model.contextWindow === undefined ? {} : { contextWindow: model.contextWindow },
|
||||
...model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -793,6 +793,26 @@ describe('plugin registration and config', () => {
|
||||
expect(ctx.llm.listProviders()).toEqual([])
|
||||
})
|
||||
|
||||
it.each([0, 1.5])('rejects a per-model output cap of %s', (maxTokens) => {
|
||||
expect(() => resolveAdapterOptions({ models: [{ id: 'bad-cap', maxTokens }] }))
|
||||
.toThrow(/maxTokens must be a positive integer/)
|
||||
})
|
||||
|
||||
it('prefers a model\'s own output cap over the profile default', async () => {
|
||||
// The profile default stays what an unlisted or uncapped model resolves
|
||||
// to, so adding a per-model cap changes one model rather than the route.
|
||||
const adapter = adapterOf({ maxTokens: 4096, models: [
|
||||
{ id: 'capped', maxTokens: 512 },
|
||||
{ id: 'uncapped' },
|
||||
] })
|
||||
await expect(adapter.resolveModel('deepseek-official', 'capped'))
|
||||
.resolves.toMatchObject({ defaultMaxTokens: 512 })
|
||||
await expect(adapter.resolveModel('deepseek-official', 'uncapped'))
|
||||
.resolves.toMatchObject({ defaultMaxTokens: 4096 })
|
||||
await expect(adapter.resolveModel('deepseek-official', 'not-in-catalog'))
|
||||
.resolves.toMatchObject({ defaultMaxTokens: 4096 })
|
||||
})
|
||||
|
||||
it('rejects invalid context capacity when apply is called directly', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
|
||||
Reference in New Issue
Block a user