@aigne/core 1.12.0 → 1.13.0
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 +13 -0
- package/lib/cjs/loader/index.js +2 -0
- package/lib/cjs/models/bedrock-chat-model.d.ts +70 -0
- package/lib/cjs/models/bedrock-chat-model.js +273 -0
- package/lib/cjs/models/chat-model.d.ts +1 -0
- package/lib/cjs/models/chat-model.js +8 -0
- package/lib/cjs/models/gemini-chat-model.d.ts +1 -0
- package/lib/cjs/models/gemini-chat-model.js +1 -0
- package/lib/cjs/models/openai-chat-model.d.ts +3 -0
- package/lib/cjs/models/openai-chat-model.js +100 -100
- package/lib/cjs/prompt/prompt-builder.js +2 -2
- package/lib/cjs/utils/prompts.js +1 -1
- package/lib/cjs/utils/type-utils.d.ts +1 -0
- package/lib/cjs/utils/type-utils.js +12 -0
- package/lib/dts/models/bedrock-chat-model.d.ts +70 -0
- package/lib/dts/models/chat-model.d.ts +1 -0
- package/lib/dts/models/gemini-chat-model.d.ts +1 -0
- package/lib/dts/models/openai-chat-model.d.ts +3 -0
- package/lib/dts/utils/type-utils.d.ts +1 -0
- package/lib/esm/loader/index.js +2 -0
- package/lib/esm/models/bedrock-chat-model.d.ts +70 -0
- package/lib/esm/models/bedrock-chat-model.js +268 -0
- package/lib/esm/models/chat-model.d.ts +1 -0
- package/lib/esm/models/chat-model.js +8 -0
- package/lib/esm/models/gemini-chat-model.d.ts +1 -0
- package/lib/esm/models/gemini-chat-model.js +1 -0
- package/lib/esm/models/openai-chat-model.d.ts +3 -0
- package/lib/esm/models/openai-chat-model.js +100 -100
- package/lib/esm/prompt/prompt-builder.js +3 -3
- package/lib/esm/utils/prompts.js +1 -1
- package/lib/esm/utils/type-utils.d.ts +1 -0
- package/lib/esm/utils/type-utils.js +11 -0
- package/package.json +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,19 @@
|
|
|
22
22
|
* rename @aigne/core-next to @aigne/core ([3a81009](https://github.com/AIGNE-io/aigne-framework/commit/3a8100962c81813217b687ae28e8de604419c622))
|
|
23
23
|
* use text resource from MCP correctly ([8b9eba8](https://github.com/AIGNE-io/aigne-framework/commit/8b9eba83352ec096a2a5d4f410d4c4bde7420bce))
|
|
24
24
|
|
|
25
|
+
## [1.13.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.12.0...core-v1.13.0) (2025-04-30)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* **core:** add BedrockChatModel support ([#101](https://github.com/AIGNE-io/aigne-framework/issues/101)) ([a0b98f0](https://github.com/AIGNE-io/aigne-framework/commit/a0b98f01bd78a135232226548848fa35a64982d1))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* **core:** deduplicate tools for chat model ([#103](https://github.com/AIGNE-io/aigne-framework/issues/103)) ([570be6d](https://github.com/AIGNE-io/aigne-framework/commit/570be6d8620ab5b9a0149f835ecd4641009a8654))
|
|
36
|
+
* export server/client api types ([93e5341](https://github.com/AIGNE-io/aigne-framework/commit/93e5341dde7a6851f08a3d4e2f6c1a1db91765e9))
|
|
37
|
+
|
|
25
38
|
## [1.12.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.11.0...core-v1.12.0) (2025-04-27)
|
|
26
39
|
|
|
27
40
|
|
package/lib/cjs/loader/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const zod_1 = require("zod");
|
|
|
11
11
|
const agent_js_1 = require("../agents/agent.js");
|
|
12
12
|
const ai_agent_js_1 = require("../agents/ai-agent.js");
|
|
13
13
|
const mcp_agent_js_1 = require("../agents/mcp-agent.js");
|
|
14
|
+
const bedrock_chat_model_js_1 = require("../models/bedrock-chat-model.js");
|
|
14
15
|
const claude_chat_model_js_1 = require("../models/claude-chat-model.js");
|
|
15
16
|
const deepseek_chat_model_js_1 = require("../models/deepseek-chat-model.js");
|
|
16
17
|
const gemini_chat_model_js_1 = require("../models/gemini-chat-model.js");
|
|
@@ -86,6 +87,7 @@ async function loadModel(model, modelOptions) {
|
|
|
86
87
|
deepseek_chat_model_js_1.DeepSeekChatModel,
|
|
87
88
|
open_router_chat_model_js_1.OpenRouterChatModel,
|
|
88
89
|
ollama_chat_model_js_1.OllamaChatModel,
|
|
90
|
+
bedrock_chat_model_js_1.BedrockChatModel,
|
|
89
91
|
];
|
|
90
92
|
const M = availableModels.find((m) => m.name
|
|
91
93
|
.toLowerCase()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { AgentInvokeOptions, AgentResponse } from "../agents/agent.js";
|
|
4
|
+
import type { Context } from "../aigne/context.js";
|
|
5
|
+
import { ChatModel, type ChatModelInput, type ChatModelOptions, type ChatModelOutput } from "./chat-model.js";
|
|
6
|
+
export declare function extractLastJsonObject(text: string): string | null;
|
|
7
|
+
export interface BedrockChatModelOptions {
|
|
8
|
+
accessKeyId?: string;
|
|
9
|
+
secretAccessKey?: string;
|
|
10
|
+
region?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
modelOptions?: ChatModelOptions;
|
|
13
|
+
}
|
|
14
|
+
export declare const bedrockChatModelOptionsSchema: z.ZodObject<{
|
|
15
|
+
region: z.ZodOptional<z.ZodString>;
|
|
16
|
+
model: z.ZodOptional<z.ZodString>;
|
|
17
|
+
modelOptions: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
model: z.ZodOptional<z.ZodString>;
|
|
19
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
topP: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
frequencyPenalty: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
presencePenalty: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
parallelToolCalls: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
parallelToolCalls: boolean;
|
|
26
|
+
model?: string | undefined;
|
|
27
|
+
temperature?: number | undefined;
|
|
28
|
+
topP?: number | undefined;
|
|
29
|
+
frequencyPenalty?: number | undefined;
|
|
30
|
+
presencePenalty?: number | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
model?: string | undefined;
|
|
33
|
+
temperature?: number | undefined;
|
|
34
|
+
topP?: number | undefined;
|
|
35
|
+
frequencyPenalty?: number | undefined;
|
|
36
|
+
presencePenalty?: number | undefined;
|
|
37
|
+
parallelToolCalls?: boolean | undefined;
|
|
38
|
+
}>>;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
modelOptions?: {
|
|
41
|
+
parallelToolCalls: boolean;
|
|
42
|
+
model?: string | undefined;
|
|
43
|
+
temperature?: number | undefined;
|
|
44
|
+
topP?: number | undefined;
|
|
45
|
+
frequencyPenalty?: number | undefined;
|
|
46
|
+
presencePenalty?: number | undefined;
|
|
47
|
+
} | undefined;
|
|
48
|
+
model?: string | undefined;
|
|
49
|
+
region?: string | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
modelOptions?: {
|
|
52
|
+
model?: string | undefined;
|
|
53
|
+
temperature?: number | undefined;
|
|
54
|
+
topP?: number | undefined;
|
|
55
|
+
frequencyPenalty?: number | undefined;
|
|
56
|
+
presencePenalty?: number | undefined;
|
|
57
|
+
parallelToolCalls?: boolean | undefined;
|
|
58
|
+
} | undefined;
|
|
59
|
+
model?: string | undefined;
|
|
60
|
+
region?: string | undefined;
|
|
61
|
+
}>;
|
|
62
|
+
export declare class BedrockChatModel extends ChatModel {
|
|
63
|
+
options?: BedrockChatModelOptions | undefined;
|
|
64
|
+
constructor(options?: BedrockChatModelOptions | undefined);
|
|
65
|
+
protected _client?: BedrockRuntimeClient;
|
|
66
|
+
get client(): BedrockRuntimeClient;
|
|
67
|
+
get modelOptions(): ChatModelOptions | undefined;
|
|
68
|
+
process(input: ChatModelInput, _context: Context, options?: AgentInvokeOptions): Promise<AgentResponse<ChatModelOutput>>;
|
|
69
|
+
private extractResultFromStream;
|
|
70
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BedrockChatModel = exports.bedrockChatModelOptionsSchema = void 0;
|
|
4
|
+
exports.extractLastJsonObject = extractLastJsonObject;
|
|
5
|
+
const client_bedrock_runtime_1 = require("@aws-sdk/client-bedrock-runtime");
|
|
6
|
+
const nanoid_1 = require("nanoid");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const json_schema_js_1 = require("../utils/json-schema.js");
|
|
9
|
+
const prompts_js_1 = require("../utils/prompts.js");
|
|
10
|
+
const stream_utils_js_1 = require("../utils/stream-utils.js");
|
|
11
|
+
const type_utils_js_1 = require("../utils/type-utils.js");
|
|
12
|
+
const chat_model_js_1 = require("./chat-model.js");
|
|
13
|
+
function extractLastJsonObject(text) {
|
|
14
|
+
return text.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").trim();
|
|
15
|
+
}
|
|
16
|
+
const BEDROCK_DEFAULT_CHAT_MODEL = "us.amazon.nova-lite-v1:0";
|
|
17
|
+
exports.bedrockChatModelOptionsSchema = zod_1.z.object({
|
|
18
|
+
region: zod_1.z.string().optional(),
|
|
19
|
+
model: zod_1.z.string().optional(),
|
|
20
|
+
modelOptions: zod_1.z
|
|
21
|
+
.object({
|
|
22
|
+
model: zod_1.z.string().optional(),
|
|
23
|
+
temperature: zod_1.z.number().optional(),
|
|
24
|
+
topP: zod_1.z.number().optional(),
|
|
25
|
+
frequencyPenalty: zod_1.z.number().optional(),
|
|
26
|
+
presencePenalty: zod_1.z.number().optional(),
|
|
27
|
+
parallelToolCalls: zod_1.z.boolean().optional().default(true),
|
|
28
|
+
})
|
|
29
|
+
.optional(),
|
|
30
|
+
});
|
|
31
|
+
class BedrockChatModel extends chat_model_js_1.ChatModel {
|
|
32
|
+
options;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
if (options)
|
|
35
|
+
(0, type_utils_js_1.checkArguments)("BedrockChatModel", exports.bedrockChatModelOptionsSchema, options);
|
|
36
|
+
super();
|
|
37
|
+
this.options = options;
|
|
38
|
+
}
|
|
39
|
+
_client;
|
|
40
|
+
get client() {
|
|
41
|
+
const credentials = this.options?.accessKeyId && this.options?.secretAccessKey
|
|
42
|
+
? {
|
|
43
|
+
accessKeyId: this.options.accessKeyId,
|
|
44
|
+
secretAccessKey: this.options.secretAccessKey,
|
|
45
|
+
}
|
|
46
|
+
: undefined;
|
|
47
|
+
this._client ??= new client_bedrock_runtime_1.BedrockRuntimeClient({
|
|
48
|
+
region: this.options?.region,
|
|
49
|
+
credentials,
|
|
50
|
+
});
|
|
51
|
+
return this._client;
|
|
52
|
+
}
|
|
53
|
+
get modelOptions() {
|
|
54
|
+
return this.options?.modelOptions;
|
|
55
|
+
}
|
|
56
|
+
async process(input, _context, options) {
|
|
57
|
+
const modelId = input.modelOptions?.model ?? this.modelOptions?.model ?? BEDROCK_DEFAULT_CHAT_MODEL;
|
|
58
|
+
const { messages, system } = getRunMessages(input);
|
|
59
|
+
const toolConfig = convertTools(input);
|
|
60
|
+
const body = {
|
|
61
|
+
modelId,
|
|
62
|
+
messages,
|
|
63
|
+
system,
|
|
64
|
+
toolConfig,
|
|
65
|
+
inferenceConfig: {
|
|
66
|
+
temperature: input.modelOptions?.temperature ?? this.modelOptions?.temperature,
|
|
67
|
+
topP: input.modelOptions?.topP ?? this.modelOptions?.topP,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const command = new client_bedrock_runtime_1.ConverseStreamCommand(body);
|
|
71
|
+
const response = await this.client.send(command);
|
|
72
|
+
const jsonMode = input.responseFormat?.type === "json_schema";
|
|
73
|
+
if (options?.streaming && !jsonMode) {
|
|
74
|
+
return this.extractResultFromStream(response.stream, modelId, false, true);
|
|
75
|
+
}
|
|
76
|
+
const result = await this.extractResultFromStream(response.stream, modelId, jsonMode, false);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async extractResultFromStream(stream, modelId, jsonMode, streaming) {
|
|
80
|
+
if (!stream)
|
|
81
|
+
throw new Error("Unable to get AI model response.");
|
|
82
|
+
const result = new ReadableStream({
|
|
83
|
+
start: async (controller) => {
|
|
84
|
+
try {
|
|
85
|
+
controller.enqueue({ delta: { json: { model: modelId } } });
|
|
86
|
+
const toolCalls = [];
|
|
87
|
+
let text = "";
|
|
88
|
+
let usage;
|
|
89
|
+
for await (const chunk of stream) {
|
|
90
|
+
if (chunk.contentBlockStart?.start?.toolUse) {
|
|
91
|
+
const toolUse = chunk.contentBlockStart.start.toolUse;
|
|
92
|
+
if (!toolUse.name)
|
|
93
|
+
throw new Error("Tool use is invalid");
|
|
94
|
+
if (chunk.contentBlockStart.contentBlockIndex === undefined)
|
|
95
|
+
throw new Error("Tool use content block index is required");
|
|
96
|
+
toolCalls[chunk.contentBlockStart.contentBlockIndex] = {
|
|
97
|
+
type: "function",
|
|
98
|
+
id: toolUse.toolUseId || (0, nanoid_1.nanoid)(),
|
|
99
|
+
function: {
|
|
100
|
+
name: toolUse.name,
|
|
101
|
+
arguments: {},
|
|
102
|
+
},
|
|
103
|
+
args: "",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (chunk.contentBlockDelta) {
|
|
107
|
+
const block = chunk.contentBlockDelta;
|
|
108
|
+
const delta = block.delta;
|
|
109
|
+
if (delta?.text) {
|
|
110
|
+
text += delta.text;
|
|
111
|
+
if (!jsonMode) {
|
|
112
|
+
controller.enqueue({ delta: { text: { text: delta.text } } });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (delta?.toolUse) {
|
|
116
|
+
if (block.contentBlockIndex === undefined)
|
|
117
|
+
throw new Error("Content block index is required");
|
|
118
|
+
const call = toolCalls[block.contentBlockIndex];
|
|
119
|
+
if (!call)
|
|
120
|
+
throw new Error("Tool call not found");
|
|
121
|
+
call.args += delta.toolUse.input;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (chunk.metadata) {
|
|
125
|
+
usage = chunk.metadata.usage;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (jsonMode && text) {
|
|
129
|
+
const match = extractLastJsonObject(text);
|
|
130
|
+
if (!match)
|
|
131
|
+
throw new Error("Failed to extract JSON object from model output");
|
|
132
|
+
controller.enqueue({
|
|
133
|
+
delta: { json: { json: (0, json_schema_js_1.parseJSON)(match) } },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (toolCalls.length) {
|
|
137
|
+
controller.enqueue({
|
|
138
|
+
delta: {
|
|
139
|
+
json: {
|
|
140
|
+
toolCalls: toolCalls
|
|
141
|
+
.map(({ args, ...c }) => ({
|
|
142
|
+
...c,
|
|
143
|
+
function: { ...c.function, arguments: (0, json_schema_js_1.parseJSON)(args) },
|
|
144
|
+
}))
|
|
145
|
+
.filter(type_utils_js_1.isNonNullable),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
controller.enqueue({ delta: { json: { usage } } });
|
|
151
|
+
controller.close();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
controller.error(error);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
return streaming ? result : await (0, stream_utils_js_1.agentResponseStreamToObject)(result);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.BedrockChatModel = BedrockChatModel;
|
|
162
|
+
const getRunMessages = ({ messages: msgs, responseFormat, }) => {
|
|
163
|
+
const system = [];
|
|
164
|
+
const messages = [];
|
|
165
|
+
for (const msg of msgs) {
|
|
166
|
+
if (msg.role === "system") {
|
|
167
|
+
if (typeof msg.content !== "string")
|
|
168
|
+
throw new Error("System message must have content");
|
|
169
|
+
system.push({ text: msg.content });
|
|
170
|
+
}
|
|
171
|
+
else if (msg.role === "tool") {
|
|
172
|
+
if (!msg.toolCallId)
|
|
173
|
+
throw new Error("Tool message must have toolCallId");
|
|
174
|
+
if (typeof msg.content !== "string")
|
|
175
|
+
throw new Error("Tool message must have string content");
|
|
176
|
+
if (messages.at(-1)?.role === "user") {
|
|
177
|
+
messages.at(-1)?.content?.push({
|
|
178
|
+
toolResult: { toolUseId: msg.toolCallId, content: [{ json: (0, json_schema_js_1.parseJSON)(msg.content) }] },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
messages.push({
|
|
183
|
+
role: "user",
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
toolResult: {
|
|
187
|
+
toolUseId: msg.toolCallId,
|
|
188
|
+
content: [{ json: (0, json_schema_js_1.parseJSON)(msg.content) }],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (msg.role === "user") {
|
|
196
|
+
if (!msg.content)
|
|
197
|
+
throw new Error("User message must have content");
|
|
198
|
+
messages.push({ role: "user", content: convertContent(msg.content) });
|
|
199
|
+
}
|
|
200
|
+
else if (msg.role === "agent") {
|
|
201
|
+
if (msg.toolCalls?.length) {
|
|
202
|
+
messages.push({
|
|
203
|
+
role: "assistant",
|
|
204
|
+
content: msg.toolCalls.map((i) => ({
|
|
205
|
+
toolUse: {
|
|
206
|
+
toolUseId: i.id,
|
|
207
|
+
name: i.function.name,
|
|
208
|
+
input: i.function.arguments,
|
|
209
|
+
},
|
|
210
|
+
})),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else if (msg.content) {
|
|
214
|
+
messages.push({ role: "assistant", content: convertContent(msg.content) });
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
throw new Error("Agent message must have content or toolCalls");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (messages.at(0)?.role !== "user") {
|
|
222
|
+
messages.unshift({ role: "user", content: [{ text: "." }] });
|
|
223
|
+
}
|
|
224
|
+
if (responseFormat?.type === "json_schema") {
|
|
225
|
+
system.push({
|
|
226
|
+
text: (0, prompts_js_1.getJsonOutputPrompt)(responseFormat.jsonSchema.schema),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return { messages, system };
|
|
230
|
+
};
|
|
231
|
+
function convertContent(content) {
|
|
232
|
+
if (typeof content === "string")
|
|
233
|
+
return [{ text: content }];
|
|
234
|
+
if (Array.isArray(content)) {
|
|
235
|
+
const blocks = [];
|
|
236
|
+
for (const item of content) {
|
|
237
|
+
if (item.type === "text")
|
|
238
|
+
blocks.push({ text: item.text });
|
|
239
|
+
}
|
|
240
|
+
return blocks;
|
|
241
|
+
}
|
|
242
|
+
throw new Error("Invalid chat message content");
|
|
243
|
+
}
|
|
244
|
+
function convertTools({ tools, toolChoice }) {
|
|
245
|
+
if (!tools?.length || toolChoice === "none")
|
|
246
|
+
return undefined;
|
|
247
|
+
let choice;
|
|
248
|
+
if (typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function") {
|
|
249
|
+
choice = { tool: { name: toolChoice.function.name } };
|
|
250
|
+
}
|
|
251
|
+
else if (toolChoice === "required") {
|
|
252
|
+
choice = { any: {} };
|
|
253
|
+
}
|
|
254
|
+
else if (toolChoice === "auto") {
|
|
255
|
+
choice = { auto: {} };
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
tools: tools.map((i) => {
|
|
259
|
+
const parameters = i.function.parameters;
|
|
260
|
+
if (Object.keys(parameters).length === 0) {
|
|
261
|
+
parameters.type = "object";
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
toolSpec: {
|
|
265
|
+
name: i.function.name,
|
|
266
|
+
description: i.function.description,
|
|
267
|
+
inputSchema: { json: parameters },
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}),
|
|
271
|
+
toolChoice: choice,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -6,6 +6,7 @@ export declare abstract class ChatModel extends Agent<ChatModelInput, ChatModelO
|
|
|
6
6
|
getModelCapabilities(): {
|
|
7
7
|
supportsParallelToolCalls: boolean;
|
|
8
8
|
};
|
|
9
|
+
private validateToolNames;
|
|
9
10
|
protected preprocess(input: ChatModelInput, context: Context): void;
|
|
10
11
|
protected postprocess(input: ChatModelInput, output: ChatModelOutput, context: Context): void;
|
|
11
12
|
}
|
|
@@ -16,6 +16,13 @@ class ChatModel extends agent_js_1.Agent {
|
|
|
16
16
|
supportsParallelToolCalls: this.supportsParallelToolCalls,
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
validateToolNames(tools) {
|
|
20
|
+
for (const tool of tools ?? []) {
|
|
21
|
+
if (!/^[a-zA-Z0-9_]+$/.test(tool.function.name)) {
|
|
22
|
+
throw new Error(`Tool name "${tool.function.name}" can only contain letters, numbers, and underscores`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
preprocess(input, context) {
|
|
20
27
|
super.preprocess(input, context);
|
|
21
28
|
const { limits, usage } = context;
|
|
@@ -23,6 +30,7 @@ class ChatModel extends agent_js_1.Agent {
|
|
|
23
30
|
if (limits?.maxTokens && usedTokens >= limits.maxTokens) {
|
|
24
31
|
throw new Error(`Exceeded max tokens ${usedTokens}/${limits.maxTokens}`);
|
|
25
32
|
}
|
|
33
|
+
this.validateToolNames(input.tools);
|
|
26
34
|
}
|
|
27
35
|
postprocess(input, output, context) {
|
|
28
36
|
super.postprocess(input, output, context);
|
|
@@ -16,5 +16,6 @@ class GeminiChatModel extends openai_chat_model_js_1.OpenAIChatModel {
|
|
|
16
16
|
supportsEndWithSystemMessage = false;
|
|
17
17
|
supportsToolsUseWithJsonSchema = false;
|
|
18
18
|
supportsParallelToolCalls = false;
|
|
19
|
+
supportsToolStreaming = false;
|
|
19
20
|
}
|
|
20
21
|
exports.GeminiChatModel = GeminiChatModel;
|
|
@@ -10,6 +10,7 @@ export interface OpenAIChatModelCapabilities {
|
|
|
10
10
|
supportsToolsUseWithJsonSchema: boolean;
|
|
11
11
|
supportsParallelToolCalls: boolean;
|
|
12
12
|
supportsToolsEmptyParameters: boolean;
|
|
13
|
+
supportsToolStreaming: boolean;
|
|
13
14
|
supportsTemperature: boolean;
|
|
14
15
|
}
|
|
15
16
|
export interface OpenAIChatModelOptions {
|
|
@@ -80,6 +81,7 @@ export declare class OpenAIChatModel extends ChatModel {
|
|
|
80
81
|
protected supportsToolsUseWithJsonSchema: boolean;
|
|
81
82
|
protected supportsParallelToolCalls: boolean;
|
|
82
83
|
protected supportsToolsEmptyParameters: boolean;
|
|
84
|
+
protected supportsToolStreaming: boolean;
|
|
83
85
|
protected supportsTemperature: boolean;
|
|
84
86
|
get client(): OpenAI;
|
|
85
87
|
get modelOptions(): ChatModelOptions | undefined;
|
|
@@ -88,6 +90,7 @@ export declare class OpenAIChatModel extends ChatModel {
|
|
|
88
90
|
private getRunMessages;
|
|
89
91
|
private getRunResponseFormat;
|
|
90
92
|
private requestStructuredOutput;
|
|
93
|
+
private extractResultFromStream;
|
|
91
94
|
}
|
|
92
95
|
export declare const ROLE_MAP: {
|
|
93
96
|
[key in Role]: ChatCompletionMessageParam["role"];
|
|
@@ -54,6 +54,7 @@ class OpenAIChatModel extends chat_model_js_1.ChatModel {
|
|
|
54
54
|
supportsToolsUseWithJsonSchema = true;
|
|
55
55
|
supportsParallelToolCalls = true;
|
|
56
56
|
supportsToolsEmptyParameters = true;
|
|
57
|
+
supportsToolStreaming = true;
|
|
57
58
|
supportsTemperature = true;
|
|
58
59
|
get client() {
|
|
59
60
|
const apiKey = this.options?.apiKey || process.env[this.apiKeyEnvName] || this.apiKeyDefault;
|
|
@@ -95,9 +96,9 @@ class OpenAIChatModel extends chat_model_js_1.ChatModel {
|
|
|
95
96
|
response_format: responseFormat,
|
|
96
97
|
});
|
|
97
98
|
if (options?.streaming && input.responseFormat?.type !== "json_schema") {
|
|
98
|
-
return await extractResultFromStream(stream, false, true);
|
|
99
|
+
return await this.extractResultFromStream(stream, false, true);
|
|
99
100
|
}
|
|
100
|
-
const result = await extractResultFromStream(stream, jsonMode);
|
|
101
|
+
const result = await this.extractResultFromStream(stream, jsonMode);
|
|
101
102
|
if (!this.supportsToolsUseWithJsonSchema &&
|
|
102
103
|
!result.toolCalls?.length &&
|
|
103
104
|
input.responseFormat?.type === "json_schema" &&
|
|
@@ -163,7 +164,103 @@ class OpenAIChatModel extends chat_model_js_1.ChatModel {
|
|
|
163
164
|
...body,
|
|
164
165
|
response_format: resolvedResponseFormat,
|
|
165
166
|
});
|
|
166
|
-
return extractResultFromStream(res, jsonMode);
|
|
167
|
+
return this.extractResultFromStream(res, jsonMode);
|
|
168
|
+
}
|
|
169
|
+
async extractResultFromStream(stream, jsonMode, streaming) {
|
|
170
|
+
const result = new ReadableStream({
|
|
171
|
+
start: async (controller) => {
|
|
172
|
+
try {
|
|
173
|
+
let text = "";
|
|
174
|
+
let refusal = "";
|
|
175
|
+
const toolCalls = [];
|
|
176
|
+
let model;
|
|
177
|
+
for await (const chunk of stream) {
|
|
178
|
+
const choice = chunk.choices?.[0];
|
|
179
|
+
if (!model) {
|
|
180
|
+
model = chunk.model;
|
|
181
|
+
controller.enqueue({
|
|
182
|
+
delta: {
|
|
183
|
+
json: {
|
|
184
|
+
model,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (choice?.delta.tool_calls?.length) {
|
|
190
|
+
for (const call of choice.delta.tool_calls) {
|
|
191
|
+
if (this.supportsToolStreaming && call.index !== undefined) {
|
|
192
|
+
handleToolCallDelta(toolCalls, call);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
handleCompleteToolCall(toolCalls, call);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (choice?.delta.content) {
|
|
200
|
+
text += choice.delta.content;
|
|
201
|
+
if (!jsonMode) {
|
|
202
|
+
controller.enqueue({
|
|
203
|
+
delta: {
|
|
204
|
+
text: {
|
|
205
|
+
text: choice.delta.content,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (choice?.delta.refusal) {
|
|
212
|
+
refusal += choice.delta.refusal;
|
|
213
|
+
if (!jsonMode) {
|
|
214
|
+
controller.enqueue({
|
|
215
|
+
delta: {
|
|
216
|
+
text: { text: choice.delta.refusal },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (chunk.usage) {
|
|
222
|
+
controller.enqueue({
|
|
223
|
+
delta: {
|
|
224
|
+
json: {
|
|
225
|
+
usage: {
|
|
226
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
227
|
+
outputTokens: chunk.usage.completion_tokens,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
text = text || refusal;
|
|
235
|
+
if (jsonMode && text) {
|
|
236
|
+
controller.enqueue({
|
|
237
|
+
delta: {
|
|
238
|
+
json: {
|
|
239
|
+
json: (0, json_schema_js_1.parseJSON)(text),
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (toolCalls.length) {
|
|
245
|
+
controller.enqueue({
|
|
246
|
+
delta: {
|
|
247
|
+
json: {
|
|
248
|
+
toolCalls: toolCalls.map(({ args, ...c }) => ({
|
|
249
|
+
...c,
|
|
250
|
+
function: { ...c.function, arguments: (0, json_schema_js_1.parseJSON)(args) },
|
|
251
|
+
})),
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
controller.close();
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
controller.error(error);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
return streaming ? result : await (0, stream_utils_js_1.agentResponseStreamToObject)(result);
|
|
167
264
|
}
|
|
168
265
|
}
|
|
169
266
|
exports.OpenAIChatModel = OpenAIChatModel;
|
|
@@ -245,103 +342,6 @@ function jsonSchemaToOpenAIJsonSchema(schema) {
|
|
|
245
342
|
}
|
|
246
343
|
return schema;
|
|
247
344
|
}
|
|
248
|
-
async function extractResultFromStream(stream, jsonMode, streaming) {
|
|
249
|
-
const result = new ReadableStream({
|
|
250
|
-
async start(controller) {
|
|
251
|
-
try {
|
|
252
|
-
let text = "";
|
|
253
|
-
let refusal = "";
|
|
254
|
-
const toolCalls = [];
|
|
255
|
-
let model;
|
|
256
|
-
for await (const chunk of stream) {
|
|
257
|
-
const choice = chunk.choices?.[0];
|
|
258
|
-
if (!model) {
|
|
259
|
-
model = chunk.model;
|
|
260
|
-
controller.enqueue({
|
|
261
|
-
delta: {
|
|
262
|
-
json: {
|
|
263
|
-
model,
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
if (choice?.delta.tool_calls?.length) {
|
|
269
|
-
for (const call of choice.delta.tool_calls) {
|
|
270
|
-
// Gemini not support tool call delta
|
|
271
|
-
if (call.index !== undefined) {
|
|
272
|
-
handleToolCallDelta(toolCalls, call);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
handleCompleteToolCall(toolCalls, call);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (choice?.delta.content) {
|
|
280
|
-
text += choice.delta.content;
|
|
281
|
-
if (!jsonMode) {
|
|
282
|
-
controller.enqueue({
|
|
283
|
-
delta: {
|
|
284
|
-
text: {
|
|
285
|
-
text: choice.delta.content,
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (choice?.delta.refusal) {
|
|
292
|
-
refusal += choice.delta.refusal;
|
|
293
|
-
if (!jsonMode) {
|
|
294
|
-
controller.enqueue({
|
|
295
|
-
delta: {
|
|
296
|
-
text: { text: choice.delta.refusal },
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (chunk.usage) {
|
|
302
|
-
controller.enqueue({
|
|
303
|
-
delta: {
|
|
304
|
-
json: {
|
|
305
|
-
usage: {
|
|
306
|
-
inputTokens: chunk.usage.prompt_tokens,
|
|
307
|
-
outputTokens: chunk.usage.completion_tokens,
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
text = text || refusal;
|
|
315
|
-
if (jsonMode && text) {
|
|
316
|
-
controller.enqueue({
|
|
317
|
-
delta: {
|
|
318
|
-
json: {
|
|
319
|
-
json: (0, json_schema_js_1.parseJSON)(text),
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
if (toolCalls.length) {
|
|
325
|
-
controller.enqueue({
|
|
326
|
-
delta: {
|
|
327
|
-
json: {
|
|
328
|
-
toolCalls: toolCalls.map(({ args, ...c }) => ({
|
|
329
|
-
...c,
|
|
330
|
-
function: { ...c.function, arguments: (0, json_schema_js_1.parseJSON)(args) },
|
|
331
|
-
})),
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
controller.close();
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
controller.error(error);
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
return streaming ? result : await (0, stream_utils_js_1.agentResponseStreamToObject)(result);
|
|
344
|
-
}
|
|
345
345
|
function handleToolCallDelta(toolCalls, call) {
|
|
346
346
|
toolCalls[call.index] ??= {
|
|
347
347
|
id: call.id || (0, nanoid_1.nanoid)(),
|