Commit
Use adaptive thinking and structured outputs in the Anthropic provider
commit
8b5e278
1 changed file with +8 and −8
modified server/providers/llmAnthropic.ts +8 −8
| @@ -1,12 +1,11 @@ | ||
| 1 | 1 | import Anthropic from '@anthropic-ai/sdk' |
| 2 | -import { betaZodOutputFormat } from '@anthropic-ai/sdk/helpers/beta/zod' | |
| 2 | +import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod' | |
| 3 | 3 | import { CATEGORY_IDS, InterviewTurnSchema, type InterviewTurn } from '../../shared/types' |
| 4 | 4 | import { weakestCategory } from '../engine/coverage' |
| 5 | 5 | import type { GenerateFileRequest, InterviewContext, InterviewLlm } from './types' |
| 6 | 6 | |
| 7 | 7 | const DEFAULT_MODEL = 'claude-opus-4-8' |
| 8 | -const INTERVIEW_MAX_TOKENS = 4096 | |
| 9 | -const INTERVIEW_THINKING_BUDGET_TOKENS = 2048 | |
| 8 | +const INTERVIEW_MAX_TOKENS = 16000 | |
| 10 | 9 | const GENERATE_MAX_TOKENS = 32000 |
| 11 | 10 | |
| 12 | 11 | export function buildInterviewSystemPrompt(context: InterviewContext): string { |
| @@ -19,7 +18,7 @@export function buildInterviewSystemPrompt(context: InterviewContext): string { | ||
| 19 | 18 | ? `The weakest category right now is "${weakest}". Your next question must target it, unless a contradiction takes priority.` |
| 20 | 19 | : 'Every category is already "clear".', |
| 21 | 20 | 'If the latest user answer conflicts with an earlier statement, do not ask a coverage question: set "contradiction" to describe both statements, referencing their segment ids, instead.', |
| 22 | - 'The interview ends only when the user answer is exactly "done" (case-insensitive, trimmed) — never for any other reason, even if "done" appears inside a longer answer.', | |
| 21 | + 'The interview ends only when the user answer is exactly "done" (case-insensitive, trimmed), never for any other reason, even if "done" appears inside a longer answer.', | |
| 23 | 22 | 'Reply with only the InterviewTurn structure described by the output schema.', |
| 24 | 23 | ].join('\n') |
| 25 | 24 | } |
| @@ -45,13 +44,13 @@export function createLlmAnthropic(): InterviewLlm { | ||
| 45 | 44 | |
| 46 | 45 | return { |
| 47 | 46 | async nextTurn(context: InterviewContext): Promise<InterviewTurn> { |
| 48 | - const message = await client.beta.messages.parse({ | |
| 47 | + const message = await client.messages.parse({ | |
| 49 | 48 | model, |
| 50 | 49 | max_tokens: INTERVIEW_MAX_TOKENS, |
| 51 | - thinking: { type: 'enabled', budget_tokens: INTERVIEW_THINKING_BUDGET_TOKENS }, | |
| 50 | + thinking: { type: 'adaptive' }, | |
| 52 | 51 | system: buildInterviewSystemPrompt(context), |
| 53 | 52 | messages: [{ role: 'user', content: buildInterviewUserMessage(context) }], |
| 54 | - output_format: betaZodOutputFormat(InterviewTurnSchema), | |
| 53 | + output_config: { format: zodOutputFormat(InterviewTurnSchema) }, | |
| 55 | 54 | }) |
| 56 | 55 | if (!message.parsed_output) { |
| 57 | 56 | throw new Error('Anthropic response did not include a parsed InterviewTurn') |
| @@ -60,9 +59,10 @@export function createLlmAnthropic(): InterviewLlm { | ||
| 60 | 59 | }, |
| 61 | 60 | |
| 62 | 61 | async generateFile(request: GenerateFileRequest): Promise<string> { |
| 63 | - const stream = client.beta.messages.stream({ | |
| 62 | + const stream = client.messages.stream({ | |
| 64 | 63 | model, |
| 65 | 64 | max_tokens: GENERATE_MAX_TOKENS, |
| 65 | + thinking: { type: 'adaptive' }, | |
| 66 | 66 | messages: [{ role: 'user', content: request.prompt }], |
| 67 | 67 | }) |
| 68 | 68 | const final = await stream.finalMessage() |