feat(llm-deepseek): expose off as a dynamic reasoning effort
This commit is contained in:
@@ -51,12 +51,17 @@ export interface DeepSeekAdapterOptions {
|
||||
/** Default maximum idle interval while an adapter stream read is outstanding. */
|
||||
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000
|
||||
const STREAM_IDLE_TIMEOUT_CODE = 'LLM_STREAM_IDLE_TIMEOUT'
|
||||
const OFF_REASONING_EFFORT = ReasoningEffortId('off')
|
||||
const HIGH_REASONING_EFFORT = ReasoningEffortId('high')
|
||||
const MAX_REASONING_EFFORT = ReasoningEffortId('max')
|
||||
const REASONING_EFFORTS = [
|
||||
{ id: OFF_REASONING_EFFORT, name: 'Off' },
|
||||
{ id: HIGH_REASONING_EFFORT, name: 'High' },
|
||||
{ id: MAX_REASONING_EFFORT, name: 'Max' },
|
||||
] as const
|
||||
const OFF_ONLY_REASONING_EFFORTS = [
|
||||
{ id: OFF_REASONING_EFFORT, name: 'Off' },
|
||||
] as const
|
||||
|
||||
function modelInfo(provider: string, model: DeepSeekCatalogModel): LlmModelInfo {
|
||||
return {
|
||||
@@ -113,8 +118,10 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
|
||||
constructor(private readonly options: DeepSeekAdapterOptions) {
|
||||
super()
|
||||
if (options.defaults?.thinking === 'disabled' && options.defaults.reasoningEffort !== undefined) {
|
||||
throw new Error('llm-deepseek: reasoningEffort cannot be configured when thinking is disabled')
|
||||
if (options.defaults?.thinking === 'disabled'
|
||||
&& options.defaults.reasoningEffort !== undefined
|
||||
&& options.defaults.reasoningEffort !== 'off') {
|
||||
throw new Error('llm-deepseek: only reasoningEffort "off" can be configured when thinking is disabled')
|
||||
}
|
||||
if (options.defaultContextWindow !== undefined
|
||||
&& (!Number.isInteger(options.defaultContextWindow) || options.defaultContextWindow <= 0)) {
|
||||
@@ -152,13 +159,20 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
: modelInfo(provider, configured),
|
||||
...contextWindow === undefined ? {} : { context: { contextWindow } },
|
||||
...this.options.defaults?.thinking === 'disabled'
|
||||
? {}
|
||||
? {
|
||||
reasoning: {
|
||||
efforts: OFF_ONLY_REASONING_EFFORTS,
|
||||
defaultEffort: OFF_REASONING_EFFORT,
|
||||
},
|
||||
}
|
||||
: {
|
||||
reasoning: {
|
||||
efforts: REASONING_EFFORTS,
|
||||
defaultEffort: this.options.defaults?.reasoningEffort === 'max'
|
||||
? MAX_REASONING_EFFORT
|
||||
: HIGH_REASONING_EFFORT,
|
||||
defaultEffort: this.options.defaults?.reasoningEffort === 'off'
|
||||
? OFF_REASONING_EFFORT
|
||||
: this.options.defaults?.reasoningEffort === 'max'
|
||||
? MAX_REASONING_EFFORT
|
||||
: HIGH_REASONING_EFFORT,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -37,10 +37,10 @@ export interface Config {
|
||||
apiKey?: string
|
||||
/** Endpoint base; falls back to $DEEPSEEK_BASE_URL, then the public API. */
|
||||
baseURL?: string
|
||||
/** Thinking-mode default for every request (provider default: enabled). */
|
||||
/** Deployment thinking policy; `disabled` limits every conversation request to `off`. */
|
||||
thinking?: 'enabled' | 'disabled'
|
||||
/** Default thinking effort when thinking is enabled (default `high`). */
|
||||
reasoningEffort?: 'high' | 'max'
|
||||
/** Default thinking effort (default `high`); `off` disables thinking per request. */
|
||||
reasoningEffort?: 'off' | 'high' | 'max'
|
||||
/** Positive context capacity used when the selected model has no exact value. */
|
||||
defaultContextWindow?: number
|
||||
/** Advisory models shown by discovery consumers; defaults to V4 Flash and V4 Pro. */
|
||||
@@ -60,7 +60,7 @@ export const Config: z<Config> = z.object({
|
||||
apiKey: z.string(),
|
||||
baseURL: z.string(),
|
||||
thinking: z.union(['enabled', 'disabled']),
|
||||
reasoningEffort: z.union(['high', 'max']),
|
||||
reasoningEffort: z.union(['off', 'high', 'max']),
|
||||
defaultContextWindow: z.number().step(1).min(1),
|
||||
models: z.array(catalogModel).default(DEFAULT_MODELS),
|
||||
streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
||||
@@ -95,8 +95,10 @@ function resolveModels(models: readonly DeepSeekCatalogModel[] | undefined): Dee
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
if (config.thinking === 'disabled' && config.reasoningEffort !== undefined) {
|
||||
throw new Error('llm-deepseek: reasoningEffort cannot be configured when thinking is disabled')
|
||||
if (config.thinking === 'disabled'
|
||||
&& config.reasoningEffort !== undefined
|
||||
&& config.reasoningEffort !== 'off') {
|
||||
throw new Error('llm-deepseek: only reasoningEffort "off" can be configured when thinking is disabled')
|
||||
}
|
||||
const apiKey = config.apiKey ?? process.env.DEEPSEEK_API_KEY
|
||||
if (apiKey === undefined || apiKey.length === 0) {
|
||||
|
||||
@@ -13,20 +13,44 @@ import type { WireMessage, WireRequest, WireTool } from './types.ts'
|
||||
/** Adapter-level request defaults (from plugin config). */
|
||||
export interface RequestDefaults {
|
||||
thinking?: 'enabled' | 'disabled' | undefined
|
||||
reasoningEffort?: 'high' | 'max' | undefined
|
||||
reasoningEffort?: 'off' | 'high' | 'max' | undefined
|
||||
}
|
||||
|
||||
/** Validate the adapter-owned effort before assigning its narrower wire type. */
|
||||
function reasoningEffort(options: GenerateOptions): 'high' | 'max' | undefined {
|
||||
const effort = options.reasoningEffort
|
||||
if (effort === undefined) return undefined
|
||||
if (effort === 'high' || effort === 'max') return effort as 'high' | 'max'
|
||||
interface ResolvedThinking {
|
||||
thinking?: 'enabled' | 'disabled'
|
||||
reasoningEffort?: 'high' | 'max'
|
||||
}
|
||||
|
||||
/** Validate the adapter-owned effort before resolving its DeepSeek wire fields. */
|
||||
function reasoningEffort(effort: NonNullable<GenerateOptions['reasoningEffort']>): 'off' | 'high' | 'max' {
|
||||
if (effort === 'off' || effort === 'high' || effort === 'max') {
|
||||
return effort as 'off' | 'high' | 'max'
|
||||
}
|
||||
throw new LlmError(
|
||||
`DeepSeek does not support reasoning effort "${effort}"`,
|
||||
'UNSUPPORTED_REASONING_EFFORT',
|
||||
)
|
||||
}
|
||||
|
||||
/** Resolve one legal thinking/effort pair without exposing `off` as a wire effort. */
|
||||
function resolveThinking(options: GenerateOptions, defaults: RequestDefaults): ResolvedThinking {
|
||||
if (options.purpose === 'session-title') return { thinking: 'disabled' }
|
||||
const effort = options.reasoningEffort === undefined
|
||||
? defaults.reasoningEffort
|
||||
: reasoningEffort(options.reasoningEffort)
|
||||
if (defaults.thinking === 'disabled' && effort !== undefined && effort !== 'off') {
|
||||
throw new LlmError(
|
||||
`DeepSeek deployment does not support reasoning effort "${effort}"`,
|
||||
'UNSUPPORTED_REASONING_EFFORT',
|
||||
)
|
||||
}
|
||||
if (effort === 'off') return { thinking: 'disabled' }
|
||||
if (effort === 'high' || effort === 'max') {
|
||||
return { thinking: 'enabled', reasoningEffort: effort }
|
||||
}
|
||||
return defaults.thinking === undefined ? {} : { thinking: defaults.thinking }
|
||||
}
|
||||
|
||||
/** Join the text blocks of a message (used for user/tool-result content). */
|
||||
function flattenText(blocks: ContentBlock[]): string {
|
||||
return blocks
|
||||
@@ -132,18 +156,17 @@ export function serializeRequest(options: GenerateOptions, defaults: RequestDefa
|
||||
}))
|
||||
// A short title budget must produce visible text; conversation and
|
||||
// compaction calls continue to inherit the adapter's thinking defaults.
|
||||
const thinking = options.purpose === 'session-title' ? 'disabled' : defaults.thinking
|
||||
const resolvedReasoningEffort = options.purpose === 'session-title'
|
||||
? undefined
|
||||
: reasoningEffort(options)
|
||||
const resolvedThinking = resolveThinking(options, defaults)
|
||||
|
||||
return {
|
||||
model: options.model,
|
||||
messages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
...thinking !== undefined ? { thinking: { type: thinking } } : {},
|
||||
...resolvedReasoningEffort !== undefined ? { reasoning_effort: resolvedReasoningEffort } : {},
|
||||
...resolvedThinking.thinking !== undefined ? { thinking: { type: resolvedThinking.thinking } } : {},
|
||||
...resolvedThinking.reasoningEffort !== undefined
|
||||
? { reasoning_effort: resolvedThinking.reasoningEffort }
|
||||
: {},
|
||||
...tools !== undefined && tools.length > 0 ? { tools } : {},
|
||||
...options.temperature !== undefined ? { temperature: options.temperature } : {},
|
||||
...options.maxTokens !== undefined ? { max_tokens: options.maxTokens } : {},
|
||||
|
||||
Reference in New Issue
Block a user