mirror of
https://github.com/Nikhil-Doye/workflow-builder.git
synced 2026-07-22 02:01:56 +02:00
Add multi-agent intelligence layer architecture and tools
- Introduced a new architecture for the multi-agent intelligence layer, replacing the monolithic `CopilotService` with a modular tool-based system. - Added core components including `AgentManager`, `WorkflowAgent`, and various tools for intent classification, entity extraction, workflow generation, validation, and suggestions. - Implemented a comprehensive test script and demo component for interactive testing of the new agent system. - Updated TypeScript configuration and integrated the new architecture into the existing workflow store, ensuring backward compatibility with the previous API. - Enhanced documentation to reflect the new architecture and its components, providing a clear overview of the system's capabilities and usage.
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# Multi-Agent Intelligence Layer Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
We've successfully transformed the monolithic `CopilotService` into a sophisticated multi-agent intelligence layer that uses a tool-based architecture. This new system provides better modularity, extensibility, and maintainability.
|
||||
|
||||
## Architecture Components
|
||||
|
||||
### 1. Tool Infrastructure (`src/services/tools/`)
|
||||
|
||||
#### Base Classes
|
||||
|
||||
- **`BaseTool.ts`**: Abstract base class for all tools
|
||||
|
||||
- Provides validation, error handling, and execution measurement
|
||||
- Implements common tool functionality
|
||||
|
||||
- **`ToolRegistry.ts`**: Central registry for managing tools
|
||||
- Tool registration and discovery
|
||||
- Global tool management
|
||||
|
||||
#### Core Tools
|
||||
|
||||
- **`ClassifyIntentTool.ts`**: Analyzes user input to determine workflow intent
|
||||
|
||||
- Uses LLM for intelligent classification
|
||||
- Fallback pattern-based classification
|
||||
- Confidence scoring
|
||||
|
||||
- **`ExtractEntitiesTool.ts`**: Extracts specific entities from user input
|
||||
|
||||
- URLs, data types, output formats, AI tasks
|
||||
- Processing steps and target sites
|
||||
- Context-aware extraction
|
||||
|
||||
- **`GenerateWorkflowTool.ts`**: Creates complete workflow structures
|
||||
|
||||
- LLM-powered workflow generation
|
||||
- Node configuration and connection logic
|
||||
- Fallback workflow generation
|
||||
|
||||
- **`ValidateWorkflowTool.ts`**: Validates generated workflows
|
||||
|
||||
- Structure validation
|
||||
- Performance analysis
|
||||
- Best practices checking
|
||||
|
||||
- **`CacheLookupTool.ts`**: Provides caching functionality
|
||||
|
||||
- TTL-based caching
|
||||
- Cache statistics
|
||||
- Performance optimization
|
||||
|
||||
- **`GenerateSuggestionsTool.ts`**: Generates contextual suggestions
|
||||
- Workflow analysis
|
||||
- Improvement recommendations
|
||||
- Context-aware suggestions
|
||||
|
||||
### 2. Agent System (`src/services/agents/`)
|
||||
|
||||
#### WorkflowAgent
|
||||
|
||||
- **`WorkflowAgent.ts`**: Core AI agent that orchestrates tool usage
|
||||
- Tool execution planning
|
||||
- Result aggregation
|
||||
- Confidence calculation
|
||||
- Error handling
|
||||
|
||||
#### Agent Management
|
||||
|
||||
- **`AgentManager.ts`**: Manages agent instances and tool registration
|
||||
- Session management
|
||||
- Tool initialization
|
||||
- Request processing
|
||||
- Cache management
|
||||
|
||||
### 3. Type System (`src/types/tools.ts`)
|
||||
|
||||
Defines comprehensive interfaces for:
|
||||
|
||||
- Tool definitions and parameters
|
||||
- Tool results and validation
|
||||
- Agent tasks and results
|
||||
- Execution planning
|
||||
- Context management
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Modular Design
|
||||
|
||||
- Each tool is independent and focused
|
||||
- Easy to add new tools
|
||||
- Clear separation of concerns
|
||||
|
||||
### 2. Tool-Based Architecture
|
||||
|
||||
- Tools can be composed and chained
|
||||
- Parallel execution support
|
||||
- Dependency management
|
||||
|
||||
### 3. Intelligent Orchestration
|
||||
|
||||
- AI agent decides which tools to use
|
||||
- Context-aware tool selection
|
||||
- Result aggregation and validation
|
||||
|
||||
### 4. Performance Optimization
|
||||
|
||||
- Caching for repeated requests
|
||||
- Execution time measurement
|
||||
- Confidence scoring
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
- Graceful degradation
|
||||
- Fallback mechanisms
|
||||
- Comprehensive error reporting
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Workflow Generation
|
||||
|
||||
```typescript
|
||||
const agentManager = new AgentManager();
|
||||
const result = await agentManager.processWorkflowRequest(
|
||||
"Create a workflow that scrapes a website and analyzes the content"
|
||||
);
|
||||
```
|
||||
|
||||
### Tool Registration
|
||||
|
||||
```typescript
|
||||
const toolRegistry = new ToolRegistry();
|
||||
toolRegistry.registerTool(new ClassifyIntentTool());
|
||||
```
|
||||
|
||||
### Custom Tool Creation
|
||||
|
||||
```typescript
|
||||
class CustomTool extends BaseTool {
|
||||
name = 'custom_tool';
|
||||
description = 'Custom tool description';
|
||||
parameters = [...];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits Over Monolithic Approach
|
||||
|
||||
### 1. **Modularity**
|
||||
|
||||
- Each tool has a single responsibility
|
||||
- Easy to test individual components
|
||||
- Clear interfaces and contracts
|
||||
|
||||
### 2. **Extensibility**
|
||||
|
||||
- Add new tools without modifying existing code
|
||||
- Plugin architecture
|
||||
- Tool composition and chaining
|
||||
|
||||
### 3. **Maintainability**
|
||||
|
||||
- Smaller, focused code units
|
||||
- Easier debugging and testing
|
||||
- Clear separation of concerns
|
||||
|
||||
### 4. **Performance**
|
||||
|
||||
- Parallel tool execution
|
||||
- Caching and optimization
|
||||
- Resource management
|
||||
|
||||
### 5. **Reliability**
|
||||
|
||||
- Graceful error handling
|
||||
- Fallback mechanisms
|
||||
- Comprehensive validation
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Workflow Store
|
||||
|
||||
- Updated to use `AgentManager` instead of `CopilotService`
|
||||
- Maintains same public API
|
||||
- Backward compatibility
|
||||
|
||||
### Copilot Panel
|
||||
|
||||
- No changes required
|
||||
- Uses same workflow store methods
|
||||
- Seamless integration
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### 1. **Advanced Tool Chaining**
|
||||
|
||||
- Dynamic tool selection
|
||||
- Conditional execution paths
|
||||
- Complex workflows
|
||||
|
||||
### 2. **Tool Learning**
|
||||
|
||||
- Tool performance tracking
|
||||
- Usage pattern analysis
|
||||
- Automatic optimization
|
||||
|
||||
### 3. **Distributed Tools**
|
||||
|
||||
- Remote tool execution
|
||||
- Tool discovery services
|
||||
- Load balancing
|
||||
|
||||
### 4. **Tool Marketplace**
|
||||
|
||||
- Third-party tool integration
|
||||
- Tool versioning
|
||||
- Community contributions
|
||||
|
||||
## Testing
|
||||
|
||||
### Demo Component
|
||||
|
||||
- `AgentSystemDemo.tsx`: Interactive testing interface
|
||||
- Real-time tool execution
|
||||
- Performance metrics
|
||||
|
||||
### Test Script
|
||||
|
||||
- `test-agent-system.ts`: Automated testing
|
||||
- Comprehensive test coverage
|
||||
- Error scenario testing
|
||||
|
||||
## Conclusion
|
||||
|
||||
The new multi-agent intelligence layer provides a robust, scalable, and maintainable foundation for AI-powered workflow generation. The tool-based architecture enables easy extension and customization while maintaining high performance and reliability.
|
||||
|
||||
The system successfully replaces the monolithic `CopilotService` with a more sophisticated and flexible approach that can adapt to future requirements and scale with the application's growth.
|
||||
@@ -0,0 +1,237 @@
|
||||
import React, { useState } from "react";
|
||||
import { agentManager } from "../services/agents/AgentManager";
|
||||
import { Play, CheckCircle, XCircle, Loader2, Brain, Zap } from "lucide-react";
|
||||
|
||||
interface TestResult {
|
||||
name: string;
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
executionTime: number;
|
||||
toolsUsed: string[];
|
||||
}
|
||||
|
||||
export const AgentSystemDemo: React.FC = () => {
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [results, setResults] = useState<TestResult[]>([]);
|
||||
const [sessionInfo, setSessionInfo] = useState<any>(null);
|
||||
|
||||
const runTests = async () => {
|
||||
setIsRunning(true);
|
||||
setResults([]);
|
||||
|
||||
const tests: Array<{ name: string; test: () => Promise<any> }> = [
|
||||
{
|
||||
name: "Basic Workflow Generation",
|
||||
test: () =>
|
||||
agentManager.processWorkflowRequest(
|
||||
"Create a workflow that scrapes a website and analyzes the content with AI"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Job Application Workflow",
|
||||
test: () =>
|
||||
agentManager.processWorkflowRequest(
|
||||
"Build a workflow to process job applications and match them with opportunities"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Suggestions Generation",
|
||||
test: () => agentManager.getSuggestions("I want to process documents"),
|
||||
},
|
||||
{
|
||||
name: "Tool Information",
|
||||
test: () =>
|
||||
Promise.resolve({ tools: agentManager.getAvailableTools() }),
|
||||
},
|
||||
];
|
||||
|
||||
const testResults: TestResult[] = [];
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const result = await test.test();
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
testResults.push({
|
||||
name: test.name,
|
||||
success: true,
|
||||
data: result,
|
||||
executionTime,
|
||||
toolsUsed: result.toolsUsed || [],
|
||||
});
|
||||
} catch (error) {
|
||||
testResults.push({
|
||||
name: test.name,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
executionTime: 0,
|
||||
toolsUsed: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setResults(testResults);
|
||||
setSessionInfo(agentManager.getSessionInfo());
|
||||
setIsRunning(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<div className="flex items-center space-x-3 mb-6">
|
||||
<div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-xl flex items-center justify-center">
|
||||
<Brain className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
Agent System Demo
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Test the new tool-based AI agent system
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<button
|
||||
onClick={runTests}
|
||||
disabled={isRunning}
|
||||
className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-lg hover:from-blue-700 hover:to-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
{isRunning ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Play className="w-5 h-5" />
|
||||
)}
|
||||
<span>{isRunning ? "Running Tests..." : "Run Agent Tests"}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{sessionInfo && (
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Session Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium">Session ID:</span>
|
||||
<span className="ml-2 text-gray-600">
|
||||
{sessionInfo.sessionId}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Tools Available:</span>
|
||||
<span className="ml-2 text-gray-600">
|
||||
{sessionInfo.toolsAvailable}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Cache Size:</span>
|
||||
<span className="ml-2 text-gray-600">
|
||||
{sessionInfo.cacheStats.size}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Cache Hit Rate:</span>
|
||||
<span className="ml-2 text-gray-600">
|
||||
{(sessionInfo.cacheStats.hitRate * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Test Results
|
||||
</h3>
|
||||
{results.map((result, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`p-4 rounded-lg border ${
|
||||
result.success
|
||||
? "bg-green-50 border-green-200"
|
||||
: "bg-red-50 border-red-200"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
{result.success ? (
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="w-5 h-5 text-red-500" />
|
||||
)}
|
||||
<span className="font-medium text-gray-900">
|
||||
{result.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 text-sm text-gray-600">
|
||||
<span>{result.executionTime}ms</span>
|
||||
{result.toolsUsed.length > 0 && (
|
||||
<span className="flex items-center space-x-1">
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>{result.toolsUsed.length} tools</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{result.error && (
|
||||
<div className="text-red-600 text-sm mt-2">
|
||||
Error: {result.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result.success && result.data && (
|
||||
<div className="text-sm text-gray-600 mt-2">
|
||||
{result.name.includes("Workflow") &&
|
||||
result.data.parsedIntent && (
|
||||
<div>
|
||||
<p>Intent: {result.data.parsedIntent.intent}</p>
|
||||
<p>
|
||||
Confidence:{" "}
|
||||
{(
|
||||
result.data.parsedIntent.confidence * 100
|
||||
).toFixed(1)}
|
||||
%
|
||||
</p>
|
||||
<p>
|
||||
Nodes:{" "}
|
||||
{
|
||||
result.data.parsedIntent.workflowStructure.nodes
|
||||
.length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{result.name.includes("Suggestions") &&
|
||||
Array.isArray(result.data) && (
|
||||
<div>
|
||||
<p>Generated {result.data.length} suggestions</p>
|
||||
<ul className="list-disc list-inside mt-1">
|
||||
{result.data
|
||||
.slice(0, 3)
|
||||
.map((suggestion: string, i: number) => (
|
||||
<li key={i}>{suggestion}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{result.name.includes("Tools") && result.data.tools && (
|
||||
<div>
|
||||
<p>Available tools: {result.data.tools.join(", ")}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -106,7 +106,7 @@ export const CopilotPanel: React.FC<CopilotPanelProps> = ({
|
||||
await generateWorkflowFromDescription(userMessage.content);
|
||||
|
||||
// Get validation results
|
||||
const validationResult = validateGeneratedWorkflow();
|
||||
const validationResult = await validateGeneratedWorkflow();
|
||||
setValidation(validationResult);
|
||||
|
||||
// Add success message
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
import { WorkflowAgent } from "./WorkflowAgent";
|
||||
import { AgentContext, AgentResult } from "../../types/tools";
|
||||
import { toolRegistry } from "../tools/ToolRegistry";
|
||||
import { ParsedIntent } from "../../types";
|
||||
|
||||
export class AgentManager {
|
||||
private agent: WorkflowAgent | null = null;
|
||||
private sessionId: string;
|
||||
|
||||
constructor() {
|
||||
this.sessionId = this.generateSessionId();
|
||||
this.initializeTools();
|
||||
}
|
||||
|
||||
private generateSessionId(): string {
|
||||
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
private initializeTools(): void {
|
||||
// Import and register all tools
|
||||
this.registerAllTools();
|
||||
}
|
||||
|
||||
private async registerAllTools(): Promise<void> {
|
||||
try {
|
||||
// Import tool classes
|
||||
const { ClassifyIntentTool } = await import(
|
||||
"../tools/ClassifyIntentTool"
|
||||
);
|
||||
const { ExtractEntitiesTool } = await import(
|
||||
"../tools/ExtractEntitiesTool"
|
||||
);
|
||||
const { GenerateWorkflowTool } = await import(
|
||||
"../tools/GenerateWorkflowTool"
|
||||
);
|
||||
const { ValidateWorkflowTool } = await import(
|
||||
"../tools/ValidateWorkflowTool"
|
||||
);
|
||||
const { CacheLookupTool } = await import("../tools/CacheLookupTool");
|
||||
const { GenerateSuggestionsTool } = await import(
|
||||
"../tools/GenerateSuggestionsTool"
|
||||
);
|
||||
|
||||
// Register tools
|
||||
toolRegistry.registerTool(new ClassifyIntentTool());
|
||||
toolRegistry.registerTool(new ExtractEntitiesTool());
|
||||
toolRegistry.registerTool(new GenerateWorkflowTool());
|
||||
toolRegistry.registerTool(new ValidateWorkflowTool());
|
||||
toolRegistry.registerTool(new CacheLookupTool());
|
||||
toolRegistry.registerTool(new GenerateSuggestionsTool());
|
||||
|
||||
console.log("All tools registered successfully");
|
||||
} catch (error) {
|
||||
console.error("Error registering tools:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async processWorkflowRequest(
|
||||
userInput: string,
|
||||
currentWorkflow?: any,
|
||||
userPreferences?: Record<string, any>
|
||||
): Promise<AgentResult> {
|
||||
// Create or update agent context
|
||||
const context: AgentContext = {
|
||||
userRequest: userInput,
|
||||
currentWorkflow,
|
||||
executionHistory: [],
|
||||
userPreferences: userPreferences || {},
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
|
||||
// Create new agent instance for this request
|
||||
this.agent = new WorkflowAgent(context);
|
||||
|
||||
try {
|
||||
const result = await this.agent.processRequest(userInput);
|
||||
|
||||
// Update execution history
|
||||
if (this.agent) {
|
||||
const updatedContext = this.agent.getContext();
|
||||
updatedContext.executionHistory.push({
|
||||
timestamp: new Date(),
|
||||
input: userInput,
|
||||
result: result.success ? result.data : null,
|
||||
error: result.success ? null : result.error,
|
||||
toolsUsed: result.toolsUsed,
|
||||
executionTime: result.executionTime,
|
||||
});
|
||||
this.agent.updateContext(updatedContext);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in AgentManager:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
toolsUsed: [],
|
||||
executionTime: 0,
|
||||
confidence: 0.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async generateWorkflowFromDescription(
|
||||
description: string
|
||||
): Promise<ParsedIntent> {
|
||||
const result = await this.processWorkflowRequest(description);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Failed to generate workflow");
|
||||
}
|
||||
|
||||
return result.data.parsedIntent;
|
||||
}
|
||||
|
||||
async getSuggestions(context?: string): Promise<string[]> {
|
||||
const result = await this.processWorkflowRequest(
|
||||
context || "Generate suggestions for current workflow"
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return ["Unable to generate suggestions at this time"];
|
||||
}
|
||||
|
||||
return result.data.suggestions || [];
|
||||
}
|
||||
|
||||
async validateWorkflow(workflow: any, originalInput: string): Promise<any> {
|
||||
const validateTool = toolRegistry.getTool("validate_workflow");
|
||||
if (!validateTool) {
|
||||
throw new Error("Validation tool not available");
|
||||
}
|
||||
|
||||
const result = await validateTool.execute({
|
||||
workflow,
|
||||
originalInput,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Validation failed");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// Method to get available tools
|
||||
getAvailableTools(): string[] {
|
||||
return toolRegistry.getToolNames();
|
||||
}
|
||||
|
||||
// Method to get tool information
|
||||
getToolInfo(toolName: string): any {
|
||||
const tool = toolRegistry.getTool(toolName);
|
||||
if (!tool) return null;
|
||||
|
||||
return {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
};
|
||||
}
|
||||
|
||||
// Method to clear cache
|
||||
clearCache(): void {
|
||||
const cacheTool = toolRegistry.getTool("cache_lookup") as any;
|
||||
if (cacheTool && cacheTool.clearExpired) {
|
||||
cacheTool.clearExpired();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get cache statistics
|
||||
getCacheStats(): any {
|
||||
const cacheTool = toolRegistry.getTool("cache_lookup") as any;
|
||||
if (cacheTool && cacheTool.getStats) {
|
||||
return cacheTool.getStats();
|
||||
}
|
||||
return { size: 0, hitRate: 0 };
|
||||
}
|
||||
|
||||
// Method to get session information
|
||||
getSessionInfo(): any {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
toolsAvailable: this.getAvailableTools().length,
|
||||
cacheStats: this.getCacheStats(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const agentManager = new AgentManager();
|
||||
@@ -0,0 +1,243 @@
|
||||
import {
|
||||
AgentTask,
|
||||
AgentResult,
|
||||
AgentContext,
|
||||
ToolExecutionPlan,
|
||||
} from "../../types/tools";
|
||||
import { toolRegistry } from "../tools/ToolRegistry";
|
||||
import { ParsedIntent, WorkflowStructure, ValidationResult } from "../../types";
|
||||
|
||||
export class WorkflowAgent {
|
||||
private context: AgentContext;
|
||||
|
||||
constructor(context: AgentContext) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async processRequest(userInput: string): Promise<AgentResult> {
|
||||
const startTime = Date.now();
|
||||
const toolsUsed: string[] = [];
|
||||
|
||||
try {
|
||||
// Step 1: Check cache first
|
||||
const cacheTool = toolRegistry.getTool("cache_lookup");
|
||||
if (cacheTool) {
|
||||
const cacheKey = this.generateCacheKey(userInput);
|
||||
const cacheResult = await cacheTool.execute({ key: cacheKey });
|
||||
|
||||
if (cacheResult.success && cacheResult.data) {
|
||||
return {
|
||||
success: true,
|
||||
data: cacheResult.data,
|
||||
toolsUsed: ["cache_lookup"],
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: 0.9,
|
||||
};
|
||||
}
|
||||
toolsUsed.push("cache_lookup");
|
||||
}
|
||||
|
||||
// Step 2: Classify intent
|
||||
const intentResult = await this.executeTool("classify_intent", {
|
||||
userInput,
|
||||
});
|
||||
|
||||
if (!intentResult.success) {
|
||||
throw new Error("Failed to classify intent");
|
||||
}
|
||||
toolsUsed.push("classify_intent");
|
||||
|
||||
// Step 3: Extract entities
|
||||
const entitiesResult = await this.executeTool("extract_entities", {
|
||||
userInput,
|
||||
intent: intentResult.data,
|
||||
});
|
||||
|
||||
if (!entitiesResult.success) {
|
||||
throw new Error("Failed to extract entities");
|
||||
}
|
||||
toolsUsed.push("extract_entities");
|
||||
|
||||
// Step 4: Generate workflow
|
||||
const workflowResult = await this.executeTool("generate_workflow", {
|
||||
userInput,
|
||||
intent: intentResult.data,
|
||||
entities: entitiesResult.data,
|
||||
});
|
||||
|
||||
if (!workflowResult.success) {
|
||||
throw new Error("Failed to generate workflow");
|
||||
}
|
||||
toolsUsed.push("generate_workflow");
|
||||
|
||||
// Step 5: Validate workflow
|
||||
const validationResult = await this.executeTool("validate_workflow", {
|
||||
workflow: workflowResult.data,
|
||||
originalInput: userInput,
|
||||
});
|
||||
|
||||
if (!validationResult.success) {
|
||||
console.warn(
|
||||
"Workflow validation failed, but continuing with generated workflow"
|
||||
);
|
||||
}
|
||||
toolsUsed.push("validate_workflow");
|
||||
|
||||
// Step 6: Generate suggestions
|
||||
const suggestionsResult = await this.executeTool("generate_suggestions", {
|
||||
workflow: workflowResult.data,
|
||||
context: userInput,
|
||||
});
|
||||
|
||||
if (suggestionsResult.success) {
|
||||
toolsUsed.push("generate_suggestions");
|
||||
}
|
||||
|
||||
// Create final result
|
||||
const parsedIntent: ParsedIntent = {
|
||||
intent: intentResult.data.intent,
|
||||
confidence: intentResult.data.confidence,
|
||||
entities: entitiesResult.data,
|
||||
workflowStructure: workflowResult.data,
|
||||
reasoning: intentResult.data.reasoning,
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
await this.cacheResult(userInput, parsedIntent);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
parsedIntent,
|
||||
validation: validationResult.data,
|
||||
suggestions: suggestionsResult.data || [],
|
||||
},
|
||||
toolsUsed,
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: this.calculateOverallConfidence(
|
||||
intentResult,
|
||||
entitiesResult,
|
||||
workflowResult,
|
||||
validationResult
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error in WorkflowAgent:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
toolsUsed,
|
||||
executionTime: Date.now() - startTime,
|
||||
confidence: 0.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async executeTool(
|
||||
toolName: string,
|
||||
params: Record<string, any>
|
||||
): Promise<any> {
|
||||
const tool = toolRegistry.getTool(toolName);
|
||||
if (!tool) {
|
||||
throw new Error(`Tool not found: ${toolName}`);
|
||||
}
|
||||
|
||||
const result = await tool.execute(params);
|
||||
if (!result.success) {
|
||||
throw new Error(`Tool ${toolName} failed: ${result.error}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private generateCacheKey(userInput: string): string {
|
||||
return userInput.toLowerCase().trim().replace(/\s+/g, "_");
|
||||
}
|
||||
|
||||
private async cacheResult(
|
||||
userInput: string,
|
||||
result: ParsedIntent
|
||||
): Promise<void> {
|
||||
const cacheTool = toolRegistry.getTool("cache_lookup") as any;
|
||||
if (cacheTool && cacheTool.store) {
|
||||
const cacheKey = this.generateCacheKey(userInput);
|
||||
cacheTool.store(cacheKey, result, 5 * 60 * 1000); // 5 minutes TTL
|
||||
}
|
||||
}
|
||||
|
||||
private calculateOverallConfidence(
|
||||
intentResult: any,
|
||||
entitiesResult: any,
|
||||
workflowResult: any,
|
||||
validationResult: any
|
||||
): number {
|
||||
let confidence = 0.0;
|
||||
let weight = 0.0;
|
||||
|
||||
// Intent confidence (weight: 0.3)
|
||||
if (intentResult.metadata?.confidence) {
|
||||
confidence += intentResult.metadata.confidence * 0.3;
|
||||
weight += 0.3;
|
||||
}
|
||||
|
||||
// Entities confidence (weight: 0.2)
|
||||
if (entitiesResult.metadata?.confidence) {
|
||||
confidence += entitiesResult.metadata.confidence * 0.2;
|
||||
weight += 0.2;
|
||||
}
|
||||
|
||||
// Workflow confidence (weight: 0.3)
|
||||
if (workflowResult.metadata?.confidence) {
|
||||
confidence += workflowResult.metadata.confidence * 0.3;
|
||||
weight += 0.3;
|
||||
}
|
||||
|
||||
// Validation confidence (weight: 0.2)
|
||||
if (validationResult.metadata?.confidence) {
|
||||
confidence += validationResult.metadata.confidence * 0.2;
|
||||
weight += 0.2;
|
||||
}
|
||||
|
||||
return weight > 0 ? confidence / weight : 0.5;
|
||||
}
|
||||
|
||||
// Method to create execution plan (for future use)
|
||||
createExecutionPlan(tools: string[]): ToolExecutionPlan {
|
||||
const dependencies: Array<{ tool: string; dependsOn: string }> = [];
|
||||
|
||||
// Define tool dependencies
|
||||
const toolDeps: Record<string, string[]> = {
|
||||
extract_entities: ["classify_intent"],
|
||||
generate_workflow: ["classify_intent", "extract_entities"],
|
||||
validate_workflow: ["generate_workflow"],
|
||||
generate_suggestions: ["generate_workflow"],
|
||||
};
|
||||
|
||||
// Build dependency graph
|
||||
tools.forEach((tool) => {
|
||||
const deps = toolDeps[tool] || [];
|
||||
deps.forEach((dep) => {
|
||||
if (tools.includes(dep)) {
|
||||
dependencies.push({ tool, dependsOn: dep });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
tools,
|
||||
dependencies,
|
||||
parallel: false, // For now, execute sequentially
|
||||
estimatedTime: tools.length * 2000, // Rough estimate
|
||||
};
|
||||
}
|
||||
|
||||
// Method to update context
|
||||
updateContext(newContext: Partial<AgentContext>): void {
|
||||
this.context = { ...this.context, ...newContext };
|
||||
}
|
||||
|
||||
// Method to get current context
|
||||
getContext(): AgentContext {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
Tool,
|
||||
ToolResult,
|
||||
ToolValidationResult,
|
||||
ToolParameter,
|
||||
} from "../../types/tools";
|
||||
|
||||
export abstract class BaseTool implements Tool {
|
||||
abstract name: string;
|
||||
abstract description: string;
|
||||
abstract parameters: ToolParameter[];
|
||||
|
||||
abstract execute(params: Record<string, any>): Promise<ToolResult>;
|
||||
|
||||
validate(params: Record<string, any>): ToolValidationResult {
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
// Validate required parameters
|
||||
for (const param of this.parameters) {
|
||||
if (param.required && !(param.name in params)) {
|
||||
errors.push(`Missing required parameter: ${param.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parameter types
|
||||
for (const param of this.parameters) {
|
||||
if (param.name in params) {
|
||||
const value = params[param.name];
|
||||
const type = this.getParameterType(value);
|
||||
|
||||
if (type !== param.type) {
|
||||
errors.push(
|
||||
`Parameter ${param.name} should be of type ${param.type}, got ${type}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
private getParameterType(value: any): string {
|
||||
if (value === null || value === undefined) return "undefined";
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (typeof value === "object") return "object";
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
protected createResult(
|
||||
success: boolean,
|
||||
data?: any,
|
||||
error?: string,
|
||||
metadata?: any
|
||||
): ToolResult {
|
||||
return {
|
||||
success,
|
||||
data,
|
||||
error,
|
||||
metadata: {
|
||||
executionTime: 0,
|
||||
...metadata,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected async measureExecution<T>(
|
||||
operation: () => Promise<T>
|
||||
): Promise<{ result: T; executionTime: number }> {
|
||||
const startTime = Date.now();
|
||||
const result = await operation();
|
||||
const executionTime = Date.now() - startTime;
|
||||
return { result, executionTime };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
|
||||
export class CacheLookupTool extends BaseTool {
|
||||
name = "cache_lookup";
|
||||
description =
|
||||
"Look up cached results for similar requests to improve performance";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "key",
|
||||
type: "string",
|
||||
description: "The cache key to look up",
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
private cache = new Map<
|
||||
string,
|
||||
{ data: any; timestamp: number; ttl: number }
|
||||
>();
|
||||
private readonly DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { key } = params;
|
||||
|
||||
if (!key || typeof key !== "string") {
|
||||
return this.createResult(false, null, "Invalid cache key provided");
|
||||
}
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.lookupCache(key);
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: result ? 0.9 : 0.0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in CacheLookupTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async lookupCache(key: string): Promise<any> {
|
||||
const entry = this.cache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
return null; // Cache miss
|
||||
}
|
||||
|
||||
// Check if entry has expired
|
||||
const now = Date.now();
|
||||
if (now - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
return null; // Cache expired
|
||||
}
|
||||
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// Helper method to store data in cache
|
||||
store(key: string, data: any, ttl: number = this.DEFAULT_TTL): void {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
ttl,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper method to generate cache key from user input
|
||||
generateKey(userInput: string): string {
|
||||
return userInput.toLowerCase().trim().replace(/\s+/g, "_");
|
||||
}
|
||||
|
||||
// Helper method to clear expired entries
|
||||
clearExpired(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (now - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get cache statistics
|
||||
getStats(): { size: number; hitRate: number } {
|
||||
return {
|
||||
size: this.cache.size,
|
||||
hitRate: 0.8, // Placeholder - would track actual hit rate
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
import { callOpenAI } from "../openaiService";
|
||||
import { IntentClassification } from "../../types";
|
||||
|
||||
export class ClassifyIntentTool extends BaseTool {
|
||||
name = "classify_intent";
|
||||
description =
|
||||
"Analyze user input to determine workflow intent and classify the type of processing needed";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "userInput",
|
||||
type: "string",
|
||||
description: "The user's natural language request",
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { userInput } = params;
|
||||
|
||||
if (!userInput || typeof userInput !== "string") {
|
||||
return this.createResult(false, null, "Invalid user input provided");
|
||||
}
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.classifyIntentWithLLM(userInput);
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: result.confidence,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in ClassifyIntentTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async classifyIntentWithLLM(
|
||||
userInput: string
|
||||
): Promise<IntentClassification> {
|
||||
const prompt = `
|
||||
Analyze this natural language description and classify the workflow intent:
|
||||
|
||||
User Input: "${userInput}"
|
||||
|
||||
Classify into one of these categories:
|
||||
- WEB_SCRAPING: Extract data from websites
|
||||
- AI_ANALYSIS: Process text with AI models
|
||||
- DATA_PROCESSING: Transform or structure data
|
||||
- SEARCH_AND_RETRIEVAL: Find similar content
|
||||
- CONTENT_GENERATION: Create new content
|
||||
- JOB_APPLICATION: Job application automation
|
||||
- MIXED: Multiple operations requiring different node types
|
||||
|
||||
Also identify if this is a MIXED intent by looking for multiple distinct operations.
|
||||
|
||||
Respond with JSON:
|
||||
{
|
||||
"intent": "WEB_SCRAPING",
|
||||
"confidence": 0.95,
|
||||
"reasoning": "User wants to extract data from a website"
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await callOpenAI(prompt, {
|
||||
model: "deepseek-chat",
|
||||
temperature: 0.1,
|
||||
maxTokens: 200,
|
||||
});
|
||||
|
||||
const result = JSON.parse(response.content);
|
||||
|
||||
// Validate the response structure
|
||||
if (!result.intent || typeof result.confidence !== "number") {
|
||||
throw new Error("Invalid response format from LLM");
|
||||
}
|
||||
|
||||
return {
|
||||
intent: result.intent,
|
||||
confidence: Math.min(Math.max(result.confidence, 0), 1), // Clamp between 0 and 1
|
||||
reasoning: result.reasoning || "Intent classified by AI",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error in LLM intent classification:", error);
|
||||
|
||||
// Fallback to pattern-based classification
|
||||
return this.fallbackIntentClassification(userInput);
|
||||
}
|
||||
}
|
||||
|
||||
private fallbackIntentClassification(
|
||||
userInput: string
|
||||
): IntentClassification {
|
||||
const input = userInput.toLowerCase();
|
||||
|
||||
// Pattern-based classification as fallback
|
||||
if (
|
||||
input.includes("web") ||
|
||||
input.includes("scrape") ||
|
||||
input.includes("url")
|
||||
) {
|
||||
return {
|
||||
intent: "WEB_SCRAPING",
|
||||
confidence: 0.7,
|
||||
reasoning: "Detected web-related keywords",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
input.includes("job") ||
|
||||
input.includes("resume") ||
|
||||
input.includes("application")
|
||||
) {
|
||||
return {
|
||||
intent: "JOB_APPLICATION",
|
||||
confidence: 0.8,
|
||||
reasoning: "Detected job application keywords",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
input.includes("ai") ||
|
||||
input.includes("analyze") ||
|
||||
input.includes("process")
|
||||
) {
|
||||
return {
|
||||
intent: "AI_ANALYSIS",
|
||||
confidence: 0.6,
|
||||
reasoning: "Detected AI analysis keywords",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
input.includes("search") ||
|
||||
input.includes("similar") ||
|
||||
input.includes("find")
|
||||
) {
|
||||
return {
|
||||
intent: "SEARCH_AND_RETRIEVAL",
|
||||
confidence: 0.6,
|
||||
reasoning: "Detected search-related keywords",
|
||||
};
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return {
|
||||
intent: "GENERAL_PROCESSING",
|
||||
confidence: 0.5,
|
||||
reasoning: "Fallback classification due to AI processing error",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
import { callOpenAI } from "../openaiService";
|
||||
import { EntityExtraction } from "../../types";
|
||||
|
||||
export class ExtractEntitiesTool extends BaseTool {
|
||||
name = "extract_entities";
|
||||
description =
|
||||
"Extract specific entities like URLs, data types, output formats, and AI tasks from user input";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "userInput",
|
||||
type: "string",
|
||||
description: "The user's natural language request",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "intent",
|
||||
type: "string",
|
||||
description: "The classified intent from previous step",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { userInput, intent } = params;
|
||||
|
||||
if (!userInput || typeof userInput !== "string") {
|
||||
return this.createResult(false, null, "Invalid user input provided");
|
||||
}
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.extractEntitiesWithLLM(userInput, intent);
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: this.calculateConfidence(result),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in ExtractEntitiesTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async extractEntitiesWithLLM(
|
||||
userInput: string,
|
||||
intent?: string
|
||||
): Promise<EntityExtraction> {
|
||||
const prompt = `
|
||||
Extract specific entities from this workflow description:
|
||||
|
||||
Input: "${userInput}"
|
||||
Intent: ${intent || "Unknown"}
|
||||
|
||||
Extract:
|
||||
- URLs: Any website addresses
|
||||
- Data types: text, JSON, CSV, PDF, etc.
|
||||
- Output formats: JSON, text, markdown, etc.
|
||||
- AI tasks: summarization, analysis, classification, etc.
|
||||
- Processing steps: what transformations are needed
|
||||
- Target sites: job boards, news sites, etc.
|
||||
- Data sources: resume, documents, etc.
|
||||
|
||||
Respond with JSON:
|
||||
{
|
||||
"urls": ["https://example.com"],
|
||||
"dataTypes": ["text"],
|
||||
"outputFormats": ["JSON"],
|
||||
"aiTasks": ["summarize", "extract key points"],
|
||||
"processingSteps": ["scrape content", "analyze with AI", "format output"],
|
||||
"targetSites": ["job boards"],
|
||||
"dataSources": ["resume"]
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await callOpenAI(prompt, {
|
||||
model: "deepseek-chat",
|
||||
temperature: 0.1,
|
||||
maxTokens: 300,
|
||||
});
|
||||
|
||||
const result = JSON.parse(response.content);
|
||||
|
||||
// Validate and normalize the response
|
||||
return this.normalizeEntityExtraction(result);
|
||||
} catch (error) {
|
||||
console.error("Error in LLM entity extraction:", error);
|
||||
|
||||
// Fallback to pattern-based extraction
|
||||
return this.fallbackEntityExtraction(userInput);
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeEntityExtraction(data: any): EntityExtraction {
|
||||
return {
|
||||
urls: Array.isArray(data.urls) ? data.urls : [],
|
||||
dataTypes: Array.isArray(data.dataTypes) ? data.dataTypes : [],
|
||||
outputFormats: Array.isArray(data.outputFormats)
|
||||
? data.outputFormats
|
||||
: [],
|
||||
aiTasks: Array.isArray(data.aiTasks) ? data.aiTasks : [],
|
||||
processingSteps: Array.isArray(data.processingSteps)
|
||||
? data.processingSteps
|
||||
: [],
|
||||
targetSites: Array.isArray(data.targetSites) ? data.targetSites : [],
|
||||
dataSources: Array.isArray(data.dataSources) ? data.dataSources : [],
|
||||
};
|
||||
}
|
||||
|
||||
private fallbackEntityExtraction(userInput: string): EntityExtraction {
|
||||
const input = userInput.toLowerCase();
|
||||
const entities: EntityExtraction = {
|
||||
urls: [],
|
||||
dataTypes: [],
|
||||
outputFormats: [],
|
||||
aiTasks: [],
|
||||
processingSteps: [],
|
||||
targetSites: [],
|
||||
dataSources: [],
|
||||
};
|
||||
|
||||
// Extract URLs
|
||||
const urlPattern = /https?:\/\/[^\s]+/gi;
|
||||
const urls = userInput.match(urlPattern);
|
||||
if (urls) {
|
||||
entities.urls = urls;
|
||||
}
|
||||
|
||||
// Extract data types
|
||||
if (input.includes("json")) entities.dataTypes.push("json");
|
||||
if (input.includes("csv")) entities.dataTypes.push("csv");
|
||||
if (input.includes("pdf")) entities.dataTypes.push("pdf");
|
||||
if (input.includes("resume") || input.includes("cv")) {
|
||||
entities.dataTypes.push("text");
|
||||
entities.dataSources!.push("resume");
|
||||
}
|
||||
if (input.includes("url") || input.includes("website")) {
|
||||
entities.dataTypes.push("url");
|
||||
}
|
||||
|
||||
// Extract AI tasks
|
||||
if (input.includes("analyze")) entities.aiTasks.push("analyze");
|
||||
if (input.includes("summarize")) entities.aiTasks.push("summarize");
|
||||
if (input.includes("generate")) entities.aiTasks.push("generate");
|
||||
if (input.includes("extract")) entities.aiTasks.push("extract");
|
||||
if (input.includes("classify")) entities.aiTasks.push("classify");
|
||||
|
||||
// Extract output formats
|
||||
if (input.includes("json")) entities.outputFormats.push("json");
|
||||
if (input.includes("text")) entities.outputFormats.push("text");
|
||||
if (input.includes("csv")) entities.outputFormats.push("csv");
|
||||
if (input.includes("markdown")) entities.outputFormats.push("markdown");
|
||||
|
||||
// Extract processing steps
|
||||
if (input.includes("scrape"))
|
||||
entities.processingSteps.push("scrape content");
|
||||
if (input.includes("analyze"))
|
||||
entities.processingSteps.push("analyze with AI");
|
||||
if (input.includes("format"))
|
||||
entities.processingSteps.push("format output");
|
||||
if (input.includes("search"))
|
||||
entities.processingSteps.push("search content");
|
||||
|
||||
// Extract target sites
|
||||
if (input.includes("job")) entities.targetSites!.push("job boards");
|
||||
if (input.includes("news")) entities.targetSites!.push("news sites");
|
||||
if (input.includes("blog")) entities.targetSites!.push("blog sites");
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private calculateConfidence(entities: EntityExtraction): number {
|
||||
let confidence = 0.5; // Base confidence
|
||||
|
||||
// Increase confidence based on number of entities found
|
||||
const totalEntities = Object.values(entities).reduce(
|
||||
(sum, arr) => sum + arr.length,
|
||||
0
|
||||
);
|
||||
confidence += Math.min(totalEntities * 0.1, 0.4);
|
||||
|
||||
// Increase confidence if we found URLs (strong indicator)
|
||||
if (entities.urls.length > 0) confidence += 0.2;
|
||||
|
||||
// Increase confidence if we found AI tasks (strong indicator)
|
||||
if (entities.aiTasks.length > 0) confidence += 0.2;
|
||||
|
||||
return Math.min(confidence, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
import { WorkflowStructure } from "../../types";
|
||||
|
||||
export class GenerateSuggestionsTool extends BaseTool {
|
||||
name = "generate_suggestions";
|
||||
description =
|
||||
"Generate contextual suggestions for improving or extending a workflow";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "workflow",
|
||||
type: "object",
|
||||
description: "The current workflow structure",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "context",
|
||||
type: "string",
|
||||
description: "Additional context for generating suggestions",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { workflow, context } = params;
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.generateSuggestions(workflow, context);
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: 0.8,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in GenerateSuggestionsTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateSuggestions(
|
||||
workflow?: WorkflowStructure,
|
||||
context?: string
|
||||
): Promise<string[]> {
|
||||
const suggestions: string[] = [];
|
||||
|
||||
if (!workflow) {
|
||||
return this.getDefaultSuggestions();
|
||||
}
|
||||
|
||||
// Analyze workflow structure and generate contextual suggestions
|
||||
this.analyzeWorkflowStructure(workflow, suggestions);
|
||||
this.analyzeNodeTypes(workflow, suggestions);
|
||||
this.analyzeConnections(workflow, suggestions);
|
||||
this.analyzeConfiguration(workflow, suggestions);
|
||||
this.analyzePerformance(workflow, suggestions);
|
||||
|
||||
// Add context-specific suggestions
|
||||
if (context) {
|
||||
this.addContextualSuggestions(context, suggestions);
|
||||
}
|
||||
|
||||
// Remove duplicates and limit to top suggestions
|
||||
return [...new Set(suggestions)].slice(0, 10);
|
||||
}
|
||||
|
||||
private getDefaultSuggestions(): string[] {
|
||||
return [
|
||||
"Start by adding a data input node to begin your workflow",
|
||||
"Consider what type of data you want to process",
|
||||
"Think about the end result you want to achieve",
|
||||
"Add an AI task node to process your data intelligently",
|
||||
"Include a data output node to export your results",
|
||||
"Try the AI Copilot to generate a complete workflow automatically",
|
||||
];
|
||||
}
|
||||
|
||||
private analyzeWorkflowStructure(
|
||||
workflow: WorkflowStructure,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const nodeCount = workflow.nodes.length;
|
||||
const hasInput = workflow.nodes.some((n) => n.type === "dataInput");
|
||||
const hasOutput = workflow.nodes.some((n) => n.type === "dataOutput");
|
||||
|
||||
if (nodeCount === 0) {
|
||||
suggestions.push("Start by adding nodes to build your workflow");
|
||||
} else if (nodeCount === 1) {
|
||||
suggestions.push("Add more nodes to create a complete workflow");
|
||||
} else if (nodeCount > 8) {
|
||||
suggestions.push(
|
||||
"Consider breaking this complex workflow into smaller, focused workflows"
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasInput) {
|
||||
suggestions.push("Add a data input node to start your workflow");
|
||||
}
|
||||
|
||||
if (!hasOutput) {
|
||||
suggestions.push("Add a data output node to complete your workflow");
|
||||
}
|
||||
|
||||
if (hasInput && hasOutput && nodeCount === 2) {
|
||||
suggestions.push(
|
||||
"Add processing nodes between input and output for more functionality"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private analyzeNodeTypes(
|
||||
workflow: WorkflowStructure,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const nodeTypes = workflow.nodes.map((n) => n.type);
|
||||
const hasWebScraping = nodeTypes.includes("webScraping");
|
||||
const hasLLM = nodeTypes.includes("llmTask");
|
||||
const hasEmbedding = nodeTypes.includes("embeddingGenerator");
|
||||
const hasSearch = nodeTypes.includes("similaritySearch");
|
||||
|
||||
if (hasWebScraping && !hasLLM) {
|
||||
suggestions.push(
|
||||
"Consider adding an AI analysis node after web scraping to process the content"
|
||||
);
|
||||
}
|
||||
|
||||
if (hasLLM && !hasWebScraping && nodeTypes.includes("dataInput")) {
|
||||
const inputNode = workflow.nodes.find((n) => n.type === "dataInput");
|
||||
if (inputNode?.config?.dataType === "url") {
|
||||
suggestions.push(
|
||||
"Add a web scraping node to extract content from URLs"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEmbedding && !hasSearch) {
|
||||
suggestions.push(
|
||||
"Add a similarity search node to find similar content using embeddings"
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSearch && !hasEmbedding) {
|
||||
suggestions.push(
|
||||
"Add an embedding generator node to create vector representations of your data"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!hasLLM &&
|
||||
nodeTypes.some((t) => ["dataInput", "webScraping"].includes(t))
|
||||
) {
|
||||
suggestions.push(
|
||||
"Add an AI task node to intelligently process your data"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private analyzeConnections(
|
||||
workflow: WorkflowStructure,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const connectedNodes = new Set<string>();
|
||||
workflow.edges.forEach((edge) => {
|
||||
connectedNodes.add(edge.source);
|
||||
connectedNodes.add(edge.target);
|
||||
});
|
||||
|
||||
const unconnectedNodes = workflow.nodes.filter((_, index) => {
|
||||
const nodeId = `node-${index}`;
|
||||
return !connectedNodes.has(nodeId);
|
||||
});
|
||||
|
||||
if (unconnectedNodes.length > 0) {
|
||||
suggestions.push("Connect all nodes to create a complete data flow");
|
||||
}
|
||||
|
||||
if (workflow.edges.length === 0 && workflow.nodes.length > 1) {
|
||||
suggestions.push(
|
||||
"Connect your nodes to establish data flow between them"
|
||||
);
|
||||
}
|
||||
|
||||
// Check for potential parallel processing opportunities
|
||||
const inputNodes = workflow.nodes.filter((n) => n.type === "dataInput");
|
||||
if (inputNodes.length === 1 && workflow.nodes.length > 3) {
|
||||
const inputNode = inputNodes[0];
|
||||
const connectedToInput = workflow.edges.filter(
|
||||
(e) => e.source === `node-${workflow.nodes.indexOf(inputNode)}`
|
||||
);
|
||||
|
||||
if (connectedToInput.length > 1) {
|
||||
suggestions.push(
|
||||
"Consider using parallel processing for better performance"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private analyzeConfiguration(
|
||||
workflow: WorkflowStructure,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
workflow.nodes.forEach((node, index) => {
|
||||
if (!node.config || Object.keys(node.config).length === 0) {
|
||||
suggestions.push(
|
||||
`Configure node ${index + 1} (${
|
||||
node.label
|
||||
}) with appropriate settings`
|
||||
);
|
||||
}
|
||||
|
||||
// Type-specific configuration suggestions
|
||||
switch (node.type) {
|
||||
case "llmTask":
|
||||
if (!node.config.prompt || node.config.prompt.trim() === "") {
|
||||
suggestions.push(`Add a prompt to LLM task node ${index + 1}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "webScraping":
|
||||
if (!node.config.url || node.config.url.trim() === "") {
|
||||
suggestions.push(
|
||||
`Configure URL for web scraping node ${index + 1}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "dataOutput":
|
||||
if (!node.config.format) {
|
||||
suggestions.push(
|
||||
`Specify output format for data output node ${index + 1}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private analyzePerformance(
|
||||
workflow: WorkflowStructure,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const estimatedTime = workflow.estimatedExecutionTime || 0;
|
||||
|
||||
if (estimatedTime > 30000) {
|
||||
suggestions.push("Consider optimizing workflow for faster execution");
|
||||
}
|
||||
|
||||
const llmNodes = workflow.nodes.filter((n) => n.type === "llmTask");
|
||||
if (llmNodes.length > 3) {
|
||||
suggestions.push(
|
||||
"Consider combining multiple AI tasks or using more efficient models"
|
||||
);
|
||||
}
|
||||
|
||||
const webScrapingNodes = workflow.nodes.filter(
|
||||
(n) => n.type === "webScraping"
|
||||
);
|
||||
if (webScrapingNodes.length > 2) {
|
||||
suggestions.push(
|
||||
"Consider using batch processing for multiple web scraping operations"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private addContextualSuggestions(
|
||||
context: string,
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const lowerContext = context.toLowerCase();
|
||||
|
||||
if (lowerContext.includes("job") || lowerContext.includes("resume")) {
|
||||
suggestions.push(
|
||||
"Consider adding a job matching node to find relevant opportunities"
|
||||
);
|
||||
suggestions.push(
|
||||
"Add a cover letter generation node for personalized applications"
|
||||
);
|
||||
}
|
||||
|
||||
if (lowerContext.includes("web") || lowerContext.includes("scrape")) {
|
||||
suggestions.push(
|
||||
"Add content filtering to extract only relevant information"
|
||||
);
|
||||
suggestions.push(
|
||||
"Consider adding error handling for failed web requests"
|
||||
);
|
||||
}
|
||||
|
||||
if (lowerContext.includes("data") || lowerContext.includes("analysis")) {
|
||||
suggestions.push("Add data validation nodes to ensure data quality");
|
||||
suggestions.push(
|
||||
"Consider adding data transformation nodes for better processing"
|
||||
);
|
||||
}
|
||||
|
||||
if (lowerContext.includes("search") || lowerContext.includes("find")) {
|
||||
suggestions.push("Add ranking algorithms to improve search results");
|
||||
suggestions.push("Consider adding filters to narrow down search results");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
import { callOpenAI } from "../openaiService";
|
||||
import {
|
||||
WorkflowStructure,
|
||||
IntentClassification,
|
||||
EntityExtraction,
|
||||
} from "../../types";
|
||||
|
||||
export class GenerateWorkflowTool extends BaseTool {
|
||||
name = "generate_workflow";
|
||||
description =
|
||||
"Generate a complete workflow structure based on intent classification and entity extraction";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "userInput",
|
||||
type: "string",
|
||||
description: "The original user request",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "intent",
|
||||
type: "object",
|
||||
description: "The classified intent from previous step",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "entities",
|
||||
type: "object",
|
||||
description: "The extracted entities from previous step",
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { userInput, intent, entities } = params;
|
||||
|
||||
if (!userInput || !intent || !entities) {
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
"Missing required parameters: userInput, intent, or entities"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.generateWorkflowWithLLM(
|
||||
userInput,
|
||||
intent,
|
||||
entities
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: this.calculateWorkflowConfidence(result),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in GenerateWorkflowTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateWorkflowWithLLM(
|
||||
userInput: string,
|
||||
intent: IntentClassification,
|
||||
entities: EntityExtraction
|
||||
): Promise<WorkflowStructure> {
|
||||
const prompt = `
|
||||
You are an AI workflow designer. Analyze the user's request and create a comprehensive workflow structure.
|
||||
|
||||
User Request: "${userInput}"
|
||||
Intent: ${intent.intent} (confidence: ${intent.confidence})
|
||||
Reasoning: ${intent.reasoning}
|
||||
|
||||
Extracted Entities:
|
||||
- URLs: ${entities.urls.join(", ") || "None"}
|
||||
- Data Types: ${entities.dataTypes.join(", ") || "None"}
|
||||
- Output Formats: ${entities.outputFormats.join(", ") || "None"}
|
||||
- AI Tasks: ${entities.aiTasks.join(", ") || "None"}
|
||||
- Processing Steps: ${entities.processingSteps.join(", ") || "None"}
|
||||
|
||||
Available node types and their purposes:
|
||||
- dataInput: Entry point for data (text, JSON, CSV, URL, PDF, etc.)
|
||||
- webScraping: Extract content from websites using Firecrawl
|
||||
- llmTask: Process data with AI models (analysis, generation, transformation)
|
||||
- structuredOutput: Format data according to JSON schemas
|
||||
- embeddingGenerator: Create vector embeddings for text
|
||||
- similaritySearch: Find similar content using vector search
|
||||
- dataOutput: Export results in various formats
|
||||
|
||||
Instructions:
|
||||
1. Understand the user's goal and break it down into logical steps
|
||||
2. Create a workflow that accomplishes their request
|
||||
3. Use appropriate node types for each step
|
||||
4. Configure nodes with realistic settings
|
||||
5. Connect nodes logically with proper data flow
|
||||
6. Use variable substitution ({{nodeId.output}}) to pass data between nodes
|
||||
7. Make the workflow practical and executable
|
||||
|
||||
For job application workflows, consider:
|
||||
- Resume analysis and skill extraction
|
||||
- Job matching and opportunity identification
|
||||
- Application generation and personalization
|
||||
- Cover letter creation
|
||||
|
||||
For web scraping workflows, consider:
|
||||
- URL input and validation
|
||||
- Content extraction with appropriate formats
|
||||
- Data cleaning and processing
|
||||
- Output formatting
|
||||
|
||||
For PDF processing workflows, consider:
|
||||
- PDF file input and validation
|
||||
- Text extraction from PDF content
|
||||
- AI analysis of extracted text
|
||||
- Structured output formatting
|
||||
|
||||
For AI analysis workflows, consider:
|
||||
- Data input and preprocessing
|
||||
- AI processing with appropriate prompts
|
||||
- Result formatting and structuring
|
||||
- Output generation
|
||||
|
||||
Respond with valid JSON only:
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"type": "dataInput",
|
||||
"label": "Descriptive Node Name",
|
||||
"config": {
|
||||
"dataType": "text|json|csv|url",
|
||||
"defaultValue": "Sample input data"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-0",
|
||||
"target": "node-1"
|
||||
}
|
||||
],
|
||||
"topology": {
|
||||
"type": "linear|fork-join|branching",
|
||||
"description": "Workflow description",
|
||||
"parallelExecution": false
|
||||
},
|
||||
"complexity": "low|medium|high",
|
||||
"estimatedExecutionTime": 5000
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await callOpenAI(prompt, {
|
||||
model: "deepseek-chat",
|
||||
temperature: 0.3,
|
||||
maxTokens: 1000,
|
||||
});
|
||||
|
||||
const result = JSON.parse(response.content);
|
||||
|
||||
// Validate the response structure
|
||||
this.validateWorkflowStructure(result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in LLM workflow generation:", error);
|
||||
|
||||
// Return a simple fallback workflow
|
||||
return this.generateFallbackWorkflow(userInput, intent, entities);
|
||||
}
|
||||
}
|
||||
|
||||
private validateWorkflowStructure(workflow: any): void {
|
||||
if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
|
||||
throw new Error("Invalid workflow: missing or invalid nodes array");
|
||||
}
|
||||
|
||||
if (!workflow.edges || !Array.isArray(workflow.edges)) {
|
||||
throw new Error("Invalid workflow: missing or invalid edges array");
|
||||
}
|
||||
|
||||
if (!workflow.topology || !workflow.topology.type) {
|
||||
throw new Error("Invalid workflow: missing or invalid topology");
|
||||
}
|
||||
}
|
||||
|
||||
private generateFallbackWorkflow(
|
||||
userInput: string,
|
||||
intent: IntentClassification,
|
||||
entities: EntityExtraction
|
||||
): WorkflowStructure {
|
||||
const nodes: any[] = [
|
||||
{
|
||||
type: "dataInput",
|
||||
label: "Input Data",
|
||||
config: {
|
||||
dataType: entities.dataTypes[0] || "text",
|
||||
defaultValue: entities.urls[0] || "Enter your data here",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Add processing nodes based on intent
|
||||
if (intent.intent === "WEB_SCRAPING" || entities.urls.length > 0) {
|
||||
nodes.push({
|
||||
type: "webScraping",
|
||||
label: "Web Scraper",
|
||||
config: {
|
||||
url: entities.urls[0] || "{{input.output}}",
|
||||
formats: ["markdown", "html"],
|
||||
onlyMainContent: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (intent.intent === "AI_ANALYSIS" || entities.aiTasks.length > 0) {
|
||||
nodes.push({
|
||||
type: "llmTask",
|
||||
label: "AI Analyzer",
|
||||
config: {
|
||||
prompt: this.generateAIPrompt(entities),
|
||||
model: "deepseek-chat",
|
||||
temperature: 0.7,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Always add output node
|
||||
nodes.push({
|
||||
type: "dataOutput",
|
||||
label: "Data Output",
|
||||
config: {
|
||||
format: entities.outputFormats[0] || "json",
|
||||
filename: `workflow_output_${Date.now()}.json`,
|
||||
},
|
||||
});
|
||||
|
||||
// Generate edges
|
||||
const edges = [];
|
||||
for (let i = 0; i < nodes.length - 1; i++) {
|
||||
edges.push({
|
||||
source: `node-${i}`,
|
||||
target: `node-${i + 1}`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
topology: {
|
||||
type: "linear",
|
||||
description: `Generated workflow for: ${intent.intent}`,
|
||||
parallelExecution: false,
|
||||
},
|
||||
complexity:
|
||||
nodes.length <= 3 ? "low" : nodes.length <= 6 ? "medium" : "high",
|
||||
estimatedExecutionTime: nodes.length * 2000,
|
||||
};
|
||||
}
|
||||
|
||||
private generateAIPrompt(entities: EntityExtraction): string {
|
||||
const tasks = entities.aiTasks || [];
|
||||
|
||||
if (tasks.includes("summarize")) {
|
||||
return "Summarize the following content in 2-3 sentences: {{input.output}}";
|
||||
}
|
||||
|
||||
if (tasks.includes("analyze")) {
|
||||
return "Analyze the following content and provide insights: {{input.output}}";
|
||||
}
|
||||
|
||||
if (tasks.includes("extract")) {
|
||||
return "Extract key information from the following content: {{input.output}}";
|
||||
}
|
||||
|
||||
if (tasks.includes("classify")) {
|
||||
return "Classify the following content into categories: {{input.output}}";
|
||||
}
|
||||
|
||||
return "Process the following content: {{input.output}}";
|
||||
}
|
||||
|
||||
private calculateWorkflowConfidence(workflow: WorkflowStructure): number {
|
||||
let confidence = 0.7; // Base confidence for generated workflow
|
||||
|
||||
// Increase confidence based on workflow complexity
|
||||
if (workflow.complexity === "low") confidence += 0.1;
|
||||
else if (workflow.complexity === "medium") confidence += 0.05;
|
||||
|
||||
// Increase confidence if workflow has proper input/output nodes
|
||||
const hasInput = workflow.nodes.some((n) => n.type === "dataInput");
|
||||
const hasOutput = workflow.nodes.some((n) => n.type === "dataOutput");
|
||||
if (hasInput && hasOutput) confidence += 0.1;
|
||||
|
||||
// Increase confidence if workflow has logical flow
|
||||
if (workflow.edges.length > 0) confidence += 0.05;
|
||||
|
||||
return Math.min(confidence, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Tool, ToolRegistry as IToolRegistry } from "../../types/tools";
|
||||
|
||||
export class ToolRegistry implements IToolRegistry {
|
||||
private tools = new Map<string, Tool>();
|
||||
|
||||
registerTool(tool: Tool): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
console.log(`Registered tool: ${tool.name}`);
|
||||
}
|
||||
|
||||
getTool(name: string): Tool | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
getAllTools(): Tool[] {
|
||||
return Array.from(this.tools.values());
|
||||
}
|
||||
|
||||
unregisterTool(name: string): void {
|
||||
this.tools.delete(name);
|
||||
console.log(`Unregistered tool: ${name}`);
|
||||
}
|
||||
|
||||
getToolsByCategory(category: string): Tool[] {
|
||||
// For now, return all tools. In the future, we can add categories
|
||||
return this.getAllTools();
|
||||
}
|
||||
|
||||
getToolNames(): string[] {
|
||||
return Array.from(this.tools.keys());
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.tools.clear();
|
||||
console.log("Cleared all tools from registry");
|
||||
}
|
||||
}
|
||||
|
||||
// Global tool registry instance
|
||||
export const toolRegistry = new ToolRegistry();
|
||||
@@ -0,0 +1,343 @@
|
||||
import { BaseTool } from "./BaseTool";
|
||||
import { ToolParameter, ToolResult } from "../../types/tools";
|
||||
import { WorkflowStructure, ValidationResult } from "../../types";
|
||||
import { validateWorkflowStructure } from "../../utils/workflowValidator";
|
||||
|
||||
export class ValidateWorkflowTool extends BaseTool {
|
||||
name = "validate_workflow";
|
||||
description =
|
||||
"Validate a generated workflow for correctness, completeness, and best practices";
|
||||
parameters: ToolParameter[] = [
|
||||
{
|
||||
name: "workflow",
|
||||
type: "object",
|
||||
description: "The workflow structure to validate",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "originalInput",
|
||||
type: "string",
|
||||
description: "The original user input for context",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
async execute(params: Record<string, any>): Promise<ToolResult> {
|
||||
const { workflow, originalInput } = params;
|
||||
|
||||
if (!workflow) {
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
"Missing required parameter: workflow"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { result, executionTime } = await this.measureExecution(
|
||||
async () => {
|
||||
return await this.validateWorkflow(workflow, originalInput || "");
|
||||
}
|
||||
);
|
||||
|
||||
return this.createResult(true, result, undefined, {
|
||||
executionTime,
|
||||
confidence: this.calculateValidationConfidence(result),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in ValidateWorkflowTool:", error);
|
||||
return this.createResult(
|
||||
false,
|
||||
null,
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateWorkflow(
|
||||
workflow: WorkflowStructure,
|
||||
originalInput: string
|
||||
): Promise<ValidationResult> {
|
||||
try {
|
||||
// Use the existing validation logic
|
||||
const validationResult = validateWorkflowStructure(
|
||||
workflow,
|
||||
originalInput
|
||||
);
|
||||
|
||||
// Add additional custom validation
|
||||
const enhancedResult = this.enhanceValidation(validationResult, workflow);
|
||||
|
||||
return enhancedResult;
|
||||
} catch (error) {
|
||||
console.error("Error in workflow validation:", error);
|
||||
|
||||
// Return a basic validation result
|
||||
return {
|
||||
isValid: false,
|
||||
issues: ["Validation failed due to internal error"],
|
||||
suggestions: ["Please check the workflow structure and try again"],
|
||||
complexity: "unknown",
|
||||
estimatedExecutionTime: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private enhanceValidation(
|
||||
baseResult: ValidationResult,
|
||||
workflow: WorkflowStructure
|
||||
): ValidationResult {
|
||||
const issues = [...(baseResult.issues || [])];
|
||||
const suggestions = [...(baseResult.suggestions || [])];
|
||||
|
||||
// Add custom validation rules
|
||||
this.validateNodeConfiguration(workflow, issues, suggestions);
|
||||
this.validateDataFlow(workflow, issues, suggestions);
|
||||
this.validatePerformance(workflow, issues, suggestions);
|
||||
this.validateBestPractices(workflow, issues, suggestions);
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
issues,
|
||||
suggestions,
|
||||
complexity: this.determineComplexity(workflow),
|
||||
estimatedExecutionTime: this.estimateExecutionTime(workflow),
|
||||
};
|
||||
}
|
||||
|
||||
private validateNodeConfiguration(
|
||||
workflow: WorkflowStructure,
|
||||
issues: string[],
|
||||
suggestions: string[]
|
||||
): void {
|
||||
workflow.nodes.forEach((node, index) => {
|
||||
// Check for empty labels
|
||||
if (!node.label || node.label.trim() === "") {
|
||||
issues.push(`Node ${index + 1} has an empty label`);
|
||||
suggestions.push(`Give node ${index + 1} a descriptive label`);
|
||||
}
|
||||
|
||||
// Check for missing configuration
|
||||
if (!node.config || Object.keys(node.config).length === 0) {
|
||||
issues.push(`Node ${index + 1} (${node.type}) has no configuration`);
|
||||
suggestions.push(
|
||||
`Configure node ${index + 1} with appropriate settings`
|
||||
);
|
||||
}
|
||||
|
||||
// Type-specific validation
|
||||
this.validateNodeTypeSpecific(node, index, issues, suggestions);
|
||||
});
|
||||
}
|
||||
|
||||
private validateNodeTypeSpecific(
|
||||
node: any,
|
||||
index: number,
|
||||
issues: string[],
|
||||
suggestions: string[]
|
||||
): void {
|
||||
switch (node.type) {
|
||||
case "dataInput":
|
||||
if (!node.config.dataType) {
|
||||
issues.push(`Data input node ${index + 1} missing data type`);
|
||||
suggestions.push(`Specify data type for input node ${index + 1}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "webScraping":
|
||||
if (!node.config.url && !node.config.url?.includes("{{")) {
|
||||
issues.push(`Web scraping node ${index + 1} missing URL`);
|
||||
suggestions.push(`Configure URL for web scraping node ${index + 1}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "llmTask":
|
||||
if (!node.config.prompt) {
|
||||
issues.push(`LLM task node ${index + 1} missing prompt`);
|
||||
suggestions.push(`Add a prompt for LLM task node ${index + 1}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "dataOutput":
|
||||
if (!node.config.format) {
|
||||
issues.push(`Data output node ${index + 1} missing output format`);
|
||||
suggestions.push(`Specify output format for node ${index + 1}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private validateDataFlow(
|
||||
workflow: WorkflowStructure,
|
||||
issues: string[],
|
||||
suggestions: string[]
|
||||
): void {
|
||||
// Check for orphaned nodes
|
||||
const connectedNodes = new Set<string>();
|
||||
workflow.edges.forEach((edge) => {
|
||||
connectedNodes.add(edge.source);
|
||||
connectedNodes.add(edge.target);
|
||||
});
|
||||
|
||||
workflow.nodes.forEach((node, index) => {
|
||||
const nodeId = `node-${index}`;
|
||||
if (!connectedNodes.has(nodeId) && workflow.nodes.length > 1) {
|
||||
issues.push(`Node ${index + 1} (${node.label}) is not connected`);
|
||||
suggestions.push(
|
||||
`Connect node ${index + 1} to other nodes in the workflow`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for circular dependencies
|
||||
const circularDeps = this.detectCircularDependencies(workflow);
|
||||
if (circularDeps.length > 0) {
|
||||
issues.push(`Circular dependencies detected: ${circularDeps.join(", ")}`);
|
||||
suggestions.push("Remove circular dependencies in the workflow");
|
||||
}
|
||||
}
|
||||
|
||||
private validatePerformance(
|
||||
workflow: WorkflowStructure,
|
||||
issues: string[],
|
||||
suggestions: string[]
|
||||
): void {
|
||||
const estimatedTime = this.estimateExecutionTime(workflow);
|
||||
|
||||
if (estimatedTime > 60000) {
|
||||
// More than 1 minute
|
||||
issues.push("Workflow execution time is very long");
|
||||
suggestions.push(
|
||||
"Consider optimizing the workflow for better performance"
|
||||
);
|
||||
}
|
||||
|
||||
if (workflow.nodes.length > 10) {
|
||||
issues.push("Workflow has many nodes which may be complex to maintain");
|
||||
suggestions.push(
|
||||
"Consider breaking down the workflow into smaller, focused workflows"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private validateBestPractices(
|
||||
workflow: WorkflowStructure,
|
||||
issues: string[],
|
||||
suggestions: string[]
|
||||
): void {
|
||||
// Check for proper input/output nodes
|
||||
const hasInput = workflow.nodes.some((n) => n.type === "dataInput");
|
||||
const hasOutput = workflow.nodes.some((n) => n.type === "dataOutput");
|
||||
|
||||
if (!hasInput) {
|
||||
issues.push("Workflow missing input node");
|
||||
suggestions.push("Add a data input node to start the workflow");
|
||||
}
|
||||
|
||||
if (!hasOutput) {
|
||||
issues.push("Workflow missing output node");
|
||||
suggestions.push("Add a data output node to complete the workflow");
|
||||
}
|
||||
|
||||
// Check for variable substitution usage
|
||||
const hasVariables = workflow.nodes.some((node) =>
|
||||
JSON.stringify(node.config).includes("{{")
|
||||
);
|
||||
|
||||
if (!hasVariables && workflow.nodes.length > 1) {
|
||||
suggestions.push(
|
||||
"Consider using variable substitution to pass data between nodes"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private detectCircularDependencies(workflow: WorkflowStructure): string[] {
|
||||
const graph = new Map<string, string[]>();
|
||||
|
||||
// Build adjacency list
|
||||
workflow.nodes.forEach((_, index) => {
|
||||
graph.set(`node-${index}`, []);
|
||||
});
|
||||
|
||||
workflow.edges.forEach((edge) => {
|
||||
const sourceList = graph.get(edge.source) || [];
|
||||
sourceList.push(edge.target);
|
||||
graph.set(edge.source, sourceList);
|
||||
});
|
||||
|
||||
// DFS to detect cycles
|
||||
const visited = new Set<string>();
|
||||
const recursionStack = new Set<string>();
|
||||
const cycles: string[] = [];
|
||||
|
||||
const dfs = (node: string, path: string[]): void => {
|
||||
if (recursionStack.has(node)) {
|
||||
const cycleStart = path.indexOf(node);
|
||||
cycles.push(path.slice(cycleStart).join(" -> ") + " -> " + node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (visited.has(node)) return;
|
||||
|
||||
visited.add(node);
|
||||
recursionStack.add(node);
|
||||
path.push(node);
|
||||
|
||||
const neighbors = graph.get(node) || [];
|
||||
neighbors.forEach((neighbor) => dfs(neighbor, [...path]));
|
||||
|
||||
recursionStack.delete(node);
|
||||
};
|
||||
|
||||
workflow.nodes.forEach((_, index) => {
|
||||
const nodeId = `node-${index}`;
|
||||
if (!visited.has(nodeId)) {
|
||||
dfs(nodeId, []);
|
||||
}
|
||||
});
|
||||
|
||||
return cycles;
|
||||
}
|
||||
|
||||
private determineComplexity(workflow: WorkflowStructure): string {
|
||||
const nodeCount = workflow.nodes.length;
|
||||
const edgeCount = workflow.edges.length;
|
||||
const hasParallel = workflow.topology.parallelExecution;
|
||||
|
||||
if (nodeCount <= 3 && edgeCount <= 2 && !hasParallel) return "low";
|
||||
if (nodeCount <= 6 && edgeCount <= 5) return "medium";
|
||||
return "high";
|
||||
}
|
||||
|
||||
private estimateExecutionTime(workflow: WorkflowStructure): number {
|
||||
const timeEstimates: Record<string, number> = {
|
||||
dataInput: 0,
|
||||
webScraping: 5000,
|
||||
llmTask: 3000,
|
||||
structuredOutput: 2000,
|
||||
embeddingGenerator: 4000,
|
||||
similaritySearch: 3000,
|
||||
dataOutput: 0,
|
||||
};
|
||||
|
||||
return workflow.nodes.reduce((total, node) => {
|
||||
return total + (timeEstimates[node.type] || 1000);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private calculateValidationConfidence(result: ValidationResult): number {
|
||||
let confidence = 0.8; // Base confidence for validation
|
||||
|
||||
// Decrease confidence based on number of issues
|
||||
if (result.issues && result.issues.length > 0) {
|
||||
confidence -= Math.min(result.issues.length * 0.1, 0.5);
|
||||
}
|
||||
|
||||
// Increase confidence if workflow is valid
|
||||
if (result.isValid) {
|
||||
confidence += 0.2;
|
||||
}
|
||||
|
||||
return Math.max(Math.min(confidence, 1.0), 0.0);
|
||||
}
|
||||
}
|
||||
+21
-36
@@ -15,7 +15,7 @@ import {
|
||||
FirecrawlConfig,
|
||||
} from "../services/firecrawlService";
|
||||
import { substituteVariables, NodeOutput } from "../utils/variableSubstitution";
|
||||
import { copilotService } from "../services/copilotService";
|
||||
import { agentManager } from "../services/agents/AgentManager";
|
||||
|
||||
// localStorage key for workflows
|
||||
const WORKFLOWS_STORAGE_KEY = "agent-workflow-builder-workflows";
|
||||
@@ -107,7 +107,7 @@ interface WorkflowStore {
|
||||
// Copilot methods
|
||||
generateWorkflowFromDescription: (description: string) => Promise<void>;
|
||||
applyCopilotSuggestions: (suggestions: any[]) => void;
|
||||
validateGeneratedWorkflow: () => ValidationResult | null;
|
||||
validateGeneratedWorkflow: () => Promise<ValidationResult | null>;
|
||||
getCopilotSuggestions: (context?: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ export const useWorkflowStore = create<WorkflowStore>((set, get) => ({
|
||||
// Copilot methods
|
||||
generateWorkflowFromDescription: async (description: string) => {
|
||||
try {
|
||||
const parsedIntent = await copilotService.parseNaturalLanguage(
|
||||
const parsedIntent = await agentManager.generateWorkflowFromDescription(
|
||||
description
|
||||
);
|
||||
const workflowStructure = parsedIntent.workflowStructure;
|
||||
@@ -565,7 +565,7 @@ export const useWorkflowStore = create<WorkflowStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
validateGeneratedWorkflow: (): ValidationResult | null => {
|
||||
validateGeneratedWorkflow: async (): Promise<ValidationResult | null> => {
|
||||
const { currentWorkflow } = get();
|
||||
if (!currentWorkflow) return null;
|
||||
|
||||
@@ -591,42 +591,27 @@ export const useWorkflowStore = create<WorkflowStore>((set, get) => ({
|
||||
complexity: "medium",
|
||||
};
|
||||
|
||||
return copilotService.validateWorkflow(workflowStructure, "");
|
||||
try {
|
||||
return await agentManager.validateWorkflow(workflowStructure, "");
|
||||
} catch (error) {
|
||||
console.error("Error validating workflow:", error);
|
||||
return {
|
||||
isValid: false,
|
||||
issues: ["Validation failed due to internal error"],
|
||||
suggestions: ["Please check the workflow structure and try again"],
|
||||
complexity: "unknown",
|
||||
estimatedExecutionTime: 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
getCopilotSuggestions: async (context?: string): Promise<string[]> => {
|
||||
const { currentWorkflow } = get();
|
||||
|
||||
if (!currentWorkflow) {
|
||||
return ["Start by creating a new workflow"];
|
||||
try {
|
||||
return await agentManager.getSuggestions(context);
|
||||
} catch (error) {
|
||||
console.error("Error getting copilot suggestions:", error);
|
||||
return ["Unable to generate suggestions at this time"];
|
||||
}
|
||||
|
||||
// Convert current workflow to WorkflowStructure format
|
||||
const workflowStructure: WorkflowStructure = {
|
||||
nodes: currentWorkflow.nodes.map((node) => ({
|
||||
type: node.type,
|
||||
label: node.data.label,
|
||||
config: node.data.config,
|
||||
position: node.position,
|
||||
})),
|
||||
edges: currentWorkflow.edges.map((edge) => ({
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
})),
|
||||
topology: {
|
||||
type: "linear",
|
||||
description: "Sequential processing",
|
||||
parallelExecution: false,
|
||||
},
|
||||
complexity: "medium",
|
||||
};
|
||||
|
||||
return await copilotService.generateContextualSuggestions(
|
||||
workflowStructure,
|
||||
context || ""
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Test script for the new agent system
|
||||
import { agentManager } from "./services/agents/AgentManager";
|
||||
|
||||
async function testAgentSystem() {
|
||||
console.log("Testing Agent System...");
|
||||
|
||||
try {
|
||||
// Test 1: Basic workflow generation
|
||||
console.log("\n=== Test 1: Basic Workflow Generation ===");
|
||||
const result1 = await agentManager.processWorkflowRequest(
|
||||
"Create a workflow that scrapes a website and analyzes the content with AI"
|
||||
);
|
||||
|
||||
console.log("Result 1:", {
|
||||
success: result1.success,
|
||||
toolsUsed: result1.toolsUsed,
|
||||
executionTime: result1.executionTime,
|
||||
confidence: result1.confidence,
|
||||
});
|
||||
|
||||
if (result1.success) {
|
||||
console.log(
|
||||
"Generated workflow nodes:",
|
||||
result1.data.parsedIntent.workflowStructure.nodes.length
|
||||
);
|
||||
}
|
||||
|
||||
// Test 2: Suggestions generation
|
||||
console.log("\n=== Test 2: Suggestions Generation ===");
|
||||
const suggestions = await agentManager.getSuggestions(
|
||||
"I want to process job applications"
|
||||
);
|
||||
console.log("Suggestions:", suggestions);
|
||||
|
||||
// Test 3: Tool information
|
||||
console.log("\n=== Test 3: Available Tools ===");
|
||||
const tools = agentManager.getAvailableTools();
|
||||
console.log("Available tools:", tools);
|
||||
|
||||
// Test 4: Session info
|
||||
console.log("\n=== Test 4: Session Info ===");
|
||||
const sessionInfo = agentManager.getSessionInfo();
|
||||
console.log("Session info:", sessionInfo);
|
||||
|
||||
console.log("\n✅ All tests completed successfully!");
|
||||
} catch (error) {
|
||||
console.error("❌ Test failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testAgentSystem();
|
||||
@@ -0,0 +1,77 @@
|
||||
// Tool system types
|
||||
export interface Tool {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: ToolParameter[];
|
||||
execute: (params: Record<string, any>) => Promise<ToolResult>;
|
||||
validate?: (params: Record<string, any>) => ToolValidationResult;
|
||||
}
|
||||
|
||||
export interface ToolParameter {
|
||||
name: string;
|
||||
type: "string" | "number" | "boolean" | "object" | "array";
|
||||
description: string;
|
||||
required: boolean;
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
metadata?: {
|
||||
executionTime?: number;
|
||||
tokensUsed?: number;
|
||||
confidence?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToolValidationResult {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export interface ToolRegistry {
|
||||
registerTool(tool: Tool): void;
|
||||
getTool(name: string): Tool | undefined;
|
||||
getAllTools(): Tool[];
|
||||
unregisterTool(name: string): void;
|
||||
}
|
||||
|
||||
export interface AgentTask {
|
||||
id: string;
|
||||
type:
|
||||
| "workflow_generation"
|
||||
| "intent_analysis"
|
||||
| "validation"
|
||||
| "optimization";
|
||||
input: string;
|
||||
context?: Record<string, any>;
|
||||
priority: "low" | "medium" | "high";
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AgentResult {
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
toolsUsed: string[];
|
||||
executionTime: number;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface AgentContext {
|
||||
userRequest: string;
|
||||
currentWorkflow?: any;
|
||||
executionHistory: any[];
|
||||
userPreferences: Record<string, any>;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface ToolExecutionPlan {
|
||||
tools: string[];
|
||||
dependencies: Array<{ tool: string; dependsOn: string }>;
|
||||
parallel: boolean;
|
||||
estimatedTime: number;
|
||||
}
|
||||
+4
-2
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"lib": ["dom", "dom.iterable", "es6"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
@@ -16,5 +17,6 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules/@types/d3-dispatch", "node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user