feat(ai): replace agent search with skills system (#16513)
## Summary - Replace the agent search mechanism with a new skills-based system - Add a `skills` module with predefined skill definitions that the AI can load on demand - Remove specialized agents (workflow-builder, data-manipulator, dashboard-builder, metadata-builder, researcher), keeping only the helper agent - Add `recordReferences` to workflow creation tool for chip linking in the UI ## Changes ### New Skills Module - `skill-definitions.ts` - Contains 5 skill definitions with detailed instructions - `skills.service.ts` - Service to get skills by name - `load-skill.tool.ts` - Tool for AI to load skills explicitly ### Removed - `agent-search.tool.ts` - Replaced by skill loading - Specialized agent definitions (converted to skills) ### Updated - Chat execution now shows skill catalog in system prompt - Workflow creation returns `recordReferences` for UI linking ## Test plan - [ ] Verify AI can load skills using `load_skill` tool - [ ] Verify skill content is returned correctly - [ ] Verify workflow creation shows clickable chip in chat - [ ] Verify helper agent still works
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export type SkillDefinition = {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
content: string;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { SkillsService } from './skills.service';
|
||||
|
||||
@Module({
|
||||
providers: [SkillsService],
|
||||
exports: [SkillsService],
|
||||
})
|
||||
export class SkillsModule {}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
import { DASHBOARD_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/dashboard-building.skill';
|
||||
import { DATA_MANIPULATION_SKILL } from 'src/engine/core-modules/skills/skills/data-manipulation.skill';
|
||||
import { METADATA_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/metadata-building.skill';
|
||||
import { RESEARCH_SKILL } from 'src/engine/core-modules/skills/skills/research.skill';
|
||||
import { WORKFLOW_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/workflow-building.skill';
|
||||
|
||||
const SKILL_DEFINITIONS: SkillDefinition[] = [
|
||||
WORKFLOW_BUILDING_SKILL,
|
||||
DATA_MANIPULATION_SKILL,
|
||||
DASHBOARD_BUILDING_SKILL,
|
||||
METADATA_BUILDING_SKILL,
|
||||
RESEARCH_SKILL,
|
||||
];
|
||||
|
||||
export type Skill = {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SkillsService {
|
||||
getAllSkills(): Skill[] {
|
||||
return SKILL_DEFINITIONS.map((skill) => ({
|
||||
name: skill.name,
|
||||
label: skill.label,
|
||||
description: skill.description,
|
||||
content: skill.content,
|
||||
}));
|
||||
}
|
||||
|
||||
getSkillByName(name: string): Skill | undefined {
|
||||
const skillDef = SKILL_DEFINITIONS.find((skill) => skill.name === name);
|
||||
|
||||
if (!skillDef) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: skillDef.name,
|
||||
label: skillDef.label,
|
||||
description: skillDef.description,
|
||||
content: skillDef.content,
|
||||
};
|
||||
}
|
||||
|
||||
getSkillsByNames(names: string[]): Skill[] {
|
||||
return names
|
||||
.map((name) => this.getSkillByName(name))
|
||||
.filter((skill): skill is Skill => skill !== undefined);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
|
||||
export const DASHBOARD_BUILDING_SKILL: SkillDefinition = {
|
||||
name: 'dashboard-building',
|
||||
label: 'Dashboard Building',
|
||||
description: 'Creating and managing dashboards with widgets and layouts',
|
||||
content: `# Dashboard Building Skill
|
||||
|
||||
You help users create and manage dashboards with widgets.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Create new dashboards from scratch
|
||||
- Add, modify, and remove widgets from dashboards
|
||||
- Configure widget types (VIEW, GRAPH, FIELDS, TIMELINE, TASKS, NOTES, FILES, EMAILS, CALENDAR, RICH_TEXT, IFRAME, WORKFLOW)
|
||||
- Manage dashboard tabs and layouts
|
||||
- Position widgets in a grid system (12-column layout)
|
||||
|
||||
## Dashboard Structure
|
||||
|
||||
- **Dashboard**: Container with a title and pageLayout
|
||||
- **PageLayout**: Contains tabs (type: DASHBOARD)
|
||||
- **PageLayoutTab**: Contains widgets with a title, position, and layoutMode (grid/vertical-list/canvas)
|
||||
- **PageLayoutWidget**: Individual widget with type, title, gridPosition, and optional configuration
|
||||
|
||||
## Grid System
|
||||
|
||||
- 12 columns total
|
||||
- Grid positions: { row, column, rowSpan, columnSpan }
|
||||
- Common sizes: Full width (columnSpan: 12), Half width (columnSpan: 6), Quarter width (columnSpan: 3)
|
||||
- Typical heights: Small (rowSpan: 4), Medium (rowSpan: 6), Large (rowSpan: 8)
|
||||
|
||||
## Widget Types Explained
|
||||
|
||||
- **VIEW**: Display a filtered view of records (companies, people, opportunities, etc.)
|
||||
- **GRAPH**: Show charts and visualizations of data
|
||||
- **FIELDS**: Display specific fields from a record
|
||||
- **TIMELINE**: Show activity timeline
|
||||
- **TASKS**: Display tasks list
|
||||
- **NOTES**: Show notes
|
||||
- **FILES**: Display file attachments
|
||||
- **EMAILS**: Show email communications
|
||||
- **CALENDAR**: Display calendar events
|
||||
- **RICH_TEXT**: Custom text content
|
||||
- **IFRAME**: Embed external content
|
||||
- **WORKFLOW**: Display workflow information
|
||||
|
||||
## Approach
|
||||
|
||||
- Ask clarifying questions about dashboard purpose and desired widgets
|
||||
- Suggest appropriate widget types and layouts for the use case
|
||||
- Create well-organized, visually balanced dashboards
|
||||
- For modifications, first understand current structure
|
||||
- Explain widget placement and purpose
|
||||
- Consider responsive design (widgets stack on smaller screens)
|
||||
|
||||
## Layout Best Practices
|
||||
|
||||
- Place most important information at the top
|
||||
- Group related widgets together
|
||||
- Use consistent widget sizes when possible
|
||||
- Leave some whitespace for visual clarity
|
||||
- Consider logical reading order (left to right, top to bottom)
|
||||
|
||||
Prioritize user needs and dashboard usability.`,
|
||||
};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
|
||||
export const DATA_MANIPULATION_SKILL: SkillDefinition = {
|
||||
name: 'data-manipulation',
|
||||
label: 'Data Manipulation',
|
||||
description:
|
||||
'Searching, filtering, creating, and updating records across all objects',
|
||||
content: `# Data Manipulation Skill
|
||||
|
||||
You explore and manage data across companies, people, opportunities, tasks, notes, and custom objects.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Search, filter, sort, create, update records
|
||||
- Manage relationships between records
|
||||
- Bulk operations and data analysis
|
||||
|
||||
## Constraints
|
||||
|
||||
- READ and WRITE access to all objects
|
||||
- CANNOT delete records or access workflow objects
|
||||
- CANNOT modify workspace settings
|
||||
|
||||
## Multi-step Approach
|
||||
|
||||
- Chain queries to solve complex requests (e.g., find companies → get their opportunities → calculate totals)
|
||||
- If a query fails or returns no results, try alternative filters or approaches
|
||||
- Validate data exists before referencing it (search before update)
|
||||
- Use results from one query to inform the next
|
||||
- Try 2-3 different approaches before giving up
|
||||
|
||||
## Sorting (Critical)
|
||||
|
||||
For "top N" queries, use orderBy with limit:
|
||||
- Examples: orderBy: [{"employees": "DescNullsLast"}], orderBy: [{"createdAt": "AscNullsFirst"}]
|
||||
- Valid directions: "AscNullsFirst", "AscNullsLast", "DescNullsFirst", "DescNullsLast"
|
||||
|
||||
## Before Bulk Operations
|
||||
|
||||
- Confirm the scope and impact
|
||||
- Explain what will change
|
||||
|
||||
Prioritize data integrity and provide clear feedback on operations performed.`,
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
|
||||
export const METADATA_BUILDING_SKILL: SkillDefinition = {
|
||||
name: 'metadata-building',
|
||||
label: 'Metadata Building',
|
||||
description:
|
||||
'Managing the data model: creating objects, fields, and relations',
|
||||
content: `# Metadata Building Skill
|
||||
|
||||
You help users manage their workspace data model by creating, updating, and organizing custom objects and fields.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Create new custom objects with appropriate naming and configuration
|
||||
- Add fields to existing objects (text, number, date, select, relation, etc.)
|
||||
- Update object and field properties (labels, descriptions, icons)
|
||||
- Manage field settings (required, unique, default values)
|
||||
- Create relations between objects
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Objects**: Represent entities in the data model (e.g., Company, Person, Opportunity)
|
||||
- **Fields**: Properties of objects with specific types (TEXT, NUMBER, DATE_TIME, SELECT, RELATION, etc.)
|
||||
- **Relations**: Links between objects (one-to-many, many-to-one)
|
||||
- **Labels vs Names**: Labels are for display, names are internal identifiers (camelCase)
|
||||
|
||||
## Field Types Available
|
||||
|
||||
- **TEXT**: Simple text fields
|
||||
- **NUMBER**: Numeric values (integers or decimals)
|
||||
- **BOOLEAN**: True/false values
|
||||
- **DATE_TIME**: Date and time values
|
||||
- **DATE**: Date only values
|
||||
- **SELECT**: Single choice from options
|
||||
- **MULTI_SELECT**: Multiple choices from options
|
||||
- **LINK**: URL fields
|
||||
- **LINKS**: Multiple URL fields
|
||||
- **EMAIL**: Email address fields
|
||||
- **EMAILS**: Multiple email fields
|
||||
- **PHONE**: Phone number fields
|
||||
- **PHONES**: Multiple phone fields
|
||||
- **CURRENCY**: Monetary values
|
||||
- **RATING**: Star ratings
|
||||
- **RELATION**: Links to other objects
|
||||
- **RICH_TEXT**: Formatted text content
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use clear, descriptive names for objects and fields
|
||||
- Follow naming conventions: singular for object names, camelCase for field names
|
||||
- Add helpful descriptions to objects and fields
|
||||
- Choose appropriate field types for the data being stored
|
||||
- Consider relationships between objects when designing the data model
|
||||
|
||||
## Approach
|
||||
|
||||
- Ask clarifying questions to understand the user's data modeling needs
|
||||
- Suggest best practices for naming and organization
|
||||
- Explain the impact of changes to the data model
|
||||
- Verify object and field existence before making updates
|
||||
- Provide clear feedback on operations performed
|
||||
|
||||
Prioritize data model integrity and user understanding.`,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
|
||||
export const RESEARCH_SKILL: SkillDefinition = {
|
||||
name: 'research',
|
||||
label: 'Research',
|
||||
description: 'Finding information and gathering facts from the web',
|
||||
content: `# Research Skill
|
||||
|
||||
You find information and gather facts from the web.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Search for current information and facts
|
||||
- Research companies, people, technologies, trends
|
||||
- Gather competitive intelligence and market data
|
||||
- Find contact details and verify information
|
||||
|
||||
## Research Strategy
|
||||
|
||||
- Try multiple search queries from different angles
|
||||
- If initial searches fail, use alternative search terms
|
||||
- Cross-reference information when possible
|
||||
- Cite sources and provide context
|
||||
|
||||
## Present Findings
|
||||
|
||||
- Be thorough but concise
|
||||
- Organize information logically
|
||||
- Distinguish facts from speculation
|
||||
- Note if information might be outdated
|
||||
- Include relevant sources
|
||||
|
||||
Be persistent in finding accurate information.`,
|
||||
};
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
|
||||
|
||||
export const WORKFLOW_BUILDING_SKILL: SkillDefinition = {
|
||||
name: 'workflow-building',
|
||||
label: 'Workflow Building',
|
||||
description:
|
||||
'Creating and managing automation workflows with triggers and steps',
|
||||
content: `# Workflow Building Skill
|
||||
|
||||
You help users create and manage automation workflows.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Create workflows from scratch
|
||||
- Modify existing workflows (add, remove, update steps)
|
||||
- Explain workflow structure and suggest improvements
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Triggers**: DATABASE_EVENT, MANUAL, CRON, WEBHOOK
|
||||
- **Steps**: CREATE_RECORD, SEND_EMAIL, CODE, etc.
|
||||
- **Data flow**: Use {{stepId.fieldName}} to reference previous step outputs
|
||||
- **Relationships**: Use nested objects like {"company": {"id": "{{reference}}"}}
|
||||
|
||||
## CRON Trigger Settings Schema
|
||||
|
||||
For CRON triggers, settings.type must be one of these exact values:
|
||||
|
||||
1. **DAYS** - Daily schedule
|
||||
- Requires: schedule: { day: number (1+), hour: number (0-23), minute: number (0-59) }
|
||||
- Example: { type: "DAYS", schedule: { day: 1, hour: 9, minute: 0 }, outputSchema: {} }
|
||||
|
||||
2. **HOURS** - Hourly schedule (USE THIS FOR "EVERY HOUR")
|
||||
- Requires: schedule: { hour: number (1+), minute: number (0-59) }
|
||||
- Example: { type: "HOURS", schedule: { hour: 1, minute: 0 }, outputSchema: {} }
|
||||
- This runs every X hours at Y minutes past the hour
|
||||
|
||||
3. **MINUTES** - Minute-based schedule
|
||||
- Requires: schedule: { minute: number (1+) }
|
||||
- Example: { type: "MINUTES", schedule: { minute: 15 }, outputSchema: {} }
|
||||
|
||||
4. **CUSTOM** - Custom cron pattern
|
||||
- Requires: pattern: string (cron expression)
|
||||
- Example: { type: "CUSTOM", pattern: "0 * * * *", outputSchema: {} }
|
||||
|
||||
## Critical Notes
|
||||
|
||||
Always rely on tool schema definitions:
|
||||
- The workflow creation tool provides comprehensive schemas with examples
|
||||
- Follow schema definitions exactly for field names, types, and structures
|
||||
- Schema includes validation rules and common patterns
|
||||
|
||||
## Approach
|
||||
|
||||
- Ask clarifying questions to understand user needs
|
||||
- Suggest appropriate actions for the use case
|
||||
- Explain each step and why it's needed
|
||||
- For modifications, understand current structure first
|
||||
- Ensure workflow logic remains coherent
|
||||
|
||||
Prioritize user understanding and workflow effectiveness.`,
|
||||
};
|
||||
+3
-2
@@ -26,8 +26,9 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
// -> WorkflowRunnerModule -> WorkflowExecutorModule -> AiAgentActionModule
|
||||
// -> AiAgentExecutionModule -> ToolProviderModule
|
||||
//
|
||||
// Instead, WorkflowToolWorkspaceService is an optional dependency that must be
|
||||
// provided by the importing module (e.g., AiChatModule imports WorkflowToolsModule).
|
||||
// Instead, WorkflowToolsModule is a @Global() module that provides WORKFLOW_TOOL_SERVICE_TOKEN.
|
||||
// When WorkflowToolsModule is imported anywhere in the app (e.g., AiChatModule),
|
||||
// the token becomes available globally to WorkflowToolProvider via @Optional() injection.
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
|
||||
export const AGENT_SEARCH_TOOL_NAME = 'agent_search';
|
||||
|
||||
export const agentSearchInputSchema = z.object({
|
||||
input: z.object({
|
||||
query: z.string().describe('What kind of expertise or help you need'),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
.default(2)
|
||||
.describe('Maximum number of agents to return'),
|
||||
}),
|
||||
});
|
||||
|
||||
export type AgentSearchInput = z.infer<typeof agentSearchInputSchema>['input'];
|
||||
|
||||
export type AgentSearchResult = {
|
||||
agents: Array<{
|
||||
name: string;
|
||||
label: string;
|
||||
expertise: string;
|
||||
}>;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type AgentSearchFunction = (
|
||||
query: string,
|
||||
options: { limit: number },
|
||||
) => Promise<AgentEntity[]>;
|
||||
|
||||
export const createAgentSearchTool = (searchAgents: AgentSearchFunction) => ({
|
||||
description:
|
||||
'Search for agent expertise/skills to help with specialized tasks. Returns agent instructions that provide domain knowledge for workflows, data manipulation, metadata management, etc.',
|
||||
inputSchema: agentSearchInputSchema,
|
||||
execute: async (parameters: {
|
||||
input: AgentSearchInput;
|
||||
}): Promise<AgentSearchResult> => {
|
||||
const { query, limit = 2 } = parameters.input;
|
||||
|
||||
const agents = await searchAgents(query, { limit });
|
||||
|
||||
if (agents.length === 0) {
|
||||
return {
|
||||
agents: [],
|
||||
message: `No agent expertise found matching "${query}". Try searching for: "workflow", "data", "metadata", "dashboard", or "research".`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
agents: agents.map((agent) => ({
|
||||
name: agent.name,
|
||||
label: agent.label,
|
||||
expertise: agent.prompt,
|
||||
})),
|
||||
message: `Found ${agents.length} agent(s) with relevant expertise. Their instructions are included above to help guide your approach.`,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,17 +1,17 @@
|
||||
export {
|
||||
createLoadToolsTool,
|
||||
LOAD_TOOLS_TOOL_NAME,
|
||||
createLoadToolsTool,
|
||||
loadToolsInputSchema,
|
||||
type DynamicToolStore,
|
||||
type LoadToolsInput,
|
||||
type LoadToolsResult,
|
||||
type DynamicToolStore,
|
||||
} from './load-tools.tool';
|
||||
|
||||
export {
|
||||
createAgentSearchTool,
|
||||
AGENT_SEARCH_TOOL_NAME,
|
||||
agentSearchInputSchema,
|
||||
type AgentSearchInput,
|
||||
type AgentSearchResult,
|
||||
type AgentSearchFunction,
|
||||
} from './agent-search.tool';
|
||||
LOAD_SKILL_TOOL_NAME,
|
||||
createLoadSkillTool,
|
||||
loadSkillInputSchema,
|
||||
type LoadSkillFunction,
|
||||
type LoadSkillInput,
|
||||
type LoadSkillResult,
|
||||
} from './load-skill.tool';
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type Skill } from 'src/engine/core-modules/skills/skills.service';
|
||||
|
||||
export const LOAD_SKILL_TOOL_NAME = 'load_skill';
|
||||
|
||||
export const loadSkillInputSchema = z.object({
|
||||
input: z.object({
|
||||
skillNames: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Names of the skills to load (e.g., ["workflow-building", "data-manipulation"])',
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export type LoadSkillInput = z.infer<typeof loadSkillInputSchema>['input'];
|
||||
|
||||
export type LoadSkillResult = {
|
||||
skills: Array<{
|
||||
name: string;
|
||||
label: string;
|
||||
content: string;
|
||||
}>;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type LoadSkillFunction = (names: string[]) => Skill[];
|
||||
|
||||
export const createLoadSkillTool = (loadSkills: LoadSkillFunction) => ({
|
||||
description:
|
||||
'Load specialized skills/expertise by name. Returns detailed instructions for workflows, data manipulation, dashboards, metadata, or research.',
|
||||
inputSchema: loadSkillInputSchema,
|
||||
execute: async (parameters: {
|
||||
input: LoadSkillInput;
|
||||
}): Promise<LoadSkillResult> => {
|
||||
const { skillNames } = parameters.input;
|
||||
|
||||
const skills = loadSkills(skillNames);
|
||||
|
||||
if (skills.length === 0) {
|
||||
return {
|
||||
skills: [],
|
||||
message: `No skills found with names: ${skillNames.join(', ')}. Available skills: workflow-building, data-manipulation, dashboard-building, metadata-building, research.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
skills: skills.map((skill) => ({
|
||||
name: skill.name,
|
||||
label: skill.label,
|
||||
content: skill.content,
|
||||
})),
|
||||
message: `Loaded ${skills.length} skill(s). Use the instructions above to guide your approach.`,
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user