illuma-agents 1.0.16 → 1.0.18
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 +3 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +18 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +79 -32
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +5 -3
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +10 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +7 -8
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +15 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +11 -6
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +16 -8
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +9 -2
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +17 -10
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/stream.cjs +30 -16
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +209 -47
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +73 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +1 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +3 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/utils/contextAnalytics.cjs +66 -0
- package/dist/cjs/utils/contextAnalytics.cjs.map +1 -0
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/cjs/utils/toonFormat.cjs +388 -0
- package/dist/cjs/utils/toonFormat.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +3 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +19 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +81 -34
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +5 -3
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +10 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +7 -8
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -2
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +11 -6
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +18 -10
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +10 -3
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +19 -12
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/stream.mjs +30 -16
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +208 -48
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +73 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -0
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +3 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/utils/contextAnalytics.mjs +64 -0
- package/dist/esm/utils/contextAnalytics.mjs.map +1 -0
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/esm/utils/toonFormat.mjs +381 -0
- package/dist/esm/utils/toonFormat.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +17 -0
- package/dist/types/graphs/Graph.d.ts +8 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +19 -0
- package/dist/types/types/tools.d.ts +3 -1
- package/dist/types/utils/contextAnalytics.d.ts +37 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/toonFormat.d.ts +111 -0
- package/package.json +3 -2
- package/src/agents/AgentContext.ts +28 -20
- package/src/common/enum.ts +18 -0
- package/src/graphs/Graph.ts +152 -62
- package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +495 -473
- package/src/llm/bedrock/index.ts +47 -35
- package/src/llm/openrouter/index.ts +11 -1
- package/src/llm/vertexai/index.ts +9 -10
- package/src/messages/cache.ts +104 -55
- package/src/messages/core.ts +29 -19
- package/src/messages/format.ts +14 -3
- package/src/messages/tools.ts +20 -13
- package/src/scripts/simple.ts +1 -1
- package/src/specs/emergency-prune.test.ts +407 -355
- package/src/stream.ts +28 -20
- package/src/tools/ProgrammaticToolCalling.ts +246 -52
- package/src/tools/ToolNode.ts +78 -5
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +155 -0
- package/src/tools/search/jina-reranker.test.ts +32 -28
- package/src/tools/search/search.ts +3 -1
- package/src/tools/search/tool.ts +16 -7
- package/src/types/tools.ts +3 -1
- package/src/utils/contextAnalytics.ts +103 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/llmConfig.ts +8 -1
- package/src/utils/run.ts +5 -4
- package/src/utils/toonFormat.ts +475 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
filterToolsByUsage,
|
|
13
13
|
executeTools,
|
|
14
14
|
normalizeToPythonIdentifier,
|
|
15
|
+
unwrapToolResponse,
|
|
15
16
|
} from '../ProgrammaticToolCalling';
|
|
16
17
|
import {
|
|
17
18
|
createProgrammaticToolRegistry,
|
|
@@ -228,6 +229,160 @@ describe('ProgrammaticToolCalling', () => {
|
|
|
228
229
|
});
|
|
229
230
|
});
|
|
230
231
|
|
|
232
|
+
describe('unwrapToolResponse', () => {
|
|
233
|
+
describe('non-MCP tools', () => {
|
|
234
|
+
it('returns result as-is for non-MCP tools', () => {
|
|
235
|
+
const result = { temperature: 65, condition: 'Foggy' };
|
|
236
|
+
expect(unwrapToolResponse(result, false)).toEqual(result);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('returns string as-is for non-MCP tools', () => {
|
|
240
|
+
expect(unwrapToolResponse('plain string', false)).toBe('plain string');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('returns array as-is for non-MCP tools', () => {
|
|
244
|
+
const result = [1, 2, 3];
|
|
245
|
+
expect(unwrapToolResponse(result, false)).toEqual(result);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('MCP tools - tuple format [content, artifacts]', () => {
|
|
250
|
+
it('extracts string content from tuple', () => {
|
|
251
|
+
const result = ['Hello world', { artifacts: [] }];
|
|
252
|
+
expect(unwrapToolResponse(result, true)).toBe('Hello world');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('parses JSON string content from tuple', () => {
|
|
256
|
+
const result = ['{"temperature": 65}', { artifacts: [] }];
|
|
257
|
+
expect(unwrapToolResponse(result, true)).toEqual({ temperature: 65 });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('parses JSON array string content from tuple', () => {
|
|
261
|
+
const result = ['[1, 2, 3]', { artifacts: [] }];
|
|
262
|
+
expect(unwrapToolResponse(result, true)).toEqual([1, 2, 3]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('extracts text from single content block in tuple', () => {
|
|
266
|
+
const result = [{ type: 'text', text: 'Spreadsheet info here' }, {}];
|
|
267
|
+
expect(unwrapToolResponse(result, true)).toBe('Spreadsheet info here');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('extracts and parses JSON from single content block in tuple', () => {
|
|
271
|
+
const result = [
|
|
272
|
+
{ type: 'text', text: '{"id": "123", "name": "Test"}' },
|
|
273
|
+
{},
|
|
274
|
+
];
|
|
275
|
+
expect(unwrapToolResponse(result, true)).toEqual({
|
|
276
|
+
id: '123',
|
|
277
|
+
name: 'Test',
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('extracts text from array of content blocks in tuple', () => {
|
|
282
|
+
const result = [
|
|
283
|
+
[
|
|
284
|
+
{ type: 'text', text: 'Line 1' },
|
|
285
|
+
{ type: 'text', text: 'Line 2' },
|
|
286
|
+
],
|
|
287
|
+
{},
|
|
288
|
+
];
|
|
289
|
+
expect(unwrapToolResponse(result, true)).toBe('Line 1\nLine 2');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('returns object content as-is when not a text block', () => {
|
|
293
|
+
const result = [{ temperature: 65, condition: 'Foggy' }, {}];
|
|
294
|
+
expect(unwrapToolResponse(result, true)).toEqual({
|
|
295
|
+
temperature: 65,
|
|
296
|
+
condition: 'Foggy',
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('MCP tools - single content block (not in tuple)', () => {
|
|
302
|
+
it('extracts text from single content block object', () => {
|
|
303
|
+
const result = { type: 'text', text: 'No data found in range' };
|
|
304
|
+
expect(unwrapToolResponse(result, true)).toBe('No data found in range');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('extracts and parses JSON from single content block object', () => {
|
|
308
|
+
const result = {
|
|
309
|
+
type: 'text',
|
|
310
|
+
text: '{"sheets": [{"name": "raw_data"}]}',
|
|
311
|
+
};
|
|
312
|
+
expect(unwrapToolResponse(result, true)).toEqual({
|
|
313
|
+
sheets: [{ name: 'raw_data' }],
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('handles real-world MCP spreadsheet response', () => {
|
|
318
|
+
const result = {
|
|
319
|
+
type: 'text',
|
|
320
|
+
text: 'Spreadsheet: "NYC Taxi - Top Pickup Neighborhoods" (ID: abc123)\nSheets (2):\n - "raw_data" (ID: 123) | Size: 1000x26',
|
|
321
|
+
};
|
|
322
|
+
expect(unwrapToolResponse(result, true)).toBe(
|
|
323
|
+
'Spreadsheet: "NYC Taxi - Top Pickup Neighborhoods" (ID: abc123)\nSheets (2):\n - "raw_data" (ID: 123) | Size: 1000x26'
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('handles real-world MCP no data response', () => {
|
|
328
|
+
const result = {
|
|
329
|
+
type: 'text',
|
|
330
|
+
text: 'No data found in range \'raw_data!A1:D25\' for user@example.com.',
|
|
331
|
+
};
|
|
332
|
+
expect(unwrapToolResponse(result, true)).toBe(
|
|
333
|
+
'No data found in range \'raw_data!A1:D25\' for user@example.com.'
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('MCP tools - array of content blocks (not in tuple)', () => {
|
|
339
|
+
it('extracts text from array of content blocks', () => {
|
|
340
|
+
const result = [
|
|
341
|
+
{ type: 'text', text: 'First block' },
|
|
342
|
+
{ type: 'text', text: 'Second block' },
|
|
343
|
+
];
|
|
344
|
+
expect(unwrapToolResponse(result, true)).toBe(
|
|
345
|
+
'First block\nSecond block'
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('filters out non-text blocks', () => {
|
|
350
|
+
const result = [
|
|
351
|
+
{ type: 'text', text: 'Text content' },
|
|
352
|
+
{ type: 'image', data: 'base64...' },
|
|
353
|
+
{ type: 'text', text: 'More text' },
|
|
354
|
+
];
|
|
355
|
+
expect(unwrapToolResponse(result, true)).toBe(
|
|
356
|
+
'Text content\nMore text'
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('edge cases', () => {
|
|
362
|
+
it('returns non-text block object as-is', () => {
|
|
363
|
+
const result = { type: 'image', data: 'base64...' };
|
|
364
|
+
expect(unwrapToolResponse(result, true)).toEqual(result);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('handles empty array', () => {
|
|
368
|
+
expect(unwrapToolResponse([], true)).toEqual([]);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('handles malformed JSON in text block gracefully', () => {
|
|
372
|
+
const result = { type: 'text', text: '{ invalid json }' };
|
|
373
|
+
expect(unwrapToolResponse(result, true)).toBe('{ invalid json }');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('handles null', () => {
|
|
377
|
+
expect(unwrapToolResponse(null, true)).toBe(null);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('handles undefined', () => {
|
|
381
|
+
expect(unwrapToolResponse(undefined, true)).toBe(undefined);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
231
386
|
describe('extractUsedToolNames', () => {
|
|
232
387
|
const createToolMap = (names: string[]): Map<string, string> => {
|
|
233
388
|
const map = new Map<string, string>();
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import { JinaReranker } from './rerankers';
|
|
1
|
+
import { JinaReranker, createReranker } from './rerankers';
|
|
2
2
|
import { createDefaultLogger } from './utils';
|
|
3
3
|
|
|
4
|
+
// Helper to access private apiUrl property for testing
|
|
5
|
+
const getApiUrl = (reranker: JinaReranker): string =>
|
|
6
|
+
(reranker as unknown as { apiUrl: string }).apiUrl;
|
|
7
|
+
|
|
4
8
|
describe('JinaReranker', () => {
|
|
5
9
|
const mockLogger = createDefaultLogger();
|
|
6
|
-
|
|
10
|
+
|
|
7
11
|
describe('constructor', () => {
|
|
8
12
|
it('should use default API URL when no apiUrl is provided', () => {
|
|
9
13
|
const reranker = new JinaReranker({
|
|
10
14
|
apiKey: 'test-key',
|
|
11
15
|
logger: mockLogger,
|
|
12
16
|
});
|
|
13
|
-
|
|
17
|
+
|
|
14
18
|
// Access private property for testing
|
|
15
|
-
const apiUrl = (reranker
|
|
19
|
+
const apiUrl = getApiUrl(reranker);
|
|
16
20
|
expect(apiUrl).toBe('https://api.jina.ai/v1/rerank');
|
|
17
21
|
});
|
|
18
22
|
|
|
@@ -23,25 +27,25 @@ describe('JinaReranker', () => {
|
|
|
23
27
|
apiUrl: customUrl,
|
|
24
28
|
logger: mockLogger,
|
|
25
29
|
});
|
|
26
|
-
|
|
27
|
-
const apiUrl = (reranker
|
|
30
|
+
|
|
31
|
+
const apiUrl = getApiUrl(reranker);
|
|
28
32
|
expect(apiUrl).toBe(customUrl);
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
it('should use environment variable JINA_API_URL when available', () => {
|
|
32
36
|
const originalEnv = process.env.JINA_API_URL;
|
|
33
37
|
process.env.JINA_API_URL = 'https://env-jina-endpoint.com/v1/rerank';
|
|
34
|
-
|
|
38
|
+
|
|
35
39
|
const reranker = new JinaReranker({
|
|
36
40
|
apiKey: 'test-key',
|
|
37
41
|
logger: mockLogger,
|
|
38
42
|
});
|
|
39
|
-
|
|
40
|
-
const apiUrl = (reranker
|
|
43
|
+
|
|
44
|
+
const apiUrl = getApiUrl(reranker);
|
|
41
45
|
expect(apiUrl).toBe('https://env-jina-endpoint.com/v1/rerank');
|
|
42
|
-
|
|
46
|
+
|
|
43
47
|
// Restore original environment
|
|
44
|
-
if (originalEnv) {
|
|
48
|
+
if (originalEnv != null && originalEnv !== '') {
|
|
45
49
|
process.env.JINA_API_URL = originalEnv;
|
|
46
50
|
} else {
|
|
47
51
|
delete process.env.JINA_API_URL;
|
|
@@ -51,19 +55,19 @@ describe('JinaReranker', () => {
|
|
|
51
55
|
it('should prioritize explicit apiUrl over environment variable', () => {
|
|
52
56
|
const originalEnv = process.env.JINA_API_URL;
|
|
53
57
|
process.env.JINA_API_URL = 'https://env-jina-endpoint.com/v1/rerank';
|
|
54
|
-
|
|
58
|
+
|
|
55
59
|
const customUrl = 'https://explicit-jina-endpoint.com/v1/rerank';
|
|
56
60
|
const reranker = new JinaReranker({
|
|
57
61
|
apiKey: 'test-key',
|
|
58
62
|
apiUrl: customUrl,
|
|
59
63
|
logger: mockLogger,
|
|
60
64
|
});
|
|
61
|
-
|
|
62
|
-
const apiUrl = (reranker
|
|
65
|
+
|
|
66
|
+
const apiUrl = getApiUrl(reranker);
|
|
63
67
|
expect(apiUrl).toBe(customUrl);
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
// Restore original environment
|
|
66
|
-
if (originalEnv) {
|
|
70
|
+
if (originalEnv != null && originalEnv !== '') {
|
|
67
71
|
process.env.JINA_API_URL = originalEnv;
|
|
68
72
|
} else {
|
|
69
73
|
delete process.env.JINA_API_URL;
|
|
@@ -79,27 +83,27 @@ describe('JinaReranker', () => {
|
|
|
79
83
|
apiUrl: customUrl,
|
|
80
84
|
logger: mockLogger,
|
|
81
85
|
});
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
const logSpy = jest.spyOn(mockLogger, 'debug');
|
|
84
|
-
|
|
88
|
+
|
|
85
89
|
try {
|
|
86
90
|
await reranker.rerank('test query', ['document1', 'document2'], 2);
|
|
87
|
-
} catch (
|
|
91
|
+
} catch (_error) {
|
|
88
92
|
// Expected to fail due to missing API key, but we can check the log
|
|
89
93
|
}
|
|
90
|
-
|
|
94
|
+
|
|
91
95
|
expect(logSpy).toHaveBeenCalledWith(
|
|
92
|
-
expect.stringContaining(
|
|
96
|
+
expect.stringContaining(
|
|
97
|
+
`Reranking 2 chunks with Jina using API URL: ${customUrl}`
|
|
98
|
+
)
|
|
93
99
|
);
|
|
94
|
-
|
|
100
|
+
|
|
95
101
|
logSpy.mockRestore();
|
|
96
102
|
});
|
|
97
103
|
});
|
|
98
104
|
});
|
|
99
105
|
|
|
100
106
|
describe('createReranker', () => {
|
|
101
|
-
const { createReranker } = require('./rerankers');
|
|
102
|
-
|
|
103
107
|
it('should create JinaReranker with jinaApiUrl when provided', () => {
|
|
104
108
|
const customUrl = 'https://custom-jina-endpoint.com/v1/rerank';
|
|
105
109
|
const reranker = createReranker({
|
|
@@ -107,9 +111,9 @@ describe('createReranker', () => {
|
|
|
107
111
|
jinaApiKey: 'test-key',
|
|
108
112
|
jinaApiUrl: customUrl,
|
|
109
113
|
});
|
|
110
|
-
|
|
114
|
+
|
|
111
115
|
expect(reranker).toBeInstanceOf(JinaReranker);
|
|
112
|
-
const apiUrl = (reranker as
|
|
116
|
+
const apiUrl = getApiUrl(reranker as JinaReranker);
|
|
113
117
|
expect(apiUrl).toBe(customUrl);
|
|
114
118
|
});
|
|
115
119
|
|
|
@@ -118,9 +122,9 @@ describe('createReranker', () => {
|
|
|
118
122
|
rerankerType: 'jina',
|
|
119
123
|
jinaApiKey: 'test-key',
|
|
120
124
|
});
|
|
121
|
-
|
|
125
|
+
|
|
122
126
|
expect(reranker).toBeInstanceOf(JinaReranker);
|
|
123
|
-
const apiUrl = (reranker as
|
|
127
|
+
const apiUrl = getApiUrl(reranker as JinaReranker);
|
|
124
128
|
expect(apiUrl).toBe('https://api.jina.ai/v1/rerank');
|
|
125
129
|
});
|
|
126
130
|
});
|
|
@@ -603,7 +603,9 @@ export const createSourceProcessor = (
|
|
|
603
603
|
|
|
604
604
|
// If content was already extracted directly (e.g., direct URL extraction), skip scraping
|
|
605
605
|
if (skipScraping) {
|
|
606
|
-
logger_.debug(
|
|
606
|
+
logger_.debug(
|
|
607
|
+
'Skipping additional scraping - content already extracted'
|
|
608
|
+
);
|
|
607
609
|
return result.data;
|
|
608
610
|
}
|
|
609
611
|
|
package/src/tools/search/tool.ts
CHANGED
|
@@ -98,15 +98,21 @@ async function extractDirectUrlContent({
|
|
|
98
98
|
|
|
99
99
|
results.push({
|
|
100
100
|
position: results.length + 1,
|
|
101
|
-
title:
|
|
101
|
+
title:
|
|
102
|
+
getString(metadata.title) ?? getString(metadata.ogTitle) ?? url,
|
|
102
103
|
link: url,
|
|
103
|
-
snippet:
|
|
104
|
+
snippet:
|
|
105
|
+
getString(metadata.description) ??
|
|
106
|
+
getString(metadata.ogDescription) ??
|
|
107
|
+
'',
|
|
104
108
|
content: content,
|
|
105
109
|
references: references,
|
|
106
110
|
processed: true,
|
|
107
111
|
});
|
|
108
112
|
} else {
|
|
109
|
-
logger.warn(
|
|
113
|
+
logger.warn(
|
|
114
|
+
`Failed to extract content from ${url}: ${response.error}`
|
|
115
|
+
);
|
|
110
116
|
// Still add the URL as a result, but without content
|
|
111
117
|
results.push({
|
|
112
118
|
position: results.length + 1,
|
|
@@ -424,15 +430,17 @@ function createTool({
|
|
|
424
430
|
async (params, runnableConfig) => {
|
|
425
431
|
const { query, date, country: _c, images, videos, news } = params;
|
|
426
432
|
const country = typeof _c === 'string' && _c ? _c : undefined;
|
|
427
|
-
|
|
433
|
+
|
|
428
434
|
// Log the incoming query for debugging URL detection
|
|
429
435
|
const toolLogger = createDefaultLogger();
|
|
430
436
|
toolLogger.debug(`[web_search] Received query: "${query}"`);
|
|
431
437
|
const detectedUrls = extractUrlsFromQuery(query);
|
|
432
438
|
if (detectedUrls.length > 0) {
|
|
433
|
-
toolLogger.debug(
|
|
439
|
+
toolLogger.debug(
|
|
440
|
+
`[web_search] Detected URLs in query: ${detectedUrls.join(', ')}`
|
|
441
|
+
);
|
|
434
442
|
}
|
|
435
|
-
|
|
443
|
+
|
|
436
444
|
const searchResult = await search({
|
|
437
445
|
query,
|
|
438
446
|
date,
|
|
@@ -452,7 +460,8 @@ function createTool({
|
|
|
452
460
|
},
|
|
453
461
|
{
|
|
454
462
|
name: Constants.WEB_SEARCH,
|
|
455
|
-
description:
|
|
463
|
+
description:
|
|
464
|
+
`Real-time web search and direct URL content extraction. Results have required citation anchors.
|
|
456
465
|
|
|
457
466
|
**CAPABILITIES:**
|
|
458
467
|
- Search: Query the web for information on any topic
|
package/src/types/tools.ts
CHANGED
|
@@ -15,7 +15,9 @@ export type CustomToolCall = {
|
|
|
15
15
|
output?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type GenericTool = StructuredToolInterface | RunnableToolLike
|
|
18
|
+
export type GenericTool = (StructuredToolInterface | RunnableToolLike) & {
|
|
19
|
+
mcp?: boolean;
|
|
20
|
+
};
|
|
19
21
|
|
|
20
22
|
export type ToolMap = Map<string, GenericTool>;
|
|
21
23
|
export type ToolRefs = {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Analytics Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides context analytics data for observability/traces.
|
|
5
|
+
* No console logging - just data structures for event emission.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
9
|
+
import type { TokenCounter } from '@/types/run';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context analytics data for traces
|
|
13
|
+
*/
|
|
14
|
+
export interface ContextAnalytics {
|
|
15
|
+
/** Total messages in context */
|
|
16
|
+
messageCount: number;
|
|
17
|
+
/** Total tokens in context */
|
|
18
|
+
totalTokens: number;
|
|
19
|
+
/** Maximum allowed context tokens */
|
|
20
|
+
maxContextTokens?: number;
|
|
21
|
+
/** Instruction/system tokens */
|
|
22
|
+
instructionTokens?: number;
|
|
23
|
+
/** Context utilization percentage (0-100) */
|
|
24
|
+
utilizationPercent?: number;
|
|
25
|
+
/** Breakdown by message type */
|
|
26
|
+
breakdown?: Record<string, { tokens: number; percent: number }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build context analytics for traces (no logging)
|
|
31
|
+
*/
|
|
32
|
+
export function buildContextAnalytics(
|
|
33
|
+
messages: BaseMessage[],
|
|
34
|
+
options: {
|
|
35
|
+
tokenCounter?: TokenCounter;
|
|
36
|
+
maxContextTokens?: number;
|
|
37
|
+
instructionTokens?: number;
|
|
38
|
+
indexTokenCountMap?: Record<string, number | undefined>;
|
|
39
|
+
}
|
|
40
|
+
): ContextAnalytics {
|
|
41
|
+
const {
|
|
42
|
+
tokenCounter,
|
|
43
|
+
maxContextTokens,
|
|
44
|
+
instructionTokens,
|
|
45
|
+
indexTokenCountMap,
|
|
46
|
+
} = options;
|
|
47
|
+
|
|
48
|
+
// Calculate total tokens
|
|
49
|
+
let totalTokens = 0;
|
|
50
|
+
const breakdown: Record<string, { tokens: number; percent: number }> = {};
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < messages.length; i++) {
|
|
53
|
+
const msg = messages[i];
|
|
54
|
+
const type = msg.getType();
|
|
55
|
+
|
|
56
|
+
let tokens = 0;
|
|
57
|
+
if (indexTokenCountMap && indexTokenCountMap[i] != null) {
|
|
58
|
+
tokens = indexTokenCountMap[i]!;
|
|
59
|
+
} else if (tokenCounter) {
|
|
60
|
+
try {
|
|
61
|
+
tokens = tokenCounter(msg);
|
|
62
|
+
} catch {
|
|
63
|
+
// Estimate from content length
|
|
64
|
+
const content =
|
|
65
|
+
typeof msg.content === 'string'
|
|
66
|
+
? msg.content
|
|
67
|
+
: JSON.stringify(msg.content);
|
|
68
|
+
tokens = Math.ceil(content.length / 4);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
totalTokens += tokens;
|
|
73
|
+
|
|
74
|
+
if (!breakdown[type]) {
|
|
75
|
+
breakdown[type] = { tokens: 0, percent: 0 };
|
|
76
|
+
}
|
|
77
|
+
breakdown[type].tokens += tokens;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Calculate percentages
|
|
81
|
+
for (const type of Object.keys(breakdown)) {
|
|
82
|
+
breakdown[type].percent =
|
|
83
|
+
totalTokens > 0
|
|
84
|
+
? Math.round((breakdown[type].tokens / totalTokens) * 1000) / 10
|
|
85
|
+
: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Calculate utilization
|
|
89
|
+
let utilizationPercent: number | undefined;
|
|
90
|
+
if (maxContextTokens && maxContextTokens > 0) {
|
|
91
|
+
utilizationPercent =
|
|
92
|
+
Math.round((totalTokens / maxContextTokens) * 1000) / 10;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
messageCount: messages.length,
|
|
97
|
+
totalTokens,
|
|
98
|
+
maxContextTokens,
|
|
99
|
+
instructionTokens,
|
|
100
|
+
utilizationPercent,
|
|
101
|
+
breakdown,
|
|
102
|
+
};
|
|
103
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/llmConfig.ts
CHANGED
|
@@ -56,7 +56,9 @@ export const llmConfigs: Record<string, t.LLMConfig | undefined> = {
|
|
|
56
56
|
provider: Providers.OPENROUTER,
|
|
57
57
|
streaming: true,
|
|
58
58
|
streamUsage: true,
|
|
59
|
-
model: 'anthropic/claude-sonnet-4',
|
|
59
|
+
// model: 'anthropic/claude-sonnet-4',
|
|
60
|
+
// model: 'moonshotai/kimi-k2-thinking',
|
|
61
|
+
model: 'google/gemini-3-pro-preview',
|
|
60
62
|
apiKey: process.env.OPENROUTER_API_KEY,
|
|
61
63
|
configuration: {
|
|
62
64
|
baseURL: process.env.OPENROUTER_BASE_URL,
|
|
@@ -145,9 +147,14 @@ export const llmConfigs: Record<string, t.LLMConfig | undefined> = {
|
|
|
145
147
|
[Providers.VERTEXAI]: {
|
|
146
148
|
provider: Providers.VERTEXAI,
|
|
147
149
|
model: 'gemini-2.5-flash',
|
|
150
|
+
// model: 'gemini-2.5-pro',
|
|
148
151
|
streaming: true,
|
|
149
152
|
streamUsage: true,
|
|
150
153
|
keyFile: process.env.VERTEXAI_KEY_FILE,
|
|
154
|
+
// maxRetries: 2,
|
|
155
|
+
// location: 'global',
|
|
156
|
+
// thinkingBudget: -1,
|
|
157
|
+
// includeThoughts: true,
|
|
151
158
|
} as t.VertexAIClientOptions & t.LLMConfig,
|
|
152
159
|
[Providers.GOOGLE]: {
|
|
153
160
|
provider: Providers.GOOGLE,
|
package/src/utils/run.ts
CHANGED
|
@@ -57,9 +57,10 @@ export class RunnableCallable<I = unknown, O = unknown> extends Runnable<I, O> {
|
|
|
57
57
|
): Promise<O> {
|
|
58
58
|
return new Promise<O>((resolve, reject) => {
|
|
59
59
|
// Defensive check: ensure runManager has getChild method before calling
|
|
60
|
-
const childCallbacks =
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const childCallbacks =
|
|
61
|
+
typeof runManager?.getChild === 'function'
|
|
62
|
+
? runManager.getChild()
|
|
63
|
+
: undefined;
|
|
63
64
|
const childConfig = patchConfig(config, {
|
|
64
65
|
callbacks: childCallbacks,
|
|
65
66
|
});
|
|
@@ -102,4 +103,4 @@ export class RunnableCallable<I = unknown, O = unknown> extends Runnable<I, O> {
|
|
|
102
103
|
|
|
103
104
|
return returnValue;
|
|
104
105
|
}
|
|
105
|
-
}
|
|
106
|
+
}
|