illuma-agents 1.0.2 → 1.0.4

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 (225) hide show
  1. package/LICENSE +25 -21
  2. package/dist/cjs/agents/AgentContext.cjs +222 -0
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -0
  4. package/dist/cjs/common/enum.cjs +5 -4
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/events.cjs +7 -5
  7. package/dist/cjs/events.cjs.map +1 -1
  8. package/dist/cjs/graphs/Graph.cjs +328 -207
  9. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs +507 -0
  11. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -0
  12. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  13. package/dist/cjs/llm/google/index.cjs.map +1 -1
  14. package/dist/cjs/llm/ollama/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openai/index.cjs +35 -0
  16. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openai/utils/index.cjs +3 -1
  18. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  19. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  20. package/dist/cjs/llm/providers.cjs +0 -2
  21. package/dist/cjs/llm/providers.cjs.map +1 -1
  22. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  23. package/dist/cjs/main.cjs +12 -1
  24. package/dist/cjs/main.cjs.map +1 -1
  25. package/dist/cjs/messages/cache.cjs +123 -0
  26. package/dist/cjs/messages/cache.cjs.map +1 -0
  27. package/dist/cjs/messages/content.cjs +53 -0
  28. package/dist/cjs/messages/content.cjs.map +1 -0
  29. package/dist/cjs/messages/format.cjs +17 -29
  30. package/dist/cjs/messages/format.cjs.map +1 -1
  31. package/dist/cjs/run.cjs +119 -74
  32. package/dist/cjs/run.cjs.map +1 -1
  33. package/dist/cjs/stream.cjs +77 -73
  34. package/dist/cjs/stream.cjs.map +1 -1
  35. package/dist/cjs/tools/Calculator.cjs +45 -0
  36. package/dist/cjs/tools/Calculator.cjs.map +1 -0
  37. package/dist/cjs/tools/CodeExecutor.cjs +22 -22
  38. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +5 -3
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/handlers.cjs +20 -20
  42. package/dist/cjs/tools/handlers.cjs.map +1 -1
  43. package/dist/cjs/utils/events.cjs +31 -0
  44. package/dist/cjs/utils/events.cjs.map +1 -0
  45. package/dist/cjs/utils/handlers.cjs +70 -0
  46. package/dist/cjs/utils/handlers.cjs.map +1 -0
  47. package/dist/cjs/utils/tokens.cjs +54 -7
  48. package/dist/cjs/utils/tokens.cjs.map +1 -1
  49. package/dist/esm/agents/AgentContext.mjs +220 -0
  50. package/dist/esm/agents/AgentContext.mjs.map +1 -0
  51. package/dist/esm/common/enum.mjs +5 -4
  52. package/dist/esm/common/enum.mjs.map +1 -1
  53. package/dist/esm/events.mjs +7 -5
  54. package/dist/esm/events.mjs.map +1 -1
  55. package/dist/esm/graphs/Graph.mjs +330 -209
  56. package/dist/esm/graphs/Graph.mjs.map +1 -1
  57. package/dist/esm/graphs/MultiAgentGraph.mjs +505 -0
  58. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -0
  59. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  60. package/dist/esm/llm/google/index.mjs.map +1 -1
  61. package/dist/esm/llm/ollama/index.mjs.map +1 -1
  62. package/dist/esm/llm/openai/index.mjs +35 -0
  63. package/dist/esm/llm/openai/index.mjs.map +1 -1
  64. package/dist/esm/llm/openai/utils/index.mjs +3 -1
  65. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  66. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  67. package/dist/esm/llm/providers.mjs +0 -2
  68. package/dist/esm/llm/providers.mjs.map +1 -1
  69. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  70. package/dist/esm/main.mjs +7 -2
  71. package/dist/esm/main.mjs.map +1 -1
  72. package/dist/esm/messages/cache.mjs +120 -0
  73. package/dist/esm/messages/cache.mjs.map +1 -0
  74. package/dist/esm/messages/content.mjs +51 -0
  75. package/dist/esm/messages/content.mjs.map +1 -0
  76. package/dist/esm/messages/format.mjs +18 -29
  77. package/dist/esm/messages/format.mjs.map +1 -1
  78. package/dist/esm/run.mjs +119 -74
  79. package/dist/esm/run.mjs.map +1 -1
  80. package/dist/esm/stream.mjs +77 -73
  81. package/dist/esm/stream.mjs.map +1 -1
  82. package/dist/esm/tools/Calculator.mjs +24 -0
  83. package/dist/esm/tools/Calculator.mjs.map +1 -0
  84. package/dist/esm/tools/CodeExecutor.mjs +22 -22
  85. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  86. package/dist/esm/tools/ToolNode.mjs +5 -3
  87. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  88. package/dist/esm/tools/handlers.mjs +20 -20
  89. package/dist/esm/tools/handlers.mjs.map +1 -1
  90. package/dist/esm/utils/events.mjs +29 -0
  91. package/dist/esm/utils/events.mjs.map +1 -0
  92. package/dist/esm/utils/handlers.mjs +68 -0
  93. package/dist/esm/utils/handlers.mjs.map +1 -0
  94. package/dist/esm/utils/tokens.mjs +54 -8
  95. package/dist/esm/utils/tokens.mjs.map +1 -1
  96. package/dist/types/agents/AgentContext.d.ts +94 -0
  97. package/dist/types/common/enum.d.ts +7 -5
  98. package/dist/types/events.d.ts +3 -3
  99. package/dist/types/graphs/Graph.d.ts +60 -66
  100. package/dist/types/graphs/MultiAgentGraph.d.ts +47 -0
  101. package/dist/types/graphs/index.d.ts +1 -0
  102. package/dist/types/index.d.ts +1 -0
  103. package/dist/types/llm/openai/index.d.ts +10 -0
  104. package/dist/types/messages/cache.d.ts +20 -0
  105. package/dist/types/messages/content.d.ts +7 -0
  106. package/dist/types/messages/format.d.ts +1 -7
  107. package/dist/types/messages/index.d.ts +2 -0
  108. package/dist/types/messages/reducer.d.ts +9 -0
  109. package/dist/types/run.d.ts +16 -10
  110. package/dist/types/stream.d.ts +4 -3
  111. package/dist/types/tools/Calculator.d.ts +8 -0
  112. package/dist/types/tools/ToolNode.d.ts +1 -1
  113. package/dist/types/tools/handlers.d.ts +9 -7
  114. package/dist/types/tools/search/tool.d.ts +4 -4
  115. package/dist/types/types/graph.d.ts +124 -11
  116. package/dist/types/types/llm.d.ts +13 -9
  117. package/dist/types/types/messages.d.ts +4 -0
  118. package/dist/types/types/run.d.ts +46 -8
  119. package/dist/types/types/stream.d.ts +3 -2
  120. package/dist/types/utils/events.d.ts +6 -0
  121. package/dist/types/utils/handlers.d.ts +34 -0
  122. package/dist/types/utils/index.d.ts +1 -0
  123. package/dist/types/utils/tokens.d.ts +24 -0
  124. package/package.json +162 -145
  125. package/src/agents/AgentContext.ts +323 -0
  126. package/src/common/enum.ts +177 -176
  127. package/src/events.ts +197 -191
  128. package/src/graphs/Graph.ts +1058 -846
  129. package/src/graphs/MultiAgentGraph.ts +598 -0
  130. package/src/graphs/index.ts +2 -1
  131. package/src/index.ts +25 -24
  132. package/src/llm/anthropic/index.ts +413 -413
  133. package/src/llm/google/index.ts +222 -222
  134. package/src/llm/google/utils/zod_to_genai_parameters.ts +86 -88
  135. package/src/llm/ollama/index.ts +92 -92
  136. package/src/llm/openai/index.ts +894 -853
  137. package/src/llm/openai/utils/index.ts +920 -918
  138. package/src/llm/openrouter/index.ts +60 -60
  139. package/src/llm/providers.ts +55 -57
  140. package/src/llm/vertexai/index.ts +360 -360
  141. package/src/messages/cache.test.ts +461 -0
  142. package/src/messages/cache.ts +151 -0
  143. package/src/messages/content.test.ts +362 -0
  144. package/src/messages/content.ts +63 -0
  145. package/src/messages/format.ts +611 -625
  146. package/src/messages/formatAgentMessages.test.ts +1144 -917
  147. package/src/messages/index.ts +6 -4
  148. package/src/messages/reducer.ts +80 -0
  149. package/src/run.ts +447 -381
  150. package/src/scripts/abort.ts +157 -138
  151. package/src/scripts/ant_web_search.ts +158 -158
  152. package/src/scripts/cli.ts +172 -167
  153. package/src/scripts/cli2.ts +133 -125
  154. package/src/scripts/cli3.ts +184 -178
  155. package/src/scripts/cli4.ts +191 -184
  156. package/src/scripts/cli5.ts +191 -184
  157. package/src/scripts/code_exec.ts +213 -214
  158. package/src/scripts/code_exec_simple.ts +147 -129
  159. package/src/scripts/content.ts +138 -120
  160. package/src/scripts/handoff-test.ts +135 -0
  161. package/src/scripts/multi-agent-chain.ts +278 -0
  162. package/src/scripts/multi-agent-conditional.ts +220 -0
  163. package/src/scripts/multi-agent-document-review-chain.ts +197 -0
  164. package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
  165. package/src/scripts/multi-agent-parallel.ts +343 -0
  166. package/src/scripts/multi-agent-sequence.ts +212 -0
  167. package/src/scripts/multi-agent-supervisor.ts +364 -0
  168. package/src/scripts/multi-agent-test.ts +186 -0
  169. package/src/scripts/search.ts +146 -150
  170. package/src/scripts/simple.ts +225 -225
  171. package/src/scripts/stream.ts +140 -122
  172. package/src/scripts/test-custom-prompt-key.ts +145 -0
  173. package/src/scripts/test-handoff-input.ts +170 -0
  174. package/src/scripts/test-multi-agent-list-handoff.ts +261 -0
  175. package/src/scripts/test-tools-before-handoff.ts +222 -0
  176. package/src/scripts/tools.ts +153 -155
  177. package/src/specs/agent-handoffs.test.ts +889 -0
  178. package/src/specs/anthropic.simple.test.ts +320 -317
  179. package/src/specs/azure.simple.test.ts +325 -316
  180. package/src/specs/openai.simple.test.ts +311 -316
  181. package/src/specs/openrouter.simple.test.ts +107 -0
  182. package/src/specs/prune.test.ts +758 -763
  183. package/src/specs/reasoning.test.ts +201 -165
  184. package/src/specs/thinking-prune.test.ts +769 -703
  185. package/src/specs/token-memoization.test.ts +39 -0
  186. package/src/stream.ts +664 -651
  187. package/src/tools/Calculator.test.ts +278 -0
  188. package/src/tools/Calculator.ts +25 -0
  189. package/src/tools/CodeExecutor.ts +220 -220
  190. package/src/tools/ToolNode.ts +170 -170
  191. package/src/tools/handlers.ts +341 -336
  192. package/src/types/graph.ts +372 -185
  193. package/src/types/llm.ts +141 -140
  194. package/src/types/messages.ts +4 -0
  195. package/src/types/run.ts +128 -89
  196. package/src/types/stream.ts +401 -400
  197. package/src/utils/events.ts +32 -0
  198. package/src/utils/handlers.ts +107 -0
  199. package/src/utils/index.ts +6 -5
  200. package/src/utils/llmConfig.ts +183 -183
  201. package/src/utils/tokens.ts +129 -70
  202. package/dist/types/scripts/abort.d.ts +0 -1
  203. package/dist/types/scripts/ant_web_search.d.ts +0 -1
  204. package/dist/types/scripts/args.d.ts +0 -7
  205. package/dist/types/scripts/caching.d.ts +0 -1
  206. package/dist/types/scripts/cli.d.ts +0 -1
  207. package/dist/types/scripts/cli2.d.ts +0 -1
  208. package/dist/types/scripts/cli3.d.ts +0 -1
  209. package/dist/types/scripts/cli4.d.ts +0 -1
  210. package/dist/types/scripts/cli5.d.ts +0 -1
  211. package/dist/types/scripts/code_exec.d.ts +0 -1
  212. package/dist/types/scripts/code_exec_files.d.ts +0 -1
  213. package/dist/types/scripts/code_exec_simple.d.ts +0 -1
  214. package/dist/types/scripts/content.d.ts +0 -1
  215. package/dist/types/scripts/empty_input.d.ts +0 -1
  216. package/dist/types/scripts/image.d.ts +0 -1
  217. package/dist/types/scripts/memory.d.ts +0 -1
  218. package/dist/types/scripts/search.d.ts +0 -1
  219. package/dist/types/scripts/simple.d.ts +0 -1
  220. package/dist/types/scripts/stream.d.ts +0 -1
  221. package/dist/types/scripts/thinking.d.ts +0 -1
  222. package/dist/types/scripts/tools.d.ts +0 -1
  223. package/dist/types/specs/spec.utils.d.ts +0 -1
  224. package/dist/types/tools/example.d.ts +0 -78
  225. package/src/tools/example.ts +0 -129
