universal-llm-client 3.1.0 → 4.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 +39 -0
- package/README.md +177 -0
- package/dist/ai-model.d.ts +89 -0
- package/dist/ai-model.d.ts.map +1 -1
- package/dist/ai-model.js +96 -0
- package/dist/ai-model.js.map +1 -1
- package/dist/auditor.d.ts +5 -1
- package/dist/auditor.d.ts.map +1 -1
- package/dist/auditor.js +9 -0
- package/dist/auditor.js.map +1 -1
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +52 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +143 -1
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +21 -0
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/ollama.d.ts +13 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +42 -7
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +4 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +51 -1
- package/dist/providers/openai.js.map +1 -1
- package/dist/router.d.ts +70 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +343 -0
- package/dist/router.js.map +1 -1
- package/dist/structured-output.d.ts +467 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +505 -0
- package/dist/structured-output.js.map +1 -0
- package/package.json +15 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAS7C,OAAO,KAAK,EACR,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,WAAW,EAId,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,qBAAa,sBAAuB,SAAQ,aAAa;gBACzC,OAAO,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,OAAO;IAalD,IAAI,CACN,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,eAAe,CAAC;IA4FpB,UAAU,CACb,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GACtB,cAAc,CAAC,YAAY,EAAE,eAAe,GAAG,IAAI,EAAE,OAAO,CAAC;IAqJ1D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAoBtC,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBpC,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAgC9B"}
|
package/dist/providers/openai.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { BaseLLMClient } from '../client.js';
|
|
8
8
|
import { httpRequest, httpStream, parseSSE, buildHeaders } from '../http.js';
|
|
9
9
|
import { StandardChatDecoder } from '../stream-decoder.js';
|
|
10
|
+
import { zodToJsonSchema, normalizeJsonSchema, } from '../structured-output.js';
|
|
10
11
|
export class OpenAICompatibleClient extends BaseLLMClient {
|
|
11
12
|
constructor(options, auditor) {
|
|
12
13
|
// Ensure URL ends with /v1 for standard endpoints
|
|
@@ -20,6 +21,7 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
20
21
|
// Chat
|
|
21
22
|
// ========================================================================
|
|
22
23
|
async chat(messages, options) {
|
|
24
|
+
// Structured output and tools can now be used together.\n // The provider sends both response_format and tools in the request.\n // The Router handles skipping validation when the response contains tool calls.
|
|
23
25
|
const url = `${this.options.url}/chat/completions`;
|
|
24
26
|
const tools = options?.tools ?? (Object.keys(this.toolRegistry).length > 0 ? this.getToolDefinitions() : undefined);
|
|
25
27
|
const body = {
|
|
@@ -27,6 +29,14 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
27
29
|
messages: this.convertMessages(messages),
|
|
28
30
|
...this.buildRequestParams(options),
|
|
29
31
|
};
|
|
32
|
+
// Handle structured output
|
|
33
|
+
const schemaOptions = this.extractSchemaOptions(options);
|
|
34
|
+
if (schemaOptions) {
|
|
35
|
+
body['response_format'] = this.buildResponseFormat(schemaOptions);
|
|
36
|
+
}
|
|
37
|
+
else if (options?.responseFormat) {
|
|
38
|
+
body['response_format'] = options.responseFormat;
|
|
39
|
+
}
|
|
30
40
|
if (tools?.length) {
|
|
31
41
|
body['tools'] = tools;
|
|
32
42
|
if (options?.toolChoice) {
|
|
@@ -63,10 +73,12 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
63
73
|
...tc,
|
|
64
74
|
id: tc.id || this.generateToolCallId(),
|
|
65
75
|
}));
|
|
76
|
+
// Get content, handling null case
|
|
77
|
+
const content = choice.message.content || '';
|
|
66
78
|
const result = {
|
|
67
79
|
message: {
|
|
68
80
|
role: 'assistant',
|
|
69
|
-
content
|
|
81
|
+
content,
|
|
70
82
|
tool_calls: toolCalls,
|
|
71
83
|
},
|
|
72
84
|
usage,
|
|
@@ -247,5 +259,43 @@ export class OpenAICompatibleClient extends BaseLLMClient {
|
|
|
247
259
|
params['max_tokens'] = options.maxTokens;
|
|
248
260
|
return params;
|
|
249
261
|
}
|
|
262
|
+
// ========================================================================
|
|
263
|
+
// Structured Output Helpers
|
|
264
|
+
// ========================================================================
|
|
265
|
+
/**
|
|
266
|
+
* Build OpenAI response_format for structured output.
|
|
267
|
+
*/
|
|
268
|
+
buildResponseFormat(options) {
|
|
269
|
+
let jsonSchema;
|
|
270
|
+
let name;
|
|
271
|
+
let description;
|
|
272
|
+
// Prefer jsonSchema if provided (handles raw JSON Schema case)
|
|
273
|
+
if (options.jsonSchema) {
|
|
274
|
+
// Use raw JSON Schema
|
|
275
|
+
jsonSchema = normalizeJsonSchema(options.jsonSchema);
|
|
276
|
+
name = options.name || 'response';
|
|
277
|
+
description = options.description;
|
|
278
|
+
}
|
|
279
|
+
else if (options.schema) {
|
|
280
|
+
// Convert Zod schema to JSON Schema
|
|
281
|
+
jsonSchema = zodToJsonSchema(options.schema);
|
|
282
|
+
name = options.name || 'response';
|
|
283
|
+
description = options.description;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Should not happen - we check this in extractSchemaOptions
|
|
287
|
+
throw new Error('Either schema or jsonSchema must be provided');
|
|
288
|
+
}
|
|
289
|
+
// OpenAI strict mode — configurable, defaults to true for reliable structured output
|
|
290
|
+
return {
|
|
291
|
+
type: 'json_schema',
|
|
292
|
+
json_schema: {
|
|
293
|
+
name,
|
|
294
|
+
...(description && { description }),
|
|
295
|
+
schema: jsonSchema,
|
|
296
|
+
strict: options.strict ?? true,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
250
300
|
}
|
|
251
301
|
//# sourceMappingURL=openai.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { BaseLLMClient } from './client.js';
|
|
|
14
14
|
import type { Auditor } from './auditor.js';
|
|
15
15
|
import type { LLMChatMessage, LLMChatResponse, ChatOptions, ModelMetadata } from './interfaces.js';
|
|
16
16
|
import type { DecodedEvent } from './stream-decoder.js';
|
|
17
|
+
import { type StructuredOutputResult } from './structured-output.js';
|
|
18
|
+
import { z } from 'zod';
|
|
17
19
|
export interface ProviderEntry {
|
|
18
20
|
/** Unique identifier for this provider entry */
|
|
19
21
|
id: string;
|
|
@@ -62,7 +64,20 @@ export declare class Router {
|
|
|
62
64
|
* On failure, retries with the next provider from the beginning.
|
|
63
65
|
*/
|
|
64
66
|
executeStream(fn: (client: BaseLLMClient) => AsyncGenerator<DecodedEvent, LLMChatResponse | void, unknown>, context?: string): AsyncGenerator<DecodedEvent, LLMChatResponse | void, unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated No longer needed — structured output and tools can now be used together.
|
|
69
|
+
*/
|
|
70
|
+
private validateOutputAndTools;
|
|
71
|
+
/**
|
|
72
|
+
* Extract schema from output options.
|
|
73
|
+
*/
|
|
74
|
+
private getSchemaFromOutput;
|
|
65
75
|
chat(messages: LLMChatMessage[], options?: ChatOptions): Promise<LLMChatResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* Chat with structured output using the output parameter.
|
|
78
|
+
* Validates response against the schema and returns structured property.
|
|
79
|
+
*/
|
|
80
|
+
private chatWithStructuredOutput;
|
|
66
81
|
chatWithTools(messages: LLMChatMessage[], options?: ChatOptions & {
|
|
67
82
|
maxIterations?: number;
|
|
68
83
|
}): Promise<LLMChatResponse>;
|
|
@@ -71,6 +86,61 @@ export declare class Router {
|
|
|
71
86
|
embedArray(texts: string[]): Promise<number[][]>;
|
|
72
87
|
getModels(): Promise<string[]>;
|
|
73
88
|
getModelInfo(): Promise<ModelMetadata>;
|
|
89
|
+
/**
|
|
90
|
+
* Generate structured output from the LLM with automatic failover.
|
|
91
|
+
* Validates the response against the provided Zod schema.
|
|
92
|
+
* Throws StructuredOutputError on validation failure.
|
|
93
|
+
*
|
|
94
|
+
* @template T The type inferred from the Zod schema
|
|
95
|
+
* @param schema Zod schema for validation
|
|
96
|
+
* @param messages Chat messages to send
|
|
97
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
98
|
+
* @returns Validated structured output
|
|
99
|
+
* @throws StructuredOutputError if validation fails
|
|
100
|
+
*/
|
|
101
|
+
generateStructured<T>(schema: z.ZodType<T>, messages: LLMChatMessage[], options?: ChatOptions): Promise<T>;
|
|
102
|
+
/**
|
|
103
|
+
* Try to generate structured output, returning a result object instead of throwing.
|
|
104
|
+
* Same as generateStructured but returns { ok: true, value } on success
|
|
105
|
+
* and { ok: false, error, rawOutput } on failure.
|
|
106
|
+
*
|
|
107
|
+
* @template T The type inferred from the Zod schema
|
|
108
|
+
* @param schema Zod schema for validation
|
|
109
|
+
* @param messages Chat messages to send
|
|
110
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
111
|
+
* @returns StructuredOutputResult<T> - either success with value or failure with error
|
|
112
|
+
*/
|
|
113
|
+
tryParseStructured<T>(schema: z.ZodType<T>, messages: LLMChatMessage[], options?: ChatOptions): Promise<StructuredOutputResult<T>>;
|
|
114
|
+
/**
|
|
115
|
+
* Stream structured output with partial validated objects.
|
|
116
|
+
*
|
|
117
|
+
* Yields partial validated objects as JSON generates, then returns the
|
|
118
|
+
* complete validated object on stream completion.
|
|
119
|
+
*
|
|
120
|
+
* For invalid partial JSON, no yield occurs (partial validation is best-effort).
|
|
121
|
+
* On stream completion, if the final JSON fails validation, throws StructuredOutputError.
|
|
122
|
+
*
|
|
123
|
+
* @template T The type inferred from the Zod schema
|
|
124
|
+
* @param schema Zod schema for validation
|
|
125
|
+
* @param messages Chat messages to send
|
|
126
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
127
|
+
* @yields Partial validated objects as the JSON stream progresses
|
|
128
|
+
* @returns Complete validated object on stream completion
|
|
129
|
+
* @throws StructuredOutputError if final validation fails
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const stream = model.generateStructuredStream(UserSchema, messages);
|
|
134
|
+
*
|
|
135
|
+
* for await (const partial of stream) {
|
|
136
|
+
* console.log('Partial:', partial); // Partial validated object
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* // Note: async generators don't have a simple way to get the return value
|
|
140
|
+
* // The last partial yielded will be the complete object when complete: true
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
generateStructuredStream<T>(schema: z.ZodType<T>, messages: LLMChatMessage[], options?: ChatOptions): AsyncGenerator<T, T, unknown>;
|
|
74
144
|
registerTool(name: string, description: string, parameters: import('./interfaces.js').LLMFunction['parameters'], handler: import('./interfaces.js').ToolHandler): void;
|
|
75
145
|
registerTools(tools: Array<{
|
|
76
146
|
name: string;
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EACR,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/router.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Not exposed publicly — AIModel delegates to it.
|
|
12
12
|
*/
|
|
13
13
|
import { NoopAuditor } from './auditor.js';
|
|
14
|
+
import { zodToJsonSchema, parseStructured, StructuredOutputError, StreamingJsonParser, } from './structured-output.js';
|
|
14
15
|
// ============================================================================
|
|
15
16
|
// Router
|
|
16
17
|
// ============================================================================
|
|
@@ -161,13 +162,163 @@ export class Router {
|
|
|
161
162
|
// ========================================================================
|
|
162
163
|
// Convenience Methods
|
|
163
164
|
// ========================================================================
|
|
165
|
+
/**
|
|
166
|
+
* @deprecated No longer needed — structured output and tools can now be used together.
|
|
167
|
+
*/
|
|
168
|
+
validateOutputAndTools(_options) {
|
|
169
|
+
// Structured output and tools are now allowed together.
|
|
170
|
+
// When both are provided, the provider sends both the schema constraint
|
|
171
|
+
// and tool definitions. If the model responds with tool calls,
|
|
172
|
+
// validation is skipped. If it responds with content, the schema is validated.
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Extract schema from output options.
|
|
176
|
+
*/
|
|
177
|
+
getSchemaFromOutput(output) {
|
|
178
|
+
if (output.schema) {
|
|
179
|
+
return {
|
|
180
|
+
schema: output.schema,
|
|
181
|
+
name: output.name,
|
|
182
|
+
description: output.description,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (output.jsonSchema) {
|
|
186
|
+
return {
|
|
187
|
+
jsonSchema: output.jsonSchema,
|
|
188
|
+
name: output.name,
|
|
189
|
+
description: output.description,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
throw new Error('output must have either schema or jsonSchema');
|
|
193
|
+
}
|
|
164
194
|
async chat(messages, options) {
|
|
195
|
+
// Validate that output and tools are not used together (VAL-API-005)
|
|
196
|
+
this.validateOutputAndTools(options);
|
|
197
|
+
// If output parameter is provided, use structured output flow (VAL-API-004)
|
|
198
|
+
if (options?.output) {
|
|
199
|
+
// Type assertion: we know output is defined at this point
|
|
200
|
+
return this.chatWithStructuredOutput(messages, options);
|
|
201
|
+
}
|
|
165
202
|
return this.execute(client => client.chat(messages, options), 'chat');
|
|
166
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Chat with structured output using the output parameter.
|
|
206
|
+
* Validates response against the schema and returns structured property.
|
|
207
|
+
*/
|
|
208
|
+
async chatWithStructuredOutput(messages, options) {
|
|
209
|
+
const { output } = options;
|
|
210
|
+
const schemaInfo = this.getSchemaFromOutput(output);
|
|
211
|
+
const schemaName = schemaInfo.name ?? 'response';
|
|
212
|
+
// Emit structured_request event
|
|
213
|
+
this.auditor.record({
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
type: 'structured_request',
|
|
216
|
+
provider: 'router',
|
|
217
|
+
schemaName,
|
|
218
|
+
});
|
|
219
|
+
// Build ChatOptions with schema for the provider
|
|
220
|
+
// Keep tools if provided — structured output and tools can work together
|
|
221
|
+
const { output: _, ...restOptions } = options;
|
|
222
|
+
const structuredOptions = {
|
|
223
|
+
...restOptions,
|
|
224
|
+
// Use jsonSchema for the provider
|
|
225
|
+
jsonSchema: 'jsonSchema' in schemaInfo ? schemaInfo.jsonSchema : zodToJsonSchema(schemaInfo.schema),
|
|
226
|
+
schemaName: schemaInfo.name,
|
|
227
|
+
schemaDescription: schemaInfo.description,
|
|
228
|
+
};
|
|
229
|
+
const start = Date.now();
|
|
230
|
+
// Get response from provider
|
|
231
|
+
const response = await this.execute(client => client.chat(messages, structuredOptions), 'chatWithStructuredOutput');
|
|
232
|
+
// If the response contains tool calls, skip validation and return as-is
|
|
233
|
+
// (the tool calling loop will handle the tool calls)
|
|
234
|
+
if (response.message.tool_calls && response.message.tool_calls.length > 0) {
|
|
235
|
+
return response;
|
|
236
|
+
}
|
|
237
|
+
// Extract text content from response
|
|
238
|
+
const content = typeof response.message.content === 'string'
|
|
239
|
+
? response.message.content
|
|
240
|
+
: response.message.content
|
|
241
|
+
.filter((part) => part.type === 'text')
|
|
242
|
+
.map(part => part.text)
|
|
243
|
+
.join('');
|
|
244
|
+
// Get the Zod schema for validation (use the one from output, or convert from jsonSchema)
|
|
245
|
+
const zodSchema = 'schema' in schemaInfo ? schemaInfo.schema : null;
|
|
246
|
+
if (!zodSchema) {
|
|
247
|
+
// If we only have jsonSchema without a Zod schema, we can't validate
|
|
248
|
+
// Return the parsed JSON without validation
|
|
249
|
+
try {
|
|
250
|
+
const structured = JSON.parse(content);
|
|
251
|
+
// Emit structured_response event on success
|
|
252
|
+
this.auditor.record({
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
type: 'structured_response',
|
|
255
|
+
provider: response.provider ?? 'router',
|
|
256
|
+
model: response.message.role,
|
|
257
|
+
duration: Date.now() - start,
|
|
258
|
+
schemaName,
|
|
259
|
+
usage: response.usage,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
...response,
|
|
263
|
+
structured,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
// JSON parse failed
|
|
268
|
+
const rawOutput = content;
|
|
269
|
+
// Emit structured_validation_error event
|
|
270
|
+
this.auditor.record({
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
type: 'structured_validation_error',
|
|
273
|
+
provider: response.provider ?? 'router',
|
|
274
|
+
schemaName,
|
|
275
|
+
error: error instanceof Error ? error.message : 'JSON parse failed',
|
|
276
|
+
rawOutput,
|
|
277
|
+
});
|
|
278
|
+
throw new StructuredOutputError(`Failed to parse JSON: ${rawOutput}`, { rawOutput: rawOutput, cause: error instanceof Error ? error : undefined });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Parse and validate against Zod schema
|
|
282
|
+
try {
|
|
283
|
+
const validated = parseStructured(zodSchema, content);
|
|
284
|
+
// Emit structured_response event on success
|
|
285
|
+
this.auditor.record({
|
|
286
|
+
timestamp: Date.now(),
|
|
287
|
+
type: 'structured_response',
|
|
288
|
+
provider: response.provider ?? 'router',
|
|
289
|
+
duration: Date.now() - start,
|
|
290
|
+
schemaName,
|
|
291
|
+
usage: response.usage,
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
...response,
|
|
295
|
+
structured: validated,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
// Emit structured_validation_error event
|
|
300
|
+
const rawOutput = content;
|
|
301
|
+
this.auditor.record({
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
type: 'structured_validation_error',
|
|
304
|
+
provider: response.provider ?? 'router',
|
|
305
|
+
schemaName,
|
|
306
|
+
error: error instanceof Error ? error.message : 'Validation failed',
|
|
307
|
+
rawOutput,
|
|
308
|
+
});
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
167
312
|
async chatWithTools(messages, options) {
|
|
168
313
|
return this.execute(client => client.chatWithTools(messages, options), 'chatWithTools');
|
|
169
314
|
}
|
|
170
315
|
async *chatStream(messages, options) {
|
|
316
|
+
// Structured output via output parameter is not supported on streaming
|
|
317
|
+
// Use generateStructuredStream() instead
|
|
318
|
+
if (options?.output) {
|
|
319
|
+
throw new Error('The "output" parameter is not supported with chatStream(). '
|
|
320
|
+
+ 'Use generateStructuredStream() for streaming structured output.');
|
|
321
|
+
}
|
|
171
322
|
return yield* this.executeStream(client => client.chatStream(messages, options), 'chatStream');
|
|
172
323
|
}
|
|
173
324
|
async embed(text) {
|
|
@@ -194,6 +345,198 @@ export class Router {
|
|
|
194
345
|
return this.execute(client => client.getModelInfo(), 'getModelInfo');
|
|
195
346
|
}
|
|
196
347
|
// ========================================================================
|
|
348
|
+
// Structured Output Methods
|
|
349
|
+
// ========================================================================
|
|
350
|
+
/**
|
|
351
|
+
* Generate structured output from the LLM with automatic failover.
|
|
352
|
+
* Validates the response against the provided Zod schema.
|
|
353
|
+
* Throws StructuredOutputError on validation failure.
|
|
354
|
+
*
|
|
355
|
+
* @template T The type inferred from the Zod schema
|
|
356
|
+
* @param schema Zod schema for validation
|
|
357
|
+
* @param messages Chat messages to send
|
|
358
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
359
|
+
* @returns Validated structured output
|
|
360
|
+
* @throws StructuredOutputError if validation fails
|
|
361
|
+
*/
|
|
362
|
+
async generateStructured(schema, messages, options) {
|
|
363
|
+
// Convert Zod schema to JSON Schema for providers
|
|
364
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
365
|
+
const schemaName = options?.schemaName ?? 'response';
|
|
366
|
+
// Emit structured_request event
|
|
367
|
+
this.auditor.record({
|
|
368
|
+
timestamp: Date.now(),
|
|
369
|
+
type: 'structured_request',
|
|
370
|
+
provider: 'router',
|
|
371
|
+
schemaName,
|
|
372
|
+
});
|
|
373
|
+
// Build ChatOptions with schema
|
|
374
|
+
const structuredOptions = {
|
|
375
|
+
...options,
|
|
376
|
+
jsonSchema,
|
|
377
|
+
};
|
|
378
|
+
const start = Date.now();
|
|
379
|
+
// Execute with failover
|
|
380
|
+
const response = await this.execute(client => client.chat(messages, structuredOptions), 'generateStructured');
|
|
381
|
+
// Parse and validate the response
|
|
382
|
+
const content = typeof response.message.content === 'string'
|
|
383
|
+
? response.message.content
|
|
384
|
+
: response.message.content
|
|
385
|
+
.filter((part) => part.type === 'text')
|
|
386
|
+
.map(part => part.text)
|
|
387
|
+
.join('');
|
|
388
|
+
try {
|
|
389
|
+
const result = parseStructured(schema, content);
|
|
390
|
+
// Emit structured_response event on success
|
|
391
|
+
this.auditor.record({
|
|
392
|
+
timestamp: Date.now(),
|
|
393
|
+
type: 'structured_response',
|
|
394
|
+
provider: response.provider ?? 'router',
|
|
395
|
+
duration: Date.now() - start,
|
|
396
|
+
schemaName,
|
|
397
|
+
usage: response.usage,
|
|
398
|
+
});
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
// Emit structured_validation_error event
|
|
403
|
+
this.auditor.record({
|
|
404
|
+
timestamp: Date.now(),
|
|
405
|
+
type: 'structured_validation_error',
|
|
406
|
+
provider: response.provider ?? 'router',
|
|
407
|
+
schemaName,
|
|
408
|
+
error: error instanceof Error ? error.message : 'Validation failed',
|
|
409
|
+
rawOutput: content,
|
|
410
|
+
});
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Try to generate structured output, returning a result object instead of throwing.
|
|
416
|
+
* Same as generateStructured but returns { ok: true, value } on success
|
|
417
|
+
* and { ok: false, error, rawOutput } on failure.
|
|
418
|
+
*
|
|
419
|
+
* @template T The type inferred from the Zod schema
|
|
420
|
+
* @param schema Zod schema for validation
|
|
421
|
+
* @param messages Chat messages to send
|
|
422
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
423
|
+
* @returns StructuredOutputResult<T> - either success with value or failure with error
|
|
424
|
+
*/
|
|
425
|
+
async tryParseStructured(schema, messages, options) {
|
|
426
|
+
try {
|
|
427
|
+
const value = await this.generateStructured(schema, messages, options);
|
|
428
|
+
return { ok: true, value };
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
// If error is already a StructuredOutputError, use it directly
|
|
432
|
+
if (error instanceof Error && 'rawOutput' in error) {
|
|
433
|
+
return {
|
|
434
|
+
ok: false,
|
|
435
|
+
error: error,
|
|
436
|
+
rawOutput: error.rawOutput,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
// Unexpected error - re-throw
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Stream structured output with partial validated objects.
|
|
445
|
+
*
|
|
446
|
+
* Yields partial validated objects as JSON generates, then returns the
|
|
447
|
+
* complete validated object on stream completion.
|
|
448
|
+
*
|
|
449
|
+
* For invalid partial JSON, no yield occurs (partial validation is best-effort).
|
|
450
|
+
* On stream completion, if the final JSON fails validation, throws StructuredOutputError.
|
|
451
|
+
*
|
|
452
|
+
* @template T The type inferred from the Zod schema
|
|
453
|
+
* @param schema Zod schema for validation
|
|
454
|
+
* @param messages Chat messages to send
|
|
455
|
+
* @param options Additional options (temperature, maxTokens, etc.)
|
|
456
|
+
* @yields Partial validated objects as the JSON stream progresses
|
|
457
|
+
* @returns Complete validated object on stream completion
|
|
458
|
+
* @throws StructuredOutputError if final validation fails
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* const stream = model.generateStructuredStream(UserSchema, messages);
|
|
463
|
+
*
|
|
464
|
+
* for await (const partial of stream) {
|
|
465
|
+
* console.log('Partial:', partial); // Partial validated object
|
|
466
|
+
* }
|
|
467
|
+
*
|
|
468
|
+
* // Note: async generators don't have a simple way to get the return value
|
|
469
|
+
* // The last partial yielded will be the complete object when complete: true
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
async *generateStructuredStream(schema, messages, options) {
|
|
473
|
+
// Convert Zod schema to JSON Schema for providers
|
|
474
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
475
|
+
const schemaName = options?.schemaName ?? 'response';
|
|
476
|
+
// Emit structured_request event
|
|
477
|
+
this.auditor.record({
|
|
478
|
+
timestamp: Date.now(),
|
|
479
|
+
type: 'structured_request',
|
|
480
|
+
provider: 'router',
|
|
481
|
+
schemaName,
|
|
482
|
+
});
|
|
483
|
+
// Build ChatOptions with schema
|
|
484
|
+
const structuredOptions = {
|
|
485
|
+
...options,
|
|
486
|
+
jsonSchema,
|
|
487
|
+
};
|
|
488
|
+
const start = Date.now();
|
|
489
|
+
// Stream with failover
|
|
490
|
+
const stream = this.executeStream(client => client.chatStream(messages, structuredOptions), 'generateStructuredStream');
|
|
491
|
+
// Accumulate text and yield partial validated objects
|
|
492
|
+
const parser = new StreamingJsonParser(schema);
|
|
493
|
+
let fullContent = '';
|
|
494
|
+
let lastYielded;
|
|
495
|
+
try {
|
|
496
|
+
for await (const event of stream) {
|
|
497
|
+
// Only process text events
|
|
498
|
+
if (event.type !== 'text')
|
|
499
|
+
continue;
|
|
500
|
+
fullContent += event.content;
|
|
501
|
+
// Try to parse partial JSON
|
|
502
|
+
const result = parser.feed(event.content);
|
|
503
|
+
// Yield if we got a valid partial and it's different from last
|
|
504
|
+
if (result.partial !== undefined) {
|
|
505
|
+
// Only yield if different from last (avoid duplicate yields)
|
|
506
|
+
if (lastYielded === undefined || JSON.stringify(result.partial) !== JSON.stringify(lastYielded)) {
|
|
507
|
+
lastYielded = result.partial;
|
|
508
|
+
yield result.partial;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Parse and validate the complete content
|
|
513
|
+
// This will throw StructuredOutputError on failure
|
|
514
|
+
const complete = parseStructured(schema, fullContent);
|
|
515
|
+
// Emit structured_response event on success
|
|
516
|
+
this.auditor.record({
|
|
517
|
+
timestamp: Date.now(),
|
|
518
|
+
type: 'structured_response',
|
|
519
|
+
provider: 'router',
|
|
520
|
+
schemaName,
|
|
521
|
+
duration: Date.now() - start,
|
|
522
|
+
});
|
|
523
|
+
// Return the complete validated object
|
|
524
|
+
return complete;
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
// Emit structured_validation_error event
|
|
528
|
+
this.auditor.record({
|
|
529
|
+
timestamp: Date.now(),
|
|
530
|
+
type: 'structured_validation_error',
|
|
531
|
+
provider: 'router',
|
|
532
|
+
schemaName,
|
|
533
|
+
error: error instanceof Error ? error.message : 'Validation failed',
|
|
534
|
+
rawOutput: fullContent,
|
|
535
|
+
});
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// ========================================================================
|
|
197
540
|
// Tool Registration (broadcast to all providers)
|
|
198
541
|
// ========================================================================
|
|
199
542
|
registerTool(name, description, parameters, handler) {
|