refactor(llm): drop the inert request knobs — prefill and strict

GenerateOptions.prefill had no production setter and both adapters
rejected it with LlmError('UNSUPPORTED') — its entire observable
behavior was two throws, each pinned by one adapter test. DeepSeek's
chat-prefix completion is a Beta feature on a base URL neither adapter
targets. ToolSchema.strict was threaded through defineTool, the
registry's schemas() allowlist, the deepseek wire mapping, a per-tool
payload-patching pass in the pi-ai adapter, and a tool-catalog render
row, yet no shipped tool set it and the internal endpoint story for
strict mode was never built.

Remove both fields end-to-end: the vocabulary in dsh-llm, the adapter
guards and wire branches, the dsh-tools threading, the tool-catalog
Strict row, the pinning tests, the core.md pastes, the adapter README
rows, and the cookbook line that used prefill as the UNSUPPORTED
example (now stated generically). The pi-ai payload fixup keeps the
half with a job: pi-ai stamps strict:false on every serialized tool,
so the fixup scrubs it unconditionally for wire parity with the
hand-rolled twin (per-tool set/delete machinery gone). temperature/
stop/maxTokens are untouched — honored end-to-end by both adapters.

Each knob returns with its first real producer: prefill with an
adapter that implements chat-prefix completion, strict with a tool
that wants it and a beta-endpoint story.

RFC: docs/rfc/implemented/simplification/2026-07-04-drop-inert-request-knobs.md
(moved from proposed/, amended to shipped reality); the content-block
vocabulary RFC's consequence line now records prefill as producer-gated.
This commit is contained in:
Tianyi Cui
2026-07-04 18:38:39 +08:00
parent 2825aa6fdc
commit 5a8234643a
19 changed files with 72 additions and 196 deletions
-2
View File
@@ -28,12 +28,10 @@ A second, independent implementation of the same seam exists in `@deepseek-ai/ds
- Streaming only (`stream_options.include_usage` always on). `usage` may arrive attached to the finish chunk or as a trailing usage-only chunk — the translator defers both to `[DONE]`, so `usage` always precedes `finish` and nothing follows `finish`.
- The first thinking-mode chunk carries `reasoning_content: ""` — handled (no spurious reasoning block).
- **Reasoning passback rule**: on assistant turns that carried tool calls, `reasoning_content` is serialized back in history (required by the API in thinking mode); on tool-call-free turns it is dropped (ignored anyway — saves tokens).
- `strict` on tool schemas passes through (officially Beta; the public API wants the `/beta` base URL for it, the internal endpoint accepts it directly).
- Cache accounting: `cacheReadTokens` ← `prompt_cache_hit_tokens` / `prompt_tokens_details.cached_tokens`; DeepSeek reports no cache-write metric.
## Limitations (MVP, documented deliberately)
- `prefill` throws `LlmError('UNSUPPORTED')` — DeepSeek's chat-prefix completion is a Beta feature on the `/beta` base URL; future work.
- `tool_choice` is not mapped (not part of the core vocabulary).
## Errors
+1 -15
View File
@@ -15,7 +15,6 @@
* @module dsh-llm-deepseek/serialize
*/
import { LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, Message } from '@deepseek-ai/dsh-llm'
import type { WireMessage, WireRequest, WireTool } from './types.ts'
@@ -98,19 +97,8 @@ export function serializeMessages(messages: Message[]): WireMessage[] {
return wire
}
/**
* Build the full wire request. Throws `LlmError('UNSUPPORTED')` for
* `prefill` (DeepSeek's chat-prefix completion is a Beta feature on a
* different base URL — see README).
*/
/** Build the full wire request. */
export function serializeRequest(options: GenerateOptions, defaults: RequestDefaults = {}): WireRequest {
if (options.prefill !== undefined) {
throw new LlmError(
'prefill is not supported by the DeepSeek adapter (Beta chat-prefix completion is future work)',
'UNSUPPORTED',
)
}
const messages: WireMessage[] = []
if (options.system !== undefined) {
messages.push({ role: 'system', content: options.system })
@@ -123,8 +111,6 @@ export function serializeRequest(options: GenerateOptions, defaults: RequestDefa
name: tool.name,
description: tool.description,
parameters: tool.parameters,
// strict is officially supported (Beta); pass the tool author's choice.
...tool.strict !== undefined ? { strict: tool.strict } : {},
},
}))
-2
View File
@@ -78,8 +78,6 @@ export interface WireTool {
name: string
description: string
parameters: Record<string, unknown>
/** Beta: strict schema adherence (official: requires the /beta base URL). */
strict?: boolean
}
}
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { CallId, LlmError } from '@deepseek-ai/dsh-llm'
import { CallId } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, Message } from '@deepseek-ai/dsh-llm'
import { serializeMessages, serializeRequest } from '@deepseek-ai/dsh-llm-deepseek'
@@ -155,17 +155,17 @@ describe('serializeRequest', () => {
expect(wire.stop).toEqual(['END'])
})
it('maps tools with strict passthrough', () => {
it('maps tools to the wire function shape', () => {
const wire = serializeRequest(request({
messages: history,
tools: [
{ name: 'a', description: 'A', parameters: { type: 'object', properties: {} } },
{ name: 'b', description: 'B', parameters: { type: 'object', properties: {} }, strict: true },
{ name: 'b', description: 'B', parameters: { type: 'object', properties: { x: { type: 'string' } } } },
],
}))
expect(wire.tools).toEqual([
{ type: 'function', function: { name: 'a', description: 'A', parameters: { type: 'object', properties: {} } } },
{ type: 'function', function: { name: 'b', description: 'B', parameters: { type: 'object', properties: {} }, strict: true } },
{ type: 'function', function: { name: 'b', description: 'B', parameters: { type: 'object', properties: { x: { type: 'string' } } } } },
])
})
@@ -185,17 +185,6 @@ describe('serializeRequest', () => {
expect(wire.thinking).toBeUndefined()
expect(wire.reasoning_effort).toBeUndefined()
})
it('rejects prefill with an UNSUPPORTED LlmError', () => {
expect(() => serializeRequest(request({ prefill: [{ type: 'text', text: 'Sure' }] })))
.toThrow(LlmError)
try {
serializeRequest(request({ prefill: [] }))
expect.unreachable()
} catch (error) {
expect((error as LlmError).code).toBe('UNSUPPORTED')
}
})
})
describe('review fixes: assistant content shapes', () => {