@@ -1,316 +1,325 @@
1
- /* eslint-disable no-console */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- // src/scripts/cli.test.ts
4
- import { config } from 'dotenv';
5
- config();
6
- import { Calculator } from '@langchain/community/tools/calculator';
7
- import {
8
- HumanMessage,
9
- BaseMessage,
10
- UsageMetadata,
11
- } from '@langchain/core/messages';
12
- import type { StandardGraph } from '@/graphs';
13
- import type * as t from '@/types';
14
- import {
15
- ToolEndHandler,
16
- ModelEndHandler,
17
- createMetadataAggregator,
18
- } from '@/events';
19
- import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
20
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
21
- import { capitalizeFirstLetter } from './spec.utils';
22
- import { getLLMConfig } from '@/utils/llmConfig';
23
- import { getArgs } from '@/scripts/args';
24
- import { Run } from '@/run';
25
-
26
- const provider = Providers.AZURE;
27
- describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
28
- jest.setTimeout(30000);
29
- let run: Run<t.IState>;
30
- let runningHistory: BaseMessage[];
31
- let collectedUsage: UsageMetadata[];
32
- let conversationHistory: BaseMessage[];
33
- let aggregateContent: t.ContentAggregator;
34
- let contentParts: t.MessageContentComplex[];
35
-
36
- const config = {
37
- configurable: {
38
- thread_id: 'conversation-num-1',
39
- },
40
- streamMode: 'values',
41
- version: 'v2' as const,
42
- };
43
-
44
- beforeEach(async () => {
45
- conversationHistory = [];
46
- collectedUsage = [];
47
- const { contentParts: cp, aggregateContent: ac } =
48
- createContentAggregator();
49
- contentParts = cp as t.MessageContentComplex[];
50
- aggregateContent = ac;
51
- });
52
-
53
- const onMessageDeltaSpy = jest.fn();
54
- const onRunStepSpy = jest.fn();
55
-
56
- afterAll(() => {
57
- onMessageDeltaSpy.mockReset();
58
- onRunStepSpy.mockReset();
59
- });
60
-
61
- const setupCustomHandlers = (): Record<
62
- string | GraphEvents,
63
- t.EventHandler
64
- > => ({
65
- [GraphEvents.TOOL_END]: new ToolEndHandler(),
66
- [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
67
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
68
- [GraphEvents.ON_RUN_STEP_COMPLETED]: {
69
- handle: (
70
- event: GraphEvents.ON_RUN_STEP_COMPLETED,
71
- data: t.StreamEventData
72
- ): void => {
73
- aggregateContent({
74
- event,
75
- data: data as unknown as { result: t.ToolEndEvent },
76
- });
77
- },
78
- },
79
- [GraphEvents.ON_RUN_STEP]: {
80
- handle: (
81
- event: GraphEvents.ON_RUN_STEP,
82
- data: t.StreamEventData,
83
- metadata,
84
- graph
85
- ): void => {
86
- onRunStepSpy(event, data, metadata, graph);
87
- aggregateContent({ event, data: data as t.RunStep });
88
- },
89
- },
90
- [GraphEvents.ON_RUN_STEP_DELTA]: {
91
- handle: (
92
- event: GraphEvents.ON_RUN_STEP_DELTA,
93
- data: t.StreamEventData
94
- ): void => {
95
- aggregateContent({ event, data: data as t.RunStepDeltaEvent });
96
- },
97
- },
98
- [GraphEvents.ON_MESSAGE_DELTA]: {
99
- handle: (
100
- event: GraphEvents.ON_MESSAGE_DELTA,
101
- data: t.StreamEventData,
102
- metadata,
103
- graph
104
- ): void => {
105
- onMessageDeltaSpy(event, data, metadata, graph);
106
- aggregateContent({ event, data: data as t.MessageDeltaEvent });
107
- },
108
- },
109
- [GraphEvents.TOOL_START]: {
110
- handle: (
111
- _event: string,
112
- _data: t.StreamEventData,
113
- _metadata?: Record<string, unknown>
114
- ): void => {
115
- // Handle tool start
116
- },
117
- },
118
- });
119
-
120
- test(`${capitalizeFirstLetter(provider)}: should process a simple message, generate title`, async () => {
121
- const { userName, location } = await getArgs();
122
- const llmConfig = getLLMConfig(provider);
123
- const customHandlers = setupCustomHandlers();
124
-
125
- run = await Run.create<t.IState>({
126
- runId: 'test-run-id',
127
- graphConfig: {
128
- type: 'standard',
129
- llmConfig,
130
- tools: [new Calculator()],
131
- instructions:
132
- 'You are a friendly AI assistant. Always address the user by their name.',
133
- additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
134
- },
135
- returnContent: true,
136
- customHandlers,
137
- });
138
-
139
- const userMessage = 'hi';
140
- conversationHistory.push(new HumanMessage(userMessage));
141
-
142
- const inputs = {
143
- messages: conversationHistory,
144
- };
145
-
146
- const finalContentParts = await run.processStream(inputs, config);
147
- expect(finalContentParts).toBeDefined();
148
- const allTextParts = finalContentParts?.every(
149
- (part) => part.type === ContentTypes.TEXT
150
- );
151
- expect(allTextParts).toBe(true);
152
- expect(collectedUsage.length).toBeGreaterThan(0);
153
- expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
154
- expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
155
-
156
- const finalMessages = run.getRunMessages();
157
- expect(finalMessages).toBeDefined();
158
- conversationHistory.push(...(finalMessages ?? []));
159
- expect(conversationHistory.length).toBeGreaterThan(1);
160
- runningHistory = conversationHistory.slice();
161
-
162
- expect(onMessageDeltaSpy).toHaveBeenCalled();
163
- expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
164
- expect(
165
- (onMessageDeltaSpy.mock.calls[0][3] as StandardGraph).provider
166
- ).toBeDefined();
167
-
168
- expect(onRunStepSpy).toHaveBeenCalled();
169
- expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
170
- expect(
171
- (onRunStepSpy.mock.calls[0][3] as StandardGraph).provider
172
- ).toBeDefined();
173
-
174
- const { handleLLMEnd, collected } = createMetadataAggregator();
175
- const titleResult = await run.generateTitle({
176
- provider,
177
- inputText: userMessage,
178
- titleMethod: TitleMethod.STRUCTURED,
179
- contentParts,
180
- chainOptions: {
181
- callbacks: [
182
- {
183
- handleLLMEnd,
184
- },
185
- ],
186
- },
187
- });
188
-
189
- expect(titleResult).toBeDefined();
190
- expect(titleResult.title).toBeDefined();
191
- expect(titleResult.language).toBeDefined();
192
- expect(collected).toBeDefined();
193
- });
194
-
195
- test(`${capitalizeFirstLetter(provider)}: should generate title using completion method`, async () => {
196
- const { userName, location } = await getArgs();
197
- const llmConfig = getLLMConfig(provider);
198
- const customHandlers = setupCustomHandlers();
199
-
200
- run = await Run.create<t.IState>({
201
- runId: 'test-run-id-completion',
202
- graphConfig: {
203
- type: 'standard',
204
- llmConfig,
205
- tools: [new Calculator()],
206
- instructions:
207
- 'You are a friendly AI assistant. Always address the user by their name.',
208
- additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
209
- },
210
- returnContent: true,
211
- customHandlers,
212
- });
213
-
214
- const userMessage = 'What is the weather like today?';
215
- conversationHistory = [];
216
- conversationHistory.push(new HumanMessage(userMessage));
217
-
218
- const inputs = {
219
- messages: conversationHistory,
220
- };
221
-
222
- const finalContentParts = await run.processStream(inputs, config);
223
- expect(finalContentParts).toBeDefined();
224
-
225
- const { handleLLMEnd, collected } = createMetadataAggregator();
226
- const titleResult = await run.generateTitle({
227
- provider,
228
- inputText: userMessage,
229
- titleMethod: TitleMethod.COMPLETION, // Using completion method
230
- contentParts,
231
- chainOptions: {
232
- callbacks: [
233
- {
234
- handleLLMEnd,
235
- },
236
- ],
237
- },
238
- });
239
-
240
- expect(titleResult).toBeDefined();
241
- expect(titleResult.title).toBeDefined();
242
- expect(titleResult.title).not.toBe('');
243
- // Completion method doesn't return language
244
- expect(titleResult.language).toBeUndefined();
245
- expect(collected).toBeDefined();
246
- console.log(`Completion method generated title: "${titleResult.title}"`);
247
- });
248
-
249
- test(`${capitalizeFirstLetter(provider)}: should follow-up`, async () => {
250
- console.log('Previous conversation length:', runningHistory.length);
251
- console.log(
252
- 'Last message:',
253
- runningHistory[runningHistory.length - 1].content
254
- );
255
- const { userName, location } = await getArgs();
256
- const llmConfig = getLLMConfig(provider);
257
- const customHandlers = setupCustomHandlers();
258
-
259
- run = await Run.create<t.IState>({
260
- runId: 'test-run-id',
261
- graphConfig: {
262
- type: 'standard',
263
- llmConfig,
264
- tools: [new Calculator()],
265
- instructions:
266
- 'You are a friendly AI assistant. Always address the user by their name.',
267
- additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
268
- },
269
- returnContent: true,
270
- customHandlers,
271
- });
272
-
273
- conversationHistory = runningHistory.slice();
274
- conversationHistory.push(new HumanMessage('how are you?'));
275
-
276
- const inputs = {
277
- messages: conversationHistory,
278
- };
279
-
280
- const finalContentParts = await run.processStream(inputs, config);
281
- expect(finalContentParts).toBeDefined();
282
- const allTextParts = finalContentParts?.every(
283
- (part) => part.type === ContentTypes.TEXT
284
- );
285
- expect(allTextParts).toBe(true);
286
- expect(collectedUsage.length).toBeGreaterThan(0);
287
- expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
288
- expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
289
-
290
- const finalMessages = run.getRunMessages();
291
- expect(finalMessages).toBeDefined();
292
- expect(finalMessages?.length).toBeGreaterThan(0);
293
- console.log(
294
- `${capitalizeFirstLetter(provider)} follow-up message:`,
295
- finalMessages?.[finalMessages.length - 1]?.content
296
- );
297
-
298
- expect(onMessageDeltaSpy).toHaveBeenCalled();
299
- expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
300
-
301
- expect(onRunStepSpy).toHaveBeenCalled();
302
- expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
303
- });
304
-
305
- test('should handle errors appropriately', async () => {
306
- // Test error scenarios
307
- await expect(async () => {
308
- await run.processStream(
309
- {
310
- messages: [],
311
- },
312
- {} as any
313
- );
314
- }).rejects.toThrow();
315
- });
316
- });
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ // src/scripts/cli.test.ts
4
+ import { config } from 'dotenv';
5
+ config();
6
+ import { Calculator } from '@/tools/Calculator';
7
+ import {
8
+ HumanMessage,
9
+ BaseMessage,
10
+ UsageMetadata,
11
+ } from '@langchain/core/messages';
12
+ import type * as t from '@/types';
13
+ import {
14
+ ToolEndHandler,
15
+ ModelEndHandler,
16
+ createMetadataAggregator,
17
+ } from '@/events';
18
+ import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
19
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
20
+ import { capitalizeFirstLetter } from './spec.utils';
21
+ import { getLLMConfig } from '@/utils/llmConfig';
22
+ import { getArgs } from '@/scripts/args';
23
+ import { Run } from '@/run';
24
+
25
+ // Auto-skip this suite if Azure env vars are not present
26
+ const requiredAzureEnv = [
27
+ 'AZURE_OPENAI_API_KEY',
28
+ 'AZURE_OPENAI_API_INSTANCE',
29
+ 'AZURE_OPENAI_API_DEPLOYMENT',
30
+ 'AZURE_OPENAI_API_VERSION',
31
+ ];
32
+ const hasAzure = requiredAzureEnv.every(
33
+ (k) => (process.env[k] ?? '').trim() !== ''
34
+ );
35
+ const describeIfAzure = hasAzure ? describe : describe.skip;
36
+
37
+ const provider = Providers.AZURE;
38
+ describeIfAzure(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
39
+ jest.setTimeout(30000);
40
+ let run: Run<t.IState>;
41
+ let runningHistory: BaseMessage[];
42
+ let collectedUsage: UsageMetadata[];
43
+ let conversationHistory: BaseMessage[];
44
+ let aggregateContent: t.ContentAggregator;
45
+ let contentParts: t.MessageContentComplex[];
46
+
47
+ const config = {
48
+ configurable: {
49
+ thread_id: 'conversation-num-1',
50
+ },
51
+ streamMode: 'values',
52
+ version: 'v2' as const,
53
+ };
54
+
55
+ beforeEach(async () => {
56
+ conversationHistory = [];
57
+ collectedUsage = [];
58
+ const { contentParts: cp, aggregateContent: ac } =
59
+ createContentAggregator();
60
+ contentParts = cp as t.MessageContentComplex[];
61
+ aggregateContent = ac;
62
+ });
63
+
64
+ const onMessageDeltaSpy = jest.fn();
65
+ const onRunStepSpy = jest.fn();
66
+
67
+ afterAll(() => {
68
+ onMessageDeltaSpy.mockReset();
69
+ onRunStepSpy.mockReset();
70
+ });
71
+
72
+ const setupCustomHandlers = (): Record<
73
+ string | GraphEvents,
74
+ t.EventHandler
75
+ > => ({
76
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
77
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
78
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
79
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
80
+ handle: (
81
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
82
+ data: t.StreamEventData
83
+ ): void => {
84
+ aggregateContent({
85
+ event,
86
+ data: data as unknown as { result: t.ToolEndEvent },
87
+ });
88
+ },
89
+ },
90
+ [GraphEvents.ON_RUN_STEP]: {
91
+ handle: (
92
+ event: GraphEvents.ON_RUN_STEP,
93
+ data: t.StreamEventData,
94
+ metadata,
95
+ graph
96
+ ): void => {
97
+ onRunStepSpy(event, data, metadata, graph);
98
+ aggregateContent({ event, data: data as t.RunStep });
99
+ },
100
+ },
101
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
102
+ handle: (
103
+ event: GraphEvents.ON_RUN_STEP_DELTA,
104
+ data: t.StreamEventData
105
+ ): void => {
106
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
107
+ },
108
+ },
109
+ [GraphEvents.ON_MESSAGE_DELTA]: {
110
+ handle: (
111
+ event: GraphEvents.ON_MESSAGE_DELTA,
112
+ data: t.StreamEventData,
113
+ metadata,
114
+ graph
115
+ ): void => {
116
+ onMessageDeltaSpy(event, data, metadata, graph);
117
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
118
+ },
119
+ },
120
+ [GraphEvents.TOOL_START]: {
121
+ handle: (
122
+ _event: string,
123
+ _data: t.StreamEventData,
124
+ _metadata?: Record<string, unknown>
125
+ ): void => {
126
+ // Handle tool start
127
+ },
128
+ },
129
+ });
130
+
131
+ test(`${capitalizeFirstLetter(provider)}: should process a simple message, generate title`, async () => {
132
+ const { userName, location } = await getArgs();
133
+ const llmConfig = getLLMConfig(provider);
134
+ const customHandlers = setupCustomHandlers();
135
+
136
+ run = await Run.create<t.IState>({
137
+ runId: 'test-run-id',
138
+ graphConfig: {
139
+ type: 'standard',
140
+ llmConfig,
141
+ tools: [new Calculator()],
142
+ instructions:
143
+ 'You are a friendly AI assistant. Always address the user by their name.',
144
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
145
+ },
146
+ returnContent: true,
147
+ customHandlers,
148
+ });
149
+
150
+ const userMessage = 'hi';
151
+ conversationHistory.push(new HumanMessage(userMessage));
152
+
153
+ const inputs = {
154
+ messages: conversationHistory,
155
+ };
156
+
157
+ const finalContentParts = await run.processStream(inputs, config);
158
+ expect(finalContentParts).toBeDefined();
159
+ const allTextParts = finalContentParts?.every(
160
+ (part) => part.type === ContentTypes.TEXT
161
+ );
162
+ expect(allTextParts).toBe(true);
163
+ expect(collectedUsage.length).toBeGreaterThan(0);
164
+ expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
165
+ expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
166
+
167
+ const finalMessages = run.getRunMessages();
168
+ expect(finalMessages).toBeDefined();
169
+ conversationHistory.push(...(finalMessages ?? []));
170
+ expect(conversationHistory.length).toBeGreaterThan(1);
171
+ runningHistory = conversationHistory.slice();
172
+
173
+ expect(onMessageDeltaSpy).toHaveBeenCalled();
174
+ expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
175
+ expect(onMessageDeltaSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
176
+
177
+ expect(onRunStepSpy).toHaveBeenCalled();
178
+ expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
179
+ expect(onRunStepSpy.mock.calls[0][3]).toBeDefined(); // Graph exists
180
+
181
+ const { handleLLMEnd, collected } = createMetadataAggregator();
182
+ const titleResult = await run.generateTitle({
183
+ provider,
184
+ inputText: userMessage,
185
+ titleMethod: TitleMethod.STRUCTURED,
186
+ contentParts,
187
+ clientOptions: llmConfig,
188
+ chainOptions: {
189
+ callbacks: [
190
+ {
191
+ handleLLMEnd,
192
+ },
193
+ ],
194
+ },
195
+ });
196
+
197
+ expect(titleResult).toBeDefined();
198
+ expect(titleResult.title).toBeDefined();
199
+ expect(titleResult.language).toBeDefined();
200
+ expect(collected).toBeDefined();
201
+ });
202
+
203
+ test(`${capitalizeFirstLetter(provider)}: should generate title using completion method`, async () => {
204
+ const { userName, location } = await getArgs();
205
+ const llmConfig = getLLMConfig(provider);
206
+ const customHandlers = setupCustomHandlers();
207
+
208
+ run = await Run.create<t.IState>({
209
+ runId: 'test-run-id-completion',
210
+ graphConfig: {
211
+ type: 'standard',
212
+ llmConfig,
213
+ tools: [new Calculator()],
214
+ instructions:
215
+ 'You are a friendly AI assistant. Always address the user by their name.',
216
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
217
+ },
218
+ returnContent: true,
219
+ customHandlers,
220
+ });
221
+
222
+ const userMessage = 'What is the weather like today?';
223
+ conversationHistory = [];
224
+ conversationHistory.push(new HumanMessage(userMessage));
225
+
226
+ const inputs = {
227
+ messages: conversationHistory,
228
+ };
229
+
230
+ const finalContentParts = await run.processStream(inputs, config);
231
+ expect(finalContentParts).toBeDefined();
232
+
233
+ const { handleLLMEnd, collected } = createMetadataAggregator();
234
+ const titleResult = await run.generateTitle({
235
+ provider,
236
+ inputText: userMessage,
237
+ titleMethod: TitleMethod.COMPLETION, // Using completion method
238
+ contentParts,
239
+ clientOptions: llmConfig,
240
+ chainOptions: {
241
+ callbacks: [
242
+ {
243
+ handleLLMEnd,
244
+ },
245
+ ],
246
+ },
247
+ });
248
+
249
+ expect(titleResult).toBeDefined();
250
+ expect(titleResult.title).toBeDefined();
251
+ expect(titleResult.title).not.toBe('');
252
+ // Completion method doesn't return language
253
+ expect(titleResult.language).toBeUndefined();
254
+ expect(collected).toBeDefined();
255
+ console.log(`Completion method generated title: "${titleResult.title}"`);
256
+ });
257
+
258
+ test(`${capitalizeFirstLetter(provider)}: should follow-up`, async () => {
259
+ console.log('Previous conversation length:', runningHistory.length);
260
+ console.log(
261
+ 'Last message:',
262
+ runningHistory[runningHistory.length - 1].content
263
+ );
264
+ const { userName, location } = await getArgs();
265
+ const llmConfig = getLLMConfig(provider);
266
+ const customHandlers = setupCustomHandlers();
267
+
268
+ run = await Run.create<t.IState>({
269
+ runId: 'test-run-id',
270
+ graphConfig: {
271
+ type: 'standard',
272
+ llmConfig,
273
+ tools: [new Calculator()],
274
+ instructions:
275
+ 'You are a friendly AI assistant. Always address the user by their name.',
276
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
277
+ },
278
+ returnContent: true,
279
+ customHandlers,
280
+ });
281
+
282
+ conversationHistory = runningHistory.slice();
283
+ conversationHistory.push(new HumanMessage('how are you?'));
284
+
285
+ const inputs = {
286
+ messages: conversationHistory,
287
+ };
288
+
289
+ const finalContentParts = await run.processStream(inputs, config);
290
+ expect(finalContentParts).toBeDefined();
291
+ const allTextParts = finalContentParts?.every(
292
+ (part) => part.type === ContentTypes.TEXT
293
+ );
294
+ expect(allTextParts).toBe(true);
295
+ expect(collectedUsage.length).toBeGreaterThan(0);
296
+ expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
297
+ expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
298
+
299
+ const finalMessages = run.getRunMessages();
300
+ expect(finalMessages).toBeDefined();
301
+ expect(finalMessages?.length).toBeGreaterThan(0);
302
+ console.log(
303
+ `${capitalizeFirstLetter(provider)} follow-up message:`,
304
+ finalMessages?.[finalMessages.length - 1]?.content
305
+ );
306
+
307
+ expect(onMessageDeltaSpy).toHaveBeenCalled();
308
+ expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
309
+
310
+ expect(onRunStepSpy).toHaveBeenCalled();
311
+ expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
312
+ });
313
+
314
+ test('should handle errors appropriately', async () => {
315
+ // Test error scenarios
316
+ await expect(async () => {
317
+ await run.processStream(
318
+ {
319
+ messages: [],
320
+ },
321
+ {} as any
322
+ );
323
+ }).rejects.toThrow();
324
+ });
325
+ });