@librechat/agents 3.1.75 → 3.1.77-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/graphs/Graph.cjs +22 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
- package/dist/cjs/hooks/HookRegistry.cjs +54 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
- package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
- package/dist/cjs/hooks/executeHooks.cjs +40 -1
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langchain/google-common.cjs +3 -0
- package/dist/cjs/langchain/google-common.cjs.map +1 -0
- package/dist/cjs/langchain/index.cjs +86 -0
- package/dist/cjs/langchain/index.cjs.map +1 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
- package/dist/cjs/langchain/messages/tool.cjs +3 -0
- package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
- package/dist/cjs/langchain/messages.cjs +51 -0
- package/dist/cjs/langchain/messages.cjs.map +1 -0
- package/dist/cjs/langchain/openai.cjs +3 -0
- package/dist/cjs/langchain/openai.cjs.map +1 -0
- package/dist/cjs/langchain/prompts.cjs +11 -0
- package/dist/cjs/langchain/prompts.cjs.map +1 -0
- package/dist/cjs/langchain/runnables.cjs +19 -0
- package/dist/cjs/langchain/runnables.cjs.map +1 -0
- package/dist/cjs/langchain/tools.cjs +23 -0
- package/dist/cjs/langchain/tools.cjs.map +1 -0
- package/dist/cjs/langchain/utils/env.cjs +11 -0
- package/dist/cjs/langchain/utils/env.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +145 -52
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +21 -14
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +5 -4
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +519 -655
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +20 -458
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +57 -175
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +5 -3
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +112 -3
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +2 -1
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +7 -6
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +73 -15
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +26 -0
- package/dist/cjs/messages/langchain.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +7 -6
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +400 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +556 -56
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +55 -66
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tavily-search.cjs +372 -0
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +26 -4
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +10 -3
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +22 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +65 -0
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
- package/dist/esm/hooks/HookRegistry.mjs +54 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
- package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
- package/dist/esm/hooks/executeHooks.mjs +40 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langchain/google-common.mjs +2 -0
- package/dist/esm/langchain/google-common.mjs.map +1 -0
- package/dist/esm/langchain/index.mjs +5 -0
- package/dist/esm/langchain/index.mjs.map +1 -0
- package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
- package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
- package/dist/esm/langchain/messages/tool.mjs +2 -0
- package/dist/esm/langchain/messages/tool.mjs.map +1 -0
- package/dist/esm/langchain/messages.mjs +2 -0
- package/dist/esm/langchain/messages.mjs.map +1 -0
- package/dist/esm/langchain/openai.mjs +2 -0
- package/dist/esm/langchain/openai.mjs.map +1 -0
- package/dist/esm/langchain/prompts.mjs +2 -0
- package/dist/esm/langchain/prompts.mjs.map +1 -0
- package/dist/esm/langchain/runnables.mjs +2 -0
- package/dist/esm/langchain/runnables.mjs.map +1 -0
- package/dist/esm/langchain/tools.mjs +2 -0
- package/dist/esm/langchain/tools.mjs.map +1 -0
- package/dist/esm/langchain/utils/env.mjs +2 -0
- package/dist/esm/langchain/utils/env.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +146 -54
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +21 -14
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +5 -4
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +520 -656
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +23 -459
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +57 -175
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +5 -3
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +2 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +7 -6
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +73 -15
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +23 -0
- package/dist/esm/messages/langchain.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +7 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +400 -42
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +557 -57
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +55 -66
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tavily-search.mjs +370 -0
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +26 -4
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +10 -3
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -0
- package/dist/types/hitl/askUserQuestion.d.ts +55 -0
- package/dist/types/hitl/index.d.ts +6 -0
- package/dist/types/hooks/HookRegistry.d.ts +58 -0
- package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
- package/dist/types/hooks/index.d.ts +4 -1
- package/dist/types/hooks/types.d.ts +109 -3
- package/dist/types/index.d.ts +10 -0
- package/dist/types/langchain/google-common.d.ts +1 -0
- package/dist/types/langchain/index.d.ts +8 -0
- package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
- package/dist/types/langchain/messages/tool.d.ts +1 -0
- package/dist/types/langchain/messages.d.ts +2 -0
- package/dist/types/langchain/openai.d.ts +1 -0
- package/dist/types/langchain/prompts.d.ts +1 -0
- package/dist/types/langchain/runnables.d.ts +2 -0
- package/dist/types/langchain/tools.d.ts +2 -0
- package/dist/types/langchain/utils/env.d.ts +1 -0
- package/dist/types/llm/anthropic/index.d.ts +22 -9
- package/dist/types/llm/anthropic/types.d.ts +5 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +21 -24
- package/dist/types/llm/openrouter/index.d.ts +11 -9
- package/dist/types/llm/vertexai/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/messages/format.d.ts +4 -1
- package/dist/types/messages/langchain.d.ts +27 -0
- package/dist/types/run.d.ts +117 -1
- package/dist/types/tools/ToolNode.d.ts +26 -1
- package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
- package/dist/types/tools/search/tavily-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +99 -5
- package/dist/types/tools/search/utils.d.ts +2 -2
- package/dist/types/types/graph.d.ts +23 -37
- package/dist/types/types/hitl.d.ts +272 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/llm.d.ts +3 -3
- package/dist/types/types/run.d.ts +33 -0
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/types/tools.d.ts +19 -0
- package/package.json +80 -17
- package/src/graphs/Graph.ts +33 -4
- package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
- package/src/hitl/askUserQuestion.ts +72 -0
- package/src/hitl/index.ts +7 -0
- package/src/hooks/HookRegistry.ts +71 -0
- package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
- package/src/hooks/createToolPolicyHook.ts +184 -0
- package/src/hooks/executeHooks.ts +50 -1
- package/src/hooks/index.ts +6 -0
- package/src/hooks/types.ts +112 -0
- package/src/index.ts +22 -0
- package/src/langchain/google-common.ts +1 -0
- package/src/langchain/index.ts +8 -0
- package/src/langchain/language_models/chat_models.ts +1 -0
- package/src/langchain/messages/tool.ts +5 -0
- package/src/langchain/messages.ts +21 -0
- package/src/langchain/openai.ts +1 -0
- package/src/langchain/prompts.ts +1 -0
- package/src/langchain/runnables.ts +7 -0
- package/src/langchain/tools.ts +8 -0
- package/src/langchain/utils/env.ts +1 -0
- package/src/llm/anthropic/index.ts +252 -84
- package/src/llm/anthropic/llm.spec.ts +751 -102
- package/src/llm/anthropic/types.ts +9 -1
- package/src/llm/anthropic/utils/message_inputs.ts +37 -19
- package/src/llm/anthropic/utils/message_outputs.ts +119 -101
- package/src/llm/bedrock/index.ts +2 -2
- package/src/llm/bedrock/llm.spec.ts +341 -0
- package/src/llm/bedrock/utils/message_inputs.ts +303 -4
- package/src/llm/bedrock/utils/message_outputs.ts +2 -1
- package/src/llm/custom-chat-models.smoke.test.ts +836 -0
- package/src/llm/google/llm.spec.ts +339 -57
- package/src/llm/google/utils/common.ts +53 -48
- package/src/llm/openai/contentBlocks.test.ts +346 -0
- package/src/llm/openai/index.ts +856 -833
- package/src/llm/openai/utils/index.ts +107 -78
- package/src/llm/openai/utils/messages.test.ts +159 -0
- package/src/llm/openrouter/index.ts +124 -247
- package/src/llm/openrouter/reasoning.test.ts +8 -1
- package/src/llm/vertexai/index.ts +11 -5
- package/src/llm/vertexai/llm.spec.ts +28 -1
- package/src/messages/cache.test.ts +4 -3
- package/src/messages/cache.ts +3 -2
- package/src/messages/core.ts +16 -9
- package/src/messages/format.ts +96 -16
- package/src/messages/formatAgentMessages.test.ts +166 -1
- package/src/messages/langchain.ts +39 -0
- package/src/messages/prune.ts +12 -8
- package/src/run.ts +456 -47
- package/src/scripts/caching.ts +2 -3
- package/src/specs/summarization.test.ts +51 -58
- package/src/tools/ToolNode.ts +706 -63
- package/src/tools/__tests__/hitl.test.ts +3593 -0
- package/src/tools/search/search.ts +83 -73
- package/src/tools/search/tavily-scraper.ts +235 -0
- package/src/tools/search/tavily-search.ts +424 -0
- package/src/tools/search/tavily.test.ts +965 -0
- package/src/tools/search/tool.ts +36 -26
- package/src/tools/search/types.ts +133 -8
- package/src/tools/search/utils.ts +13 -5
- package/src/types/graph.ts +32 -87
- package/src/types/hitl.ts +303 -0
- package/src/types/index.ts +1 -0
- package/src/types/llm.ts +3 -3
- package/src/types/run.ts +33 -0
- package/src/types/stream.ts +1 -1
- package/src/types/tools.ts +19 -0
- package/src/utils/llmConfig.ts +1 -6
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable no-process-env */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
2
|
import { config } from 'dotenv';
|
|
4
3
|
config();
|
|
5
4
|
import { expect, test } from '@jest/globals';
|
|
@@ -7,7 +6,6 @@ import * as fs from 'fs/promises';
|
|
|
7
6
|
import {
|
|
8
7
|
AIMessage,
|
|
9
8
|
AIMessageChunk,
|
|
10
|
-
BaseMessage,
|
|
11
9
|
HumanMessage,
|
|
12
10
|
SystemMessage,
|
|
13
11
|
ToolMessage,
|
|
@@ -22,13 +20,32 @@ import {
|
|
|
22
20
|
} from '@langchain/core/prompts';
|
|
23
21
|
import { CallbackManager } from '@langchain/core/callbacks/manager';
|
|
24
22
|
import { concat } from '@langchain/core/utils/stream';
|
|
23
|
+
import type Anthropic from '@anthropic-ai/sdk';
|
|
25
24
|
import { AnthropicVertex } from '@anthropic-ai/vertex-sdk';
|
|
26
|
-
import { BaseLanguageModelInput } from '@langchain/core/language_models/base';
|
|
27
25
|
import { tool } from '@langchain/core/tools';
|
|
28
26
|
import { z } from 'zod';
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
27
|
+
import type { BaseLanguageModelInput } from '@langchain/core/language_models/base';
|
|
28
|
+
import type {
|
|
29
|
+
BaseMessage,
|
|
30
|
+
ContentBlock,
|
|
31
|
+
MessageContentComplex,
|
|
32
|
+
} from '@langchain/core/messages';
|
|
33
|
+
import { toLangChainContent } from '@/messages/langchain';
|
|
34
|
+
import { _documentsInParams, CustomAnthropic as ChatAnthropic } from './index';
|
|
35
|
+
import type { CustomAnthropicCallOptions } from './index';
|
|
36
|
+
import type {
|
|
37
|
+
AnthropicContextManagementConfigParam,
|
|
38
|
+
AnthropicMessageCreateParams,
|
|
39
|
+
AnthropicMessageResponse,
|
|
40
|
+
AnthropicOutputConfig,
|
|
41
|
+
AnthropicThinkingConfigParam,
|
|
42
|
+
ChatAnthropicContentBlock,
|
|
43
|
+
} from './types';
|
|
31
44
|
import { _convertMessagesToAnthropicPayload } from './utils/message_inputs';
|
|
45
|
+
import {
|
|
46
|
+
_makeMessageChunkFromAnthropicEvent,
|
|
47
|
+
getAnthropicUsageMetadata,
|
|
48
|
+
} from './utils/message_outputs';
|
|
32
49
|
jest.setTimeout(120000);
|
|
33
50
|
|
|
34
51
|
async function invoke(
|
|
@@ -62,10 +79,126 @@ const extendedThinkingModelName = 'claude-sonnet-4-5-20250929';
|
|
|
62
79
|
const citationsModelName = 'claude-sonnet-4-5-20250929';
|
|
63
80
|
|
|
64
81
|
// use this for tests involving PDF documents
|
|
65
|
-
const pdfModelName = 'claude-haiku-4-5';
|
|
82
|
+
const pdfModelName = 'claude-haiku-4-5-20251001';
|
|
83
|
+
|
|
84
|
+
const remoteImageUrl =
|
|
85
|
+
'https://raw.githubusercontent.com/langchain-ai/langchainjs/main/libs/providers/langchain-google-genai/src/tests/data/hotdog.jpg';
|
|
66
86
|
|
|
67
87
|
// Use this model for all other tests
|
|
68
|
-
const modelName = 'claude-
|
|
88
|
+
const modelName = 'claude-haiku-4-5-20251001';
|
|
89
|
+
|
|
90
|
+
type AnthropicThinkingResponseBlock = Anthropic.Messages.ThinkingBlock & {
|
|
91
|
+
index?: number;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type AnthropicRedactedThinkingResponseBlock =
|
|
95
|
+
Anthropic.Messages.RedactedThinkingBlock & {
|
|
96
|
+
index?: number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
type AnthropicOutputConfigWithTaskBudget = AnthropicOutputConfig & {
|
|
100
|
+
task_budget: {
|
|
101
|
+
type: 'tokens';
|
|
102
|
+
total: number;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type LangChainErrorWithCode = {
|
|
107
|
+
lc_error_code?: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type CitationContentBlock = ContentBlock & {
|
|
111
|
+
citations: Array<{
|
|
112
|
+
type?: string;
|
|
113
|
+
source?: unknown;
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type CompactionContentBlock = ContentBlock & {
|
|
118
|
+
type: 'compaction';
|
|
119
|
+
content: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function getLangChainErrorCode(error: unknown): string | undefined {
|
|
123
|
+
if (typeof error !== 'object' || error == null) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!('lc_error_code' in error)) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { lc_error_code } = error as LangChainErrorWithCode;
|
|
132
|
+
return typeof lc_error_code === 'string' ? lc_error_code : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function expectContentArray<T>(content: string | T[]): T[] {
|
|
136
|
+
expect(Array.isArray(content)).toBe(true);
|
|
137
|
+
if (!Array.isArray(content)) {
|
|
138
|
+
throw new Error('Expected array content');
|
|
139
|
+
}
|
|
140
|
+
return content;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function expectDefined<T>(value: T | undefined): T {
|
|
144
|
+
expect(value).toBeDefined();
|
|
145
|
+
if (value === undefined) {
|
|
146
|
+
throw new Error('Expected defined value');
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isThinkingBlock(
|
|
152
|
+
block: unknown
|
|
153
|
+
): block is AnthropicThinkingResponseBlock {
|
|
154
|
+
return (
|
|
155
|
+
typeof block === 'object' &&
|
|
156
|
+
block !== null &&
|
|
157
|
+
'type' in block &&
|
|
158
|
+
block.type === 'thinking'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isRedactedThinkingBlock(
|
|
163
|
+
block: unknown
|
|
164
|
+
): block is AnthropicRedactedThinkingResponseBlock {
|
|
165
|
+
return (
|
|
166
|
+
typeof block === 'object' &&
|
|
167
|
+
block !== null &&
|
|
168
|
+
'type' in block &&
|
|
169
|
+
block.type === 'redacted_thinking'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isReasoningContentBlock(
|
|
174
|
+
block: ContentBlock
|
|
175
|
+
): block is ContentBlock.Reasoning {
|
|
176
|
+
return block.type === 'reasoning';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isTextContentBlock(block: ContentBlock): block is ContentBlock.Text {
|
|
180
|
+
return block.type === 'text';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function hasCitations(block: ContentBlock): block is CitationContentBlock {
|
|
184
|
+
if (!('citations' in block)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { citations } = block as { citations?: unknown };
|
|
189
|
+
return Array.isArray(citations) && citations.length > 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isCompactionBlock(
|
|
193
|
+
block: ContentBlock
|
|
194
|
+
): block is CompactionContentBlock {
|
|
195
|
+
if (block.type !== 'compaction') {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { content } = block as { content?: unknown };
|
|
200
|
+
return typeof content === 'string';
|
|
201
|
+
}
|
|
69
202
|
|
|
70
203
|
test('Test ChatAnthropic', async () => {
|
|
71
204
|
const chat = new ChatAnthropic({
|
|
@@ -83,7 +216,7 @@ test('Test ChatAnthropic with a bad API key throws appropriate error', async ()
|
|
|
83
216
|
maxRetries: 0,
|
|
84
217
|
apiKey: 'bad',
|
|
85
218
|
});
|
|
86
|
-
let error;
|
|
219
|
+
let error: unknown;
|
|
87
220
|
try {
|
|
88
221
|
const message = new HumanMessage('Hello!');
|
|
89
222
|
await chat.invoke([message]);
|
|
@@ -91,7 +224,7 @@ test('Test ChatAnthropic with a bad API key throws appropriate error', async ()
|
|
|
91
224
|
error = e;
|
|
92
225
|
}
|
|
93
226
|
expect(error).toBeDefined();
|
|
94
|
-
expect((error
|
|
227
|
+
expect(getLangChainErrorCode(error)).toEqual('MODEL_AUTHENTICATION');
|
|
95
228
|
});
|
|
96
229
|
|
|
97
230
|
test('Test ChatAnthropic with unknown model throws appropriate error', async () => {
|
|
@@ -99,7 +232,7 @@ test('Test ChatAnthropic with unknown model throws appropriate error', async ()
|
|
|
99
232
|
modelName: 'badbad',
|
|
100
233
|
maxRetries: 0,
|
|
101
234
|
});
|
|
102
|
-
let error;
|
|
235
|
+
let error: unknown;
|
|
103
236
|
try {
|
|
104
237
|
const message = new HumanMessage('Hello!');
|
|
105
238
|
await chat.invoke([message]);
|
|
@@ -107,7 +240,7 @@ test('Test ChatAnthropic with unknown model throws appropriate error', async ()
|
|
|
107
240
|
error = e;
|
|
108
241
|
}
|
|
109
242
|
expect(error).toBeDefined();
|
|
110
|
-
expect((error
|
|
243
|
+
expect(getLangChainErrorCode(error)).toEqual('MODEL_NOT_FOUND');
|
|
111
244
|
});
|
|
112
245
|
|
|
113
246
|
test('Test ChatAnthropic Generate', async () => {
|
|
@@ -311,7 +444,7 @@ test.skip('ChatAnthropic, Anthropic apiUrl set manually via constructor', async
|
|
|
311
444
|
anthropicApiUrl,
|
|
312
445
|
});
|
|
313
446
|
const message = new HumanMessage('Hello!');
|
|
314
|
-
const res = await chat.
|
|
447
|
+
const res = await chat.invoke([message]);
|
|
315
448
|
// console.log({ res });
|
|
316
449
|
});
|
|
317
450
|
|
|
@@ -413,8 +546,7 @@ describe('ChatAnthropic image inputs', () => {
|
|
|
413
546
|
content: [
|
|
414
547
|
{
|
|
415
548
|
type: 'image_url',
|
|
416
|
-
image_url:
|
|
417
|
-
'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/RedDisc.svg/24px-RedDisc.svg.png',
|
|
549
|
+
image_url: remoteImageUrl,
|
|
418
550
|
},
|
|
419
551
|
{ type: 'text', text: 'Describe this image.' },
|
|
420
552
|
],
|
|
@@ -500,7 +632,7 @@ describe('ChatAnthropic image inputs', () => {
|
|
|
500
632
|
type: 'image',
|
|
501
633
|
source: {
|
|
502
634
|
type: 'url',
|
|
503
|
-
url:
|
|
635
|
+
url: remoteImageUrl,
|
|
504
636
|
},
|
|
505
637
|
},
|
|
506
638
|
],
|
|
@@ -540,6 +672,40 @@ test('Stream tokens', async () => {
|
|
|
540
672
|
);
|
|
541
673
|
});
|
|
542
674
|
|
|
675
|
+
test('Anthropic usage metadata includes cache input token buckets', () => {
|
|
676
|
+
const usageMetadata = getAnthropicUsageMetadata({
|
|
677
|
+
input_tokens: 10,
|
|
678
|
+
output_tokens: 5,
|
|
679
|
+
cache_creation_input_tokens: 20,
|
|
680
|
+
cache_read_input_tokens: 30,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
expect(usageMetadata).toEqual({
|
|
684
|
+
input_tokens: 60,
|
|
685
|
+
output_tokens: 5,
|
|
686
|
+
total_tokens: 65,
|
|
687
|
+
input_token_details: {
|
|
688
|
+
cache_creation: 20,
|
|
689
|
+
cache_read: 30,
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
test('document detection ignores null content placeholders', () => {
|
|
695
|
+
const params: AnthropicMessageCreateParams = {
|
|
696
|
+
model: modelName,
|
|
697
|
+
max_tokens: 16,
|
|
698
|
+
messages: [
|
|
699
|
+
{
|
|
700
|
+
role: 'user',
|
|
701
|
+
content: [null as never, { type: 'text', text: 'hello' }],
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
expect(_documentsInParams(params)).toBe(false);
|
|
707
|
+
});
|
|
708
|
+
|
|
543
709
|
test('id is supplied when invoking', async () => {
|
|
544
710
|
const model = new ChatAnthropic({ modelName });
|
|
545
711
|
const result = await model.invoke('Hello');
|
|
@@ -822,7 +988,7 @@ The current date is ${new Date().toISOString()}`;
|
|
|
822
988
|
|
|
823
989
|
test('system prompt caching', async () => {
|
|
824
990
|
const model = new ChatAnthropic({
|
|
825
|
-
modelName,
|
|
991
|
+
modelName: citationsModelName,
|
|
826
992
|
clientOptions: {
|
|
827
993
|
defaultHeaders: {
|
|
828
994
|
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
@@ -832,6 +998,10 @@ test('system prompt caching', async () => {
|
|
|
832
998
|
const messages = [
|
|
833
999
|
new SystemMessage({
|
|
834
1000
|
content: [
|
|
1001
|
+
{
|
|
1002
|
+
type: 'text',
|
|
1003
|
+
text: `${new Date().toISOString()} (Now)`,
|
|
1004
|
+
},
|
|
835
1005
|
{
|
|
836
1006
|
type: 'text',
|
|
837
1007
|
text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
|
|
@@ -848,11 +1018,17 @@ test('system prompt caching', async () => {
|
|
|
848
1018
|
res.usage_metadata?.input_token_details?.cache_creation
|
|
849
1019
|
).toBeGreaterThan(0);
|
|
850
1020
|
expect(res.usage_metadata?.input_token_details?.cache_read).toBe(0);
|
|
1021
|
+
expect(res.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1022
|
+
res.usage_metadata?.input_token_details?.cache_creation ?? 0
|
|
1023
|
+
);
|
|
851
1024
|
const res2 = await model.invoke(messages);
|
|
852
1025
|
expect(res2.usage_metadata?.input_token_details?.cache_creation).toBe(0);
|
|
853
1026
|
expect(res2.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
|
|
854
1027
|
0
|
|
855
1028
|
);
|
|
1029
|
+
expect(res2.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1030
|
+
res2.usage_metadata?.input_token_details?.cache_read ?? 0
|
|
1031
|
+
);
|
|
856
1032
|
const stream = await model.stream(messages);
|
|
857
1033
|
let agg;
|
|
858
1034
|
for await (const chunk of stream) {
|
|
@@ -863,6 +1039,9 @@ test('system prompt caching', async () => {
|
|
|
863
1039
|
expect(agg!.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
|
|
864
1040
|
0
|
|
865
1041
|
);
|
|
1042
|
+
expect(agg!.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1043
|
+
agg!.usage_metadata?.input_token_details?.cache_read ?? 0
|
|
1044
|
+
);
|
|
866
1045
|
});
|
|
867
1046
|
|
|
868
1047
|
// TODO: Add proper test with long tool content
|
|
@@ -928,7 +1107,7 @@ test.skip('Test ChatAnthropic with custom client', async () => {
|
|
|
928
1107
|
|
|
929
1108
|
test('human message caching', async () => {
|
|
930
1109
|
const model = new ChatAnthropic({
|
|
931
|
-
modelName,
|
|
1110
|
+
modelName: citationsModelName,
|
|
932
1111
|
});
|
|
933
1112
|
|
|
934
1113
|
const messages = [
|
|
@@ -936,7 +1115,11 @@ test('human message caching', async () => {
|
|
|
936
1115
|
content: [
|
|
937
1116
|
{
|
|
938
1117
|
type: 'text',
|
|
939
|
-
text:
|
|
1118
|
+
text: `${new Date().toISOString()} (Now)`,
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
type: 'text',
|
|
1122
|
+
text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
|
|
940
1123
|
},
|
|
941
1124
|
],
|
|
942
1125
|
}),
|
|
@@ -956,11 +1139,31 @@ test('human message caching', async () => {
|
|
|
956
1139
|
res.usage_metadata?.input_token_details?.cache_creation
|
|
957
1140
|
).toBeGreaterThan(0);
|
|
958
1141
|
expect(res.usage_metadata?.input_token_details?.cache_read).toBe(0);
|
|
1142
|
+
expect(res.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1143
|
+
res.usage_metadata?.input_token_details?.cache_creation ?? 0
|
|
1144
|
+
);
|
|
959
1145
|
const res2 = await model.invoke(messages);
|
|
960
1146
|
expect(res2.usage_metadata?.input_token_details?.cache_creation).toBe(0);
|
|
961
1147
|
expect(res2.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
|
|
962
1148
|
0
|
|
963
1149
|
);
|
|
1150
|
+
expect(res2.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1151
|
+
res2.usage_metadata?.input_token_details?.cache_read ?? 0
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const stream = await model.stream(messages);
|
|
1155
|
+
let agg;
|
|
1156
|
+
for await (const chunk of stream) {
|
|
1157
|
+
agg = agg === undefined ? chunk : concat(agg, chunk);
|
|
1158
|
+
}
|
|
1159
|
+
expect(agg).toBeDefined();
|
|
1160
|
+
expect(agg!.usage_metadata?.input_token_details?.cache_creation).toBe(0);
|
|
1161
|
+
expect(agg!.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
|
|
1162
|
+
0
|
|
1163
|
+
);
|
|
1164
|
+
expect(agg!.usage_metadata?.input_tokens).toBeGreaterThan(
|
|
1165
|
+
agg!.usage_metadata?.input_token_details?.cache_read ?? 0
|
|
1166
|
+
);
|
|
964
1167
|
});
|
|
965
1168
|
|
|
966
1169
|
test('Can accept PDF documents', async () => {
|
|
@@ -1027,11 +1230,9 @@ describe('Citations', () => {
|
|
|
1027
1230
|
|
|
1028
1231
|
const response = await citationsModel.invoke(messages);
|
|
1029
1232
|
|
|
1030
|
-
|
|
1031
|
-
expect(
|
|
1032
|
-
const blocksWithCitations =
|
|
1033
|
-
(block) => block.citations !== undefined
|
|
1034
|
-
);
|
|
1233
|
+
const responseBlocks = expectContentArray<ContentBlock>(response.content);
|
|
1234
|
+
expect(responseBlocks.length).toBeGreaterThan(2);
|
|
1235
|
+
const blocksWithCitations = responseBlocks.filter(hasCitations);
|
|
1035
1236
|
expect(blocksWithCitations.length).toEqual(2);
|
|
1036
1237
|
expect(typeof blocksWithCitations[0].citations[0]).toEqual('object');
|
|
1037
1238
|
|
|
@@ -1043,17 +1244,17 @@ describe('Citations', () => {
|
|
|
1043
1244
|
if (
|
|
1044
1245
|
!chunkHasCitation &&
|
|
1045
1246
|
Array.isArray(chunk.content) &&
|
|
1046
|
-
chunk.content.some(
|
|
1247
|
+
chunk.content.some(hasCitations)
|
|
1047
1248
|
) {
|
|
1048
1249
|
chunkHasCitation = true;
|
|
1049
1250
|
}
|
|
1050
1251
|
}
|
|
1051
1252
|
expect(chunkHasCitation).toBe(true);
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
).toBe(true);
|
|
1253
|
+
const aggregatedBlocks = expectContentArray<ContentBlock>(
|
|
1254
|
+
aggregated?.content ?? []
|
|
1255
|
+
);
|
|
1256
|
+
expect(aggregatedBlocks.length).toBeGreaterThan(2);
|
|
1257
|
+
expect(aggregatedBlocks.some(hasCitations)).toBe(true);
|
|
1057
1258
|
});
|
|
1058
1259
|
describe('search result blocks', () => {
|
|
1059
1260
|
const citationsModel = new ChatAnthropic({
|
|
@@ -1112,13 +1313,11 @@ describe('Citations', () => {
|
|
|
1112
1313
|
test('without streaming', async () => {
|
|
1113
1314
|
const response = await citationsModel.invoke(messages);
|
|
1114
1315
|
|
|
1115
|
-
|
|
1116
|
-
expect(
|
|
1316
|
+
const responseBlocks = expectContentArray<ContentBlock>(response.content);
|
|
1317
|
+
expect(responseBlocks.length).toBeGreaterThan(0);
|
|
1117
1318
|
|
|
1118
1319
|
// Check that we have cited content
|
|
1119
|
-
const blocksWithCitations =
|
|
1120
|
-
(block) => block.citations !== undefined
|
|
1121
|
-
);
|
|
1320
|
+
const blocksWithCitations = responseBlocks.filter(hasCitations);
|
|
1122
1321
|
expect(blocksWithCitations.length).toBeGreaterThan(0);
|
|
1123
1322
|
|
|
1124
1323
|
// Verify citation structure
|
|
@@ -1138,16 +1337,16 @@ describe('Citations', () => {
|
|
|
1138
1337
|
if (
|
|
1139
1338
|
!chunkHasCitation &&
|
|
1140
1339
|
Array.isArray(chunk.content) &&
|
|
1141
|
-
chunk.content.some(
|
|
1340
|
+
chunk.content.some(hasCitations)
|
|
1142
1341
|
) {
|
|
1143
1342
|
chunkHasCitation = true;
|
|
1144
1343
|
}
|
|
1145
1344
|
}
|
|
1146
1345
|
expect(chunkHasCitation).toBe(true);
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
).toBe(true);
|
|
1346
|
+
const aggregatedBlocks = expectContentArray<ContentBlock>(
|
|
1347
|
+
aggregated?.content ?? []
|
|
1348
|
+
);
|
|
1349
|
+
expect(aggregatedBlocks.some(hasCitations)).toBe(true);
|
|
1151
1350
|
});
|
|
1152
1351
|
});
|
|
1153
1352
|
|
|
@@ -1205,7 +1404,7 @@ describe('Citations', () => {
|
|
|
1205
1404
|
},
|
|
1206
1405
|
}).bindTools([ragTool]);
|
|
1207
1406
|
|
|
1208
|
-
const messages = [
|
|
1407
|
+
const messages: BaseMessage[] = [
|
|
1209
1408
|
new HumanMessage(
|
|
1210
1409
|
'Search for information about France and tell me what you find with proper citations.'
|
|
1211
1410
|
),
|
|
@@ -1226,19 +1425,131 @@ describe('Citations', () => {
|
|
|
1226
1425
|
|
|
1227
1426
|
const response2 = await citationsModel.invoke(messages);
|
|
1228
1427
|
|
|
1229
|
-
|
|
1230
|
-
expect(
|
|
1428
|
+
const response2Blocks = expectContentArray<ContentBlock>(response2.content);
|
|
1429
|
+
expect(response2Blocks.length).toBeGreaterThan(0);
|
|
1231
1430
|
// Make sure that a citation exists somewhere in the content list
|
|
1232
|
-
const citationBlock = (
|
|
1233
|
-
(block: any) =>
|
|
1234
|
-
Array.isArray(block.citations) && block.citations.length > 0
|
|
1235
|
-
);
|
|
1236
|
-
expect(citationBlock).toBeDefined();
|
|
1431
|
+
const citationBlock = expectDefined(response2Blocks.find(hasCitations));
|
|
1237
1432
|
expect(citationBlock.citations[0].type).toBe('search_result_location');
|
|
1238
1433
|
expect(citationBlock.citations[0].source).toBeDefined();
|
|
1239
1434
|
});
|
|
1240
1435
|
});
|
|
1241
1436
|
|
|
1437
|
+
describe('Opus 4.7', () => {
|
|
1438
|
+
test('default max_tokens for claude-opus-4-7 is 16384', () => {
|
|
1439
|
+
const model = new ChatAnthropic({
|
|
1440
|
+
model: 'claude-opus-4-7',
|
|
1441
|
+
apiKey: 'testing',
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
const params = model.invocationParams({});
|
|
1445
|
+
|
|
1446
|
+
expect(params.max_tokens).toBe(16384);
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
test('does not apply Opus 4.7 rules to longer prefix matches', () => {
|
|
1450
|
+
const model = new ChatAnthropic({
|
|
1451
|
+
model: 'claude-opus-4-70',
|
|
1452
|
+
apiKey: 'testing',
|
|
1453
|
+
topK: 40,
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
const params = model.invocationParams({});
|
|
1457
|
+
|
|
1458
|
+
expect(params.top_k).toBe(40);
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
test('rejects thinking.type=enabled for claude-opus-4-7', () => {
|
|
1462
|
+
const model = new ChatAnthropic({
|
|
1463
|
+
model: 'claude-opus-4-7',
|
|
1464
|
+
apiKey: 'testing',
|
|
1465
|
+
thinking: { type: 'enabled', budget_tokens: 2048 },
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
expect(() => model.invocationParams({})).toThrow(
|
|
1469
|
+
'thinking.type="enabled" is not supported for claude-opus-4-7; use thinking.type="adaptive" instead'
|
|
1470
|
+
);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
test('rejects thinking.budget_tokens for claude-opus-4-7', () => {
|
|
1474
|
+
const model = new ChatAnthropic({
|
|
1475
|
+
model: 'claude-opus-4-7',
|
|
1476
|
+
apiKey: 'testing',
|
|
1477
|
+
thinking: {
|
|
1478
|
+
type: 'adaptive',
|
|
1479
|
+
budget_tokens: 2048,
|
|
1480
|
+
} as AnthropicThinkingConfigParam & { budget_tokens: number },
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
expect(() => model.invocationParams({})).toThrow(
|
|
1484
|
+
'thinking.budget_tokens is not supported for claude-opus-4-7; use outputConfig.effort instead'
|
|
1485
|
+
);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
test('rejects non-default sampling params for claude-opus-4-7', () => {
|
|
1489
|
+
const model = new ChatAnthropic({
|
|
1490
|
+
model: 'claude-opus-4-7',
|
|
1491
|
+
apiKey: 'testing',
|
|
1492
|
+
temperature: 0.1,
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
expect(() => model.invocationParams({})).toThrow(
|
|
1496
|
+
'temperature is not supported for claude-opus-4-7 when set to non-default values'
|
|
1497
|
+
);
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
test('does not include sampling params for claude-opus-4-7 even if set to defaults', () => {
|
|
1501
|
+
const model = new ChatAnthropic({
|
|
1502
|
+
model: 'claude-opus-4-7',
|
|
1503
|
+
apiKey: 'testing',
|
|
1504
|
+
temperature: 1,
|
|
1505
|
+
topP: 1,
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
const params = model.invocationParams({});
|
|
1509
|
+
|
|
1510
|
+
expect(params.temperature).toBeUndefined();
|
|
1511
|
+
expect(params.top_p).toBeUndefined();
|
|
1512
|
+
expect(params.top_k).toBeUndefined();
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
test('passes thinking.display through for claude-opus-4-7', () => {
|
|
1516
|
+
const model = new ChatAnthropic({
|
|
1517
|
+
model: 'claude-opus-4-7',
|
|
1518
|
+
apiKey: 'testing',
|
|
1519
|
+
thinking: { type: 'adaptive', display: 'summarized' },
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
const params = model.invocationParams({});
|
|
1523
|
+
|
|
1524
|
+
expect(params.thinking).toEqual({
|
|
1525
|
+
type: 'adaptive',
|
|
1526
|
+
display: 'summarized',
|
|
1527
|
+
});
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
test('auto-adds task budget beta when outputConfig.task_budget is provided', () => {
|
|
1531
|
+
const model = new ChatAnthropic({
|
|
1532
|
+
model: 'claude-opus-4-7',
|
|
1533
|
+
apiKey: 'testing',
|
|
1534
|
+
outputConfig: {
|
|
1535
|
+
effort: 'high',
|
|
1536
|
+
task_budget: { type: 'tokens', total: 128000 },
|
|
1537
|
+
} as AnthropicOutputConfigWithTaskBudget,
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
const params = model.invocationParams({});
|
|
1541
|
+
|
|
1542
|
+
expect(params.betas).toContain('task-budgets-2026-03-13');
|
|
1543
|
+
expect(params.output_config).toEqual({
|
|
1544
|
+
effort: 'high',
|
|
1545
|
+
task_budget: {
|
|
1546
|
+
type: 'tokens',
|
|
1547
|
+
total: 128000,
|
|
1548
|
+
},
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1242
1553
|
test('Test thinking blocks multiturn invoke', async () => {
|
|
1243
1554
|
const model = new ChatAnthropic({
|
|
1244
1555
|
model: extendedThinkingModelName,
|
|
@@ -1251,24 +1562,24 @@ test('Test thinking blocks multiturn invoke', async () => {
|
|
|
1251
1562
|
|
|
1252
1563
|
expect(Array.isArray(response.content)).toBe(true);
|
|
1253
1564
|
const content = response.content as AnthropicMessageResponse[];
|
|
1254
|
-
expect(content.some(
|
|
1565
|
+
expect(content.some(isThinkingBlock)).toBe(true);
|
|
1255
1566
|
|
|
1256
|
-
for (const block of
|
|
1567
|
+
for (const block of content) {
|
|
1257
1568
|
expect(typeof block).toBe('object');
|
|
1258
|
-
if ((block
|
|
1569
|
+
if (isThinkingBlock(block)) {
|
|
1259
1570
|
expect(Object.keys(block).sort()).toEqual(
|
|
1260
1571
|
['type', 'thinking', 'signature'].sort()
|
|
1261
1572
|
);
|
|
1262
|
-
expect(
|
|
1263
|
-
expect(typeof
|
|
1264
|
-
expect(
|
|
1265
|
-
expect(typeof
|
|
1573
|
+
expect(block.thinking).toBeTruthy();
|
|
1574
|
+
expect(typeof block.thinking).toBe('string');
|
|
1575
|
+
expect(block.signature).toBeTruthy();
|
|
1576
|
+
expect(typeof block.signature).toBe('string');
|
|
1266
1577
|
}
|
|
1267
1578
|
}
|
|
1268
1579
|
return response;
|
|
1269
1580
|
}
|
|
1270
1581
|
|
|
1271
|
-
const invokeMessages = [new HumanMessage('Hello')];
|
|
1582
|
+
const invokeMessages: BaseMessage[] = [new HumanMessage('Hello')];
|
|
1272
1583
|
|
|
1273
1584
|
invokeMessages.push(await doInvoke(invokeMessages));
|
|
1274
1585
|
invokeMessages.push(new HumanMessage('What is 42+7?'));
|
|
@@ -1292,24 +1603,24 @@ test('Test thinking blocks multiturn streaming', async () => {
|
|
|
1292
1603
|
expect(full).toBeInstanceOf(AIMessageChunk);
|
|
1293
1604
|
expect(Array.isArray(full?.content)).toBe(true);
|
|
1294
1605
|
const content3 = full?.content as AnthropicMessageResponse[];
|
|
1295
|
-
expect(content3.some(
|
|
1606
|
+
expect(content3.some(isThinkingBlock)).toBe(true);
|
|
1296
1607
|
|
|
1297
|
-
for (const block of
|
|
1608
|
+
for (const block of content3) {
|
|
1298
1609
|
expect(typeof block).toBe('object');
|
|
1299
|
-
if ((block
|
|
1610
|
+
if (isThinkingBlock(block)) {
|
|
1300
1611
|
expect(Object.keys(block).sort()).toEqual(
|
|
1301
1612
|
['type', 'thinking', 'signature', 'index'].sort()
|
|
1302
1613
|
);
|
|
1303
|
-
expect(
|
|
1304
|
-
expect(typeof
|
|
1305
|
-
expect(
|
|
1306
|
-
expect(typeof
|
|
1614
|
+
expect(block.thinking).toBeTruthy();
|
|
1615
|
+
expect(typeof block.thinking).toBe('string');
|
|
1616
|
+
expect(block.signature).toBeTruthy();
|
|
1617
|
+
expect(typeof block.signature).toBe('string');
|
|
1307
1618
|
}
|
|
1308
1619
|
}
|
|
1309
1620
|
return full as AIMessageChunk;
|
|
1310
1621
|
}
|
|
1311
1622
|
|
|
1312
|
-
const streamingMessages = [new HumanMessage('Hello')];
|
|
1623
|
+
const streamingMessages: BaseMessage[] = [new HumanMessage('Hello')];
|
|
1313
1624
|
|
|
1314
1625
|
streamingMessages.push(await doStreaming(streamingMessages));
|
|
1315
1626
|
streamingMessages.push(new HumanMessage('What is 42+7?'));
|
|
@@ -1328,21 +1639,24 @@ test('Test redacted thinking blocks multiturn invoke', async () => {
|
|
|
1328
1639
|
async function doInvoke(messages: BaseMessage[]) {
|
|
1329
1640
|
const response = await model.invoke(messages);
|
|
1330
1641
|
let hasReasoning = false;
|
|
1642
|
+
const content = response.content as AnthropicMessageResponse[];
|
|
1331
1643
|
|
|
1332
|
-
for (const block of
|
|
1644
|
+
for (const block of content) {
|
|
1333
1645
|
expect(typeof block).toBe('object');
|
|
1334
|
-
if ((block
|
|
1646
|
+
if (isRedactedThinkingBlock(block)) {
|
|
1335
1647
|
hasReasoning = true;
|
|
1336
1648
|
expect(Object.keys(block).sort()).toEqual(['type', 'data'].sort());
|
|
1337
|
-
expect(
|
|
1338
|
-
expect(typeof
|
|
1649
|
+
expect(block.data).toBeTruthy();
|
|
1650
|
+
expect(typeof block.data).toBe('string');
|
|
1339
1651
|
}
|
|
1340
1652
|
}
|
|
1341
|
-
|
|
1653
|
+
if (!hasReasoning) {
|
|
1654
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1655
|
+
}
|
|
1342
1656
|
return response;
|
|
1343
1657
|
}
|
|
1344
1658
|
|
|
1345
|
-
const invokeMessages = [
|
|
1659
|
+
const invokeMessages: BaseMessage[] = [
|
|
1346
1660
|
new HumanMessage(
|
|
1347
1661
|
'ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
|
|
1348
1662
|
),
|
|
@@ -1370,23 +1684,26 @@ test('Test redacted thinking blocks multiturn streaming', async () => {
|
|
|
1370
1684
|
expect(full).toBeInstanceOf(AIMessageChunk);
|
|
1371
1685
|
expect(Array.isArray(full?.content)).toBe(true);
|
|
1372
1686
|
let streamHasReasoning = false;
|
|
1687
|
+
const content = full?.content as AnthropicMessageResponse[];
|
|
1373
1688
|
|
|
1374
|
-
for (const block of
|
|
1689
|
+
for (const block of content) {
|
|
1375
1690
|
expect(typeof block).toBe('object');
|
|
1376
|
-
if ((block
|
|
1691
|
+
if (isRedactedThinkingBlock(block)) {
|
|
1377
1692
|
streamHasReasoning = true;
|
|
1378
1693
|
expect(Object.keys(block).sort()).toEqual(
|
|
1379
1694
|
['type', 'data', 'index'].sort()
|
|
1380
1695
|
);
|
|
1381
|
-
expect(
|
|
1382
|
-
expect(typeof
|
|
1696
|
+
expect(block.data).toBeTruthy();
|
|
1697
|
+
expect(typeof block.data).toBe('string');
|
|
1383
1698
|
}
|
|
1384
1699
|
}
|
|
1385
|
-
|
|
1700
|
+
if (!streamHasReasoning) {
|
|
1701
|
+
expect(full?.content.length).toBeGreaterThan(0);
|
|
1702
|
+
}
|
|
1386
1703
|
return full as AIMessageChunk;
|
|
1387
1704
|
}
|
|
1388
1705
|
|
|
1389
|
-
const streamingMessages = [
|
|
1706
|
+
const streamingMessages: BaseMessage[] = [
|
|
1390
1707
|
new HumanMessage(
|
|
1391
1708
|
'ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
|
|
1392
1709
|
),
|
|
@@ -1399,6 +1716,71 @@ test('Test redacted thinking blocks multiturn streaming', async () => {
|
|
|
1399
1716
|
await doStreaming(streamingMessages);
|
|
1400
1717
|
});
|
|
1401
1718
|
|
|
1719
|
+
test('Can properly format messages with redacted thinking blocks', () => {
|
|
1720
|
+
const messageHistory = [
|
|
1721
|
+
new AIMessage({
|
|
1722
|
+
content: toLangChainContent([
|
|
1723
|
+
{
|
|
1724
|
+
type: 'redacted_thinking',
|
|
1725
|
+
data: 'encrypted-redacted-thinking-data',
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
type: 'text',
|
|
1729
|
+
text: 'Continuing after redacted thinking.',
|
|
1730
|
+
},
|
|
1731
|
+
] satisfies ChatAnthropicContentBlock[]),
|
|
1732
|
+
}),
|
|
1733
|
+
];
|
|
1734
|
+
|
|
1735
|
+
const formattedMessages = _convertMessagesToAnthropicPayload(messageHistory);
|
|
1736
|
+
|
|
1737
|
+
expect(formattedMessages.messages).toHaveLength(1);
|
|
1738
|
+
expect(formattedMessages.messages[0].role).toBe('assistant');
|
|
1739
|
+
expect(formattedMessages.messages[0].content).toEqual([
|
|
1740
|
+
{
|
|
1741
|
+
type: 'redacted_thinking',
|
|
1742
|
+
data: 'encrypted-redacted-thinking-data',
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
type: 'text',
|
|
1746
|
+
text: 'Continuing after redacted thinking.',
|
|
1747
|
+
},
|
|
1748
|
+
]);
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
test('Can convert redacted thinking stream blocks', () => {
|
|
1752
|
+
const event: Anthropic.Beta.Messages.BetaRawMessageStreamEvent = {
|
|
1753
|
+
type: 'content_block_start',
|
|
1754
|
+
index: 0,
|
|
1755
|
+
content_block: {
|
|
1756
|
+
type: 'redacted_thinking',
|
|
1757
|
+
data: 'encrypted-redacted-thinking-data',
|
|
1758
|
+
},
|
|
1759
|
+
};
|
|
1760
|
+
const result = _makeMessageChunkFromAnthropicEvent(event, {
|
|
1761
|
+
streamUsage: true,
|
|
1762
|
+
coerceContentToString: false,
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
expect(result?.chunk.content).toEqual([
|
|
1766
|
+
{
|
|
1767
|
+
index: 0,
|
|
1768
|
+
type: 'redacted_thinking',
|
|
1769
|
+
data: 'encrypted-redacted-thinking-data',
|
|
1770
|
+
},
|
|
1771
|
+
]);
|
|
1772
|
+
expect(result?.chunk.contentBlocks).toEqual([
|
|
1773
|
+
{
|
|
1774
|
+
type: 'non_standard',
|
|
1775
|
+
value: {
|
|
1776
|
+
index: 0,
|
|
1777
|
+
type: 'redacted_thinking',
|
|
1778
|
+
data: 'encrypted-redacted-thinking-data',
|
|
1779
|
+
},
|
|
1780
|
+
},
|
|
1781
|
+
]);
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1402
1784
|
test('Can handle google function calling blocks in content', async () => {
|
|
1403
1785
|
const chat = new ChatAnthropic({
|
|
1404
1786
|
modelName: 'claude-sonnet-4-5-20250929',
|
|
@@ -1409,7 +1791,7 @@ test('Can handle google function calling blocks in content', async () => {
|
|
|
1409
1791
|
new SystemMessage("You're a helpful assistant"),
|
|
1410
1792
|
new HumanMessage('What is the weather like in San Francisco?'),
|
|
1411
1793
|
new AIMessage({
|
|
1412
|
-
content: [
|
|
1794
|
+
content: toLangChainContent([
|
|
1413
1795
|
{
|
|
1414
1796
|
// Pass a content block with the `functionCall` object that Google returns.
|
|
1415
1797
|
functionCall: {
|
|
@@ -1418,8 +1800,8 @@ test('Can handle google function calling blocks in content', async () => {
|
|
|
1418
1800
|
},
|
|
1419
1801
|
name: 'get_weather',
|
|
1420
1802
|
},
|
|
1421
|
-
},
|
|
1422
|
-
],
|
|
1803
|
+
} as MessageContentComplex,
|
|
1804
|
+
]),
|
|
1423
1805
|
tool_calls: [
|
|
1424
1806
|
{
|
|
1425
1807
|
id: toolCallId,
|
|
@@ -1442,6 +1824,170 @@ test('Can handle google function calling blocks in content', async () => {
|
|
|
1442
1824
|
expect(res.content.length).toBeGreaterThan(1);
|
|
1443
1825
|
});
|
|
1444
1826
|
|
|
1827
|
+
describe('Opus 4.1', () => {
|
|
1828
|
+
test('works without passing any args', async () => {
|
|
1829
|
+
const model = new ChatAnthropic({
|
|
1830
|
+
model: 'claude-opus-4-1',
|
|
1831
|
+
});
|
|
1832
|
+
|
|
1833
|
+
const response = await model.invoke(
|
|
1834
|
+
'Please respond to this message simply with: Hello'
|
|
1835
|
+
);
|
|
1836
|
+
|
|
1837
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
test('works with streaming and thinking', async () => {
|
|
1841
|
+
const model = new ChatAnthropic({
|
|
1842
|
+
model: 'claude-opus-4-1',
|
|
1843
|
+
thinking: {
|
|
1844
|
+
type: 'enabled',
|
|
1845
|
+
budget_tokens: 1024,
|
|
1846
|
+
},
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
const response = await model.invoke(
|
|
1850
|
+
'Please respond to this message simply with: Hello'
|
|
1851
|
+
);
|
|
1852
|
+
|
|
1853
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
describe('Sonnet 4.5', () => {
|
|
1858
|
+
test('works without passing any args', async () => {
|
|
1859
|
+
const model = new ChatAnthropic({
|
|
1860
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
const response = await model.invoke(
|
|
1864
|
+
'Please respond to this message simply with: Hello'
|
|
1865
|
+
);
|
|
1866
|
+
|
|
1867
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
test('works with streaming and thinking', async () => {
|
|
1871
|
+
const model = new ChatAnthropic({
|
|
1872
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
1873
|
+
thinking: {
|
|
1874
|
+
type: 'enabled',
|
|
1875
|
+
budget_tokens: 1024,
|
|
1876
|
+
},
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
const response = await model.invoke(
|
|
1880
|
+
'Please respond to this message simply with: Hello'
|
|
1881
|
+
);
|
|
1882
|
+
|
|
1883
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
test('works when passing topP arg', async () => {
|
|
1887
|
+
const model = new ChatAnthropic({
|
|
1888
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
1889
|
+
topP: 0.99,
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
const response = await model.invoke(
|
|
1893
|
+
'Please respond to this message simply with: Hello'
|
|
1894
|
+
);
|
|
1895
|
+
|
|
1896
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1897
|
+
});
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
describe('Opus 4.5', () => {
|
|
1901
|
+
test('works without passing any args', async () => {
|
|
1902
|
+
const model = new ChatAnthropic({
|
|
1903
|
+
model: 'claude-opus-4-5',
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
const response = await model.invoke(
|
|
1907
|
+
'Please respond to this message simply with: Hello'
|
|
1908
|
+
);
|
|
1909
|
+
|
|
1910
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1911
|
+
});
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
test("won't modify structured output content if outputVersion is set", async () => {
|
|
1915
|
+
const schema = z.object({ name: z.string() });
|
|
1916
|
+
const model = new ChatAnthropic({
|
|
1917
|
+
model: 'claude-opus-4-1',
|
|
1918
|
+
outputVersion: 'v1',
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
const response = await model
|
|
1922
|
+
.withStructuredOutput(schema)
|
|
1923
|
+
.invoke("respond with the name 'John'");
|
|
1924
|
+
|
|
1925
|
+
expect(response.name).toBeDefined();
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
describe('will work with native structured output', () => {
|
|
1929
|
+
const schema = z.object({ name: z.string() });
|
|
1930
|
+
|
|
1931
|
+
test.each(['claude-opus-4-1', 'claude-sonnet-4-5-20250929'])(
|
|
1932
|
+
'works with %s',
|
|
1933
|
+
async (structuredOutputModelName) => {
|
|
1934
|
+
const model = new ChatAnthropic({
|
|
1935
|
+
model: structuredOutputModelName,
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const response = await model
|
|
1939
|
+
.withStructuredOutput(schema, { method: 'jsonSchema' })
|
|
1940
|
+
.invoke("respond with the name 'John'");
|
|
1941
|
+
|
|
1942
|
+
expect(response.name).toBeDefined();
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
describe('Anthropic Reasoning with contentBlocks', () => {
|
|
1948
|
+
test('invoke returns thinking as reasoning in contentBlocks', async () => {
|
|
1949
|
+
const model = new ChatAnthropic({
|
|
1950
|
+
model: extendedThinkingModelName,
|
|
1951
|
+
maxTokens: 5000,
|
|
1952
|
+
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
const result = await model.invoke('What is 2 + 2?');
|
|
1956
|
+
const blocks = result.contentBlocks;
|
|
1957
|
+
|
|
1958
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
1959
|
+
|
|
1960
|
+
const reasoningBlocks = blocks.filter(isReasoningContentBlock);
|
|
1961
|
+
expect(reasoningBlocks.length).toBeGreaterThan(0);
|
|
1962
|
+
expect(reasoningBlocks[0].reasoning.length).toBeGreaterThan(10);
|
|
1963
|
+
|
|
1964
|
+
const textBlocks = blocks.filter((block) => block.type === 'text');
|
|
1965
|
+
expect(textBlocks.length).toBeGreaterThan(0);
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
test('stream returns thinking as reasoning in contentBlocks', async () => {
|
|
1969
|
+
const model = new ChatAnthropic({
|
|
1970
|
+
model: extendedThinkingModelName,
|
|
1971
|
+
maxTokens: 5000,
|
|
1972
|
+
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
let fullMessage: AIMessageChunk | null = null;
|
|
1976
|
+
for await (const chunk of await model.stream('What is 3 + 3?')) {
|
|
1977
|
+
fullMessage = fullMessage ? concat(fullMessage, chunk) : chunk;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
expect(fullMessage).toBeDefined();
|
|
1981
|
+
|
|
1982
|
+
const blocks = fullMessage!.contentBlocks;
|
|
1983
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
1984
|
+
|
|
1985
|
+
const reasoningBlocks = blocks.filter(isReasoningContentBlock);
|
|
1986
|
+
expect(reasoningBlocks.length).toBeGreaterThan(0);
|
|
1987
|
+
expect(reasoningBlocks[0].reasoning.length).toBeGreaterThan(10);
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1445
1991
|
const opus46Model = 'claude-opus-4-6';
|
|
1446
1992
|
|
|
1447
1993
|
describe('Opus 4.6', () => {
|
|
@@ -1474,6 +2020,24 @@ describe('Opus 4.6', () => {
|
|
|
1474
2020
|
expect(params.top_p).toBeUndefined();
|
|
1475
2021
|
});
|
|
1476
2022
|
|
|
2023
|
+
test('adaptive thinking treats sampling sentinels as unset', () => {
|
|
2024
|
+
const model = new ChatAnthropic({
|
|
2025
|
+
model: opus46Model,
|
|
2026
|
+
apiKey: 'testing',
|
|
2027
|
+
maxTokens: 4096,
|
|
2028
|
+
thinking: { type: 'adaptive' },
|
|
2029
|
+
});
|
|
2030
|
+
model.temperature = -1;
|
|
2031
|
+
model.topP = -1;
|
|
2032
|
+
model.top_k = -1;
|
|
2033
|
+
|
|
2034
|
+
const params = model.invocationParams({});
|
|
2035
|
+
|
|
2036
|
+
expect(params.temperature).toBeUndefined();
|
|
2037
|
+
expect(params.top_k).toBeUndefined();
|
|
2038
|
+
expect(params.top_p).toBeUndefined();
|
|
2039
|
+
});
|
|
2040
|
+
|
|
1477
2041
|
test('adaptive thinking throws on non-default temperature', () => {
|
|
1478
2042
|
const model = new ChatAnthropic({
|
|
1479
2043
|
model: opus46Model,
|
|
@@ -1499,9 +2063,7 @@ describe('Opus 4.6', () => {
|
|
|
1499
2063
|
expect(result.content).toBeDefined();
|
|
1500
2064
|
|
|
1501
2065
|
if (Array.isArray(result.content)) {
|
|
1502
|
-
const textBlocks =
|
|
1503
|
-
(b) => b.type === 'text'
|
|
1504
|
-
);
|
|
2066
|
+
const textBlocks = result.content.filter(isTextContentBlock);
|
|
1505
2067
|
expect(textBlocks.length).toBeGreaterThan(0);
|
|
1506
2068
|
} else {
|
|
1507
2069
|
expect(typeof result.content).toBe('string');
|
|
@@ -1524,6 +2086,25 @@ describe('Opus 4.6', () => {
|
|
|
1524
2086
|
const response2 = await model.invoke(messages);
|
|
1525
2087
|
expect(response2.content).toBeDefined();
|
|
1526
2088
|
});
|
|
2089
|
+
|
|
2090
|
+
test('withStructuredOutput jsonSchema', async () => {
|
|
2091
|
+
const model = new ChatAnthropic({
|
|
2092
|
+
model: opus46Model,
|
|
2093
|
+
maxTokens: 4096,
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
const schema = z.object({
|
|
2097
|
+
answer: z.number().describe('The numeric answer'),
|
|
2098
|
+
});
|
|
2099
|
+
|
|
2100
|
+
const structured = model.withStructuredOutput(schema, {
|
|
2101
|
+
method: 'jsonSchema',
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
const result = await structured.invoke('What is 2 + 2?');
|
|
2105
|
+
expect(result).toBeDefined();
|
|
2106
|
+
expect(typeof result.answer).toBe('number');
|
|
2107
|
+
});
|
|
1527
2108
|
});
|
|
1528
2109
|
|
|
1529
2110
|
describe('Effort parameter (outputConfig)', () => {
|
|
@@ -1549,7 +2130,7 @@ describe('Opus 4.6', () => {
|
|
|
1549
2130
|
|
|
1550
2131
|
const params = model.invocationParams({
|
|
1551
2132
|
outputConfig: { effort: 'low' },
|
|
1552
|
-
}
|
|
2133
|
+
});
|
|
1553
2134
|
|
|
1554
2135
|
expect(params.output_config).toEqual({ effort: 'low' });
|
|
1555
2136
|
});
|
|
@@ -1564,7 +2145,7 @@ describe('Opus 4.6', () => {
|
|
|
1564
2145
|
|
|
1565
2146
|
const params = model.invocationParams({
|
|
1566
2147
|
outputConfig: { effort: 'low' },
|
|
1567
|
-
}
|
|
2148
|
+
});
|
|
1568
2149
|
|
|
1569
2150
|
expect(params.output_config).toEqual({ effort: 'low' });
|
|
1570
2151
|
});
|
|
@@ -1635,9 +2216,10 @@ describe('Opus 4.6', () => {
|
|
|
1635
2216
|
thinking: { type: 'adaptive' },
|
|
1636
2217
|
});
|
|
1637
2218
|
|
|
1638
|
-
const
|
|
2219
|
+
const options: CustomAnthropicCallOptions = {
|
|
1639
2220
|
outputConfig: { effort: 'low' },
|
|
1640
|
-
}
|
|
2221
|
+
};
|
|
2222
|
+
const result = await model.invoke('Say hello.', options);
|
|
1641
2223
|
expect(result.content).toBeDefined();
|
|
1642
2224
|
});
|
|
1643
2225
|
});
|
|
@@ -1655,7 +2237,7 @@ describe('Opus 4.6', () => {
|
|
|
1655
2237
|
type: 'json_schema',
|
|
1656
2238
|
schema: { type: 'object' },
|
|
1657
2239
|
},
|
|
1658
|
-
}
|
|
2240
|
+
});
|
|
1659
2241
|
|
|
1660
2242
|
expect(params.output_config).toEqual({
|
|
1661
2243
|
format: {
|
|
@@ -1683,7 +2265,7 @@ describe('Opus 4.6', () => {
|
|
|
1683
2265
|
type: 'json_schema',
|
|
1684
2266
|
schema: { type: 'object', properties: { b: { type: 'number' } } },
|
|
1685
2267
|
},
|
|
1686
|
-
}
|
|
2268
|
+
});
|
|
1687
2269
|
|
|
1688
2270
|
expect(params.output_config?.format).toEqual({
|
|
1689
2271
|
type: 'json_schema',
|
|
@@ -1704,7 +2286,7 @@ describe('Opus 4.6', () => {
|
|
|
1704
2286
|
type: 'json_schema',
|
|
1705
2287
|
schema: { type: 'object' },
|
|
1706
2288
|
},
|
|
1707
|
-
}
|
|
2289
|
+
});
|
|
1708
2290
|
|
|
1709
2291
|
expect(params.output_config).toEqual({
|
|
1710
2292
|
effort: 'medium',
|
|
@@ -1739,7 +2321,7 @@ describe('Opus 4.6', () => {
|
|
|
1739
2321
|
|
|
1740
2322
|
const params = model.invocationParams({
|
|
1741
2323
|
inferenceGeo: 'us',
|
|
1742
|
-
}
|
|
2324
|
+
});
|
|
1743
2325
|
|
|
1744
2326
|
expect(params.inference_geo).toBe('us');
|
|
1745
2327
|
});
|
|
@@ -1754,7 +2336,7 @@ describe('Opus 4.6', () => {
|
|
|
1754
2336
|
|
|
1755
2337
|
const params = model.invocationParams({
|
|
1756
2338
|
inferenceGeo: 'us',
|
|
1757
|
-
}
|
|
2339
|
+
});
|
|
1758
2340
|
|
|
1759
2341
|
expect(params.inference_geo).toBe('us');
|
|
1760
2342
|
});
|
|
@@ -1784,25 +2366,90 @@ describe('Opus 4.6', () => {
|
|
|
1784
2366
|
});
|
|
1785
2367
|
|
|
1786
2368
|
describe('Compaction API', () => {
|
|
2369
|
+
const compactionConfig: AnthropicContextManagementConfigParam = {
|
|
2370
|
+
edits: [
|
|
2371
|
+
{
|
|
2372
|
+
type: 'compact_20260112' as const,
|
|
2373
|
+
trigger: { type: 'input_tokens' as const, value: 50000 },
|
|
2374
|
+
},
|
|
2375
|
+
],
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
function buildLongConversation(): BaseMessage[] {
|
|
2379
|
+
const padding =
|
|
2380
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
|
|
2381
|
+
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' +
|
|
2382
|
+
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris ' +
|
|
2383
|
+
'nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in ' +
|
|
2384
|
+
'reprehenderit in voluptate velit esse cillum dolore eu fugiat. ';
|
|
2385
|
+
|
|
2386
|
+
const messages: BaseMessage[] = [];
|
|
2387
|
+
for (let i = 0; i < 300; i++) {
|
|
2388
|
+
messages.push(new HumanMessage(`${padding} (message ${i})`));
|
|
2389
|
+
messages.push(new AIMessage(`Acknowledged message ${i}. ${padding}`));
|
|
2390
|
+
}
|
|
2391
|
+
messages.push(new HumanMessage('Summarize our conversation.'));
|
|
2392
|
+
return messages;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
1787
2395
|
test('context_management passed through invocationParams', () => {
|
|
1788
2396
|
const model = new ChatAnthropic({
|
|
1789
2397
|
model: opus46Model,
|
|
1790
2398
|
apiKey: 'testing',
|
|
1791
2399
|
maxTokens: 4096,
|
|
1792
|
-
contextManagement:
|
|
1793
|
-
edits: [
|
|
1794
|
-
{
|
|
1795
|
-
type: 'compact_20260112',
|
|
1796
|
-
trigger: { type: 'input_tokens', value: 50000 },
|
|
1797
|
-
},
|
|
1798
|
-
],
|
|
1799
|
-
},
|
|
2400
|
+
contextManagement: compactionConfig,
|
|
1800
2401
|
});
|
|
1801
2402
|
|
|
1802
2403
|
const params = model.invocationParams({});
|
|
1803
2404
|
|
|
1804
|
-
|
|
1805
|
-
|
|
2405
|
+
const contextManagement = expectDefined(params.context_management);
|
|
2406
|
+
const edits = expectDefined(contextManagement.edits);
|
|
2407
|
+
expect(edits[0].type).toBe('compact_20260112');
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
test('accepted by API without triggering', async () => {
|
|
2411
|
+
const model = new ChatAnthropic({
|
|
2412
|
+
model: opus46Model,
|
|
2413
|
+
maxTokens: 4096,
|
|
2414
|
+
contextManagement: compactionConfig,
|
|
2415
|
+
});
|
|
2416
|
+
|
|
2417
|
+
const response = await model.invoke('Say hello.');
|
|
2418
|
+
expect(response.content).toBeDefined();
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2421
|
+
test('triggers compaction block (invoke)', async () => {
|
|
2422
|
+
const model = new ChatAnthropic({
|
|
2423
|
+
model: opus46Model,
|
|
2424
|
+
maxTokens: 4096,
|
|
2425
|
+
contextManagement: compactionConfig,
|
|
2426
|
+
});
|
|
2427
|
+
|
|
2428
|
+
const result = await model.invoke(buildLongConversation());
|
|
2429
|
+
|
|
2430
|
+
const blocks = expectContentArray<ContentBlock>(result.content);
|
|
2431
|
+
const compactionBlock = expectDefined(blocks.find(isCompactionBlock));
|
|
2432
|
+
expect(typeof compactionBlock.content).toBe('string');
|
|
2433
|
+
expect(compactionBlock.content.length).toBeGreaterThan(0);
|
|
2434
|
+
});
|
|
2435
|
+
|
|
2436
|
+
test('triggers compaction block (stream)', async () => {
|
|
2437
|
+
const model = new ChatAnthropic({
|
|
2438
|
+
model: opus46Model,
|
|
2439
|
+
maxTokens: 4096,
|
|
2440
|
+
contextManagement: compactionConfig,
|
|
2441
|
+
});
|
|
2442
|
+
|
|
2443
|
+
let full: AIMessageChunk | undefined;
|
|
2444
|
+
for await (const chunk of await model.stream(buildLongConversation())) {
|
|
2445
|
+
full = full ? concat(full, chunk) : chunk;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
expect(full).toBeInstanceOf(AIMessageChunk);
|
|
2449
|
+
const blocks = expectContentArray<ContentBlock>(full!.content);
|
|
2450
|
+
const compactionBlock = expectDefined(blocks.find(isCompactionBlock));
|
|
2451
|
+
expect(typeof compactionBlock.content).toBe('string');
|
|
2452
|
+
expect(compactionBlock.content.length).toBeGreaterThan(0);
|
|
1806
2453
|
});
|
|
1807
2454
|
|
|
1808
2455
|
test('Can properly format messages with compaction blocks', () => {
|
|
@@ -1829,8 +2476,10 @@ describe('Opus 4.6', () => {
|
|
|
1829
2476
|
expect(formattedMessages.messages[0].role).toBe('assistant');
|
|
1830
2477
|
expect(formattedMessages.messages[0].content).toHaveLength(2);
|
|
1831
2478
|
|
|
1832
|
-
const [compactionBlock, textBlock] =
|
|
1833
|
-
.
|
|
2479
|
+
const [compactionBlock, textBlock] =
|
|
2480
|
+
expectContentArray<Anthropic.Messages.ContentBlockParam>(
|
|
2481
|
+
formattedMessages.messages[0].content
|
|
2482
|
+
);
|
|
1834
2483
|
expect(compactionBlock).toEqual({
|
|
1835
2484
|
type: 'compaction',
|
|
1836
2485
|
content: 'Summary: The user asked about building a web scraper...',
|