@aigne/anthropic 0.14.16-beta.2 → 0.14.16-beta.20
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,232 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.14.16-beta.20](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.19...anthropic-v0.14.16-beta.20) (2026-01-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **anthropic:** handle null content blocks in streaming responses ([9fefd6f](https://github.com/AIGNE-io/aigne-framework/commit/9fefd6fcca58bb8a59616560f86a04a0015f6aca))
|
|
9
|
+
|
|
10
|
+
## [0.14.16-beta.19](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.18...anthropic-v0.14.16-beta.19) (2026-01-13)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **anthropic:** simplify structured output with output tool pattern ([#899](https://github.com/AIGNE-io/aigne-framework/issues/899)) ([a6033c8](https://github.com/AIGNE-io/aigne-framework/commit/a6033c8a9ccf5e8ff6bcb14bc68c43a990ce2fa2))
|
|
16
|
+
* **anthropic:** update structured output tool name to generate_json ([897e94d](https://github.com/AIGNE-io/aigne-framework/commit/897e94d82a1bbfa46ae13038a58a65cba6a3b259))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Dependencies
|
|
20
|
+
|
|
21
|
+
* The following workspace dependencies were updated
|
|
22
|
+
* dependencies
|
|
23
|
+
* @aigne/core bumped to 1.72.0-beta.18
|
|
24
|
+
* devDependencies
|
|
25
|
+
* @aigne/test-utils bumped to 0.5.69-beta.18
|
|
26
|
+
|
|
27
|
+
## [0.14.16-beta.18](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.17...anthropic-v0.14.16-beta.18) (2026-01-12)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
* The following workspace dependencies were updated
|
|
33
|
+
* dependencies
|
|
34
|
+
* @aigne/core bumped to 1.72.0-beta.17
|
|
35
|
+
* devDependencies
|
|
36
|
+
* @aigne/test-utils bumped to 0.5.69-beta.17
|
|
37
|
+
|
|
38
|
+
## [0.14.16-beta.17](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.16...anthropic-v0.14.16-beta.17) (2026-01-12)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Dependencies
|
|
42
|
+
|
|
43
|
+
* The following workspace dependencies were updated
|
|
44
|
+
* dependencies
|
|
45
|
+
* @aigne/core bumped to 1.72.0-beta.16
|
|
46
|
+
* devDependencies
|
|
47
|
+
* @aigne/test-utils bumped to 0.5.69-beta.16
|
|
48
|
+
|
|
49
|
+
## [0.14.16-beta.16](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.15...anthropic-v0.14.16-beta.16) (2026-01-10)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* **core:** simplify token-estimator logic for remaining characters ([45d43cc](https://github.com/AIGNE-io/aigne-framework/commit/45d43ccd3afd636cfb459eea2e6551e8f9c53765))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Dependencies
|
|
58
|
+
|
|
59
|
+
* The following workspace dependencies were updated
|
|
60
|
+
* dependencies
|
|
61
|
+
* @aigne/core bumped to 1.72.0-beta.15
|
|
62
|
+
* devDependencies
|
|
63
|
+
* @aigne/test-utils bumped to 0.5.69-beta.15
|
|
64
|
+
|
|
65
|
+
## [0.14.16-beta.15](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.14...anthropic-v0.14.16-beta.15) (2026-01-09)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Bug Fixes
|
|
69
|
+
|
|
70
|
+
* **core:** default enable auto breakpoints for chat model ([d4a6b83](https://github.com/AIGNE-io/aigne-framework/commit/d4a6b8323d6c83be45669885b32febb545bdf797))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
### Dependencies
|
|
74
|
+
|
|
75
|
+
* The following workspace dependencies were updated
|
|
76
|
+
* dependencies
|
|
77
|
+
* @aigne/core bumped to 1.72.0-beta.14
|
|
78
|
+
* devDependencies
|
|
79
|
+
* @aigne/test-utils bumped to 0.5.69-beta.14
|
|
80
|
+
|
|
81
|
+
## [0.14.16-beta.14](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.13...anthropic-v0.14.16-beta.14) (2026-01-08)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### Bug Fixes
|
|
85
|
+
|
|
86
|
+
* bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Dependencies
|
|
90
|
+
|
|
91
|
+
* The following workspace dependencies were updated
|
|
92
|
+
* dependencies
|
|
93
|
+
* @aigne/core bumped to 1.72.0-beta.13
|
|
94
|
+
* @aigne/platform-helpers bumped to 0.6.7-beta.1
|
|
95
|
+
* devDependencies
|
|
96
|
+
* @aigne/test-utils bumped to 0.5.69-beta.13
|
|
97
|
+
|
|
98
|
+
## [0.14.16-beta.13](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.12...anthropic-v0.14.16-beta.13) (2026-01-07)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Dependencies
|
|
102
|
+
|
|
103
|
+
* The following workspace dependencies were updated
|
|
104
|
+
* dependencies
|
|
105
|
+
* @aigne/core bumped to 1.72.0-beta.12
|
|
106
|
+
* devDependencies
|
|
107
|
+
* @aigne/test-utils bumped to 0.5.69-beta.12
|
|
108
|
+
|
|
109
|
+
## [0.14.16-beta.12](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.11...anthropic-v0.14.16-beta.12) (2026-01-06)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Bug Fixes
|
|
113
|
+
|
|
114
|
+
* **core:** preserve Agent Skill in session compact and support complex tool result content ([#876](https://github.com/AIGNE-io/aigne-framework/issues/876)) ([edb86ae](https://github.com/AIGNE-io/aigne-framework/commit/edb86ae2b9cfe56a8f08b276f843606e310566cf))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
### Dependencies
|
|
118
|
+
|
|
119
|
+
* The following workspace dependencies were updated
|
|
120
|
+
* dependencies
|
|
121
|
+
* @aigne/core bumped to 1.72.0-beta.11
|
|
122
|
+
* devDependencies
|
|
123
|
+
* @aigne/test-utils bumped to 0.5.69-beta.11
|
|
124
|
+
|
|
125
|
+
## [0.14.16-beta.11](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.10...anthropic-v0.14.16-beta.11) (2026-01-06)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### Dependencies
|
|
129
|
+
|
|
130
|
+
* The following workspace dependencies were updated
|
|
131
|
+
* dependencies
|
|
132
|
+
* @aigne/core bumped to 1.72.0-beta.10
|
|
133
|
+
* devDependencies
|
|
134
|
+
* @aigne/test-utils bumped to 0.5.69-beta.10
|
|
135
|
+
|
|
136
|
+
## [0.14.16-beta.10](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.9...anthropic-v0.14.16-beta.10) (2026-01-02)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
### Dependencies
|
|
140
|
+
|
|
141
|
+
* The following workspace dependencies were updated
|
|
142
|
+
* dependencies
|
|
143
|
+
* @aigne/core bumped to 1.72.0-beta.9
|
|
144
|
+
* devDependencies
|
|
145
|
+
* @aigne/test-utils bumped to 0.5.69-beta.9
|
|
146
|
+
|
|
147
|
+
## [0.14.16-beta.9](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.8...anthropic-v0.14.16-beta.9) (2025-12-31)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
### Features
|
|
151
|
+
|
|
152
|
+
* add session compact support for AIAgent ([#863](https://github.com/AIGNE-io/aigne-framework/issues/863)) ([9010918](https://github.com/AIGNE-io/aigne-framework/commit/9010918cd3f18b02b5c60ddc9ed5c34b568d0b28))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
### Dependencies
|
|
156
|
+
|
|
157
|
+
* The following workspace dependencies were updated
|
|
158
|
+
* dependencies
|
|
159
|
+
* @aigne/core bumped to 1.72.0-beta.8
|
|
160
|
+
* devDependencies
|
|
161
|
+
* @aigne/test-utils bumped to 0.5.69-beta.8
|
|
162
|
+
|
|
163
|
+
## [0.14.16-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.7...anthropic-v0.14.16-beta.8) (2025-12-26)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
### Dependencies
|
|
167
|
+
|
|
168
|
+
* The following workspace dependencies were updated
|
|
169
|
+
* dependencies
|
|
170
|
+
* @aigne/core bumped to 1.72.0-beta.7
|
|
171
|
+
* devDependencies
|
|
172
|
+
* @aigne/test-utils bumped to 0.5.69-beta.7
|
|
173
|
+
|
|
174
|
+
## [0.14.16-beta.7](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.6...anthropic-v0.14.16-beta.7) (2025-12-25)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
### Dependencies
|
|
178
|
+
|
|
179
|
+
* The following workspace dependencies were updated
|
|
180
|
+
* dependencies
|
|
181
|
+
* @aigne/core bumped to 1.72.0-beta.6
|
|
182
|
+
* devDependencies
|
|
183
|
+
* @aigne/test-utils bumped to 0.5.69-beta.6
|
|
184
|
+
|
|
185
|
+
## [0.14.16-beta.6](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.5...anthropic-v0.14.16-beta.6) (2025-12-25)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### Bug Fixes
|
|
189
|
+
|
|
190
|
+
* **models:** support cache the last message for anthropic chat model ([#853](https://github.com/AIGNE-io/aigne-framework/issues/853)) ([bd08e44](https://github.com/AIGNE-io/aigne-framework/commit/bd08e44b28c46ac9a85234f0100d6dd144703c9d))
|
|
191
|
+
|
|
192
|
+
## [0.14.16-beta.5](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.4...anthropic-v0.14.16-beta.5) (2025-12-25)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
### Dependencies
|
|
196
|
+
|
|
197
|
+
* The following workspace dependencies were updated
|
|
198
|
+
* dependencies
|
|
199
|
+
* @aigne/core bumped to 1.72.0-beta.5
|
|
200
|
+
* devDependencies
|
|
201
|
+
* @aigne/test-utils bumped to 0.5.69-beta.5
|
|
202
|
+
|
|
203
|
+
## [0.14.16-beta.4](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.3...anthropic-v0.14.16-beta.4) (2025-12-24)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
### Dependencies
|
|
207
|
+
|
|
208
|
+
* The following workspace dependencies were updated
|
|
209
|
+
* dependencies
|
|
210
|
+
* @aigne/core bumped to 1.72.0-beta.4
|
|
211
|
+
* devDependencies
|
|
212
|
+
* @aigne/test-utils bumped to 0.5.69-beta.4
|
|
213
|
+
|
|
214
|
+
## [0.14.16-beta.3](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.2...anthropic-v0.14.16-beta.3) (2025-12-19)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
### Features
|
|
218
|
+
|
|
219
|
+
* add prompt caching for OpenAI/Gemini/Anthropic and cache token display ([#838](https://github.com/AIGNE-io/aigne-framework/issues/838)) ([46c628f](https://github.com/AIGNE-io/aigne-framework/commit/46c628f180572ea1b955d1a9888aad6145204842))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
### Dependencies
|
|
223
|
+
|
|
224
|
+
* The following workspace dependencies were updated
|
|
225
|
+
* dependencies
|
|
226
|
+
* @aigne/core bumped to 1.72.0-beta.3
|
|
227
|
+
* devDependencies
|
|
228
|
+
* @aigne/test-utils bumped to 0.5.69-beta.3
|
|
229
|
+
|
|
3
230
|
## [0.14.16-beta.2](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.1...anthropic-v0.14.16-beta.2) (2025-12-19)
|
|
4
231
|
|
|
5
232
|
|
|
@@ -124,19 +124,22 @@ export declare class AnthropicChatModel extends ChatModel {
|
|
|
124
124
|
reasoningEffort?: number | "minimal" | "low" | "medium" | "high" | {
|
|
125
125
|
$get: string;
|
|
126
126
|
} | undefined;
|
|
127
|
+
cacheConfig?: import("@aigne/core").CacheConfig | {
|
|
128
|
+
$get: string;
|
|
129
|
+
} | undefined;
|
|
127
130
|
}> | undefined;
|
|
128
131
|
get credential(): {
|
|
129
132
|
apiKey: string | undefined;
|
|
130
133
|
model: string;
|
|
131
134
|
};
|
|
135
|
+
countTokens(input: ChatModelInput): Promise<number>;
|
|
136
|
+
private getMessageCreateParams;
|
|
132
137
|
private getMaxTokens;
|
|
133
138
|
/**
|
|
134
139
|
* Process the input using Claude's chat model
|
|
135
140
|
* @param input - The input to process
|
|
136
141
|
* @returns The processed output from the model
|
|
137
142
|
*/
|
|
138
|
-
process(input: ChatModelInput,
|
|
139
|
-
private
|
|
140
|
-
private extractResultFromAnthropicStream;
|
|
141
|
-
private requestStructuredOutput;
|
|
143
|
+
process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
144
|
+
private processInput;
|
|
142
145
|
}
|
|
@@ -6,13 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AnthropicChatModel = exports.claudeChatModelOptionsSchema = void 0;
|
|
7
7
|
const core_1 = require("@aigne/core");
|
|
8
8
|
const json_schema_js_1 = require("@aigne/core/utils/json-schema.js");
|
|
9
|
-
const logger_js_1 = require("@aigne/core/utils/logger.js");
|
|
10
|
-
const model_utils_js_1 = require("@aigne/core/utils/model-utils.js");
|
|
11
|
-
const stream_utils_js_1 = require("@aigne/core/utils/stream-utils.js");
|
|
12
9
|
const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
|
|
13
10
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
14
11
|
const zod_1 = require("zod");
|
|
15
12
|
const CHAT_MODEL_CLAUDE_DEFAULT_MODEL = "claude-3-7-sonnet-latest";
|
|
13
|
+
const OUTPUT_FUNCTION_NAME = "generate_json";
|
|
16
14
|
/**
|
|
17
15
|
* @hidden
|
|
18
16
|
*/
|
|
@@ -82,6 +80,23 @@ class AnthropicChatModel extends core_1.ChatModel {
|
|
|
82
80
|
model: this.options?.model || CHAT_MODEL_CLAUDE_DEFAULT_MODEL,
|
|
83
81
|
};
|
|
84
82
|
}
|
|
83
|
+
async countTokens(input) {
|
|
84
|
+
const request = await this.getMessageCreateParams(input);
|
|
85
|
+
return (await this.client.messages.countTokens((0, type_utils_js_1.omit)(request, "max_tokens"))).input_tokens;
|
|
86
|
+
}
|
|
87
|
+
async getMessageCreateParams(input) {
|
|
88
|
+
const { modelOptions = {} } = input;
|
|
89
|
+
const model = modelOptions.model || this.credential.model;
|
|
90
|
+
const disableParallelToolUse = modelOptions.parallelToolCalls === false;
|
|
91
|
+
return {
|
|
92
|
+
model,
|
|
93
|
+
temperature: modelOptions.temperature,
|
|
94
|
+
top_p: modelOptions.topP,
|
|
95
|
+
max_tokens: this.getMaxTokens(model),
|
|
96
|
+
...(await convertMessages(input)),
|
|
97
|
+
...convertTools({ ...input, disableParallelToolUse }),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
85
100
|
getMaxTokens(model) {
|
|
86
101
|
const matchers = [
|
|
87
102
|
[/claude-opus-4-/, 32000],
|
|
@@ -102,186 +117,131 @@ class AnthropicChatModel extends core_1.ChatModel {
|
|
|
102
117
|
* @param input - The input to process
|
|
103
118
|
* @returns The processed output from the model
|
|
104
119
|
*/
|
|
105
|
-
process(input,
|
|
106
|
-
return this.
|
|
120
|
+
process(input, _options) {
|
|
121
|
+
return this.processInput(input);
|
|
107
122
|
}
|
|
108
|
-
async
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
async *processInput(input) {
|
|
124
|
+
const body = await this.getMessageCreateParams(input);
|
|
125
|
+
const stream = this.client.messages.stream({ ...body, stream: true });
|
|
126
|
+
const blocks = [];
|
|
127
|
+
let usage;
|
|
128
|
+
let json;
|
|
129
|
+
for await (const chunk of stream) {
|
|
130
|
+
if (chunk.type === "message_start") {
|
|
131
|
+
yield { delta: { json: { model: chunk.message.model } } };
|
|
132
|
+
const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
|
|
133
|
+
usage = {
|
|
134
|
+
inputTokens: input_tokens,
|
|
135
|
+
outputTokens: output_tokens,
|
|
136
|
+
cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
|
|
137
|
+
cacheReadInputTokens: cache_read_input_tokens ?? undefined,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (chunk.type === "message_delta" && usage) {
|
|
141
|
+
usage.outputTokens = chunk.usage.output_tokens;
|
|
142
|
+
}
|
|
143
|
+
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
144
|
+
yield { delta: { text: { text: chunk.delta.text } } };
|
|
145
|
+
}
|
|
146
|
+
if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
|
|
147
|
+
blocks[chunk.index] = {
|
|
148
|
+
type: "function",
|
|
149
|
+
id: chunk.content_block.id,
|
|
150
|
+
function: { name: chunk.content_block.name, arguments: {} },
|
|
151
|
+
args: "",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
|
|
155
|
+
const call = blocks[chunk.index];
|
|
156
|
+
if (!call)
|
|
157
|
+
throw new Error("Tool call not found");
|
|
158
|
+
call.args += chunk.delta.partial_json;
|
|
159
|
+
}
|
|
125
160
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
161
|
+
const toolCalls = blocks.filter(type_utils_js_1.isNonNullable);
|
|
162
|
+
// Separate output tool from business tool calls
|
|
163
|
+
const outputToolCall = toolCalls.find((c) => c.function.name === OUTPUT_FUNCTION_NAME);
|
|
164
|
+
const businessToolCalls = toolCalls
|
|
165
|
+
.filter((c) => c.function.name !== OUTPUT_FUNCTION_NAME)
|
|
166
|
+
.map(({ args, ...c }) => ({
|
|
167
|
+
...c,
|
|
168
|
+
function: {
|
|
169
|
+
...c.function,
|
|
170
|
+
arguments: args.trim() ? (0, json_schema_js_1.parseJSON)(args) : {},
|
|
171
|
+
},
|
|
172
|
+
}))
|
|
173
|
+
.filter(type_utils_js_1.isNonNullable);
|
|
174
|
+
if (outputToolCall) {
|
|
175
|
+
json = outputToolCall.args.trim() ? (0, json_schema_js_1.parseJSON)(outputToolCall.args) : {};
|
|
132
176
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (result.toolCalls?.length)
|
|
136
|
-
return result;
|
|
137
|
-
// Try to parse the text response as JSON
|
|
138
|
-
// If it matches the json_schema, return it as json
|
|
139
|
-
const json = (0, core_1.safeParseJSON)(result.text || "");
|
|
140
|
-
const validated = this.validateJsonSchema(input.responseFormat.jsonSchema.schema, json, {
|
|
141
|
-
safe: true,
|
|
142
|
-
});
|
|
143
|
-
if (validated.success) {
|
|
144
|
-
return { ...result, json: validated.data, text: undefined };
|
|
177
|
+
if (businessToolCalls.length) {
|
|
178
|
+
yield { delta: { json: { toolCalls: businessToolCalls } } };
|
|
145
179
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// so we need to make a separate request for json_schema response when the tool calls is empty
|
|
149
|
-
const output = await this.requestStructuredOutput(body, input.responseFormat);
|
|
150
|
-
return {
|
|
151
|
-
...output,
|
|
152
|
-
// merge usage from both requests
|
|
153
|
-
usage: (0, model_utils_js_1.mergeUsage)(result.usage, output.usage),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
async extractResultFromAnthropicStream(stream, streaming) {
|
|
157
|
-
const result = new ReadableStream({
|
|
158
|
-
async start(controller) {
|
|
159
|
-
try {
|
|
160
|
-
const toolCalls = [];
|
|
161
|
-
let usage;
|
|
162
|
-
let model;
|
|
163
|
-
for await (const chunk of stream) {
|
|
164
|
-
if (chunk.type === "message_start") {
|
|
165
|
-
if (!model) {
|
|
166
|
-
model = chunk.message.model;
|
|
167
|
-
controller.enqueue({ delta: { json: { model } } });
|
|
168
|
-
}
|
|
169
|
-
const { input_tokens, output_tokens } = chunk.message.usage;
|
|
170
|
-
usage = {
|
|
171
|
-
inputTokens: input_tokens,
|
|
172
|
-
outputTokens: output_tokens,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (chunk.type === "message_delta" && usage) {
|
|
176
|
-
usage.outputTokens = chunk.usage.output_tokens;
|
|
177
|
-
}
|
|
178
|
-
// handle streaming text
|
|
179
|
-
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
180
|
-
controller.enqueue({
|
|
181
|
-
delta: { text: { text: chunk.delta.text } },
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
|
|
185
|
-
toolCalls[chunk.index] = {
|
|
186
|
-
type: "function",
|
|
187
|
-
id: chunk.content_block.id,
|
|
188
|
-
function: {
|
|
189
|
-
name: chunk.content_block.name,
|
|
190
|
-
arguments: {},
|
|
191
|
-
},
|
|
192
|
-
args: "",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
|
|
196
|
-
const call = toolCalls[chunk.index];
|
|
197
|
-
if (!call)
|
|
198
|
-
throw new Error("Tool call not found");
|
|
199
|
-
call.args += chunk.delta.partial_json;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
controller.enqueue({ delta: { json: { usage } } });
|
|
203
|
-
if (toolCalls.length) {
|
|
204
|
-
controller.enqueue({
|
|
205
|
-
delta: {
|
|
206
|
-
json: {
|
|
207
|
-
toolCalls: toolCalls
|
|
208
|
-
.map(({ args, ...c }) => ({
|
|
209
|
-
...c,
|
|
210
|
-
function: {
|
|
211
|
-
...c.function,
|
|
212
|
-
// NOTE: claude may return a blank string for empty object (the tool's input schema is a empty object)
|
|
213
|
-
arguments: args.trim() ? (0, json_schema_js_1.parseJSON)(args) : {},
|
|
214
|
-
},
|
|
215
|
-
}))
|
|
216
|
-
.filter(type_utils_js_1.isNonNullable),
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
controller.close();
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
controller.error(error);
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
return streaming ? result : await (0, stream_utils_js_1.agentResponseStreamToObject)(result);
|
|
229
|
-
}
|
|
230
|
-
async requestStructuredOutput(body, responseFormat) {
|
|
231
|
-
if (responseFormat?.type !== "json_schema") {
|
|
232
|
-
throw new Error("Expected json_schema response format");
|
|
180
|
+
if (json !== undefined) {
|
|
181
|
+
yield { delta: { json: { json: json } } };
|
|
233
182
|
}
|
|
234
|
-
|
|
235
|
-
...body,
|
|
236
|
-
tools: [
|
|
237
|
-
{
|
|
238
|
-
name: "generate_json",
|
|
239
|
-
description: "Generate a json result by given context",
|
|
240
|
-
input_schema: responseFormat.jsonSchema.schema,
|
|
241
|
-
},
|
|
242
|
-
],
|
|
243
|
-
tool_choice: {
|
|
244
|
-
type: "tool",
|
|
245
|
-
name: "generate_json",
|
|
246
|
-
disable_parallel_tool_use: true,
|
|
247
|
-
},
|
|
248
|
-
stream: false,
|
|
249
|
-
});
|
|
250
|
-
const jsonTool = result.content.find((i) => i.type === "tool_use" && i.name === "generate_json");
|
|
251
|
-
if (!jsonTool)
|
|
252
|
-
throw new Error("Json tool not found");
|
|
253
|
-
return {
|
|
254
|
-
json: jsonTool.input,
|
|
255
|
-
model: result.model,
|
|
256
|
-
usage: {
|
|
257
|
-
inputTokens: result.usage.input_tokens,
|
|
258
|
-
outputTokens: result.usage.output_tokens,
|
|
259
|
-
},
|
|
260
|
-
};
|
|
183
|
+
yield { delta: { json: { usage } } };
|
|
261
184
|
}
|
|
262
185
|
}
|
|
263
186
|
exports.AnthropicChatModel = AnthropicChatModel;
|
|
264
|
-
|
|
265
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Parse cache configuration from model options
|
|
189
|
+
*/
|
|
190
|
+
function parseCacheConfig(modelOptions) {
|
|
191
|
+
const cacheConfig = modelOptions?.cacheConfig || {};
|
|
192
|
+
const shouldCache = cacheConfig.enabled !== false; // Default: enabled
|
|
193
|
+
const ttl = cacheConfig.ttl === "1h" ? "1h" : "5m"; // Default: 5m
|
|
194
|
+
const strategy = cacheConfig.strategy || "auto"; // Default: auto
|
|
195
|
+
const autoBreakpoints = {
|
|
196
|
+
tools: cacheConfig.autoBreakpoints?.tools !== false, // Default: true
|
|
197
|
+
system: cacheConfig.autoBreakpoints?.system !== false, // Default: true
|
|
198
|
+
lastMessage: cacheConfig.autoBreakpoints?.lastMessage === true, // Default: false
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
shouldCache,
|
|
202
|
+
ttl,
|
|
203
|
+
strategy,
|
|
204
|
+
autoBreakpoints,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function convertMessages({ messages, modelOptions }) {
|
|
208
|
+
const systemBlocks = [];
|
|
266
209
|
const msgs = [];
|
|
210
|
+
// Extract cache configuration with defaults
|
|
211
|
+
const { shouldCache, strategy, autoBreakpoints, ...cacheConfig } = parseCacheConfig(modelOptions);
|
|
212
|
+
const ttl = cacheConfig.ttl === "1h" ? "1h" : undefined;
|
|
267
213
|
for (const msg of messages) {
|
|
268
214
|
if (msg.role === "system") {
|
|
269
|
-
if (typeof msg.content
|
|
270
|
-
|
|
271
|
-
|
|
215
|
+
if (typeof msg.content === "string") {
|
|
216
|
+
const block = {
|
|
217
|
+
type: "text",
|
|
218
|
+
text: msg.content,
|
|
219
|
+
};
|
|
220
|
+
systemBlocks.push(block);
|
|
221
|
+
}
|
|
222
|
+
else if (Array.isArray(msg.content)) {
|
|
223
|
+
systemBlocks.push(...msg.content.map((item) => {
|
|
224
|
+
if (item.type !== "text")
|
|
225
|
+
throw new Error("System message only supports text content blocks");
|
|
226
|
+
return { type: "text", text: item.text };
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
throw new Error("System message must have string or array content");
|
|
231
|
+
}
|
|
272
232
|
}
|
|
273
233
|
else if (msg.role === "tool") {
|
|
274
234
|
if (!msg.toolCallId)
|
|
275
235
|
throw new Error("Tool message must have toolCallId");
|
|
276
|
-
if (
|
|
277
|
-
throw new Error("Tool message must have
|
|
236
|
+
if (!msg.content)
|
|
237
|
+
throw new Error("Tool message must have content");
|
|
278
238
|
msgs.push({
|
|
279
239
|
role: "user",
|
|
280
240
|
content: [
|
|
281
241
|
{
|
|
282
242
|
type: "tool_result",
|
|
283
243
|
tool_use_id: msg.toolCallId,
|
|
284
|
-
content: msg.content,
|
|
244
|
+
content: await convertContent(msg.content),
|
|
285
245
|
},
|
|
286
246
|
],
|
|
287
247
|
});
|
|
@@ -311,19 +271,60 @@ async function convertMessages({ messages, responseFormat, tools }) {
|
|
|
311
271
|
}
|
|
312
272
|
}
|
|
313
273
|
}
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
274
|
+
// Apply cache_control to the last system block if auto strategy is enabled
|
|
275
|
+
if (shouldCache && strategy === "auto") {
|
|
276
|
+
if (autoBreakpoints.system && systemBlocks.length > 0) {
|
|
277
|
+
const lastBlock = systemBlocks[systemBlocks.length - 1];
|
|
278
|
+
if (lastBlock) {
|
|
279
|
+
lastBlock.cache_control = { type: "ephemeral", ttl };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (autoBreakpoints.lastMessage) {
|
|
283
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
284
|
+
if (lastMsg) {
|
|
285
|
+
if (typeof lastMsg.content === "string") {
|
|
286
|
+
lastMsg.content = [
|
|
287
|
+
{ type: "text", text: lastMsg.content, cache_control: { type: "ephemeral", ttl } },
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
else if (Array.isArray(lastMsg.content)) {
|
|
291
|
+
const lastBlock = lastMsg.content[lastMsg.content.length - 1];
|
|
292
|
+
if (lastBlock &&
|
|
293
|
+
lastBlock.type !== "thinking" &&
|
|
294
|
+
lastBlock.type !== "redacted_thinking") {
|
|
295
|
+
lastBlock.cache_control = { type: "ephemeral", ttl };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Manual cache control: apply user-specified cacheControl from system messages
|
|
302
|
+
if (shouldCache && strategy === "manual") {
|
|
303
|
+
for (const [index, msg] of messages.entries()) {
|
|
304
|
+
const msgWithCache = msg;
|
|
305
|
+
if (msg.role === "system" && msgWithCache.cacheControl) {
|
|
306
|
+
const block = systemBlocks[index];
|
|
307
|
+
if (block) {
|
|
308
|
+
block.cache_control = {
|
|
309
|
+
type: msgWithCache.cacheControl.type,
|
|
310
|
+
...(msgWithCache.cacheControl.ttl && { ttl: msgWithCache.cacheControl.ttl }),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
318
315
|
}
|
|
319
|
-
const system = systemMessages.join("\n").trim() || undefined;
|
|
320
316
|
// Claude requires at least one message, so we add a system message if there are no messages
|
|
321
317
|
if (msgs.length === 0) {
|
|
322
|
-
if (
|
|
318
|
+
if (systemBlocks.length === 0)
|
|
323
319
|
throw new Error("No messages provided");
|
|
324
|
-
|
|
320
|
+
// Convert system blocks to a single user message
|
|
321
|
+
const systemText = systemBlocks.map((b) => b.text).join("\n");
|
|
322
|
+
return { messages: [{ role: "user", content: systemText }] };
|
|
325
323
|
}
|
|
326
|
-
return {
|
|
324
|
+
return {
|
|
325
|
+
messages: msgs,
|
|
326
|
+
system: systemBlocks.length > 0 ? systemBlocks : undefined,
|
|
327
|
+
};
|
|
327
328
|
}
|
|
328
329
|
async function convertContent(content) {
|
|
329
330
|
if (typeof content === "string")
|
|
@@ -348,38 +349,64 @@ async function convertContent(content) {
|
|
|
348
349
|
}
|
|
349
350
|
throw new Error("Invalid chat message content");
|
|
350
351
|
}
|
|
351
|
-
function convertTools({ tools, toolChoice, disableParallelToolUse, }) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
else if (toolChoice === "auto") {
|
|
364
|
-
choice = {
|
|
365
|
-
type: "auto",
|
|
366
|
-
disable_parallel_tool_use: disableParallelToolUse,
|
|
352
|
+
function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
|
|
353
|
+
// Extract cache configuration with defaults
|
|
354
|
+
const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
|
|
355
|
+
const shouldCacheTools = shouldCache && strategy === "auto" && autoBreakpoints.tools;
|
|
356
|
+
// Convert business tools
|
|
357
|
+
const convertedTools = (tools ?? []).map((i) => {
|
|
358
|
+
const tool = {
|
|
359
|
+
name: i.function.name,
|
|
360
|
+
description: i.function.description,
|
|
361
|
+
input_schema: (0, type_utils_js_1.isEmpty)(i.function.parameters)
|
|
362
|
+
? { type: "object" }
|
|
363
|
+
: i.function.parameters,
|
|
367
364
|
};
|
|
365
|
+
// Manual cache mode: apply tool-specific cacheControl
|
|
366
|
+
if (shouldCache && strategy === "manual" && i.cacheControl) {
|
|
367
|
+
tool.cache_control = {
|
|
368
|
+
type: i.cacheControl.type,
|
|
369
|
+
...(i.cacheControl.ttl && { ttl: i.cacheControl.ttl }),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return tool;
|
|
373
|
+
});
|
|
374
|
+
// Add output tool for structured output
|
|
375
|
+
if (responseFormat?.type === "json_schema") {
|
|
376
|
+
convertedTools.push({
|
|
377
|
+
name: OUTPUT_FUNCTION_NAME,
|
|
378
|
+
description: "Generate a json result by given context",
|
|
379
|
+
input_schema: responseFormat.jsonSchema.schema,
|
|
380
|
+
});
|
|
368
381
|
}
|
|
369
|
-
|
|
370
|
-
|
|
382
|
+
// Auto cache mode: add cache_control to the last tool
|
|
383
|
+
if (shouldCacheTools && convertedTools.length) {
|
|
384
|
+
const lastTool = convertedTools[convertedTools.length - 1];
|
|
385
|
+
if (lastTool) {
|
|
386
|
+
lastTool.cache_control = { type: "ephemeral", ...(ttl === "1h" && { ttl: "1h" }) };
|
|
387
|
+
}
|
|
371
388
|
}
|
|
389
|
+
// Determine tool choice
|
|
390
|
+
const choice = responseFormat?.type === "json_schema"
|
|
391
|
+
? // For structured output: force output tool if no business tools, otherwise let model choose
|
|
392
|
+
tools?.length
|
|
393
|
+
? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
|
|
394
|
+
: { type: "tool", name: OUTPUT_FUNCTION_NAME, disable_parallel_tool_use: true }
|
|
395
|
+
: typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function"
|
|
396
|
+
? {
|
|
397
|
+
type: "tool",
|
|
398
|
+
name: toolChoice.function.name,
|
|
399
|
+
disable_parallel_tool_use: disableParallelToolUse,
|
|
400
|
+
}
|
|
401
|
+
: toolChoice === "required"
|
|
402
|
+
? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
|
|
403
|
+
: toolChoice === "auto"
|
|
404
|
+
? { type: "auto", disable_parallel_tool_use: disableParallelToolUse }
|
|
405
|
+
: toolChoice === "none"
|
|
406
|
+
? { type: "none" }
|
|
407
|
+
: undefined;
|
|
372
408
|
return {
|
|
373
|
-
tools:
|
|
374
|
-
? tools.map((i) => ({
|
|
375
|
-
name: i.function.name,
|
|
376
|
-
description: i.function.description,
|
|
377
|
-
input_schema: (0, type_utils_js_1.isEmpty)(i.function.parameters)
|
|
378
|
-
? { type: "object" }
|
|
379
|
-
: i.function.parameters,
|
|
380
|
-
}))
|
|
381
|
-
: undefined,
|
|
409
|
+
tools: convertedTools.length ? convertedTools : undefined,
|
|
382
410
|
tool_choice: choice,
|
|
383
411
|
};
|
|
384
412
|
}
|
|
385
|
-
// safeParseJSON is now imported from @aigne/core
|
|
@@ -124,19 +124,22 @@ export declare class AnthropicChatModel extends ChatModel {
|
|
|
124
124
|
reasoningEffort?: number | "minimal" | "low" | "medium" | "high" | {
|
|
125
125
|
$get: string;
|
|
126
126
|
} | undefined;
|
|
127
|
+
cacheConfig?: import("@aigne/core").CacheConfig | {
|
|
128
|
+
$get: string;
|
|
129
|
+
} | undefined;
|
|
127
130
|
}> | undefined;
|
|
128
131
|
get credential(): {
|
|
129
132
|
apiKey: string | undefined;
|
|
130
133
|
model: string;
|
|
131
134
|
};
|
|
135
|
+
countTokens(input: ChatModelInput): Promise<number>;
|
|
136
|
+
private getMessageCreateParams;
|
|
132
137
|
private getMaxTokens;
|
|
133
138
|
/**
|
|
134
139
|
* Process the input using Claude's chat model
|
|
135
140
|
* @param input - The input to process
|
|
136
141
|
* @returns The processed output from the model
|
|
137
142
|
*/
|
|
138
|
-
process(input: ChatModelInput,
|
|
139
|
-
private
|
|
140
|
-
private extractResultFromAnthropicStream;
|
|
141
|
-
private requestStructuredOutput;
|
|
143
|
+
process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
144
|
+
private processInput;
|
|
142
145
|
}
|
|
@@ -124,19 +124,22 @@ export declare class AnthropicChatModel extends ChatModel {
|
|
|
124
124
|
reasoningEffort?: number | "minimal" | "low" | "medium" | "high" | {
|
|
125
125
|
$get: string;
|
|
126
126
|
} | undefined;
|
|
127
|
+
cacheConfig?: import("@aigne/core").CacheConfig | {
|
|
128
|
+
$get: string;
|
|
129
|
+
} | undefined;
|
|
127
130
|
}> | undefined;
|
|
128
131
|
get credential(): {
|
|
129
132
|
apiKey: string | undefined;
|
|
130
133
|
model: string;
|
|
131
134
|
};
|
|
135
|
+
countTokens(input: ChatModelInput): Promise<number>;
|
|
136
|
+
private getMessageCreateParams;
|
|
132
137
|
private getMaxTokens;
|
|
133
138
|
/**
|
|
134
139
|
* Process the input using Claude's chat model
|
|
135
140
|
* @param input - The input to process
|
|
136
141
|
* @returns The processed output from the model
|
|
137
142
|
*/
|
|
138
|
-
process(input: ChatModelInput,
|
|
139
|
-
private
|
|
140
|
-
private extractResultFromAnthropicStream;
|
|
141
|
-
private requestStructuredOutput;
|
|
143
|
+
process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
144
|
+
private processInput;
|
|
142
145
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { ChatModel,
|
|
1
|
+
import { ChatModel, } from "@aigne/core";
|
|
2
2
|
import { parseJSON } from "@aigne/core/utils/json-schema.js";
|
|
3
|
-
import {
|
|
4
|
-
import { mergeUsage } from "@aigne/core/utils/model-utils.js";
|
|
5
|
-
import { agentResponseStreamToObject } from "@aigne/core/utils/stream-utils.js";
|
|
6
|
-
import { checkArguments, isEmpty, isNonNullable, } from "@aigne/core/utils/type-utils.js";
|
|
3
|
+
import { checkArguments, isEmpty, isNonNullable, omit, } from "@aigne/core/utils/type-utils.js";
|
|
7
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
8
5
|
import { z } from "zod";
|
|
9
6
|
const CHAT_MODEL_CLAUDE_DEFAULT_MODEL = "claude-3-7-sonnet-latest";
|
|
7
|
+
const OUTPUT_FUNCTION_NAME = "generate_json";
|
|
10
8
|
/**
|
|
11
9
|
* @hidden
|
|
12
10
|
*/
|
|
@@ -76,6 +74,23 @@ export class AnthropicChatModel extends ChatModel {
|
|
|
76
74
|
model: this.options?.model || CHAT_MODEL_CLAUDE_DEFAULT_MODEL,
|
|
77
75
|
};
|
|
78
76
|
}
|
|
77
|
+
async countTokens(input) {
|
|
78
|
+
const request = await this.getMessageCreateParams(input);
|
|
79
|
+
return (await this.client.messages.countTokens(omit(request, "max_tokens"))).input_tokens;
|
|
80
|
+
}
|
|
81
|
+
async getMessageCreateParams(input) {
|
|
82
|
+
const { modelOptions = {} } = input;
|
|
83
|
+
const model = modelOptions.model || this.credential.model;
|
|
84
|
+
const disableParallelToolUse = modelOptions.parallelToolCalls === false;
|
|
85
|
+
return {
|
|
86
|
+
model,
|
|
87
|
+
temperature: modelOptions.temperature,
|
|
88
|
+
top_p: modelOptions.topP,
|
|
89
|
+
max_tokens: this.getMaxTokens(model),
|
|
90
|
+
...(await convertMessages(input)),
|
|
91
|
+
...convertTools({ ...input, disableParallelToolUse }),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
79
94
|
getMaxTokens(model) {
|
|
80
95
|
const matchers = [
|
|
81
96
|
[/claude-opus-4-/, 32000],
|
|
@@ -96,185 +111,130 @@ export class AnthropicChatModel extends ChatModel {
|
|
|
96
111
|
* @param input - The input to process
|
|
97
112
|
* @returns The processed output from the model
|
|
98
113
|
*/
|
|
99
|
-
process(input,
|
|
100
|
-
return this.
|
|
114
|
+
process(input, _options) {
|
|
115
|
+
return this.processInput(input);
|
|
101
116
|
}
|
|
102
|
-
async
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
async *processInput(input) {
|
|
118
|
+
const body = await this.getMessageCreateParams(input);
|
|
119
|
+
const stream = this.client.messages.stream({ ...body, stream: true });
|
|
120
|
+
const blocks = [];
|
|
121
|
+
let usage;
|
|
122
|
+
let json;
|
|
123
|
+
for await (const chunk of stream) {
|
|
124
|
+
if (chunk.type === "message_start") {
|
|
125
|
+
yield { delta: { json: { model: chunk.message.model } } };
|
|
126
|
+
const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
|
|
127
|
+
usage = {
|
|
128
|
+
inputTokens: input_tokens,
|
|
129
|
+
outputTokens: output_tokens,
|
|
130
|
+
cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
|
|
131
|
+
cacheReadInputTokens: cache_read_input_tokens ?? undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (chunk.type === "message_delta" && usage) {
|
|
135
|
+
usage.outputTokens = chunk.usage.output_tokens;
|
|
136
|
+
}
|
|
137
|
+
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
138
|
+
yield { delta: { text: { text: chunk.delta.text } } };
|
|
139
|
+
}
|
|
140
|
+
if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
|
|
141
|
+
blocks[chunk.index] = {
|
|
142
|
+
type: "function",
|
|
143
|
+
id: chunk.content_block.id,
|
|
144
|
+
function: { name: chunk.content_block.name, arguments: {} },
|
|
145
|
+
args: "",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
|
|
149
|
+
const call = blocks[chunk.index];
|
|
150
|
+
if (!call)
|
|
151
|
+
throw new Error("Tool call not found");
|
|
152
|
+
call.args += chunk.delta.partial_json;
|
|
153
|
+
}
|
|
119
154
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
const toolCalls = blocks.filter(isNonNullable);
|
|
156
|
+
// Separate output tool from business tool calls
|
|
157
|
+
const outputToolCall = toolCalls.find((c) => c.function.name === OUTPUT_FUNCTION_NAME);
|
|
158
|
+
const businessToolCalls = toolCalls
|
|
159
|
+
.filter((c) => c.function.name !== OUTPUT_FUNCTION_NAME)
|
|
160
|
+
.map(({ args, ...c }) => ({
|
|
161
|
+
...c,
|
|
162
|
+
function: {
|
|
163
|
+
...c.function,
|
|
164
|
+
arguments: args.trim() ? parseJSON(args) : {},
|
|
165
|
+
},
|
|
166
|
+
}))
|
|
167
|
+
.filter(isNonNullable);
|
|
168
|
+
if (outputToolCall) {
|
|
169
|
+
json = outputToolCall.args.trim() ? parseJSON(outputToolCall.args) : {};
|
|
126
170
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (result.toolCalls?.length)
|
|
130
|
-
return result;
|
|
131
|
-
// Try to parse the text response as JSON
|
|
132
|
-
// If it matches the json_schema, return it as json
|
|
133
|
-
const json = safeParseJSON(result.text || "");
|
|
134
|
-
const validated = this.validateJsonSchema(input.responseFormat.jsonSchema.schema, json, {
|
|
135
|
-
safe: true,
|
|
136
|
-
});
|
|
137
|
-
if (validated.success) {
|
|
138
|
-
return { ...result, json: validated.data, text: undefined };
|
|
171
|
+
if (businessToolCalls.length) {
|
|
172
|
+
yield { delta: { json: { toolCalls: businessToolCalls } } };
|
|
139
173
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// so we need to make a separate request for json_schema response when the tool calls is empty
|
|
143
|
-
const output = await this.requestStructuredOutput(body, input.responseFormat);
|
|
144
|
-
return {
|
|
145
|
-
...output,
|
|
146
|
-
// merge usage from both requests
|
|
147
|
-
usage: mergeUsage(result.usage, output.usage),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
async extractResultFromAnthropicStream(stream, streaming) {
|
|
151
|
-
const result = new ReadableStream({
|
|
152
|
-
async start(controller) {
|
|
153
|
-
try {
|
|
154
|
-
const toolCalls = [];
|
|
155
|
-
let usage;
|
|
156
|
-
let model;
|
|
157
|
-
for await (const chunk of stream) {
|
|
158
|
-
if (chunk.type === "message_start") {
|
|
159
|
-
if (!model) {
|
|
160
|
-
model = chunk.message.model;
|
|
161
|
-
controller.enqueue({ delta: { json: { model } } });
|
|
162
|
-
}
|
|
163
|
-
const { input_tokens, output_tokens } = chunk.message.usage;
|
|
164
|
-
usage = {
|
|
165
|
-
inputTokens: input_tokens,
|
|
166
|
-
outputTokens: output_tokens,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (chunk.type === "message_delta" && usage) {
|
|
170
|
-
usage.outputTokens = chunk.usage.output_tokens;
|
|
171
|
-
}
|
|
172
|
-
// handle streaming text
|
|
173
|
-
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
174
|
-
controller.enqueue({
|
|
175
|
-
delta: { text: { text: chunk.delta.text } },
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
|
|
179
|
-
toolCalls[chunk.index] = {
|
|
180
|
-
type: "function",
|
|
181
|
-
id: chunk.content_block.id,
|
|
182
|
-
function: {
|
|
183
|
-
name: chunk.content_block.name,
|
|
184
|
-
arguments: {},
|
|
185
|
-
},
|
|
186
|
-
args: "",
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
|
|
190
|
-
const call = toolCalls[chunk.index];
|
|
191
|
-
if (!call)
|
|
192
|
-
throw new Error("Tool call not found");
|
|
193
|
-
call.args += chunk.delta.partial_json;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
controller.enqueue({ delta: { json: { usage } } });
|
|
197
|
-
if (toolCalls.length) {
|
|
198
|
-
controller.enqueue({
|
|
199
|
-
delta: {
|
|
200
|
-
json: {
|
|
201
|
-
toolCalls: toolCalls
|
|
202
|
-
.map(({ args, ...c }) => ({
|
|
203
|
-
...c,
|
|
204
|
-
function: {
|
|
205
|
-
...c.function,
|
|
206
|
-
// NOTE: claude may return a blank string for empty object (the tool's input schema is a empty object)
|
|
207
|
-
arguments: args.trim() ? parseJSON(args) : {},
|
|
208
|
-
},
|
|
209
|
-
}))
|
|
210
|
-
.filter(isNonNullable),
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
controller.close();
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
controller.error(error);
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
return streaming ? result : await agentResponseStreamToObject(result);
|
|
223
|
-
}
|
|
224
|
-
async requestStructuredOutput(body, responseFormat) {
|
|
225
|
-
if (responseFormat?.type !== "json_schema") {
|
|
226
|
-
throw new Error("Expected json_schema response format");
|
|
174
|
+
if (json !== undefined) {
|
|
175
|
+
yield { delta: { json: { json: json } } };
|
|
227
176
|
}
|
|
228
|
-
|
|
229
|
-
...body,
|
|
230
|
-
tools: [
|
|
231
|
-
{
|
|
232
|
-
name: "generate_json",
|
|
233
|
-
description: "Generate a json result by given context",
|
|
234
|
-
input_schema: responseFormat.jsonSchema.schema,
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
tool_choice: {
|
|
238
|
-
type: "tool",
|
|
239
|
-
name: "generate_json",
|
|
240
|
-
disable_parallel_tool_use: true,
|
|
241
|
-
},
|
|
242
|
-
stream: false,
|
|
243
|
-
});
|
|
244
|
-
const jsonTool = result.content.find((i) => i.type === "tool_use" && i.name === "generate_json");
|
|
245
|
-
if (!jsonTool)
|
|
246
|
-
throw new Error("Json tool not found");
|
|
247
|
-
return {
|
|
248
|
-
json: jsonTool.input,
|
|
249
|
-
model: result.model,
|
|
250
|
-
usage: {
|
|
251
|
-
inputTokens: result.usage.input_tokens,
|
|
252
|
-
outputTokens: result.usage.output_tokens,
|
|
253
|
-
},
|
|
254
|
-
};
|
|
177
|
+
yield { delta: { json: { usage } } };
|
|
255
178
|
}
|
|
256
179
|
}
|
|
257
|
-
|
|
258
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Parse cache configuration from model options
|
|
182
|
+
*/
|
|
183
|
+
function parseCacheConfig(modelOptions) {
|
|
184
|
+
const cacheConfig = modelOptions?.cacheConfig || {};
|
|
185
|
+
const shouldCache = cacheConfig.enabled !== false; // Default: enabled
|
|
186
|
+
const ttl = cacheConfig.ttl === "1h" ? "1h" : "5m"; // Default: 5m
|
|
187
|
+
const strategy = cacheConfig.strategy || "auto"; // Default: auto
|
|
188
|
+
const autoBreakpoints = {
|
|
189
|
+
tools: cacheConfig.autoBreakpoints?.tools !== false, // Default: true
|
|
190
|
+
system: cacheConfig.autoBreakpoints?.system !== false, // Default: true
|
|
191
|
+
lastMessage: cacheConfig.autoBreakpoints?.lastMessage === true, // Default: false
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
shouldCache,
|
|
195
|
+
ttl,
|
|
196
|
+
strategy,
|
|
197
|
+
autoBreakpoints,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function convertMessages({ messages, modelOptions }) {
|
|
201
|
+
const systemBlocks = [];
|
|
259
202
|
const msgs = [];
|
|
203
|
+
// Extract cache configuration with defaults
|
|
204
|
+
const { shouldCache, strategy, autoBreakpoints, ...cacheConfig } = parseCacheConfig(modelOptions);
|
|
205
|
+
const ttl = cacheConfig.ttl === "1h" ? "1h" : undefined;
|
|
260
206
|
for (const msg of messages) {
|
|
261
207
|
if (msg.role === "system") {
|
|
262
|
-
if (typeof msg.content
|
|
263
|
-
|
|
264
|
-
|
|
208
|
+
if (typeof msg.content === "string") {
|
|
209
|
+
const block = {
|
|
210
|
+
type: "text",
|
|
211
|
+
text: msg.content,
|
|
212
|
+
};
|
|
213
|
+
systemBlocks.push(block);
|
|
214
|
+
}
|
|
215
|
+
else if (Array.isArray(msg.content)) {
|
|
216
|
+
systemBlocks.push(...msg.content.map((item) => {
|
|
217
|
+
if (item.type !== "text")
|
|
218
|
+
throw new Error("System message only supports text content blocks");
|
|
219
|
+
return { type: "text", text: item.text };
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
throw new Error("System message must have string or array content");
|
|
224
|
+
}
|
|
265
225
|
}
|
|
266
226
|
else if (msg.role === "tool") {
|
|
267
227
|
if (!msg.toolCallId)
|
|
268
228
|
throw new Error("Tool message must have toolCallId");
|
|
269
|
-
if (
|
|
270
|
-
throw new Error("Tool message must have
|
|
229
|
+
if (!msg.content)
|
|
230
|
+
throw new Error("Tool message must have content");
|
|
271
231
|
msgs.push({
|
|
272
232
|
role: "user",
|
|
273
233
|
content: [
|
|
274
234
|
{
|
|
275
235
|
type: "tool_result",
|
|
276
236
|
tool_use_id: msg.toolCallId,
|
|
277
|
-
content: msg.content,
|
|
237
|
+
content: await convertContent(msg.content),
|
|
278
238
|
},
|
|
279
239
|
],
|
|
280
240
|
});
|
|
@@ -304,19 +264,60 @@ async function convertMessages({ messages, responseFormat, tools }) {
|
|
|
304
264
|
}
|
|
305
265
|
}
|
|
306
266
|
}
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
267
|
+
// Apply cache_control to the last system block if auto strategy is enabled
|
|
268
|
+
if (shouldCache && strategy === "auto") {
|
|
269
|
+
if (autoBreakpoints.system && systemBlocks.length > 0) {
|
|
270
|
+
const lastBlock = systemBlocks[systemBlocks.length - 1];
|
|
271
|
+
if (lastBlock) {
|
|
272
|
+
lastBlock.cache_control = { type: "ephemeral", ttl };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (autoBreakpoints.lastMessage) {
|
|
276
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
277
|
+
if (lastMsg) {
|
|
278
|
+
if (typeof lastMsg.content === "string") {
|
|
279
|
+
lastMsg.content = [
|
|
280
|
+
{ type: "text", text: lastMsg.content, cache_control: { type: "ephemeral", ttl } },
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
else if (Array.isArray(lastMsg.content)) {
|
|
284
|
+
const lastBlock = lastMsg.content[lastMsg.content.length - 1];
|
|
285
|
+
if (lastBlock &&
|
|
286
|
+
lastBlock.type !== "thinking" &&
|
|
287
|
+
lastBlock.type !== "redacted_thinking") {
|
|
288
|
+
lastBlock.cache_control = { type: "ephemeral", ttl };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Manual cache control: apply user-specified cacheControl from system messages
|
|
295
|
+
if (shouldCache && strategy === "manual") {
|
|
296
|
+
for (const [index, msg] of messages.entries()) {
|
|
297
|
+
const msgWithCache = msg;
|
|
298
|
+
if (msg.role === "system" && msgWithCache.cacheControl) {
|
|
299
|
+
const block = systemBlocks[index];
|
|
300
|
+
if (block) {
|
|
301
|
+
block.cache_control = {
|
|
302
|
+
type: msgWithCache.cacheControl.type,
|
|
303
|
+
...(msgWithCache.cacheControl.ttl && { ttl: msgWithCache.cacheControl.ttl }),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
311
308
|
}
|
|
312
|
-
const system = systemMessages.join("\n").trim() || undefined;
|
|
313
309
|
// Claude requires at least one message, so we add a system message if there are no messages
|
|
314
310
|
if (msgs.length === 0) {
|
|
315
|
-
if (
|
|
311
|
+
if (systemBlocks.length === 0)
|
|
316
312
|
throw new Error("No messages provided");
|
|
317
|
-
|
|
313
|
+
// Convert system blocks to a single user message
|
|
314
|
+
const systemText = systemBlocks.map((b) => b.text).join("\n");
|
|
315
|
+
return { messages: [{ role: "user", content: systemText }] };
|
|
318
316
|
}
|
|
319
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
messages: msgs,
|
|
319
|
+
system: systemBlocks.length > 0 ? systemBlocks : undefined,
|
|
320
|
+
};
|
|
320
321
|
}
|
|
321
322
|
async function convertContent(content) {
|
|
322
323
|
if (typeof content === "string")
|
|
@@ -341,38 +342,64 @@ async function convertContent(content) {
|
|
|
341
342
|
}
|
|
342
343
|
throw new Error("Invalid chat message content");
|
|
343
344
|
}
|
|
344
|
-
function convertTools({ tools, toolChoice, disableParallelToolUse, }) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
else if (toolChoice === "auto") {
|
|
357
|
-
choice = {
|
|
358
|
-
type: "auto",
|
|
359
|
-
disable_parallel_tool_use: disableParallelToolUse,
|
|
345
|
+
function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
|
|
346
|
+
// Extract cache configuration with defaults
|
|
347
|
+
const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
|
|
348
|
+
const shouldCacheTools = shouldCache && strategy === "auto" && autoBreakpoints.tools;
|
|
349
|
+
// Convert business tools
|
|
350
|
+
const convertedTools = (tools ?? []).map((i) => {
|
|
351
|
+
const tool = {
|
|
352
|
+
name: i.function.name,
|
|
353
|
+
description: i.function.description,
|
|
354
|
+
input_schema: isEmpty(i.function.parameters)
|
|
355
|
+
? { type: "object" }
|
|
356
|
+
: i.function.parameters,
|
|
360
357
|
};
|
|
358
|
+
// Manual cache mode: apply tool-specific cacheControl
|
|
359
|
+
if (shouldCache && strategy === "manual" && i.cacheControl) {
|
|
360
|
+
tool.cache_control = {
|
|
361
|
+
type: i.cacheControl.type,
|
|
362
|
+
...(i.cacheControl.ttl && { ttl: i.cacheControl.ttl }),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return tool;
|
|
366
|
+
});
|
|
367
|
+
// Add output tool for structured output
|
|
368
|
+
if (responseFormat?.type === "json_schema") {
|
|
369
|
+
convertedTools.push({
|
|
370
|
+
name: OUTPUT_FUNCTION_NAME,
|
|
371
|
+
description: "Generate a json result by given context",
|
|
372
|
+
input_schema: responseFormat.jsonSchema.schema,
|
|
373
|
+
});
|
|
361
374
|
}
|
|
362
|
-
|
|
363
|
-
|
|
375
|
+
// Auto cache mode: add cache_control to the last tool
|
|
376
|
+
if (shouldCacheTools && convertedTools.length) {
|
|
377
|
+
const lastTool = convertedTools[convertedTools.length - 1];
|
|
378
|
+
if (lastTool) {
|
|
379
|
+
lastTool.cache_control = { type: "ephemeral", ...(ttl === "1h" && { ttl: "1h" }) };
|
|
380
|
+
}
|
|
364
381
|
}
|
|
382
|
+
// Determine tool choice
|
|
383
|
+
const choice = responseFormat?.type === "json_schema"
|
|
384
|
+
? // For structured output: force output tool if no business tools, otherwise let model choose
|
|
385
|
+
tools?.length
|
|
386
|
+
? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
|
|
387
|
+
: { type: "tool", name: OUTPUT_FUNCTION_NAME, disable_parallel_tool_use: true }
|
|
388
|
+
: typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function"
|
|
389
|
+
? {
|
|
390
|
+
type: "tool",
|
|
391
|
+
name: toolChoice.function.name,
|
|
392
|
+
disable_parallel_tool_use: disableParallelToolUse,
|
|
393
|
+
}
|
|
394
|
+
: toolChoice === "required"
|
|
395
|
+
? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
|
|
396
|
+
: toolChoice === "auto"
|
|
397
|
+
? { type: "auto", disable_parallel_tool_use: disableParallelToolUse }
|
|
398
|
+
: toolChoice === "none"
|
|
399
|
+
? { type: "none" }
|
|
400
|
+
: undefined;
|
|
365
401
|
return {
|
|
366
|
-
tools:
|
|
367
|
-
? tools.map((i) => ({
|
|
368
|
-
name: i.function.name,
|
|
369
|
-
description: i.function.description,
|
|
370
|
-
input_schema: isEmpty(i.function.parameters)
|
|
371
|
-
? { type: "object" }
|
|
372
|
-
: i.function.parameters,
|
|
373
|
-
}))
|
|
374
|
-
: undefined,
|
|
402
|
+
tools: convertedTools.length ? convertedTools : undefined,
|
|
375
403
|
tool_choice: choice,
|
|
376
404
|
};
|
|
377
405
|
}
|
|
378
|
-
// safeParseJSON is now imported from @aigne/core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/anthropic",
|
|
3
|
-
"version": "0.14.16-beta.
|
|
3
|
+
"version": "0.14.16-beta.20",
|
|
4
4
|
"description": "AIGNE Anthropic SDK for integrating with Claude AI models",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@anthropic-ai/sdk": "^0.63.0",
|
|
39
39
|
"zod": "^3.25.67",
|
|
40
|
-
"@aigne/core": "^1.72.0-beta.
|
|
41
|
-
"@aigne/platform-helpers": "^0.6.7-beta"
|
|
40
|
+
"@aigne/core": "^1.72.0-beta.18",
|
|
41
|
+
"@aigne/platform-helpers": "^0.6.7-beta.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/bun": "^1.2.22",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"npm-run-all": "^4.1.5",
|
|
47
47
|
"rimraf": "^6.0.1",
|
|
48
48
|
"typescript": "^5.9.2",
|
|
49
|
-
"@aigne/test-utils": "^0.5.69-beta.
|
|
49
|
+
"@aigne/test-utils": "^0.5.69-beta.18"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"lint": "tsc --noEmit",
|