@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.
Files changed (71) hide show
  1. package/README.md +400 -0
  2. package/dist/index.cjs +9366 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +9284 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +80 -0
  7. package/src/__tests__/action-chaining.test.ts +532 -0
  8. package/src/__tests__/actions.test.ts +118 -0
  9. package/src/__tests__/cache-rate-limiter.test.ts +303 -0
  10. package/src/__tests__/content-extractors.test.ts +26 -0
  11. package/src/__tests__/deepresearch-bench-integration.test.ts +520 -0
  12. package/src/__tests__/deepresearch-bench-simplified.e2e.test.ts +290 -0
  13. package/src/__tests__/deepresearch-bench.e2e.test.ts +376 -0
  14. package/src/__tests__/e2e.test.ts +1870 -0
  15. package/src/__tests__/multi-benchmark-runner.ts +427 -0
  16. package/src/__tests__/providers.test.ts +156 -0
  17. package/src/__tests__/real-world.e2e.test.ts +788 -0
  18. package/src/__tests__/research-scenarios.test.ts +755 -0
  19. package/src/__tests__/research.e2e.test.ts +704 -0
  20. package/src/__tests__/research.test.ts +174 -0
  21. package/src/__tests__/search-providers.test.ts +174 -0
  22. package/src/__tests__/single-benchmark-runner.ts +735 -0
  23. package/src/__tests__/test-search-providers.ts +171 -0
  24. package/src/__tests__/verify-apis.test.ts +82 -0
  25. package/src/actions.ts +1677 -0
  26. package/src/benchmark/deepresearch-benchmark.ts +369 -0
  27. package/src/evaluation/research-evaluator.ts +444 -0
  28. package/src/examples/api-integration.md +498 -0
  29. package/src/examples/browserbase-integration.md +132 -0
  30. package/src/examples/debug-research-query.ts +162 -0
  31. package/src/examples/defi-code-scenarios.md +536 -0
  32. package/src/examples/defi-implementation-guide.md +454 -0
  33. package/src/examples/eliza-research-example.ts +142 -0
  34. package/src/examples/fix-renewable-energy-research.ts +209 -0
  35. package/src/examples/research-scenarios.md +408 -0
  36. package/src/examples/run-complete-renewable-research.ts +303 -0
  37. package/src/examples/run-deep-research.ts +352 -0
  38. package/src/examples/run-logged-research.ts +304 -0
  39. package/src/examples/run-real-research.ts +151 -0
  40. package/src/examples/save-research-output.ts +133 -0
  41. package/src/examples/test-file-logging.ts +199 -0
  42. package/src/examples/test-real-research.ts +67 -0
  43. package/src/examples/test-renewable-energy-research.ts +229 -0
  44. package/src/index.ts +28 -0
  45. package/src/integrations/cache.ts +128 -0
  46. package/src/integrations/content-extractors/firecrawl.ts +314 -0
  47. package/src/integrations/content-extractors/pdf-extractor.ts +350 -0
  48. package/src/integrations/content-extractors/playwright.ts +420 -0
  49. package/src/integrations/factory.ts +419 -0
  50. package/src/integrations/index.ts +18 -0
  51. package/src/integrations/rate-limiter.ts +181 -0
  52. package/src/integrations/search-providers/academic.ts +290 -0
  53. package/src/integrations/search-providers/exa.ts +205 -0
  54. package/src/integrations/search-providers/npm.ts +330 -0
  55. package/src/integrations/search-providers/pypi.ts +211 -0
  56. package/src/integrations/search-providers/serpapi.ts +277 -0
  57. package/src/integrations/search-providers/serper.ts +358 -0
  58. package/src/integrations/search-providers/stagehand-google.ts +87 -0
  59. package/src/integrations/search-providers/tavily.ts +187 -0
  60. package/src/processing/relevance-analyzer.ts +353 -0
  61. package/src/processing/research-logger.ts +450 -0
  62. package/src/processing/result-processor.ts +372 -0
  63. package/src/prompts/research-prompts.ts +419 -0
  64. package/src/providers/cacheProvider.ts +164 -0
  65. package/src/providers.ts +173 -0
  66. package/src/service.ts +2588 -0
  67. package/src/services/swe-bench.ts +286 -0
  68. package/src/strategies/research-strategies.ts +790 -0
  69. package/src/types/pdf-parse.d.ts +34 -0
  70. package/src/types.ts +551 -0
  71. package/src/verification/claim-verifier.ts +443 -0
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Test file logging functionality with a simple research example
4
+ */
5
+
6
+ import { ResearchService } from '../service';
7
+ import { IAgentRuntime } from '@elizaos/core';
8
+ import { v4 as uuidv4 } from 'uuid';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+
12
+ // Enable file logging
13
+ process.env.FILE_LOGGING = 'true';
14
+
15
+ // Create a simple runtime
16
+ const runtime = {
17
+ getSetting: (key: string) => {
18
+ if (key === 'FILE_LOGGING') return 'true';
19
+ if (key === 'TAVILY_API_KEY') return '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';
20
+ return null;
21
+ },
22
+ useModel: async () => 'Mock response',
23
+ logger: {
24
+ info: console.log,
25
+ warn: console.warn,
26
+ error: console.error,
27
+ debug: () => {},
28
+ },
29
+ getService: () => null,
30
+ } as unknown as IAgentRuntime;
31
+
32
+ async function testFileLogging() {
33
+ console.log('🧪 Testing File Logging Functionality\n');
34
+
35
+ // Create research service
36
+ const service = new ResearchService(runtime);
37
+
38
+ // Create sources first
39
+ const source1 = {
40
+ id: uuidv4(),
41
+ url: 'https://example.com/ai-research',
42
+ title: 'Latest AI Research 2024',
43
+ content: 'This is a test source about AI advancements...',
44
+ accessedAt: Date.now(),
45
+ type: 'web' as const,
46
+ reliability: 0.9,
47
+ metadata: {
48
+ author: ['Test Author'],
49
+ publishDate: '2024-01-15',
50
+ domain: 'example.com',
51
+ language: 'en',
52
+ },
53
+ };
54
+
55
+ const source2 = {
56
+ id: uuidv4(),
57
+ url: 'https://example.org/machine-learning',
58
+ title: 'Machine Learning Breakthroughs',
59
+ content: 'Recent breakthroughs in machine learning include...',
60
+ accessedAt: Date.now(),
61
+ type: 'web' as const,
62
+ reliability: 0.85,
63
+ metadata: {
64
+ author: ['ML Expert'],
65
+ publishDate: '2024-02-20',
66
+ domain: 'example.org',
67
+ language: 'en',
68
+ },
69
+ };
70
+
71
+ // Create a mock research project
72
+ const projectId = uuidv4();
73
+ const mockProject = {
74
+ id: projectId,
75
+ query: 'Test research about AI advancements',
76
+ status: 'completed' as const,
77
+ phase: 'reporting' as const,
78
+ createdAt: Date.now(),
79
+ completedAt: Date.now() + 5000,
80
+ sources: [source1, source2],
81
+ findings: [
82
+ {
83
+ id: uuidv4(),
84
+ content: 'Large language models have shown significant improvements in reasoning capabilities.',
85
+ source: source1,
86
+ relevance: 0.95,
87
+ confidence: 0.9,
88
+ category: 'llm',
89
+ timestamp: Date.now(),
90
+ citations: [],
91
+ factualClaims: [
92
+ {
93
+ id: uuidv4(),
94
+ statement: 'LLMs show improved reasoning',
95
+ supportingEvidence: ['Research shows LLMs can solve complex problems'],
96
+ sourceUrls: ['https://example.com/ai-research'],
97
+ verificationStatus: 'verified' as const,
98
+ confidenceScore: 0.9,
99
+ relatedClaims: [],
100
+ },
101
+ ],
102
+ relatedFindings: [],
103
+ verificationStatus: 'verified' as const,
104
+ extractionMethod: 'manual',
105
+ },
106
+ {
107
+ id: uuidv4(),
108
+ content: 'Multimodal AI systems can now process text, images, and audio simultaneously.',
109
+ source: source2,
110
+ relevance: 0.9,
111
+ confidence: 0.85,
112
+ category: 'multimodal',
113
+ timestamp: Date.now(),
114
+ citations: [],
115
+ factualClaims: [
116
+ {
117
+ id: uuidv4(),
118
+ statement: 'Multimodal AI processes multiple data types',
119
+ supportingEvidence: ['Systems can handle text, images, and audio'],
120
+ sourceUrls: ['https://example.org/machine-learning'],
121
+ verificationStatus: 'verified' as const,
122
+ confidenceScore: 0.85,
123
+ relatedClaims: [],
124
+ },
125
+ ],
126
+ relatedFindings: [],
127
+ verificationStatus: 'verified' as const,
128
+ extractionMethod: 'manual',
129
+ },
130
+ ],
131
+ metadata: {
132
+ domain: 'computer_science',
133
+ taskType: 'analytical',
134
+ depth: 'comprehensive',
135
+ synthesis: 'The year 2024 has witnessed remarkable advances in artificial intelligence, particularly in large language models and multimodal systems. LLMs have demonstrated enhanced reasoning capabilities, while multimodal systems have achieved unprecedented integration across different data types.',
136
+ categoryAnalysis: {
137
+ llm: 'Large language models have evolved to show emergent reasoning abilities, with improvements in mathematical problem-solving and logical deduction.',
138
+ multimodal: 'Multimodal AI represents a significant leap forward, enabling systems to understand and generate content across multiple modalities seamlessly.',
139
+ },
140
+ },
141
+ report: null,
142
+ };
143
+
144
+ // Store the project
145
+ (service as any).projects.set(projectId, mockProject);
146
+
147
+ // Generate report (this will trigger file saving)
148
+ console.log('📝 Generating report...');
149
+ await (service as any).generateReport(mockProject);
150
+
151
+ console.log('✅ Report generation complete\n');
152
+
153
+ // Check if files were created
154
+ const logsDir = path.join(process.cwd(), 'research_logs');
155
+
156
+ try {
157
+ const files = await fs.readdir(logsDir);
158
+ console.log('📁 Files created in research_logs/:');
159
+
160
+ const mdFiles = files.filter(f => f.endsWith('.md'));
161
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
162
+
163
+ if (mdFiles.length > 0) {
164
+ console.log('\nMarkdown files:');
165
+ mdFiles.forEach(file => console.log(` 📄 ${file}`));
166
+ }
167
+
168
+ if (jsonFiles.length > 0) {
169
+ console.log('\nJSON files:');
170
+ jsonFiles.forEach(file => console.log(` 📊 ${file}`));
171
+ }
172
+
173
+ // Read and display the first markdown file
174
+ if (mdFiles.length > 0) {
175
+ console.log('\n📖 Preview of the first report:\n');
176
+ console.log('─'.repeat(80));
177
+
178
+ const content = await fs.readFile(path.join(logsDir, mdFiles[0]), 'utf-8');
179
+ // Show first 50 lines
180
+ const lines = content.split('\n');
181
+ const preview = lines.slice(0, 50).join('\n');
182
+ console.log(preview);
183
+
184
+ if (lines.length > 50) {
185
+ console.log(`\n... (${lines.length - 50} more lines)`);
186
+ }
187
+ console.log('─'.repeat(80));
188
+
189
+ console.log(`\n💡 To view the full report, run:`);
190
+ console.log(` cat research_logs/${mdFiles[0]}`);
191
+ }
192
+
193
+ } catch (error) {
194
+ console.error('❌ Error checking files:', error);
195
+ }
196
+ }
197
+
198
+ // Run the test
199
+ testFileLogging().catch(console.error);
@@ -0,0 +1,67 @@
1
+ import { IAgentRuntime, elizaLogger } from '@elizaos/core';
2
+ import { ResearchService } from '../service';
3
+
4
+ export async function testRealResearch(runtime: IAgentRuntime) {
5
+ elizaLogger.info('🔬 Testing Real Research Functionality');
6
+
7
+ const service = runtime.getService<ResearchService>('research');
8
+ if (!service) {
9
+ elizaLogger.error('Research service not available');
10
+ return;
11
+ }
12
+
13
+ // Test 1: Simple research query
14
+ elizaLogger.info('\n📚 Test 1: Simple Research Query');
15
+ const project1 = await service.createResearchProject(
16
+ 'Latest developments in quantum computing 2024',
17
+ { maxSearchResults: 3 }
18
+ );
19
+
20
+ elizaLogger.info(`Created project: ${project1.id}`);
21
+ elizaLogger.info(`Query: ${project1.query}`);
22
+ elizaLogger.info(`Status: ${project1.status}`);
23
+
24
+ // Wait a bit and check progress
25
+ let checkCount = 0;
26
+ const checkInterval = setInterval(async () => {
27
+ checkCount++;
28
+ const current = await service.getProject(project1.id);
29
+ if (!current) return;
30
+
31
+ elizaLogger.info(`Progress: Phase=${current.phase}, Sources=${current.sources.length}, Findings=${current.findings.length}`);
32
+
33
+ // Sample a finding if available
34
+ if (current.findings.length > 0 && checkCount === 1) {
35
+ const sample = current.findings[0];
36
+ elizaLogger.info(`Sample finding: "${sample.content.substring(0, 200)}..."`);
37
+ elizaLogger.info(`From source: ${sample.source.title} (${sample.source.url})`);
38
+ }
39
+
40
+ // Check if completed or timeout
41
+ if (current.status === 'completed' || current.status === 'failed' || checkCount > 30) {
42
+ clearInterval(checkInterval);
43
+
44
+ if (current.status === 'completed') {
45
+ elizaLogger.success(`✅ Research completed successfully!`);
46
+ elizaLogger.info(`Final results: ${current.sources.length} sources, ${current.findings.length} findings`);
47
+
48
+ if (current.report) {
49
+ elizaLogger.info(`Report generated with ${current.report.sections.length} sections`);
50
+ }
51
+ } else if (current.status === 'failed') {
52
+ elizaLogger.error(`❌ Research failed: ${current.error}`);
53
+ } else {
54
+ elizaLogger.warn(`⏱️ Research timed out after ${checkCount * 5} seconds`);
55
+ }
56
+ }
57
+ }, 5000); // Check every 5 seconds
58
+ }
59
+
60
+ // Test runner
61
+ export async function runRealResearchTests(runtime: IAgentRuntime) {
62
+ try {
63
+ await testRealResearch(runtime);
64
+ } catch (error) {
65
+ elizaLogger.error('Test failed:', error);
66
+ }
67
+ }
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Test script specifically for renewable energy storage research
4
+ */
5
+
6
+ // Load environment variables
7
+ import dotenv from 'dotenv';
8
+ import path from 'path';
9
+ dotenv.config({ path: path.join(__dirname, '../../.env') });
10
+
11
+ import { ResearchService } from '../service';
12
+ import { elizaLogger, IAgentRuntime, ModelType, Character, UUID, asUUID } from '@elizaos/core';
13
+ import { ResearchConfig, ResearchStatus } from '../types';
14
+ import fs from 'fs/promises';
15
+
16
+ // Create a more complete runtime with real model support
17
+ const createRealRuntime = (): IAgentRuntime => {
18
+ const testCharacter: Character = {
19
+ name: 'EnergyResearcher',
20
+ bio: ['An expert researcher focused on renewable energy and sustainability'],
21
+ system: 'You are an expert research assistant specializing in renewable energy, environmental science, and grid-scale energy storage technologies.',
22
+ messageExamples: [],
23
+ postExamples: [],
24
+ topics: ['renewable energy', 'energy storage', 'environmental impact', 'grid technology'],
25
+ adjectives: ['analytical', 'thorough', 'technical'],
26
+ knowledge: [],
27
+ plugins: ['research'],
28
+ };
29
+
30
+ return {
31
+ agentId: asUUID('12345678-1234-1234-1234-123456789012'),
32
+ character: testCharacter,
33
+ getSetting: (key: string) => {
34
+ const value = process.env[key];
35
+ if (key.includes('API_KEY') && value) {
36
+ elizaLogger.debug(`[Runtime] Found ${key}`);
37
+ }
38
+ return value || null;
39
+ },
40
+ getService: (name: string) => null,
41
+ providers: [],
42
+ actions: [],
43
+ evaluators: [],
44
+ plugins: [],
45
+ services: new Map(),
46
+
47
+ // Simplified model that returns reasonable responses
48
+ useModel: async (modelType: string, params: any) => {
49
+ const messages = params?.messages || [];
50
+ const lastMessage = messages[messages.length - 1]?.content || '';
51
+
52
+ elizaLogger.debug(`[Model ${modelType}] Processing:`, lastMessage.substring(0, 100) + '...');
53
+
54
+ // Handle different types of model requests
55
+ if (lastMessage.includes('Analyze this research query')) {
56
+ return {
57
+ content: JSON.stringify({
58
+ domain: 'ENGINEERING',
59
+ taskType: 'comparative',
60
+ temporalFocus: 'current',
61
+ geographicScope: ['global'],
62
+ requiredExpertise: ['energy storage', 'environmental science', 'economics'],
63
+ expectedSourceTypes: ['academic', 'technical', 'government']
64
+ })
65
+ };
66
+ } else if (lastMessage.includes('Generate sub-queries')) {
67
+ return {
68
+ content: `PURPOSE: Compare different storage technologies
69
+ QUERY: lithium-ion batteries environmental impact grid scale
70
+ TYPE: comparative
71
+ PRIORITY: high
72
+ ---
73
+ PURPOSE: Economic analysis of storage technologies
74
+ QUERY: grid scale energy storage cost comparison economics
75
+ TYPE: statistical
76
+ PRIORITY: high
77
+ ---
78
+ PURPOSE: Environmental lifecycle assessment
79
+ QUERY: renewable energy storage lifecycle assessment environmental impact
80
+ TYPE: analytical
81
+ PRIORITY: medium`
82
+ };
83
+ } else if (lastMessage.includes('Extract key findings')) {
84
+ return {
85
+ content: JSON.stringify([{
86
+ content: "Based on the analysis, lithium-ion batteries currently dominate grid-scale storage with 90% market share, but face environmental challenges in mining and disposal.",
87
+ relevance: 0.95,
88
+ confidence: 0.9,
89
+ category: "fact"
90
+ }])
91
+ };
92
+ } else if (lastMessage.includes('research findings')) {
93
+ return {
94
+ content: "The research reveals that while lithium-ion batteries currently dominate grid-scale energy storage, alternative technologies like flow batteries and compressed air storage show promise for specific applications with potentially lower environmental impacts."
95
+ };
96
+ }
97
+
98
+ // Default response
99
+ return { content: "Analysis complete. Findings integrated into research report." };
100
+ },
101
+
102
+ // Memory managers
103
+ messageManager: {
104
+ createMemory: async () => asUUID('12345678-1234-1234-1234-123456789013'),
105
+ getMemories: async () => [],
106
+ getMemoriesByRoomIds: async () => [],
107
+ getCachedEmbeddings: async () => [],
108
+ searchMemoriesByEmbedding: async () => [],
109
+ },
110
+ descriptionManager: {
111
+ createMemory: async () => asUUID('12345678-1234-1234-1234-123456789014'),
112
+ getMemories: async () => [],
113
+ getMemoriesByRoomIds: async () => [],
114
+ getCachedEmbeddings: async () => [],
115
+ searchMemoriesByEmbedding: async () => [],
116
+ },
117
+ documentsManager: {
118
+ createMemory: async () => asUUID('12345678-1234-1234-1234-123456789015'),
119
+ getMemories: async () => [],
120
+ getMemoriesByRoomIds: async () => [],
121
+ getCachedEmbeddings: async () => [],
122
+ searchMemoriesByEmbedding: async () => [],
123
+ },
124
+ knowledgeManager: {
125
+ createMemory: async () => asUUID('12345678-1234-1234-1234-123456789016'),
126
+ getMemories: async () => [],
127
+ getMemoriesByRoomIds: async () => [],
128
+ getCachedEmbeddings: async () => [],
129
+ searchMemoriesByEmbedding: async () => [],
130
+ },
131
+ loreManager: {
132
+ createMemory: async () => asUUID('12345678-1234-1234-1234-123456789017'),
133
+ getMemories: async () => [],
134
+ getMemoriesByRoomIds: async () => [],
135
+ getCachedEmbeddings: async () => [],
136
+ searchMemoriesByEmbedding: async () => [],
137
+ },
138
+
139
+ stop: async () => {},
140
+ } as any as IAgentRuntime;
141
+ };
142
+
143
+ async function testRenewableEnergyResearch() {
144
+ elizaLogger.info('=== Testing Renewable Energy Storage Research ===');
145
+
146
+ const runtime = createRealRuntime();
147
+ const service = new ResearchService(runtime);
148
+
149
+ const query = "Compare the environmental and economic impacts of different renewable energy storage technologies for grid-scale deployment";
150
+
151
+ elizaLogger.info(`Research Query: "${query}"`);
152
+
153
+ try {
154
+ // Create research project with specific configuration
155
+ const config: Partial<ResearchConfig> = {
156
+ searchProviders: ['web', 'academic'],
157
+ maxSearchResults: 10,
158
+ maxDepth: 3,
159
+ enableImages: false,
160
+ evaluationEnabled: false,
161
+ };
162
+
163
+ const project = await service.createResearchProject(query, config);
164
+
165
+ elizaLogger.info('✅ Project created successfully:', {
166
+ id: project.id,
167
+ status: project.status,
168
+ });
169
+
170
+ // Wait for research to complete (max 30 seconds)
171
+ let attempts = 0;
172
+ while (attempts < 30) {
173
+ await new Promise(resolve => setTimeout(resolve, 1000));
174
+
175
+ const currentProject = await service.getProject(project.id);
176
+ if (!currentProject) break;
177
+
178
+ elizaLogger.info(`Progress: ${currentProject.phase} - Sources: ${currentProject.sources.length}, Findings: ${currentProject.findings.length}`);
179
+
180
+ if (currentProject.status === ResearchStatus.COMPLETED ||
181
+ currentProject.status === ResearchStatus.FAILED) {
182
+ elizaLogger.info(`Research ${currentProject.status}`);
183
+
184
+ // Save the report if completed
185
+ if (currentProject.report) {
186
+ // Export as markdown
187
+ const markdownReport = await service.exportProject(project.id, 'markdown');
188
+ const reportPath = path.join(__dirname, '../../test-renewable-energy-report.md');
189
+ await fs.writeFile(reportPath, markdownReport);
190
+ elizaLogger.info(`✅ Report saved to: ${reportPath}`);
191
+
192
+ // Show first 500 chars of the report
193
+ elizaLogger.info('\n📄 Report Preview:\n' + markdownReport.substring(0, 500) + '...\n');
194
+
195
+ // Check if report contains renewable energy content
196
+ const hasRelevantContent = markdownReport.toLowerCase().includes('energy') ||
197
+ markdownReport.toLowerCase().includes('storage') ||
198
+ markdownReport.toLowerCase().includes('battery');
199
+
200
+ if (hasRelevantContent) {
201
+ elizaLogger.info('✅ Report contains relevant renewable energy content!');
202
+ } else {
203
+ elizaLogger.error('❌ Report does NOT contain relevant renewable energy content!');
204
+ }
205
+ }
206
+
207
+ break;
208
+ }
209
+
210
+ attempts++;
211
+ }
212
+
213
+ // Export the project data for analysis
214
+ const exportData = await service.exportProject(project.id, 'json');
215
+ const exportPath = path.join(__dirname, '../../test-renewable-energy-export.json');
216
+ await fs.writeFile(exportPath, exportData);
217
+ elizaLogger.info(`✅ Project data exported to: ${exportPath}`);
218
+
219
+ await service.stop();
220
+
221
+ } catch (error) {
222
+ elizaLogger.error('❌ Test failed:', error);
223
+ }
224
+
225
+ elizaLogger.info('=== Test Complete ===');
226
+ }
227
+
228
+ // Run the test
229
+ testRenewableEnergyResearch().catch(console.error);
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { Plugin } from '@elizaos/core';
2
+ import { ResearchService } from './service';
3
+ import { researchProviders } from './providers';
4
+ import { researchActions } from './actions';
5
+ import deepResearchBenchSimplifiedTests from './__tests__/deepresearch-bench-simplified.e2e.test';
6
+
7
+ export * from './types';
8
+ export * from './service';
9
+ export * from './actions';
10
+ export * from './providers';
11
+ export * from './integrations';
12
+ export * from './strategies/research-strategies';
13
+ export * from './evaluation/research-evaluator';
14
+
15
+ export const researchPlugin: Plugin = {
16
+ name: 'research',
17
+ description: 'PhD-level deep research across 22 domains with RACE/FACT evaluation',
18
+
19
+ services: [ResearchService],
20
+ actions: researchActions,
21
+ providers: researchProviders,
22
+
23
+ tests: [
24
+ deepResearchBenchSimplifiedTests
25
+ ],
26
+ };
27
+
28
+ export default researchPlugin;
@@ -0,0 +1,128 @@
1
+ import { SearchResult } from '../types';
2
+ import { elizaLogger } from '@elizaos/core';
3
+ import { SearchProvider } from './rate-limiter';
4
+ import crypto from 'crypto';
5
+
6
+ export interface CacheConfig {
7
+ ttlMinutes?: number;
8
+ maxSize?: number;
9
+ sizeCalculation?: (value: SearchResult[], key: string) => number;
10
+ }
11
+
12
+ // Built-in LRU cache implementation
13
+ class SimpleLRUCache<K, V> {
14
+ private cache: Map<K, { value: V; timestamp: number }> = new Map();
15
+ private maxSize: number;
16
+ private ttl: number;
17
+
18
+ constructor(options: { max: number; ttl: number }) {
19
+ this.maxSize = options.max;
20
+ this.ttl = options.ttl;
21
+ }
22
+
23
+ get(key: K): V | undefined {
24
+ const item = this.cache.get(key);
25
+ if (!item) return undefined;
26
+
27
+ // Check if expired
28
+ if (Date.now() - item.timestamp > this.ttl) {
29
+ this.cache.delete(key);
30
+ return undefined;
31
+ }
32
+
33
+ // Move to end (LRU behavior)
34
+ this.cache.delete(key);
35
+ this.cache.set(key, item);
36
+
37
+ return item.value;
38
+ }
39
+
40
+ set(key: K, value: V): void {
41
+ // Remove item if it exists (to reinsert at end)
42
+ this.cache.delete(key);
43
+
44
+ // Remove oldest items if at capacity
45
+ if (this.cache.size >= this.maxSize) {
46
+ const firstKey = this.cache.keys().next().value;
47
+ if (firstKey !== undefined) {
48
+ this.cache.delete(firstKey);
49
+ }
50
+ }
51
+
52
+ this.cache.set(key, { value, timestamp: Date.now() });
53
+ }
54
+
55
+ clear(): void {
56
+ this.cache.clear();
57
+ }
58
+
59
+ get size(): number {
60
+ return this.cache.size;
61
+ }
62
+ }
63
+
64
+ export class CachedSearchProvider implements SearchProvider {
65
+ private cache: SimpleLRUCache<string, SearchResult[]>;
66
+ public readonly name: string;
67
+
68
+ constructor(
69
+ private provider: SearchProvider,
70
+ config: CacheConfig = {}
71
+ ) {
72
+ this.name = `Cached(${provider.name || 'Unknown'})`;
73
+
74
+ this.cache = new SimpleLRUCache<string, SearchResult[]>({
75
+ max: config.maxSize || 1000,
76
+ ttl: (config.ttlMinutes || 60) * 60 * 1000, // Convert to milliseconds
77
+ });
78
+ }
79
+
80
+ private getCacheKey(query: string, maxResults?: number): string {
81
+ const keyData = `${query}:${maxResults || 'default'}`;
82
+ return crypto.createHash('md5').update(keyData).digest('hex');
83
+ }
84
+
85
+ async search(query: string, maxResults?: number): Promise<SearchResult[]> {
86
+ const cacheKey = this.getCacheKey(query, maxResults);
87
+
88
+ // Check cache first
89
+ const cached = this.cache.get(cacheKey);
90
+ if (cached) {
91
+ elizaLogger.info(`[Cache] Hit for query: ${query}`);
92
+ return cached;
93
+ }
94
+
95
+ elizaLogger.info(`[Cache] Miss for query: ${query}`);
96
+
97
+ // Fetch from underlying provider
98
+ const results = await this.provider.search(query, maxResults);
99
+
100
+ // Store in cache
101
+ if (results && results.length > 0) {
102
+ this.cache.set(cacheKey, results);
103
+ }
104
+
105
+ return results;
106
+ }
107
+
108
+ // Cache management methods
109
+ clear(): void {
110
+ this.cache.clear();
111
+ }
112
+
113
+ getCacheStats(): { size: number; hits: number; misses: number } {
114
+ return {
115
+ size: this.cache.size,
116
+ hits: 0, // Could track this if needed
117
+ misses: 0,
118
+ };
119
+ }
120
+ }
121
+
122
+ // Helper to create a cached provider with default settings
123
+ export function withCache(
124
+ provider: SearchProvider,
125
+ ttlMinutes: number = 60
126
+ ): CachedSearchProvider {
127
+ return new CachedSearchProvider(provider, { ttlMinutes });
128
+ }