Skip to content

Commit 2d84bc2

Browse files
committed
feat(anthropic): add Claude 4.5 Haiku model and update logging for thought display
This commit introduces the Claude 4.5 Haiku model to the Anthropic integration, including its pricing and token limits. Additionally, it enhances the logging functionality to better format and display thought information in the output.
1 parent 78d27f8 commit 2d84bc2

File tree

4 files changed

+57
-34
lines changed

4 files changed

+57
-34
lines changed

src/ax/ai/anthropic/info.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ import type { AxModelInfo } from '../types.js';
33
import { AxAIAnthropicModel } from './types.js';
44

55
export const axModelInfoAnthropic: AxModelInfo[] = [
6+
// 4.5 Haiku (2025-10)
7+
{
8+
name: AxAIAnthropicModel.Claude45Haiku,
9+
currency: 'usd',
10+
// Pricing per Anthropic announcement: $1 input / $5 output per 1M tokens
11+
promptTokenCostPer1M: 1.0,
12+
completionTokenCostPer1M: 5.0,
13+
maxTokens: 200000, // match modern context window similar to Sonnet 4.5 era
14+
supported: { thinkingBudget: true, showThoughts: true },
15+
},
616
// 4
717
{
818
name: AxAIAnthropicModel.Claude41Opus,

src/ax/ai/anthropic/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export enum AxAIAnthropicModel {
44
Claude41Opus = 'claude-opus-4-1-20250805',
55
Claude4Opus = 'claude-opus-4-20250514',
66
Claude4Sonnet = 'claude-sonnet-4-20250514',
7+
Claude45Haiku = 'claude-haiku-4-5',
78
Claude37Sonnet = 'claude-3-7-sonnet-latest',
89

910
Claude35Sonnet = 'claude-3-5-sonnet-latest',

src/ax/dsp/loggers.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const axCreateDefaultColorLogger = (
128128
if (result.thoughtBlock?.data || result.thought) {
129129
lines.push(
130130
cl.gray(
131-
`[thought${result.thoughtBlock?.encrypted ? ' (redacted)' : ''}] ` +
131+
`[THOUGHT${result.thoughtBlock?.encrypted ? ' (redacted)' : ''}]\n` +
132132
(result.thoughtBlock?.data ?? result.thought ?? '')
133133
)
134134
);
@@ -149,15 +149,24 @@ export const axCreateDefaultColorLogger = (
149149
const streamingContent =
150150
thought || typedData.value.delta || typedData.value.content || '';
151151
formattedMessage = thought
152-
? cl.gray(`[thought] ${thought}`)
152+
? cl.gray(`[THOUGHT]\n${thought}`)
153153
: cl.cyanBright(streamingContent);
154-
return;
154+
break;
155155
}
156156
case 'ChatResponseStreamingDoneResult': {
157157
formattedMessage = `\n${cl.cyanBright('[ CHAT RESPONSE ]')}\n${divider}\n`;
158158
if (typedData.value.content) {
159159
formattedMessage += cl.cyanBright(typedData.value.content);
160160
}
161+
if (typedData.value.thoughtBlock?.data || typedData.value.thought) {
162+
formattedMessage += `\n`;
163+
formattedMessage += cl.gray(
164+
`[THOUGHT${typedData.value.thoughtBlock?.encrypted ? ' (redacted)' : ''}]\n` +
165+
(typedData.value.thoughtBlock?.data ??
166+
typedData.value.thought ??
167+
'')
168+
);
169+
}
161170
if (typedData.value.functionCalls) {
162171
formattedMessage += cl.cyanBright(
163172
JSON.stringify(typedData.value.functionCalls, null, 2)
@@ -279,7 +288,20 @@ export const axCreateDefaultTextLogger = (
279288
case 'ChatResponseResults':
280289
formattedMessage = '\n[ CHAT RESPONSE ]\n';
281290
typedData.value.forEach((result, i) => {
282-
formattedMessage += result.content || '[No content]';
291+
const lines: string[] = [];
292+
if (result.thoughtBlock?.data || result.thought) {
293+
lines.push(
294+
`[thought${result.thoughtBlock?.encrypted ? ' (redacted)' : ''}] ` +
295+
(result.thoughtBlock?.data ?? result.thought ?? '')
296+
);
297+
}
298+
if (result.content) {
299+
lines.push(result.content);
300+
}
301+
if (lines.length === 0) {
302+
lines.push('[No content]');
303+
}
304+
formattedMessage += lines.join('\n');
283305
if (i < typedData.value.length - 1)
284306
formattedMessage += `\n${divider}\n`;
285307
});
@@ -296,6 +318,14 @@ export const axCreateDefaultTextLogger = (
296318
if (typedData.value.content) {
297319
formattedMessage += typedData.value.content;
298320
}
321+
if (typedData.value.thoughtBlock?.data || typedData.value.thought) {
322+
formattedMessage += `\n`;
323+
formattedMessage +=
324+
`[thought${typedData.value.thoughtBlock?.encrypted ? ' (redacted)' : ''}] ` +
325+
(typedData.value.thoughtBlock?.data ??
326+
typedData.value.thought ??
327+
'');
328+
}
299329
if (typedData.value.functionCalls) {
300330
formattedMessage += JSON.stringify(
301331
typedData.value.functionCalls,

src/examples/anthropic-thinking-separation.ts

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ export const reasoningGen = ax(
77
'userQuestion:string "User question" -> responseText:string "Final answer", thought?:string "Reasoning tokens (if available)"'
88
);
99

10-
console.log('=== Anthropic thinking separation demo ===');
10+
console.log('=== Anthropic thinking demo ===');
1111

1212
const llmWithThoughts = ai({
1313
name: 'anthropic',
1414
apiKey: process.env.ANTHROPIC_APIKEY!,
1515
config: {
16-
model: AxAIAnthropicModel.Claude37Sonnet,
16+
model: AxAIAnthropicModel.Claude4Sonnet,
1717
stream: false,
1818
temperature: 0.1,
1919
},
@@ -23,33 +23,15 @@ const llmWithThoughts = ai({
2323
},
2424
});
2525

26-
const withThoughts = await reasoningGen.forward(llmWithThoughts, {
27-
userQuestion:
28-
'In one sentence, explain why the sky appears blue on a clear day.',
29-
});
30-
31-
console.log('[showThoughts=true] answer:', withThoughts.responseText);
32-
if (withThoughts.thought)
33-
console.log('[showThoughts=true] thought:', withThoughts.thought);
34-
35-
const llmNoThoughts = ai({
36-
name: 'anthropic',
37-
apiKey: process.env.ANTHROPIC_APIKEY!,
38-
config: {
39-
model: AxAIAnthropicModel.Claude37Sonnet,
40-
stream: false,
41-
temperature: 0.1,
26+
await reasoningGen.forward(
27+
llmWithThoughts,
28+
{
29+
userQuestion:
30+
'In one sentence, explain why the sky appears blue on a clear day.',
4231
},
43-
options: {
32+
{
33+
showThoughts: true,
4434
thinkingTokenBudget: 'low',
45-
showThoughts: false,
46-
},
47-
});
48-
49-
const noThoughts = await reasoningGen.forward(llmNoThoughts, {
50-
userQuestion:
51-
'In one sentence, explain why the sky appears blue on a clear day.',
52-
});
53-
54-
console.log('[showThoughts=false] answer:', noThoughts.responseText);
55-
console.log('[showThoughts=false] thought:', String(noThoughts.thought ?? ''));
35+
debug: true,
36+
}
37+
);

0 commit comments

Comments
 (0)