illuma-agents 1.0.38 → 1.0.40
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/dist/cjs/agents/AgentContext.cjs +45 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +110 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +140 -47
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/schemas/validate.cjs +173 -0
- package/dist/cjs/schemas/validate.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +45 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +110 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +140 -47
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/schemas/validate.mjs +167 -0
- package/dist/esm/schemas/validate.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +19 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/schemas/index.d.ts +1 -0
- package/dist/types/schemas/validate.d.ts +36 -0
- package/dist/types/types/graph.d.ts +69 -0
- package/package.json +2 -2
- package/src/agents/AgentContext.test.ts +312 -0
- package/src/agents/AgentContext.ts +56 -0
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +166 -2
- package/src/index.ts +3 -0
- package/src/messages/cache.test.ts +51 -6
- package/src/messages/cache.ts +149 -122
- package/src/schemas/index.ts +2 -0
- package/src/schemas/validate.test.ts +358 -0
- package/src/schemas/validate.ts +238 -0
- package/src/specs/cache.simple.test.ts +396 -0
- package/src/types/graph.test.ts +183 -0
- package/src/types/graph.ts +71 -0
|
@@ -8,18 +8,21 @@ type MessageWithContent = {
|
|
|
8
8
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
9
9
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
10
10
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
11
|
+
* Returns a new array - only clones messages that require modification.
|
|
11
12
|
* @param messages - The array of message objects.
|
|
12
|
-
* @returns -
|
|
13
|
+
* @returns - A new array of message objects with cache control added.
|
|
13
14
|
*/
|
|
14
15
|
export declare function addCacheControl<T extends AnthropicMessage | BaseMessage>(messages: T[]): T[];
|
|
15
16
|
/**
|
|
16
17
|
* Removes all Anthropic cache_control fields from messages
|
|
17
18
|
* Used when switching from Anthropic to Bedrock provider
|
|
19
|
+
* Returns a new array - only clones messages that require modification.
|
|
18
20
|
*/
|
|
19
21
|
export declare function stripAnthropicCacheControl<T extends MessageWithContent>(messages: T[]): T[];
|
|
20
22
|
/**
|
|
21
23
|
* Removes all Bedrock cachePoint blocks from messages
|
|
22
24
|
* Used when switching from Bedrock to Anthropic provider
|
|
25
|
+
* Returns a new array - only clones messages that require modification.
|
|
23
26
|
*/
|
|
24
27
|
export declare function stripBedrockCacheControl<T extends MessageWithContent>(messages: T[]): T[];
|
|
25
28
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './validate';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type * as t from '@/types';
|
|
3
|
+
/**
|
|
4
|
+
* Validation result from structured output
|
|
5
|
+
*/
|
|
6
|
+
export interface ValidationResult<T = Record<string, unknown>> {
|
|
7
|
+
success: boolean;
|
|
8
|
+
data?: T;
|
|
9
|
+
error?: string;
|
|
10
|
+
raw?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validates structured output against a JSON Schema.
|
|
14
|
+
*
|
|
15
|
+
* @param output - The output to validate
|
|
16
|
+
* @param schema - JSON Schema to validate against
|
|
17
|
+
* @returns Validation result with success flag and data or error
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateStructuredOutput(output: unknown, schema: Record<string, unknown>): ValidationResult;
|
|
20
|
+
/**
|
|
21
|
+
* Converts a Zod schema to JSON Schema format.
|
|
22
|
+
* This is a simplified converter for common types.
|
|
23
|
+
*/
|
|
24
|
+
export declare function zodToJsonSchema(zodSchema: z.ZodType): Record<string, unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a structured output error message for retry.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createValidationErrorMessage(result: ValidationResult, customMessage?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Checks if a value is a valid JSON Schema object.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isValidJsonSchema(value: unknown): value is Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes a JSON Schema by adding defaults and cleaning up.
|
|
35
|
+
*/
|
|
36
|
+
export declare function normalizeJsonSchema(schema: Record<string, unknown>, config?: t.StructuredOutputConfig): Record<string, unknown>;
|
|
@@ -27,6 +27,11 @@ export type SystemCallbacks = {
|
|
|
27
27
|
};
|
|
28
28
|
export type BaseGraphState = {
|
|
29
29
|
messages: BaseMessage[];
|
|
30
|
+
/**
|
|
31
|
+
* Structured response when using structured output mode.
|
|
32
|
+
* Contains the validated JSON response conforming to the configured schema.
|
|
33
|
+
*/
|
|
34
|
+
structuredResponse?: Record<string, unknown>;
|
|
30
35
|
};
|
|
31
36
|
export type MultiAgentGraphState = BaseGraphState & {
|
|
32
37
|
agentMessages?: BaseMessage[];
|
|
@@ -234,6 +239,64 @@ export type GraphEdge = {
|
|
|
234
239
|
export type MultiAgentGraphInput = StandardGraphInput & {
|
|
235
240
|
edges: GraphEdge[];
|
|
236
241
|
};
|
|
242
|
+
/**
|
|
243
|
+
* Structured output mode determines how the agent returns structured data.
|
|
244
|
+
* - 'tool': Uses tool calling to return structured output (works with all tool-calling models)
|
|
245
|
+
* - 'provider': Uses provider-native structured output (OpenAI, Anthropic, etc.)
|
|
246
|
+
* - 'auto': Automatically selects the best strategy based on model capabilities
|
|
247
|
+
*/
|
|
248
|
+
export type StructuredOutputMode = 'tool' | 'provider' | 'auto';
|
|
249
|
+
/**
|
|
250
|
+
* Configuration for structured JSON output from agents.
|
|
251
|
+
* When configured, the agent will return a validated JSON response
|
|
252
|
+
* instead of streaming text.
|
|
253
|
+
*/
|
|
254
|
+
export interface StructuredOutputConfig {
|
|
255
|
+
/**
|
|
256
|
+
* JSON Schema defining the output structure.
|
|
257
|
+
* The model will be forced to return data conforming to this schema.
|
|
258
|
+
*/
|
|
259
|
+
schema: Record<string, unknown>;
|
|
260
|
+
/**
|
|
261
|
+
* Name for the structured output format (used in tool mode).
|
|
262
|
+
* @default 'StructuredResponse'
|
|
263
|
+
*/
|
|
264
|
+
name?: string;
|
|
265
|
+
/**
|
|
266
|
+
* Description of what the structured output represents.
|
|
267
|
+
* Helps the model understand the expected format.
|
|
268
|
+
*/
|
|
269
|
+
description?: string;
|
|
270
|
+
/**
|
|
271
|
+
* Output mode strategy.
|
|
272
|
+
* @default 'auto'
|
|
273
|
+
*/
|
|
274
|
+
mode?: StructuredOutputMode;
|
|
275
|
+
/**
|
|
276
|
+
* Enable strict schema validation.
|
|
277
|
+
* When true, the response must exactly match the schema.
|
|
278
|
+
* @default true
|
|
279
|
+
*/
|
|
280
|
+
strict?: boolean;
|
|
281
|
+
/**
|
|
282
|
+
* Error handling configuration.
|
|
283
|
+
* - true: Auto-retry on validation errors (default)
|
|
284
|
+
* - false: Throw error on validation failure
|
|
285
|
+
* - string: Custom error message for retry
|
|
286
|
+
*/
|
|
287
|
+
handleErrors?: boolean | string;
|
|
288
|
+
/**
|
|
289
|
+
* Maximum number of retry attempts on validation failure.
|
|
290
|
+
* @default 2
|
|
291
|
+
*/
|
|
292
|
+
maxRetries?: number;
|
|
293
|
+
/**
|
|
294
|
+
* Include the raw AI message along with structured response.
|
|
295
|
+
* Useful for debugging.
|
|
296
|
+
* @default false
|
|
297
|
+
*/
|
|
298
|
+
includeRaw?: boolean;
|
|
299
|
+
}
|
|
237
300
|
export interface AgentInputs {
|
|
238
301
|
agentId: string;
|
|
239
302
|
/** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
|
|
@@ -263,4 +326,10 @@ export interface AgentInputs {
|
|
|
263
326
|
* and can be cached by Bedrock/Anthropic prompt caching.
|
|
264
327
|
*/
|
|
265
328
|
dynamicContext?: string;
|
|
329
|
+
/**
|
|
330
|
+
* Structured output configuration.
|
|
331
|
+
* When set, disables streaming and returns a validated JSON response
|
|
332
|
+
* conforming to the specified schema.
|
|
333
|
+
*/
|
|
334
|
+
structuredOutput?: StructuredOutputConfig;
|
|
266
335
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "illuma-agents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"start:collab5": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/collab_design_v5.ts",
|
|
92
92
|
"start:dev": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/main.ts",
|
|
93
93
|
"supervised": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/supervised.ts --provider anthropic --name Jo --location \"New York, NY\"",
|
|
94
|
-
"test": "jest",
|
|
94
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
95
95
|
"test:memory": "NODE_OPTIONS='--expose-gc' npx jest src/specs/title.memory-leak.test.ts",
|
|
96
96
|
"test:all": "npm test -- --testPathIgnorePatterns=title.memory-leak.test.ts && npm run test:memory",
|
|
97
97
|
"reinstall": "npm run clean && npm ci && rm -rf ./dist && npm run build",
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// src/agents/AgentContext.test.ts
|
|
2
|
+
import { AgentContext } from './AgentContext';
|
|
3
|
+
import { Providers } from '@/common';
|
|
4
|
+
import type * as t from '@/types';
|
|
5
|
+
|
|
6
|
+
describe('AgentContext', () => {
|
|
7
|
+
describe('structured output', () => {
|
|
8
|
+
const baseConfig: t.AgentInputs = {
|
|
9
|
+
agentId: 'test-agent',
|
|
10
|
+
provider: Providers.OPENAI,
|
|
11
|
+
clientOptions: {
|
|
12
|
+
model: 'gpt-4',
|
|
13
|
+
streaming: true,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('isStructuredOutputMode', () => {
|
|
18
|
+
it('should return false when structuredOutput is not configured', () => {
|
|
19
|
+
const context = AgentContext.fromConfig(baseConfig);
|
|
20
|
+
expect(context.isStructuredOutputMode).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return false when structuredOutput has no schema', () => {
|
|
24
|
+
const config: t.AgentInputs = {
|
|
25
|
+
...baseConfig,
|
|
26
|
+
structuredOutput: {
|
|
27
|
+
schema: undefined as unknown as Record<string, unknown>,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const context = AgentContext.fromConfig(config);
|
|
31
|
+
expect(context.isStructuredOutputMode).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return false when structuredOutput schema is null', () => {
|
|
35
|
+
const config: t.AgentInputs = {
|
|
36
|
+
...baseConfig,
|
|
37
|
+
structuredOutput: {
|
|
38
|
+
schema: null as unknown as Record<string, unknown>,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const context = AgentContext.fromConfig(config);
|
|
42
|
+
expect(context.isStructuredOutputMode).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return true when structuredOutput has a valid schema', () => {
|
|
46
|
+
const config: t.AgentInputs = {
|
|
47
|
+
...baseConfig,
|
|
48
|
+
structuredOutput: {
|
|
49
|
+
schema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
answer: { type: 'string' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const context = AgentContext.fromConfig(config);
|
|
58
|
+
expect(context.isStructuredOutputMode).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return true with minimal schema', () => {
|
|
62
|
+
const config: t.AgentInputs = {
|
|
63
|
+
...baseConfig,
|
|
64
|
+
structuredOutput: {
|
|
65
|
+
schema: { type: 'string' },
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const context = AgentContext.fromConfig(config);
|
|
69
|
+
expect(context.isStructuredOutputMode).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('getStructuredOutputSchema', () => {
|
|
74
|
+
it('should return undefined when structuredOutput is not configured', () => {
|
|
75
|
+
const context = AgentContext.fromConfig(baseConfig);
|
|
76
|
+
expect(context.getStructuredOutputSchema()).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return undefined when schema is not set', () => {
|
|
80
|
+
const config: t.AgentInputs = {
|
|
81
|
+
...baseConfig,
|
|
82
|
+
structuredOutput: {} as t.StructuredOutputConfig,
|
|
83
|
+
};
|
|
84
|
+
const context = AgentContext.fromConfig(config);
|
|
85
|
+
expect(context.getStructuredOutputSchema()).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return the schema as-is when properly configured', () => {
|
|
89
|
+
const schema = {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
name: { type: 'string' },
|
|
93
|
+
age: { type: 'number' },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const config: t.AgentInputs = {
|
|
97
|
+
...baseConfig,
|
|
98
|
+
structuredOutput: { schema },
|
|
99
|
+
};
|
|
100
|
+
const context = AgentContext.fromConfig(config);
|
|
101
|
+
const result = context.getStructuredOutputSchema();
|
|
102
|
+
expect(result).toMatchObject(schema);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should add type: object when properties exist but type is missing', () => {
|
|
106
|
+
const schema = {
|
|
107
|
+
properties: {
|
|
108
|
+
name: { type: 'string' },
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const config: t.AgentInputs = {
|
|
112
|
+
...baseConfig,
|
|
113
|
+
structuredOutput: { schema },
|
|
114
|
+
};
|
|
115
|
+
const context = AgentContext.fromConfig(config);
|
|
116
|
+
const result = context.getStructuredOutputSchema();
|
|
117
|
+
expect(result?.type).toBe('object');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should add title from config name', () => {
|
|
121
|
+
const config: t.AgentInputs = {
|
|
122
|
+
...baseConfig,
|
|
123
|
+
structuredOutput: {
|
|
124
|
+
schema: { type: 'object' },
|
|
125
|
+
name: 'ResponseSchema',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
const context = AgentContext.fromConfig(config);
|
|
129
|
+
const result = context.getStructuredOutputSchema();
|
|
130
|
+
expect(result?.title).toBe('ResponseSchema');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not override existing title', () => {
|
|
134
|
+
const config: t.AgentInputs = {
|
|
135
|
+
...baseConfig,
|
|
136
|
+
structuredOutput: {
|
|
137
|
+
schema: { type: 'object', title: 'ExistingTitle' },
|
|
138
|
+
name: 'NewName',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const context = AgentContext.fromConfig(config);
|
|
142
|
+
const result = context.getStructuredOutputSchema();
|
|
143
|
+
expect(result?.title).toBe('ExistingTitle');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should add description from config', () => {
|
|
147
|
+
const config: t.AgentInputs = {
|
|
148
|
+
...baseConfig,
|
|
149
|
+
structuredOutput: {
|
|
150
|
+
schema: { type: 'object' },
|
|
151
|
+
description: 'A structured response',
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
const context = AgentContext.fromConfig(config);
|
|
155
|
+
const result = context.getStructuredOutputSchema();
|
|
156
|
+
expect(result?.description).toBe('A structured response');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should not override existing description', () => {
|
|
160
|
+
const config: t.AgentInputs = {
|
|
161
|
+
...baseConfig,
|
|
162
|
+
structuredOutput: {
|
|
163
|
+
schema: { type: 'object', description: 'Original description' },
|
|
164
|
+
description: 'New description',
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
const context = AgentContext.fromConfig(config);
|
|
168
|
+
const result = context.getStructuredOutputSchema();
|
|
169
|
+
expect(result?.description).toBe('Original description');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should set additionalProperties: false in strict mode by default', () => {
|
|
173
|
+
const config: t.AgentInputs = {
|
|
174
|
+
...baseConfig,
|
|
175
|
+
structuredOutput: {
|
|
176
|
+
schema: { type: 'object', properties: {} },
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
const context = AgentContext.fromConfig(config);
|
|
180
|
+
const result = context.getStructuredOutputSchema();
|
|
181
|
+
expect(result?.additionalProperties).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should set additionalProperties: false when strict is explicitly true', () => {
|
|
185
|
+
const config: t.AgentInputs = {
|
|
186
|
+
...baseConfig,
|
|
187
|
+
structuredOutput: {
|
|
188
|
+
schema: { type: 'object', properties: {} },
|
|
189
|
+
strict: true,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const context = AgentContext.fromConfig(config);
|
|
193
|
+
const result = context.getStructuredOutputSchema();
|
|
194
|
+
expect(result?.additionalProperties).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should not set additionalProperties when strict is false', () => {
|
|
198
|
+
const config: t.AgentInputs = {
|
|
199
|
+
...baseConfig,
|
|
200
|
+
structuredOutput: {
|
|
201
|
+
schema: { type: 'object', properties: {} },
|
|
202
|
+
strict: false,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
const context = AgentContext.fromConfig(config);
|
|
206
|
+
const result = context.getStructuredOutputSchema();
|
|
207
|
+
expect(result?.additionalProperties).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should preserve existing additionalProperties value', () => {
|
|
211
|
+
const config: t.AgentInputs = {
|
|
212
|
+
...baseConfig,
|
|
213
|
+
structuredOutput: {
|
|
214
|
+
schema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {},
|
|
217
|
+
additionalProperties: true,
|
|
218
|
+
},
|
|
219
|
+
strict: true,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const context = AgentContext.fromConfig(config);
|
|
223
|
+
const result = context.getStructuredOutputSchema();
|
|
224
|
+
expect(result?.additionalProperties).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should not affect non-object types', () => {
|
|
228
|
+
const config: t.AgentInputs = {
|
|
229
|
+
...baseConfig,
|
|
230
|
+
structuredOutput: {
|
|
231
|
+
schema: { type: 'array', items: { type: 'string' } },
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
const context = AgentContext.fromConfig(config);
|
|
235
|
+
const result = context.getStructuredOutputSchema();
|
|
236
|
+
expect(result?.additionalProperties).toBeUndefined();
|
|
237
|
+
expect(result?.type).toBe('array');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should not mutate the original schema', () => {
|
|
241
|
+
const originalSchema = {
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: { name: { type: 'string' } },
|
|
244
|
+
};
|
|
245
|
+
const config: t.AgentInputs = {
|
|
246
|
+
...baseConfig,
|
|
247
|
+
structuredOutput: {
|
|
248
|
+
schema: originalSchema,
|
|
249
|
+
name: 'TestSchema',
|
|
250
|
+
description: 'Test description',
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
const context = AgentContext.fromConfig(config);
|
|
254
|
+
context.getStructuredOutputSchema();
|
|
255
|
+
|
|
256
|
+
// Original should not have been modified
|
|
257
|
+
expect(originalSchema).not.toHaveProperty('title');
|
|
258
|
+
expect(originalSchema).not.toHaveProperty('description');
|
|
259
|
+
expect(originalSchema).not.toHaveProperty('additionalProperties');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('fromConfig with structuredOutput', () => {
|
|
264
|
+
it('should properly initialize structuredOutput from config', () => {
|
|
265
|
+
const structuredOutput: t.StructuredOutputConfig = {
|
|
266
|
+
schema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
response: { type: 'string' },
|
|
270
|
+
confidence: { type: 'number' },
|
|
271
|
+
},
|
|
272
|
+
required: ['response'],
|
|
273
|
+
},
|
|
274
|
+
name: 'AnalysisResult',
|
|
275
|
+
description: 'The analysis result',
|
|
276
|
+
mode: 'auto',
|
|
277
|
+
strict: true,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const config: t.AgentInputs = {
|
|
281
|
+
...baseConfig,
|
|
282
|
+
structuredOutput,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const context = AgentContext.fromConfig(config);
|
|
286
|
+
|
|
287
|
+
expect(context.isStructuredOutputMode).toBe(true);
|
|
288
|
+
expect(context.structuredOutput).toEqual(structuredOutput);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should handle all mode options', () => {
|
|
292
|
+
const modes: Array<t.StructuredOutputMode> = [
|
|
293
|
+
'auto',
|
|
294
|
+
'tool',
|
|
295
|
+
'provider',
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
for (const mode of modes) {
|
|
299
|
+
const config: t.AgentInputs = {
|
|
300
|
+
...baseConfig,
|
|
301
|
+
structuredOutput: {
|
|
302
|
+
schema: { type: 'object' },
|
|
303
|
+
mode,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
const context = AgentContext.fromConfig(config);
|
|
307
|
+
expect(context.structuredOutput?.mode).toBe(mode);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -41,6 +41,7 @@ export class AgentContext {
|
|
|
41
41
|
reasoningKey,
|
|
42
42
|
useLegacyContent,
|
|
43
43
|
dynamicContext,
|
|
44
|
+
structuredOutput,
|
|
44
45
|
} = agentConfig;
|
|
45
46
|
|
|
46
47
|
const agentContext = new AgentContext({
|
|
@@ -61,6 +62,7 @@ export class AgentContext {
|
|
|
61
62
|
tokenCounter,
|
|
62
63
|
useLegacyContent,
|
|
63
64
|
dynamicContext,
|
|
65
|
+
structuredOutput,
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
if (tokenCounter) {
|
|
@@ -187,6 +189,12 @@ export class AgentContext {
|
|
|
187
189
|
/** Names of sibling agents executing in parallel (empty if sequential) */
|
|
188
190
|
parallelSiblings: string[];
|
|
189
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* Structured output configuration.
|
|
194
|
+
* When set, the agent will return a validated JSON response
|
|
195
|
+
* instead of streaming text.
|
|
196
|
+
*/
|
|
197
|
+
structuredOutput?: t.StructuredOutputConfig;
|
|
190
198
|
|
|
191
199
|
constructor({
|
|
192
200
|
agentId,
|
|
@@ -206,6 +214,7 @@ export class AgentContext {
|
|
|
206
214
|
toolEnd,
|
|
207
215
|
instructionTokens,
|
|
208
216
|
useLegacyContent,
|
|
217
|
+
structuredOutput,
|
|
209
218
|
}: {
|
|
210
219
|
agentId: string;
|
|
211
220
|
name?: string;
|
|
@@ -224,6 +233,7 @@ export class AgentContext {
|
|
|
224
233
|
toolEnd?: boolean;
|
|
225
234
|
instructionTokens?: number;
|
|
226
235
|
useLegacyContent?: boolean;
|
|
236
|
+
structuredOutput?: t.StructuredOutputConfig;
|
|
227
237
|
}) {
|
|
228
238
|
this.agentId = agentId;
|
|
229
239
|
this.name = name;
|
|
@@ -238,6 +248,7 @@ export class AgentContext {
|
|
|
238
248
|
this.instructions = instructions;
|
|
239
249
|
this.additionalInstructions = additionalInstructions;
|
|
240
250
|
this.dynamicContext = dynamicContext;
|
|
251
|
+
this.structuredOutput = structuredOutput;
|
|
241
252
|
if (reasoningKey) {
|
|
242
253
|
this.reasoningKey = reasoningKey;
|
|
243
254
|
}
|
|
@@ -251,6 +262,51 @@ export class AgentContext {
|
|
|
251
262
|
this.useLegacyContent = useLegacyContent ?? false;
|
|
252
263
|
}
|
|
253
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Checks if structured output mode is enabled for this agent.
|
|
267
|
+
* When enabled, the agent will use model.invoke() instead of streaming
|
|
268
|
+
* and return a validated JSON response.
|
|
269
|
+
*/
|
|
270
|
+
get isStructuredOutputMode(): boolean {
|
|
271
|
+
return (
|
|
272
|
+
this.structuredOutput != null && this.structuredOutput.schema != null
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Gets the structured output schema with normalized defaults.
|
|
278
|
+
* Returns undefined if structured output is not configured.
|
|
279
|
+
*/
|
|
280
|
+
getStructuredOutputSchema(): Record<string, unknown> | undefined {
|
|
281
|
+
if (!this.structuredOutput?.schema) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const schema = { ...this.structuredOutput.schema };
|
|
286
|
+
|
|
287
|
+
// Ensure type is set
|
|
288
|
+
if (!schema.type && schema.properties) {
|
|
289
|
+
schema.type = 'object';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Add title from config name
|
|
293
|
+
if (this.structuredOutput.name && !schema.title) {
|
|
294
|
+
schema.title = this.structuredOutput.name;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Add description from config
|
|
298
|
+
if (this.structuredOutput.description && !schema.description) {
|
|
299
|
+
schema.description = this.structuredOutput.description;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Enable strict mode by default
|
|
303
|
+
if (this.structuredOutput.strict !== false && schema.type === 'object') {
|
|
304
|
+
schema.additionalProperties = schema.additionalProperties ?? false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return schema;
|
|
308
|
+
}
|
|
309
|
+
|
|
254
310
|
/**
|
|
255
311
|
* Builds instructions text for tools that are ONLY callable via programmatic code execution.
|
|
256
312
|
* These tools cannot be called directly by the LLM but are available through the
|
package/src/common/enum.ts
CHANGED
|
@@ -21,6 +21,8 @@ export enum GraphEvents {
|
|
|
21
21
|
ON_REASONING_DELTA = 'on_reasoning_delta',
|
|
22
22
|
/** [Custom] Context analytics event for traces */
|
|
23
23
|
ON_CONTEXT_ANALYTICS = 'on_context_analytics',
|
|
24
|
+
/** [Custom] Structured output event - emitted when agent returns structured JSON */
|
|
25
|
+
ON_STRUCTURED_OUTPUT = 'on_structured_output',
|
|
24
26
|
|
|
25
27
|
/* Official Events */
|
|
26
28
|
|