@elizaos/plugin-research 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +400 -0
- package/dist/index.cjs +9366 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +9284 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/__tests__/action-chaining.test.ts +532 -0
- package/src/__tests__/actions.test.ts +118 -0
- package/src/__tests__/cache-rate-limiter.test.ts +303 -0
- package/src/__tests__/content-extractors.test.ts +26 -0
- package/src/__tests__/deepresearch-bench-integration.test.ts +520 -0
- package/src/__tests__/deepresearch-bench-simplified.e2e.test.ts +290 -0
- package/src/__tests__/deepresearch-bench.e2e.test.ts +376 -0
- package/src/__tests__/e2e.test.ts +1870 -0
- package/src/__tests__/multi-benchmark-runner.ts +427 -0
- package/src/__tests__/providers.test.ts +156 -0
- package/src/__tests__/real-world.e2e.test.ts +788 -0
- package/src/__tests__/research-scenarios.test.ts +755 -0
- package/src/__tests__/research.e2e.test.ts +704 -0
- package/src/__tests__/research.test.ts +174 -0
- package/src/__tests__/search-providers.test.ts +174 -0
- package/src/__tests__/single-benchmark-runner.ts +735 -0
- package/src/__tests__/test-search-providers.ts +171 -0
- package/src/__tests__/verify-apis.test.ts +82 -0
- package/src/actions.ts +1677 -0
- package/src/benchmark/deepresearch-benchmark.ts +369 -0
- package/src/evaluation/research-evaluator.ts +444 -0
- package/src/examples/api-integration.md +498 -0
- package/src/examples/browserbase-integration.md +132 -0
- package/src/examples/debug-research-query.ts +162 -0
- package/src/examples/defi-code-scenarios.md +536 -0
- package/src/examples/defi-implementation-guide.md +454 -0
- package/src/examples/eliza-research-example.ts +142 -0
- package/src/examples/fix-renewable-energy-research.ts +209 -0
- package/src/examples/research-scenarios.md +408 -0
- package/src/examples/run-complete-renewable-research.ts +303 -0
- package/src/examples/run-deep-research.ts +352 -0
- package/src/examples/run-logged-research.ts +304 -0
- package/src/examples/run-real-research.ts +151 -0
- package/src/examples/save-research-output.ts +133 -0
- package/src/examples/test-file-logging.ts +199 -0
- package/src/examples/test-real-research.ts +67 -0
- package/src/examples/test-renewable-energy-research.ts +229 -0
- package/src/index.ts +28 -0
- package/src/integrations/cache.ts +128 -0
- package/src/integrations/content-extractors/firecrawl.ts +314 -0
- package/src/integrations/content-extractors/pdf-extractor.ts +350 -0
- package/src/integrations/content-extractors/playwright.ts +420 -0
- package/src/integrations/factory.ts +419 -0
- package/src/integrations/index.ts +18 -0
- package/src/integrations/rate-limiter.ts +181 -0
- package/src/integrations/search-providers/academic.ts +290 -0
- package/src/integrations/search-providers/exa.ts +205 -0
- package/src/integrations/search-providers/npm.ts +330 -0
- package/src/integrations/search-providers/pypi.ts +211 -0
- package/src/integrations/search-providers/serpapi.ts +277 -0
- package/src/integrations/search-providers/serper.ts +358 -0
- package/src/integrations/search-providers/stagehand-google.ts +87 -0
- package/src/integrations/search-providers/tavily.ts +187 -0
- package/src/processing/relevance-analyzer.ts +353 -0
- package/src/processing/research-logger.ts +450 -0
- package/src/processing/result-processor.ts +372 -0
- package/src/prompts/research-prompts.ts +419 -0
- package/src/providers/cacheProvider.ts +164 -0
- package/src/providers.ts +173 -0
- package/src/service.ts +2588 -0
- package/src/services/swe-bench.ts +286 -0
- package/src/strategies/research-strategies.ts +790 -0
- package/src/types/pdf-parse.d.ts +34 -0
- package/src/types.ts +551 -0
- package/src/verification/claim-verifier.ts +443 -0
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elizaos/plugin-research",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deep research plugin for ElizaOS with multi-phase internet research capabilities",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"clean": "rm -rf dist",
|
|
11
|
+
"test": "vitest run src/__tests__/ --passWithNoTests",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"lint": "echo \"No package-level lint config\"",
|
|
15
|
+
"typecheck": "tsc --noEmit --noCheck",
|
|
16
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,md}\""
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@elizaos/core": "workspace:*",
|
|
20
|
+
"@types/pdf-parse": "^1.1.1",
|
|
21
|
+
"axios": "^1.7.2",
|
|
22
|
+
"cheerio": "^1.0.0",
|
|
23
|
+
"duck-duck-scrape": "^2.2.7",
|
|
24
|
+
"lru-cache": "^10.2.0",
|
|
25
|
+
"pdf-parse": "^1.1.1",
|
|
26
|
+
"playwright": "^1.45.0",
|
|
27
|
+
"uuid": "^9.0.1",
|
|
28
|
+
"zod": "^3.23.8"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@browserbasehq/stagehand": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@browserbasehq/stagehand": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"@types/uuid": "^9.0.7",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
42
|
+
"@typescript-eslint/parser": "^6.18.1",
|
|
43
|
+
"eslint": "^8.56.0",
|
|
44
|
+
"prettier": "^3.2.2",
|
|
45
|
+
"tsup": "^8.0.1",
|
|
46
|
+
"typescript": "^5.3.3",
|
|
47
|
+
"vitest": "^1.2.0"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"src"
|
|
52
|
+
],
|
|
53
|
+
"keywords": [
|
|
54
|
+
"elizaos",
|
|
55
|
+
"ai",
|
|
56
|
+
"research",
|
|
57
|
+
"plugin",
|
|
58
|
+
"web-search",
|
|
59
|
+
"content-extraction",
|
|
60
|
+
"deep-research",
|
|
61
|
+
"defi"
|
|
62
|
+
],
|
|
63
|
+
"author": "ElizaOS Contributors",
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "https://github.com/elizaos/eliza.git"
|
|
71
|
+
},
|
|
72
|
+
"bugs": {
|
|
73
|
+
"url": "https://github.com/elizaos/eliza/issues"
|
|
74
|
+
},
|
|
75
|
+
"homepage": "https://github.com/elizaos/eliza#readme",
|
|
76
|
+
"agentConfig": {
|
|
77
|
+
"pluginType": "elizaos:plugin:1.0.0",
|
|
78
|
+
"pluginParameters": {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Memory, UUID, IAgentRuntime } from '@elizaos/core';
|
|
3
|
+
import { researchActions } from '../actions';
|
|
4
|
+
import { ResearchService } from '../service';
|
|
5
|
+
import { ResearchStatus, ResearchPhase, ResearchDomain, TaskType, ResearchDepth, ResearchReport } from '../types';
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
|
|
8
|
+
// Extract individual actions from the array
|
|
9
|
+
const researchAction = researchActions.find(a => a.name === 'start_research')!;
|
|
10
|
+
const checkStatusAction = researchActions.find(a => a.name === 'check_research_status')!;
|
|
11
|
+
const pauseResearchAction = researchActions.find(a => a.name === 'pause_research')!;
|
|
12
|
+
const resumeResearchAction = researchActions.find(a => a.name === 'resume_research')!;
|
|
13
|
+
const refineResearchQueryAction = researchActions.find(a => a.name === 'refine_research_query')!;
|
|
14
|
+
const evaluateResearchAction = researchActions.find(a => a.name === 'evaluate_research')!;
|
|
15
|
+
const exportResearchAction = researchActions.find(a => a.name === 'export_research')!;
|
|
16
|
+
const compareResearchAction = researchActions.find(a => a.name === 'compare_research')!;
|
|
17
|
+
|
|
18
|
+
// Helper to create a simple mock runtime without vi.fn()
|
|
19
|
+
function createSimpleRuntime(serviceOverrides?: Partial<ResearchService>): IAgentRuntime {
|
|
20
|
+
let researchService: ResearchService;
|
|
21
|
+
|
|
22
|
+
const runtime = {
|
|
23
|
+
agentId: uuidv4() as UUID,
|
|
24
|
+
character: {
|
|
25
|
+
name: 'TestAgent',
|
|
26
|
+
bio: ['Test bio'],
|
|
27
|
+
system: 'Test system prompt',
|
|
28
|
+
messageExamples: [],
|
|
29
|
+
postExamples: [],
|
|
30
|
+
topics: [],
|
|
31
|
+
adjectives: [],
|
|
32
|
+
knowledge: [],
|
|
33
|
+
clients: [],
|
|
34
|
+
plugins: [],
|
|
35
|
+
},
|
|
36
|
+
getSetting: (key: string) => {
|
|
37
|
+
if (key.includes('API_KEY')) return 'test-key';
|
|
38
|
+
return null;
|
|
39
|
+
},
|
|
40
|
+
getService: (name: string) => {
|
|
41
|
+
if (name === 'research') return researchService;
|
|
42
|
+
return null;
|
|
43
|
+
},
|
|
44
|
+
useModel: async (modelType: any, params: any) => {
|
|
45
|
+
// Return a more realistic response for FastPath synthesis
|
|
46
|
+
if (params?.messages) {
|
|
47
|
+
return {
|
|
48
|
+
content: 'Based on the search results, here is a concise answer to your query.'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return 'mock response';
|
|
52
|
+
},
|
|
53
|
+
composeState: async () => ({ values: {}, data: {}, text: '' }),
|
|
54
|
+
updateState: async () => true,
|
|
55
|
+
messageManager: {
|
|
56
|
+
createMemory: async () => true,
|
|
57
|
+
getMemories: async () => [],
|
|
58
|
+
updateMemory: async () => true,
|
|
59
|
+
deleteMemory: async () => true,
|
|
60
|
+
searchMemories: async () => [],
|
|
61
|
+
getLastMessages: async () => [],
|
|
62
|
+
},
|
|
63
|
+
actions: [],
|
|
64
|
+
providers: [],
|
|
65
|
+
evaluators: [],
|
|
66
|
+
logger: {
|
|
67
|
+
info: () => {},
|
|
68
|
+
warn: () => {},
|
|
69
|
+
error: () => {},
|
|
70
|
+
debug: () => {},
|
|
71
|
+
},
|
|
72
|
+
} as unknown as IAgentRuntime;
|
|
73
|
+
|
|
74
|
+
// Create the service with the runtime
|
|
75
|
+
researchService = new ResearchService(runtime);
|
|
76
|
+
|
|
77
|
+
// Override service methods if needed
|
|
78
|
+
if (serviceOverrides) {
|
|
79
|
+
Object.assign(researchService, serviceOverrides);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return runtime;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper to create test memory
|
|
86
|
+
function createTestMemory(content: any): Memory {
|
|
87
|
+
return {
|
|
88
|
+
id: uuidv4() as UUID,
|
|
89
|
+
entityId: uuidv4() as UUID,
|
|
90
|
+
roomId: uuidv4() as UUID,
|
|
91
|
+
agentId: uuidv4() as UUID,
|
|
92
|
+
content: typeof content === 'string' ? { text: content } : content,
|
|
93
|
+
createdAt: Date.now(),
|
|
94
|
+
} as Memory;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe('Action Chaining Integration Tests', () => {
|
|
98
|
+
describe('Research Creation → Status Check Chain', () => {
|
|
99
|
+
it('should create research and check its status', async () => {
|
|
100
|
+
const runtime = createSimpleRuntime();
|
|
101
|
+
const responses: any[] = [];
|
|
102
|
+
const callback = async (response: any) => {
|
|
103
|
+
responses.push(response);
|
|
104
|
+
return [];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Step 1: Create research
|
|
108
|
+
const createMessage = createTestMemory({
|
|
109
|
+
text: 'research quantum computing applications',
|
|
110
|
+
action: 'RESEARCH',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const createResult = await researchAction.handler(
|
|
114
|
+
runtime,
|
|
115
|
+
createMessage,
|
|
116
|
+
{ values: {}, data: {}, text: '' },
|
|
117
|
+
{},
|
|
118
|
+
callback
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Verify research was created
|
|
122
|
+
expect(responses.length).toBeGreaterThan(0);
|
|
123
|
+
const createResponse = responses[responses.length - 1];
|
|
124
|
+
expect(createResponse.text).toContain('research project');
|
|
125
|
+
expect(createResponse.metadata).toBeDefined();
|
|
126
|
+
expect(createResponse.metadata.projectId).toBeDefined();
|
|
127
|
+
|
|
128
|
+
const projectId = createResponse.metadata.projectId;
|
|
129
|
+
|
|
130
|
+
// Step 2: Check status
|
|
131
|
+
responses.length = 0; // Clear responses
|
|
132
|
+
const statusMessage = createTestMemory({
|
|
133
|
+
text: `check research status ${projectId}`,
|
|
134
|
+
action: 'CHECK_RESEARCH_STATUS',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await checkStatusAction.handler(
|
|
138
|
+
runtime,
|
|
139
|
+
statusMessage,
|
|
140
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
141
|
+
{},
|
|
142
|
+
callback
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Verify status was checked
|
|
146
|
+
expect(responses.length).toBeGreaterThan(0);
|
|
147
|
+
const statusResponse = responses[responses.length - 1];
|
|
148
|
+
expect(statusResponse.text).toContain('Research Status');
|
|
149
|
+
expect(statusResponse.metadata).toBeDefined();
|
|
150
|
+
expect(statusResponse.metadata.projects).toBeDefined();
|
|
151
|
+
expect(statusResponse.metadata.projects.length).toBeGreaterThan(0);
|
|
152
|
+
expect(statusResponse.metadata.projects[0].id).toBe(projectId);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('Pause → Resume Chain', () => {
|
|
157
|
+
it('should pause and resume research', async () => {
|
|
158
|
+
const runtime = createSimpleRuntime();
|
|
159
|
+
const responses: any[] = [];
|
|
160
|
+
const callback = async (response: any) => {
|
|
161
|
+
responses.push(response);
|
|
162
|
+
return [];
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// First create a research project
|
|
166
|
+
const createMessage = createTestMemory('research AI ethics');
|
|
167
|
+
await researchAction.handler(runtime, createMessage, { values: {}, data: {}, text: '' }, {}, callback);
|
|
168
|
+
|
|
169
|
+
if (responses.length === 0 || !responses[0].metadata?.projectId) {
|
|
170
|
+
// If research creation failed, skip this test
|
|
171
|
+
expect(true).toBe(true);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const projectId = responses[0].metadata.projectId;
|
|
176
|
+
|
|
177
|
+
// Wait for project to start
|
|
178
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
179
|
+
|
|
180
|
+
// Pause the research
|
|
181
|
+
responses.length = 0;
|
|
182
|
+
const pauseMessage = createTestMemory({
|
|
183
|
+
text: `pause research ${projectId}`,
|
|
184
|
+
action: 'PAUSE_RESEARCH',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await pauseResearchAction.handler(
|
|
188
|
+
runtime,
|
|
189
|
+
pauseMessage,
|
|
190
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
191
|
+
{},
|
|
192
|
+
callback
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (responses.length > 0) {
|
|
196
|
+
expect(responses[0].text.toLowerCase()).toContain('pause');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Resume the research
|
|
200
|
+
responses.length = 0;
|
|
201
|
+
const resumeMessage = createTestMemory({
|
|
202
|
+
text: `resume research ${projectId}`,
|
|
203
|
+
action: 'RESUME_RESEARCH',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await resumeResearchAction.handler(
|
|
207
|
+
runtime,
|
|
208
|
+
resumeMessage,
|
|
209
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
210
|
+
{},
|
|
211
|
+
callback
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (responses.length > 0) {
|
|
215
|
+
expect(responses[0].text.toLowerCase()).toContain('resum');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('Research → Evaluate → Export Chain', () => {
|
|
221
|
+
it('should create, evaluate, and export research', async () => {
|
|
222
|
+
const runtime = createSimpleRuntime();
|
|
223
|
+
const responses: any[] = [];
|
|
224
|
+
const callback = async (response: any) => {
|
|
225
|
+
responses.push(response);
|
|
226
|
+
return [];
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Step 1: Create research
|
|
230
|
+
const createMessage = createTestMemory('research quantum cryptography');
|
|
231
|
+
await researchAction.handler(runtime, createMessage, { values: {}, data: {}, text: '' }, {}, callback);
|
|
232
|
+
|
|
233
|
+
const projectId = responses[0].metadata.projectId;
|
|
234
|
+
|
|
235
|
+
// Wait for research to complete (in real scenario)
|
|
236
|
+
// For testing, we'll manually update the project status
|
|
237
|
+
const service = runtime.getService('research') as ResearchService;
|
|
238
|
+
const project = await service.getProject(projectId);
|
|
239
|
+
if (project) {
|
|
240
|
+
project.status = ResearchStatus.COMPLETED;
|
|
241
|
+
project.report = {
|
|
242
|
+
id: uuidv4(),
|
|
243
|
+
title: 'Quantum Cryptography Research',
|
|
244
|
+
abstract: 'Test abstract',
|
|
245
|
+
summary: 'Test summary',
|
|
246
|
+
sections: [],
|
|
247
|
+
citations: [],
|
|
248
|
+
bibliography: [],
|
|
249
|
+
generatedAt: Date.now(),
|
|
250
|
+
wordCount: 1000,
|
|
251
|
+
readingTime: 5,
|
|
252
|
+
evaluationMetrics: {
|
|
253
|
+
raceScore: {
|
|
254
|
+
overall: 0.8,
|
|
255
|
+
comprehensiveness: 0.8,
|
|
256
|
+
depth: 0.8,
|
|
257
|
+
instructionFollowing: 0.8,
|
|
258
|
+
readability: 0.8,
|
|
259
|
+
breakdown: [],
|
|
260
|
+
},
|
|
261
|
+
factScore: {
|
|
262
|
+
citationAccuracy: 0.8,
|
|
263
|
+
effectiveCitations: 5,
|
|
264
|
+
totalCitations: 5,
|
|
265
|
+
verifiedCitations: 4,
|
|
266
|
+
disputedCitations: 0,
|
|
267
|
+
citationCoverage: 0.8,
|
|
268
|
+
sourceCredibility: 0.8,
|
|
269
|
+
breakdown: [],
|
|
270
|
+
},
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
evaluatorVersion: '1.0',
|
|
273
|
+
},
|
|
274
|
+
exportFormats: [],
|
|
275
|
+
} as ResearchReport;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Step 2: Evaluate research
|
|
279
|
+
responses.length = 0;
|
|
280
|
+
const evaluateMessage = createTestMemory({
|
|
281
|
+
text: `evaluate research ${projectId}`,
|
|
282
|
+
action: 'EVALUATE_RESEARCH',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const evalResult = await evaluateResearchAction.handler(
|
|
286
|
+
runtime,
|
|
287
|
+
evaluateMessage,
|
|
288
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
289
|
+
{},
|
|
290
|
+
callback
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Check if evaluation was attempted
|
|
294
|
+
if (responses.length > 0) {
|
|
295
|
+
expect(responses[0].text).toContain('valuation');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Step 3: Export research
|
|
299
|
+
responses.length = 0;
|
|
300
|
+
const exportMessage = createTestMemory({
|
|
301
|
+
text: `export research ${projectId} as markdown`,
|
|
302
|
+
action: 'EXPORT_RESEARCH',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
await exportResearchAction.handler(
|
|
306
|
+
runtime,
|
|
307
|
+
exportMessage,
|
|
308
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
309
|
+
{},
|
|
310
|
+
callback
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (responses.length > 0) {
|
|
314
|
+
expect(responses[0].text).toContain('export');
|
|
315
|
+
expect(responses[0].metadata.format).toBe('markdown');
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('Action Validation', () => {
|
|
321
|
+
it('should validate actions based on service availability', async () => {
|
|
322
|
+
// Runtime without research service
|
|
323
|
+
const runtimeNoService = createSimpleRuntime();
|
|
324
|
+
(runtimeNoService.getService as any) = () => null;
|
|
325
|
+
|
|
326
|
+
const message = createTestMemory('start research test topic');
|
|
327
|
+
|
|
328
|
+
// All actions should fail validation without service
|
|
329
|
+
expect(await researchAction.validate(runtimeNoService, message)).toBe(false);
|
|
330
|
+
expect(await checkStatusAction.validate(runtimeNoService, message)).toBe(false);
|
|
331
|
+
expect(await pauseResearchAction.validate(runtimeNoService, message)).toBe(false);
|
|
332
|
+
|
|
333
|
+
// With service, should pass
|
|
334
|
+
const runtimeWithService = createSimpleRuntime();
|
|
335
|
+
expect(await researchAction.validate(runtimeWithService, message)).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('Compare Research Projects', () => {
|
|
340
|
+
it('should compare multiple research projects', async () => {
|
|
341
|
+
const runtime = createSimpleRuntime();
|
|
342
|
+
const responses: any[] = [];
|
|
343
|
+
const callback = async (response: any) => {
|
|
344
|
+
responses.push(response);
|
|
345
|
+
return [];
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Create two research projects
|
|
349
|
+
const message1 = createTestMemory('research AI safety');
|
|
350
|
+
await researchAction.handler(runtime, message1, { values: {}, data: {}, text: '' }, {}, callback);
|
|
351
|
+
const projectId1 = responses[0].metadata.projectId;
|
|
352
|
+
|
|
353
|
+
responses.length = 0;
|
|
354
|
+
const message2 = createTestMemory('research AI ethics');
|
|
355
|
+
await researchAction.handler(runtime, message2, { values: {}, data: {}, text: '' }, {}, callback);
|
|
356
|
+
const projectId2 = responses[0].metadata.projectId;
|
|
357
|
+
|
|
358
|
+
// Mark both as completed for comparison
|
|
359
|
+
const service = runtime.getService('research') as ResearchService;
|
|
360
|
+
const project1 = await service.getProject(projectId1);
|
|
361
|
+
const project2 = await service.getProject(projectId2);
|
|
362
|
+
|
|
363
|
+
if (project1) {
|
|
364
|
+
project1.status = ResearchStatus.COMPLETED;
|
|
365
|
+
project1.report = {
|
|
366
|
+
id: uuidv4(),
|
|
367
|
+
title: 'AI Safety Research',
|
|
368
|
+
abstract: 'Safety abstract',
|
|
369
|
+
summary: 'Safety summary',
|
|
370
|
+
sections: [],
|
|
371
|
+
citations: [],
|
|
372
|
+
bibliography: [],
|
|
373
|
+
generatedAt: Date.now(),
|
|
374
|
+
wordCount: 1500,
|
|
375
|
+
readingTime: 7,
|
|
376
|
+
evaluationMetrics: {
|
|
377
|
+
raceScore: {
|
|
378
|
+
overall: 0.8,
|
|
379
|
+
comprehensiveness: 0.8,
|
|
380
|
+
depth: 0.8,
|
|
381
|
+
instructionFollowing: 0.8,
|
|
382
|
+
readability: 0.8,
|
|
383
|
+
breakdown: [],
|
|
384
|
+
},
|
|
385
|
+
factScore: {
|
|
386
|
+
citationAccuracy: 0.8,
|
|
387
|
+
effectiveCitations: 5,
|
|
388
|
+
totalCitations: 5,
|
|
389
|
+
verifiedCitations: 4,
|
|
390
|
+
disputedCitations: 0,
|
|
391
|
+
citationCoverage: 0.8,
|
|
392
|
+
sourceCredibility: 0.8,
|
|
393
|
+
breakdown: [],
|
|
394
|
+
},
|
|
395
|
+
timestamp: Date.now(),
|
|
396
|
+
evaluatorVersion: '1.0',
|
|
397
|
+
},
|
|
398
|
+
exportFormats: [],
|
|
399
|
+
} as ResearchReport;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (project2) {
|
|
403
|
+
project2.status = ResearchStatus.COMPLETED;
|
|
404
|
+
project2.report = {
|
|
405
|
+
id: uuidv4(),
|
|
406
|
+
title: 'AI Ethics Research',
|
|
407
|
+
abstract: 'Ethics abstract',
|
|
408
|
+
summary: 'Ethics summary',
|
|
409
|
+
sections: [],
|
|
410
|
+
citations: [],
|
|
411
|
+
bibliography: [],
|
|
412
|
+
generatedAt: Date.now(),
|
|
413
|
+
wordCount: 1200,
|
|
414
|
+
readingTime: 6,
|
|
415
|
+
evaluationMetrics: {
|
|
416
|
+
raceScore: {
|
|
417
|
+
overall: 0.8,
|
|
418
|
+
comprehensiveness: 0.8,
|
|
419
|
+
depth: 0.8,
|
|
420
|
+
instructionFollowing: 0.8,
|
|
421
|
+
readability: 0.8,
|
|
422
|
+
breakdown: [],
|
|
423
|
+
},
|
|
424
|
+
factScore: {
|
|
425
|
+
citationAccuracy: 0.8,
|
|
426
|
+
effectiveCitations: 5,
|
|
427
|
+
totalCitations: 5,
|
|
428
|
+
verifiedCitations: 4,
|
|
429
|
+
disputedCitations: 0,
|
|
430
|
+
citationCoverage: 0.8,
|
|
431
|
+
sourceCredibility: 0.8,
|
|
432
|
+
breakdown: [],
|
|
433
|
+
},
|
|
434
|
+
timestamp: Date.now(),
|
|
435
|
+
evaluatorVersion: '1.0',
|
|
436
|
+
},
|
|
437
|
+
exportFormats: [],
|
|
438
|
+
} as ResearchReport;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Compare projects
|
|
442
|
+
responses.length = 0;
|
|
443
|
+
const compareMessage = createTestMemory({
|
|
444
|
+
text: `compare research projects ${projectId1} and ${projectId2}`,
|
|
445
|
+
action: 'COMPARE_RESEARCH',
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await compareResearchAction.handler(
|
|
449
|
+
runtime,
|
|
450
|
+
compareMessage,
|
|
451
|
+
{ values: {}, data: {}, text: '' },
|
|
452
|
+
{},
|
|
453
|
+
callback
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
if (responses.length > 0) {
|
|
457
|
+
expect(responses[0].text).toContain('ompar');
|
|
458
|
+
expect(responses[0].metadata.projects).toBeDefined();
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe('Refine Research Query', () => {
|
|
464
|
+
it('should refine research with additional queries', async () => {
|
|
465
|
+
const runtime = createSimpleRuntime();
|
|
466
|
+
const responses: any[] = [];
|
|
467
|
+
const callback = async (response: any) => {
|
|
468
|
+
responses.push(response);
|
|
469
|
+
return [];
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Create research first
|
|
473
|
+
const createMessage = createTestMemory('research machine learning');
|
|
474
|
+
await researchAction.handler(runtime, createMessage, { values: {}, data: {}, text: '' }, {}, callback);
|
|
475
|
+
const projectId = responses[0].metadata.projectId;
|
|
476
|
+
|
|
477
|
+
// Refine the research
|
|
478
|
+
responses.length = 0;
|
|
479
|
+
const refineMessage = createTestMemory({
|
|
480
|
+
text: 'refine with focus on neural networks',
|
|
481
|
+
action: 'REFINE_RESEARCH_QUERY',
|
|
482
|
+
metadata: { projectId },
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
await refineResearchQueryAction.handler(
|
|
486
|
+
runtime,
|
|
487
|
+
refineMessage,
|
|
488
|
+
{ values: { research_project_id: projectId }, data: {}, text: '' },
|
|
489
|
+
{},
|
|
490
|
+
callback
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
if (responses.length > 0) {
|
|
494
|
+
expect(responses[0].text).toContain('refin');
|
|
495
|
+
expect(responses[0].metadata.refinement).toBeDefined();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('Action Result Chaining', () => {
|
|
501
|
+
it('should pass data between actions via result objects', async () => {
|
|
502
|
+
const runtime = createSimpleRuntime();
|
|
503
|
+
|
|
504
|
+
// Test that start_research returns proper ActionResult
|
|
505
|
+
const message = createTestMemory('research blockchain technology');
|
|
506
|
+
const result = await researchAction.handler(
|
|
507
|
+
runtime,
|
|
508
|
+
message,
|
|
509
|
+
{ values: {}, data: {}, text: '' },
|
|
510
|
+
{},
|
|
511
|
+
async () => []
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Verify ActionResult structure
|
|
515
|
+
if (result && typeof result === 'object' && 'success' in result) {
|
|
516
|
+
expect(result.success).toBeDefined();
|
|
517
|
+
if ('data' in result) {
|
|
518
|
+
expect(result.data).toBeDefined();
|
|
519
|
+
if (result.success && result.data) {
|
|
520
|
+
expect(result.data.id).toBeDefined();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if ('nextActions' in result) {
|
|
524
|
+
expect((result as any).nextActions).toContain('check_research_status');
|
|
525
|
+
}
|
|
526
|
+
if ('metadata' in result) {
|
|
527
|
+
expect((result as any).metadata).toBeDefined();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
});
|