Show tool execution messages in AI agent chat (#13117)
https://github.com/user-attachments/assets/c0a42726-50ac-496e-a993-9d6076a84a6a --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -6,6 +6,7 @@ import { Repository } from 'typeorm';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@ -115,6 +116,14 @@ describe('WorkspaceManagerService', () => {
|
||||
deleteObjectsMetadata: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AgentService,
|
||||
useValue: {
|
||||
createOneAgent: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ id: 'mock-agent-id' }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@ -0,0 +1,277 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { AgentChatMessageRole } from 'src/engine/metadata-modules/agent/agent-chat-message.entity';
|
||||
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
|
||||
const agentTableName = 'agent';
|
||||
const workspaceTableName = 'workspace';
|
||||
const agentChatThreadTableName = 'agentChatThread';
|
||||
const agentChatMessageTableName = 'agentChatMessage';
|
||||
|
||||
export const AGENT_DATA_SEED_IDS = {
|
||||
APPLE_DEFAULT_AGENT: '20202020-0000-4000-8000-000000000001',
|
||||
YCOMBINATOR_DEFAULT_AGENT: '20202020-0000-4000-8000-000000000002',
|
||||
};
|
||||
|
||||
export const AGENT_CHAT_THREAD_DATA_SEED_IDS = {
|
||||
APPLE_DEFAULT_THREAD: '20202020-0000-4000-8000-000000000011',
|
||||
YCOMBINATOR_DEFAULT_THREAD: '20202020-0000-4000-8000-000000000012',
|
||||
};
|
||||
|
||||
export const AGENT_CHAT_MESSAGE_DATA_SEED_IDS = {
|
||||
APPLE_MESSAGE_1: '20202020-0000-4000-8000-000000000021',
|
||||
APPLE_MESSAGE_2: '20202020-0000-4000-8000-000000000022',
|
||||
APPLE_MESSAGE_3: '20202020-0000-4000-8000-000000000023',
|
||||
APPLE_MESSAGE_4: '20202020-0000-4000-8000-000000000024',
|
||||
YCOMBINATOR_MESSAGE_1: '20202020-0000-4000-8000-000000000031',
|
||||
YCOMBINATOR_MESSAGE_2: '20202020-0000-4000-8000-000000000032',
|
||||
YCOMBINATOR_MESSAGE_3: '20202020-0000-4000-8000-000000000033',
|
||||
YCOMBINATOR_MESSAGE_4: '20202020-0000-4000-8000-000000000034',
|
||||
};
|
||||
|
||||
const seedAgentChatThreads = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
) => {
|
||||
let threadId: string;
|
||||
let userWorkspaceId: string;
|
||||
|
||||
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
||||
threadId = AGENT_CHAT_THREAD_DATA_SEED_IDS.APPLE_DEFAULT_THREAD;
|
||||
userWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM;
|
||||
} else if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
|
||||
threadId = AGENT_CHAT_THREAD_DATA_SEED_IDS.YCOMBINATOR_DEFAULT_THREAD;
|
||||
userWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM_ACME;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported workspace ID for agent chat thread seeding: ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentChatThreadTableName}`, [
|
||||
'id',
|
||||
'agentId',
|
||||
'userWorkspaceId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
id: threadId,
|
||||
agentId,
|
||||
userWorkspaceId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
|
||||
return threadId;
|
||||
};
|
||||
|
||||
const seedAgentChatMessages = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
threadId: string,
|
||||
) => {
|
||||
let messageIds: string[];
|
||||
let messages: Array<{
|
||||
id: string;
|
||||
threadId: string;
|
||||
role: AgentChatMessageRole;
|
||||
content: string;
|
||||
createdAt: Date;
|
||||
}>;
|
||||
|
||||
const now = new Date();
|
||||
const baseTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago
|
||||
|
||||
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
||||
messageIds = [
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.APPLE_MESSAGE_1,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.APPLE_MESSAGE_2,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.APPLE_MESSAGE_3,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.APPLE_MESSAGE_4,
|
||||
];
|
||||
messages = [
|
||||
{
|
||||
id: messageIds[0],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.USER,
|
||||
content:
|
||||
'Hello! Can you help me understand our current product roadmap and key metrics?',
|
||||
createdAt: new Date(baseTime.getTime()),
|
||||
},
|
||||
{
|
||||
id: messageIds[1],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.ASSISTANT,
|
||||
content:
|
||||
"Hello! I'd be happy to help you understand Apple's product roadmap and metrics. Based on your workspace data, I can see you have various projects and initiatives tracked. What specific aspect would you like to explore - product development timelines, user engagement metrics, or revenue targets?",
|
||||
createdAt: new Date(baseTime.getTime() + 5 * 60 * 1000), // 5 minutes later
|
||||
},
|
||||
{
|
||||
id: messageIds[2],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.USER,
|
||||
content:
|
||||
"I'd like to focus on our user engagement metrics and how they're trending over the last quarter.",
|
||||
createdAt: new Date(baseTime.getTime() + 10 * 60 * 1000), // 10 minutes later
|
||||
},
|
||||
{
|
||||
id: messageIds[3],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.ASSISTANT,
|
||||
content:
|
||||
'Great! Looking at your user engagement data, I can see several key trends from the last quarter. Your active user base has grown by 15%, with particularly strong engagement in the mobile app. Daily active users are averaging 2.3 million, and session duration has increased by 8%. Would you like me to dive deeper into any specific engagement metrics or create a detailed report?',
|
||||
createdAt: new Date(baseTime.getTime() + 15 * 60 * 1000), // 15 minutes later
|
||||
},
|
||||
];
|
||||
} else if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
|
||||
messageIds = [
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_1,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_2,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_3,
|
||||
AGENT_CHAT_MESSAGE_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_4,
|
||||
];
|
||||
messages = [
|
||||
{
|
||||
id: messageIds[0],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.USER,
|
||||
content:
|
||||
'What are the current startup trends and which companies in our portfolio are performing best?',
|
||||
createdAt: new Date(baseTime.getTime()),
|
||||
},
|
||||
{
|
||||
id: messageIds[1],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.ASSISTANT,
|
||||
content:
|
||||
'Hello! I can help you analyze startup trends and portfolio performance. From your YCombinator workspace data, I can see strong performance in AI/ML startups, particularly in the B2B SaaS space. Several companies are showing 40%+ month-over-month growth. Would you like me to provide specific company performance metrics or focus on broader industry trends?',
|
||||
createdAt: new Date(baseTime.getTime() + 3 * 60 * 1000), // 3 minutes later
|
||||
},
|
||||
{
|
||||
id: messageIds[2],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.USER,
|
||||
content:
|
||||
'Please focus on our top 5 performing companies and their key metrics.',
|
||||
createdAt: new Date(baseTime.getTime() + 8 * 60 * 1000), // 8 minutes later
|
||||
},
|
||||
{
|
||||
id: messageIds[3],
|
||||
threadId,
|
||||
role: AgentChatMessageRole.ASSISTANT,
|
||||
content:
|
||||
'Here are your top 5 performing portfolio companies: 1) TechFlow AI - 45% MoM growth, $2M ARR, 2) DataSync Pro - 38% MoM growth, $1.5M ARR, 3) CloudOps Solutions - 35% MoM growth, $3.2M ARR, 4) SecureNet - 32% MoM growth, $1.8M ARR, 5) HealthTech Plus - 28% MoM growth, $2.5M ARR. All are showing strong customer retention (>95%) and expanding market share. Would you like detailed breakdowns for any specific company?',
|
||||
createdAt: new Date(baseTime.getTime() + 12 * 60 * 1000), // 12 minutes later
|
||||
},
|
||||
];
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported workspace ID for agent chat message seeding: ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentChatMessageTableName}`, [
|
||||
'id',
|
||||
'threadId',
|
||||
'role',
|
||||
'content',
|
||||
'createdAt',
|
||||
])
|
||||
.orIgnore()
|
||||
.values(messages)
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const seedAgents = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
let agentId: string;
|
||||
let agentName: string;
|
||||
let agentLabel: string;
|
||||
let agentDescription: string;
|
||||
|
||||
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
||||
agentId = AGENT_DATA_SEED_IDS.APPLE_DEFAULT_AGENT;
|
||||
agentName = 'apple-ai-assistant';
|
||||
agentLabel = 'Apple AI Assistant';
|
||||
agentDescription =
|
||||
'AI assistant for Apple workspace to help with tasks, insights, and workflow guidance';
|
||||
} else if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
|
||||
agentId = AGENT_DATA_SEED_IDS.YCOMBINATOR_DEFAULT_AGENT;
|
||||
agentName = 'yc-ai-assistant';
|
||||
agentLabel = 'YC AI Assistant';
|
||||
agentDescription =
|
||||
'AI assistant for YCombinator workspace to help with tasks, insights, and workflow guidance';
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported workspace ID for agent seeding: ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentTableName}`, [
|
||||
'id',
|
||||
'name',
|
||||
'label',
|
||||
'description',
|
||||
'prompt',
|
||||
'modelId',
|
||||
'responseFormat',
|
||||
'workspaceId',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
id: agentId,
|
||||
name: agentName,
|
||||
label: agentLabel,
|
||||
description: agentDescription,
|
||||
prompt:
|
||||
'You are a helpful AI assistant for this workspace. Help users with their tasks, provide insights about their data, and guide them through workflows. Be concise but thorough in your responses.',
|
||||
modelId: 'auto',
|
||||
responseFormat: null,
|
||||
workspaceId,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(`${schemaName}.${workspaceTableName}`)
|
||||
.set({ defaultAgentId: agentId })
|
||||
.where('id = :workspaceId', { workspaceId })
|
||||
.execute();
|
||||
|
||||
const threadId = await seedAgentChatThreads(
|
||||
dataSource,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
agentId,
|
||||
);
|
||||
|
||||
await seedAgentChatMessages(dataSource, schemaName, workspaceId, threadId);
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util';
|
||||
import { seedAgents } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util';
|
||||
import { seedApiKeys } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-api-keys.util';
|
||||
import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util';
|
||||
import { seedUserWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
@ -33,6 +34,8 @@ export const seedCoreSchema = async ({
|
||||
await seedUsers(dataSource, schemaName);
|
||||
await seedUserWorkspaces(dataSource, schemaName, workspaceId);
|
||||
|
||||
await seedAgents(dataSource, schemaName, workspaceId);
|
||||
|
||||
await seedApiKeys(dataSource, schemaName, workspaceId);
|
||||
|
||||
if (shouldSeedFeatureFlags) {
|
||||
|
||||
@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
@ -33,6 +34,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
|
||||
WorkspaceHealthModule,
|
||||
FeatureFlagModule,
|
||||
PermissionsModule,
|
||||
AgentModule,
|
||||
TypeOrmModule.forFeature([UserWorkspace, Workspace], 'core'),
|
||||
RoleModule,
|
||||
UserRoleModule,
|
||||
|
||||
@ -3,9 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@ -42,6 +44,7 @@ export class WorkspaceManagerService {
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(RoleTargetsEntity, 'core')
|
||||
private readonly roleTargetsRepository: Repository<RoleTargetsEntity>,
|
||||
private readonly agentService: AgentService,
|
||||
) {}
|
||||
|
||||
public async init({
|
||||
@ -95,6 +98,17 @@ export class WorkspaceManagerService {
|
||||
`Permissions enabled took ${permissionsEnabledEnd - permissionsEnabledStart}ms`,
|
||||
);
|
||||
|
||||
if (featureFlags[FeatureFlagKey.IS_AI_ENABLED]) {
|
||||
const defaultAgentEnabledStart = performance.now();
|
||||
|
||||
await this.initDefaultAgent(workspaceId);
|
||||
const defaultAgentEnabledEnd = performance.now();
|
||||
|
||||
this.logger.log(
|
||||
`Default agent enabled took ${defaultAgentEnabledEnd - defaultAgentEnabledStart}ms`,
|
||||
);
|
||||
}
|
||||
|
||||
const prefillStandardObjectsStart = performance.now();
|
||||
|
||||
await this.prefillWorkspaceWithStandardObjectsRecords(
|
||||
@ -194,4 +208,21 @@ export class WorkspaceManagerService {
|
||||
defaultRoleId: memberRole.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async initDefaultAgent(workspaceId: string) {
|
||||
const agent = await this.agentService.createOneAgent(
|
||||
{
|
||||
label: 'Routing Agent',
|
||||
name: 'routing-agent',
|
||||
description: 'Default Routing Agent',
|
||||
prompt: '',
|
||||
modelId: 'auto',
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.workspaceRepository.update(workspaceId, {
|
||||
defaultAgentId: agent.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user