@illuma-ai/agents 1.1.20 → 1.1.22

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.
Files changed (246) hide show
  1. package/dist/cjs/graphs/Graph.cjs +12 -1
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +85 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +14 -0
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/run.cjs +20 -9
  8. package/dist/cjs/run.cjs.map +1 -1
  9. package/dist/esm/graphs/Graph.mjs +12 -1
  10. package/dist/esm/graphs/Graph.mjs.map +1 -1
  11. package/dist/esm/graphs/MultiAgentGraph.mjs +85 -1
  12. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  13. package/dist/esm/llm/bedrock/index.mjs +14 -0
  14. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  15. package/dist/esm/run.mjs +20 -9
  16. package/dist/esm/run.mjs.map +1 -1
  17. package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
  18. package/package.json +1 -1
  19. package/src/graphs/Graph.ts +12 -1
  20. package/src/graphs/MultiAgentGraph.ts +105 -1
  21. package/src/graphs/__tests__/multi-agent-delegate.test.ts +191 -0
  22. package/src/llm/bedrock/index.ts +17 -0
  23. package/src/run.ts +20 -11
  24. package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
  25. package/src/agents/AgentContext.js +0 -782
  26. package/src/agents/AgentContext.test.js +0 -421
  27. package/src/agents/__tests__/AgentContext.test.js +0 -678
  28. package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
  29. package/src/common/enum.js +0 -192
  30. package/src/common/index.js +0 -3
  31. package/src/events.js +0 -166
  32. package/src/graphs/Graph.js +0 -1857
  33. package/src/graphs/MultiAgentGraph.js +0 -1092
  34. package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
  35. package/src/graphs/__tests__/structured-output.test.js +0 -144
  36. package/src/graphs/contextManagement.e2e.test.js +0 -718
  37. package/src/graphs/contextManagement.test.js +0 -485
  38. package/src/graphs/handoffValidation.test.js +0 -276
  39. package/src/graphs/index.js +0 -3
  40. package/src/index.js +0 -28
  41. package/src/instrumentation.js +0 -21
  42. package/src/llm/anthropic/index.js +0 -319
  43. package/src/llm/anthropic/types.js +0 -46
  44. package/src/llm/anthropic/utils/message_inputs.js +0 -627
  45. package/src/llm/anthropic/utils/message_outputs.js +0 -290
  46. package/src/llm/anthropic/utils/output_parsers.js +0 -89
  47. package/src/llm/anthropic/utils/tools.js +0 -25
  48. package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
  49. package/src/llm/bedrock/index.js +0 -303
  50. package/src/llm/bedrock/types.js +0 -2
  51. package/src/llm/bedrock/utils/index.js +0 -6
  52. package/src/llm/bedrock/utils/message_inputs.js +0 -463
  53. package/src/llm/bedrock/utils/message_outputs.js +0 -269
  54. package/src/llm/fake.js +0 -92
  55. package/src/llm/google/index.js +0 -215
  56. package/src/llm/google/types.js +0 -12
  57. package/src/llm/google/utils/common.js +0 -670
  58. package/src/llm/google/utils/tools.js +0 -111
  59. package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
  60. package/src/llm/openai/index.js +0 -1033
  61. package/src/llm/openai/types.js +0 -2
  62. package/src/llm/openai/utils/index.js +0 -756
  63. package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
  64. package/src/llm/openrouter/index.js +0 -261
  65. package/src/llm/openrouter/reasoning.test.js +0 -181
  66. package/src/llm/providers.js +0 -36
  67. package/src/llm/text.js +0 -65
  68. package/src/llm/vertexai/index.js +0 -402
  69. package/src/messages/__tests__/tools.test.js +0 -392
  70. package/src/messages/cache.js +0 -404
  71. package/src/messages/cache.test.js +0 -1167
  72. package/src/messages/content.js +0 -48
  73. package/src/messages/content.test.js +0 -314
  74. package/src/messages/core.js +0 -359
  75. package/src/messages/ensureThinkingBlock.test.js +0 -997
  76. package/src/messages/format.js +0 -973
  77. package/src/messages/formatAgentMessages.test.js +0 -2278
  78. package/src/messages/formatAgentMessages.tools.test.js +0 -362
  79. package/src/messages/formatMessage.test.js +0 -608
  80. package/src/messages/ids.js +0 -18
  81. package/src/messages/index.js +0 -9
  82. package/src/messages/labelContentByAgent.test.js +0 -725
  83. package/src/messages/prune.js +0 -438
  84. package/src/messages/reducer.js +0 -60
  85. package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
  86. package/src/messages/summarize.js +0 -146
  87. package/src/messages/summarize.test.js +0 -332
  88. package/src/messages/tools.js +0 -90
  89. package/src/mockStream.js +0 -81
  90. package/src/prompts/collab.js +0 -7
  91. package/src/prompts/index.js +0 -3
  92. package/src/prompts/taskmanager.js +0 -58
  93. package/src/run.js +0 -427
  94. package/src/schemas/index.js +0 -3
  95. package/src/schemas/schema-preparation.test.js +0 -370
  96. package/src/schemas/validate.js +0 -314
  97. package/src/schemas/validate.test.js +0 -264
  98. package/src/scripts/abort.js +0 -127
  99. package/src/scripts/ant_web_search.js +0 -130
  100. package/src/scripts/ant_web_search_edge_case.js +0 -133
  101. package/src/scripts/ant_web_search_error_edge_case.js +0 -119
  102. package/src/scripts/args.js +0 -41
  103. package/src/scripts/bedrock-cache-debug.js +0 -186
  104. package/src/scripts/bedrock-content-aggregation-test.js +0 -195
  105. package/src/scripts/bedrock-merge-test.js +0 -80
  106. package/src/scripts/bedrock-parallel-tools-test.js +0 -150
  107. package/src/scripts/caching.js +0 -106
  108. package/src/scripts/cli.js +0 -152
  109. package/src/scripts/cli2.js +0 -119
  110. package/src/scripts/cli3.js +0 -163
  111. package/src/scripts/cli4.js +0 -165
  112. package/src/scripts/cli5.js +0 -165
  113. package/src/scripts/code_exec.js +0 -171
  114. package/src/scripts/code_exec_files.js +0 -180
  115. package/src/scripts/code_exec_multi_session.js +0 -185
  116. package/src/scripts/code_exec_ptc.js +0 -265
  117. package/src/scripts/code_exec_session.js +0 -217
  118. package/src/scripts/code_exec_simple.js +0 -120
  119. package/src/scripts/content.js +0 -111
  120. package/src/scripts/empty_input.js +0 -125
  121. package/src/scripts/handoff-test.js +0 -96
  122. package/src/scripts/image.js +0 -138
  123. package/src/scripts/memory.js +0 -83
  124. package/src/scripts/multi-agent-chain.js +0 -271
  125. package/src/scripts/multi-agent-conditional.js +0 -185
  126. package/src/scripts/multi-agent-document-review-chain.js +0 -171
  127. package/src/scripts/multi-agent-hybrid-flow.js +0 -264
  128. package/src/scripts/multi-agent-parallel-start.js +0 -214
  129. package/src/scripts/multi-agent-parallel.js +0 -346
  130. package/src/scripts/multi-agent-sequence.js +0 -184
  131. package/src/scripts/multi-agent-supervisor.js +0 -324
  132. package/src/scripts/multi-agent-test.js +0 -147
  133. package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
  134. package/src/scripts/parallel-full-metadata-test.js +0 -176
  135. package/src/scripts/parallel-tools-test.js +0 -256
  136. package/src/scripts/programmatic_exec.js +0 -277
  137. package/src/scripts/programmatic_exec_agent.js +0 -168
  138. package/src/scripts/search.js +0 -118
  139. package/src/scripts/sequential-full-metadata-test.js +0 -143
  140. package/src/scripts/simple.js +0 -174
  141. package/src/scripts/single-agent-metadata-test.js +0 -152
  142. package/src/scripts/stream.js +0 -113
  143. package/src/scripts/test-custom-prompt-key.js +0 -132
  144. package/src/scripts/test-handoff-input.js +0 -143
  145. package/src/scripts/test-handoff-preamble.js +0 -227
  146. package/src/scripts/test-handoff-steering.js +0 -353
  147. package/src/scripts/test-multi-agent-list-handoff.js +0 -318
  148. package/src/scripts/test-parallel-agent-labeling.js +0 -253
  149. package/src/scripts/test-parallel-handoffs.js +0 -229
  150. package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
  151. package/src/scripts/test-thinking-handoff.js +0 -132
  152. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
  153. package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
  154. package/src/scripts/test-tools-before-handoff.js +0 -187
  155. package/src/scripts/test_code_api.js +0 -263
  156. package/src/scripts/thinking-bedrock.js +0 -128
  157. package/src/scripts/thinking-vertexai.js +0 -130
  158. package/src/scripts/thinking.js +0 -134
  159. package/src/scripts/tool_search.js +0 -114
  160. package/src/scripts/tools.js +0 -125
  161. package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
  162. package/src/specs/agent-handoffs.test.js +0 -924
  163. package/src/specs/anthropic.simple.test.js +0 -287
  164. package/src/specs/azure.simple.test.js +0 -381
  165. package/src/specs/cache.simple.test.js +0 -282
  166. package/src/specs/custom-event-await.test.js +0 -148
  167. package/src/specs/deepseek.simple.test.js +0 -189
  168. package/src/specs/emergency-prune.test.js +0 -308
  169. package/src/specs/moonshot.simple.test.js +0 -237
  170. package/src/specs/observability.integration.test.js +0 -1337
  171. package/src/specs/openai.simple.test.js +0 -233
  172. package/src/specs/openrouter.simple.test.js +0 -202
  173. package/src/specs/prune.test.js +0 -733
  174. package/src/specs/reasoning.test.js +0 -144
  175. package/src/specs/spec.utils.js +0 -4
  176. package/src/specs/thinking-handoff.test.js +0 -486
  177. package/src/specs/thinking-prune.test.js +0 -600
  178. package/src/specs/token-distribution-edge-case.test.js +0 -246
  179. package/src/specs/token-memoization.test.js +0 -32
  180. package/src/specs/tokens.test.js +0 -49
  181. package/src/specs/tool-error.test.js +0 -139
  182. package/src/splitStream.js +0 -204
  183. package/src/splitStream.test.js +0 -504
  184. package/src/stream.js +0 -650
  185. package/src/stream.test.js +0 -225
  186. package/src/test/mockTools.js +0 -340
  187. package/src/tools/BrowserTools.js +0 -245
  188. package/src/tools/Calculator.js +0 -38
  189. package/src/tools/Calculator.test.js +0 -225
  190. package/src/tools/CodeExecutor.js +0 -233
  191. package/src/tools/ProgrammaticToolCalling.js +0 -602
  192. package/src/tools/StreamingToolCallBuffer.js +0 -179
  193. package/src/tools/ToolNode.js +0 -930
  194. package/src/tools/ToolSearch.js +0 -904
  195. package/src/tools/__tests__/BrowserTools.test.js +0 -306
  196. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
  197. package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
  198. package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
  199. package/src/tools/__tests__/ToolApproval.test.js +0 -675
  200. package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
  201. package/src/tools/__tests__/ToolNode.session.test.js +0 -319
  202. package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
  203. package/src/tools/__tests__/ToolSearch.test.js +0 -812
  204. package/src/tools/__tests__/handlers.test.js +0 -799
  205. package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
  206. package/src/tools/handlers.js +0 -306
  207. package/src/tools/schema.js +0 -25
  208. package/src/tools/search/anthropic.js +0 -34
  209. package/src/tools/search/content.js +0 -116
  210. package/src/tools/search/content.test.js +0 -133
  211. package/src/tools/search/firecrawl.js +0 -173
  212. package/src/tools/search/format.js +0 -198
  213. package/src/tools/search/highlights.js +0 -241
  214. package/src/tools/search/index.js +0 -3
  215. package/src/tools/search/jina-reranker.test.js +0 -106
  216. package/src/tools/search/rerankers.js +0 -165
  217. package/src/tools/search/schema.js +0 -102
  218. package/src/tools/search/search.js +0 -561
  219. package/src/tools/search/serper-scraper.js +0 -126
  220. package/src/tools/search/test.js +0 -129
  221. package/src/tools/search/tool.js +0 -453
  222. package/src/tools/search/types.js +0 -2
  223. package/src/tools/search/utils.js +0 -59
  224. package/src/types/graph.js +0 -24
  225. package/src/types/graph.test.js +0 -192
  226. package/src/types/index.js +0 -7
  227. package/src/types/llm.js +0 -2
  228. package/src/types/messages.js +0 -2
  229. package/src/types/run.js +0 -2
  230. package/src/types/stream.js +0 -2
  231. package/src/types/tools.js +0 -2
  232. package/src/utils/contextAnalytics.js +0 -79
  233. package/src/utils/contextAnalytics.test.js +0 -166
  234. package/src/utils/events.js +0 -26
  235. package/src/utils/graph.js +0 -11
  236. package/src/utils/handlers.js +0 -65
  237. package/src/utils/index.js +0 -10
  238. package/src/utils/llm.js +0 -21
  239. package/src/utils/llmConfig.js +0 -205
  240. package/src/utils/logging.js +0 -37
  241. package/src/utils/misc.js +0 -51
  242. package/src/utils/run.js +0 -69
  243. package/src/utils/schema.js +0 -21
  244. package/src/utils/title.js +0 -119
  245. package/src/utils/tokens.js +0 -92
  246. package/src/utils/toonFormat.js +0 -379
