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.`,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -7,17 +7,18 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { SkillsModule } from 'src/engine/core-modules/skills/skills.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { ToolProviderModule } from 'src/engine/core-modules/tool-provider/tool-provider.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { AiAgentExecutionModule } from 'src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module';
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkflowToolsModule } from 'src/modules/workflow/workflow-tools/workflow-tools.module';
|
||||
|
||||
import { AgentChatController } from './controllers/agent-chat.controller';
|
||||
import { AgentChatThreadEntity } from './entities/agent-chat-thread.entity';
|
||||
@@ -34,13 +35,13 @@ import { ChatExecutionService } from './services/chat-execution.service';
|
||||
FileEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
AiAgentModule,
|
||||
AiAgentExecutionModule,
|
||||
ThrottlerModule,
|
||||
FeatureFlagModule,
|
||||
FileUploadModule,
|
||||
FileModule,
|
||||
PermissionsModule,
|
||||
SkillsModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceDomainsModule,
|
||||
@@ -49,6 +50,7 @@ import { ChatExecutionService } from './services/chat-execution.service';
|
||||
UserWorkspaceModule,
|
||||
AiBillingModule,
|
||||
ToolProviderModule,
|
||||
WorkflowToolsModule,
|
||||
],
|
||||
controllers: [AgentChatController],
|
||||
providers: [
|
||||
|
||||
+37
-50
@@ -15,22 +15,21 @@ import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { SkillsService } from 'src/engine/core-modules/skills/skills.service';
|
||||
import {
|
||||
type ToolIndexEntry,
|
||||
ToolRegistryService,
|
||||
} from 'src/engine/core-modules/tool-provider/services/tool-registry.service';
|
||||
import {
|
||||
AGENT_SEARCH_TOOL_NAME,
|
||||
createAgentSearchTool,
|
||||
createLoadSkillTool,
|
||||
createLoadToolsTool,
|
||||
type DynamicToolStore,
|
||||
LOAD_SKILL_TOOL_NAME,
|
||||
LOAD_TOOLS_TOOL_NAME,
|
||||
} from 'src/engine/core-modules/tool-provider/tools';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentActorContextService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-actor-context.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type BrowsingContextType } from 'src/engine/metadata-modules/ai/ai-agent/types/browsingContext.type';
|
||||
import { repairToolCall } from 'src/engine/metadata-modules/ai/ai-agent/utils/repair-tool-call.util';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/services/ai-billing.service';
|
||||
@@ -49,11 +48,8 @@ export type ChatExecutionOptions = {
|
||||
export type ChatExecutionResult = {
|
||||
stream: ReturnType<typeof streamText>;
|
||||
preloadedTools: string[];
|
||||
initialAgents: string[];
|
||||
};
|
||||
|
||||
const INITIAL_AGENTS_LIMIT = 2;
|
||||
|
||||
// Common tools to pre-load for quick access
|
||||
const COMMON_PRELOAD_TOOLS = ['http_request', 'search_help_center'];
|
||||
|
||||
@@ -63,7 +59,7 @@ export class ChatExecutionService {
|
||||
|
||||
constructor(
|
||||
private readonly toolRegistry: ToolRegistryService,
|
||||
private readonly agentService: AgentService,
|
||||
private readonly skillsService: SkillsService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
private readonly aiBillingService: AIBillingService,
|
||||
private readonly agentActorContextService: AgentActorContextService,
|
||||
@@ -84,21 +80,19 @@ export class ChatExecutionService {
|
||||
|
||||
const toolContext = { workspaceId: workspace.id, roleId, actorContext };
|
||||
|
||||
const lastUserMessage = this.getLastUserMessage(messages);
|
||||
|
||||
const contextString = browsingContext
|
||||
? this.buildContextFromBrowsingContext(workspace, browsingContext)
|
||||
: undefined;
|
||||
|
||||
const [toolCatalog, initialAgents] = await Promise.all([
|
||||
this.toolRegistry.buildToolIndex(workspace.id, roleId),
|
||||
this.agentService.searchAgents(lastUserMessage, workspace.id, {
|
||||
limit: INITIAL_AGENTS_LIMIT,
|
||||
}),
|
||||
]);
|
||||
const toolCatalog = await this.toolRegistry.buildToolIndex(
|
||||
workspace.id,
|
||||
roleId,
|
||||
);
|
||||
|
||||
const skillCatalog = this.skillsService.getAllSkills();
|
||||
|
||||
this.logger.log(
|
||||
`Built tool catalog with ${toolCatalog.length} tools, ${initialAgents.length} agents`,
|
||||
`Built tool catalog with ${toolCatalog.length} tools, ${skillCatalog.length} skills available`,
|
||||
);
|
||||
|
||||
const preloadedTools = await this.toolRegistry.getToolsByName(
|
||||
@@ -132,14 +126,14 @@ export class ChatExecutionService {
|
||||
this.logger.log(`Dynamically loaded tools: ${toolNames.join(', ')}`);
|
||||
},
|
||||
),
|
||||
[AGENT_SEARCH_TOOL_NAME]: createAgentSearchTool((query, options) =>
|
||||
this.agentService.searchAgents(query, workspace.id, options),
|
||||
[LOAD_SKILL_TOOL_NAME]: createLoadSkillTool((skillNames) =>
|
||||
this.skillsService.getSkillsByNames(skillNames),
|
||||
),
|
||||
};
|
||||
|
||||
const systemPrompt = this.buildSystemPrompt(
|
||||
toolCatalog,
|
||||
initialAgents,
|
||||
skillCatalog,
|
||||
preloadedToolNames,
|
||||
contextString,
|
||||
);
|
||||
@@ -187,7 +181,6 @@ export class ChatExecutionService {
|
||||
return {
|
||||
stream,
|
||||
preloadedTools: preloadedToolNames,
|
||||
initialAgents: initialAgents.map((a) => a.name),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -247,27 +240,9 @@ export class ChatExecutionService {
|
||||
return context;
|
||||
}
|
||||
|
||||
private getLastUserMessage(
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[],
|
||||
): string {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i];
|
||||
|
||||
if (message.role === 'user') {
|
||||
const textPart = message.parts.find((part) => part.type === 'text');
|
||||
|
||||
if (textPart && 'text' in textPart) {
|
||||
return textPart.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private buildSystemPrompt(
|
||||
toolCatalog: ToolIndexEntry[],
|
||||
agents: AgentEntity[],
|
||||
skillCatalog: Array<{ name: string; label: string; description: string }>,
|
||||
preloadedTools: string[],
|
||||
contextString?: string,
|
||||
): string {
|
||||
@@ -276,15 +251,8 @@ export class ChatExecutionService {
|
||||
CHAT_SYSTEM_PROMPTS.RESPONSE_FORMAT,
|
||||
];
|
||||
|
||||
if (agents.length > 0) {
|
||||
const skillsSection = agents
|
||||
.map((agent) => `## ${agent.label} Expertise\n${agent.prompt}`)
|
||||
.join('\n\n');
|
||||
|
||||
parts.push(`\nYou have the following expertise:\n\n${skillsSection}`);
|
||||
}
|
||||
|
||||
parts.push(this.buildToolCatalogSection(toolCatalog, preloadedTools));
|
||||
parts.push(this.buildSkillCatalogSection(skillCatalog));
|
||||
|
||||
if (contextString) {
|
||||
parts.push(
|
||||
@@ -295,6 +263,26 @@ export class ChatExecutionService {
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
private buildSkillCatalogSection(
|
||||
skillCatalog: Array<{ name: string; label: string; description: string }>,
|
||||
): string {
|
||||
if (skillCatalog.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const skillsList = skillCatalog
|
||||
.map((skill) => `- \`${skill.name}\`: ${skill.description}`)
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
## Available Skills
|
||||
|
||||
Skills provide detailed expertise for specialized tasks. Load a skill before attempting complex operations.
|
||||
To load a skill, call \`${LOAD_SKILL_TOOL_NAME}\` with the skill name(s).
|
||||
|
||||
${skillsList}`;
|
||||
}
|
||||
|
||||
private buildToolCatalogSection(
|
||||
toolCatalog: ToolIndexEntry[],
|
||||
preloadedTools: string[],
|
||||
@@ -357,8 +345,7 @@ ${tools
|
||||
### How to Use Tools
|
||||
1. **Web search** (\`web_search\`): Use for ANY request requiring current/real-time information from the internet
|
||||
2. **Pre-loaded tools** (marked with ✓): Use directly
|
||||
3. **Other tools**: First call \`${LOAD_TOOLS_TOOL_NAME}({toolNames: ["tool_name"]})\`, then use the tool
|
||||
4. **Agent expertise**: Call \`${AGENT_SEARCH_TOOL_NAME}\` to load specialized knowledge for workflows, etc.`);
|
||||
3. **Other tools**: First call \`${LOAD_TOOLS_TOOL_NAME}({toolNames: ["tool_name"]})\`, then use the tool`);
|
||||
|
||||
return sections.join('\n');
|
||||
}
|
||||
|
||||
+4
-4
@@ -146,7 +146,7 @@ export class FieldMetadataToolsFactory {
|
||||
|
||||
generateTools(workspaceId: string): ToolSet {
|
||||
return {
|
||||
'get-field-metadata': {
|
||||
get_field_metadata: {
|
||||
description:
|
||||
'Find fields metadata. Retrieve information about the fields of objects in the workspace data model.',
|
||||
inputSchema: GetFieldMetadataInputSchema,
|
||||
@@ -169,7 +169,7 @@ export class FieldMetadataToolsFactory {
|
||||
});
|
||||
},
|
||||
},
|
||||
'create-field-metadata': {
|
||||
create_field_metadata: {
|
||||
description:
|
||||
'Create a new field metadata on an object. Specify the objectMetadataId and field properties.',
|
||||
inputSchema: CreateFieldMetadataInputSchema,
|
||||
@@ -209,7 +209,7 @@ export class FieldMetadataToolsFactory {
|
||||
}
|
||||
},
|
||||
},
|
||||
'update-field-metadata': {
|
||||
update_field_metadata: {
|
||||
description:
|
||||
'Update an existing field metadata. Provide the field ID and the properties to update.',
|
||||
inputSchema: UpdateFieldMetadataInputSchema,
|
||||
@@ -249,7 +249,7 @@ export class FieldMetadataToolsFactory {
|
||||
}
|
||||
},
|
||||
},
|
||||
'delete-field-metadata': {
|
||||
delete_field_metadata: {
|
||||
description: 'Delete a field metadata by its ID.',
|
||||
inputSchema: DeleteFieldMetadataInputSchema,
|
||||
execute: async (parameters: { input: { id: string } }) => {
|
||||
|
||||
+4
-4
@@ -123,7 +123,7 @@ export class ObjectMetadataToolsFactory {
|
||||
|
||||
generateTools(workspaceId: string): ToolSet {
|
||||
return {
|
||||
'get-object-metadata': {
|
||||
get_object_metadata: {
|
||||
description:
|
||||
'Find objects metadata. Retrieve information about the data model objects in the workspace.',
|
||||
inputSchema: GetObjectMetadataInputSchema,
|
||||
@@ -146,7 +146,7 @@ export class ObjectMetadataToolsFactory {
|
||||
);
|
||||
},
|
||||
},
|
||||
'create-object-metadata': {
|
||||
create_object_metadata: {
|
||||
description:
|
||||
'Create a new object metadata in the workspace data model.',
|
||||
inputSchema: CreateObjectMetadataInputSchema,
|
||||
@@ -183,7 +183,7 @@ export class ObjectMetadataToolsFactory {
|
||||
}
|
||||
},
|
||||
},
|
||||
'update-object-metadata': {
|
||||
update_object_metadata: {
|
||||
description:
|
||||
'Update an existing object metadata. Provide the object ID and the fields to update.',
|
||||
inputSchema: UpdateObjectMetadataInputSchema,
|
||||
@@ -223,7 +223,7 @@ export class ObjectMetadataToolsFactory {
|
||||
}
|
||||
},
|
||||
},
|
||||
'delete-object-metadata': {
|
||||
delete_object_metadata: {
|
||||
description:
|
||||
'Delete an object metadata by its ID. This will also delete all associated fields.',
|
||||
inputSchema: DeleteObjectMetadataInputSchema,
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
import { DASHBOARD_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/dashboard-manager-role';
|
||||
|
||||
export const DASHBOARD_BUILDER_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000006',
|
||||
name: 'dashboard-builder',
|
||||
label: 'Dashboard Builder',
|
||||
description: 'AI agent specialized in creating and managing dashboards',
|
||||
icon: 'IconLayoutDashboard',
|
||||
applicationId: null,
|
||||
prompt: `You are a Dashboard Builder Agent for Twenty. 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.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
standardRoleId: DASHBOARD_MANAGER_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
outputStrategy: 'direct',
|
||||
evaluationInputs: [],
|
||||
};
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
import { DATA_MANIPULATOR_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role';
|
||||
|
||||
export const DATA_MANIPULATOR_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000003',
|
||||
name: 'data-manipulator',
|
||||
label: 'Data Manipulator',
|
||||
description:
|
||||
'AI agent specialized in exploring, reading, creating, updating, and managing data across all objects',
|
||||
icon: 'IconEdit',
|
||||
applicationId: null,
|
||||
prompt: `You are a Data Manipulator Agent for Twenty. 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.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
standardRoleId: DATA_MANIPULATOR_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
evaluationInputs: [],
|
||||
};
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
import { DATA_MODEL_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-model-manager-role';
|
||||
|
||||
export const METADATA_BUILDER_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000007',
|
||||
name: 'metadata-builder',
|
||||
label: 'Metadata Builder',
|
||||
description:
|
||||
'AI agent specialized in modifying the workspace SCHEMA/DATA MODEL - creating new object types, adding fields to objects, and managing object structure (NOT for creating data records)',
|
||||
icon: 'IconDatabaseEdit',
|
||||
applicationId: null,
|
||||
prompt: `You are a Metadata Builder Agent for Twenty. 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.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
standardRoleId: DATA_MODEL_MANAGER_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
outputStrategy: 'direct',
|
||||
evaluationInputs: [
|
||||
'Create a custom object called "Project" with fields for name, description, start date, and status',
|
||||
'Add a currency field called "budget" to the Project object',
|
||||
'Create a relation between Project and Company so each project belongs to a company',
|
||||
'Add a multi-select field for project tags with options: urgent, internal, client-facing',
|
||||
'Update the Project object description to explain what it tracks',
|
||||
'Create a "Task" object that relates to both Project and Person',
|
||||
'Add a rating field to track project priority from 1-5 stars',
|
||||
'Create a custom object for tracking Invoices with amount, date, and status fields',
|
||||
'Add a link field to the Company object for their website',
|
||||
'Create a relation between Person and Company for the account manager',
|
||||
],
|
||||
};
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
|
||||
export const RESEARCHER_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000005',
|
||||
name: 'researcher',
|
||||
label: 'Researcher',
|
||||
description:
|
||||
'AI agent specialized in researching information, finding facts, and gathering data from the web',
|
||||
icon: 'IconSearch',
|
||||
applicationId: null,
|
||||
prompt: `You are a Researcher Agent for Twenty. 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.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
modelConfiguration: {
|
||||
webSearch: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
evaluationInputs: [],
|
||||
};
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
import { WORKFLOW_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role';
|
||||
|
||||
export const WORKFLOW_BUILDER_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000001',
|
||||
name: 'workflow-builder',
|
||||
label: 'Workflow Builder',
|
||||
description: 'AI agent specialized in creating and managing workflows',
|
||||
icon: 'IconSettingsAutomation',
|
||||
applicationId: null,
|
||||
prompt: `You are a Workflow Builder Agent for Twenty. 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: 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.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
standardRoleId: WORKFLOW_MANAGER_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
outputStrategy: 'direct',
|
||||
evaluationInputs: [
|
||||
'Build a workflow that runs everyday at 9:00 AM, finds the companies added in the last 24 hours, and create task title Welcome {companyName} for each',
|
||||
'Create a workflow that listens to company creation events and makes an http call to companies.twenty.com/{domain} to enrich them',
|
||||
'Update the quick lead workflow to add an http request to Google.com as the last step',
|
||||
'when a new lead is created, automatically send an email to the sales team with the lead details',
|
||||
'I need a workflow that runs every monday morning and creates a weekly summary report of all closed deals',
|
||||
'can you make a workflow to automatically assign new oppurtunities to sales reps based on territory?',
|
||||
'create workflow that updates contact status to inactive if theres no activity for 90 days',
|
||||
'Build automation to send followup email 3 days after first contact with prospect',
|
||||
'i want to automatically create a task for account manager when deal reaches negotiation stage',
|
||||
'setup a workflow that enriches company data from clearbit when new account is created',
|
||||
'make a workflow to notify slack channel when deal amount is over $50k',
|
||||
'need workflow that runs daily and finds all overdue tasks then sends reminder emails',
|
||||
'Create automation to update lead score when contact opens email or clicks link',
|
||||
'workflow to automatically create renewal opportunity 60 days before contract end date',
|
||||
'can you build a flow that copies contact info to company record when deal is won?',
|
||||
'I need to send a survey email 7 days after deal closes',
|
||||
'make workflow that assigns leads round-robin style to available sales reps',
|
||||
'create automation to tag contacts as "hot lead" when they visit pricing page 3 times',
|
||||
'workflow that escalates support tickets to manager if not resolved in 48 hours',
|
||||
'need a workflow to sync new contacts to mailchimp mailing list',
|
||||
'build flow that updates deal stage to lost if no activity for 30 days',
|
||||
'create workflow that sends birthday email to contacts on thier birthday',
|
||||
'can i get a workflow that creates calendar event when meeting is scheduled with prospect',
|
||||
'workflow to automatically generate quote pdf when opportunity moves to proposal stage',
|
||||
],
|
||||
};
|
||||
-10
@@ -1,16 +1,6 @@
|
||||
import { DASHBOARD_BUILDER_AGENT } from './agents/dashboard-builder-agent';
|
||||
import { DATA_MANIPULATOR_AGENT } from './agents/data-manipulator-agent';
|
||||
import { HELPER_AGENT } from './agents/helper-agent';
|
||||
import { METADATA_BUILDER_AGENT } from './agents/metadata-builder-agent';
|
||||
import { RESEARCHER_AGENT } from './agents/researcher-agent';
|
||||
import { WORKFLOW_BUILDER_AGENT } from './agents/workflow-builder-agent';
|
||||
import { type StandardAgentDefinition } from './types/standard-agent-definition.interface';
|
||||
|
||||
export const STANDARD_AGENT_DEFINITIONS = [
|
||||
WORKFLOW_BUILDER_AGENT,
|
||||
DATA_MANIPULATOR_AGENT,
|
||||
DASHBOARD_BUILDER_AGENT,
|
||||
HELPER_AGENT,
|
||||
RESEARCHER_AGENT,
|
||||
METADATA_BUILDER_AGENT,
|
||||
] as const satisfies StandardAgentDefinition[];
|
||||
|
||||
+7
@@ -177,6 +177,13 @@ This is the most efficient way for AI to create workflows as it handles all the
|
||||
trigger: parameters.trigger,
|
||||
steps: parameters.steps,
|
||||
},
|
||||
recordReferences: [
|
||||
{
|
||||
objectNameSingular: 'workflow',
|
||||
recordId: workflowId,
|
||||
displayName: parameters.name,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
|
||||
+13
-3
@@ -1,7 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||
import { ToolGeneratorModule } from 'src/engine/core-modules/tool-generator/tool-generator.module';
|
||||
import { WORKFLOW_TOOL_SERVICE_TOKEN } from 'src/engine/core-modules/tool-provider/constants/workflow-tool-service.token';
|
||||
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
|
||||
import { WorkflowVersionEdgeModule } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module';
|
||||
import { WorkflowVersionStepModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module';
|
||||
@@ -10,6 +11,9 @@ import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/wor
|
||||
|
||||
import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace-service';
|
||||
|
||||
// Global module to make WORKFLOW_TOOL_SERVICE_TOKEN available to ToolProviderModule
|
||||
// without creating a circular dependency (ToolProviderModule cannot import this module directly)
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
WorkflowVersionStepModule,
|
||||
@@ -20,7 +24,13 @@ import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace
|
||||
RecordPositionModule,
|
||||
ToolGeneratorModule,
|
||||
],
|
||||
providers: [WorkflowToolWorkspaceService],
|
||||
exports: [WorkflowToolWorkspaceService],
|
||||
providers: [
|
||||
WorkflowToolWorkspaceService,
|
||||
{
|
||||
provide: WORKFLOW_TOOL_SERVICE_TOKEN,
|
||||
useExisting: WorkflowToolWorkspaceService,
|
||||
},
|
||||
],
|
||||
exports: [WorkflowToolWorkspaceService, WORKFLOW_TOOL_SERVICE_TOKEN],
|
||||
})
|
||||
export class WorkflowToolsModule {}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ exports[`Agent deletion should fail should fail when attempting to delete a stan
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"id": Any<String>,
|
||||
"name": "dashboard-builder",
|
||||
"name": "helper",
|
||||
},
|
||||
"status": "fail",
|
||||
"type": "delete_agent",
|
||||
|
||||
+4
-4
@@ -50,16 +50,16 @@ describe('Agent deletion should fail', () => {
|
||||
gqlFields: 'id name isCustom',
|
||||
});
|
||||
|
||||
const dashboardBuilderAgent = data.findManyAgents.find(
|
||||
(agent) => agent.name === 'dashboard-builder' && agent.isCustom === false,
|
||||
const helperAgent = data.findManyAgents.find(
|
||||
(agent) => agent.name === 'helper' && agent.isCustom === false,
|
||||
);
|
||||
|
||||
expect(dashboardBuilderAgent).toBeDefined();
|
||||
expect(helperAgent).toBeDefined();
|
||||
|
||||
const { errors } = await deleteOneAgent({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: dashboardBuilderAgent!.id,
|
||||
id: helperAgent!.id,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -205,16 +205,16 @@ describe('Agent update should fail', () => {
|
||||
gqlFields: 'id name isCustom',
|
||||
});
|
||||
|
||||
const dashboardBuilderAgent = data.findManyAgents.find(
|
||||
(agent) => agent.name === 'dashboard-builder' && agent.isCustom === false,
|
||||
const helperAgent = data.findManyAgents.find(
|
||||
(agent) => agent.name === 'helper' && agent.isCustom === false,
|
||||
);
|
||||
|
||||
expect(dashboardBuilderAgent).toBeDefined();
|
||||
expect(helperAgent).toBeDefined();
|
||||
|
||||
const { errors } = await updateOneAgent({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: dashboardBuilderAgent!.id,
|
||||
id: helperAgent!.id,
|
||||
label: 'Attempted Update to Standard Agent',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user