diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index b286aae4c2..9d82bd9b2c 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -540,8 +540,8 @@ en: 028791baa24a6a0a zh: 028791baa24a6a0a /session-persistence-event-catalog/resolved-persistence-types/in-history: - en: 7ee8e3e9b3947c22 - zh: 035ceca2d5031800 + en: 8670ed3a7c75b1b2 + zh: d0161500ca0eca47 /session-persistence-event-catalog/resolved-persistence-types/in-progress: en: 61de2b0c9de8c15b zh: 61de2b0c9de8c15b @@ -1128,8 +1128,8 @@ en: 07e04c3e336f880f zh: 6f8986ede9994fa1 /session-persistence-event-catalog/resolved-persistence-types/replayenvelope: - en: 9a80b6f5eb08eaea - zh: 061c632b8f3e4d87 + en: d166ff7b19affbab + zh: a749b1a02ea2ef66 /session-persistence-event-catalog/resolved-persistence-types/requestcontext: en: cc8bb7075d546941 zh: 77209abf30744be0 @@ -1176,8 +1176,8 @@ en: ecc15ebadb77afdf zh: 97ac27d6a4175f6f /session-persistence-event-catalog/resolved-persistence-types/streamchunk: - en: 0d571899222eb04f - zh: de28c6d67d1763c1 + en: 4c7342a2921d0cf3 + zh: 3c8d6057c9715ace /session-persistence-event-catalog/resolved-persistence-types/subagentcatalogevent: en: 725539dcd5c335ed zh: 5ca08d3667f84ff9 @@ -1245,8 +1245,8 @@ en: 2258683c3cfa4ce3 zh: 5663aa76c911761b /session-persistence-event-catalog/resolved-persistence-types/toolschema: - en: 6e1e6ebc7c1c38c8 - zh: e47a3ca80c4c3b71 + en: 2d99dd4714e356ea + zh: a628abffea35a7ac /session-persistence-event-catalog/resolved-persistence-types/toolschema~2: en: 12c14727bb7834e2 zh: b0f3c877873c36db @@ -1890,11 +1890,11 @@ en: bb507524bcc39c53 zh: 8314898562437473 /session-persistence-event-catalog/resolved-persistence-types/type-block-end: - en: 8c5a05fa6adf3e70 - zh: 66768b25d6fb3e07 + en: 751e387163ebb8d9 + zh: ea0f510adbd3739d /session-persistence-event-catalog/resolved-persistence-types/type-block-start: - en: 5caed426cab416ee - zh: 86f47f90f8c440f2 + en: 85410c5bb7e09732 + zh: 619ff3bc86ecc65b /session-persistence-event-catalog/resolved-persistence-types/type-chunk: en: 2186ede3e9917aea zh: 0cfef64045dce95b @@ -1932,8 +1932,8 @@ en: 14e4b48d856bcf57 zh: 72ba176535dc7f3f /session-persistence-event-catalog/resolved-persistence-types/type-finish: - en: 21627e72a24990ff - zh: d71375de325ee9f8 + en: b073b33ea2bdc924 + zh: 4121d8178d6f8755 /session-persistence-event-catalog/resolved-persistence-types/type-goal-change: en: aad2516b62d3ee83 zh: e7204c4dc4e8e408 @@ -1965,8 +1965,8 @@ en: 34e5591d75322649 zh: a216839f305c9796 /session-persistence-event-catalog/resolved-persistence-types/type-reasoning-delta: - en: a18feb76be195fa0 - zh: 3db7147ee4c5c963 + en: 32be5bf5ee54cfba + zh: 5f28dfcea660d09a /session-persistence-event-catalog/resolved-persistence-types/type-request-context: en: f8addb1a01da9ca1 zh: 4ef11ebddd302c05 @@ -2025,8 +2025,8 @@ en: 1484738718de5312 zh: 3e6fbe9c64068225 /session-persistence-event-catalog/resolved-persistence-types/type-text-delta: - en: 47ae2d57be710450 - zh: b072753349ac505e + en: e8736975e930c16f + zh: 61b5e30e58dc6aa1 /session-persistence-event-catalog/resolved-persistence-types/type-todo-write: en: 2fa7ba77598a95b4 zh: 50972b51d77e9155 @@ -2034,8 +2034,8 @@ en: 32f732b97739e4d2 zh: f288e48e356969c0 /session-persistence-event-catalog/resolved-persistence-types/type-tool-call-delta: - en: 1df89f427ccac815 - zh: 1bec3d025ea1f7e3 + en: d32aa20dc53f4ad9 + zh: 05ff20fcf7d6acd1 /session-persistence-event-catalog/resolved-persistence-types/type-tool-workflow-agent-end: en: 8eb5e1eb95722bc7 zh: 8dd5980029600648 @@ -2067,8 +2067,8 @@ en: 1b045de96172be4b zh: 8a15c6f35b576b37 /session-persistence-event-catalog/resolved-persistence-types/type-usage: - en: fa542ccb91698e56 - zh: 7b725c813d69588d + en: 729b66be0e2b9122 + zh: eaca5d90f09f9329 /session-persistence-event-catalog/resolved-persistence-types/type-user-message: en: fb49c375aec5952c zh: 19b80920c1068b86 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index e7335c2e14..0ff97c4e40 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -1954,7 +1954,7 @@ SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8` SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5` -Sources: [`packages/llm/llm/src/types.ts:396`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:402`](../packages/llm/llm/src/types.ts) `"in-history"` @@ -4314,7 +4314,7 @@ Sources: [`packages/llm/llm/src/types.ts:68`](../packages/llm/llm/src/types.ts) SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555` -Sources: [`packages/llm/llm/src/types.ts:430`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:436`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4608,7 +4608,7 @@ Sources: [`packages/skill/skill/src/index.ts:146`](../packages/skill/skill/src/i SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180` -Sources: [`packages/llm/llm/src/types.ts:452`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:458`](../packages/llm/llm/src/types.ts) One of: @@ -5033,7 +5033,7 @@ Sources: [`packages/llm/llm/src/message.ts:173`](../packages/llm/llm/src/message SHA-256: `66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2` -Sources: [`packages/llm/llm/src/types.ts:473`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:479`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -7830,7 +7830,7 @@ SHA-256: `1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625` SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d` -Sources: [`packages/llm/llm/src/types.ts:457`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:463`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -7844,7 +7844,7 @@ Sources: [`packages/llm/llm/src/types.ts:457`](../packages/llm/llm/src/types.ts) SHA-256: `3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90` -Sources: [`packages/llm/llm/src/types.ts:453`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:459`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8050,7 +8050,7 @@ SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` SHA-256: `a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1` -Sources: [`packages/llm/llm/src/types.ts:459`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:465`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8224,7 +8224,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:28`](../packages/llm/llm/src SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095` -Sources: [`packages/llm/llm/src/types.ts:455`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:461`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8544,7 +8544,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:21`](../packages/llm/llm/src SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65` -Sources: [`packages/llm/llm/src/types.ts:454`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:460`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8592,7 +8592,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:35`](../packages/llm/llm/src SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a` -Sources: [`packages/llm/llm/src/types.ts:456`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:462`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8770,7 +8770,7 @@ SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` -Sources: [`packages/llm/llm/src/types.ts:458`](../packages/llm/llm/src/types.ts) +Sources: [`packages/llm/llm/src/types.ts:464`](../packages/llm/llm/src/types.ts) | Property | Presence | Type | |---|---|---| diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index b0c7414222..f4ea1a6a4c 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -1956,7 +1956,7 @@ SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8` SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5` -来源:[`packages/llm/llm/src/types.ts:396`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:402`](../packages/llm/llm/src/types.ts) `"in-history"` @@ -4316,7 +4316,7 @@ SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e` SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555` -来源:[`packages/llm/llm/src/types.ts:430`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:436`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4610,7 +4610,7 @@ SHA-256: `672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db` SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180` -来源:[`packages/llm/llm/src/types.ts:452`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:458`](../packages/llm/llm/src/types.ts) 以下类型之一: @@ -5035,7 +5035,7 @@ SHA-256: `4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731` SHA-256: `66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2` -来源:[`packages/llm/llm/src/types.ts:473`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:479`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7832,7 +7832,7 @@ SHA-256: `1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625` SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d` -来源:[`packages/llm/llm/src/types.ts:457`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:463`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7846,7 +7846,7 @@ SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d` SHA-256: `3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90` -来源:[`packages/llm/llm/src/types.ts:453`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:459`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8052,7 +8052,7 @@ SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` SHA-256: `a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1` -来源:[`packages/llm/llm/src/types.ts:459`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:465`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8226,7 +8226,7 @@ SHA-256: `71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd` SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095` -来源:[`packages/llm/llm/src/types.ts:455`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:461`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8546,7 +8546,7 @@ SHA-256: `e72269f931559e901e5e8fe1cfcbd0669fb795f43cc038ff39fba62f60403c33` SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65` -来源:[`packages/llm/llm/src/types.ts:454`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:460`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8594,7 +8594,7 @@ SHA-256: `3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6` SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a` -来源:[`packages/llm/llm/src/types.ts:456`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:462`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8772,7 +8772,7 @@ SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` -来源:[`packages/llm/llm/src/types.ts:458`](../packages/llm/llm/src/types.ts) +来源:[`packages/llm/llm/src/types.ts:464`](../packages/llm/llm/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index d7d9328556..e9268ae0f7 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -22117,7 +22117,7 @@ "packages/llm/llm/src/types.ts#ReplayEnvelope" ], "sources": [ - "packages/llm/llm/src/types.ts:430" + "packages/llm/llm/src/types.ts:436" ] }, { @@ -22643,7 +22643,7 @@ "digest": "2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:455" + "packages/llm/llm/src/types.ts:461" ] }, { @@ -22880,7 +22880,7 @@ "digest": "3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:453" + "packages/llm/llm/src/types.ts:459" ] }, { @@ -23016,7 +23016,7 @@ "packages/llm/llm/src/types.ts#StreamChunk" ], "sources": [ - "packages/llm/llm/src/types.ts:452" + "packages/llm/llm/src/types.ts:458" ] }, { @@ -23516,7 +23516,7 @@ "packages/llm/llm/src/types.ts#ToolSchema" ], "sources": [ - "packages/llm/llm/src/types.ts:473" + "packages/llm/llm/src/types.ts:479" ] }, { @@ -24035,7 +24035,7 @@ "digest": "88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:456" + "packages/llm/llm/src/types.ts:462" ] }, { @@ -24202,7 +24202,7 @@ "digest": "95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:454" + "packages/llm/llm/src/types.ts:460" ] }, { @@ -24390,7 +24390,7 @@ "digest": "a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:459" + "packages/llm/llm/src/types.ts:465" ] }, { @@ -25071,7 +25071,7 @@ "digest": "d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:457" + "packages/llm/llm/src/types.ts:463" ] }, { @@ -25459,7 +25459,7 @@ "digest": "f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515", "names": [], "sources": [ - "packages/llm/llm/src/types.ts:458" + "packages/llm/llm/src/types.ts:464" ] }, { @@ -25487,7 +25487,7 @@ "packages/llm/llm/src/types.ts#SystemPromptUpdate" ], "sources": [ - "packages/llm/llm/src/types.ts:396" + "packages/llm/llm/src/types.ts:402" ] }, { diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index 804da814fd..f6f62760f2 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -553,6 +553,12 @@ interface LlmReasoningEffortInfo { interface LlmModelReasoningInfo { /** Supported efforts in adapter-preferred display order. */ efforts: readonly LlmReasoningEffortInfo[] + /** + * The least effort this route accepts. A caller selects it when the request + * must not spend its output on reasoning. Absence means the adapter reports + * no selectable effort. + */ + floorEffort?: ReasoningEffortId /** * Adapter-configured default materialized into requests when callers omit * an effort. Absence preserves the provider's own default. @@ -638,15 +644,6 @@ interface GenerateOptions { * requests leave it unset. */ purpose?: 'compaction' | 'session-title' - /** - * Dispatch with the least reasoning the routed model supports: `off` when - * the model offers it, otherwise its lowest declared level. An auxiliary - * caller sets this when reasoning would consume the output budget it - * reserved for a short visible result. The request's own effort and the - * profile default do not apply, and an effort the model does not support is - * not validated for such a request. - */ - minimizeReasoning?: boolean } ``` diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index 6d6ebb6cd8..2f9517f3d0 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -559,6 +559,12 @@ interface LlmReasoningEffortInfo { interface LlmModelReasoningInfo { /** Supported efforts in adapter-preferred display order. */ efforts: readonly LlmReasoningEffortInfo[] + /** + * The least effort this route accepts. A caller selects it when the request + * must not spend its output on reasoning. Absence means the adapter reports + * no selectable effort. + */ + floorEffort?: ReasoningEffortId /** * Adapter-configured default materialized into requests when callers omit * an effort. Absence preserves the provider's own default. @@ -644,15 +650,6 @@ interface GenerateOptions { * requests leave it unset. */ purpose?: 'compaction' | 'session-title' - /** - * Dispatch with the least reasoning the routed model supports: `off` when - * the model offers it, otherwise its lowest declared level. An auxiliary - * caller sets this when reasoning would consume the output budget it - * reserved for a short visible result. The request's own effort and the - * profile default do not apply, and an effort the model does not support is - * not validated for such a request. - */ - minimizeReasoning?: boolean } ``` diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index 45a5b384fd..819dce9e50 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -5389,7 +5389,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'GenerateOptions', - declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n messages: RequestMessage[];\n system?: string;\n tools?: ToolSchema[];\n toolHistory?: ToolHistory;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n purpose?: \'compaction\' | \'session-title\';\n minimizeReasoning?: boolean;\n}', + declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n messages: RequestMessage[];\n system?: string;\n tools?: ToolSchema[];\n toolHistory?: ToolHistory;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n purpose?: \'compaction\' | \'session-title\';\n}', }, { name: 'GenericCallView', @@ -5765,7 +5765,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'LlmModelReasoningInfo', - declaration: 'export interface LlmModelReasoningInfo {\n efforts: readonly LlmReasoningEffortInfo[];\n defaultEffort?: ReasoningEffortId;\n}', + declaration: 'export interface LlmModelReasoningInfo {\n efforts: readonly LlmReasoningEffortInfo[];\n floorEffort?: ReasoningEffortId;\n defaultEffort?: ReasoningEffortId;\n}', }, { name: 'LlmProviderInfo', diff --git a/packages/llm/llm-deepseek/README.i18n.yaml b/packages/llm/llm-deepseek/README.i18n.yaml index f2df7f023f..de8924ad6e 100644 --- a/packages/llm/llm-deepseek/README.i18n.yaml +++ b/packages/llm/llm-deepseek/README.i18n.yaml @@ -30,8 +30,8 @@ en: 3853ba1a1ffbb46c zh: 97203e80ad933e59 /deepseek-ai-dsh-llm-deepseek/use-this-package/streaming-with-thinking-and-images: - en: 1d7ba1bedfd11a18 - zh: 9e1a3372f610e9aa + en: aa523e10ffa24d7a + zh: 11c2f049652facc6 /deepseek-ai-dsh-llm-deepseek/use-this-package/dynamic-configuration: en: 49290fc5647136b4 zh: 96fa9ce0f9e83c13 diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 4b46311293..24d54f9cd5 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -99,7 +99,7 @@ Cached ids are scoped by endpoint and credential, refreshed before expiry, inval Files mode bounds retained request versions by `maxRequestFilesBytes` and `maxImagesPerRequest`; inline fallback has its own base64 budget. Both remove an oldest prefix in configured byte or count quanta. Each omitted image gets its own model-visible placeholder with its display name or attachment id and, when available, normalized dimensions, media type, and current read-only path. The stepped high-watermark policy avoids rewriting an old request prefix after every new image. -`reasoningEffort` selects the advertised default. Exact-model metadata exposes ordered `off`, `low`, `high`, and `max` efforts with selection guidance when deployment policy permits thinking. `low`, `high`, and `max` enable thinking and serialize as `output_config.effort`, while adapter-owned `off` sends `thinking.type: disabled` instead. An unsupported value fails with `UNSUPPORTED_REASONING_EFFORT` before network I/O, and `thinking: disabled` rejects any non-`off` effort at plugin load. Requests with `GenerateOptions.minimizeReasoning` force thinking off to reserve output for a short visible result. The adapter forwards an explicit `temperature`; DeepSeek accepts it with thinking enabled but ignores its value in that mode. +`reasoningEffort` selects the advertised default. Exact-model metadata exposes ordered `off`, `low`, `high`, and `max` efforts with selection guidance when deployment policy permits thinking, and reports `off` as the floor effort that a caller selects when the request must not spend output on reasoning. `low`, `high`, and `max` enable thinking and serialize as `output_config.effort`, while adapter-owned `off` sends `thinking.type: disabled` instead. An unsupported value fails with `UNSUPPORTED_REASONING_EFFORT` before network I/O, and `thinking: disabled` rejects any non-`off` effort at plugin load. The adapter forwards an explicit `temperature`; DeepSeek accepts it with thinking enabled but ignores its value in that mode. ### Dynamic configuration diff --git a/packages/llm/llm-deepseek/README.zh.md b/packages/llm/llm-deepseek/README.zh.md index d1301a9272..98a07a6773 100644 --- a/packages/llm/llm-deepseek/README.zh.md +++ b/packages/llm/llm-deepseek/README.zh.md @@ -99,7 +99,7 @@ Messages 和 Files 请求通过 `x-dsh-auth-token` 发送账号 token,不加 B Files 模式通过 `maxRequestFilesBytes` 与 `maxImagesPerRequest` 限制保留请求版本;内联回退有独立 base64 预算。两种模式都按配置的字节或数量量子移除最旧前缀。每张省略图片都有自己的模型可见占位符,包含显示名或附件 id,以及可用时的规范化尺寸、媒体类型与当前只读路径。分阶高水位策略避免每新增一张图片都改写旧请求前缀。 -`reasoningEffort` 选择公布的默认值。当部署策略允许 thinking 时,确切模型元数据会按顺序公开 `off`、`low`、`high` 与 `max` 强度及选择指引。`low`、`high` 与 `max` 启用 thinking,并以 `output_config.effort` 序列化,适配器自有的 `off` 则发送 `thinking.type: disabled`。不支持的取值会在网络 I/O 前以 `UNSUPPORTED_REASONING_EFFORT` 失败;`thinking: disabled` 会在插件加载时拒绝任何非 `off` 强度。携带 `GenerateOptions.minimizeReasoning` 的请求会强制关闭 thinking,把输出留给简短的可见结果。适配器转发显式 `temperature`;DeepSeek 在启用 thinking 时接受该参数,但忽略其值。 +`reasoningEffort` 选择公布的默认值。当部署策略允许 thinking 时,确切模型元数据会按顺序公开 `off`、`low`、`high` 与 `max` 强度及选择指引,并把 `off` 公布为最低强度,供需要避免把输出花在推理上的调用方选择。`low`、`high` 与 `max` 启用 thinking,并以 `output_config.effort` 序列化,适配器自有的 `off` 则发送 `thinking.type: disabled`。不支持的取值会在网络 I/O 前以 `UNSUPPORTED_REASONING_EFFORT` 失败;`thinking: disabled` 会在插件加载时拒绝任何非 `off` 强度。适配器转发显式 `temperature`;DeepSeek 在启用 thinking 时接受该参数,但忽略其值。 ### 动态配置 diff --git a/packages/llm/llm-deepseek/src/model-info.ts b/packages/llm/llm-deepseek/src/model-info.ts index c385ae93ec..50f045004e 100644 --- a/packages/llm/llm-deepseek/src/model-info.ts +++ b/packages/llm/llm-deepseek/src/model-info.ts @@ -81,12 +81,14 @@ export function modelInfo( ? { reasoning: { efforts: OFF_ONLY_REASONING_EFFORTS, + floorEffort: OFF_REASONING_EFFORT, defaultEffort: OFF_REASONING_EFFORT, }, } : { reasoning: { efforts: REASONING_EFFORTS, + floorEffort: OFF_REASONING_EFFORT, defaultEffort: connection.defaults.reasoningEffort === 'off' ? OFF_REASONING_EFFORT : connection.defaults.reasoningEffort === 'low' diff --git a/packages/llm/llm-deepseek/src/serialize.ts b/packages/llm/llm-deepseek/src/serialize.ts index 7fa7d242da..824b9909df 100644 --- a/packages/llm/llm-deepseek/src/serialize.ts +++ b/packages/llm/llm-deepseek/src/serialize.ts @@ -143,7 +143,7 @@ export function serialize( } } if (pending.size > 0) throw new LlmError('DeepSeek Messages history ends with unresolved tools', 'INVALID_REQUEST') - const effort = options.minimizeReasoning === true ? 'off' : options.reasoningEffort ?? (connection.defaults.reasoningEffort ?? (connection.defaults.thinking === 'disabled' ? 'off' : 'high')) + const effort = options.reasoningEffort ?? (connection.defaults.reasoningEffort ?? (connection.defaults.thinking === 'disabled' ? 'off' : 'high')) if (!['off', 'low', 'high', 'max'].includes(effort) || (connection.defaults.thinking === 'disabled' && effort !== 'off')) { throw new LlmError(`DeepSeek Messages does not support reasoning effort ${effort}`, 'UNSUPPORTED_REASONING_EFFORT') } diff --git a/packages/llm/llm-deepseek/tests/serialize.spec.ts b/packages/llm/llm-deepseek/tests/serialize.spec.ts index 0cf232f7d7..40d6930c84 100644 --- a/packages/llm/llm-deepseek/tests/serialize.spec.ts +++ b/packages/llm/llm-deepseek/tests/serialize.spec.ts @@ -285,8 +285,9 @@ describe('Messages request conversion', () => { expect(request.output_config).toEqual(effort === 'off' ? undefined : { effort }) }) - it('disables thinking for a minimized-reasoning request, passes temperature with thinking and refuses unsupported effort', () => { - expect(body([user()], { minimizeReasoning: true, temperature: 0 })).toMatchObject({ thinking: { type: 'disabled' }, temperature: 0 }) + it('disables thinking for the off effort, passes temperature with thinking and refuses unsupported effort', () => { + expect(body([user()], { reasoningEffort: ReasoningEffortId('off'), temperature: 0 })) + .toMatchObject({ thinking: { type: 'disabled' }, temperature: 0 }) expect(body([user()], { temperature: 0 })).toMatchObject({ thinking: { type: 'enabled' }, temperature: 0 }) expect(() => body([user()], { reasoningEffort: ReasoningEffortId('medium') })).toThrow(/effort/) const disabled = resolveAdapterOptions({ thinking: 'disabled' }) @@ -396,7 +397,7 @@ describe('Messages request conversion', () => { describe('validated configuration', () => { it('advertises exact model metadata and allows unlisted text models', () => { - expect(modelInfo(connection, 'deepseek-official', MODEL)).toMatchObject({ context: { contextWindow: 1_000_000 }, defaultMaxTokens: 256_000, reasoning: { defaultEffort: 'high' } }) + expect(modelInfo(connection, 'deepseek-official', MODEL)).toMatchObject({ context: { contextWindow: 1_000_000 }, defaultMaxTokens: 256_000, reasoning: { floorEffort: 'off', defaultEffort: 'high' } }) expect(modelInfo(connection, 'deepseek-official', 'custom').inputModalities).toEqual(['text']) expect(modelInfo(connection, 'deepseek-official', MODEL).systemPromptUpdate).toBeUndefined() expect(modelInfo(connection, 'deepseek-official', 'custom').systemPromptUpdate).toBeUndefined() diff --git a/packages/llm/llm-pi-ai/README.i18n.yaml b/packages/llm/llm-pi-ai/README.i18n.yaml index 42f83ddbba..bb63129e2b 100644 --- a/packages/llm/llm-pi-ai/README.i18n.yaml +++ b/packages/llm/llm-pi-ai/README.i18n.yaml @@ -30,8 +30,8 @@ en: 376f83206858b601 zh: 38bb6993609c96a6 /deepseek-ai-dsh-llm-pi-ai/use-this-package/run-with-reasoning-and-wire-compatibility: - en: 38f5251e412f2445 - zh: f492ef40cdafc20e + en: b1f319877df7c6ba + zh: 351a49c549a1850f /deepseek-ai-dsh-llm-pi-ai/use-this-package/change-configuration-at-runtime: en: 92f554e1718614ca zh: f17ed3ad3e5d28e2 diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index a73ae58bd2..00bcef63d0 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -102,7 +102,7 @@ A profile's `models` list replaces the route's installed catalog rather than ext `reasoningEfforts` declares a model's selectable thinking levels: each key is a level selectors offer, its value the spelling dispatch sends on the wire, so `max: ultra` renames a level for a gateway with its own vocabulary. Omitting the field keeps the installed catalog entry's capability; `false` declares a non-reasoning model. `compat` switches reshape the request for endpoints pi-ai cannot recognize — which role carries the system prompt, which field caps output, how a thinking level travels — configurable per route and per model. A model neither the entry nor the installed catalog sizes takes the route's `defaultContextWindow` and `defaultMaxTokens` fallbacks. -A request carrying `GenerateOptions.minimizeReasoning` dispatches at the lowest level its model supports, whatever effort the request or profile names: `off` when the model offers it, otherwise the lowest declared level. Such a caller reserves its output budget for a short visible result, so it does not spend that budget on reasoning. `off` dispatches exactly as a selected `off` does, so a protocol that expresses it by omitting the reasoning field leaves the gateway's default in effect; reasoning tokens at any of these levels count toward the request's output cap. The requested effort is not validated for such a request, so a name the model does not support fails an ordinary call rather than this one. +Exact-model metadata reports `reasoning.floorEffort`, the least level the route accepts: `off` when the model can stop reasoning, otherwise its lowest declared level. A caller that must not spend its output on reasoning selects that effort, and this adapter dispatches the effort it received. `off` dispatches exactly as a selected `off` does, so a protocol that expresses it by omitting the reasoning field leaves the gateway's default in effect; reasoning tokens at any selected level count toward the request's output cap. For self-hosted Chat Completions endpoints, `thinkingTokenBudgetField` selects the reasoning-budget parameter, and `vllmPriority` sets an integer scheduler priority when the server enables priority scheduling. Template arguments accept `$var: thinking.budget`. `openai-responses` gateways can set `supportsMaxOutputTokens: false` to omit `max_output_tokens`; Azure and Codex transports ignore this shared compatibility field. These controls are opt-in; catalog-owned Anthropic effort and fallback capabilities are not configurable switches. diff --git a/packages/llm/llm-pi-ai/README.zh.md b/packages/llm/llm-pi-ai/README.zh.md index 6624f0b85d..c518c9b307 100644 --- a/packages/llm/llm-pi-ai/README.zh.md +++ b/packages/llm/llm-pi-ai/README.zh.md @@ -102,7 +102,7 @@ profile 的 `models` 列表会替换而非扩展路由的已安装目录;每 `reasoningEfforts` 声明模型可选择的 thinking 等级:每个键都是选择器提供的等级,其值是分派时在协议中发送的拼写,因此 `max: ultra` 可以为拥有自有词汇的网关重命名等级。省略该字段时保留已安装目录条目的能力;`false` 声明非推理模型。对于 pi-ai 无法识别的端点,`compat` 开关重塑请求——哪个角色携带系统提示词、哪个字段限制输出、thinking 等级如何传递——可逐路由、逐模型配置。条目与已安装目录都没有尺寸的模型,会采用路由的 `defaultContextWindow` 与 `defaultMaxTokens` 回退值。 -携带 `GenerateOptions.minimizeReasoning` 的请求会以其模型支持的最低等级分派,不论请求或 profile 指定何种推理强度:模型提供 `off` 时使用 `off`,否则使用声明的最低等级。这类调用方把输出预算留给简短的可见结果,因此不把该预算花在推理上。`off` 的分派方式与手动选择 `off` 完全相同,因此以省略推理字段表达 `off` 的协议会沿用网关默认行为;这些等级下产生的推理 token 都计入该请求的输出上限。该请求不校验所请求的强度,因此模型不支持的名称会让普通调用失败,而不是该请求。 +确切模型元数据会公布 `reasoning.floorEffort`,即该路由接受的最低等级:模型能停止推理时为 `off`,否则为声明的最低等级。需要避免把输出花在推理上的调用方选择该强度,本适配器分派收到的强度。`off` 的分派方式与手动选择 `off` 完全相同,因此以省略推理字段表达 `off` 的协议会沿用网关默认行为;所选等级下产生的推理 token 都计入该请求的输出上限。 对于自托管 Chat Completions 端点,`thinkingTokenBudgetField` 选择推理预算参数,`vllmPriority` 在服务端启用优先级调度时设置整数调度优先级。模板参数接受 `$var: thinking.budget`。`openai-responses` 网关可设置 `supportsMaxOutputTokens: false` 来省略 `max_output_tokens`;Azure 与 Codex 传输会忽略这个共享兼容字段。这些控制均需显式启用;目录拥有的 Anthropic effort 和回退能力不是可配置开关。 diff --git a/packages/llm/llm-pi-ai/src/adapter.ts b/packages/llm/llm-pi-ai/src/adapter.ts index 63b6025b99..f84a78c4d6 100644 --- a/packages/llm/llm-pi-ai/src/adapter.ts +++ b/packages/llm/llm-pi-ai/src/adapter.ts @@ -169,26 +169,20 @@ function resolveReasoningLevel( } /** - * The level one request dispatches with. A request asking to minimize reasoning - * takes the model's lowest supported level, which is `off` whenever the model - * can stop reasoning, because reasoning a model cannot switch off still counts - * toward the output budget the caller reserved. That request deliberately - * skips the requested-effort check, so a name the model does not support fails - * an ordinary call rather than this one. Every other request validates the - * requested or profile effort. + * The level one request dispatches with: the effort the caller selected, or + * the profile default. The adapter validates that choice and dispatches it; a + * caller that wants the least reasoning for a short result selects the floor + * effort the route reports. * @param model - the resolved model descriptor. - * @param options - the request's reasoning preference and requested effort. + * @param options - the request's requested effort. * @param profile - the route profile carrying the default effort. - * @returns the level to dispatch, or undefined to send none, which a - * minimized-reasoning request gets only for a model whose metadata leaves it - * no supported level. + * @returns the level to dispatch, or undefined to send none. */ function requestReasoningLevel( model: Model, options: GenerateOptions, profile: ResolvedPiAiProviderProfile, ): ModelThinkingLevel | undefined { - if (options.minimizeReasoning === true) return getSupportedThinkingLevels(model)[0] return resolveReasoningLevel(model, options.reasoningEffort ?? profile.reasoning) } @@ -214,12 +208,14 @@ function reasoningInfo( ): Pick | Record { if (!model.reasoning) return {} const levels = getSupportedThinkingLevels(model) + const floor = levels[0] return { reasoning: { efforts: levels.map(level => ({ id: ReasoningEffortId(level), name: `${level.charAt(0).toUpperCase()}${level.slice(1)}`, })), + ...floor === undefined ? {} : { floorEffort: ReasoningEffortId(floor) }, ...defaultLevel === undefined ? {} : { defaultEffort: ReasoningEffortId(defaultLevel) }, }, } diff --git a/packages/llm/llm-pi-ai/tests/adapter.spec.ts b/packages/llm/llm-pi-ai/tests/adapter.spec.ts index a88ab1f751..43df3fc695 100644 --- a/packages/llm/llm-pi-ai/tests/adapter.spec.ts +++ b/packages/llm/llm-pi-ai/tests/adapter.spec.ts @@ -788,7 +788,7 @@ describe('provider profile lifecycle', () => { expect(server.requests[0]).not.toHaveProperty('reasoning_effort') }) - it('dispatches a minimized-reasoning request at the lowest level each model supports', async () => { + it('reports the floor effort and dispatches the level a caller selects', async () => { vi.stubEnv('PI_TEST_KEY', 'test-key') const server = await mockServer([ { events: anthropicTextEvents }, @@ -813,18 +813,23 @@ describe('provider profile lifecycle', () => { }, }, }) - const request = (model: string, minimizeReasoning?: true): Promise => assemble(ctx, { + const request = (model: string, effort: string): Promise => assemble(ctx, { provider: 'adaptive-gateway', model, - reasoningEffort: ReasoningEffortId('max'), + reasoningEffort: ReasoningEffortId(effort), maxTokens: 64, messages: [], - ...minimizeReasoning === undefined ? {} : { minimizeReasoning }, }) - await request('always-thinks') - await request('always-thinks', true) - await request('may-think', true) + // Each route names its own floor, so a caller can select the least reasoning. + await expect(ctx.llm.resolveModelInfo('adaptive-gateway', 'always-thinks')) + .resolves.toMatchObject({ reasoning: { floorEffort: ReasoningEffortId('low') } }) + await expect(ctx.llm.resolveModelInfo('adaptive-gateway', 'may-think')) + .resolves.toMatchObject({ reasoning: { floorEffort: ReasoningEffortId('off') } }) + + await request('always-thinks', 'max') + await request('always-thinks', 'low') + await request('may-think', 'off') expect(server.requests[0]).toMatchObject({ thinking: { type: 'adaptive' }, output_config: { effort: 'max' } }) expect(server.requests[1]).toMatchObject({ diff --git a/packages/llm/llm/src/index.ts b/packages/llm/llm/src/index.ts index b1c9081bfe..25f405bac2 100644 --- a/packages/llm/llm/src/index.ts +++ b/packages/llm/llm/src/index.ts @@ -849,10 +849,17 @@ export class LlmRuntime extends TypertRemoteService { 'INVALID_MODEL_REASONING', ) } + if (reasoning.floorEffort !== undefined && !seen.has(reasoning.floorEffort)) { + throw new LlmError( + `adapter returned an unknown floor reasoning effort for provider "${provider}" model "${model}"`, + 'INVALID_MODEL_REASONING', + ) + } return { ...info, reasoning: { efforts, + ...reasoning.floorEffort === undefined ? {} : { floorEffort: reasoning.floorEffort }, ...reasoning.defaultEffort === undefined ? {} : { defaultEffort: reasoning.defaultEffort }, }, } diff --git a/packages/llm/llm/src/types.ts b/packages/llm/llm/src/types.ts index 7251493bb9..ff535365bf 100644 --- a/packages/llm/llm/src/types.ts +++ b/packages/llm/llm/src/types.ts @@ -380,6 +380,12 @@ export interface LlmReasoningEffortInfo { export interface LlmModelReasoningInfo { /** Supported efforts in adapter-preferred display order. */ efforts: readonly LlmReasoningEffortInfo[] + /** + * The least effort this route accepts. A caller selects it when the request + * must not spend its output on reasoning. Absence means the adapter reports + * no selectable effort. + */ + floorEffort?: ReasoningEffortId /** * Adapter-configured default materialized into requests when callers omit * an effort. Absence preserves the provider's own default. @@ -550,13 +556,4 @@ export interface GenerateOptions { * requests leave it unset. */ purpose?: 'compaction' | 'session-title' - /** - * Dispatch with the least reasoning the routed model supports: `off` when - * the model offers it, otherwise its lowest declared level. An auxiliary - * caller sets this when reasoning would consume the output budget it - * reserved for a short visible result. The request's own effort and the - * profile default do not apply, and an effort the model does not support is - * not validated for such a request. - */ - minimizeReasoning?: boolean } diff --git a/packages/llm/llm/tests/service.spec.ts b/packages/llm/llm/tests/service.spec.ts index 5179e88c59..f4aff272e3 100644 --- a/packages/llm/llm/tests/service.spec.ts +++ b/packages/llm/llm/tests/service.spec.ts @@ -861,6 +861,7 @@ describe('LlmRuntime', () => { [{ efforts: [{ id: 'valid', name: 'Valid', description: 1 }] }, 'non-string description'], [{ efforts: [{ id: 'same', name: 'One' }, { id: 'same', name: 'Two' }] }, 'duplicate id'], [{ efforts: [{ id: 'valid', name: 'Valid' }], defaultEffort: 'other' }, 'unknown default'], + [{ efforts: [{ id: 'valid', name: 'Valid' }], floorEffort: 'other' }, 'unknown floor'], ] as const)('rejects invalid model reasoning metadata (%s: %s)', async (metadata, _label) => { const ctx = new Context() await ctx.plugin(LlmRuntime) diff --git a/packages/session/session-title-llm/README.i18n.yaml b/packages/session/session-title-llm/README.i18n.yaml index d595206e93..abe2dfe55e 100644 --- a/packages/session/session-title-llm/README.i18n.yaml +++ b/packages/session/session-title-llm/README.i18n.yaml @@ -36,8 +36,8 @@ en: 0fdc928704abdd3a zh: bd4c0a7dd47578cf /deepseek-ai-dsh-session-title-llm/understand-the-implementation/request-flow: - en: d60754b3baf4e947 - zh: e347c55c9dba1987 + en: 5a093319f914059b + zh: f881f282bbd2fbf5 /deepseek-ai-dsh-session-title-llm/further-exploration: en: 641f31c35c48916b zh: 8e809fbdfef61b61 diff --git a/packages/session/session-title-llm/README.md b/packages/session/session-title-llm/README.md index cf670f1dc1..577c2d950b 100644 --- a/packages/session/session-title-llm/README.md +++ b/packages/session/session-title-llm/README.md @@ -72,7 +72,7 @@ One shared policy so provider plugins cannot drift: config validation, route res ### Request flow -A generation validates the config once at registration; each revision frames the selected messages as JSON, measures the framed prompt's UTF-8 bytes against `maxInputBytes`, resolves the route (the explicit pair or the logged `request/header`), appends a log-only `session/title-llm-request` event carrying the exact dispatchable request, then streams through `ctx.llm` under a composed timeout and cancellation deadline. The dispatched envelope carries `purpose: 'session-title'` for replay labeling and `minimizeReasoning`, and deliberately lacks the agent loop's process-local request identity; adapters dispatch a minimized-reasoning request with the least reasoning the model supports, so a title request does not spend its output ceiling on reasoning: the DeepSeek adapter disables thinking, and the pi-ai adapter selects the model's lowest supported level. Output assembles into text blocks only; tool calls, malformed or empty output, and non-stop finish reasons reject, and a later model failure leaves the request record intact. +A generation validates the config once at registration; each revision frames the selected messages as JSON, measures the framed prompt's UTF-8 bytes against `maxInputBytes`, resolves the route (the explicit pair or the logged `request/header`), reads the routed model's floor effort, appends a log-only `session/title-llm-request` event carrying the exact dispatchable request, then streams through `ctx.llm` under a composed timeout and cancellation deadline. The dispatched envelope carries `purpose: 'session-title'` for replay labeling and that floor effort when the route reports one, and deliberately lacks the agent loop's process-local request identity; a title request therefore does not spend its output ceiling on reasoning: the DeepSeek adapter disables thinking, and the pi-ai adapter dispatches the lowest level its model declares. Output assembles into text blocks only; tool calls, malformed or empty output, and non-stop finish reasons reject, and a later model failure leaves the request record intact. diff --git a/packages/session/session-title-llm/README.zh.md b/packages/session/session-title-llm/README.zh.md index 36733cb025..39c31c9877 100644 --- a/packages/session/session-title-llm/README.zh.md +++ b/packages/session/session-title-llm/README.zh.md @@ -72,7 +72,7 @@ kind: "package-library" ### 请求流程 -生成在注册时校验一次配置;每次修订把选中的消息封装为 JSON,依据 `maxInputBytes` 检查封装提示词的 UTF-8 字节数,解析路由(显式对或已记录 `request/header`),追加一条携带确切可分发请求的仅日志 `session/title-llm-request` 事件,然后在组合的超时与取消截止时间内通过 `ctx.llm` 流式生成。分发的封套携带用于重放标注的 `purpose: 'session-title'` 与 `minimizeReasoning`,且有意不包含 agent loop 的进程本地请求身份;适配器以模型支持的最少推理分发最小化推理请求,使标题请求不把输出上限花在推理上:DeepSeek 适配器禁用思考,pi-ai 适配器选择模型支持的最低等级。输出只组装为文本块;工具调用、格式错误或空输出与非 stop 结束原因都会拒绝,后续模型失败会保留请求记录。 +生成在注册时校验一次配置;每次修订把选中的消息封装为 JSON,依据 `maxInputBytes` 检查封装提示词的 UTF-8 字节数,解析路由(显式对或已记录 `request/header`),读取路由模型的最低强度,追加一条携带确切可分发请求的仅日志 `session/title-llm-request` 事件,然后在组合的超时与取消截止时间内通过 `ctx.llm` 流式生成。分发的封套携带用于重放标注的 `purpose: 'session-title'`,并在路由公布时携带该最低强度,且有意不包含 agent loop 的进程本地请求身份;因此标题请求不把输出上限花在推理上:DeepSeek 适配器禁用思考,pi-ai 适配器分派其模型声明的最低等级。输出只组装为文本块;工具调用、格式错误或空输出与非 stop 结束原因都会拒绝,后续模型失败会保留请求记录。 diff --git a/packages/session/session-title-llm/src/index.ts b/packages/session/session-title-llm/src/index.ts index f7017eee6b..7c62d9bd3e 100644 --- a/packages/session/session-title-llm/src/index.ts +++ b/packages/session/session-title-llm/src/index.ts @@ -14,7 +14,7 @@ declare module '@deepseek-ai/dsh-llm' { } } -import type { FinishReason, GenerateOptions, Message } from '@deepseek-ai/dsh-llm' +import type { FinishReason, GenerateOptions, Message, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import { deadline, MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout' import { deepFreeze } from '@deepseek-ai/dsh-util-values' import type { SessionSeq } from '@deepseek-ai/dsh-session' @@ -208,6 +208,25 @@ function titleMaxTokens( return materialized === undefined ? config.maxOutputTokens : Math.min(config.maxOutputTokens, materialized) } +/** + * The least effort the routed model accepts, so a title request does not spend + * its output cap on reasoning. A route with no registered adapter cannot report + * model metadata, and a route that reports no selectable effort dispatches with + * none; both keep the adapter's own default in force. + * @param ctx - context exposing the registered LLM service. + * @param route - exact provider and model route of this title request. + * @param signal - cancellation covering the lookup. + * @returns the model's floor effort, or undefined when it reports none. + */ +async function titleReasoningEffort( + ctx: Context, + route: SessionTitleModelIdentity, + signal: AbortSignal, +): Promise { + if (!ctx.llm.listProviders().some(provider => provider.id === route.provider)) return undefined + return (await ctx.llm.resolveModelInfo(route.provider, route.model, signal)).reasoning?.floorEffort +} + /** Stable language-aware system instruction shared by both provider plugins. */ function systemPrompt(config: ResolvedSessionTitleLlmConfig): string { return [ @@ -296,6 +315,7 @@ export async function generateSessionTitleWithLlm( const system = systemPrompt(config) using callDeadline = deadline(request.signal, config.timeoutMs, SESSION_TITLE_TIMEOUT_CODE) const maxTokens = titleMaxTokens(config, request) + const floorEffort = await titleReasoningEffort(ctx, route, callDeadline.signal) const options: GenerateOptions = deepFreeze({ provider: route.provider, model: route.model, @@ -304,7 +324,7 @@ export async function generateSessionTitleWithLlm( maxTokens, sessionId: request.session.id, purpose: 'session-title', - minimizeReasoning: true, + ...floorEffort === undefined ? {} : { reasoningEffort: floorEffort }, signal: callDeadline.signal, }) request.session.append('session/title-llm-request', { diff --git a/packages/session/session-title-llm/tests/llm.spec.ts b/packages/session/session-title-llm/tests/llm.spec.ts index d925a1bb76..341f6ade61 100644 --- a/packages/session/session-title-llm/tests/llm.spec.ts +++ b/packages/session/session-title-llm/tests/llm.spec.ts @@ -1,7 +1,7 @@ import { Context } from '@deepseek-ai/cordis' import { describe, expect, it, vi } from 'vitest' -import LlmRuntime, { createUserMessage, ToolCallId, isAgentLoopRequest, LlmAdapter } from '@deepseek-ai/dsh-llm' -import type { FinishReason, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId, isAgentLoopRequest, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import type { FinishReason, GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title' import type { SessionTitleProviderRequest } from '@deepseek-ai/dsh-session-title' @@ -19,10 +19,29 @@ class RecordingAdapter extends LlmAdapter { constructor( private readonly script: readonly StreamChunk[], private readonly onDispatch?: () => void, + private readonly reasoningEfforts?: readonly string[], ) { super() } + override resolveModel(provider: string, model: string): Promise { + const efforts = this.reasoningEfforts + const floor = efforts?.[0] + return Promise.resolve({ + provider, + id: model, + name: model, + ...efforts === undefined || floor === undefined + ? {} + : { + reasoning: { + efforts: efforts.map(id => ({ id: ReasoningEffortId(id), name: id })), + floorEffort: ReasoningEffortId(floor), + }, + }, + }) + } + override async * stream(options: GenerateOptions): AsyncIterable { this.onDispatch?.() this.requests.push(options) @@ -116,20 +135,20 @@ function requestWithoutRoute(ctx: Context, signal = new AbortController().signal return { session: routed.session, messages: routed.messages, signal } } -async function withScript(script: readonly StreamChunk[]): Promise<{ +async function withScript(script: readonly StreamChunk[], reasoningEfforts?: readonly string[]): Promise<{ ctx: Context adapter: RecordingAdapter }> { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(LlmRuntime) - const adapter = new RecordingAdapter(script) + const adapter = new RecordingAdapter(script, undefined, reasoningEfforts) ctx.llm.registerAdapter(['current-route'], adapter) return { ctx, adapter } } describe('generateSessionTitleWithLlm', () => { - it('uses the exact logged route, language targets, full framed input, and output token cap', async () => { + it('uses the exact logged route, language targets, framed input, cap, and floor effort', async () => { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(LlmRuntime) @@ -138,7 +157,7 @@ describe('generateSessionTitleWithLlm', () => { const adapter = new RecordingAdapter(SCRIPT, () => { requestWasLoggedAtDispatch = providerRequest.session.snapshotEvents() .some(event => event.type === 'session/title-llm-request') - }) + }, ['off', 'low', 'high', 'max']) ctx.llm.registerAdapter(['current-route'], adapter) const result = await generateSessionTitleWithLlm( @@ -166,7 +185,7 @@ describe('generateSessionTitleWithLlm', () => { maxTokens: 32, sessionId: providerRequest.session.id, purpose: 'session-title', - minimizeReasoning: true, + reasoningEffort: ReasoningEffortId('off'), }) expect(options.system).toContain('5 words') expect(options.system).toContain('10 CJK characters') @@ -217,6 +236,45 @@ describe('generateSessionTitleWithLlm', () => { expect(adapter.requests[0]?.maxTokens).toBe(32) }) + it('sends no effort when the route has no registered adapter', async () => { + const ctx = new Context() + await ctx.plugin(SessionStore) + await ctx.plugin(LlmRuntime) + const requests: GenerateOptions[] = [] + // A composition can serve a route without a registered adapter, as the + // recorded replay lanes do; the lookup then reports no floor. + ctx.on('llm/stream', (options: GenerateOptions) => { + requests.push(options) + return (async function* (): AsyncIterable { yield * SCRIPT })() + }) + const providerRequest = request(ctx) + + await expect(generateSessionTitleWithLlm( + ctx, + resolveSessionTitleLlmConfig(CONFIG), + providerRequest, + providerRequest.messages, + TITLE_PROVIDER, + )).resolves.toMatchObject({ title: '五个字标题' }) + + expect(requests[0]).not.toHaveProperty('reasoningEffort') + }) + + it('sends no effort when the route reports no selectable level', async () => { + const { ctx, adapter } = await withScript(SCRIPT) + const providerRequest = request(ctx) + + await expect(generateSessionTitleWithLlm( + ctx, + resolveSessionTitleLlmConfig(CONFIG), + providerRequest, + providerRequest.messages, + TITLE_PROVIDER, + )).resolves.toMatchObject({ title: '五个字标题' }) + + expect(adapter.requests[0]).not.toHaveProperty('reasoningEffort') + }) + it('uses paired explicit overrides and bounds the final framed input before model dispatch', async () => { const ctx = new Context() await ctx.plugin(SessionStore)