@@ -1,624 +0,0 @@
1
- /* eslint-disable no-process-env */
2
- /* eslint-disable no-console */
3
- /* eslint-disable @typescript-eslint/no-explicit-any */
4
- /**
5
- * Integration tests for structured output with real LLM API calls.
6
- *
7
- * These tests require actual API credentials. They will be skipped if
8
- * the required environment variables are not set.
9
- *
10
- * To run:
11
- * 1. Copy ranger/.env credentials to agents/.env (or set env vars directly)
12
- * 2. npm test -- --testPathPattern=structured-output.integration
13
- *
14
- * Environment variables needed:
15
- * - BEDROCK_AWS_ACCESS_KEY_ID + BEDROCK_AWS_SECRET_ACCESS_KEY + BEDROCK_AWS_DEFAULT_REGION
16
- * - ANTHROPIC_API_KEY (optional, for direct Anthropic tests)
17
- * - OPENAI_API_KEY (optional, for direct OpenAI tests)
18
- */
19
- import { config } from 'dotenv';
20
- import { resolve } from 'path';
21
- // Load from ranger/.env if agents/.env doesn't exist
22
- config(); // Try local first
23
- config({ path: resolve(__dirname, '../../../../ranger/.env') }); // Then ranger
24
- import { HumanMessage, SystemMessage } from '@langchain/core/messages';
25
- import { CustomChatBedrockConverse } from '@/llm/bedrock';
26
- import { Providers } from '@/common';
27
- import { validateStructuredOutput, prepareSchemaForProvider } from '@/schemas/validate';
28
- jest.setTimeout(120000);
29
- // ──────────────────────────────────────────────────────────────
30
- // Helper: check if credentials are available
31
- // ──────────────────────────────────────────────────────────────
32
- const hasBedrock = !!process.env.BEDROCK_AWS_ACCESS_KEY_ID &&
33
- !!process.env.BEDROCK_AWS_SECRET_ACCESS_KEY;
34
- const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
35
- const hasOpenAI = !!process.env.OPENAI_API_KEY;
36
- // ──────────────────────────────────────────────────────────────
37
- // Shared test schema
38
- // ──────────────────────────────────────────────────────────────
39
- const personSchema = {
40
- type: 'object',
41
- properties: {
42
- name: { type: 'string', description: 'Full name of the person' },
43
- age: { type: 'number', description: 'Age in years' },
44
- occupation: { type: 'string', description: 'Job title or occupation' },
45
- },
46
- required: ['name', 'age', 'occupation'],
47
- additionalProperties: false,
48
- };
49
- const classificationSchema = {
50
- type: 'object',
51
- properties: {
52
- category: {
53
- type: 'string',
54
- enum: ['positive', 'negative', 'neutral'],
55
- description: 'Sentiment category',
56
- },
57
- confidence: {
58
- type: 'number',
59
- description: 'Confidence score between 0 and 1',
60
- },
61
- reasoning: {
62
- type: 'string',
63
- description: 'Brief explanation of the classification',
64
- },
65
- },
66
- required: ['category', 'confidence', 'reasoning'],
67
- additionalProperties: false,
68
- };
69
- // ──────────────────────────────────────────────────────────────
70
- // Bedrock Integration Tests
71
- // ──────────────────────────────────────────────────────────────
72
- describe('Bedrock structured output integration', () => {
73
- const bedrockRegion = process.env.BEDROCK_AWS_DEFAULT_REGION || 'us-east-1';
74
- const createBedrockModel = () => new CustomChatBedrockConverse({
75
- // Use Claude 3 Haiku which is widely available and supports on-demand invocation
76
- model: 'anthropic.claude-3-haiku-20240307-v1:0',
77
- region: bedrockRegion,
78
- credentials: {
79
- accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID,
80
- secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY,
81
- ...(process.env.BEDROCK_AWS_SESSION_TOKEN
82
- ? { sessionToken: process.env.BEDROCK_AWS_SESSION_TOKEN }
83
- : {}),
84
- },
85
- maxTokens: 1024,
86
- });
87
- const conditionalTest = hasBedrock ? test : test.skip;
88
- conditionalTest('withStructuredOutput functionCalling - basic person extraction', async () => {
89
- const model = createBedrockModel();
90
- const structuredModel = model.withStructuredOutput(personSchema, {
91
- name: 'PersonInfo',
92
- method: 'functionCalling',
93
- includeRaw: true,
94
- strict: true,
95
- });
96
- const result = await structuredModel.invoke([
97
- new SystemMessage('Extract person information from the text.'),
98
- new HumanMessage('John Smith is a 35 year old software engineer from Seattle.'),
99
- ]);
100
- console.log('[Integration] Bedrock functionCalling result:', JSON.stringify(result.parsed ?? result, null, 2));
101
- const parsed = result.parsed ?? result;
102
- expect(parsed).toBeDefined();
103
- expect(typeof parsed.name).toBe('string');
104
- expect(typeof parsed.age).toBe('number');
105
- expect(typeof parsed.occupation).toBe('string');
106
- // Validate against schema
107
- const validation = validateStructuredOutput(parsed, personSchema);
108
- expect(validation.success).toBe(true);
109
- });
110
- conditionalTest('withStructuredOutput functionCalling - classification with enum', async () => {
111
- const model = createBedrockModel();
112
- const structuredModel = model.withStructuredOutput(classificationSchema, {
113
- name: 'Classification',
114
- method: 'functionCalling',
115
- includeRaw: true,
116
- strict: true,
117
- });
118
- const result = await structuredModel.invoke([
119
- new SystemMessage('Classify the sentiment of the following text.'),
120
- new HumanMessage('I absolutely love this product! It exceeded all my expectations.'),
121
- ]);
122
- const parsed = result.parsed ?? result;
123
- console.log('[Integration] Bedrock classification result:', JSON.stringify(parsed, null, 2));
124
- expect(parsed).toBeDefined();
125
- expect(['positive', 'negative', 'neutral']).toContain(parsed.category);
126
- expect(typeof parsed.confidence).toBe('number');
127
- expect(typeof parsed.reasoning).toBe('string');
128
- });
129
- conditionalTest('withStructuredOutput with prepared schema', async () => {
130
- const rawSchema = {
131
- type: 'object',
132
- properties: {
133
- summary: { type: 'string', maxLength: 500 },
134
- topics: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 5 },
135
- wordCount: { type: 'number', minimum: 0 },
136
- },
137
- };
138
- // Prepare schema for Bedrock
139
- const { schema: prepared, warnings } = prepareSchemaForProvider(rawSchema, Providers.BEDROCK);
140
- console.log('[Integration] Schema preparation warnings:', warnings);
141
- // Verify preparation worked
142
- expect(prepared.additionalProperties).toBe(false);
143
- expect(prepared.required).toEqual(expect.arrayContaining(['summary', 'topics', 'wordCount']));
144
- const summaryProp = prepared.properties.summary;
145
- expect(summaryProp.maxLength).toBeUndefined();
146
- expect(summaryProp.description).toContain('maxLength');
147
- // Now use the prepared schema with the model
148
- const model = createBedrockModel();
149
- const structuredModel = model.withStructuredOutput(prepared, {
150
- name: 'Analysis',
151
- method: 'functionCalling',
152
- includeRaw: true,
153
- strict: true,
154
- });
155
- const result = await structuredModel.invoke([
156
- new SystemMessage('Analyze the following text and provide a summary, key topics, and word count.'),
157
- new HumanMessage('Artificial intelligence has transformed the technology industry in recent years. ' +
158
- 'Machine learning models are now capable of generating text, images, and even code. ' +
159
- 'Companies are investing heavily in AI research and development.'),
160
- ]);
161
- const parsed = result.parsed ?? result;
162
- console.log('[Integration] Bedrock analysis result:', JSON.stringify(parsed, null, 2));
163
- expect(parsed).toBeDefined();
164
- expect(typeof parsed.summary).toBe('string');
165
- expect(Array.isArray(parsed.topics)).toBe(true);
166
- expect(typeof parsed.wordCount).toBe('number');
167
- });
168
- conditionalTest('withStructuredOutput with tools - deferred structured output pattern', async () => {
169
- const model = createBedrockModel();
170
- // Step 1: Regular invocation with tools (simulate tool use)
171
- const messages = [
172
- new SystemMessage('You are a helpful assistant. When asked to analyze text, provide a structured analysis.'),
173
- new HumanMessage('Analyze the sentiment: "This movie was absolutely terrible, worst I have ever seen."'),
174
- ];
175
- // Step 2: Get structured output (this simulates the deferred structured output pattern)
176
- const structuredModel = model.withStructuredOutput(classificationSchema, {
177
- name: 'SentimentAnalysis',
178
- method: 'functionCalling',
179
- includeRaw: true,
180
- strict: true,
181
- });
182
- const result = await structuredModel.invoke(messages);
183
- const parsed = result.parsed ?? result;
184
- console.log('[Integration] Bedrock deferred structured result:', JSON.stringify(parsed, null, 2));
185
- expect(parsed).toBeDefined();
186
- expect(['positive', 'negative', 'neutral']).toContain(parsed.category);
187
- // The movie review is clearly negative
188
- expect(parsed.category).toBe('negative');
189
- expect(typeof parsed.confidence).toBe('number');
190
- });
191
- conditionalTest('validates response against schema and detects failures', async () => {
192
- // Test schema validation on a known good response
193
- const goodResponse = {
194
- name: 'Alice',
195
- age: 30,
196
- occupation: 'Engineer',
197
- };
198
- const goodResult = validateStructuredOutput(goodResponse, personSchema);
199
- expect(goodResult.success).toBe(true);
200
- // Test with a bad response
201
- const badResponse = {
202
- name: 123, // Wrong type
203
- age: 'thirty', // Wrong type
204
- };
205
- const badResult = validateStructuredOutput(badResponse, personSchema);
206
- expect(badResult.success).toBe(false);
207
- expect(badResult.error).toBeDefined();
208
- });
209
- });
210
- // ──────────────────────────────────────────────────────────────
211
- // Complex Tool + Structured Output Integration Tests (Bedrock)
212
- // ──────────────────────────────────────────────────────────────
213
- describe('Bedrock complex tool + structured output integration', () => {
214
- const bedrockRegion = process.env.BEDROCK_AWS_DEFAULT_REGION || 'us-east-1';
215
- const conditionalTest = hasBedrock ? test : test.skip;
216
- const createBedrockModel = () => new CustomChatBedrockConverse({
217
- model: 'anthropic.claude-3-haiku-20240307-v1:0',
218
- region: bedrockRegion,
219
- credentials: {
220
- accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID,
221
- secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY,
222
- ...(process.env.BEDROCK_AWS_SESSION_TOKEN
223
- ? { sessionToken: process.env.BEDROCK_AWS_SESSION_TOKEN }
224
- : {}),
225
- },
226
- maxTokens: 2048,
227
- });
228
- conditionalTest('complex schema with nested objects and arrays - multi-step analysis', async () => {
229
- const complexSchema = {
230
- type: 'object',
231
- properties: {
232
- analysis: {
233
- type: 'object',
234
- properties: {
235
- overall_sentiment: {
236
- type: 'string',
237
- enum: ['very_positive', 'positive', 'neutral', 'negative', 'very_negative'],
238
- },
239
- confidence: { type: 'number' },
240
- key_themes: {
241
- type: 'array',
242
- items: {
243
- type: 'object',
244
- properties: {
245
- theme: { type: 'string' },
246
- sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
247
- evidence: { type: 'string' },
248
- },
249
- required: ['theme', 'sentiment', 'evidence'],
250
- additionalProperties: false,
251
- },
252
- },
253
- },
254
- required: ['overall_sentiment', 'confidence', 'key_themes'],
255
- additionalProperties: false,
256
- },
257
- entities: {
258
- type: 'array',
259
- items: {
260
- type: 'object',
261
- properties: {
262
- name: { type: 'string' },
263
- type: { type: 'string', enum: ['person', 'organization', 'location', 'product'] },
264
- role: { type: 'string' },
265
- },
266
- required: ['name', 'type', 'role'],
267
- additionalProperties: false,
268
- },
269
- },
270
- summary: { type: 'string' },
271
- action_items: {
272
- type: 'array',
273
- items: { type: 'string' },
274
- },
275
- },
276
- required: ['analysis', 'entities', 'summary', 'action_items'],
277
- additionalProperties: false,
278
- };
279
- const model = createBedrockModel();
280
- const structuredModel = model.withStructuredOutput(complexSchema, {
281
- name: 'DetailedAnalysis',
282
- method: 'functionCalling',
283
- includeRaw: true,
284
- strict: true,
285
- });
286
- const result = await structuredModel.invoke([
287
- new SystemMessage('You are an expert analyst. Analyze the following business communication and extract detailed structured information.'),
288
- new HumanMessage('From: Sarah Johnson, VP of Engineering at TechCorp\n' +
289
- 'To: All Engineering Teams\n\n' +
290
- 'I am pleased to announce that our new AI platform, CodeAssist, has exceeded Q3 targets by 45%. ' +
291
- 'The Seattle development team, led by Marcus Chen, deserves special recognition for their outstanding work. ' +
292
- 'However, we need to address the performance issues reported by our European clients. ' +
293
- 'The London office has flagged latency problems that must be resolved before the Q4 launch. ' +
294
- 'Action items: 1) Marcus to lead a performance task force. 2) Schedule a meeting with the London team. ' +
295
- '3) Update the deployment pipeline for EU regions.'),
296
- ]);
297
- const parsed = result.parsed ?? result;
298
- console.log('[Integration] Complex analysis result:', JSON.stringify(parsed, null, 2));
299
- // Verify top-level structure
300
- // Note: functionCalling mode is best-effort, so some fields may be missing.
301
- // Native constrained decoding (when available) would guarantee all fields.
302
- expect(parsed).toBeDefined();
303
- expect(typeof parsed.summary).toBe('string');
304
- // Analysis is the most complex nested field — verify if present
305
- if (parsed.analysis) {
306
- expect(['very_positive', 'positive', 'neutral', 'negative', 'very_negative']).toContain(parsed.analysis.overall_sentiment);
307
- expect(typeof parsed.analysis.confidence).toBe('number');
308
- if (Array.isArray(parsed.analysis.key_themes)) {
309
- expect(parsed.analysis.key_themes.length).toBeGreaterThan(0);
310
- for (const theme of parsed.analysis.key_themes) {
311
- expect(typeof theme.theme).toBe('string');
312
- expect(['positive', 'negative', 'neutral']).toContain(theme.sentiment);
313
- expect(typeof theme.evidence).toBe('string');
314
- }
315
- }
316
- }
317
- // Verify entities if present
318
- if (Array.isArray(parsed.entities)) {
319
- expect(parsed.entities.length).toBeGreaterThan(0);
320
- for (const entity of parsed.entities) {
321
- expect(typeof entity.name).toBe('string');
322
- expect(['person', 'organization', 'location', 'product']).toContain(entity.type);
323
- }
324
- }
325
- // Verify action items
326
- if (Array.isArray(parsed.action_items)) {
327
- expect(parsed.action_items.length).toBeGreaterThan(0);
328
- }
329
- // At minimum, we expect the model returned something parseable with the key fields
330
- expect(parsed.summary.length).toBeGreaterThan(0);
331
- });
332
- conditionalTest('tool-like multi-turn conversation with structured output at the end', async () => {
333
- // Simulate a multi-turn conversation where the agent has gathered info
334
- // and now needs to produce structured output
335
- const reportSchema = {
336
- type: 'object',
337
- properties: {
338
- report_title: { type: 'string' },
339
- findings: {
340
- type: 'array',
341
- items: {
342
- type: 'object',
343
- properties: {
344
- category: { type: 'string' },
345
- description: { type: 'string' },
346
- severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
347
- recommendation: { type: 'string' },
348
- },
349
- required: ['category', 'description', 'severity', 'recommendation'],
350
- additionalProperties: false,
351
- },
352
- },
353
- overall_risk_level: {
354
- type: 'string',
355
- enum: ['low', 'medium', 'high', 'critical'],
356
- },
357
- next_steps: {
358
- type: 'array',
359
- items: { type: 'string' },
360
- },
361
- },
362
- required: ['report_title', 'findings', 'overall_risk_level', 'next_steps'],
363
- additionalProperties: false,
364
- };
365
- const model = createBedrockModel();
366
- // Multi-turn conversation simulating tool results being fed in
367
- const messages = [
368
- new SystemMessage('You are a security analyst. You have performed a security audit and gathered the following data from various scans. ' +
369
- 'Produce a structured security report.'),
370
- new HumanMessage('Here are the scan results:\n\n' +
371
- 'Port Scan: Ports 22, 80, 443, 8080 are open. Port 8080 is running an outdated Apache Tomcat 8.5.\n\n' +
372
- 'Vulnerability Scan: Found SQL injection in /api/users endpoint. XSS vulnerability in search form. ' +
373
- 'Outdated OpenSSL 1.1.1 (should be 3.x). No rate limiting on login endpoint.\n\n' +
374
- 'Configuration Review: Default admin credentials found on Tomcat manager. ' +
375
- 'CORS is set to wildcard (*). No CSP headers. Cookies without HttpOnly flag.\n\n' +
376
- 'Provide your structured security report.'),
377
- ];
378
- const structuredModel = model.withStructuredOutput(reportSchema, {
379
- name: 'SecurityReport',
380
- method: 'functionCalling',
381
- includeRaw: true,
382
- strict: true,
383
- });
384
- const result = await structuredModel.invoke(messages);
385
- const parsed = result.parsed ?? result;
386
- console.log('[Integration] Security report result:', JSON.stringify(parsed, null, 2));
387
- // Verify structure
388
- expect(typeof parsed.report_title).toBe('string');
389
- expect(Array.isArray(parsed.findings)).toBe(true);
390
- expect(parsed.findings.length).toBeGreaterThanOrEqual(3); // At least SQL injection, XSS, outdated software
391
- expect(['low', 'medium', 'high', 'critical']).toContain(parsed.overall_risk_level);
392
- expect(Array.isArray(parsed.next_steps)).toBe(true);
393
- expect(parsed.next_steps.length).toBeGreaterThan(0);
394
- // Verify each finding
395
- for (const finding of parsed.findings) {
396
- expect(typeof finding.category).toBe('string');
397
- expect(typeof finding.description).toBe('string');
398
- expect(['low', 'medium', 'high', 'critical']).toContain(finding.severity);
399
- expect(typeof finding.recommendation).toBe('string');
400
- }
401
- // Given the scan results, risk should be high or critical
402
- expect(['high', 'critical']).toContain(parsed.overall_risk_level);
403
- // Validate entire response
404
- const validation = validateStructuredOutput(parsed, reportSchema);
405
- expect(validation.success).toBe(true);
406
- });
407
- conditionalTest('prepared schema with constraint stripping works end-to-end', async () => {
408
- // A schema with MANY unsupported constraints that need stripping
409
- const rawSchema = {
410
- type: 'object',
411
- properties: {
412
- title: { type: 'string', minLength: 5, maxLength: 100 },
413
- rating: { type: 'number', minimum: 1, maximum: 5, multipleOf: 0.5 },
414
- tags: {
415
- type: 'array',
416
- items: { type: 'string', minLength: 1, maxLength: 50 },
417
- minItems: 1,
418
- maxItems: 5,
419
- uniqueItems: true,
420
- },
421
- metadata: {
422
- type: 'object',
423
- properties: {
424
- author: { type: 'string', pattern: '^[A-Za-z ]+$' },
425
- word_count: { type: 'integer', minimum: 0, maximum: 100000 },
426
- language: { type: 'string', enum: ['en', 'es', 'fr', 'de'] },
427
- },
428
- },
429
- },
430
- };
431
- // Prepare for Bedrock
432
- const { schema: prepared, warnings } = prepareSchemaForProvider(rawSchema, Providers.BEDROCK);
433
- console.log('[Integration] Complex schema warnings count:', warnings.length);
434
- expect(warnings.length).toBeGreaterThan(5); // Should have many warnings
435
- // Verify ALL constraints were stripped
436
- const titleProp = prepared.properties.title;
437
- expect(titleProp.minLength).toBeUndefined();
438
- expect(titleProp.maxLength).toBeUndefined();
439
- const ratingProp = prepared.properties.rating;
440
- expect(ratingProp.minimum).toBeUndefined();
441
- expect(ratingProp.maximum).toBeUndefined();
442
- expect(ratingProp.multipleOf).toBeUndefined();
443
- const tagsProp = prepared.properties.tags;
444
- expect(tagsProp.minItems).toBeUndefined();
445
- expect(tagsProp.maxItems).toBeUndefined();
446
- expect(tagsProp.uniqueItems).toBeUndefined();
447
- // Now use with actual model
448
- const model = createBedrockModel();
449
- const structuredModel = model.withStructuredOutput(prepared, {
450
- name: 'BookReview',
451
- method: 'functionCalling',
452
- includeRaw: true,
453
- strict: true,
454
- });
455
- const result = await structuredModel.invoke([
456
- new SystemMessage('You are a book reviewer. Review the book described below.'),
457
- new HumanMessage('Review "The Great Gatsby" by F. Scott Fitzgerald. ' +
458
- 'It is a classic American novel written in English about the decline of the American Dream.'),
459
- ]);
460
- const parsed = result.parsed ?? result;
461
- console.log('[Integration] Book review result:', JSON.stringify(parsed, null, 2));
462
- expect(typeof parsed.title).toBe('string');
463
- expect(typeof parsed.rating).toBe('number');
464
- expect(Array.isArray(parsed.tags)).toBe(true);
465
- expect(parsed.metadata).toBeDefined();
466
- expect(['en', 'es', 'fr', 'de']).toContain(parsed.metadata.language);
467
- });
468
- });
469
- // ──────────────────────────────────────────────────────────────
470
- // Anthropic Direct Integration Tests
471
- // ──────────────────────────────────────────────────────────────
472
- describe('Anthropic direct structured output integration', () => {
473
- const conditionalTest = hasAnthropic ? test : test.skip;
474
- conditionalTest('withStructuredOutput jsonSchema (native) - person extraction', async () => {
475
- // Dynamic import to avoid errors when @langchain/anthropic is not installed
476
- const { CustomAnthropic } = await import('@/llm/anthropic');
477
- const model = new CustomAnthropic({
478
- anthropicApiKey: process.env.ANTHROPIC_API_KEY,
479
- model: 'claude-sonnet-4-20250514',
480
- maxTokens: 1024,
481
- });
482
- const structuredModel = model.withStructuredOutput(personSchema, {
483
- name: 'PersonInfo',
484
- method: 'jsonSchema',
485
- includeRaw: true,
486
- strict: true,
487
- });
488
- const result = await structuredModel.invoke([
489
- new SystemMessage('Extract person information from the text.'),
490
- new HumanMessage('Marie Curie was a 66 year old physicist and chemist.'),
491
- ]);
492
- const parsed = result.parsed ?? result;
493
- console.log('[Integration] Anthropic native result:', JSON.stringify(parsed, null, 2));
494
- expect(parsed).toBeDefined();
495
- expect(typeof parsed.name).toBe('string');
496
- expect(typeof parsed.age).toBe('number');
497
- expect(typeof parsed.occupation).toBe('string');
498
- const validation = validateStructuredOutput(parsed, personSchema);
499
- expect(validation.success).toBe(true);
500
- });
501
- conditionalTest('withStructuredOutput jsonSchema - classification with enum', async () => {
502
- const { CustomAnthropic } = await import('@/llm/anthropic');
503
- const model = new CustomAnthropic({
504
- anthropicApiKey: process.env.ANTHROPIC_API_KEY,
505
- model: 'claude-sonnet-4-20250514',
506
- maxTokens: 1024,
507
- });
508
- const structuredModel = model.withStructuredOutput(classificationSchema, {
509
- name: 'Sentiment',
510
- method: 'jsonSchema',
511
- includeRaw: true,
512
- strict: true,
513
- });
514
- const result = await structuredModel.invoke([
515
- new SystemMessage('Classify the sentiment.'),
516
- new HumanMessage('This is the best day of my life!'),
517
- ]);
518
- const parsed = result.parsed ?? result;
519
- console.log('[Integration] Anthropic classification:', JSON.stringify(parsed, null, 2));
520
- expect(['positive', 'negative', 'neutral']).toContain(parsed.category);
521
- expect(parsed.category).toBe('positive');
522
- });
523
- });
524
- // ──────────────────────────────────────────────────────────────
525
- // OpenAI Integration Tests
526
- // ──────────────────────────────────────────────────────────────
527
- describe('OpenAI structured output integration', () => {
528
- const conditionalTest = hasOpenAI ? test : test.skip;
529
- conditionalTest('withStructuredOutput jsonSchema (native) - person extraction', async () => {
530
- const { ChatOpenAI } = await import('@/llm/openai');
531
- const model = new ChatOpenAI({
532
- openAIApiKey: process.env.OPENAI_API_KEY,
533
- model: 'gpt-4o-mini',
534
- maxTokens: 1024,
535
- });
536
- const structuredModel = model.withStructuredOutput(personSchema, {
537
- name: 'PersonInfo',
538
- method: 'jsonSchema',
539
- includeRaw: true,
540
- strict: true,
541
- });
542
- const result = await structuredModel.invoke([
543
- new SystemMessage('Extract person information from the text.'),
544
- new HumanMessage('Albert Einstein was a 76 year old theoretical physicist.'),
545
- ]);
546
- const parsed = result.parsed ?? result;
547
- console.log('[Integration] OpenAI native result:', JSON.stringify(parsed, null, 2));
548
- expect(parsed).toBeDefined();
549
- expect(typeof parsed.name).toBe('string');
550
- expect(typeof parsed.age).toBe('number');
551
- const validation = validateStructuredOutput(parsed, personSchema);
552
- expect(validation.success).toBe(true);
553
- });
554
- });
555
- // ──────────────────────────────────────────────────────────────
556
- // Schema Preparation Integration Tests
557
- // ──────────────────────────────────────────────────────────────
558
- describe('Schema preparation produces valid schemas for all providers', () => {
559
- const complexSchema = {
560
- type: 'object',
561
- properties: {
562
- analysis: {
563
- type: 'object',
564
- properties: {
565
- score: { type: 'number', minimum: 0, maximum: 100 },
566
- label: { type: 'string', enum: ['low', 'medium', 'high'] },
567
- details: { type: 'string', maxLength: 1000 },
568
- },
569
- },
570
- tags: {
571
- type: 'array',
572
- items: { type: 'string', minLength: 1 },
573
- minItems: 1,
574
- maxItems: 10,
575
- },
576
- metadata: {
577
- type: 'object',
578
- properties: {
579
- source: { type: 'string' },
580
- timestamp: { type: 'string' },
581
- },
582
- },
583
- },
584
- };
585
- test('prepares valid schema for Anthropic', () => {
586
- const { schema, warnings } = prepareSchemaForProvider(complexSchema, Providers.ANTHROPIC);
587
- // Root object
588
- expect(schema.additionalProperties).toBe(false);
589
- expect(schema.required).toEqual(expect.arrayContaining(['analysis', 'tags', 'metadata']));
590
- // Nested object: analysis
591
- const analysis = schema.properties.analysis;
592
- expect(analysis.additionalProperties).toBe(false);
593
- expect(analysis.required).toEqual(expect.arrayContaining(['score', 'label', 'details']));
594
- // Numeric constraints stripped
595
- expect(analysis.properties.score.minimum).toBeUndefined();
596
- expect(analysis.properties.score.maximum).toBeUndefined();
597
- // String constraints stripped
598
- expect(analysis.properties.details.maxLength).toBeUndefined();
599
- // Array constraints stripped
600
- const tags = schema.properties.tags;
601
- expect(tags.minItems).toBeUndefined();
602
- expect(tags.maxItems).toBeUndefined();
603
- // Array item string constraints stripped
604
- expect(tags.items.minLength).toBeUndefined();
605
- // Nested object: metadata
606
- const metadata = schema.properties.metadata;
607
- expect(metadata.additionalProperties).toBe(false);
608
- expect(warnings.length).toBeGreaterThan(0);
609
- console.log('[Integration] Anthropic schema warnings:', warnings);
610
- });
611
- test('prepares valid schema for OpenAI', () => {
612
- const { schema } = prepareSchemaForProvider(complexSchema, Providers.OPENAI);
613
- expect(schema.additionalProperties).toBe(false);
614
- const analysis = schema.properties.analysis;
615
- expect(analysis.properties.score.minimum).toBeUndefined();
616
- });
617
- test('prepares valid schema for Bedrock', () => {
618
- const { schema } = prepareSchemaForProvider(complexSchema, Providers.BEDROCK);
619
- expect(schema.additionalProperties).toBe(false);
620
- const analysis = schema.properties.analysis;
621
- expect(analysis.properties.score.minimum).toBeUndefined();
622
- });
623
- });
624
- //# sourceMappingURL=structured-output.integration.test.js.map