@codemation/core-nodes 0.4.2 → 1.0.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 +51 -0
- package/dist/index.cjs +480 -410
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +220 -116
- package/dist/index.d.ts +220 -116
- package/dist/index.js +476 -403
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/chatModels/OpenAIChatModelFactory.ts +17 -8
- package/src/chatModels/OpenAiStrictJsonSchemaFactory.ts +123 -0
- package/src/index.ts +1 -1
- package/src/nodes/AIAgentExecutionHelpersFactory.ts +45 -59
- package/src/nodes/AIAgentNode.ts +293 -288
- package/src/nodes/AgentMessageFactory.ts +57 -49
- package/src/nodes/AgentStructuredOutputRunner.ts +65 -71
- package/src/nodes/AgentToolExecutionCoordinator.ts +3 -14
- package/src/nodes/aiAgentSupport.types.ts +7 -2
- package/src/chatModels/OpenAIStructuredOutputMethodFactory.ts +0 -46
|
@@ -1,69 +1,77 @@
|
|
|
1
1
|
import type { AgentMessageDto, AgentToolCall } from "@codemation/core";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { AssistantModelMessage, ModelMessage, ToolModelMessage } from "ai";
|
|
4
4
|
|
|
5
|
+
import type { ExecutedToolCall } from "./aiAgentSupport.types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AI-SDK-shaped message construction for the AIAgent stack. Emits plain `ModelMessage[]`
|
|
9
|
+
* ( `{ role: 'system' | 'user' | 'assistant' | 'tool', content: ... }` ) as consumed by
|
|
10
|
+
* `generateText({ messages })` from the `ai` package.
|
|
11
|
+
*/
|
|
5
12
|
export class AgentMessageFactory {
|
|
6
|
-
static createPromptMessages(messages: ReadonlyArray<AgentMessageDto>): ReadonlyArray<
|
|
13
|
+
static createPromptMessages(messages: ReadonlyArray<AgentMessageDto>): ReadonlyArray<ModelMessage> {
|
|
7
14
|
return messages.map((message) => this.createPromptMessage(message));
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
static
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
static createToolMessage(toolCallId: string, content: string): ToolMessage {
|
|
23
|
-
return new ToolMessage({ tool_call_id: toolCallId, content });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static extractContent(message: unknown): string {
|
|
27
|
-
if (typeof message === "string") return message;
|
|
28
|
-
if (!this.isRecord(message)) return String(message);
|
|
29
|
-
const content = message.content;
|
|
30
|
-
if (typeof content === "string") return content;
|
|
31
|
-
if (Array.isArray(content)) {
|
|
32
|
-
return content
|
|
33
|
-
.map((part) => {
|
|
34
|
-
if (typeof part === "string") return part;
|
|
35
|
-
if (this.isRecord(part) && typeof part.text === "string") return part.text;
|
|
36
|
-
return JSON.stringify(part);
|
|
37
|
-
})
|
|
38
|
-
.join("\n");
|
|
17
|
+
/**
|
|
18
|
+
* Builds the assistant message that contains optional text plus one or more tool-call parts,
|
|
19
|
+
* matching the shape AI SDK emits between steps.
|
|
20
|
+
*/
|
|
21
|
+
static createAssistantWithToolCalls(
|
|
22
|
+
text: string | undefined,
|
|
23
|
+
toolCalls: ReadonlyArray<AgentToolCall>,
|
|
24
|
+
): AssistantModelMessage {
|
|
25
|
+
const content: AssistantModelMessage["content"] = [];
|
|
26
|
+
if (text && text.length > 0) {
|
|
27
|
+
content.push({ type: "text", text });
|
|
39
28
|
}
|
|
40
|
-
|
|
29
|
+
for (const toolCall of toolCalls) {
|
|
30
|
+
content.push({
|
|
31
|
+
type: "tool-call",
|
|
32
|
+
toolCallId: toolCall.id ?? toolCall.name,
|
|
33
|
+
toolName: toolCall.name,
|
|
34
|
+
input: toolCall.input ?? {},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return { role: "assistant", content };
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Builds the `{ role: "tool", content: [{ type: "tool-result", ... }, ...] }` message returned
|
|
42
|
+
* to the model after each tool round.
|
|
43
|
+
*/
|
|
44
|
+
static createToolResultsMessage(executedToolCalls: ReadonlyArray<ExecutedToolCall>): ToolModelMessage {
|
|
45
|
+
return {
|
|
46
|
+
role: "tool",
|
|
47
|
+
content: executedToolCalls.map((executed) => ({
|
|
48
|
+
type: "tool-result",
|
|
49
|
+
toolCallId: executed.toolCallId,
|
|
50
|
+
toolName: executed.toolName,
|
|
51
|
+
output: {
|
|
52
|
+
type: "json",
|
|
53
|
+
value: AgentMessageFactory.toToolResultJson(executed.result),
|
|
54
|
+
},
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
private static
|
|
57
|
-
|
|
59
|
+
private static toToolResultJson(value: unknown): import("ai").JSONValue {
|
|
60
|
+
if (value === undefined) return null;
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(JSON.stringify(value)) as import("ai").JSONValue;
|
|
63
|
+
} catch {
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
private static createPromptMessage(message: AgentMessageDto):
|
|
68
|
+
private static createPromptMessage(message: AgentMessageDto): ModelMessage {
|
|
61
69
|
if (message.role === "system") {
|
|
62
|
-
return
|
|
70
|
+
return { role: "system", content: message.content };
|
|
63
71
|
}
|
|
64
72
|
if (message.role === "assistant") {
|
|
65
|
-
return
|
|
73
|
+
return { role: "assistant", content: message.content };
|
|
66
74
|
}
|
|
67
|
-
return
|
|
75
|
+
return { role: "user", content: message.content };
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChatModelConfig,
|
|
3
|
-
ChatModelStructuredOutputOptions,
|
|
4
|
-
LangChainChatModelLike,
|
|
5
|
-
LangChainStructuredOutputModelLike,
|
|
6
|
-
ZodSchemaAny,
|
|
7
|
-
} from "@codemation/core";
|
|
1
|
+
import type { ChatLanguageModel, ChatModelConfig, StructuredOutputOptions, ZodSchemaAny } from "@codemation/core";
|
|
8
2
|
import { inject, injectable } from "@codemation/core";
|
|
9
3
|
|
|
10
|
-
import {
|
|
4
|
+
import type { ModelMessage } from "ai";
|
|
11
5
|
import { ZodError } from "zod";
|
|
12
6
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
7
|
+
import { OpenAIChatModelFactory } from "../chatModels/OpenAIChatModelFactory";
|
|
8
|
+
import { OpenAiStrictJsonSchemaFactory } from "../chatModels/OpenAiStrictJsonSchemaFactory";
|
|
15
9
|
import { AgentStructuredOutputRepairPromptFactory } from "./AgentStructuredOutputRepairPromptFactory";
|
|
10
|
+
import { AgentMessageFactory } from "./AgentMessageFactory";
|
|
16
11
|
|
|
17
12
|
interface ParsedStructuredOutputSuccess<TValue> {
|
|
18
13
|
readonly ok: true;
|
|
@@ -27,50 +22,54 @@ interface ParsedStructuredOutputFailure {
|
|
|
27
22
|
|
|
28
23
|
type ParsedStructuredOutputResult<TValue> = ParsedStructuredOutputSuccess<TValue> | ParsedStructuredOutputFailure;
|
|
29
24
|
|
|
25
|
+
export type StructuredOutputSchemaForModel = ZodSchemaAny | Readonly<Record<string, unknown>>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Orchestrates a 2-attempt repair loop on top of `generateText({ output: Output.object(...) })`.
|
|
29
|
+
*
|
|
30
|
+
* Strategy:
|
|
31
|
+
* 1. If the caller already has a raw final text (from a prior tool-calling turn), try parsing it
|
|
32
|
+
* directly against the schema — fast path for models that already emit strict JSON.
|
|
33
|
+
* 2. Otherwise, run a native structured-output call via {@link invokeStructuredModel}. For the
|
|
34
|
+
* OpenAI-strict path, a {@link OpenAiStrictJsonSchemaFactory}-built JSON Schema record is
|
|
35
|
+
* handed to AI SDK's `jsonSchema(...)` wrapper (preserves `additionalProperties: false` at
|
|
36
|
+
* every object depth).
|
|
37
|
+
* 3. If the structured call fails (AI_NoObjectGeneratedError / ZodError / schema reject), run a
|
|
38
|
+
* text-mode repair prompt with the validation error appended, up to 2 attempts.
|
|
39
|
+
*/
|
|
30
40
|
@injectable()
|
|
31
41
|
export class AgentStructuredOutputRunner {
|
|
32
42
|
private static readonly repairAttemptCount = 2;
|
|
43
|
+
private static readonly structuredOutputSchemaName = "agent_output";
|
|
33
44
|
|
|
34
45
|
constructor(
|
|
35
46
|
@inject(AgentStructuredOutputRepairPromptFactory)
|
|
36
47
|
private readonly repairPromptFactory: AgentStructuredOutputRepairPromptFactory,
|
|
37
|
-
@inject(
|
|
38
|
-
private readonly
|
|
48
|
+
@inject(OpenAiStrictJsonSchemaFactory)
|
|
49
|
+
private readonly openAiStrictJsonSchemaFactory: OpenAiStrictJsonSchemaFactory,
|
|
39
50
|
) {}
|
|
40
51
|
|
|
41
52
|
async resolve<TOutput>(
|
|
42
53
|
args: Readonly<{
|
|
43
|
-
model:
|
|
54
|
+
model: ChatLanguageModel;
|
|
44
55
|
chatModelConfig: ChatModelConfig;
|
|
45
56
|
schema: ZodSchemaAny;
|
|
46
|
-
conversation: ReadonlyArray<
|
|
47
|
-
|
|
57
|
+
conversation: ReadonlyArray<ModelMessage>;
|
|
58
|
+
rawFinalText?: string;
|
|
48
59
|
agentName: string;
|
|
49
60
|
nodeId: string;
|
|
50
|
-
invokeTextModel: (messages: ReadonlyArray<
|
|
61
|
+
invokeTextModel: (messages: ReadonlyArray<ModelMessage>) => Promise<{ text: string }>;
|
|
51
62
|
invokeStructuredModel: (
|
|
52
|
-
|
|
53
|
-
messages: ReadonlyArray<
|
|
63
|
+
schema: StructuredOutputSchemaForModel,
|
|
64
|
+
messages: ReadonlyArray<ModelMessage>,
|
|
65
|
+
options: StructuredOutputOptions | undefined,
|
|
54
66
|
) => Promise<unknown>;
|
|
55
67
|
}>,
|
|
56
68
|
): Promise<TOutput> {
|
|
57
69
|
let lastFailure: ParsedStructuredOutputFailure | undefined;
|
|
58
70
|
|
|
59
|
-
if (args.
|
|
60
|
-
const directResult = this.tryParseAndValidate<TOutput>(
|
|
61
|
-
AgentMessageFactory.extractContent(args.rawFinalResponse),
|
|
62
|
-
args.schema,
|
|
63
|
-
);
|
|
64
|
-
if (directResult.ok) {
|
|
65
|
-
return directResult.value;
|
|
66
|
-
}
|
|
67
|
-
lastFailure = directResult;
|
|
68
|
-
} else if (!this.supportsNativeStructuredOutput(args.model)) {
|
|
69
|
-
const rawResponse = await args.invokeTextModel(args.conversation);
|
|
70
|
-
const directResult = this.tryParseAndValidate<TOutput>(
|
|
71
|
-
AgentMessageFactory.extractContent(rawResponse),
|
|
72
|
-
args.schema,
|
|
73
|
-
);
|
|
71
|
+
if (args.rawFinalText !== undefined) {
|
|
72
|
+
const directResult = this.tryParseAndValidate<TOutput>(args.rawFinalText, args.schema);
|
|
74
73
|
if (directResult.ok) {
|
|
75
74
|
return directResult.value;
|
|
76
75
|
}
|
|
@@ -78,19 +77,18 @@ export class AgentStructuredOutputRunner {
|
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
try {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
lastFailure = nativeResult;
|
|
80
|
+
const structuredOptions = this.resolveStructuredOutputOptions(args.chatModelConfig);
|
|
81
|
+
const schemaForModel = this.resolveOutputSchemaForModel(args.schema, structuredOptions);
|
|
82
|
+
const nativeResult = this.tryValidateStructuredValue<TOutput>(
|
|
83
|
+
await args.invokeStructuredModel(schemaForModel, args.conversation, structuredOptions),
|
|
84
|
+
args.schema,
|
|
85
|
+
);
|
|
86
|
+
if (nativeResult.ok) {
|
|
87
|
+
return nativeResult.value;
|
|
91
88
|
}
|
|
89
|
+
lastFailure = nativeResult;
|
|
92
90
|
} catch (error) {
|
|
93
|
-
lastFailure = {
|
|
91
|
+
lastFailure = lastFailure ?? {
|
|
94
92
|
ok: false,
|
|
95
93
|
invalidContent: "",
|
|
96
94
|
validationError: `Native structured output failed: ${this.summarizeError(error)}`,
|
|
@@ -112,16 +110,16 @@ export class AgentStructuredOutputRunner {
|
|
|
112
110
|
private async retryWithRepairPrompt<TOutput>(
|
|
113
111
|
args: Readonly<{
|
|
114
112
|
schema: ZodSchemaAny;
|
|
115
|
-
conversation: ReadonlyArray<
|
|
113
|
+
conversation: ReadonlyArray<ModelMessage>;
|
|
116
114
|
lastFailure: ParsedStructuredOutputFailure;
|
|
117
115
|
agentName: string;
|
|
118
116
|
nodeId: string;
|
|
119
|
-
invokeTextModel: (messages: ReadonlyArray<
|
|
117
|
+
invokeTextModel: (messages: ReadonlyArray<ModelMessage>) => Promise<{ text: string }>;
|
|
120
118
|
}>,
|
|
121
119
|
): Promise<TOutput> {
|
|
122
120
|
let failure = args.lastFailure;
|
|
123
121
|
for (let attempt = 1; attempt <= AgentStructuredOutputRunner.repairAttemptCount; attempt++) {
|
|
124
|
-
const repairMessages = [
|
|
122
|
+
const repairMessages: ReadonlyArray<ModelMessage> = [
|
|
125
123
|
...args.conversation,
|
|
126
124
|
...AgentMessageFactory.createPromptMessages(
|
|
127
125
|
this.repairPromptFactory.create({
|
|
@@ -132,10 +130,7 @@ export class AgentStructuredOutputRunner {
|
|
|
132
130
|
),
|
|
133
131
|
];
|
|
134
132
|
const repairResponse = await args.invokeTextModel(repairMessages);
|
|
135
|
-
const repairResult = this.tryParseAndValidate<TOutput>(
|
|
136
|
-
AgentMessageFactory.extractContent(repairResponse),
|
|
137
|
-
args.schema,
|
|
138
|
-
);
|
|
133
|
+
const repairResult = this.tryParseAndValidate<TOutput>(repairResponse.text, args.schema);
|
|
139
134
|
if (repairResult.ok) {
|
|
140
135
|
return repairResult.value;
|
|
141
136
|
}
|
|
@@ -146,29 +141,27 @@ export class AgentStructuredOutputRunner {
|
|
|
146
141
|
);
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
):
|
|
154
|
-
if (
|
|
144
|
+
/**
|
|
145
|
+
* Chooses strict mode for OpenAI chat-model configs, off otherwise. Extendable in future for
|
|
146
|
+
* other providers that adopt the same "supply a JSON Schema record directly" contract.
|
|
147
|
+
*/
|
|
148
|
+
private resolveStructuredOutputOptions(chatModelConfig: ChatModelConfig): StructuredOutputOptions | undefined {
|
|
149
|
+
if (chatModelConfig.type !== OpenAIChatModelFactory) {
|
|
155
150
|
return undefined;
|
|
156
151
|
}
|
|
157
|
-
|
|
158
|
-
return model.withStructuredOutput(schema, options);
|
|
152
|
+
return { strict: true, schemaName: AgentStructuredOutputRunner.structuredOutputSchemaName };
|
|
159
153
|
}
|
|
160
154
|
|
|
161
|
-
private
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return typeof model.withStructuredOutput === "function";
|
|
155
|
+
private resolveOutputSchemaForModel(
|
|
156
|
+
schema: ZodSchemaAny,
|
|
157
|
+
options: StructuredOutputOptions | undefined,
|
|
158
|
+
): StructuredOutputSchemaForModel {
|
|
159
|
+
if (!options?.strict) {
|
|
160
|
+
return schema;
|
|
161
|
+
}
|
|
162
|
+
return this.openAiStrictJsonSchemaFactory.createStructuredOutputRecord(schema, {
|
|
163
|
+
schemaName: options.schemaName ?? AgentStructuredOutputRunner.structuredOutputSchemaName,
|
|
164
|
+
});
|
|
172
165
|
}
|
|
173
166
|
|
|
174
167
|
private tryParseAndValidate<TOutput>(content: string, schema: ZodSchemaAny): ParsedStructuredOutputResult<TOutput> {
|
|
@@ -214,7 +207,8 @@ export class AgentStructuredOutputRunner {
|
|
|
214
207
|
|
|
215
208
|
private toJson(value: unknown): string {
|
|
216
209
|
try {
|
|
217
|
-
|
|
210
|
+
const serialized = JSON.stringify(value);
|
|
211
|
+
return serialized ?? String(value);
|
|
218
212
|
} catch (error) {
|
|
219
213
|
return `<<unserializable: ${this.summarizeError(error)}>>`;
|
|
220
214
|
}
|
|
@@ -71,8 +71,8 @@ export class AgentToolExecutionCoordinator {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
try {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
74
|
+
const result = await plannedToolCall.binding.execute(plannedToolCall.toolCall.input ?? {});
|
|
75
|
+
const serialized = typeof result === "string" ? result : JSON.stringify(result);
|
|
76
76
|
const finishedAt = new Date();
|
|
77
77
|
await ctx.nodeState?.markCompleted({
|
|
78
78
|
nodeId: plannedToolCall.nodeId,
|
|
@@ -113,7 +113,7 @@ export class AgentToolExecutionCoordinator {
|
|
|
113
113
|
const classification = this.errorClassifier.classify({
|
|
114
114
|
error,
|
|
115
115
|
toolName: plannedToolCall.binding.config.name,
|
|
116
|
-
schema: plannedToolCall.binding.
|
|
116
|
+
schema: plannedToolCall.binding.inputSchema,
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
if (classification.kind !== "repairable_validation_error") {
|
|
@@ -324,17 +324,6 @@ export class AgentToolExecutionCoordinator {
|
|
|
324
324
|
return `Your previous tool call for "${toolName}" was invalid because field "${fieldPath}" failed validation: ${firstIssue.message}`;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
private parseToolOutput(serialized: unknown): unknown {
|
|
328
|
-
if (typeof serialized !== "string") {
|
|
329
|
-
return serialized;
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
return JSON.parse(serialized) as unknown;
|
|
333
|
-
} catch {
|
|
334
|
-
return serialized;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
327
|
private toJsonValue(value: unknown): JsonValue | undefined {
|
|
339
328
|
if (value === undefined) {
|
|
340
329
|
return undefined;
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
ToolExecuteArgs,
|
|
7
7
|
ZodSchemaAny,
|
|
8
8
|
} from "@codemation/core";
|
|
9
|
-
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
10
9
|
|
|
11
10
|
export class AgentItemPortMap {
|
|
12
11
|
static fromItem(item: Item): NodeInputsByPort {
|
|
@@ -23,9 +22,15 @@ export type ResolvedTool = Readonly<{
|
|
|
23
22
|
}>;
|
|
24
23
|
}>;
|
|
25
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Per-item binding of a tool: the user config plus the resolved runtime and a snapshot of the
|
|
27
|
+
* original Zod `inputSchema` used to convert to AI SDK `Tool` + OpenAI-strict JSON Schema for
|
|
28
|
+
* repair prompts.
|
|
29
|
+
*/
|
|
26
30
|
export type ItemScopedToolBinding = Readonly<{
|
|
27
31
|
config: ToolConfig;
|
|
28
|
-
|
|
32
|
+
inputSchema: ZodSchemaAny;
|
|
33
|
+
execute(input: unknown): Promise<unknown>;
|
|
29
34
|
}>;
|
|
30
35
|
|
|
31
36
|
export type PlannedToolCall = Readonly<{
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { ChatModelConfig, ChatModelStructuredOutputOptions } from "@codemation/core";
|
|
2
|
-
import { injectable } from "@codemation/core";
|
|
3
|
-
|
|
4
|
-
import { OpenAIChatModelFactory } from "./OpenAIChatModelFactory";
|
|
5
|
-
|
|
6
|
-
@injectable()
|
|
7
|
-
export class OpenAIStructuredOutputMethodFactory {
|
|
8
|
-
private static readonly isoDatePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
9
|
-
|
|
10
|
-
create(chatModelConfig: ChatModelConfig): ChatModelStructuredOutputOptions | undefined {
|
|
11
|
-
if (chatModelConfig.type !== OpenAIChatModelFactory) {
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
const model = this.readModelName(chatModelConfig);
|
|
15
|
-
if (!model) {
|
|
16
|
-
return { method: "functionCalling", strict: true };
|
|
17
|
-
}
|
|
18
|
-
return {
|
|
19
|
-
method: this.supportsJsonSchema(model) ? "jsonSchema" : "functionCalling",
|
|
20
|
-
strict: true,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private readModelName(chatModelConfig: ChatModelConfig): string | undefined {
|
|
25
|
-
const candidate = chatModelConfig as Readonly<{ model?: unknown }>;
|
|
26
|
-
return typeof candidate.model === "string" ? candidate.model : undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private supportsJsonSchema(model: string): boolean {
|
|
30
|
-
if (model === "gpt-4o" || model === "gpt-4o-mini") {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
return (
|
|
34
|
-
this.supportsSnapshotAtOrAfter(model, "gpt-4o-", "2024-08-06") ||
|
|
35
|
-
this.supportsSnapshotAtOrAfter(model, "gpt-4o-mini-", "2024-07-18")
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private supportsSnapshotAtOrAfter(model: string, prefix: string, minimumSnapshotDate: string): boolean {
|
|
40
|
-
if (!model.startsWith(prefix)) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const snapshotDate = model.slice(prefix.length);
|
|
44
|
-
return OpenAIStructuredOutputMethodFactory.isoDatePattern.test(snapshotDate) && snapshotDate >= minimumSnapshotDate;
|
|
45
|
-
}
|
|
46
|
-
}
|