@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/src/actions.ts
ADDED
|
@@ -0,0 +1,1677 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Action,
|
|
3
|
+
ActionExample,
|
|
4
|
+
IAgentRuntime,
|
|
5
|
+
Memory,
|
|
6
|
+
State,
|
|
7
|
+
HandlerCallback,
|
|
8
|
+
ModelType,
|
|
9
|
+
elizaLogger,
|
|
10
|
+
} from '@elizaos/core';
|
|
11
|
+
import { ResearchService } from './service';
|
|
12
|
+
import {
|
|
13
|
+
ResearchStatus,
|
|
14
|
+
ResearchPhase,
|
|
15
|
+
ResearchDomain,
|
|
16
|
+
TaskType,
|
|
17
|
+
ResearchDepth,
|
|
18
|
+
ActionResult,
|
|
19
|
+
ActionContext,
|
|
20
|
+
ResearchProject,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
// Helper function to extract domain from text
|
|
24
|
+
async function extractDomain(runtime: IAgentRuntime, text: string): Promise<ResearchDomain> {
|
|
25
|
+
try {
|
|
26
|
+
const prompt = `Analyze this text and determine the most appropriate research domain:
|
|
27
|
+
Text: "${text}"
|
|
28
|
+
|
|
29
|
+
Domains: ${Object.values(ResearchDomain).join(', ')}
|
|
30
|
+
|
|
31
|
+
Respond with just the domain name.`;
|
|
32
|
+
|
|
33
|
+
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
34
|
+
messages: [{ role: 'user', content: prompt }],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const domainText = (typeof response === 'string' ? response : (response as any).content || '')
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.trim();
|
|
40
|
+
|
|
41
|
+
// Map response to enum
|
|
42
|
+
for (const domain of Object.values(ResearchDomain)) {
|
|
43
|
+
if (domainText.includes(domain)) {
|
|
44
|
+
return domain as ResearchDomain;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return ResearchDomain.GENERAL;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
elizaLogger.error('Error extracting domain:', error);
|
|
51
|
+
// Default to GENERAL if model call fails
|
|
52
|
+
return ResearchDomain.GENERAL;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Helper function to extract task type
|
|
57
|
+
async function extractTaskType(runtime: IAgentRuntime, text: string): Promise<TaskType> {
|
|
58
|
+
try {
|
|
59
|
+
const prompt = `Analyze this research query and determine the task type:
|
|
60
|
+
Query: "${text}"
|
|
61
|
+
|
|
62
|
+
Task Types:
|
|
63
|
+
- exploratory: General exploration of a topic
|
|
64
|
+
- comparative: Comparing multiple items/concepts
|
|
65
|
+
- analytical: Deep analysis of a specific aspect
|
|
66
|
+
- synthetic: Combining multiple perspectives
|
|
67
|
+
- evaluative: Assessing or judging something
|
|
68
|
+
- predictive: Forecasting or trend analysis
|
|
69
|
+
|
|
70
|
+
Respond with just the task type.`;
|
|
71
|
+
|
|
72
|
+
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
73
|
+
messages: [{ role: 'user', content: prompt }],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const taskText = (typeof response === 'string' ? response : (response as any).content || '')
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.trim();
|
|
79
|
+
|
|
80
|
+
if (taskText.includes('comparative')) return TaskType.COMPARATIVE;
|
|
81
|
+
if (taskText.includes('analytical')) return TaskType.ANALYTICAL;
|
|
82
|
+
if (taskText.includes('synthetic')) return TaskType.SYNTHETIC;
|
|
83
|
+
if (taskText.includes('evaluative')) return TaskType.EVALUATIVE;
|
|
84
|
+
if (taskText.includes('predictive')) return TaskType.PREDICTIVE;
|
|
85
|
+
|
|
86
|
+
return TaskType.EXPLORATORY;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
elizaLogger.error('Error extracting task type:', error);
|
|
89
|
+
// Default to EXPLORATORY if model call fails
|
|
90
|
+
return TaskType.EXPLORATORY;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Helper function to extract research depth
|
|
95
|
+
async function extractDepth(runtime: IAgentRuntime, text: string): Promise<ResearchDepth> {
|
|
96
|
+
try {
|
|
97
|
+
const prompt = `Analyze this research query and determine the appropriate depth:
|
|
98
|
+
Query: "${text}"
|
|
99
|
+
|
|
100
|
+
Depths:
|
|
101
|
+
- surface: Quick overview, basic information
|
|
102
|
+
- moderate: Standard research with good coverage
|
|
103
|
+
- deep: Comprehensive research with detailed analysis
|
|
104
|
+
- phd-level: Expert-level research with citations and academic rigor
|
|
105
|
+
|
|
106
|
+
Look for keywords like: quick, overview, detailed, comprehensive, expert, academic, thorough
|
|
107
|
+
|
|
108
|
+
Respond with just the depth level.`;
|
|
109
|
+
|
|
110
|
+
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
111
|
+
messages: [{ role: 'user', content: prompt }],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const depthText = (typeof response === 'string' ? response : (response as any).content || '')
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.trim();
|
|
117
|
+
|
|
118
|
+
if (depthText.includes('surface')) return ResearchDepth.SURFACE;
|
|
119
|
+
if (depthText.includes('deep')) return ResearchDepth.DEEP;
|
|
120
|
+
if (depthText.includes('phd')) return ResearchDepth.PHD_LEVEL;
|
|
121
|
+
|
|
122
|
+
return ResearchDepth.MODERATE;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
elizaLogger.error('Error extracting depth:', error);
|
|
125
|
+
// Default to MODERATE if model call fails
|
|
126
|
+
return ResearchDepth.MODERATE;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Start a new research project
|
|
132
|
+
*/
|
|
133
|
+
export const startResearchAction: Action = {
|
|
134
|
+
name: 'start_research',
|
|
135
|
+
description: 'Start a new deep research project on any topic across 22+ domains',
|
|
136
|
+
|
|
137
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
138
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
139
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
140
|
+
const __avKeywords = ['start', 'research'];
|
|
141
|
+
const __avKeywordOk =
|
|
142
|
+
__avKeywords.length > 0 &&
|
|
143
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
144
|
+
const __avRegex = new RegExp('\\b(?:start|research)\\b', 'i');
|
|
145
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
146
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
147
|
+
const __avExpectedSource = '';
|
|
148
|
+
const __avSourceOk = __avExpectedSource
|
|
149
|
+
? __avSource === __avExpectedSource
|
|
150
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
151
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
152
|
+
const __avInputOk =
|
|
153
|
+
__avText.trim().length > 0 ||
|
|
154
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
155
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
156
|
+
|
|
157
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
162
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
163
|
+
return !!researchService && (message.content.text?.trim().length || 0) > 3;
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async handler(
|
|
173
|
+
runtime: IAgentRuntime,
|
|
174
|
+
message: Memory,
|
|
175
|
+
state?: State,
|
|
176
|
+
options?: Record<string, unknown>,
|
|
177
|
+
callback?: HandlerCallback
|
|
178
|
+
): Promise<ActionResult> {
|
|
179
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
180
|
+
if (!researchService) {
|
|
181
|
+
throw new Error('Research service not available');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const query = message.content.text?.trim() || '';
|
|
185
|
+
|
|
186
|
+
// Extract research parameters
|
|
187
|
+
const [domain, taskType, depth] = await Promise.all([
|
|
188
|
+
extractDomain(runtime, query),
|
|
189
|
+
extractTaskType(runtime, query),
|
|
190
|
+
extractDepth(runtime, query),
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
elizaLogger.info(`Starting research: domain=${domain}, type=${taskType}, depth=${depth}`);
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const project = await researchService.createResearchProject(query, {
|
|
197
|
+
domain,
|
|
198
|
+
researchDepth: depth,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const response = {
|
|
202
|
+
text: `I've initiated a ${depth}-level ${taskType} research project in the ${domain} domain.
|
|
203
|
+
|
|
204
|
+
📋 **Research Details:**
|
|
205
|
+
- Query: "${project.query}"
|
|
206
|
+
- Domain: ${domain}
|
|
207
|
+
- Task Type: ${taskType}
|
|
208
|
+
- Depth: ${depth}
|
|
209
|
+
- Project ID: ${project.id}
|
|
210
|
+
|
|
211
|
+
🔄 **Research Phases:**
|
|
212
|
+
1. Planning - Developing research strategy
|
|
213
|
+
2. Searching - Gathering sources
|
|
214
|
+
3. Analyzing - Extracting insights
|
|
215
|
+
4. Synthesizing - Connecting findings
|
|
216
|
+
5. Evaluating - Quality assessment
|
|
217
|
+
6. Reporting - Creating comprehensive report
|
|
218
|
+
|
|
219
|
+
The research will follow DeepResearch Bench standards for quality. I'll notify you when complete.`,
|
|
220
|
+
metadata: {
|
|
221
|
+
projectId: project.id,
|
|
222
|
+
project,
|
|
223
|
+
action: 'start_research',
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (callback) {
|
|
228
|
+
await callback(response);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
success: true,
|
|
233
|
+
data: project,
|
|
234
|
+
nextActions: ['check_research_status', 'refine_research_query', 'pause_research'],
|
|
235
|
+
metadata: {
|
|
236
|
+
projectId: project.id,
|
|
237
|
+
domain,
|
|
238
|
+
taskType,
|
|
239
|
+
depth,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
elizaLogger.error('Failed to start research:', error);
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
nextActions: ['start_research'],
|
|
248
|
+
metadata: {},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
examples: [
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
name: '{{user}}',
|
|
257
|
+
content: {
|
|
258
|
+
text: 'Research the impact of quantum computing on cryptography with academic rigor',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: '{{assistant}}',
|
|
263
|
+
content: {
|
|
264
|
+
text: "I'll start a comprehensive research project on quantum computing's impact on cryptography.",
|
|
265
|
+
action: 'start_research',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
[
|
|
270
|
+
{
|
|
271
|
+
name: '{{user}}',
|
|
272
|
+
content: {
|
|
273
|
+
text: 'Give me a quick overview of sustainable urban transportation solutions',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: '{{assistant}}',
|
|
278
|
+
content: {
|
|
279
|
+
text: "I'll conduct a surface-level exploratory research on sustainable urban transportation.",
|
|
280
|
+
action: 'start_research',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
] as ActionExample[][],
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check research status
|
|
289
|
+
*/
|
|
290
|
+
export const checkResearchStatusAction: Action = {
|
|
291
|
+
name: 'check_research_status',
|
|
292
|
+
description: 'Check the status and progress of research projects',
|
|
293
|
+
|
|
294
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
295
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
296
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
297
|
+
const __avKeywords = ['check', 'research', 'status'];
|
|
298
|
+
const __avKeywordOk =
|
|
299
|
+
__avKeywords.length > 0 &&
|
|
300
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
301
|
+
const __avRegex = new RegExp('\\b(?:check|research|status)\\b', 'i');
|
|
302
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
303
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
304
|
+
const __avExpectedSource = '';
|
|
305
|
+
const __avSourceOk = __avExpectedSource
|
|
306
|
+
? __avSource === __avExpectedSource
|
|
307
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
308
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
309
|
+
const __avInputOk =
|
|
310
|
+
__avText.trim().length > 0 ||
|
|
311
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
312
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
313
|
+
|
|
314
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
319
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
320
|
+
return !!researchService;
|
|
321
|
+
};
|
|
322
|
+
try {
|
|
323
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
async handler(
|
|
330
|
+
runtime: IAgentRuntime,
|
|
331
|
+
message: Memory,
|
|
332
|
+
state?: State,
|
|
333
|
+
options?: Record<string, unknown>,
|
|
334
|
+
callback?: HandlerCallback
|
|
335
|
+
): Promise<ActionResult> {
|
|
336
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
337
|
+
if (!researchService) {
|
|
338
|
+
throw new Error('Research service not available');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Check if a specific project ID is mentioned
|
|
343
|
+
const projectIdMatch = message.content.text?.match(/project[:\s]+([a-zA-Z0-9-]+)/i);
|
|
344
|
+
let projects: any[] = [];
|
|
345
|
+
|
|
346
|
+
if (projectIdMatch) {
|
|
347
|
+
const project = await researchService.getProject(projectIdMatch[1]);
|
|
348
|
+
if (project) projects = [project];
|
|
349
|
+
} else {
|
|
350
|
+
// Get all active projects
|
|
351
|
+
projects = await researchService.getActiveProjects();
|
|
352
|
+
|
|
353
|
+
// If no active, get recent completed
|
|
354
|
+
if (projects.length === 0) {
|
|
355
|
+
const allProjects = await researchService.getAllProjects();
|
|
356
|
+
projects = allProjects.slice(-3); // Last 3 projects
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (projects.length === 0) {
|
|
361
|
+
const response = {
|
|
362
|
+
text: 'No research projects found. Would you like to start a new research project?',
|
|
363
|
+
metadata: { status: 'no_projects' },
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
if (callback) await callback(response);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
data: { projects: [] },
|
|
371
|
+
nextActions: ['start_research'],
|
|
372
|
+
metadata: { count: 0 },
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Format status report
|
|
377
|
+
const statusReports = projects
|
|
378
|
+
.map((project) => {
|
|
379
|
+
const phaseProgress =
|
|
380
|
+
Object.values(ResearchPhase).indexOf(project.phase) /
|
|
381
|
+
(Object.values(ResearchPhase).length - 1);
|
|
382
|
+
const percentComplete = Math.round(phaseProgress * 100);
|
|
383
|
+
|
|
384
|
+
return `📊 **${project.query}**
|
|
385
|
+
- Status: ${project.status} (${percentComplete}% complete)
|
|
386
|
+
- Phase: ${project.phase}
|
|
387
|
+
- Domain: ${project.metadata.domain}
|
|
388
|
+
- Sources: ${project.sources.length} collected
|
|
389
|
+
- Findings: ${project.findings.length} extracted
|
|
390
|
+
- Started: ${new Date(project.createdAt).toLocaleString()}`;
|
|
391
|
+
})
|
|
392
|
+
.join('\n\n');
|
|
393
|
+
|
|
394
|
+
const response = {
|
|
395
|
+
text: `# Research Status Report\n\n${statusReports}`,
|
|
396
|
+
metadata: { projects },
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
if (callback) await callback(response);
|
|
400
|
+
|
|
401
|
+
// Determine next actions based on project states
|
|
402
|
+
const hasActive = projects.some((p) => p.status === ResearchStatus.ACTIVE);
|
|
403
|
+
const hasCompleted = projects.some((p) => p.status === ResearchStatus.COMPLETED);
|
|
404
|
+
|
|
405
|
+
const nextActions = [];
|
|
406
|
+
if (hasActive) {
|
|
407
|
+
nextActions.push('pause_research', 'refine_research_query');
|
|
408
|
+
}
|
|
409
|
+
if (hasCompleted) {
|
|
410
|
+
nextActions.push('get_research_report', 'evaluate_research', 'export_research');
|
|
411
|
+
}
|
|
412
|
+
nextActions.push('start_research');
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
success: true,
|
|
416
|
+
data: { projects },
|
|
417
|
+
nextActions,
|
|
418
|
+
metadata: {
|
|
419
|
+
activeCount: projects.filter((p) => p.status === ResearchStatus.ACTIVE).length,
|
|
420
|
+
completedCount: projects.filter((p) => p.status === ResearchStatus.COMPLETED).length,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
} catch (error) {
|
|
424
|
+
elizaLogger.error('Failed to check research status:', error);
|
|
425
|
+
return {
|
|
426
|
+
success: false,
|
|
427
|
+
error: error instanceof Error ? error.message : String(error),
|
|
428
|
+
nextActions: ['start_research'],
|
|
429
|
+
metadata: {},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
examples: [
|
|
435
|
+
[
|
|
436
|
+
{
|
|
437
|
+
name: '{{user}}',
|
|
438
|
+
content: {
|
|
439
|
+
text: "What's the status of my research projects?",
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: '{{assistant}}',
|
|
444
|
+
content: {
|
|
445
|
+
text: "I'll check the status of your research projects.",
|
|
446
|
+
action: 'check_research_status',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
] as ActionExample[][],
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Refine research query
|
|
455
|
+
*/
|
|
456
|
+
export const refineResearchQueryAction: Action = {
|
|
457
|
+
name: 'refine_research_query',
|
|
458
|
+
description: 'Refine or expand the research query based on findings',
|
|
459
|
+
|
|
460
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
461
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
462
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
463
|
+
const __avKeywords = ['refine', 'research', 'query'];
|
|
464
|
+
const __avKeywordOk =
|
|
465
|
+
__avKeywords.length > 0 &&
|
|
466
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
467
|
+
const __avRegex = new RegExp('\\b(?:refine|research|query)\\b', 'i');
|
|
468
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
469
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
470
|
+
const __avExpectedSource = '';
|
|
471
|
+
const __avSourceOk = __avExpectedSource
|
|
472
|
+
? __avSource === __avExpectedSource
|
|
473
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
474
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
475
|
+
const __avInputOk =
|
|
476
|
+
__avText.trim().length > 0 ||
|
|
477
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
478
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
479
|
+
|
|
480
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
485
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
486
|
+
if (!researchService) return false;
|
|
487
|
+
|
|
488
|
+
const activeProjects = await researchService.getActiveProjects();
|
|
489
|
+
return activeProjects.length > 0;
|
|
490
|
+
};
|
|
491
|
+
try {
|
|
492
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
493
|
+
} catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
async handler(
|
|
499
|
+
runtime: IAgentRuntime,
|
|
500
|
+
message: Memory,
|
|
501
|
+
state?: State,
|
|
502
|
+
options?: Record<string, unknown>,
|
|
503
|
+
callback?: HandlerCallback
|
|
504
|
+
): Promise<ActionResult> {
|
|
505
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
506
|
+
if (!researchService) {
|
|
507
|
+
throw new Error('Research service not available');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const activeProjects = await researchService.getActiveProjects();
|
|
512
|
+
if (activeProjects.length === 0) {
|
|
513
|
+
return {
|
|
514
|
+
success: false,
|
|
515
|
+
error: 'No active research projects to refine',
|
|
516
|
+
nextActions: ['start_research'],
|
|
517
|
+
metadata: {},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const project = activeProjects[activeProjects.length - 1];
|
|
522
|
+
|
|
523
|
+
// Extract refinement direction from message
|
|
524
|
+
const refinementPrompt = `Analyze this refinement request for the research query "${project.query}":
|
|
525
|
+
User request: "${message.content.text || ''}"
|
|
526
|
+
|
|
527
|
+
Determine:
|
|
528
|
+
1. Refinement type (narrow/broaden/pivot/deepen)
|
|
529
|
+
2. New focus areas (2-3 specific areas)
|
|
530
|
+
3. Additional queries (2-3 refined queries)
|
|
531
|
+
|
|
532
|
+
Respond with JSON:
|
|
533
|
+
{
|
|
534
|
+
"refinementType": "type",
|
|
535
|
+
"focusAreas": ["area1", "area2"],
|
|
536
|
+
"queries": ["query1", "query2"]
|
|
537
|
+
}`;
|
|
538
|
+
|
|
539
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
540
|
+
messages: [{ role: 'user', content: refinementPrompt }],
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
let refinement;
|
|
544
|
+
try {
|
|
545
|
+
const content = typeof response === 'string' ? response : (response as any).content || '';
|
|
546
|
+
refinement = JSON.parse(content);
|
|
547
|
+
} catch (e) {
|
|
548
|
+
refinement = {
|
|
549
|
+
refinementType: 'deepen',
|
|
550
|
+
focusAreas: ['specific aspects', 'detailed analysis'],
|
|
551
|
+
queries: [`${project.query} detailed analysis`, `${project.query} case studies`],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Add refined queries to the project
|
|
556
|
+
await researchService.addRefinedQueries(project.id, refinement.queries);
|
|
557
|
+
|
|
558
|
+
const responseText = {
|
|
559
|
+
text: `I've refined the research focus for your project.
|
|
560
|
+
|
|
561
|
+
🎯 **Refinement Applied:**
|
|
562
|
+
- Type: ${refinement.refinementType}
|
|
563
|
+
- New Focus Areas: ${refinement.focusAreas.join(', ')}
|
|
564
|
+
|
|
565
|
+
📝 **Additional Queries:**
|
|
566
|
+
${refinement.queries.map((q: string, i: number) => `${i + 1}. ${q}`).join('\n')}
|
|
567
|
+
|
|
568
|
+
The research will now explore these refined aspects while maintaining the original scope.`,
|
|
569
|
+
metadata: { project, refinement },
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
if (callback) await callback(responseText);
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
success: true,
|
|
576
|
+
data: { project, refinement },
|
|
577
|
+
nextActions: ['check_research_status', 'get_research_report'],
|
|
578
|
+
metadata: {
|
|
579
|
+
projectId: project.id,
|
|
580
|
+
refinementType: refinement.refinementType,
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
} catch (error) {
|
|
584
|
+
elizaLogger.error('Failed to refine research query:', error);
|
|
585
|
+
return {
|
|
586
|
+
success: false,
|
|
587
|
+
error: error instanceof Error ? error.message : String(error),
|
|
588
|
+
nextActions: ['check_research_status'],
|
|
589
|
+
metadata: {},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
examples: [
|
|
595
|
+
[
|
|
596
|
+
{
|
|
597
|
+
name: '{{user}}',
|
|
598
|
+
content: {
|
|
599
|
+
text: 'Focus more on the security implications and recent breakthroughs',
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: '{{assistant}}',
|
|
604
|
+
content: {
|
|
605
|
+
text: "I'll refine the research to focus on security implications and recent breakthroughs.",
|
|
606
|
+
action: 'refine_research_query',
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
] as ActionExample[][],
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Get research report
|
|
615
|
+
*/
|
|
616
|
+
export const getResearchReportAction: Action = {
|
|
617
|
+
name: 'get_research_report',
|
|
618
|
+
description: 'Get the comprehensive research report with citations',
|
|
619
|
+
|
|
620
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
621
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
622
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
623
|
+
const __avKeywords = ['get', 'research', 'report'];
|
|
624
|
+
const __avKeywordOk =
|
|
625
|
+
__avKeywords.length > 0 &&
|
|
626
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
627
|
+
const __avRegex = new RegExp('\\b(?:get|research|report)\\b', 'i');
|
|
628
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
629
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
630
|
+
const __avExpectedSource = '';
|
|
631
|
+
const __avSourceOk = __avExpectedSource
|
|
632
|
+
? __avSource === __avExpectedSource
|
|
633
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
634
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
635
|
+
const __avInputOk =
|
|
636
|
+
__avText.trim().length > 0 ||
|
|
637
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
638
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
639
|
+
|
|
640
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
645
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
646
|
+
if (!researchService) return false;
|
|
647
|
+
|
|
648
|
+
const projects = await researchService.getAllProjects();
|
|
649
|
+
return projects.some((p: ResearchProject) => p.status === ResearchStatus.COMPLETED);
|
|
650
|
+
};
|
|
651
|
+
try {
|
|
652
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
653
|
+
} catch {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
async handler(
|
|
659
|
+
runtime: IAgentRuntime,
|
|
660
|
+
message: Memory,
|
|
661
|
+
state?: State,
|
|
662
|
+
options?: Record<string, unknown>,
|
|
663
|
+
callback?: HandlerCallback
|
|
664
|
+
): Promise<ActionResult> {
|
|
665
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
666
|
+
if (!researchService) {
|
|
667
|
+
throw new Error('Research service not available');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
try {
|
|
671
|
+
const allProjects = await researchService.getAllProjects();
|
|
672
|
+
const completedProjects = allProjects.filter((p: ResearchProject) => p.status === ResearchStatus.COMPLETED);
|
|
673
|
+
|
|
674
|
+
if (completedProjects.length === 0) {
|
|
675
|
+
return {
|
|
676
|
+
success: false,
|
|
677
|
+
error: 'No completed research projects found',
|
|
678
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
679
|
+
metadata: {},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const project = completedProjects[completedProjects.length - 1];
|
|
684
|
+
if (!project.report) {
|
|
685
|
+
return {
|
|
686
|
+
success: false,
|
|
687
|
+
error: 'Report generation in progress, please try again shortly',
|
|
688
|
+
nextActions: ['check_research_status'],
|
|
689
|
+
metadata: { projectId: project.id },
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Format the report
|
|
694
|
+
let reportText = `# ${project.report.title}\n\n`;
|
|
695
|
+
reportText += `**Generated:** ${new Date(project.report.generatedAt).toLocaleString()}\n`;
|
|
696
|
+
reportText += `**Domain:** ${project.metadata.domain}\n`;
|
|
697
|
+
reportText += `**Word Count:** ${project.report.wordCount.toLocaleString()}\n`;
|
|
698
|
+
reportText += `**Reading Time:** ${project.report.readingTime} minutes\n\n`;
|
|
699
|
+
|
|
700
|
+
// Add evaluation scores if available
|
|
701
|
+
if (project.report.evaluationMetrics) {
|
|
702
|
+
const race = project.report.evaluationMetrics.raceScore;
|
|
703
|
+
const fact = project.report.evaluationMetrics.factScore;
|
|
704
|
+
|
|
705
|
+
reportText += `## Quality Metrics\n`;
|
|
706
|
+
reportText += `**RACE Score:** ${(race.overall * 100).toFixed(1)}%\n`;
|
|
707
|
+
reportText += `**FACT Score:** ${(fact.citationAccuracy * 100).toFixed(1)}% accuracy\n\n`;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
reportText += `## Executive Summary\n\n${project.report.summary}\n\n`;
|
|
711
|
+
|
|
712
|
+
// Add sections
|
|
713
|
+
for (const section of project.report.sections) {
|
|
714
|
+
reportText += `## ${section.heading}\n\n`;
|
|
715
|
+
reportText += `${section.content}\n\n`;
|
|
716
|
+
|
|
717
|
+
if (section.subsections) {
|
|
718
|
+
for (const subsection of section.subsections) {
|
|
719
|
+
reportText += `### ${subsection.heading}\n\n`;
|
|
720
|
+
reportText += `${subsection.content}\n\n`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Add references
|
|
726
|
+
reportText += `## References (${project.report.citations.length})\n\n`;
|
|
727
|
+
const bibliography = project.report.bibliography || [];
|
|
728
|
+
bibliography.forEach((entry: any, idx: number) => {
|
|
729
|
+
reportText += `${idx + 1}. ${entry.citation}\n`;
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const response = {
|
|
733
|
+
text: reportText,
|
|
734
|
+
metadata: { project, report: project.report },
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
if (callback) await callback(response);
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
success: true,
|
|
741
|
+
data: { project, report: project.report },
|
|
742
|
+
nextActions: ['evaluate_research', 'export_research', 'compare_research', 'start_research'],
|
|
743
|
+
metadata: {
|
|
744
|
+
projectId: project.id,
|
|
745
|
+
wordCount: project.report.wordCount,
|
|
746
|
+
citationCount: project.report.citations.length,
|
|
747
|
+
},
|
|
748
|
+
};
|
|
749
|
+
} catch (error) {
|
|
750
|
+
elizaLogger.error('Failed to get research report:', error);
|
|
751
|
+
return {
|
|
752
|
+
success: false,
|
|
753
|
+
error: error instanceof Error ? error.message : String(error),
|
|
754
|
+
nextActions: ['check_research_status'],
|
|
755
|
+
metadata: {},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
examples: [
|
|
761
|
+
[
|
|
762
|
+
{
|
|
763
|
+
name: '{{user}}',
|
|
764
|
+
content: {
|
|
765
|
+
text: 'Show me the research report',
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
name: '{{assistant}}',
|
|
770
|
+
content: {
|
|
771
|
+
text: "I'll retrieve the comprehensive research report for you.",
|
|
772
|
+
action: 'get_research_report',
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
] as ActionExample[][],
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Evaluate research quality
|
|
781
|
+
*/
|
|
782
|
+
export const evaluateResearchAction: Action = {
|
|
783
|
+
name: 'evaluate_research',
|
|
784
|
+
description: 'Evaluate research quality using RACE and FACT frameworks',
|
|
785
|
+
|
|
786
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
787
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
788
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
789
|
+
const __avKeywords = ['evaluate', 'research'];
|
|
790
|
+
const __avKeywordOk =
|
|
791
|
+
__avKeywords.length > 0 &&
|
|
792
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
793
|
+
const __avRegex = new RegExp('\\b(?:evaluate|research)\\b', 'i');
|
|
794
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
795
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
796
|
+
const __avExpectedSource = '';
|
|
797
|
+
const __avSourceOk = __avExpectedSource
|
|
798
|
+
? __avSource === __avExpectedSource
|
|
799
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
800
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
801
|
+
const __avInputOk =
|
|
802
|
+
__avText.trim().length > 0 ||
|
|
803
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
804
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
805
|
+
|
|
806
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
811
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
812
|
+
if (!researchService) return false;
|
|
813
|
+
|
|
814
|
+
const projects = await researchService.getAllProjects();
|
|
815
|
+
return projects.some((p: ResearchProject) => p.status === ResearchStatus.COMPLETED && p.report);
|
|
816
|
+
};
|
|
817
|
+
try {
|
|
818
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
819
|
+
} catch {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
async handler(
|
|
825
|
+
runtime: IAgentRuntime,
|
|
826
|
+
message: Memory,
|
|
827
|
+
state?: State,
|
|
828
|
+
options?: Record<string, unknown>,
|
|
829
|
+
callback?: HandlerCallback
|
|
830
|
+
): Promise<ActionResult> {
|
|
831
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
832
|
+
if (!researchService) {
|
|
833
|
+
throw new Error('Research service not available');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
try {
|
|
837
|
+
const allProjects = await researchService.getAllProjects();
|
|
838
|
+
const evaluableProjects = allProjects.filter(
|
|
839
|
+
(p: ResearchProject) => p.status === ResearchStatus.COMPLETED && p.report && !p.evaluationResults
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
if (evaluableProjects.length === 0) {
|
|
843
|
+
const alreadyEvaluated = allProjects.filter((p: ResearchProject) => p.evaluationResults);
|
|
844
|
+
if (alreadyEvaluated.length > 0) {
|
|
845
|
+
const project = alreadyEvaluated[alreadyEvaluated.length - 1];
|
|
846
|
+
const evaluation = project.evaluationResults!;
|
|
847
|
+
|
|
848
|
+
const response = {
|
|
849
|
+
text: `This research has already been evaluated:
|
|
850
|
+
|
|
851
|
+
**Overall Score:** ${(evaluation.overallScore * 100).toFixed(1)}%
|
|
852
|
+
|
|
853
|
+
**RACE Scores:**
|
|
854
|
+
- Comprehensiveness: ${(evaluation.raceEvaluation.scores.comprehensiveness * 100).toFixed(1)}%
|
|
855
|
+
- Depth: ${(evaluation.raceEvaluation.scores.depth * 100).toFixed(1)}%
|
|
856
|
+
- Instruction Following: ${(evaluation.raceEvaluation.scores.instructionFollowing * 100).toFixed(1)}%
|
|
857
|
+
- Readability: ${(evaluation.raceEvaluation.scores.readability * 100).toFixed(1)}%
|
|
858
|
+
|
|
859
|
+
**FACT Scores:**
|
|
860
|
+
- Citation Accuracy: ${(evaluation.factEvaluation.scores.citationAccuracy * 100).toFixed(1)}%
|
|
861
|
+
- Effective Citations: ${evaluation.factEvaluation.scores.effectiveCitations}
|
|
862
|
+
- Source Credibility: ${(evaluation.factEvaluation.scores.sourceCredibility * 100).toFixed(1)}%`,
|
|
863
|
+
metadata: { evaluation },
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
if (callback) await callback(response);
|
|
867
|
+
|
|
868
|
+
return {
|
|
869
|
+
success: true,
|
|
870
|
+
data: evaluation,
|
|
871
|
+
nextActions: ['export_research', 'compare_research', 'start_research'],
|
|
872
|
+
metadata: { projectId: project.id },
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return {
|
|
877
|
+
success: false,
|
|
878
|
+
error: 'No completed research projects available for evaluation',
|
|
879
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
880
|
+
metadata: {},
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const project = evaluableProjects[0];
|
|
885
|
+
elizaLogger.info(`Starting evaluation for project ${project.id}`);
|
|
886
|
+
|
|
887
|
+
// Run evaluation
|
|
888
|
+
const evaluation = await researchService.evaluateProject(project.id);
|
|
889
|
+
|
|
890
|
+
const response = {
|
|
891
|
+
text: `# Research Evaluation Complete
|
|
892
|
+
|
|
893
|
+
**Overall Score:** ${(evaluation.overallScore * 100).toFixed(1)}%
|
|
894
|
+
|
|
895
|
+
## RACE Evaluation (Research Quality)
|
|
896
|
+
- **Comprehensiveness:** ${(evaluation.raceEvaluation.scores.comprehensiveness * 100).toFixed(1)}%
|
|
897
|
+
- **Depth:** ${(evaluation.raceEvaluation.scores.depth * 100).toFixed(1)}%
|
|
898
|
+
- **Instruction Following:** ${(evaluation.raceEvaluation.scores.instructionFollowing * 100).toFixed(1)}%
|
|
899
|
+
- **Readability:** ${(evaluation.raceEvaluation.scores.readability * 100).toFixed(1)}%
|
|
900
|
+
|
|
901
|
+
## FACT Evaluation (Citation Quality)
|
|
902
|
+
- **Citation Accuracy:** ${(evaluation.factEvaluation.scores.citationAccuracy * 100).toFixed(1)}%
|
|
903
|
+
- **Verified Citations:** ${evaluation.factEvaluation.scores.verifiedCitations}/${evaluation.factEvaluation.scores.totalCitations}
|
|
904
|
+
- **Source Credibility:** ${(evaluation.factEvaluation.scores.sourceCredibility * 100).toFixed(1)}%
|
|
905
|
+
|
|
906
|
+
## Recommendations
|
|
907
|
+
${evaluation.recommendations.map((r: string, i: number) => `${i + 1}. ${r}`).join('\n')}`,
|
|
908
|
+
metadata: { project, evaluation },
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
if (callback) await callback(response);
|
|
912
|
+
|
|
913
|
+
return {
|
|
914
|
+
success: true,
|
|
915
|
+
data: evaluation,
|
|
916
|
+
nextActions: ['export_research', 'compare_research', 'start_research'],
|
|
917
|
+
metadata: {
|
|
918
|
+
projectId: project.id,
|
|
919
|
+
overallScore: evaluation.overallScore,
|
|
920
|
+
},
|
|
921
|
+
};
|
|
922
|
+
} catch (error) {
|
|
923
|
+
elizaLogger.error('Failed to evaluate research:', error);
|
|
924
|
+
return {
|
|
925
|
+
success: false,
|
|
926
|
+
error: error instanceof Error ? error.message : String(error),
|
|
927
|
+
nextActions: ['get_research_report'],
|
|
928
|
+
metadata: {},
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
examples: [
|
|
934
|
+
[
|
|
935
|
+
{
|
|
936
|
+
name: '{{user}}',
|
|
937
|
+
content: {
|
|
938
|
+
text: 'Evaluate the quality of my research',
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: '{{assistant}}',
|
|
943
|
+
content: {
|
|
944
|
+
text: "I'll evaluate your research using RACE and FACT frameworks.",
|
|
945
|
+
action: 'evaluate_research',
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
] as ActionExample[][],
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Export research in various formats
|
|
954
|
+
*/
|
|
955
|
+
export const exportResearchAction: Action = {
|
|
956
|
+
name: 'export_research',
|
|
957
|
+
description: 'Export research report in various formats including DeepResearch Bench',
|
|
958
|
+
|
|
959
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
960
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
961
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
962
|
+
const __avKeywords = ['export', 'research'];
|
|
963
|
+
const __avKeywordOk =
|
|
964
|
+
__avKeywords.length > 0 &&
|
|
965
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
966
|
+
const __avRegex = new RegExp('\\b(?:export|research)\\b', 'i');
|
|
967
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
968
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
969
|
+
const __avExpectedSource = '';
|
|
970
|
+
const __avSourceOk = __avExpectedSource
|
|
971
|
+
? __avSource === __avExpectedSource
|
|
972
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
973
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
974
|
+
const __avInputOk =
|
|
975
|
+
__avText.trim().length > 0 ||
|
|
976
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
977
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
978
|
+
|
|
979
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
984
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
985
|
+
if (!researchService) return false;
|
|
986
|
+
|
|
987
|
+
const projects = await researchService.getAllProjects();
|
|
988
|
+
return projects.some((p: ResearchProject) => p.status === ResearchStatus.COMPLETED && p.report);
|
|
989
|
+
};
|
|
990
|
+
try {
|
|
991
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
992
|
+
} catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
|
|
997
|
+
async handler(
|
|
998
|
+
runtime: IAgentRuntime,
|
|
999
|
+
message: Memory,
|
|
1000
|
+
state?: State,
|
|
1001
|
+
options?: Record<string, unknown>,
|
|
1002
|
+
callback?: HandlerCallback
|
|
1003
|
+
): Promise<ActionResult> {
|
|
1004
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1005
|
+
if (!researchService) {
|
|
1006
|
+
throw new Error('Research service not available');
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
// Extract format from message
|
|
1011
|
+
const formatMatch = message.content.text?.match(
|
|
1012
|
+
/\b(json|markdown|deepresearch|pdf|latex|docx)\b/i
|
|
1013
|
+
);
|
|
1014
|
+
const format = (formatMatch?.[1]?.toLowerCase() || 'markdown') as any;
|
|
1015
|
+
|
|
1016
|
+
const allProjects = await researchService.getAllProjects();
|
|
1017
|
+
const exportableProjects = allProjects.filter(
|
|
1018
|
+
(p: ResearchProject) => p.status === ResearchStatus.COMPLETED && p.report
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
if (exportableProjects.length === 0) {
|
|
1022
|
+
return {
|
|
1023
|
+
success: false,
|
|
1024
|
+
error: 'No completed research projects available for export',
|
|
1025
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
1026
|
+
metadata: {},
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const project = exportableProjects[exportableProjects.length - 1];
|
|
1031
|
+
const exported = await researchService.exportProject(project.id, format);
|
|
1032
|
+
|
|
1033
|
+
let responseText = `Research exported successfully in ${format.toUpperCase()} format.\n\n`;
|
|
1034
|
+
|
|
1035
|
+
if (format === 'deepresearch') {
|
|
1036
|
+
responseText += `**DeepResearch Bench Format:**
|
|
1037
|
+
- ID: ${project.id}
|
|
1038
|
+
- Domain: ${project.metadata.domain}
|
|
1039
|
+
- Task Type: ${project.metadata.taskType}
|
|
1040
|
+
- Ready for submission to DeepResearch Bench evaluation\n\n`;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// For text formats, include a preview
|
|
1044
|
+
if (['json', 'markdown'].includes(format)) {
|
|
1045
|
+
const preview = exported.substring(0, 500);
|
|
1046
|
+
responseText += `**Preview:**\n\`\`\`${format}\n${preview}...\n\`\`\``;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const response = {
|
|
1050
|
+
text: responseText,
|
|
1051
|
+
metadata: {
|
|
1052
|
+
project,
|
|
1053
|
+
format,
|
|
1054
|
+
exported: format === 'json' || format === 'markdown' ? exported : '[Binary data]',
|
|
1055
|
+
},
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
if (callback) await callback(response);
|
|
1059
|
+
|
|
1060
|
+
return {
|
|
1061
|
+
success: true,
|
|
1062
|
+
data: { format, content: exported },
|
|
1063
|
+
nextActions: ['compare_research', 'start_research'],
|
|
1064
|
+
metadata: {
|
|
1065
|
+
projectId: project.id,
|
|
1066
|
+
format,
|
|
1067
|
+
size: exported.length,
|
|
1068
|
+
},
|
|
1069
|
+
};
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
elizaLogger.error('Failed to export research:', error);
|
|
1072
|
+
return {
|
|
1073
|
+
success: false,
|
|
1074
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1075
|
+
nextActions: ['get_research_report'],
|
|
1076
|
+
metadata: {},
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
1080
|
+
|
|
1081
|
+
examples: [
|
|
1082
|
+
[
|
|
1083
|
+
{
|
|
1084
|
+
name: '{{user}}',
|
|
1085
|
+
content: {
|
|
1086
|
+
text: 'Export my research in DeepResearch format',
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
name: '{{assistant}}',
|
|
1091
|
+
content: {
|
|
1092
|
+
text: "I'll export your research in DeepResearch Bench format.",
|
|
1093
|
+
action: 'export_research',
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
1097
|
+
] as ActionExample[][],
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Compare multiple research projects
|
|
1102
|
+
*/
|
|
1103
|
+
export const compareResearchAction: Action = {
|
|
1104
|
+
name: 'compare_research',
|
|
1105
|
+
description: 'Compare multiple research projects on similar topics',
|
|
1106
|
+
|
|
1107
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
1108
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
1109
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
1110
|
+
const __avKeywords = ['compare', 'research'];
|
|
1111
|
+
const __avKeywordOk =
|
|
1112
|
+
__avKeywords.length > 0 &&
|
|
1113
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
1114
|
+
const __avRegex = new RegExp('\\b(?:compare|research)\\b', 'i');
|
|
1115
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
1116
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
1117
|
+
const __avExpectedSource = '';
|
|
1118
|
+
const __avSourceOk = __avExpectedSource
|
|
1119
|
+
? __avSource === __avExpectedSource
|
|
1120
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
1121
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
1122
|
+
const __avInputOk =
|
|
1123
|
+
__avText.trim().length > 0 ||
|
|
1124
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
1125
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
1126
|
+
|
|
1127
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
1132
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1133
|
+
if (!researchService) return false;
|
|
1134
|
+
|
|
1135
|
+
const projects = await researchService.getAllProjects();
|
|
1136
|
+
const completedProjects = projects.filter((p: ResearchProject) => p.status === ResearchStatus.COMPLETED);
|
|
1137
|
+
return completedProjects.length >= 2;
|
|
1138
|
+
};
|
|
1139
|
+
try {
|
|
1140
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
1141
|
+
} catch {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1146
|
+
async handler(
|
|
1147
|
+
runtime: IAgentRuntime,
|
|
1148
|
+
message: Memory,
|
|
1149
|
+
state?: State,
|
|
1150
|
+
options?: Record<string, unknown>,
|
|
1151
|
+
callback?: HandlerCallback
|
|
1152
|
+
): Promise<ActionResult> {
|
|
1153
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1154
|
+
if (!researchService) {
|
|
1155
|
+
throw new Error('Research service not available');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
try {
|
|
1159
|
+
const allProjects = await researchService.getAllProjects();
|
|
1160
|
+
const completedProjects = allProjects.filter((p: ResearchProject) => p.status === ResearchStatus.COMPLETED);
|
|
1161
|
+
|
|
1162
|
+
if (completedProjects.length < 2) {
|
|
1163
|
+
return {
|
|
1164
|
+
success: false,
|
|
1165
|
+
error: 'Need at least 2 completed research projects to compare',
|
|
1166
|
+
nextActions: ['start_research', 'check_research_status'],
|
|
1167
|
+
metadata: { availableProjects: completedProjects.length },
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Get the most recent projects or specific ones mentioned
|
|
1172
|
+
const projectsToCompare = completedProjects.slice(-2);
|
|
1173
|
+
const projectIds = projectsToCompare.map((p: ResearchProject) => p.id);
|
|
1174
|
+
|
|
1175
|
+
const comparison = await researchService.compareProjects(projectIds);
|
|
1176
|
+
|
|
1177
|
+
const response = {
|
|
1178
|
+
text: `# Research Comparison
|
|
1179
|
+
|
|
1180
|
+
## Projects Compared:
|
|
1181
|
+
${projectsToCompare.map((p: ResearchProject, i: number) => `${i + 1}. **${p.query}** (${p.metadata.domain})`).join('\n')}
|
|
1182
|
+
|
|
1183
|
+
## Similarity Score: ${(comparison.similarity * 100).toFixed(1)}%
|
|
1184
|
+
|
|
1185
|
+
## Key Differences:
|
|
1186
|
+
${comparison.differences.map((d: string, i: number) => `${i + 1}. ${d}`).join('\n')}
|
|
1187
|
+
|
|
1188
|
+
## Unique Insights:
|
|
1189
|
+
${Object.entries(comparison.uniqueInsights)
|
|
1190
|
+
.map(([id, insights]) => {
|
|
1191
|
+
const project = projectsToCompare.find((p: ResearchProject) => p.id === id);
|
|
1192
|
+
return `\n**${project?.query}:**\n${(insights as string[]).map((insight, i) => `- ${insight}`).join('\n')}`;
|
|
1193
|
+
})
|
|
1194
|
+
.join('\n')}
|
|
1195
|
+
|
|
1196
|
+
## Quality Comparison:
|
|
1197
|
+
${comparison.qualityComparison.map((q: any) => `- ${q.metric}: ${q.comparison}`).join('\n')}
|
|
1198
|
+
|
|
1199
|
+
## Recommendation:
|
|
1200
|
+
${comparison.recommendation}`,
|
|
1201
|
+
metadata: { comparison, projects: projectsToCompare },
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
if (callback) await callback(response);
|
|
1205
|
+
|
|
1206
|
+
return {
|
|
1207
|
+
success: true,
|
|
1208
|
+
data: comparison,
|
|
1209
|
+
nextActions: ['start_research', 'export_research'],
|
|
1210
|
+
metadata: {
|
|
1211
|
+
projectIds,
|
|
1212
|
+
similarity: comparison.similarity,
|
|
1213
|
+
},
|
|
1214
|
+
};
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
elizaLogger.error('Failed to compare research:', error);
|
|
1217
|
+
return {
|
|
1218
|
+
success: false,
|
|
1219
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1220
|
+
nextActions: ['check_research_status'],
|
|
1221
|
+
metadata: {},
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
|
|
1226
|
+
examples: [
|
|
1227
|
+
[
|
|
1228
|
+
{
|
|
1229
|
+
name: '{{user}}',
|
|
1230
|
+
content: {
|
|
1231
|
+
text: 'Compare my recent research projects',
|
|
1232
|
+
},
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
name: '{{assistant}}',
|
|
1236
|
+
content: {
|
|
1237
|
+
text: "I'll compare your recent research projects for similarities and differences.",
|
|
1238
|
+
action: 'compare_research',
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
],
|
|
1242
|
+
] as ActionExample[][],
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Pause ongoing research
|
|
1247
|
+
*/
|
|
1248
|
+
export const pauseResearchAction: Action = {
|
|
1249
|
+
name: 'pause_research',
|
|
1250
|
+
description: 'Pause an active research project',
|
|
1251
|
+
|
|
1252
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
1253
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
1254
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
1255
|
+
const __avKeywords = ['pause', 'research'];
|
|
1256
|
+
const __avKeywordOk =
|
|
1257
|
+
__avKeywords.length > 0 &&
|
|
1258
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
1259
|
+
const __avRegex = new RegExp('\\b(?:pause|research)\\b', 'i');
|
|
1260
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
1261
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
1262
|
+
const __avExpectedSource = '';
|
|
1263
|
+
const __avSourceOk = __avExpectedSource
|
|
1264
|
+
? __avSource === __avExpectedSource
|
|
1265
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
1266
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
1267
|
+
const __avInputOk =
|
|
1268
|
+
__avText.trim().length > 0 ||
|
|
1269
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
1270
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
1271
|
+
|
|
1272
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
1277
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1278
|
+
if (!researchService) return false;
|
|
1279
|
+
|
|
1280
|
+
const activeProjects = await researchService.getActiveProjects();
|
|
1281
|
+
return activeProjects.length > 0;
|
|
1282
|
+
};
|
|
1283
|
+
try {
|
|
1284
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
1285
|
+
} catch {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1290
|
+
async handler(
|
|
1291
|
+
runtime: IAgentRuntime,
|
|
1292
|
+
message: Memory,
|
|
1293
|
+
state?: State,
|
|
1294
|
+
options?: Record<string, unknown>,
|
|
1295
|
+
callback?: HandlerCallback
|
|
1296
|
+
): Promise<ActionResult> {
|
|
1297
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1298
|
+
if (!researchService) {
|
|
1299
|
+
throw new Error('Research service not available');
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
try {
|
|
1303
|
+
const activeProjects = await researchService.getActiveProjects();
|
|
1304
|
+
if (activeProjects.length === 0) {
|
|
1305
|
+
return {
|
|
1306
|
+
success: false,
|
|
1307
|
+
error: 'No active research projects to pause',
|
|
1308
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
1309
|
+
metadata: {},
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const project = activeProjects[activeProjects.length - 1];
|
|
1314
|
+
await researchService.pauseResearch(project.id);
|
|
1315
|
+
|
|
1316
|
+
const response = {
|
|
1317
|
+
text: `Research project paused successfully.
|
|
1318
|
+
|
|
1319
|
+
**Project:** ${project.query}
|
|
1320
|
+
**Phase:** ${project.phase}
|
|
1321
|
+
**Progress:** ${project.sources.length} sources, ${project.findings.length} findings
|
|
1322
|
+
|
|
1323
|
+
You can resume this research at any time.`,
|
|
1324
|
+
metadata: { project },
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
if (callback) await callback(response);
|
|
1328
|
+
|
|
1329
|
+
return {
|
|
1330
|
+
success: true,
|
|
1331
|
+
data: project,
|
|
1332
|
+
nextActions: ['resume_research', 'check_research_status'],
|
|
1333
|
+
metadata: { projectId: project.id },
|
|
1334
|
+
};
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
elizaLogger.error('Failed to pause research:', error);
|
|
1337
|
+
return {
|
|
1338
|
+
success: false,
|
|
1339
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1340
|
+
nextActions: ['check_research_status'],
|
|
1341
|
+
metadata: {},
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
|
|
1346
|
+
examples: [
|
|
1347
|
+
[
|
|
1348
|
+
{
|
|
1349
|
+
name: '{{user}}',
|
|
1350
|
+
content: {
|
|
1351
|
+
text: 'Pause the current research',
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
name: '{{assistant}}',
|
|
1356
|
+
content: {
|
|
1357
|
+
text: "I'll pause the active research project.",
|
|
1358
|
+
action: 'pause_research',
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
],
|
|
1362
|
+
] as ActionExample[][],
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Resume paused research
|
|
1367
|
+
*/
|
|
1368
|
+
export const resumeResearchAction: Action = {
|
|
1369
|
+
name: 'resume_research',
|
|
1370
|
+
description: 'Resume a paused research project',
|
|
1371
|
+
|
|
1372
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
1373
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
1374
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
1375
|
+
const __avKeywords = ['resume', 'research'];
|
|
1376
|
+
const __avKeywordOk =
|
|
1377
|
+
__avKeywords.length > 0 &&
|
|
1378
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
1379
|
+
const __avRegex = new RegExp('\\b(?:resume|research)\\b', 'i');
|
|
1380
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
1381
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
1382
|
+
const __avExpectedSource = '';
|
|
1383
|
+
const __avSourceOk = __avExpectedSource
|
|
1384
|
+
? __avSource === __avExpectedSource
|
|
1385
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
1386
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
1387
|
+
const __avInputOk =
|
|
1388
|
+
__avText.trim().length > 0 ||
|
|
1389
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
1390
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
1391
|
+
|
|
1392
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
1397
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1398
|
+
if (!researchService) return false;
|
|
1399
|
+
|
|
1400
|
+
const allProjects = await researchService.getAllProjects();
|
|
1401
|
+
return allProjects.some((p: ResearchProject) => p.status === ResearchStatus.PAUSED);
|
|
1402
|
+
};
|
|
1403
|
+
try {
|
|
1404
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
1405
|
+
} catch {
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
|
|
1410
|
+
async handler(
|
|
1411
|
+
runtime: IAgentRuntime,
|
|
1412
|
+
message: Memory,
|
|
1413
|
+
state?: State,
|
|
1414
|
+
options?: Record<string, unknown>,
|
|
1415
|
+
callback?: HandlerCallback
|
|
1416
|
+
): Promise<ActionResult> {
|
|
1417
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1418
|
+
if (!researchService) {
|
|
1419
|
+
throw new Error('Research service not available');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
try {
|
|
1423
|
+
const allProjects = await researchService.getAllProjects();
|
|
1424
|
+
const pausedProjects = allProjects.filter((p: ResearchProject) => p.status === ResearchStatus.PAUSED);
|
|
1425
|
+
|
|
1426
|
+
if (pausedProjects.length === 0) {
|
|
1427
|
+
return {
|
|
1428
|
+
success: false,
|
|
1429
|
+
error: 'No paused research projects to resume',
|
|
1430
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
1431
|
+
metadata: {},
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
const project = pausedProjects[pausedProjects.length - 1];
|
|
1436
|
+
await researchService.resumeResearch(project.id);
|
|
1437
|
+
|
|
1438
|
+
const response = {
|
|
1439
|
+
text: `Research project resumed successfully.
|
|
1440
|
+
|
|
1441
|
+
**Project:** ${project.query}
|
|
1442
|
+
**Resuming from:** ${project.phase}
|
|
1443
|
+
**Current progress:** ${project.sources.length} sources, ${project.findings.length} findings
|
|
1444
|
+
|
|
1445
|
+
The research will continue from where it left off.`,
|
|
1446
|
+
metadata: { project },
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
if (callback) await callback(response);
|
|
1450
|
+
|
|
1451
|
+
return {
|
|
1452
|
+
success: true,
|
|
1453
|
+
data: project,
|
|
1454
|
+
nextActions: ['check_research_status', 'pause_research', 'refine_research_query'],
|
|
1455
|
+
metadata: { projectId: project.id },
|
|
1456
|
+
};
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
elizaLogger.error('Failed to resume research:', error);
|
|
1459
|
+
return {
|
|
1460
|
+
success: false,
|
|
1461
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1462
|
+
nextActions: ['check_research_status'],
|
|
1463
|
+
metadata: {},
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
examples: [
|
|
1469
|
+
[
|
|
1470
|
+
{
|
|
1471
|
+
name: '{{user}}',
|
|
1472
|
+
content: {
|
|
1473
|
+
text: 'Resume the paused research',
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: '{{assistant}}',
|
|
1478
|
+
content: {
|
|
1479
|
+
text: "I'll resume the paused research project.",
|
|
1480
|
+
action: 'resume_research',
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
],
|
|
1484
|
+
] as ActionExample[][],
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* Cancel a research project
|
|
1489
|
+
*/
|
|
1490
|
+
export const cancelResearchAction: Action = {
|
|
1491
|
+
name: 'cancel_research',
|
|
1492
|
+
description: 'Cancel an active or paused research project',
|
|
1493
|
+
|
|
1494
|
+
validate: async (runtime: any, message: any, state?: any, options?: any): Promise<boolean> => {
|
|
1495
|
+
const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : '';
|
|
1496
|
+
const __avText = __avTextRaw.toLowerCase();
|
|
1497
|
+
const __avKeywords = ['cancel', 'research'];
|
|
1498
|
+
const __avKeywordOk =
|
|
1499
|
+
__avKeywords.length > 0 &&
|
|
1500
|
+
__avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
|
|
1501
|
+
const __avRegex = new RegExp('\\b(?:cancel|research)\\b', 'i');
|
|
1502
|
+
const __avRegexOk = __avRegex.test(__avText);
|
|
1503
|
+
const __avSource = String(message?.content?.source ?? message?.source ?? '');
|
|
1504
|
+
const __avExpectedSource = '';
|
|
1505
|
+
const __avSourceOk = __avExpectedSource
|
|
1506
|
+
? __avSource === __avExpectedSource
|
|
1507
|
+
: Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
|
|
1508
|
+
const __avOptions = options && typeof options === 'object' ? options : {};
|
|
1509
|
+
const __avInputOk =
|
|
1510
|
+
__avText.trim().length > 0 ||
|
|
1511
|
+
Object.keys(__avOptions as Record<string, unknown>).length > 0 ||
|
|
1512
|
+
Boolean(message?.content && typeof message.content === 'object');
|
|
1513
|
+
|
|
1514
|
+
if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const __avLegacyValidate = async (runtime: IAgentRuntime, message: Memory, state?: State) => {
|
|
1519
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1520
|
+
if (!researchService) return false;
|
|
1521
|
+
|
|
1522
|
+
const allProjects = await researchService.getAllProjects();
|
|
1523
|
+
return allProjects.some((p: ResearchProject) =>
|
|
1524
|
+
p.status === ResearchStatus.ACTIVE ||
|
|
1525
|
+
p.status === ResearchStatus.PAUSED ||
|
|
1526
|
+
p.status === ResearchStatus.PENDING
|
|
1527
|
+
);
|
|
1528
|
+
};
|
|
1529
|
+
try {
|
|
1530
|
+
return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options));
|
|
1531
|
+
} catch {
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
},
|
|
1535
|
+
|
|
1536
|
+
async handler(
|
|
1537
|
+
runtime: IAgentRuntime,
|
|
1538
|
+
message: Memory,
|
|
1539
|
+
state?: State,
|
|
1540
|
+
options?: Record<string, unknown>,
|
|
1541
|
+
callback?: HandlerCallback
|
|
1542
|
+
): Promise<ActionResult> {
|
|
1543
|
+
const researchService = runtime.getService<ResearchService>('research');
|
|
1544
|
+
if (!researchService) {
|
|
1545
|
+
throw new Error('Research service not available');
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
try {
|
|
1549
|
+
// Check for specific project ID in message
|
|
1550
|
+
const projectIdMatch = message.content.text?.match(/project[:\s]+([a-zA-Z0-9-]+)/i);
|
|
1551
|
+
let projectToCancel: any = null;
|
|
1552
|
+
|
|
1553
|
+
if (projectIdMatch) {
|
|
1554
|
+
const projectId = projectIdMatch[1];
|
|
1555
|
+
projectToCancel = await researchService.getProject(projectId);
|
|
1556
|
+
|
|
1557
|
+
if (!projectToCancel) {
|
|
1558
|
+
return {
|
|
1559
|
+
success: false,
|
|
1560
|
+
error: `Project ${projectId} not found`,
|
|
1561
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
1562
|
+
metadata: {},
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
} else {
|
|
1566
|
+
// Get all cancellable projects
|
|
1567
|
+
const allProjects = await researchService.getAllProjects();
|
|
1568
|
+
const cancellableProjects = allProjects.filter((p: ResearchProject) =>
|
|
1569
|
+
p.status === ResearchStatus.ACTIVE ||
|
|
1570
|
+
p.status === ResearchStatus.PAUSED ||
|
|
1571
|
+
p.status === ResearchStatus.PENDING
|
|
1572
|
+
);
|
|
1573
|
+
|
|
1574
|
+
if (cancellableProjects.length === 0) {
|
|
1575
|
+
return {
|
|
1576
|
+
success: false,
|
|
1577
|
+
error: 'No active or paused research projects to cancel',
|
|
1578
|
+
nextActions: ['check_research_status', 'start_research'],
|
|
1579
|
+
metadata: {},
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// Cancel the most recent cancellable project
|
|
1584
|
+
projectToCancel = cancellableProjects[cancellableProjects.length - 1];
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Cancel the project by setting status to FAILED
|
|
1588
|
+
projectToCancel.status = ResearchStatus.FAILED;
|
|
1589
|
+
projectToCancel.error = 'Cancelled by user';
|
|
1590
|
+
projectToCancel.updatedAt = Date.now();
|
|
1591
|
+
|
|
1592
|
+
// Stop active research if running
|
|
1593
|
+
await researchService.pauseResearch(projectToCancel.id);
|
|
1594
|
+
|
|
1595
|
+
const response = {
|
|
1596
|
+
text: `Research project cancelled successfully.
|
|
1597
|
+
|
|
1598
|
+
**Project:** ${projectToCancel.query}
|
|
1599
|
+
**Status was:** ${projectToCancel.status}
|
|
1600
|
+
**Phase was:** ${projectToCancel.phase}
|
|
1601
|
+
**Progress:** ${projectToCancel.sources.length} sources collected, ${projectToCancel.findings.length} findings extracted
|
|
1602
|
+
|
|
1603
|
+
The project has been permanently cancelled and cannot be resumed.`,
|
|
1604
|
+
metadata: { project: projectToCancel },
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
if (callback) await callback(response);
|
|
1608
|
+
|
|
1609
|
+
return {
|
|
1610
|
+
success: true,
|
|
1611
|
+
data: projectToCancel,
|
|
1612
|
+
nextActions: ['start_research', 'check_research_status'],
|
|
1613
|
+
metadata: {
|
|
1614
|
+
projectId: projectToCancel.id,
|
|
1615
|
+
cancelledFrom: projectToCancel.status
|
|
1616
|
+
},
|
|
1617
|
+
};
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
elizaLogger.error('Failed to cancel research:', error);
|
|
1620
|
+
return {
|
|
1621
|
+
success: false,
|
|
1622
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1623
|
+
nextActions: ['check_research_status'],
|
|
1624
|
+
metadata: {},
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
},
|
|
1628
|
+
|
|
1629
|
+
examples: [
|
|
1630
|
+
[
|
|
1631
|
+
{
|
|
1632
|
+
name: '{{user}}',
|
|
1633
|
+
content: {
|
|
1634
|
+
text: 'Cancel the current research project',
|
|
1635
|
+
},
|
|
1636
|
+
},
|
|
1637
|
+
{
|
|
1638
|
+
name: '{{assistant}}',
|
|
1639
|
+
content: {
|
|
1640
|
+
text: "I'll cancel the active research project.",
|
|
1641
|
+
action: 'cancel_research',
|
|
1642
|
+
},
|
|
1643
|
+
},
|
|
1644
|
+
],
|
|
1645
|
+
[
|
|
1646
|
+
{
|
|
1647
|
+
name: '{{user}}',
|
|
1648
|
+
content: {
|
|
1649
|
+
text: 'Cancel project: abc-123-def',
|
|
1650
|
+
},
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
name: '{{assistant}}',
|
|
1654
|
+
content: {
|
|
1655
|
+
text: "I'll cancel the specified research project.",
|
|
1656
|
+
action: 'cancel_research',
|
|
1657
|
+
},
|
|
1658
|
+
},
|
|
1659
|
+
],
|
|
1660
|
+
] as ActionExample[][],
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
// Export all actions
|
|
1664
|
+
export const researchActions = [
|
|
1665
|
+
startResearchAction,
|
|
1666
|
+
checkResearchStatusAction,
|
|
1667
|
+
refineResearchQueryAction,
|
|
1668
|
+
getResearchReportAction,
|
|
1669
|
+
evaluateResearchAction,
|
|
1670
|
+
exportResearchAction,
|
|
1671
|
+
compareResearchAction,
|
|
1672
|
+
pauseResearchAction,
|
|
1673
|
+
resumeResearchAction,
|
|
1674
|
+
cancelResearchAction,
|
|
1675
|
+
];
|
|
1676
|
+
|
|
1677
|
+
export const allResearchActions = researchActions;
|