feat(llm): resolve minimum reasoning during call preparation
This commit is contained in:
1 parent
aa514ef8f8
commit
ffb88d331e
50 files changed
+3502
-164
No files matched your search
@@ -24,6 +24,9 @@
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
config:
|
||||
providers:
|
||||
- id: deepseek-official
|
||||
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
config:
|
||||
providers:
|
||||
- id: deepseek-official
|
||||
|
||||
# Hold the parent's second step until the manager notice enters its inbox.
|
||||
- id: settlement-fence
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"cli-mock","model":"cli-mock"}}
|
||||
{"type":"session/title","data":{"title":"Prove the product headless profile","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}
|
||||
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Inspecting the task before the tool call."},{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[],"texts":["Inspecting the task before the tool call."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Inspecting the task before the tool call."}}},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[],"id":"cli-smoke-call","name":"bash","args":["{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{sessionId}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"say pong","messageSeqs":[8],"source":{"kind":"fallback"}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/attempt","seq":14,"time":0,"data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}]}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"say pong","messageSeqs":[8],"source":{"kind":"fallback"}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/attempt","seq":14,"time":0,"data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}]}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}}
|
||||
|
||||
@@ -269,6 +269,8 @@ describe('headless stream-json snapshots', () => {
|
||||
if (actual === undefined) throw new Error('the headless profile did not persist its session')
|
||||
const context = contextFromLogs([actual.content])
|
||||
const session = normalizeSessionSnapshot(actual.content, context)
|
||||
const titleRequest = parseJsonl(session).find(event => event.type === 'session/title-llm-request')
|
||||
expect(titleRequest?.data).toMatchObject({ reasoningEffort: 'off', maxTokens: TITLE_MAX_TOKENS })
|
||||
if (refreshing) await writeFile(headlessSessionExpected, session)
|
||||
await expectSessionSnapshot(session, context, headlessSessionExpected)
|
||||
expect(session).toContain(task)
|
||||
@@ -716,9 +718,7 @@ describe('headless stream-json snapshots', () => {
|
||||
const agentRequest = server.requests.find(request => !isTitleRequest(request))
|
||||
const titleRequest = server.requests.find(isTitleRequest)
|
||||
expect(agentRequest).not.toHaveProperty('max_completion_tokens')
|
||||
// The route override caps this session's requests at 1024, so the title
|
||||
// request dispatches below the base bundle's 4096 ceiling.
|
||||
expect(titleRequest?.max_tokens).toBe(1_024)
|
||||
expect(titleRequest?.max_tokens).toBe(TITLE_MAX_TOKENS)
|
||||
const header = (parseJsonl(result.stdout)
|
||||
.map(record => record.event)
|
||||
.find((event): event is JsonObject => (
|
||||
|
||||
@@ -1943,7 +1943,7 @@ export type PiAiThinkingTokenBudgetField = NonNullable<OpenAICompletionsCompat['
|
||||
|
||||
- `inject`: `llm`
|
||||
- `refs`: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1129`](../packages/test-support/llm-replay/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1134`](../packages/test-support/llm-replay/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config: the {@link ReplayConfig} inputs, each defaulting to its `DSH_SNAPSHOT_*` env var in `apply`. */
|
||||
@@ -2004,6 +2004,8 @@ export interface ReplayModelConfig {
|
||||
imageRequestTokens?: number
|
||||
/** Optional reasoning-effort ids the replay route accepts, in display order. */
|
||||
reasoningEfforts?: string[]
|
||||
/** Optional least selectable effort; must appear in {@link reasoningEfforts}. */
|
||||
floorReasoningEffort?: string
|
||||
/**
|
||||
* Optional effort materialized when callers omit one; must appear in
|
||||
* {@link reasoningEfforts} or call resolution rejects the route.
|
||||
|
||||
@@ -1945,7 +1945,7 @@ export type PiAiThinkingTokenBudgetField = NonNullable<OpenAICompletionsCompat['
|
||||
|
||||
- `inject`: `llm`
|
||||
- `refs`: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1129`](../packages/test-support/llm-replay/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1134`](../packages/test-support/llm-replay/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config: the {@link ReplayConfig} inputs, each defaulting to its `DSH_SNAPSHOT_*` env var in `apply`. */
|
||||
@@ -2006,6 +2006,8 @@ export interface ReplayModelConfig {
|
||||
imageRequestTokens?: number
|
||||
/** Optional reasoning-effort ids the replay route accepts, in display order. */
|
||||
reasoningEfforts?: string[]
|
||||
/** Optional least selectable effort; must appear in {@link reasoningEfforts}. */
|
||||
floorReasoningEffort?: string
|
||||
/**
|
||||
* Optional effort materialized when callers omit one; must appear in
|
||||
* {@link reasoningEfforts} or call resolution rejects the route.
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 1387c4505dde2faf
|
||||
zh: 48570fa1c13d6910
|
||||
/session-persistence-event-catalog/persistence-type-fingerprints:
|
||||
en: 8497b2c45ef6b896
|
||||
zh: 1c5acbc898958ea9
|
||||
en: d8542cb8987022e0
|
||||
zh: 38e135021cb21f20
|
||||
/session-persistence-event-catalog/event-envelope:
|
||||
en: 7ebb1a2e0dd1f916
|
||||
zh: 8946f415dda5c1b8
|
||||
@@ -177,8 +177,8 @@
|
||||
en: 6c6a27578dff5828
|
||||
zh: 9a5add2bc0df2d0a
|
||||
/session-persistence-event-catalog/events/session/session-title-llm-request-log-only:
|
||||
en: 201b220320c466ab
|
||||
zh: 2823df91f681793d
|
||||
en: 5eb108772a8498c9
|
||||
zh: 1fbc45e377084bae
|
||||
/session-persistence-event-catalog/events/session-log-deepseek:
|
||||
en: 7b827672c80aecdd
|
||||
zh: 7b827672c80aecdd
|
||||
@@ -1065,8 +1065,8 @@
|
||||
en: aa3a91341bf04b9e
|
||||
zh: d767b45d45846333
|
||||
/session-persistence-event-catalog/resolved-persistence-types/llmcallconfigadapterdefaults:
|
||||
en: 5cdc7289f276f93e
|
||||
zh: 11b399ac1a145e9c
|
||||
en: 3ccf327c8a9aa240
|
||||
zh: 5148542d981b1ae3
|
||||
/session-persistence-event-catalog/resolved-persistence-types/llmfailure:
|
||||
en: a18d2091f7e64726
|
||||
zh: 550fc25036bd096e
|
||||
@@ -1155,11 +1155,11 @@
|
||||
en: 9a54c4478ad3b620
|
||||
zh: 02c4c1fd7e252770
|
||||
/session-persistence-event-catalog/resolved-persistence-types/sessiontitleeventdata:
|
||||
en: 5c4a679ff9d5564d
|
||||
zh: 31cc6885c7f74f61
|
||||
en: 4ff17eae0b30056d
|
||||
zh: 6642afbaa87da3ab
|
||||
/session-persistence-event-catalog/resolved-persistence-types/sessiontitlellmrequesteventdata:
|
||||
en: 37bbd04fbfc802e7
|
||||
zh: 37c001a2d8cae514
|
||||
en: 390c1daad2342ce8
|
||||
zh: 07ce68854dce8ea4
|
||||
/session-persistence-event-catalog/resolved-persistence-types/sessiontitlemodelidentity:
|
||||
en: 9531468264499498
|
||||
zh: 9da23c0ad2776563
|
||||
@@ -1986,11 +1986,11 @@
|
||||
en: acf7e21e4d21ebff
|
||||
zh: fb83a853634d204a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-session-title:
|
||||
en: 1e7d458c2d04c067
|
||||
zh: d68876f6130194cb
|
||||
en: 203f5661ca1850b1
|
||||
zh: b3d9ec1019188df4
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-session-title-llm-request:
|
||||
en: 0ddaf7b3bb8ec5c7
|
||||
zh: 82eb798c41b17372
|
||||
en: 246920f06c3a2175
|
||||
zh: 56deee3b86499174
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-step-end:
|
||||
en: 7fc233c4a569dae2
|
||||
zh: bc2b3fed4a397c00
|
||||
|
||||
@@ -56,7 +56,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover
|
||||
| `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) |
|
||||
| `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) |
|
||||
| `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) |
|
||||
| `event:session/title-llm-request` | event | `19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd) |
|
||||
| `event:session/title-llm-request` | event | `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6) |
|
||||
| `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) |
|
||||
| `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) |
|
||||
| `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) |
|
||||
@@ -843,7 +843,7 @@ Source: [`packages/session/session-title/src/index.ts:77`](../packages/session/s
|
||||
|
||||
Types: [SessionTitleLlmRequestEventData](subsystems/session-title.md)
|
||||
|
||||
Source: [`packages/session/session-title-llm/src/index.ts:52`](../packages/session/session-title-llm/src/index.ts)
|
||||
Source: [`packages/session/session-title-llm/src/index.ts:54`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
### `session-log-deepseek/*`
|
||||
|
||||
@@ -3836,7 +3836,7 @@ Sources: [`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call
|
||||
|
||||
SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693`
|
||||
|
||||
Sources: [`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts)
|
||||
Sources: [`packages/llm/llm/src/call-config.ts:50`](../packages/llm/llm/src/call-config.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4493,7 +4493,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/
|
||||
| `source` | required | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) |
|
||||
| `title` | required | `string` |
|
||||
|
||||
<a id="persistence-type-sha256-681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff"></a>
|
||||
<a id="persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19"></a>
|
||||
|
||||
<a id="persistence-type-packagessessionsession-title-llmsrcindextssessiontitlellmrequesteventdata"></a>
|
||||
|
||||
@@ -4501,7 +4501,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/
|
||||
|
||||
### `SessionTitleLlmRequestEventData`
|
||||
|
||||
SHA-256: `681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff`
|
||||
SHA-256: `045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19`
|
||||
|
||||
Sources: [`packages/session/session-title-llm/src/index.ts:34`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
@@ -4510,6 +4510,7 @@ Sources: [`packages/session/session-title-llm/src/index.ts:34`](../packages/sess
|
||||
| `maxTokens` | required | `number` |
|
||||
| `messageSeqs` | required | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) |
|
||||
| `messages` | required | [`Message[]`](#persistence-type-sha256-4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c) |
|
||||
| `reasoningEffort` | optional | `string` |
|
||||
| `route` | required | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) |
|
||||
| `system` | required | `string` |
|
||||
| `titleProvider` | required | `string` |
|
||||
@@ -8344,17 +8345,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5`
|
||||
| `time` | required | `number` |
|
||||
| `type` | required | `"session/title"` |
|
||||
|
||||
<a id="persistence-type-sha256-19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd"></a>
|
||||
<a id="persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6"></a>
|
||||
|
||||
<a id="persistence-type-eventsessiontitle-llm-request"></a>
|
||||
|
||||
### `{ type: "session/title-llm-request" }`
|
||||
|
||||
SHA-256: `19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd`
|
||||
SHA-256: `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6`
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff) |
|
||||
| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19) |
|
||||
| `ignorable` | optional | `true` |
|
||||
| `seq` | required | `number` |
|
||||
| `time` | required | `number` |
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
| `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) |
|
||||
| `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) |
|
||||
| `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) |
|
||||
| `event:session/title-llm-request` | event | `19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd) |
|
||||
| `event:session/title-llm-request` | event | `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6) |
|
||||
| `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) |
|
||||
| `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) |
|
||||
| `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) |
|
||||
@@ -845,7 +845,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[SessionTitleLlmRequestEventData](subsystems/session-title.zh.md)
|
||||
|
||||
来源:[`packages/session/session-title-llm/src/index.ts:52`](../packages/session/session-title-llm/src/index.ts)
|
||||
来源:[`packages/session/session-title-llm/src/index.ts:54`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
### `session-log-deepseek/*`
|
||||
|
||||
@@ -3838,7 +3838,7 @@ SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0`
|
||||
|
||||
SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693`
|
||||
|
||||
来源:[`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts)
|
||||
来源:[`packages/llm/llm/src/call-config.ts:50`](../packages/llm/llm/src/call-config.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4495,7 +4495,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4`
|
||||
| `source` | 必需 | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) |
|
||||
| `title` | 必需 | `string` |
|
||||
|
||||
<a id="persistence-type-sha256-681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff"></a>
|
||||
<a id="persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19"></a>
|
||||
|
||||
<a id="persistence-type-packagessessionsession-title-llmsrcindextssessiontitlellmrequesteventdata"></a>
|
||||
|
||||
@@ -4503,7 +4503,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4`
|
||||
|
||||
### `SessionTitleLlmRequestEventData`
|
||||
|
||||
SHA-256: `681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff`
|
||||
SHA-256: `045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19`
|
||||
|
||||
来源:[`packages/session/session-title-llm/src/index.ts:34`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
@@ -4512,6 +4512,7 @@ SHA-256: `681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff`
|
||||
| `maxTokens` | 必需 | `number` |
|
||||
| `messageSeqs` | 必需 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) |
|
||||
| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c) |
|
||||
| `reasoningEffort` | 可选 | `string` |
|
||||
| `route` | 必需 | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) |
|
||||
| `system` | 必需 | `string` |
|
||||
| `titleProvider` | 必需 | `string` |
|
||||
@@ -8346,17 +8347,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5`
|
||||
| `time` | 必需 | `number` |
|
||||
| `type` | 必需 | `"session/title"` |
|
||||
|
||||
<a id="persistence-type-sha256-19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd"></a>
|
||||
<a id="persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6"></a>
|
||||
|
||||
<a id="persistence-type-eventsessiontitle-llm-request"></a>
|
||||
|
||||
### `{ type: "session/title-llm-request" }`
|
||||
|
||||
SHA-256: `19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd`
|
||||
SHA-256: `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6`
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff) |
|
||||
| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19) |
|
||||
| `ignorable` | 可选 | `true` |
|
||||
| `seq` | 必需 | `number` |
|
||||
| `time` | 必需 | `number` |
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Bilingual-pair consistency record for 2026-10-07-title-reasoning-effort.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-07-title-reasoning-effort.md
|
||||
/:
|
||||
en: 3d5a424ae7d7e46b
|
||||
zh: c22a8e4a62d8cc1a
|
||||
/2026-10-07-title-reasoning-effort:
|
||||
en: 62238f237d762372
|
||||
zh: 67c868935122faba
|
||||
/2026-10-07-title-reasoning-effort/summary:
|
||||
en: a23a870e04888548
|
||||
zh: a794ea9022adf492
|
||||
/2026-10-07-title-reasoning-effort/table-of-contents:
|
||||
en: 6cf314a069ceb451
|
||||
zh: 9adff678e9ecab3b
|
||||
/2026-10-07-title-reasoning-effort/declaration:
|
||||
en: 6907136f28e48c90
|
||||
zh: 02f8298485f3d8e7
|
||||
/2026-10-07-title-reasoning-effort/compatibility:
|
||||
en: 2aef689aa3af8d1c
|
||||
zh: dc468f9d2c98d70c
|
||||
/2026-10-07-title-reasoning-effort/verification:
|
||||
en: 48ef9b977eaf074b
|
||||
zh: b1183bda3a6a38be
|
||||
/2026-10-07-title-reasoning-effort/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: "Records a persistence type transition and its compatibility acknowledgement."
|
||||
kind: persistence-change
|
||||
---
|
||||
|
||||
# 2026-10-07-title-reasoning-effort
|
||||
|
||||
English | [中文](2026-10-07-title-reasoning-effort.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Records the concrete reasoning effort selected for auxiliary session-title requests.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Declaration](#declaration)
|
||||
- [Compatibility](#compatibility)
|
||||
- [Verification](#verification)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
<a id="declaration"></a>
|
||||
## Declaration
|
||||
|
||||
```yaml persistence-change
|
||||
schemaVersion: 1
|
||||
id: 2026-10-07-title-reasoning-effort
|
||||
baseline: false
|
||||
changes:
|
||||
- root: "event:session/title-llm-request"
|
||||
previous: "2026-10-05-working-directory-attribution"
|
||||
after: "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6"
|
||||
decision: same-version
|
||||
```
|
||||
|
||||
<a id="compatibility"></a>
|
||||
## Compatibility
|
||||
|
||||
The new reasoningEffort field is optional. Existing V4 records remain valid and retain their original absence of a recorded effort. Prepared title requests write the resolved effort when the route exposes selectable reasoning; no Session format bump is required.
|
||||
|
||||
<a id="verification"></a>
|
||||
## Verification
|
||||
|
||||
Focused LLM, adapter, title-provider, and replay tests passed: 50 files and 1960 tests. The session-title-after-turn SDK snapshot passed with a recorded low effort selected from a route whose default is high.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
None.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: "记录持久化类型更改及其兼容性确认。"
|
||||
kind: persistence-change
|
||||
---
|
||||
|
||||
# 2026-10-07-title-reasoning-effort
|
||||
|
||||
[English](2026-10-07-title-reasoning-effort.md) | 中文
|
||||
|
||||
## 概述
|
||||
|
||||
记录辅助会话标题请求选择的具体推理强度。
|
||||
|
||||
## 目录
|
||||
|
||||
- [声明](#declaration)
|
||||
- [兼容性](#compatibility)
|
||||
- [验证](#verification)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
<a id="declaration"></a>
|
||||
## 声明
|
||||
|
||||
```yaml persistence-change
|
||||
schemaVersion: 1
|
||||
id: 2026-10-07-title-reasoning-effort
|
||||
baseline: false
|
||||
changes:
|
||||
- root: "event:session/title-llm-request"
|
||||
previous: "2026-10-05-working-directory-attribution"
|
||||
after: "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6"
|
||||
decision: same-version
|
||||
```
|
||||
|
||||
<a id="compatibility"></a>
|
||||
## 兼容性
|
||||
|
||||
新增 reasoningEffort 字段为可选。现有 V4 记录仍有效,继续保留其未记录强度的事实。路由提供可选推理强度时,已准备的标题请求写入解析后的强度;无需提升 Session 格式版本。
|
||||
|
||||
<a id="verification"></a>
|
||||
## 验证
|
||||
|
||||
LLM、适配器、标题提供方与回放的定向测试通过:50 个文件、1960 个测试。session-title-after-turn SDK 快照通过,记录从默认 high 的路由中选出的 low 强度。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
无。
|
||||
Generated
+21
-16
@@ -11890,7 +11890,7 @@
|
||||
"kind": "event",
|
||||
"event": "session/title-llm-request",
|
||||
"surface": false,
|
||||
"digest": "19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd",
|
||||
"digest": "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6",
|
||||
"schema": {
|
||||
"root": 0,
|
||||
"nodes": [
|
||||
@@ -11943,6 +11943,11 @@
|
||||
"type": 4,
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "reasoningEffort",
|
||||
"type": 10,
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "route",
|
||||
"type": 209,
|
||||
@@ -22041,6 +22046,15 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19",
|
||||
"names": [
|
||||
"packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/session/session-title-llm/src/index.ts:34"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "05a2796621b1ee6c03ef7abe560b25116bb4da4d592312117242a78415a32b04",
|
||||
"names": [],
|
||||
@@ -22359,11 +22373,6 @@
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "1a2718b00937b236a0d0910ef5dab2f9f8b65e4bde410cc21f15fbbf29f584d3",
|
||||
"names": [],
|
||||
@@ -22758,7 +22767,7 @@
|
||||
"packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults"
|
||||
],
|
||||
"sources": [
|
||||
"packages/llm/llm/src/call-config.ts:36"
|
||||
"packages/llm/llm/src/call-config.ts:50"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -23537,15 +23546,6 @@
|
||||
"packages/llm/llm/src/call-config.ts:23"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "681028d4517ac1ac96af970b6d1d084e8c257d9ba7941478985922880b0eccff",
|
||||
"names": [
|
||||
"packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"
|
||||
],
|
||||
"sources": [
|
||||
"packages/session/session-title-llm/src/index.ts:34"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe",
|
||||
"names": [],
|
||||
@@ -24691,6 +24691,11 @@
|
||||
"packages/experimental/agent-team/src/types.ts:71"
|
||||
]
|
||||
},
|
||||
{
|
||||
"digest": "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6",
|
||||
"names": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"digest": "b3681c026a0b98e98b103f057dfd217fbe904129c090efd668253ec9aea7fece",
|
||||
"names": [],
|
||||
|
||||
@@ -554,9 +554,9 @@ 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.
|
||||
* The least selectable effort this route accepts. Minimum-effort call
|
||||
* preparation requires this field when reasoning controls are available.
|
||||
* It does not guarantee that the provider disables reasoning.
|
||||
*/
|
||||
floorEffort?: ReasoningEffortId
|
||||
/**
|
||||
@@ -767,6 +767,24 @@ interface LlmCallConfig {
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact effort or a request to select the route's least controllable reasoning. */
|
||||
type ReasoningSelection = ReasoningEffortId | { readonly select: 'minimum' }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Preparation input. Concrete call configs are accepted unchanged. A minimum
|
||||
* selection uses the adapter's declared floor; selectable efforts without a
|
||||
* declared floor reject. A route with no selectable reasoning leaves the
|
||||
* effort unset. Minimum selection does not guarantee zero reasoning tokens.
|
||||
*/
|
||||
interface LlmCallRequest extends Omit<LlmCallConfig, 'reasoningEffort'> {
|
||||
/** Omission uses the adapter default; an id requires that exact effort. */
|
||||
reasoningEffort?: ReasoningSelection
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Effective config fields supplied by exact-model adapter resolution rather
|
||||
@@ -1060,11 +1078,14 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise<Ll
|
||||
* Resolve one call under its current adapter registration. The returned
|
||||
* one-shot handle keeps that registration across header logging and dispatch,
|
||||
* so HMR cannot combine one adapter's capability result with another adapter.
|
||||
* @param config - provider/model route and optional request controls.
|
||||
* Minimum reasoning selects the adapter-declared floor, or leaves the effort
|
||||
* unset when the route has no selectable reasoning. An undeclared floor for
|
||||
* selectable efforts rejects with `UNSUPPORTED_REASONING_SELECTION`.
|
||||
* @param request - provider/model route and concrete or intent-based controls.
|
||||
* @param signal - optional cancellation for adapter-owned capability lookup.
|
||||
* @returns a prepared config and its registration-bound stream entry point.
|
||||
*/
|
||||
async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>
|
||||
async prepareCall(request: LlmCallRequest, signal?: AbortSignal): Promise<PreparedLlmCall>
|
||||
|
||||
/**
|
||||
* Stream one model call as raw chunks (token-level deltas). Replay state is
|
||||
|
||||
@@ -560,9 +560,9 @@ 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.
|
||||
* The least selectable effort this route accepts. Minimum-effort call
|
||||
* preparation requires this field when reasoning controls are available.
|
||||
* It does not guarantee that the provider disables reasoning.
|
||||
*/
|
||||
floorEffort?: ReasoningEffortId
|
||||
/**
|
||||
@@ -773,6 +773,24 @@ interface LlmCallConfig {
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact effort or a request to select the route's least controllable reasoning. */
|
||||
type ReasoningSelection = ReasoningEffortId | { readonly select: 'minimum' }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Preparation input. Concrete call configs are accepted unchanged. A minimum
|
||||
* selection uses the adapter's declared floor; selectable efforts without a
|
||||
* declared floor reject. A route with no selectable reasoning leaves the
|
||||
* effort unset. Minimum selection does not guarantee zero reasoning tokens.
|
||||
*/
|
||||
interface LlmCallRequest extends Omit<LlmCallConfig, 'reasoningEffort'> {
|
||||
/** Omission uses the adapter default; an id requires that exact effort. */
|
||||
reasoningEffort?: ReasoningSelection
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Effective config fields supplied by exact-model adapter resolution rather
|
||||
@@ -1066,11 +1084,14 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise<Ll
|
||||
* Resolve one call under its current adapter registration. The returned
|
||||
* one-shot handle keeps that registration across header logging and dispatch,
|
||||
* so HMR cannot combine one adapter's capability result with another adapter.
|
||||
* @param config - provider/model route and optional request controls.
|
||||
* Minimum reasoning selects the adapter-declared floor, or leaves the effort
|
||||
* unset when the route has no selectable reasoning. An undeclared floor for
|
||||
* selectable efforts rejects with `UNSUPPORTED_REASONING_SELECTION`.
|
||||
* @param request - provider/model route and concrete or intent-based controls.
|
||||
* @param signal - optional cancellation for adapter-owned capability lookup.
|
||||
* @returns a prepared config and its registration-bound stream entry point.
|
||||
*/
|
||||
async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>
|
||||
async prepareCall(request: LlmCallRequest, signal?: AbortSignal): Promise<PreparedLlmCall>
|
||||
|
||||
/**
|
||||
* Stream one model call as raw chunks (token-level deltas). Replay state is
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: c6be2d86647daa6f
|
||||
zh: fb259d9fb4738f39
|
||||
/session-titles/auxiliary-request-record:
|
||||
en: 4942bec7a7d1d9de
|
||||
zh: 9edfc1c908577c37
|
||||
en: c36916905dad76f9
|
||||
zh: c82ff6c3d62e53e5
|
||||
/session-titles/provider-input-and-output:
|
||||
en: 886e036e442ae06a
|
||||
zh: 49f95782b8aa9bf3
|
||||
@@ -64,7 +64,7 @@ interface SessionTitleSnapshot extends SessionTitleEventData {
|
||||
|
||||
## Auxiliary request record
|
||||
|
||||
The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, provider ownership, and source-message attribution even when generation later fails.
|
||||
The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, resolved reasoning effort, provider ownership, and source-message attribution even when generation later fails.
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact model-visible request recorded before one auxiliary title dispatch. */
|
||||
@@ -81,6 +81,8 @@ interface SessionTitleLlmRequestEventData {
|
||||
readonly messages: Message[]
|
||||
/** Exact auxiliary output-token cap. */
|
||||
readonly maxTokens: number
|
||||
/** Resolved reasoning effort when recorded; older requests can omit it. */
|
||||
readonly reasoningEffort?: ReasoningEffortId
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ interface SessionTitleSnapshot extends SessionTitleEventData {
|
||||
|
||||
## 辅助请求记录
|
||||
|
||||
共享 LLM 辅助组件会在调用模型前,记录每一项已经过验证且可分发的标题请求。即使后续生成失败,载荷仍会复现模型可见的系统输入与消息输入、路由、输出上限、提供方归属和源消息归因。
|
||||
共享 LLM 辅助组件会在调用模型前,记录每一项已经过验证且可分发的标题请求。即使后续生成失败,载荷仍会复现模型可见的系统输入与消息输入、路由、输出上限、解析后的推理强度、提供方归属和源消息归因。
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact model-visible request recorded before one auxiliary title dispatch. */
|
||||
@@ -81,6 +81,8 @@ interface SessionTitleLlmRequestEventData {
|
||||
readonly messages: Message[]
|
||||
/** Exact auxiliary output-token cap. */
|
||||
readonly maxTokens: number
|
||||
/** Resolved reasoning effort when recorded; older requests can omit it. */
|
||||
readonly reasoningEffort?: ReasoningEffortId
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.md
|
||||
/:
|
||||
en: 87becdc19338ba63
|
||||
zh: a9a177200eb0900f
|
||||
/prepare-custom-title-routes:
|
||||
en: cab86bb2cc427297
|
||||
zh: 7a14e268bf53fc73
|
||||
/prepare-custom-title-routes/change:
|
||||
en: 9bfbb27b3183431d
|
||||
zh: 7ca1eed2ec9d33fa
|
||||
/prepare-custom-title-routes/migration:
|
||||
en: 35e465b350feb1ff
|
||||
zh: 89f766ee9aee5398
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Custom title routes require registered adapters and minimum-effort metadata."
|
||||
---
|
||||
|
||||
# Prepare custom title routes
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Model-backed session titles prepare their requests through `ctx.llm.prepareCall()`. Compositions that serve title routes only through `llm/stream` middleware must also register an adapter. Custom adapters that expose selectable reasoning efforts must declare `reasoning.floorEffort`; otherwise minimum-effort preparation rejects before dispatch. Existing concrete `prepareCall()` inputs remain valid.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Register the title route with `ctx.llm.registerAdapter()`. Replay compositions can configure the existing `llm-replay` plugin's `providers` list.
|
||||
2. In the adapter's model metadata, set `reasoning.floorEffort` to the least selectable id in `reasoning.efforts`. Replay models use `floorReasoningEffort` alongside `reasoningEfforts`. Omit reasoning metadata when the route has no selectable controls.
|
||||
3. Generate a title and check that `session/title-llm-request` records the selected effort and a provider title is saved. Existing Session records without that optional field remain readable.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "自定义标题路由要求注册适配器并提供最低推理强度元数据。"
|
||||
---
|
||||
|
||||
# 准备自定义标题路由
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
模型生成的会话标题通过 `ctx.llm.prepareCall()` 准备请求。仅通过 `llm/stream` 中间件提供标题路由的组合还必须注册适配器。公开可选推理强度的自定义适配器必须声明 `reasoning.floorEffort`;否则最低强度准备会在分发前拒绝。现有具体 `prepareCall()` 输入仍然有效。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 通过 `ctx.llm.registerAdapter()` 注册标题路由。回放组合可配置现有 `llm-replay` 插件的 `providers` 列表。
|
||||
2. 在适配器的模型元数据中,将 `reasoning.floorEffort` 设为 `reasoning.efforts` 中最低可选强度的 id。回放模型使用 `floorReasoningEffort` 和 `reasoningEfforts`。路由没有可选控制时省略推理元数据。
|
||||
3. 生成标题,确认 `session/title-llm-request` 记录了所选强度,且提供方标题已保存。不含该可选字段的现有 Session 记录仍可读取。
|
||||
@@ -1465,9 +1465,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
returns: 'a detached config only when a default must be materialized.',
|
||||
},
|
||||
{
|
||||
signature: 'async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>',
|
||||
description: 'Resolve one call under its current adapter registration. The returned one-shot handle keeps that registration across header logging and dispatch, so HMR cannot combine one adapter\'s capability result with another adapter.',
|
||||
parameters: [{ name: 'config', description: 'provider/model route and optional request controls.' }, { name: 'signal', description: 'optional cancellation for adapter-owned capability lookup.' }],
|
||||
signature: 'async prepareCall(request: LlmCallRequest, signal?: AbortSignal): Promise<PreparedLlmCall>',
|
||||
description: 'Resolve one call under its current adapter registration. The returned one-shot handle keeps that registration across header logging and dispatch, so HMR cannot combine one adapter\'s capability result with another adapter. Minimum reasoning selects the adapter-declared floor, or leaves the effort unset when the route has no selectable reasoning. An undeclared floor for selectable efforts rejects with `UNSUPPORTED_REASONING_SELECTION`.',
|
||||
parameters: [{ name: 'request', description: 'provider/model route and concrete or intent-based controls.' }, { name: 'signal', description: 'optional cancellation for adapter-owned capability lookup.' }],
|
||||
returns: 'a prepared config and its registration-bound stream entry point.',
|
||||
},
|
||||
{
|
||||
@@ -5731,6 +5731,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'LlmCallConfigAdapterDefaults',
|
||||
declaration: 'export interface LlmCallConfigAdapterDefaults {\n reasoningEffort?: true;\n maxTokens?: true;\n}',
|
||||
},
|
||||
{
|
||||
name: 'LlmCallRequest',
|
||||
declaration: 'export interface LlmCallRequest extends Omit<LlmCallConfig, \'reasoningEffort\'> {\n reasoningEffort?: ReasoningSelection;\n}',
|
||||
},
|
||||
{
|
||||
name: 'LlmConfigurableProvider',
|
||||
declaration: 'export interface LlmConfigurableProvider {\n provider: string;\n displayName: string;\n settingsNs: string;\n settingsPath: readonly string[];\n declared?: boolean;\n error?: string;\n}',
|
||||
@@ -5781,7 +5785,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'LlmRuntime',
|
||||
declaration: 'export class LlmRuntime extends TypertRemoteService {\n constructor(ctx: Context);\n registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle;\n @Remote\n listProviders(): LlmProviderInfo[];\n registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle;\n @Remote\n listConfigurableProviders(): LlmConfigurableProvider[];\n registerModelDiscovery(settingsNs: string, discover: (request: LlmModelDiscoveryRequest, signal?: AbortSignal) => Promise<readonly LlmDiscoveredModel[]>): () => void;\n async discoverModels(settingsNs: string, request: LlmModelDiscoveryRequest, signal?: AbortSignal): Promise<LlmDiscoveredModel[]>;\n @Remote(\'discoverModels\')\n async remoteDiscoverModels(settingsNs: string, request: LlmModelDiscoveryRequest, signal: AbortSignal): Promise<LlmDiscoveredModel[]>;\n providerRetryPolicy(provider: string): ResolvedRetryPolicy;\n imageRequestPricing(provider: string, model: string): LlmImageRequestPricing | undefined;\n fileRequestText(ref: FileAttachmentRef): string;\n async listModels(provider: string): Promise<LlmModelInfo[]>;\n async resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;\n async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise<LlmCallConfig>;\n async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>;\n stream(options: GenerateOptions) /* …truncated — full shape in source */',
|
||||
declaration: 'export class LlmRuntime extends TypertRemoteService {\n constructor(ctx: Context);\n registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle;\n @Remote\n listProviders(): LlmProviderInfo[];\n registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle;\n @Remote\n listConfigurableProviders(): LlmConfigurableProvider[];\n registerModelDiscovery(settingsNs: string, discover: (request: LlmModelDiscoveryRequest, signal?: AbortSignal) => Promise<readonly LlmDiscoveredModel[]>): () => void;\n async discoverModels(settingsNs: string, request: LlmModelDiscoveryRequest, signal?: AbortSignal): Promise<LlmDiscoveredModel[]>;\n @Remote(\'discoverModels\')\n async remoteDiscoverModels(settingsNs: string, request: LlmModelDiscoveryRequest, signal: AbortSignal): Promise<LlmDiscoveredModel[]>;\n providerRetryPolicy(provider: string): ResolvedRetryPolicy;\n imageRequestPricing(provider: string, model: string): LlmImageRequestPricing | undefined;\n fileRequestText(ref: FileAttachmentRef): string;\n async listModels(provider: string): Promise<LlmModelInfo[]>;\n async resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;\n async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise<LlmCallConfig>;\n async prepareCall(request: LlmCallRequest, signal?: AbortSignal): Promise<PreparedLlmCall>;\n stream(options: GenerateOption /* …truncated — full shape in source */',
|
||||
},
|
||||
{
|
||||
name: 'LocalAtInput',
|
||||
@@ -6363,6 +6367,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'ReasoningEffortId',
|
||||
declaration: 'export type ReasoningEffortId = Branded<\'ReasoningEffortId\'>;',
|
||||
},
|
||||
{
|
||||
name: 'ReasoningSelection',
|
||||
declaration: 'export type ReasoningSelection = ReasoningEffortId | {\n readonly select: \'minimum\';\n};',
|
||||
},
|
||||
{
|
||||
name: 'RecurringScheduleRecord',
|
||||
declaration: 'export type RecurringScheduleRecord = EveryScheduleRecord | DailyScheduleRecord | WeeklyScheduleRecord | CronScheduleRecord;',
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
en: 376f83206858b601
|
||||
zh: 38bb6993609c96a6
|
||||
/deepseek-ai-dsh-llm-pi-ai/use-this-package/run-with-reasoning-and-wire-compatibility:
|
||||
en: b1f319877df7c6ba
|
||||
zh: 351a49c549a1850f
|
||||
en: d3a4f32e13ea83a3
|
||||
zh: b0506ebbf17dcaaf
|
||||
/deepseek-ai-dsh-llm-pi-ai/use-this-package/change-configuration-at-runtime:
|
||||
en: 92f554e1718614ca
|
||||
zh: f17ed3ad3e5d28e2
|
||||
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
Exact-model metadata reports `reasoning.floorEffort`, the least selectable level the route accepts. `prepareCall()` can resolve a minimum-effort request to this id; the adapter receives only the resolved effort. Selecting `off` does not guarantee disabled reasoning: 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.
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ profile 的 `models` 列表会替换而非扩展路由的已安装目录;每
|
||||
|
||||
`reasoningEfforts` 声明模型可选择的 thinking 等级:每个键都是选择器提供的等级,其值是分派时在协议中发送的拼写,因此 `max: ultra` 可以为拥有自有词汇的网关重命名等级。省略该字段时保留已安装目录条目的能力;`false` 声明非推理模型。对于 pi-ai 无法识别的端点,`compat` 开关重塑请求——哪个角色携带系统提示词、哪个字段限制输出、thinking 等级如何传递——可逐路由、逐模型配置。条目与已安装目录都没有尺寸的模型,会采用路由的 `defaultContextWindow` 与 `defaultMaxTokens` 回退值。
|
||||
|
||||
确切模型元数据会公布 `reasoning.floorEffort`,即该路由接受的最低等级:模型能停止推理时为 `off`,否则为声明的最低等级。需要避免把输出花在推理上的调用方选择该强度,本适配器分派收到的强度。`off` 的分派方式与手动选择 `off` 完全相同,因此以省略推理字段表达 `off` 的协议会沿用网关默认行为;所选等级下产生的推理 token 都计入该请求的输出上限。
|
||||
确切模型元数据公布 `reasoning.floorEffort`,即该路由接受的最低可选等级。`prepareCall()` 可将最低强度请求解析为此 id;适配器只接收解析后的强度。选择 `off` 不保证禁用推理:以省略推理字段表达该值的协议会沿用网关默认行为。所选等级下产生的推理 token 都计入该请求的输出上限。
|
||||
|
||||
对于自托管 Chat Completions 端点,`thinkingTokenBudgetField` 选择推理预算参数,`vllmPriority` 在服务端启用优先级调度时设置整数调度优先级。模板参数接受 `$var: thinking.budget`。`openai-responses` 网关可设置 `supportsMaxOutputTokens: false` 来省略 `max_output_tokens`;Azure 与 Codex 传输会忽略这个共享兼容字段。这些控制均需显式启用;目录拥有的 Anthropic effort 和回退能力不是可配置开关。
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: c3ecb5a1421f27db
|
||||
zh: 4bbd3c0e513845e1
|
||||
/deepseek-ai-dsh-llm/use-this-package/minimal-composition:
|
||||
en: 7550d716b90fcb09
|
||||
zh: 77891a44ee623c7a
|
||||
en: edb00bff3175f3b6
|
||||
zh: 927048e5604787b0
|
||||
/deepseek-ai-dsh-llm/use-this-package/what-you-can-do:
|
||||
en: b45d7a414141734e
|
||||
zh: a641c8ea3827cee6
|
||||
|
||||
@@ -60,6 +60,8 @@ After a successful mount, `ctx.llm.listProviders()` reports the registered route
|
||||
|
||||
`GenerateOptions.messages` accepts durable `Message` values and request-only `RequestUserInput` values. Request-only inputs carry user-role content with no `id` or `source`; Session writes and Agent delivery still require durable messages. Callers keep auxiliary inputs unchanged until the stream settles. A caller that records its exact request, such as session-title generation, must use durable messages.
|
||||
|
||||
`prepareCall(request, signal)` accepts existing `LlmCallConfig` values and `reasoningEffort: { select: 'minimum' }`. It returns a frozen concrete configuration for recording and dispatch. An exact effort id is validated unchanged; omission uses the route default. Minimum selection uses the declared `floorEffort`, rejects selectable efforts without a floor, and leaves effort unspecified on routes without reasoning controls. It does not guarantee zero reasoning tokens. Preparation requires a registered adapter and rejects before streaming when resolution fails.
|
||||
|
||||
### What you can do
|
||||
|
||||
- **Stream one model call** — `ctx.llm.stream(options)` yields raw chunks (token-level deltas) for any registered provider and model; consumers assemble them with `BlockAssembler`.
|
||||
|
||||
@@ -60,6 +60,8 @@ for await (const chunk of ctx.llm.stream({
|
||||
|
||||
`GenerateOptions.messages` 接受持久 `Message` 值和仅供请求使用的 `RequestUserInput` 值。仅供请求使用的输入包含 user-role 内容,不含 `id` 或 `source`;Session 写入和 Agent 投递仍然要求持久消息。调用方必须在流结束前保持辅助输入不变。会记录完整请求的调用方(例如会话标题生成)必须使用持久消息。
|
||||
|
||||
`prepareCall(request, signal)` 接受现有的 `LlmCallConfig` 值,也接受 `reasoningEffort: { select: 'minimum' }`。它返回用于记录与分发的冻结具体配置。确切强度 id 保持原值并接受校验;省略时使用路由默认值。最低强度选择使用声明的 `floorEffort`;有可选强度却没有最低强度时拒绝,没有推理控制时不指定强度。它不保证零推理 token。准备操作要求注册适配器,解析失败会在流式调用前拒绝。
|
||||
|
||||
### 你可以做什么
|
||||
|
||||
- **流式发起一次模型调用**——`ctx.llm.stream(options)` 为任何已注册提供方与模型产出原始分片(token 级增量);消费方用 `BlockAssembler` 组装。
|
||||
|
||||
@@ -29,6 +29,20 @@ export interface LlmCallConfig {
|
||||
stop?: string[]
|
||||
}
|
||||
|
||||
/** Exact effort or a request to select the route's least controllable reasoning. */
|
||||
export type ReasoningSelection = ReasoningEffortId | { readonly select: 'minimum' }
|
||||
|
||||
/**
|
||||
* Preparation input. Concrete call configs are accepted unchanged. A minimum
|
||||
* selection uses the adapter's declared floor; selectable efforts without a
|
||||
* declared floor reject. A route with no selectable reasoning leaves the
|
||||
* effort unset. Minimum selection does not guarantee zero reasoning tokens.
|
||||
*/
|
||||
export interface LlmCallRequest extends Omit<LlmCallConfig, 'reasoningEffort'> {
|
||||
/** Omission uses the adapter default; an id requires that exact effort. */
|
||||
reasoningEffort?: ReasoningSelection
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective config fields supplied by exact-model adapter resolution rather
|
||||
* than by the caller's request proposal.
|
||||
|
||||
@@ -32,7 +32,7 @@ import { resolveRetryPolicy } from './retry-policy.ts'
|
||||
import type { ResolvedRetryPolicy } from './retry-policy.ts'
|
||||
import type { ProviderRequestId } from './brand.ts'
|
||||
import { callConfigEquals } from './call-config.ts'
|
||||
import type { LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts'
|
||||
import type { LlmCallConfig, LlmCallConfigAdapterDefaults, LlmCallRequest } from './call-config.ts'
|
||||
import { HarnessError, INVALID_CREDENTIAL_CODE } from './error.ts'
|
||||
import { normalizeLlmFailure } from './adapter-failure.ts'
|
||||
import { normalizeApiKey } from './api-key.ts'
|
||||
@@ -52,7 +52,7 @@ export * from './message.ts'
|
||||
export * from './retry-policy.ts'
|
||||
export { BlockAssembler } from './assembler.ts'
|
||||
export { callConfigEquals, isAgentLoopRequest, markAgentLoopRequest } from './call-config.ts'
|
||||
export type { LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts'
|
||||
export type { LlmCallConfig, LlmCallConfigAdapterDefaults, LlmCallRequest, ReasoningSelection } from './call-config.ts'
|
||||
|
||||
declare module '@deepseek-ai/cordis' {
|
||||
interface Context {
|
||||
@@ -929,21 +929,37 @@ export class LlmRuntime extends TypertRemoteService {
|
||||
* Resolve one call under its current adapter registration. The returned
|
||||
* one-shot handle keeps that registration across header logging and dispatch,
|
||||
* so HMR cannot combine one adapter's capability result with another adapter.
|
||||
* @param config - provider/model route and optional request controls.
|
||||
* Minimum reasoning selects the adapter-declared floor, or leaves the effort
|
||||
* unset when the route has no selectable reasoning. An undeclared floor for
|
||||
* selectable efforts rejects with `UNSUPPORTED_REASONING_SELECTION`.
|
||||
* @param request - provider/model route and concrete or intent-based controls.
|
||||
* @param signal - optional cancellation for adapter-owned capability lookup.
|
||||
* @returns a prepared config and its registration-bound stream entry point.
|
||||
*/
|
||||
async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall> {
|
||||
const registration = this.registration(config.provider)
|
||||
const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal)
|
||||
const modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model)
|
||||
async prepareCall(request: LlmCallRequest, signal?: AbortSignal): Promise<PreparedLlmCall> {
|
||||
const registration = this.registration(request.provider)
|
||||
const adapterCall = await registration.adapter.prepareCall(request.provider, request.model, signal)
|
||||
const modelInfo = this.normalizeModelInfo(registration, request.model, adapterCall.model)
|
||||
const { reasoningEffort: selection, ...controls } = request
|
||||
if (typeof selection === 'object'
|
||||
&& modelInfo.reasoning !== undefined && modelInfo.reasoning.floorEffort === undefined) {
|
||||
throw new LlmError(
|
||||
`provider "${request.provider}" model "${request.model}" does not declare a minimum reasoning effort`,
|
||||
'UNSUPPORTED_REASONING_SELECTION',
|
||||
)
|
||||
}
|
||||
const reasoningEffort = typeof selection === 'object' ? modelInfo.reasoning?.floorEffort : selection
|
||||
const config: LlmCallConfig = {
|
||||
...controls,
|
||||
...reasoningEffort === undefined ? {} : { reasoningEffort },
|
||||
}
|
||||
const resolved = this.resolveCallWithInfo(config, modelInfo)
|
||||
const resolvedConfig = deepFreeze(structuredClone(resolved.config))
|
||||
const context = resolved.context === undefined
|
||||
? undefined
|
||||
: deepFreeze(structuredClone(resolved.context))
|
||||
const adapterDefaults = deepFreeze<LlmCallConfigAdapterDefaults>({
|
||||
...config.reasoningEffort === undefined && resolvedConfig.reasoningEffort !== undefined
|
||||
...request.reasoningEffort === undefined && resolvedConfig.reasoningEffort !== undefined
|
||||
? { reasoningEffort: true }
|
||||
: {},
|
||||
...config.maxTokens === undefined && resolvedConfig.maxTokens !== undefined
|
||||
|
||||
@@ -381,9 +381,9 @@ 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.
|
||||
* The least selectable effort this route accepts. Minimum-effort call
|
||||
* preparation requires this field when reasoning controls are available.
|
||||
* It does not guarantee that the provider disables reasoning.
|
||||
*/
|
||||
floorEffort?: ReasoningEffortId
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ import LlmRuntime, {
|
||||
createUserMessage,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
LlmCallConfig,
|
||||
LlmModelContext,
|
||||
LlmModelInfo,
|
||||
LlmModelReasoningInfo,
|
||||
@@ -831,7 +832,7 @@ describe('LlmRuntime', () => {
|
||||
})
|
||||
const preparedDefault = await ctx.llm.prepareCall({ provider: 'route', model: 'model' })
|
||||
expect(preparedDefault.adapterDefaults).toEqual({ maxTokens: true })
|
||||
const explicit = { provider: 'route', model: 'model', maxTokens: 8_192 }
|
||||
const explicit: LlmCallConfig = { provider: 'route', model: 'model', maxTokens: 8_192 }
|
||||
await expect(ctx.llm.resolveCallConfig(explicit)).resolves.toBe(explicit)
|
||||
const preparedExplicit = await ctx.llm.prepareCall(explicit)
|
||||
expect(preparedExplicit.adapterDefaults).toEqual({})
|
||||
@@ -1031,6 +1032,65 @@ describe('LlmRuntime', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
[undefined, 'high', { reasoningEffort: true }],
|
||||
[ReasoningEffortId('minimum'), 'minimum', {}],
|
||||
[{ select: 'minimum' } as const, 'low', {}],
|
||||
])('prepares reasoning selection %s as a concrete effort', async (selection, expected, defaults) => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmRuntime)
|
||||
const adapter = new class extends RecordingAdapter {
|
||||
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
|
||||
return Promise.resolve({
|
||||
provider, id: model, name: model,
|
||||
reasoning: {
|
||||
efforts: ['high', 'minimum', 'low'].map(id => ({ id: ReasoningEffortId(id), name: id })),
|
||||
floorEffort: ReasoningEffortId('low'),
|
||||
defaultEffort: ReasoningEffortId('high'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}(SCRIPT)
|
||||
ctx.llm.registerAdapter(['route'], adapter)
|
||||
const prepared = await ctx.llm.prepareCall({
|
||||
provider: 'route', model: 'model',
|
||||
...selection === undefined ? {} : { reasoningEffort: selection },
|
||||
})
|
||||
expect(prepared.config.reasoningEffort).toBe(expected)
|
||||
expect(prepared.adapterDefaults).toEqual(defaults)
|
||||
await collect(prepared.stream({ ...prepared.config, messages: [] }))
|
||||
expect(adapter.lastOptions?.reasoningEffort).toBe(expected)
|
||||
})
|
||||
|
||||
it('leaves minimum reasoning unspecified when a route has no selectable efforts', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmRuntime)
|
||||
const adapter = new RecordingAdapter(SCRIPT)
|
||||
ctx.llm.registerAdapter(['route'], adapter)
|
||||
const prepared = await ctx.llm.prepareCall({
|
||||
provider: 'route', model: 'model', reasoningEffort: { select: 'minimum' },
|
||||
})
|
||||
expect(prepared.config).not.toHaveProperty('reasoningEffort')
|
||||
expect(prepared.adapterDefaults).toEqual({})
|
||||
await collect(prepared.stream({ ...prepared.config, messages: [] }))
|
||||
expect(adapter.lastOptions).not.toHaveProperty('reasoningEffort')
|
||||
})
|
||||
|
||||
it('rejects minimum selection when the advertised efforts declare no floor', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmRuntime)
|
||||
ctx.llm.registerAdapter(['route'], new CatalogAdapter(
|
||||
{ id: 'route', name: 'Route' }, [], {},
|
||||
{ model: { efforts: [{ id: ReasoningEffortId('high'), name: 'High' }] } },
|
||||
))
|
||||
await expect(ctx.llm.prepareCall({
|
||||
provider: 'route', model: 'model', reasoningEffort: { select: 'minimum' },
|
||||
})).rejects.toMatchObject({ code: 'UNSUPPORTED_REASONING_SELECTION' })
|
||||
await expect(ctx.llm.prepareCall({
|
||||
provider: 'route', model: 'model', reasoningEffort: ReasoningEffortId('low'),
|
||||
})).rejects.toMatchObject({ code: 'UNSUPPORTED_REASONING_EFFORT' })
|
||||
})
|
||||
|
||||
it('reuses one exact-model lookup for prepared config and context metadata', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmRuntime)
|
||||
@@ -1082,7 +1142,13 @@ describe('LlmRuntime', () => {
|
||||
override prepareCall(provider: string, model: string) {
|
||||
const captured = generation
|
||||
return Promise.resolve({
|
||||
model: { provider, id: model, name: model, inputModalities: ['text'] as const },
|
||||
model: {
|
||||
provider, id: model, name: model, inputModalities: ['text'] as const,
|
||||
reasoning: {
|
||||
efforts: [{ id: ReasoningEffortId(captured), name: captured }],
|
||||
floorEffort: ReasoningEffortId(captured),
|
||||
},
|
||||
},
|
||||
stream: (options: GenerateOptions) => {
|
||||
dispatched = captured
|
||||
return super.stream(options)
|
||||
@@ -1092,8 +1158,11 @@ describe('LlmRuntime', () => {
|
||||
}(SCRIPT)
|
||||
ctx.llm.registerAdapter(['route'], adapter)
|
||||
|
||||
const prepared = await ctx.llm.prepareCall({ provider: 'route', model: 'model' })
|
||||
const prepared = await ctx.llm.prepareCall({
|
||||
provider: 'route', model: 'model', reasoningEffort: { select: 'minimum' },
|
||||
})
|
||||
generation = 'second'
|
||||
expect(prepared.config.reasoningEffort).toBe('first')
|
||||
expect(prepared.inputModalities).toEqual(['text'])
|
||||
expect(Object.isFrozen(prepared.inputModalities)).toBe(true)
|
||||
await collect(prepared.stream({ ...prepared.config, messages: [] }))
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
en: cf61c021b1bdb52f
|
||||
zh: dac17588e4143815
|
||||
/deepseek-ai-dsh-session-title-llm/use-this-package/route-and-failure-contract:
|
||||
en: df5a8011c87fc6ca
|
||||
zh: 86eaa45c30d4e2ac
|
||||
en: ef4c7003eaa84cf6
|
||||
zh: 5a13dde678ea3e14
|
||||
/deepseek-ai-dsh-session-title-llm/use-this-package/configuration:
|
||||
en: 2fca3f5b5a4ab43c
|
||||
zh: cdc5b285e31cc4bf
|
||||
en: b06986867581393e
|
||||
zh: 9b4d0b1ac115f800
|
||||
/deepseek-ai-dsh-session-title-llm/understand-the-implementation:
|
||||
en: aa0fd15305df90bb
|
||||
zh: d28e3a28bb324167
|
||||
@@ -36,8 +36,8 @@
|
||||
en: 0fdc928704abdd3a
|
||||
zh: bd4c0a7dd47578cf
|
||||
/deepseek-ai-dsh-session-title-llm/understand-the-implementation/request-flow:
|
||||
en: 5a093319f914059b
|
||||
zh: f881f282bbd2fbf5
|
||||
en: a47f30c863234c24
|
||||
zh: 6d36b42b872a6d5d
|
||||
/deepseek-ai-dsh-session-title-llm/further-exploration:
|
||||
en: 641f31c35c48916b
|
||||
zh: 8e809fbdfef61b61
|
||||
@@ -57,8 +57,8 @@
|
||||
en: 5d3b5b7c8d8b4b67
|
||||
zh: 1b231d96bf6ca545
|
||||
/deepseek-ai-dsh-session-title-llm/known-limitations-and-deferred-work:
|
||||
en: 43c7d18b582537dc
|
||||
zh: 984df0095b18adb9
|
||||
en: 990bc9065ed9f225
|
||||
zh: c8848b7d05e3eeb0
|
||||
/deepseek-ai-dsh-session-title-llm/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -33,7 +33,7 @@ A provider plugin calls `registerSessionTitleLlmProvider(ctx, config, id, automa
|
||||
|
||||
### Route and failure contract
|
||||
|
||||
`provider` and `model` overrides are optional but must be supplied together as non-empty strings. Without that pair, the helper uses the exact provider/model route captured from the current session's logged `request/header`, so an explicit refresh before any route exists needs overrides. The helper measures the final JSON-framed user prompt against `maxInputBytes` before logging or dispatch instead of truncating it, and rechecks timeout and caller cancellation while consuming the stream and after it completes, so a late successful result cannot be accepted even if an interceptor or adapter ignores abort. Malformed or empty output, tool calls, and non-stop finish reasons reject; the session-title service decides whether that rejection is an automatic warning or an explicit caller failure. The accepted title is the first non-empty line of the model's text output, with one emphasis pair removed when it wraps that whole line, so commentary a model writes after the title cannot become the title. The dispatched output cap is the smaller of `maxOutputTokens` and the cap the session's latest folded `request/header` recorded; a session whose header records no cap leaves `maxOutputTokens` in force.
|
||||
`provider` and `model` overrides are optional but must be supplied together as non-empty strings. Without that pair, the helper uses the exact provider/model route captured from the current session's logged `request/header`, so an explicit refresh before any route exists needs overrides. The helper measures the final JSON-framed user prompt against `maxInputBytes` before logging or dispatch instead of truncating it, and rechecks timeout and caller cancellation while consuming the stream and after it completes, so a late successful result cannot be accepted even if an interceptor or adapter ignores abort. Malformed or empty output, tool calls, and non-stop finish reasons reject; the session-title service decides whether that rejection is an automatic warning or an explicit caller failure. The accepted title is the first non-empty line of the model's text output, with one emphasis pair removed when it wraps that whole line, so commentary a model writes after the title cannot become the title. The title request uses `maxOutputTokens` independently of the conversation's cap. Its route must have a registered adapter so preparation can resolve the request before it is recorded.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -46,7 +46,7 @@ Every field is required except the paired route override; there are no library d
|
||||
| `targetWords` | required | Target word count for non-CJK titles |
|
||||
| `targetCjkCharacters` | required | Target character count for Chinese, Japanese, or Korean titles |
|
||||
| `maxInputBytes` | required | UTF-8 byte ceiling for the final JSON-framed user prompt |
|
||||
| `maxOutputTokens` | required | Output-token ceiling; dispatch uses the smaller of this and the cap the session header recorded |
|
||||
| `maxOutputTokens` | required | Title output-token cap, independent of conversation requests |
|
||||
| `timeoutMs` | required | End-to-end deadline within the runtime timer limit |
|
||||
| `provider`, `model` | optional | Explicit route; both or neither |
|
||||
|
||||
@@ -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`), 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.
|
||||
Each revision frames the selected messages as JSON and checks `maxInputBytes`. `ctx.llm.prepareCall()` resolves minimum reasoning under the selected route's captured adapter generation. The helper records the exact input, output cap, and resolved effort in `session/title-llm-request`, then dispatches through that prepared call under the shared deadline. `purpose: 'session-title'` supplies attribution only. The request has no agent-loop identity and does not enter conversation history. Generation failures preserve the request record.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -117,7 +117,7 @@ These limits define the accepted generation shapes. They are current package con
|
||||
|
||||
- **Text output only** — the helper accepts text output and rejects tool calls; structured-output adapters and provider-specific prompt variants are not exposed.
|
||||
- **Whole-prompt byte ceiling** — it enforces a byte ceiling for the whole framed user prompt rather than clipping individual messages or applying a retention policy.
|
||||
- **Output ceiling follows the session header** — the dispatched cap is the smaller of `maxOutputTokens` and the cap the latest folded `request/header` recorded, which is the newest routed request rather than necessarily the request whose prompt is being titled. A deployment that caps main requests tightly also caps its titles, and a model that cannot stop reasoning then has that much less room for visible text; a title routed through an explicit `provider`/`model` override is bounded by the main route's cap instead of the override model's own.
|
||||
- **Minimum reasoning is capability-dependent** — routes with selectable efforts must declare their minimum. Routes without selectable reasoning leave effort unspecified; minimum selection does not guarantee zero reasoning tokens or a complete title within the output cap.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -33,7 +33,7 @@ kind: "package-library"
|
||||
|
||||
### 路由与失败约定
|
||||
|
||||
`provider` 与 `model` 覆盖项都是可选的,但必须同时作为非空字符串提供。如果没有这一对取值,辅助函数使用当前会话已记录 `request/header` 中捕获的确切提供方/模型路由,因此在任何路由出现前显式刷新时必须提供覆盖项。辅助函数在记录或分发前,依据 `maxInputBytes` 检查最终 JSON 封装用户提示词的大小,而不是将其截断,并在消费流期间与完成后重新检查超时与调用方取消,因此即使拦截器或适配器忽略 abort,也不能接受迟到的成功结果。格式错误或空输出、工具调用与非 stop 结束原因都会拒绝;会话标题服务决定该拒绝属于自动警告还是显式调用方失败。被接受的标题取模型文本输出的第一个非空行;当整行被一对强调标记包裹时去掉这对标记,因此模型写在标题之后的说明不会成为标题。分发的输出上限取 `maxOutputTokens` 与会话最新折叠 `request/header` 记录的上限中的较小值;会话 header 未记录上限时,`maxOutputTokens` 生效。
|
||||
`provider` 与 `model` 覆盖项都是可选的,但必须同时作为非空字符串提供。如果没有这一对取值,辅助函数使用当前会话已记录 `request/header` 中捕获的确切提供方/模型路由,因此在任何路由出现前显式刷新时必须提供覆盖项。辅助函数在记录或分发前,依据 `maxInputBytes` 检查最终 JSON 封装用户提示词的大小,而不是将其截断,并在消费流期间与完成后重新检查超时与调用方取消,因此即使拦截器或适配器忽略 abort,也不能接受迟到的成功结果。格式错误或空输出、工具调用与非 stop 结束原因都会拒绝;会话标题服务决定该拒绝属于自动警告还是显式调用方失败。被接受的标题取模型文本输出的第一个非空行;当整行被一对强调标记包裹时去掉这对标记,因此模型写在标题之后的说明不会成为标题。标题请求独立使用 `maxOutputTokens`,不继承对话请求的上限。其路由必须注册适配器,以便在记录前完成请求准备。
|
||||
|
||||
### 配置
|
||||
|
||||
@@ -46,7 +46,7 @@ kind: "package-library"
|
||||
| `targetWords` | 必填 | 非 CJK 标题的目标词数 |
|
||||
| `targetCjkCharacters` | 必填 | 中文、日文或韩文标题的目标字符数 |
|
||||
| `maxInputBytes` | 必填 | 最终 JSON 封装用户提示词的 UTF-8 字节上限 |
|
||||
| `maxOutputTokens` | 必填 | 输出 token 上限;分发取它与会话 header 记录上限的较小值 |
|
||||
| `maxOutputTokens` | 必填 | 标题输出 token 上限,独立于对话请求 |
|
||||
| `timeoutMs` | 必填 | 运行时定时器限制内的端到端时限 |
|
||||
| `provider`, `model` | 可选 | 显式路由;二者同时提供或同时省略 |
|
||||
|
||||
@@ -72,7 +72,7 @@ kind: "package-library"
|
||||
|
||||
### 请求流程
|
||||
|
||||
生成在注册时校验一次配置;每次修订把选中的消息封装为 JSON,依据 `maxInputBytes` 检查封装提示词的 UTF-8 字节数,解析路由(显式对或已记录 `request/header`),读取路由模型的最低强度,追加一条携带确切可分发请求的仅日志 `session/title-llm-request` 事件,然后在组合的超时与取消截止时间内通过 `ctx.llm` 流式生成。分发的封套携带用于重放标注的 `purpose: 'session-title'`,并在路由公布时携带该最低强度,且有意不包含 agent loop 的进程本地请求身份;因此标题请求不把输出上限花在推理上:DeepSeek 适配器禁用思考,pi-ai 适配器分派其模型声明的最低等级。输出只组装为文本块;工具调用、格式错误或空输出与非 stop 结束原因都会拒绝,后续模型失败会保留请求记录。
|
||||
每次修订把选中的消息封装为 JSON,并检查 `maxInputBytes`。`ctx.llm.prepareCall()` 在所选路由捕获的适配器代次中解析最低推理强度。辅助函数把确切输入、输出上限与解析后的强度记录到 `session/title-llm-request`,然后在共享截止时间内通过该已准备调用分发。`purpose: 'session-title'` 仅提供归属标注。请求不带 agent loop 身份,也不进入对话历史。生成失败仍保留请求记录。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -117,7 +117,7 @@ kind: "package-library"
|
||||
|
||||
- **仅文本输出**——辅助函数只接受文本输出并拒绝工具调用;不公开结构化输出适配器或提供方专用提示词变体。
|
||||
- **整体提示词字节上限**——它对整个封装用户提示词强制执行字节上限,而不是剪裁单条消息或应用保留策略。
|
||||
- **输出上限跟随会话 header**——分发上限取 `maxOutputTokens` 与最新折叠 `request/header` 记录的上限中的较小值;该 header 是最新一次带路由的请求,不一定正是当前取标题的请求。把主请求上限配得很紧的部署同样会收紧标题,无法停止推理的模型留给可见文本的空间随之变小;通过显式 `provider`/`model` 覆盖路由的标题,受主路由上限约束,而不受覆盖模型自身上限保护。
|
||||
- **最低推理依赖能力声明**——提供可选强度的路由必须声明最低强度。没有可选推理控制的路由不指定强度;最低强度选择不保证零推理 token,也不保证在输出上限内生成完整标题。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface SessionTitleLlmRequestEventData {
|
||||
readonly messages: Message[]
|
||||
/** Exact auxiliary output-token cap. */
|
||||
readonly maxTokens: number
|
||||
/** Resolved reasoning effort when recorded; older requests can omit it. */
|
||||
readonly reasoningEffort?: ReasoningEffortId
|
||||
}
|
||||
|
||||
declare module '@deepseek-ai/dsh-session/types' {
|
||||
@@ -64,7 +66,7 @@ export interface SessionTitleLlmConfig {
|
||||
readonly targetCjkCharacters: number
|
||||
/** Maximum UTF-8 bytes in the final JSON-framed user prompt. */
|
||||
readonly maxInputBytes: number
|
||||
/** Configured output-token ceiling; dispatch uses the smaller of this and the cap the session's header recorded. */
|
||||
/** Output-token cap for the title request, independent of conversation requests. */
|
||||
readonly maxOutputTokens: number
|
||||
/** End-to-end auxiliary request deadline in milliseconds. */
|
||||
readonly timeoutMs: number
|
||||
@@ -191,42 +193,6 @@ function resolveRoute(
|
||||
return request.route
|
||||
}
|
||||
|
||||
/**
|
||||
* Bound the configured output cap by the cap the session's current main
|
||||
* request materialized, so a route the deployment capped below the configured
|
||||
* value is never asked for more. A session whose header records no cap leaves
|
||||
* the configured cap unchanged.
|
||||
* @param config - validated model-provider policy.
|
||||
* @param request - service-owned session of this title request.
|
||||
* @returns the smaller of the configured cap and the logged request cap.
|
||||
*/
|
||||
function titleMaxTokens(
|
||||
config: ResolvedSessionTitleLlmConfig,
|
||||
request: SessionTitleProviderRequest,
|
||||
): number {
|
||||
const materialized = request.session.requestHeader()?.config.maxTokens
|
||||
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<ReasoningEffortId | undefined> {
|
||||
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 [
|
||||
@@ -314,17 +280,18 @@ 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 maxTokens = config.maxOutputTokens
|
||||
const call = await ctx.llm.prepareCall({
|
||||
...route,
|
||||
maxTokens,
|
||||
reasoningEffort: { select: 'minimum' },
|
||||
}, callDeadline.signal)
|
||||
const options: GenerateOptions = deepFreeze({
|
||||
provider: route.provider,
|
||||
model: route.model,
|
||||
...call.config,
|
||||
messages,
|
||||
system,
|
||||
maxTokens,
|
||||
sessionId: request.session.id,
|
||||
purpose: 'session-title',
|
||||
...floorEffort === undefined ? {} : { reasoningEffort: floorEffort },
|
||||
signal: callDeadline.signal,
|
||||
})
|
||||
request.session.append('session/title-llm-request', {
|
||||
@@ -334,10 +301,11 @@ export async function generateSessionTitleWithLlm(
|
||||
system,
|
||||
messages,
|
||||
maxTokens,
|
||||
...call.config.reasoningEffort === undefined ? {} : { reasoningEffort: call.config.reasoningEffort },
|
||||
})
|
||||
callDeadline.signal.throwIfAborted()
|
||||
const assembler = new BlockAssembler()
|
||||
for await (const chunk of ctx.llm.stream(options)) {
|
||||
for await (const chunk of call.stream(options)) {
|
||||
callDeadline.signal.throwIfAborted()
|
||||
assembler.push(chunk)
|
||||
}
|
||||
|
||||
@@ -201,10 +201,11 @@ describe('generateSessionTitleWithLlm', () => {
|
||||
system: options.system,
|
||||
messages: options.messages,
|
||||
maxTokens: 32,
|
||||
reasoningEffort: ReasoningEffortId('off'),
|
||||
})
|
||||
})
|
||||
|
||||
it('bounds the dispatched output cap by a smaller cap the session request recorded', async () => {
|
||||
it('keeps the title output cap independent of a smaller conversation cap', async () => {
|
||||
const { ctx, adapter } = await withScript(SCRIPT)
|
||||
const providerRequest = request(ctx, undefined, 8)
|
||||
|
||||
@@ -216,9 +217,9 @@ describe('generateSessionTitleWithLlm', () => {
|
||||
TITLE_PROVIDER,
|
||||
)).resolves.toMatchObject({ title: '五个字标题' })
|
||||
|
||||
expect(adapter.requests[0]?.maxTokens).toBe(8)
|
||||
expect(adapter.requests[0]?.maxTokens).toBe(32)
|
||||
expect(providerRequest.session.snapshotEvents()
|
||||
.findLast(event => event.type === 'session/title-llm-request')?.data.maxTokens).toBe(8)
|
||||
.findLast(event => event.type === 'session/title-llm-request')?.data.maxTokens).toBe(32)
|
||||
})
|
||||
|
||||
it('keeps the configured output cap when the session request recorded a larger one', async () => {
|
||||
@@ -236,13 +237,11 @@ describe('generateSessionTitleWithLlm', () => {
|
||||
expect(adapter.requests[0]?.maxTokens).toBe(32)
|
||||
})
|
||||
|
||||
it('sends no effort when the route has no registered adapter', async () => {
|
||||
it('rejects an unregistered route before recording or dispatching a title request', 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<StreamChunk> { yield * SCRIPT })()
|
||||
@@ -255,9 +254,10 @@ describe('generateSessionTitleWithLlm', () => {
|
||||
providerRequest,
|
||||
providerRequest.messages,
|
||||
TITLE_PROVIDER,
|
||||
)).resolves.toMatchObject({ title: '五个字标题' })
|
||||
)).rejects.toMatchObject({ code: 'NO_ADAPTER' })
|
||||
|
||||
expect(requests[0]).not.toHaveProperty('reasoningEffort')
|
||||
expect(requests).toEqual([])
|
||||
expect(providerRequest.session.snapshotEvents().some(event => event.type === 'session/title-llm-request')).toBe(false)
|
||||
})
|
||||
|
||||
it('sends no effort when the route reports no selectable level', async () => {
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
en: e2116be9c7a5c18f
|
||||
zh: dc89da423decf4aa
|
||||
/deepseek-ai-dsh-llm-replay/use-this-package/mounting-it:
|
||||
en: 8885809b3bc7da3e
|
||||
zh: cb3f1a3268164d11
|
||||
en: 431b305f7f1f5d02
|
||||
zh: 227dcbaeaf4bdfef
|
||||
/deepseek-ai-dsh-llm-replay/use-this-package/how-the-fixture-works:
|
||||
en: d7dd048cd69e723d
|
||||
zh: 9f5dfa5186549cd0
|
||||
|
||||
@@ -29,6 +29,8 @@ This package gives a keyless test a real agent with a fixed model transcript: mo
|
||||
|
||||
### Mounting it
|
||||
|
||||
Prepared callers require the routed adapter mode. Configure `models[].floorReasoningEffort` together with `reasoningEfforts` to replay minimum-effort selection; display order alone does not establish the minimum.
|
||||
|
||||
With `providers` configured, the plugin registers a replay-only adapter whose catalog is available to scenarios that exercise model discovery; without `providers`, it installs the catch-all `llm/stream` waterfall used by tests that do not need discovery:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -29,6 +29,8 @@ kind: "package-reference"
|
||||
|
||||
### 挂载它
|
||||
|
||||
已准备调用要求使用路由适配器模式。将 `models[].floorReasoningEffort` 与 `reasoningEfforts` 一起配置,即可回放最低强度选择;仅凭显示顺序不能确定最低强度。
|
||||
|
||||
配置 `providers` 后,插件会注册仅用于回放的适配器,其模型目录可供测试模型发现功能的场景使用;未配置 `providers` 时,它安装无需模型发现功能的测试所用的 catch-all `llm/stream` waterfall(瀑布式事件):
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface ReplayModelConfig {
|
||||
imageRequestTokens?: number
|
||||
/** Optional reasoning-effort ids the replay route accepts, in display order. */
|
||||
reasoningEfforts?: string[]
|
||||
/** Optional least selectable effort; must appear in {@link reasoningEfforts}. */
|
||||
floorReasoningEffort?: string
|
||||
/**
|
||||
* Optional effort materialized when callers omit one; must appear in
|
||||
* {@link reasoningEfforts} or call resolution rejects the route.
|
||||
@@ -932,6 +934,9 @@ class ReplayAdapter extends LlmAdapter {
|
||||
: {
|
||||
reasoning: {
|
||||
efforts: configuredModel.reasoningEfforts.map(id => ({ id: ReasoningEffortId(id), name: id })),
|
||||
...configuredModel.floorReasoningEffort === undefined
|
||||
? {}
|
||||
: { floorEffort: ReasoningEffortId(configuredModel.floorReasoningEffort) },
|
||||
...configuredModel.defaultReasoningEffort === undefined
|
||||
? {}
|
||||
: { defaultEffort: ReasoningEffortId(configuredModel.defaultReasoningEffort) },
|
||||
|
||||
@@ -1414,6 +1414,7 @@ describe('installLlmReplay (through the real LlmRuntime)', () => {
|
||||
inputModalities: ['text', 'image'],
|
||||
defaultMaxTokens: 64_000,
|
||||
reasoningEfforts: ['off', 'max'],
|
||||
floorReasoningEffort: 'off',
|
||||
defaultReasoningEffort: 'max',
|
||||
systemPromptUpdate: 'in-history',
|
||||
toolUpdate: 'addition-only',
|
||||
@@ -1440,6 +1441,7 @@ describe('installLlmReplay (through the real LlmRuntime)', () => {
|
||||
defaultMaxTokens: 64_000,
|
||||
reasoning: {
|
||||
efforts: [{ id: 'off', name: 'off' }, { id: 'max', name: 'max' }],
|
||||
floorEffort: 'off',
|
||||
defaultEffort: 'max',
|
||||
},
|
||||
systemPromptUpdate: 'in-history',
|
||||
@@ -1470,7 +1472,11 @@ describe('installLlmReplay (through the real LlmRuntime)', () => {
|
||||
maxDelayMs: 10_000,
|
||||
jitterRatio: 0.1,
|
||||
})
|
||||
expect(await drain(ctx.llm.stream({ provider: 'deepseek', model: 'pro', messages: [] }))).toEqual(TEXT_CHUNKS)
|
||||
const prepared = await ctx.llm.prepareCall({
|
||||
provider: 'deepseek', model: 'flash', reasoningEffort: { select: 'minimum' },
|
||||
})
|
||||
expect(prepared.config.reasoningEffort).toBe('off')
|
||||
expect(await drain(prepared.stream({ ...prepared.config, messages: [] }))).toEqual(TEXT_CHUNKS)
|
||||
|
||||
dispose()
|
||||
expect(ctx.llm.listProviders()).toEqual([])
|
||||
|
||||
@@ -27,6 +27,7 @@ class CliMockAdapter extends LlmAdapter {
|
||||
{ id: OFF, name: 'Off' },
|
||||
{ id: HIGH, name: 'High' },
|
||||
],
|
||||
floorEffort: OFF,
|
||||
defaultEffort: HIGH,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -339,6 +339,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
|
||||
DeepSeekLlmApiExtensionProvider: 'llm-streaming.md',
|
||||
DeepSeekLlmApiExtensionRequest: 'llm-streaming.md',
|
||||
LlmCallConfig: 'llm-streaming.md',
|
||||
LlmCallRequest: 'llm-streaming.md',
|
||||
LlmModelContext: 'llm-streaming.md',
|
||||
LlmModelReasoningInfo: 'llm-streaming.md',
|
||||
LlmResolvedModelInfo: 'llm-streaming.md',
|
||||
|
||||
@@ -126,6 +126,16 @@
|
||||
"symbol": "LlmCallConfig",
|
||||
"source": "packages/llm/llm/src/call-config.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/llm-streaming.md",
|
||||
"symbol": "ReasoningSelection",
|
||||
"source": "packages/llm/llm/src/call-config.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/llm-streaming.md",
|
||||
"symbol": "LlmCallRequest",
|
||||
"source": "packages/llm/llm/src/call-config.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/llm-streaming.md",
|
||||
"symbol": "LlmCallConfigAdapterDefaults",
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
name: Title replay
|
||||
models:
|
||||
- id: title-model
|
||||
reasoningEfforts: [high, low]
|
||||
floorReasoningEffort: low
|
||||
defaultReasoningEffort: high
|
||||
- id: session-title-provider
|
||||
name: '@deepseek-ai/dsh-session-title-first-prompt-llm'
|
||||
config:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"Reply with exactly TITLE_DONE. Do","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"title-replay","model":"title-model"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Reply with exactly TITLE_DONE. Do not use tools.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{message:4}}"}],"maxTokens":32}}
|
||||
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"title-replay","model":"title-model"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Reply with exactly TITLE_DONE. Do not use tools.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{message:4}}"}],"maxTokens":32,"reasoningEffort":"low"}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"TITLE_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":10,"outputTokens":2},"stream":[{"type":"chunk","time":1788699457976,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1788699457976,"index":0,"dt":[],"texts":["TITLE_DONE"]},{"type":"chunk","time":1788699457976,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"TITLE_DONE"}}},{"type":"chunk","time":1788699457976,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}},{"type":"chunk","time":1788699457976,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
|
||||
Reference in new issue
Block a user