@aigne/openai 0.16.16 → 1.74.0-beta
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/README.md +11 -11
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +5 -0
- package/dist/openai-chat-model.cjs +371 -0
- package/dist/openai-chat-model.d.cts +165 -0
- package/dist/openai-chat-model.d.cts.map +1 -0
- package/dist/openai-chat-model.d.mts +165 -0
- package/dist/openai-chat-model.d.mts.map +1 -0
- package/dist/openai-chat-model.mjs +368 -0
- package/dist/openai-chat-model.mjs.map +1 -0
- package/dist/openai-image-model.cjs +123 -0
- package/dist/openai-image-model.d.cts +57 -0
- package/dist/openai-image-model.d.cts.map +1 -0
- package/dist/openai-image-model.d.mts +57 -0
- package/dist/openai-image-model.d.mts.map +1 -0
- package/dist/openai-image-model.mjs +123 -0
- package/dist/openai-image-model.mjs.map +1 -0
- package/dist/openai-video-model.cjs +112 -0
- package/dist/openai-video-model.d.cts +95 -0
- package/dist/openai-video-model.d.cts.map +1 -0
- package/dist/openai-video-model.d.mts +95 -0
- package/dist/openai-video-model.d.mts.map +1 -0
- package/dist/openai-video-model.mjs +112 -0
- package/dist/openai-video-model.mjs.map +1 -0
- package/dist/openai.cjs +14 -0
- package/dist/openai.mjs +13 -0
- package/dist/openai.mjs.map +1 -0
- package/package.json +29 -30
- package/CHANGELOG.md +0 -2448
- package/lib/cjs/index.d.ts +0 -3
- package/lib/cjs/index.js +0 -19
- package/lib/cjs/openai-chat-model.d.ts +0 -160
- package/lib/cjs/openai-chat-model.js +0 -465
- package/lib/cjs/openai-image-model.d.ts +0 -55
- package/lib/cjs/openai-image-model.js +0 -110
- package/lib/cjs/openai-video-model.d.ts +0 -92
- package/lib/cjs/openai-video-model.js +0 -118
- package/lib/cjs/openai.d.ts +0 -4
- package/lib/cjs/openai.js +0 -17
- package/lib/cjs/package.json +0 -3
- package/lib/dts/index.d.ts +0 -3
- package/lib/dts/openai-chat-model.d.ts +0 -160
- package/lib/dts/openai-image-model.d.ts +0 -55
- package/lib/dts/openai-video-model.d.ts +0 -92
- package/lib/dts/openai.d.ts +0 -4
- package/lib/esm/index.d.ts +0 -3
- package/lib/esm/index.js +0 -3
- package/lib/esm/openai-chat-model.d.ts +0 -160
- package/lib/esm/openai-chat-model.js +0 -459
- package/lib/esm/openai-image-model.d.ts +0 -55
- package/lib/esm/openai-image-model.js +0 -106
- package/lib/esm/openai-video-model.d.ts +0 -92
- package/lib/esm/openai-video-model.js +0 -114
- package/lib/esm/openai.d.ts +0 -4
- package/lib/esm/openai.js +0 -10
- package/lib/esm/package.json +0 -3
package/README.md
CHANGED
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<picture>
|
|
5
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
6
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
7
|
-
<img src="https://raw.githubusercontent.com/
|
|
5
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo-dark.svg" media="(prefers-color-scheme: dark)">
|
|
6
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" media="(prefers-color-scheme: light)">
|
|
7
|
+
<img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" alt="AIGNE Logo" width="400" />
|
|
8
8
|
</picture>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
[](https://star-history.com/#ArcBlock/aigne-framework)
|
|
12
|
+
[](https://github.com/ArcBlock/aigne-framework/issues)
|
|
13
|
+
[](https://codecov.io/gh/ArcBlock/aigne-framework)
|
|
14
14
|
[](https://www.npmjs.com/package/@aigne/openai)
|
|
15
|
-
[](https://github.com/
|
|
15
|
+
[](https://github.com/ArcBlock/aigne-framework/blob/main/LICENSE.md)
|
|
16
16
|
|
|
17
|
-
AIGNE OpenAI SDK for integrating with OpenAI's GPT models and API services within the [AIGNE Framework](https://github.com/
|
|
17
|
+
AIGNE OpenAI SDK for integrating with OpenAI's GPT models and API services within the [AIGNE Framework](https://github.com/ArcBlock/aigne-framework).
|
|
18
18
|
|
|
19
19
|
## Introduction
|
|
20
20
|
|
|
21
21
|
`@aigne/openai` provides a seamless integration between the AIGNE Framework and OpenAI's powerful language models and APIs. This package enables developers to easily leverage OpenAI's GPT models in their AIGNE applications, providing a consistent interface across the framework while taking advantage of OpenAI's advanced AI capabilities.
|
|
22
22
|
|
|
23
23
|
<picture>
|
|
24
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
25
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
26
|
-
<img src="https://raw.githubusercontent.com/
|
|
24
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-openai-dark.png" media="(prefers-color-scheme: dark)">
|
|
25
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-openai.png" media="(prefers-color-scheme: light)">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/aigne-openai.png" alt="AIGNE Arch" />
|
|
27
27
|
</picture>
|
|
28
28
|
|
|
29
29
|
## Features
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
|
|
29
|
+
exports.__toESM = __toESM;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const require_openai_chat_model = require('./openai-chat-model.cjs');
|
|
2
|
+
const require_openai_image_model = require('./openai-image-model.cjs');
|
|
3
|
+
const require_openai_video_model = require('./openai-video-model.cjs');
|
|
4
|
+
|
|
5
|
+
exports.OpenAIChatModel = require_openai_chat_model.OpenAIChatModel;
|
|
6
|
+
exports.OpenAIImageModel = require_openai_image_model.OpenAIImageModel;
|
|
7
|
+
exports.OpenAIVideoModel = require_openai_video_model.OpenAIVideoModel;
|
|
8
|
+
exports.contentsFromInputMessages = require_openai_chat_model.contentsFromInputMessages;
|
|
9
|
+
exports.openAIChatModelOptionsSchema = require_openai_chat_model.openAIChatModelOptionsSchema;
|
|
10
|
+
exports.toolsFromInputTools = require_openai_chat_model.toolsFromInputTools;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { OpenAIChatModel, OpenAIChatModelCapabilities, OpenAIChatModelOptions, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools } from "./openai-chat-model.cjs";
|
|
2
|
+
import { OpenAIImageModel, OpenAIImageModelInput, OpenAIImageModelOptions, OpenAIImageModelOutput } from "./openai-image-model.cjs";
|
|
3
|
+
import { OpenAIVideoModel, OpenAIVideoModelInput, OpenAIVideoModelOptions, OpenAIVideoModelOutput } from "./openai-video-model.cjs";
|
|
4
|
+
export { OpenAIChatModel, OpenAIChatModelCapabilities, OpenAIChatModelOptions, OpenAIImageModel, OpenAIImageModelInput, OpenAIImageModelOptions, OpenAIImageModelOutput, OpenAIVideoModel, OpenAIVideoModelInput, OpenAIVideoModelOptions, OpenAIVideoModelOutput, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { OpenAIChatModel, OpenAIChatModelCapabilities, OpenAIChatModelOptions, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools } from "./openai-chat-model.mjs";
|
|
2
|
+
import { OpenAIImageModel, OpenAIImageModelInput, OpenAIImageModelOptions, OpenAIImageModelOutput } from "./openai-image-model.mjs";
|
|
3
|
+
import { OpenAIVideoModel, OpenAIVideoModelInput, OpenAIVideoModelOptions, OpenAIVideoModelOutput } from "./openai-video-model.mjs";
|
|
4
|
+
export { OpenAIChatModel, OpenAIChatModelCapabilities, OpenAIChatModelOptions, OpenAIImageModel, OpenAIImageModelInput, OpenAIImageModelOptions, OpenAIImageModelOutput, OpenAIVideoModel, OpenAIVideoModelInput, OpenAIVideoModelOptions, OpenAIVideoModelOutput, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { OpenAIChatModel, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools } from "./openai-chat-model.mjs";
|
|
2
|
+
import { OpenAIImageModel } from "./openai-image-model.mjs";
|
|
3
|
+
import { OpenAIVideoModel } from "./openai-video-model.mjs";
|
|
4
|
+
|
|
5
|
+
export { OpenAIChatModel, OpenAIImageModel, OpenAIVideoModel, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools };
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_openai = require('./openai.cjs');
|
|
3
|
+
let _aigne_core = require("@aigne/core");
|
|
4
|
+
let _aigne_core_utils_logger = require("@aigne/core/utils/logger");
|
|
5
|
+
let _aigne_core_utils_model_utils = require("@aigne/core/utils/model-utils");
|
|
6
|
+
let _aigne_core_utils_prompts = require("@aigne/core/utils/prompts");
|
|
7
|
+
let _aigne_core_utils_stream_utils = require("@aigne/core/utils/stream-utils");
|
|
8
|
+
let _aigne_core_utils_type_utils = require("@aigne/core/utils/type-utils");
|
|
9
|
+
let _aigne_uuid = require("@aigne/uuid");
|
|
10
|
+
let zod = require("zod");
|
|
11
|
+
|
|
12
|
+
//#region src/openai-chat-model.ts
|
|
13
|
+
const CHAT_MODEL_OPENAI_DEFAULT_MODEL = "gpt-4o-mini";
|
|
14
|
+
const OPENAI_CHAT_MODEL_CAPABILITIES = {
|
|
15
|
+
"o4-mini": {
|
|
16
|
+
supportsParallelToolCalls: false,
|
|
17
|
+
supportsTemperature: false
|
|
18
|
+
},
|
|
19
|
+
"o3-mini": {
|
|
20
|
+
supportsParallelToolCalls: false,
|
|
21
|
+
supportsTemperature: false
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* @hidden
|
|
26
|
+
*/
|
|
27
|
+
const openAIChatModelOptionsSchema = zod.z.object({
|
|
28
|
+
apiKey: zod.z.string().optional(),
|
|
29
|
+
baseURL: zod.z.string().optional(),
|
|
30
|
+
model: zod.z.string().optional(),
|
|
31
|
+
modelOptions: zod.z.object({
|
|
32
|
+
model: zod.z.string().optional(),
|
|
33
|
+
temperature: zod.z.number().optional(),
|
|
34
|
+
topP: zod.z.number().optional(),
|
|
35
|
+
frequencyPenalty: zod.z.number().optional(),
|
|
36
|
+
presencePenalty: zod.z.number().optional(),
|
|
37
|
+
parallelToolCalls: zod.z.boolean().optional().default(true)
|
|
38
|
+
}).optional()
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Implementation of the ChatModel interface for OpenAI's API
|
|
42
|
+
*
|
|
43
|
+
* This model provides access to OpenAI's capabilities including:
|
|
44
|
+
* - Text generation
|
|
45
|
+
* - Tool use with parallel tool calls
|
|
46
|
+
* - JSON structured output
|
|
47
|
+
* - Image understanding
|
|
48
|
+
*
|
|
49
|
+
* Default model: 'gpt-4o-mini'
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* Here's how to create and use an OpenAI chat model:
|
|
53
|
+
* {@includeCode ../test/openai-chat-model.test.ts#example-openai-chat-model}
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* Here's an example with streaming response:
|
|
57
|
+
* {@includeCode ../test/openai-chat-model.test.ts#example-openai-chat-model-stream}
|
|
58
|
+
*/
|
|
59
|
+
var OpenAIChatModel = class extends _aigne_core.ChatModel {
|
|
60
|
+
constructor(options) {
|
|
61
|
+
super();
|
|
62
|
+
this.options = options;
|
|
63
|
+
if (options) (0, _aigne_core_utils_type_utils.checkArguments)(this.name, openAIChatModelOptionsSchema, options);
|
|
64
|
+
const preset = options?.model ? OPENAI_CHAT_MODEL_CAPABILITIES[options.model] : void 0;
|
|
65
|
+
Object.assign(this, preset);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* @hidden
|
|
69
|
+
*/
|
|
70
|
+
_client;
|
|
71
|
+
apiKeyEnvName = "OPENAI_API_KEY";
|
|
72
|
+
apiKeyDefault;
|
|
73
|
+
supportsNativeStructuredOutputs = true;
|
|
74
|
+
supportsToolsUseWithJsonSchema = true;
|
|
75
|
+
supportsParallelToolCalls = true;
|
|
76
|
+
supportsToolsEmptyParameters = true;
|
|
77
|
+
supportsToolStreaming = true;
|
|
78
|
+
supportsTemperature = true;
|
|
79
|
+
get client() {
|
|
80
|
+
const { apiKey, url } = this.credential;
|
|
81
|
+
if (!apiKey) throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
|
|
82
|
+
this._client ??= new require_openai.CustomOpenAI({
|
|
83
|
+
baseURL: url,
|
|
84
|
+
apiKey,
|
|
85
|
+
...this.options?.clientOptions
|
|
86
|
+
});
|
|
87
|
+
return this._client;
|
|
88
|
+
}
|
|
89
|
+
get credential() {
|
|
90
|
+
return {
|
|
91
|
+
url: this.options?.baseURL || process.env.OPENAI_BASE_URL,
|
|
92
|
+
apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName] || this.apiKeyDefault,
|
|
93
|
+
model: this.options?.model || CHAT_MODEL_OPENAI_DEFAULT_MODEL
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Process the input and generate a response
|
|
98
|
+
* @param input The input to process
|
|
99
|
+
* @returns The generated response
|
|
100
|
+
*/
|
|
101
|
+
process(input, options) {
|
|
102
|
+
return this._process(input, options);
|
|
103
|
+
}
|
|
104
|
+
getReasoningEffort(effort) {
|
|
105
|
+
if (typeof effort === "number") {
|
|
106
|
+
if (effort > 5e3) return "high";
|
|
107
|
+
if (effort > 1e3) return "medium";
|
|
108
|
+
if (effort > 500) return "low";
|
|
109
|
+
if (effort > 0) return "minimal";
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
return effort;
|
|
113
|
+
}
|
|
114
|
+
async _process(input, _options) {
|
|
115
|
+
const { modelOptions = {} } = input;
|
|
116
|
+
const messages = await this.getRunMessages(input);
|
|
117
|
+
const model = modelOptions?.model || this.credential.model;
|
|
118
|
+
const body = {
|
|
119
|
+
model,
|
|
120
|
+
temperature: this.supportsTemperature ? modelOptions.temperature : void 0,
|
|
121
|
+
top_p: modelOptions.topP,
|
|
122
|
+
frequency_penalty: modelOptions.frequencyPenalty,
|
|
123
|
+
presence_penalty: modelOptions.presencePenalty,
|
|
124
|
+
messages,
|
|
125
|
+
stream_options: { include_usage: true },
|
|
126
|
+
stream: true,
|
|
127
|
+
reasoning_effort: this.getReasoningEffort(modelOptions.reasoningEffort)
|
|
128
|
+
};
|
|
129
|
+
if (model.includes("gpt-5") || model.includes("o1-")) {
|
|
130
|
+
delete body.temperature;
|
|
131
|
+
delete body.top_p;
|
|
132
|
+
}
|
|
133
|
+
if (!input.tools?.length && input.responseFormat?.type === "json_schema") return await this.requestStructuredOutput(body, input.responseFormat);
|
|
134
|
+
const { jsonMode, responseFormat } = await this.getRunResponseFormat(input);
|
|
135
|
+
const stream = await this.client.chat.completions.create({
|
|
136
|
+
...body,
|
|
137
|
+
tools: toolsFromInputTools(input.tools, { addTypeToEmptyParameters: !this.supportsToolsEmptyParameters }),
|
|
138
|
+
tool_choice: input.toolChoice,
|
|
139
|
+
parallel_tool_calls: this.getParallelToolCalls(input, modelOptions),
|
|
140
|
+
response_format: responseFormat
|
|
141
|
+
});
|
|
142
|
+
if (input.responseFormat?.type !== "json_schema") return await this.extractResultFromStream(body, stream, false, true);
|
|
143
|
+
const result = await this.extractResultFromStream(body, stream, jsonMode);
|
|
144
|
+
if (result.toolCalls?.length || result.json) return result;
|
|
145
|
+
const json = (0, _aigne_core.safeParseJSON)(result.text || "");
|
|
146
|
+
const validated = this.validateJsonSchema(input.responseFormat.jsonSchema.schema, json, { safe: true });
|
|
147
|
+
if (validated.success) return {
|
|
148
|
+
...result,
|
|
149
|
+
json: validated.data,
|
|
150
|
+
text: void 0
|
|
151
|
+
};
|
|
152
|
+
_aigne_core_utils_logger.logger.warn(`${this.name}: Text response does not match JSON schema, trying to use tool to extract json `, { text: result.text });
|
|
153
|
+
const output = await this.requestStructuredOutput(body, input.responseFormat);
|
|
154
|
+
return {
|
|
155
|
+
...output,
|
|
156
|
+
usage: (0, _aigne_core_utils_model_utils.mergeUsage)(result.usage, output.usage)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
getParallelToolCalls(input, modelOptions) {
|
|
160
|
+
if (!this.supportsParallelToolCalls) return void 0;
|
|
161
|
+
if (!input.tools?.length) return void 0;
|
|
162
|
+
return modelOptions.parallelToolCalls;
|
|
163
|
+
}
|
|
164
|
+
async getRunMessages(input) {
|
|
165
|
+
const messages = await contentsFromInputMessages(input.messages);
|
|
166
|
+
if (input.responseFormat?.type === "json_schema") {
|
|
167
|
+
if (!this.supportsNativeStructuredOutputs || !this.supportsToolsUseWithJsonSchema && input.tools?.length) messages.unshift({
|
|
168
|
+
role: "system",
|
|
169
|
+
content: (0, _aigne_core_utils_prompts.getJsonOutputPrompt)(input.responseFormat.jsonSchema.schema)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return messages;
|
|
173
|
+
}
|
|
174
|
+
async getRunResponseFormat(input) {
|
|
175
|
+
if (!this.supportsToolsUseWithJsonSchema && input.tools?.length) return {
|
|
176
|
+
jsonMode: false,
|
|
177
|
+
responseFormat: void 0
|
|
178
|
+
};
|
|
179
|
+
if (!this.supportsNativeStructuredOutputs) {
|
|
180
|
+
const jsonMode = input.responseFormat?.type === "json_schema";
|
|
181
|
+
return {
|
|
182
|
+
jsonMode,
|
|
183
|
+
responseFormat: jsonMode ? { type: "json_object" } : void 0
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (input.responseFormat?.type === "json_schema") return {
|
|
187
|
+
jsonMode: true,
|
|
188
|
+
responseFormat: {
|
|
189
|
+
type: "json_schema",
|
|
190
|
+
json_schema: {
|
|
191
|
+
...input.responseFormat.jsonSchema,
|
|
192
|
+
schema: this.jsonSchemaToOpenAIJsonSchema(input.responseFormat.jsonSchema.schema)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
jsonMode: false,
|
|
198
|
+
responseFormat: void 0
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async requestStructuredOutput(body, responseFormat) {
|
|
202
|
+
if (responseFormat?.type !== "json_schema") throw new Error("Expected json_schema response format");
|
|
203
|
+
const { jsonMode, responseFormat: resolvedResponseFormat } = await this.getRunResponseFormat({ responseFormat });
|
|
204
|
+
const res = await this.client.chat.completions.create({
|
|
205
|
+
...body,
|
|
206
|
+
response_format: resolvedResponseFormat
|
|
207
|
+
});
|
|
208
|
+
return this.extractResultFromStream(body, res, jsonMode);
|
|
209
|
+
}
|
|
210
|
+
async extractResultFromStream(body, stream, jsonMode, streaming) {
|
|
211
|
+
const result = new ReadableStream({ start: async (controller) => {
|
|
212
|
+
try {
|
|
213
|
+
controller.enqueue({ delta: { json: { modelOptions: { reasoningEffort: body.reasoning_effort } } } });
|
|
214
|
+
let text = "";
|
|
215
|
+
let refusal = "";
|
|
216
|
+
const toolCalls = [];
|
|
217
|
+
let model;
|
|
218
|
+
for await (const chunk of stream) {
|
|
219
|
+
const delta = (chunk.choices?.[0])?.delta;
|
|
220
|
+
if (!model) {
|
|
221
|
+
model = chunk.model;
|
|
222
|
+
controller.enqueue({ delta: { json: { model } } });
|
|
223
|
+
}
|
|
224
|
+
if (delta?.tool_calls?.length) for (const call of delta.tool_calls) if (this.supportsToolStreaming && call.index !== void 0) handleToolCallDelta(toolCalls, call);
|
|
225
|
+
else handleCompleteToolCall(toolCalls, call);
|
|
226
|
+
if (delta && "reasoning" in delta && typeof delta.reasoning === "string") controller.enqueue({ delta: { text: { thoughts: delta.reasoning } } });
|
|
227
|
+
if (delta?.content) {
|
|
228
|
+
text += delta.content;
|
|
229
|
+
if (!jsonMode) controller.enqueue({ delta: { text: { text: delta.content } } });
|
|
230
|
+
}
|
|
231
|
+
if (delta?.refusal) refusal += delta.refusal;
|
|
232
|
+
if (chunk.usage) {
|
|
233
|
+
const usage = {
|
|
234
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
235
|
+
outputTokens: chunk.usage.completion_tokens
|
|
236
|
+
};
|
|
237
|
+
const inputDetails = chunk.usage.prompt_tokens_details;
|
|
238
|
+
if (inputDetails?.cached_tokens) usage.cacheReadInputTokens = inputDetails.cached_tokens;
|
|
239
|
+
controller.enqueue({ delta: { json: { usage } } });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (jsonMode && text) controller.enqueue({ delta: { json: { json: (0, _aigne_core.safeParseJSON)(text) } } });
|
|
243
|
+
if (toolCalls.length) controller.enqueue({ delta: { json: { toolCalls: toolCalls.map(({ args, ...c }) => ({
|
|
244
|
+
...c,
|
|
245
|
+
function: {
|
|
246
|
+
...c.function,
|
|
247
|
+
arguments: args ? (0, _aigne_core.safeParseJSON)(args) : {}
|
|
248
|
+
}
|
|
249
|
+
})) } } });
|
|
250
|
+
if (refusal) controller.error(/* @__PURE__ */ new Error(`Got refusal from LLM: ${refusal}`));
|
|
251
|
+
controller.close();
|
|
252
|
+
} catch (error) {
|
|
253
|
+
controller.error(error);
|
|
254
|
+
}
|
|
255
|
+
} });
|
|
256
|
+
return streaming ? result : await (0, _aigne_core_utils_stream_utils.agentResponseStreamToObject)(result);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Controls how optional fields are handled in JSON schema conversion
|
|
260
|
+
* - "anyOf": All fields are required but can be null (default)
|
|
261
|
+
* - "optional": Fields marked as optional in schema remain optional
|
|
262
|
+
*/
|
|
263
|
+
optionalFieldMode = "anyOf";
|
|
264
|
+
jsonSchemaToOpenAIJsonSchema(schema) {
|
|
265
|
+
if (schema?.type === "object") {
|
|
266
|
+
const s = schema;
|
|
267
|
+
const required = this.optionalFieldMode === "anyOf" ? Object.keys(s.properties) : s.required;
|
|
268
|
+
return {
|
|
269
|
+
...schema,
|
|
270
|
+
properties: Object.fromEntries(Object.entries(s.properties).map(([key, value]) => {
|
|
271
|
+
const valueSchema = this.jsonSchemaToOpenAIJsonSchema(value);
|
|
272
|
+
return [key, this.optionalFieldMode === "optional" || s.required?.includes(key) ? valueSchema : { anyOf: [valueSchema, { type: ["null"] }] }];
|
|
273
|
+
})),
|
|
274
|
+
required
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (schema?.type === "array") {
|
|
278
|
+
const { items } = schema;
|
|
279
|
+
return {
|
|
280
|
+
...schema,
|
|
281
|
+
items: this.jsonSchemaToOpenAIJsonSchema(items)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return schema;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const mapRole = (0, _aigne_core.createRoleMapper)(_aigne_core.STANDARD_ROLE_MAP);
|
|
288
|
+
/**
|
|
289
|
+
* @hidden
|
|
290
|
+
*/
|
|
291
|
+
async function contentsFromInputMessages(messages) {
|
|
292
|
+
return Promise.all(messages.map(async (i) => ({
|
|
293
|
+
role: mapRole(i.role),
|
|
294
|
+
content: typeof i.content === "string" ? i.content : i.content && (await Promise.all(i.content.map(async (c) => {
|
|
295
|
+
switch (c.type) {
|
|
296
|
+
case "text": return {
|
|
297
|
+
type: "text",
|
|
298
|
+
text: c.text
|
|
299
|
+
};
|
|
300
|
+
case "url": return {
|
|
301
|
+
type: "image_url",
|
|
302
|
+
image_url: { url: c.url }
|
|
303
|
+
};
|
|
304
|
+
case "file": return {
|
|
305
|
+
type: "image_url",
|
|
306
|
+
image_url: { url: `data:${c.mimeType || "image/png"};base64,${c.data}` }
|
|
307
|
+
};
|
|
308
|
+
case "local": throw new Error(`Unsupported local file: ${c.path}, it should be converted to base64 at ChatModel`);
|
|
309
|
+
}
|
|
310
|
+
}))).filter(_aigne_core_utils_type_utils.isNonNullable),
|
|
311
|
+
tool_calls: i.toolCalls?.map((i$1) => ({
|
|
312
|
+
...i$1,
|
|
313
|
+
function: {
|
|
314
|
+
...i$1.function,
|
|
315
|
+
arguments: JSON.stringify(i$1.function.arguments)
|
|
316
|
+
}
|
|
317
|
+
})),
|
|
318
|
+
tool_call_id: i.toolCallId,
|
|
319
|
+
name: i.name
|
|
320
|
+
})));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* @hidden
|
|
324
|
+
*/
|
|
325
|
+
function toolsFromInputTools(tools, options) {
|
|
326
|
+
return tools?.length ? tools.map((i) => {
|
|
327
|
+
const parameters = i.function.parameters;
|
|
328
|
+
if (options?.addTypeToEmptyParameters && Object.keys(parameters).length === 0) parameters.type = "object";
|
|
329
|
+
return {
|
|
330
|
+
type: "function",
|
|
331
|
+
function: {
|
|
332
|
+
name: i.function.name,
|
|
333
|
+
description: i.function.description,
|
|
334
|
+
parameters
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}) : void 0;
|
|
338
|
+
}
|
|
339
|
+
function handleToolCallDelta(toolCalls, call) {
|
|
340
|
+
toolCalls[call.index] ??= {
|
|
341
|
+
id: call.id || (0, _aigne_uuid.v7)(),
|
|
342
|
+
type: "function",
|
|
343
|
+
function: {
|
|
344
|
+
name: "",
|
|
345
|
+
arguments: {}
|
|
346
|
+
},
|
|
347
|
+
args: ""
|
|
348
|
+
};
|
|
349
|
+
const c = toolCalls[call.index];
|
|
350
|
+
if (!c) throw new Error("Tool call not found");
|
|
351
|
+
if (call.type) c.type = call.type;
|
|
352
|
+
c.function.name = c.function.name + (call.function?.name || "");
|
|
353
|
+
c.args = c.args.concat(call.function?.arguments || "");
|
|
354
|
+
}
|
|
355
|
+
function handleCompleteToolCall(toolCalls, call) {
|
|
356
|
+
toolCalls.push({
|
|
357
|
+
id: call.id || (0, _aigne_uuid.v7)(),
|
|
358
|
+
type: "function",
|
|
359
|
+
function: {
|
|
360
|
+
name: call.function?.name || "",
|
|
361
|
+
arguments: (0, _aigne_core.safeParseJSON)(call.function?.arguments || "{}")
|
|
362
|
+
},
|
|
363
|
+
args: call.function?.arguments || ""
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
exports.OpenAIChatModel = OpenAIChatModel;
|
|
369
|
+
exports.contentsFromInputMessages = contentsFromInputMessages;
|
|
370
|
+
exports.openAIChatModelOptionsSchema = openAIChatModelOptionsSchema;
|
|
371
|
+
exports.toolsFromInputTools = toolsFromInputTools;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { AgentInvokeOptions, AgentProcessResult, ChatModel, ChatModelInput, ChatModelInputMessage, ChatModelInputTool, ChatModelOptions, ChatModelOutput } from "@aigne/core";
|
|
2
|
+
import { PromiseOrValue } from "@aigne/core/utils/type-utils";
|
|
3
|
+
import { ClientOptions, OpenAI as OpenAI$1 } from "openai";
|
|
4
|
+
import { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/openai-chat-model.d.ts
|
|
8
|
+
interface OpenAIChatModelCapabilities {
|
|
9
|
+
supportsNativeStructuredOutputs: boolean;
|
|
10
|
+
supportsEndWithSystemMessage: boolean;
|
|
11
|
+
supportsToolsUseWithJsonSchema: boolean;
|
|
12
|
+
supportsParallelToolCalls: boolean;
|
|
13
|
+
supportsToolsEmptyParameters: boolean;
|
|
14
|
+
supportsToolStreaming: boolean;
|
|
15
|
+
supportsTemperature: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Configuration options for OpenAI Chat Model
|
|
19
|
+
*/
|
|
20
|
+
interface OpenAIChatModelOptions extends ChatModelOptions {
|
|
21
|
+
/**
|
|
22
|
+
* API key for OpenAI API
|
|
23
|
+
*
|
|
24
|
+
* If not provided, will look for OPENAI_API_KEY in environment variables
|
|
25
|
+
*/
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Base URL for OpenAI API
|
|
29
|
+
*
|
|
30
|
+
* Useful for proxies or alternate endpoints
|
|
31
|
+
*/
|
|
32
|
+
baseURL?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Client options for OpenAI API
|
|
35
|
+
*/
|
|
36
|
+
clientOptions?: Partial<ClientOptions>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @hidden
|
|
40
|
+
*/
|
|
41
|
+
declare const openAIChatModelOptionsSchema: z.ZodObject<{
|
|
42
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
43
|
+
baseURL: z.ZodOptional<z.ZodString>;
|
|
44
|
+
model: z.ZodOptional<z.ZodString>;
|
|
45
|
+
modelOptions: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
model: z.ZodOptional<z.ZodString>;
|
|
47
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
topP: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
frequencyPenalty: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
presencePenalty: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
parallelToolCalls: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
parallelToolCalls: boolean;
|
|
54
|
+
model?: string | undefined;
|
|
55
|
+
temperature?: number | undefined;
|
|
56
|
+
topP?: number | undefined;
|
|
57
|
+
frequencyPenalty?: number | undefined;
|
|
58
|
+
presencePenalty?: number | undefined;
|
|
59
|
+
}, {
|
|
60
|
+
model?: string | undefined;
|
|
61
|
+
temperature?: number | undefined;
|
|
62
|
+
topP?: number | undefined;
|
|
63
|
+
frequencyPenalty?: number | undefined;
|
|
64
|
+
presencePenalty?: number | undefined;
|
|
65
|
+
parallelToolCalls?: boolean | undefined;
|
|
66
|
+
}>>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
baseURL?: string | undefined;
|
|
69
|
+
apiKey?: string | undefined;
|
|
70
|
+
model?: string | undefined;
|
|
71
|
+
modelOptions?: {
|
|
72
|
+
parallelToolCalls: boolean;
|
|
73
|
+
model?: string | undefined;
|
|
74
|
+
temperature?: number | undefined;
|
|
75
|
+
topP?: number | undefined;
|
|
76
|
+
frequencyPenalty?: number | undefined;
|
|
77
|
+
presencePenalty?: number | undefined;
|
|
78
|
+
} | undefined;
|
|
79
|
+
}, {
|
|
80
|
+
baseURL?: string | undefined;
|
|
81
|
+
apiKey?: string | undefined;
|
|
82
|
+
model?: string | undefined;
|
|
83
|
+
modelOptions?: {
|
|
84
|
+
model?: string | undefined;
|
|
85
|
+
temperature?: number | undefined;
|
|
86
|
+
topP?: number | undefined;
|
|
87
|
+
frequencyPenalty?: number | undefined;
|
|
88
|
+
presencePenalty?: number | undefined;
|
|
89
|
+
parallelToolCalls?: boolean | undefined;
|
|
90
|
+
} | undefined;
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Implementation of the ChatModel interface for OpenAI's API
|
|
94
|
+
*
|
|
95
|
+
* This model provides access to OpenAI's capabilities including:
|
|
96
|
+
* - Text generation
|
|
97
|
+
* - Tool use with parallel tool calls
|
|
98
|
+
* - JSON structured output
|
|
99
|
+
* - Image understanding
|
|
100
|
+
*
|
|
101
|
+
* Default model: 'gpt-4o-mini'
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* Here's how to create and use an OpenAI chat model:
|
|
105
|
+
* {@includeCode ../test/openai-chat-model.test.ts#example-openai-chat-model}
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* Here's an example with streaming response:
|
|
109
|
+
* {@includeCode ../test/openai-chat-model.test.ts#example-openai-chat-model-stream}
|
|
110
|
+
*/
|
|
111
|
+
declare class OpenAIChatModel extends ChatModel {
|
|
112
|
+
options?: OpenAIChatModelOptions | undefined;
|
|
113
|
+
constructor(options?: OpenAIChatModelOptions | undefined);
|
|
114
|
+
/**
|
|
115
|
+
* @hidden
|
|
116
|
+
*/
|
|
117
|
+
protected _client?: OpenAI$1;
|
|
118
|
+
protected apiKeyEnvName: string;
|
|
119
|
+
protected apiKeyDefault: string | undefined;
|
|
120
|
+
protected supportsNativeStructuredOutputs: boolean;
|
|
121
|
+
protected supportsToolsUseWithJsonSchema: boolean;
|
|
122
|
+
protected supportsParallelToolCalls: boolean;
|
|
123
|
+
protected supportsToolsEmptyParameters: boolean;
|
|
124
|
+
protected supportsToolStreaming: boolean;
|
|
125
|
+
protected supportsTemperature: boolean;
|
|
126
|
+
get client(): OpenAI$1;
|
|
127
|
+
get credential(): {
|
|
128
|
+
url: string | undefined;
|
|
129
|
+
apiKey: string | undefined;
|
|
130
|
+
model: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Process the input and generate a response
|
|
134
|
+
* @param input The input to process
|
|
135
|
+
* @returns The generated response
|
|
136
|
+
*/
|
|
137
|
+
process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
138
|
+
private getReasoningEffort;
|
|
139
|
+
private _process;
|
|
140
|
+
private getParallelToolCalls;
|
|
141
|
+
protected getRunMessages(input: ChatModelInput): Promise<ChatCompletionMessageParam[]>;
|
|
142
|
+
private getRunResponseFormat;
|
|
143
|
+
private requestStructuredOutput;
|
|
144
|
+
private extractResultFromStream;
|
|
145
|
+
/**
|
|
146
|
+
* Controls how optional fields are handled in JSON schema conversion
|
|
147
|
+
* - "anyOf": All fields are required but can be null (default)
|
|
148
|
+
* - "optional": Fields marked as optional in schema remain optional
|
|
149
|
+
*/
|
|
150
|
+
protected optionalFieldMode?: "anyOf" | "optional";
|
|
151
|
+
protected jsonSchemaToOpenAIJsonSchema(schema: Record<string, unknown>): Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* @hidden
|
|
155
|
+
*/
|
|
156
|
+
declare function contentsFromInputMessages(messages: ChatModelInputMessage[]): Promise<ChatCompletionMessageParam[]>;
|
|
157
|
+
/**
|
|
158
|
+
* @hidden
|
|
159
|
+
*/
|
|
160
|
+
declare function toolsFromInputTools(tools?: ChatModelInputTool[], options?: {
|
|
161
|
+
addTypeToEmptyParameters?: boolean;
|
|
162
|
+
}): ChatCompletionTool[] | undefined;
|
|
163
|
+
//#endregion
|
|
164
|
+
export { OpenAIChatModel, OpenAIChatModelCapabilities, OpenAIChatModelOptions, contentsFromInputMessages, openAIChatModelOptionsSchema, toolsFromInputTools };
|
|
165
|
+
//# sourceMappingURL=openai-chat-model.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-chat-model.d.cts","names":[],"sources":["../src/openai-chat-model.ts"],"mappings":";;;;;;;UAoCiB,2BAAA;EAAA,+BAAA;EAAA,4BAAA;EAAA,8BAAA;EAAA,yBAAA;EAAA,4BAAA;EAAA,qBAAA;EAAA,mBAAA;AAAA;AAAA;AAkBjB;;AAlBiB,UAkBA,sBAAA,SAA+B,gBAAA;EAAA;;AAwBhD;;;EAxBgD,MAAA;EAAA;;AAwBhD;;;EAxBgD,OAAA;EAAA;;AAwBhD;EAxBgD,aAAA,GAkB9B,OAAA,CAAQ,aAAA;AAAA;AAAA;;;AAAA,cAMb,4BAAA,EAA4B,CAAA,CAAA,SAAA;EAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCzC;;;;;;;;;;;;;;;;cAAa,eAAA,SAAwB,SAAA;EAAA,OAAA,GACG,sBAAA;EAAA,YAAA,OAAA,GAAA,sBAAA;EAAA;;;EAAA,UAAA,OAAA,GAWlB,QAAA;EAAA,UAAA,aAAA;EAAA,UAAA,aAAA;EAAA,UAAA,+BAAA;EAAA,UAAA,8BAAA;EAAA,UAAA,yBAAA;EAAA,UAAA,4BAAA;EAAA,UAAA,qBAAA;EAAA,UAAA,mBAAA;EAAA,IAAA,OAAA,GAWV,QAAA;EAAA,IAAA,WAAA;IAAA,GAAA;IAAA,MAAA;IAAA,KAAA;EAAA;EAAA;;;;;EAAA,QAAA,KAAA,EA6BD,cAAA,EAAA,OAAA,EACE,kBAAA,GACR,cAAA,CAAe,kBAAA,CAAmB,eAAA;EAAA,QAAA,kBAAA;EAAA,QAAA,QAAA;EAAA,QAAA,oBAAA;EAAA,UAAA,eAAA,KAAA,EAkGC,cAAA,GAAiB,OAAA,CAAQ,0BAAA;EAAA,QAAA,oBAAA;EAAA,QAAA,uBAAA;EAAA,QAAA,uBAAA;EAAA;;;;;EAAA,UAAA,iBAAA;EAAA,UAAA,6BAAA,MAAA,EAyNhB,MAAA,oBAA0B,MAAA;AAAA;AAAA;;;AAAA,iBA+CrD,yBAAA,CAAA,QAAA,EACV,qBAAA,KACT,OAAA,CAAQ,0BAAA;AAAA;;;AAAA,iBAmDK,mBAAA,CAAA,KAAA,GACN,kBAAA,IAAA,OAAA;EAAA,wBAAA;AAAA,IAEP,kBAAA"}
|