feat: Add Agent Evaluation System and Refactor AI Modules (#16111)
## Summary This PR introduces a comprehensive agent evaluation system and refactors the AI module structure for better organization. ## Key Changes ### 🎯 Agent Evaluation System - Added **Agent Turn Evaluation** entities, DTOs, and database schema - New GraphQL mutations: `evaluateAgentTurn` and `runEvaluationInput` - Added `evaluationInputs` field to Agent entity for storing test inputs - New `AgentTurnGraderService` for automatic turn evaluation - Added evaluation UI with new **Evals** and **Logs** tabs in agent detail pages ### 🏗️ Entity & Module Refactoring - Renamed `AgentChatMessage` → `AgentMessage` for clarity - Consolidated chat entities: `AgentMessage`, `AgentTurn`, and `AgentChatThread` - Reorganized AI modules under `ai/` subdirectory structure - Updated imports across codebase to reflect new module paths ### 🤖 New Agents & Roles - Added **Dashboard Builder Agent** for dashboard creation and management - Added **Dashboard Manager Role** with appropriate permissions - Updated role permissions to be more granular (users vs agents vs API keys) ### 🔐 Permission System Updates - Added `HTTP_REQUEST_TOOL` permission flag - Updated Workflow Manager role permissions (restricted tool access) - Enhanced permission flag types to differentiate between user/agent/API key contexts - Added `isRelevantForAgents`, `isRelevantForApiKeys`, `isRelevantForUsers` to permission flags ### 📨 Message Role Enhancement - Added `system` role to `AgentMessageRole` enum (alongside user/assistant) - Updated message handling to support system prompts ### 🎨 UI/UX Improvements - New tabs in agent detail: **Evals** and **Logs** - Added turn detail page: `/ai/agents/:agentId/turns/:turnId` - Fixed text overflow in `SettingsListItemCardContent` - Updated role applicability labels ("Assignable to Workspace Members") ### 🛠️ Technical Improvements - Fixed Zod schema validation for UUID and Date fields (use string validators) - Updated `ToolRegistryService` to properly register HTTP tool with permission flag - Enhanced error handling in agent execution services - Updated database migrations for new entity schema ## Database Migrations - `1764210000000-add-system-role-to-agent-message.ts` - `1764220000000-add-evaluation-inputs-to-agent.ts` - `1764200000000-add-agent-turn-evaluation.ts` - `1764100000000-refactor-agent-chat-entities.ts` ## Testing - [ ] Agent evaluation flow tested - [ ] Dashboard Builder agent tested - [ ] Permission system validated - [ ] UI tabs and navigation tested - [ ] Database migrations run successfully ## Breaking Changes ⚠️ **Entity Rename**: `AgentChatMessage` renamed to `AgentMessage` - GraphQL queries need updating ## Related Issues <!-- Link any related issues here --> ## Screenshots <!-- Add screenshots if applicable -->
This commit is contained in:
-23
@@ -1,23 +0,0 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
import { AgentChatMessagePartDTO } from './agent-chat-message-part.dto';
|
||||
|
||||
@ObjectType('AgentChatMessage')
|
||||
export class AgentChatMessageDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
threadId: string;
|
||||
|
||||
@Field()
|
||||
role: 'user' | 'assistant';
|
||||
|
||||
@Field(() => [AgentChatMessagePartDTO])
|
||||
parts: AgentChatMessagePartDTO[];
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-thread.entity';
|
||||
|
||||
import { AgentChatMessagePartEntity } from './agent-chat-message-part.entity';
|
||||
|
||||
export enum AgentChatMessageRole {
|
||||
USER = 'user',
|
||||
ASSISTANT = 'assistant',
|
||||
}
|
||||
|
||||
@Entity('agentChatMessage')
|
||||
export class AgentChatMessageEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column('uuid')
|
||||
@Index()
|
||||
threadId: string;
|
||||
|
||||
@ManyToOne(() => AgentChatThreadEntity, (thread) => thread.messages, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'threadId' })
|
||||
thread: Relation<AgentChatThreadEntity>;
|
||||
|
||||
@Column({ type: 'enum', enum: AgentChatMessageRole })
|
||||
role: AgentChatMessageRole;
|
||||
|
||||
@OneToMany(() => AgentChatMessagePartEntity, (part) => part.message)
|
||||
parts: Relation<AgentChatMessagePartEntity[]>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai-tools/ai-tools.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
import { AiRouterService } from './ai-router.service';
|
||||
|
||||
import { AiRouterPlanGeneratorService } from './services/ai-router-plan-generator.service';
|
||||
import { AiRouterStrategyDeciderService } from './services/ai-router-strategy-decider.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AgentEntity, WorkspaceEntity]),
|
||||
AiModelsModule,
|
||||
AiToolsModule,
|
||||
ObjectMetadataModule,
|
||||
],
|
||||
providers: [
|
||||
AiRouterService,
|
||||
AiRouterStrategyDeciderService,
|
||||
AiRouterPlanGeneratorService,
|
||||
],
|
||||
exports: [AiRouterService],
|
||||
})
|
||||
export class AiRouterModule {}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai/ai-tools/ai-tools.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
|
||||
import { AgentMessagePartEntity } from './entities/agent-message-part.entity';
|
||||
import { AgentMessageEntity } from './entities/agent-message.entity';
|
||||
import { AgentTurnEntity } from './entities/agent-turn.entity';
|
||||
import { AgentAsyncExecutorService } from './services/agent-async-executor.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AiBillingModule,
|
||||
AiModelsModule,
|
||||
AiToolsModule,
|
||||
TypeOrmModule.forFeature([
|
||||
AgentMessageEntity,
|
||||
AgentMessagePartEntity,
|
||||
AgentTurnEntity,
|
||||
RoleTargetsEntity,
|
||||
]),
|
||||
],
|
||||
providers: [AgentAsyncExecutorService],
|
||||
exports: [
|
||||
AgentAsyncExecutorService,
|
||||
TypeOrmModule.forFeature([
|
||||
AgentMessageEntity,
|
||||
AgentMessagePartEntity,
|
||||
AgentTurnEntity,
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AiAgentExecutionModule {}
|
||||
+5
-4
@@ -1,12 +1,12 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { JSONValue } from 'ai';
|
||||
import { IsDateString } from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('AgentChatMessagePart')
|
||||
export class AgentChatMessagePartDTO {
|
||||
@ObjectType('AgentMessagePart')
|
||||
export class AgentMessagePartDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@@ -77,8 +77,9 @@ export class AgentChatMessagePartDTO {
|
||||
fileUrl?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
providerMetadata?: Record<string, Record<string, JSONValue>>;
|
||||
providerMetadata?: Record<string, unknown>;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDateString } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AgentMessagePartDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/agent-message-part.dto';
|
||||
|
||||
@ObjectType('AgentMessage')
|
||||
export class AgentMessageDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
threadId: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
turnId: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
agentId: string | null;
|
||||
|
||||
@Field()
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
|
||||
@Field(() => [AgentMessagePartDTO])
|
||||
parts: AgentMessagePartDTO[];
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDateString } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AgentMessageDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/agent-message.dto';
|
||||
import { AgentTurnEvaluationDTO } from 'src/engine/metadata-modules/ai/ai-agent-monitor/dtos/agent-turn-evaluation.dto';
|
||||
|
||||
@ObjectType('AgentTurn')
|
||||
export class AgentTurnDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
threadId: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
agentId: string | null;
|
||||
|
||||
@Field(() => [AgentTurnEvaluationDTO])
|
||||
evaluations: AgentTurnEvaluationDTO[];
|
||||
|
||||
@Field(() => [AgentMessageDTO])
|
||||
messages: AgentMessageDTO[];
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
+5
-5
@@ -10,10 +10,10 @@ import {
|
||||
Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { AgentChatMessageEntity } from './agent-chat-message.entity';
|
||||
import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
|
||||
@Entity('agentChatMessagePart')
|
||||
export class AgentChatMessagePartEntity {
|
||||
@Entity('agentMessagePart')
|
||||
export class AgentMessagePartEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@@ -21,11 +21,11 @@ export class AgentChatMessagePartEntity {
|
||||
@Index()
|
||||
messageId: string;
|
||||
|
||||
@ManyToOne(() => AgentChatMessageEntity, (message) => message.parts, {
|
||||
@ManyToOne(() => AgentMessageEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'messageId' })
|
||||
message: Relation<AgentChatMessageEntity>;
|
||||
message: Relation<AgentMessageEntity>;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
orderIndex: number;
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { AgentMessagePartEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
|
||||
export enum AgentMessageRole {
|
||||
SYSTEM = 'system',
|
||||
USER = 'user',
|
||||
ASSISTANT = 'assistant',
|
||||
}
|
||||
|
||||
@Entity('agentMessage')
|
||||
export class AgentMessageEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column('uuid')
|
||||
@Index()
|
||||
threadId: string;
|
||||
|
||||
@ManyToOne(() => AgentChatThreadEntity, (thread) => thread.messages, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'threadId' })
|
||||
thread: Relation<AgentChatThreadEntity>;
|
||||
|
||||
@Column('uuid')
|
||||
@Index()
|
||||
turnId: string;
|
||||
|
||||
@ManyToOne(() => AgentTurnEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'turnId' })
|
||||
turn: Relation<AgentTurnEntity>;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
@Index()
|
||||
agentId: string | null;
|
||||
|
||||
@Column({ type: 'enum', enum: AgentMessageRole })
|
||||
role: AgentMessageRole;
|
||||
|
||||
@OneToMany(() => AgentMessagePartEntity, (part) => part.message)
|
||||
parts: Relation<AgentMessagePartEntity[]>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
import { AgentTurnEvaluationEntity } from 'src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
|
||||
@Entity('agentTurn')
|
||||
export class AgentTurnEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column('uuid')
|
||||
@Index()
|
||||
threadId: string;
|
||||
|
||||
@ManyToOne(() => AgentChatThreadEntity, (thread) => thread.turns, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'threadId' })
|
||||
thread: Relation<AgentChatThreadEntity>;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
@Index()
|
||||
agentId: string | null;
|
||||
|
||||
@OneToMany(() => AgentMessageEntity, (message) => message.turn)
|
||||
messages: Relation<AgentMessageEntity[]>;
|
||||
|
||||
@OneToMany(() => AgentTurnEvaluationEntity, (evaluation) => evaluation.turn)
|
||||
evaluations: Relation<AgentTurnEvaluationEntity[]>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import {
|
||||
generateObject,
|
||||
generateText,
|
||||
jsonSchema,
|
||||
stepCountIs,
|
||||
ToolSet,
|
||||
} from 'ai';
|
||||
import { type ActorMetadata } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const';
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-system-prompts.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AgentExecutionResult } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool.service';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
|
||||
@Injectable()
|
||||
export class AgentAsyncExecutorService {
|
||||
private readonly logger = new Logger(AgentAsyncExecutorService.name);
|
||||
constructor(
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
private readonly toolAdapterService: ToolAdapterService,
|
||||
@InjectRepository(RoleTargetsEntity)
|
||||
private readonly roleTargetsRepository: Repository<RoleTargetsEntity>,
|
||||
private readonly toolService: ToolService,
|
||||
) {}
|
||||
|
||||
private async getTools(
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
actorContext?: ActorMetadata,
|
||||
rolePermissionConfig?: RolePermissionConfig,
|
||||
): Promise<ToolSet> {
|
||||
const roleTarget = await this.roleTargetsRepository.findOne({
|
||||
where: {
|
||||
agentId: agentId,
|
||||
workspaceId,
|
||||
},
|
||||
select: ['roleId'],
|
||||
});
|
||||
|
||||
const agentRoleId = roleTarget?.roleId;
|
||||
|
||||
if (!rolePermissionConfig && !agentRoleId) {
|
||||
return await this.toolAdapterService.getTools();
|
||||
}
|
||||
|
||||
let effectiveRoleContext: RolePermissionConfig;
|
||||
|
||||
if (
|
||||
rolePermissionConfig &&
|
||||
('intersectionOf' in rolePermissionConfig ||
|
||||
'unionOf' in rolePermissionConfig)
|
||||
) {
|
||||
effectiveRoleContext = rolePermissionConfig;
|
||||
} else if (agentRoleId) {
|
||||
effectiveRoleContext = { unionOf: [agentRoleId] };
|
||||
} else {
|
||||
return await this.toolAdapterService.getTools();
|
||||
}
|
||||
|
||||
const actionTools = await this.toolAdapterService.getTools(
|
||||
effectiveRoleContext,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const databaseTools = await this.toolService.listTools(
|
||||
effectiveRoleContext,
|
||||
workspaceId,
|
||||
actorContext,
|
||||
);
|
||||
|
||||
return {
|
||||
...databaseTools,
|
||||
...actionTools,
|
||||
};
|
||||
}
|
||||
|
||||
async executeAgent({
|
||||
agent,
|
||||
userPrompt,
|
||||
actorContext,
|
||||
rolePermissionConfig,
|
||||
}: {
|
||||
agent: AgentEntity | null;
|
||||
userPrompt: string;
|
||||
actorContext?: ActorMetadata;
|
||||
rolePermissionConfig?: RolePermissionConfig;
|
||||
}): Promise<AgentExecutionResult> {
|
||||
try {
|
||||
const registeredModel =
|
||||
await this.aiModelRegistryService.resolveModelForAgent(agent);
|
||||
|
||||
const tools = agent
|
||||
? await this.getTools(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
actorContext,
|
||||
rolePermissionConfig,
|
||||
)
|
||||
: {};
|
||||
|
||||
this.logger.log(`Generated ${Object.keys(tools).length} tools for agent`);
|
||||
|
||||
const textResponse = await generateText({
|
||||
system: `${AGENT_SYSTEM_PROMPTS.BASE}\n${AGENT_SYSTEM_PROMPTS.WORKFLOW_ADDITIONS}\n\n${agent ? agent.prompt : ''}`,
|
||||
tools,
|
||||
model: registeredModel.model,
|
||||
prompt: userPrompt,
|
||||
stopWhen: stepCountIs(AGENT_CONFIG.MAX_STEPS),
|
||||
experimental_telemetry: AI_TELEMETRY_CONFIG,
|
||||
});
|
||||
|
||||
const agentSchema =
|
||||
agent?.responseFormat?.type === 'json'
|
||||
? agent.responseFormat.schema
|
||||
: undefined;
|
||||
|
||||
if (!agentSchema) {
|
||||
return {
|
||||
result: { response: textResponse.text },
|
||||
usage: textResponse.usage,
|
||||
};
|
||||
}
|
||||
|
||||
const output = await generateObject({
|
||||
system: AGENT_SYSTEM_PROMPTS.OUTPUT_GENERATOR,
|
||||
model: registeredModel.model,
|
||||
prompt: `Based on the following execution results, generate the structured output according to the schema:
|
||||
|
||||
Execution Results: ${textResponse.text}
|
||||
|
||||
Please generate the structured output based on the execution results and context above.`,
|
||||
schema: jsonSchema(agentSchema),
|
||||
experimental_telemetry: AI_TELEMETRY_CONFIG,
|
||||
});
|
||||
|
||||
return {
|
||||
result: output.object as object,
|
||||
usage: {
|
||||
inputTokens:
|
||||
(textResponse.usage?.inputTokens ?? 0) +
|
||||
(output.usage?.inputTokens ?? 0),
|
||||
outputTokens:
|
||||
(textResponse.usage?.outputTokens ?? 0) +
|
||||
(output.usage?.outputTokens ?? 0),
|
||||
totalTokens:
|
||||
(textResponse.usage?.totalTokens ?? 0) +
|
||||
(output.usage?.totalTokens ?? 0),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AgentException) {
|
||||
throw error;
|
||||
}
|
||||
throw new AgentException(
|
||||
error instanceof Error ? error.message : 'Agent execution failed',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { type ToolUIPart } from 'ai';
|
||||
import { type ExtendedUIMessagePart } from 'twenty-shared/ai';
|
||||
|
||||
import { type AgentChatMessagePartEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message-part.entity';
|
||||
import { type AgentMessagePartEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity';
|
||||
|
||||
const isToolPart = (part: ExtendedUIMessagePart): part is ToolUIPart => {
|
||||
return part.type.includes('tool-') && 'toolCallId' in part;
|
||||
@@ -10,9 +10,9 @@ const isToolPart = (part: ExtendedUIMessagePart): part is ToolUIPart => {
|
||||
export const mapUIMessagePartsToDBParts = (
|
||||
uiMessageParts: ExtendedUIMessagePart[],
|
||||
messageId: string,
|
||||
): Partial<AgentChatMessagePartEntity>[] => {
|
||||
): Partial<AgentMessagePartEntity>[] => {
|
||||
return uiMessageParts.map((part, index) => {
|
||||
const basePart: Partial<AgentChatMessagePartEntity> = {
|
||||
const basePart: Partial<AgentMessagePartEntity> = {
|
||||
messageId,
|
||||
orderIndex: index,
|
||||
type: part.type,
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
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 { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
|
||||
import { AgentTurnEvaluationEntity } from './entities/agent-turn-evaluation.entity';
|
||||
import { AgentTurnResolver } from './resolvers/agent-turn.resolver';
|
||||
import { AgentTurnGraderService } from './services/agent-turn-grader.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
AgentTurnEvaluationEntity,
|
||||
AgentEntity,
|
||||
AgentChatThreadEntity,
|
||||
]),
|
||||
AiAgentModule,
|
||||
AiAgentExecutionModule,
|
||||
AiChatModule,
|
||||
AiModelsModule,
|
||||
PermissionsModule,
|
||||
],
|
||||
providers: [AgentTurnGraderService, AgentTurnResolver],
|
||||
exports: [AgentTurnGraderService],
|
||||
})
|
||||
export class AiAgentMonitorModule {}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDateString } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('AgentTurnEvaluation')
|
||||
export class AgentTurnEvaluationDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
turnId: string;
|
||||
|
||||
@Field(() => Int)
|
||||
score: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
comment?: string;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
|
||||
@Entity('agentTurnEvaluation')
|
||||
export class AgentTurnEvaluationEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column('uuid')
|
||||
@Index()
|
||||
turnId: string;
|
||||
|
||||
@ManyToOne(() => AgentTurnEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'turnId' })
|
||||
turn: Relation<AgentTurnEntity>;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
score: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
comment: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AgentTurnDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/agent-turn.dto';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { AgentAsyncExecutorService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service';
|
||||
import { AgentTurnEvaluationDTO } from 'src/engine/metadata-modules/ai/ai-agent-monitor/dtos/agent-turn-evaluation.dto';
|
||||
import { AgentTurnGraderService } from 'src/engine/metadata-modules/ai/ai-agent-monitor/services/agent-turn-grader.service';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, SettingsPermissionGuard(PermissionFlagType.AI))
|
||||
@Resolver()
|
||||
export class AgentTurnResolver {
|
||||
constructor(
|
||||
@InjectRepository(AgentTurnEntity)
|
||||
private readonly turnRepository: Repository<AgentTurnEntity>,
|
||||
@InjectRepository(AgentChatThreadEntity)
|
||||
private readonly threadRepository: Repository<AgentChatThreadEntity>,
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly graderService: AgentTurnGraderService,
|
||||
private readonly aiAgentExecutorService: AgentAsyncExecutorService,
|
||||
private readonly agentChatService: AgentChatService,
|
||||
) {}
|
||||
|
||||
@Query(() => [AgentTurnDTO])
|
||||
async agentTurns(
|
||||
@Args('agentId', { type: () => UUIDScalarType }) agentId: string,
|
||||
): Promise<AgentTurnDTO[]> {
|
||||
const turns = await this.turnRepository.find({
|
||||
where: { agentId },
|
||||
relations: ['evaluations', 'messages', 'messages.parts'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
return turns as unknown as AgentTurnDTO[];
|
||||
}
|
||||
|
||||
@Mutation(() => AgentTurnEvaluationDTO)
|
||||
async evaluateAgentTurn(
|
||||
@Args('turnId', { type: () => UUIDScalarType }) turnId: string,
|
||||
): Promise<AgentTurnEvaluationDTO> {
|
||||
const evaluation = await this.graderService.evaluateTurn(turnId);
|
||||
|
||||
return evaluation as unknown as AgentTurnEvaluationDTO;
|
||||
}
|
||||
|
||||
@Mutation(() => AgentTurnDTO)
|
||||
async runEvaluationInput(
|
||||
@Args('agentId', { type: () => UUIDScalarType }) agentId: string,
|
||||
@Args('input') input: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||
): Promise<AgentTurnDTO> {
|
||||
const thread = this.threadRepository.create({
|
||||
userWorkspaceId,
|
||||
title: `Eval: ${input.substring(0, 50)}...`,
|
||||
});
|
||||
const savedThread = await this.threadRepository.save(thread);
|
||||
|
||||
const turn = this.turnRepository.create({
|
||||
threadId: savedThread.id,
|
||||
agentId,
|
||||
});
|
||||
const savedTurn = await this.turnRepository.save(turn);
|
||||
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: { id: agentId },
|
||||
});
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId: savedThread.id,
|
||||
turnId: savedTurn.id,
|
||||
uiMessage: {
|
||||
role: 'user',
|
||||
parts: [{ type: 'text', text: input }],
|
||||
},
|
||||
});
|
||||
|
||||
const executionResult = await this.aiAgentExecutorService.executeAgent({
|
||||
agent,
|
||||
userPrompt: input,
|
||||
});
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId: savedThread.id,
|
||||
turnId: savedTurn.id,
|
||||
agentId: agent?.id,
|
||||
uiMessage: {
|
||||
role: 'assistant',
|
||||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(executionResult.result) || '',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await this.graderService.evaluateTurn(savedTurn.id);
|
||||
|
||||
const turnWithEvaluations = await this.turnRepository.findOne({
|
||||
where: { id: savedTurn.id },
|
||||
relations: ['evaluations', 'messages', 'messages.parts'],
|
||||
});
|
||||
|
||||
if (!turnWithEvaluations) {
|
||||
throw new Error('Turn not found after execution');
|
||||
}
|
||||
|
||||
return turnWithEvaluations as unknown as AgentTurnDTO;
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { generateText } from 'ai';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { AgentTurnEvaluationEntity } from 'src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AgentTurnGraderService {
|
||||
private readonly logger = new Logger(AgentTurnGraderService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(AgentTurnEntity)
|
||||
private readonly turnRepository: Repository<AgentTurnEntity>,
|
||||
@InjectRepository(AgentTurnEvaluationEntity)
|
||||
private readonly evaluationRepository: Repository<AgentTurnEvaluationEntity>,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
) {}
|
||||
|
||||
async evaluateTurn(turnId: string): Promise<AgentTurnEvaluationEntity> {
|
||||
const turn = await this.turnRepository.findOne({
|
||||
where: { id: turnId },
|
||||
relations: ['messages', 'messages.parts'],
|
||||
});
|
||||
|
||||
if (!turn) {
|
||||
throw new Error(`Turn ${turnId} not found`);
|
||||
}
|
||||
|
||||
const { score, comment } = await this.evaluateWithAI(turn);
|
||||
|
||||
const evaluation = this.evaluationRepository.create({
|
||||
turnId,
|
||||
score,
|
||||
comment,
|
||||
});
|
||||
|
||||
return this.evaluationRepository.save(evaluation);
|
||||
}
|
||||
|
||||
private async evaluateWithAI(
|
||||
turn: AgentTurnEntity & { messages: AgentMessageEntity[] },
|
||||
): Promise<{ score: number; comment: string }> {
|
||||
try {
|
||||
const defaultModel = this.aiModelRegistryService.getDefaultSpeedModel();
|
||||
|
||||
if (!defaultModel) {
|
||||
this.logger.warn('No default AI model available for evaluation');
|
||||
|
||||
return this.getFallbackEvaluation(turn);
|
||||
}
|
||||
|
||||
const evaluationContext = this.buildEvaluationContext(turn);
|
||||
|
||||
const prompt = `You are evaluating an AI agent's performance on a single turn (user request + agent response).
|
||||
|
||||
${evaluationContext}
|
||||
|
||||
Evaluate this agent turn based on:
|
||||
1. **Task Completion**: Did the agent accomplish what the user asked?
|
||||
2. **Tool Usage**: Were tools used correctly and appropriately?
|
||||
3. **Response Quality**: Is the response clear, accurate, and helpful?
|
||||
4. **Error Handling**: Were errors handled gracefully?
|
||||
|
||||
Provide:
|
||||
- A score from 0 to 100 (0 = complete failure, 100 = perfect)
|
||||
- A brief comment explaining the score (max 200 characters)
|
||||
|
||||
Respond ONLY with valid JSON in this exact format:
|
||||
{"score": <number>, "comment": "<string>"}`;
|
||||
|
||||
const result = await generateText({
|
||||
model: defaultModel.model,
|
||||
prompt,
|
||||
temperature: 0.3,
|
||||
experimental_telemetry: AI_TELEMETRY_CONFIG,
|
||||
});
|
||||
|
||||
const parsed = JSON.parse(result.text);
|
||||
|
||||
return {
|
||||
score: Math.max(0, Math.min(100, Math.round(parsed.score))),
|
||||
comment: (parsed.comment || 'Evaluation completed').substring(0, 500),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to evaluate turn with AI:', error);
|
||||
|
||||
return this.getFallbackEvaluation(turn);
|
||||
}
|
||||
}
|
||||
|
||||
private buildEvaluationContext(
|
||||
turn: AgentTurnEntity & { messages: AgentMessageEntity[] },
|
||||
): string {
|
||||
const userMessages = turn.messages.filter((m) => m.role === 'user');
|
||||
const assistantMessages = turn.messages.filter(
|
||||
(m) => m.role === 'assistant',
|
||||
);
|
||||
|
||||
const userText = userMessages
|
||||
.flatMap((m) => m.parts || [])
|
||||
.filter((p) => p.textContent)
|
||||
.map((p) => p.textContent)
|
||||
.join('\n');
|
||||
|
||||
const assistantParts = assistantMessages.flatMap((m) => m.parts || []);
|
||||
|
||||
const assistantText = assistantParts
|
||||
.filter((p) => p.textContent)
|
||||
.map((p) => p.textContent)
|
||||
.join('\n');
|
||||
|
||||
const toolCalls = assistantParts
|
||||
.filter((p) => p.toolName)
|
||||
.map((p) => ({
|
||||
tool: p.toolName,
|
||||
hasError: !!p.errorMessage,
|
||||
error: p.errorMessage,
|
||||
}));
|
||||
|
||||
const errors = assistantParts
|
||||
.filter((p) => p.errorMessage)
|
||||
.map((p) => p.errorMessage);
|
||||
|
||||
let context = `**User Request:**\n${userText || '(no text)'}\n\n`;
|
||||
|
||||
context += `**Agent Response:**\n${assistantText || '(no text response)'}\n\n`;
|
||||
|
||||
if (toolCalls.length > 0) {
|
||||
context += `**Tools Used:**\n${toolCalls.map((t) => `- ${t.tool}${t.hasError ? ' (FAILED)' : ''}`).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
context += `**Errors:**\n${errors.map((e) => `- ${e}`).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private getFallbackEvaluation(
|
||||
turn: AgentTurnEntity & { messages: AgentMessageEntity[] },
|
||||
): {
|
||||
score: number;
|
||||
comment: string;
|
||||
} {
|
||||
const parts = turn.messages.flatMap((m) => m.parts || []);
|
||||
const errorCount = parts.filter((p) => p.errorMessage).length;
|
||||
const hasResponse = parts.some((p) => p.textContent);
|
||||
const toolCount = parts.filter((p) => p.toolName).length;
|
||||
|
||||
let score = 100;
|
||||
|
||||
if (errorCount > 0) {
|
||||
score -= errorCount * 30;
|
||||
}
|
||||
|
||||
if (!hasResponse) {
|
||||
score -= 50;
|
||||
}
|
||||
|
||||
const comments = [];
|
||||
|
||||
if (errorCount > 0) {
|
||||
comments.push(`${errorCount} error(s)`);
|
||||
}
|
||||
if (toolCount > 0) {
|
||||
comments.push(`${toolCount} tool(s) used`);
|
||||
}
|
||||
if (!hasResponse) {
|
||||
comments.push('No response');
|
||||
}
|
||||
|
||||
return {
|
||||
score: Math.max(0, score),
|
||||
comment: comments.length > 0 ? comments.join('; ') : 'Completed',
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -3,12 +3,12 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
+2
-2
@@ -3,11 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai-agent-role/ai-agent-role.service';
|
||||
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai-agent/dtos/create-agent.input';
|
||||
import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai-agent/dtos/update-agent.input';
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service';
|
||||
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
|
||||
import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label.util';
|
||||
|
||||
+10
-8
@@ -10,11 +10,11 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai-agent-role/ai-agent-role.module';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai-billing/ai-billing.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai-models/ai-models.module';
|
||||
import { AiRouterModule } from 'src/engine/metadata-modules/ai-router/ai-router.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai-tools/ai-tools.module';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
|
||||
import { AiChatRouterModule } from 'src/engine/metadata-modules/ai/ai-chat-router/ai-chat-router.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai/ai-tools/ai-tools.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
@@ -48,7 +48,7 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi
|
||||
FileModule,
|
||||
ObjectMetadataModule,
|
||||
PermissionsModule,
|
||||
AiRouterModule,
|
||||
AiChatRouterModule,
|
||||
WorkspacePermissionsCacheModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
TokenModule,
|
||||
@@ -63,17 +63,19 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi
|
||||
AgentService,
|
||||
AgentExecutionService,
|
||||
AgentModelConfigService,
|
||||
AgentToolGeneratorService,
|
||||
AgentPlanExecutorService,
|
||||
AgentToolGeneratorService,
|
||||
AgentTitleGenerationService,
|
||||
AgentActorContextService,
|
||||
],
|
||||
exports: [
|
||||
AgentService,
|
||||
AgentExecutionService,
|
||||
AgentToolGeneratorService,
|
||||
AgentPlanExecutorService,
|
||||
AgentToolGeneratorService,
|
||||
AgentTitleGenerationService,
|
||||
AgentActorContextService,
|
||||
AgentModelConfigService,
|
||||
TypeOrmModule.forFeature([AgentEntity]),
|
||||
],
|
||||
})
|
||||
+5
-2
@@ -10,8 +10,8 @@ import {
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai-agent/types/modelConfiguration';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
@ObjectType('Agent')
|
||||
export class AgentDTO {
|
||||
@@ -73,4 +73,7 @@ export class AgentDTO {
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
modelConfiguration: ModelConfiguration;
|
||||
|
||||
@Field(() => [String])
|
||||
evaluationInputs: string[];
|
||||
}
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai-agent/types/modelConfiguration';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentInput {
|
||||
@@ -60,6 +61,12 @@ export class CreateAgentInput {
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
modelConfiguration?: ModelConfiguration;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
@Field(() => [String], { nullable: true })
|
||||
evaluationInputs?: string[];
|
||||
|
||||
@HideField()
|
||||
standardId?: string;
|
||||
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai-agent/types/modelConfiguration';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
@InputType()
|
||||
export class UpdateAgentInput {
|
||||
@@ -64,4 +65,10 @@ export class UpdateAgentInput {
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
modelConfiguration?: ModelConfiguration;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
@Field(() => [String], { nullable: true })
|
||||
evaluationInputs?: string[];
|
||||
}
|
||||
+9
-6
@@ -13,13 +13,13 @@ import {
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
|
||||
|
||||
import {
|
||||
ModelId,
|
||||
DEFAULT_SMART_MODEL,
|
||||
} from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentResponseFormat } from 'src/engine/metadata-modules/ai-agent/types/agent-response-format.type';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai-agent/types/modelConfiguration';
|
||||
import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import {
|
||||
DEFAULT_SMART_MODEL,
|
||||
ModelId,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
@Entity('agent')
|
||||
@Index('IDX_AGENT_ID_DELETED_AT', ['id', 'deletedAt'])
|
||||
@@ -81,4 +81,7 @@ export class AgentEntity
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
modelConfiguration: ModelConfiguration;
|
||||
|
||||
@Column({ type: 'text', array: true, default: '{}' })
|
||||
evaluationInputs: string[];
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
+17
-18
@@ -15,29 +15,28 @@ import { getAppPath } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { getAllSelectableColumnNames } from 'src/engine/api/utils/get-all-selectable-column-names.utils';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai-billing/services/ai-billing.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentService } from 'src/engine/metadata-modules/ai-agent/agent.service';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai-agent/constants/agent-config.const';
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai-agent/constants/agent-system-prompts.const';
|
||||
import { AgentActorContextService } from 'src/engine/metadata-modules/ai-agent/services/agent-actor-context.service';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { type ToolHints } from 'src/engine/metadata-modules/ai-router/types/tool-hints.interface';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
import { repairToolCall } from 'src/engine/metadata-modules/ai-agent/utils/repair-tool-call.util';
|
||||
|
||||
import { AgentToolGeneratorService } from './agent-tool-generator.service';
|
||||
import { AgentModelConfigService } from './agent-model-config.service';
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
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 { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-system-prompts.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AgentActorContextService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-actor-context.service';
|
||||
import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service';
|
||||
import { AgentToolGeneratorService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-tool-generator.service';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.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';
|
||||
import { type ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export interface AgentExecutionResult {
|
||||
result: object;
|
||||
+4
-4
@@ -5,10 +5,10 @@ import { openai } from '@ai-sdk/openai';
|
||||
import { ProviderOptions } from '@ai-sdk/provider-utils';
|
||||
import { ToolSet } from 'ai';
|
||||
|
||||
import { ModelProvider } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { RegisteredAIModel } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai-agent/constants/agent-config.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { ModelProvider } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { RegisteredAIModel } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AgentModelConfigService {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { type PlanStep } from 'src/engine/metadata-modules/ai-router/types/router-result.interface';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { type PlanStep } from 'src/engine/metadata-modules/ai/ai-chat-router/types/router-result.interface';
|
||||
import { standardAgentDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents';
|
||||
|
||||
import { AgentExecutionService } from './agent-execution.service';
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { generateText } from 'ai';
|
||||
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AgentTitleGenerationService {
|
||||
+4
-4
@@ -6,11 +6,11 @@ import { Repository } from 'typeorm';
|
||||
|
||||
import type { ActorMetadata } from 'twenty-shared/types';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai-tools/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai-tools/services/tool.service';
|
||||
import { SearchArticlesTool } from 'src/engine/core-modules/tool/tools/search-articles-tool/search-articles-tool';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import type { ToolHints } from 'src/engine/metadata-modules/ai-router/types/tool-hints.interface';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import type { ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { HELPER_AGENT } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/agents/helper-agent';
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
describe('isWorkflowRelatedObject', () => {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { generateObject, type LanguageModel, NoSuchToolError } from 'ai';
|
||||
import { type z } from 'zod';
|
||||
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
|
||||
type ToolCall = {
|
||||
type: 'tool-call';
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai-billing/services/ai-billing.service';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai-models/ai-models.module';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/services/ai-billing.service';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
|
||||
@Module({
|
||||
+2
-2
@@ -2,9 +2,9 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/billing-feature-used.constant';
|
||||
import { BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/services/ai-billing.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai-billing/services/ai-billing.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
|
||||
describe('AIBillingService', () => {
|
||||
let service: AIBillingService;
|
||||
+3
-3
@@ -2,12 +2,12 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { LanguageModelUsage } from 'ai';
|
||||
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { convertCentsToBillingCredits } from 'src/engine/metadata-modules/ai-billing/utils/convert-cents-to-billing-credits.util';
|
||||
import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/billing-feature-used.constant';
|
||||
import { BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names';
|
||||
import { type BillingUsageEvent } from 'src/engine/core-modules/billing/types/billing-usage-event.type';
|
||||
import { convertCentsToBillingCredits } from 'src/engine/metadata-modules/ai/ai-billing/utils/convert-cents-to-billing-credits.util';
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
@Injectable()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { DOLLAR_TO_CREDIT_MULTIPLIER } from 'src/engine/metadata-modules/ai-billing/constants/dollar-to-credit-multiplier';
|
||||
import { DOLLAR_TO_CREDIT_MULTIPLIER } from 'src/engine/metadata-modules/ai/ai-billing/constants/dollar-to-credit-multiplier';
|
||||
|
||||
// Converts cost in cents to cost in credits
|
||||
// Formula: credits = (cents / 100) * DOLLAR_TO_CREDIT_MULTIPLIER
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai/ai-tools/ai-tools.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
import { AiChatRouterService } from './ai-chat-router.service';
|
||||
|
||||
import { AiChatRouterPlanGeneratorService } from './services/ai-chat-router-plan-generator.service';
|
||||
import { AiChatRouterStrategyDeciderService } from './services/ai-chat-router-strategy-decider.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AgentEntity, WorkspaceEntity]),
|
||||
AiModelsModule,
|
||||
AiToolsModule,
|
||||
ObjectMetadataModule,
|
||||
],
|
||||
providers: [
|
||||
AiChatRouterService,
|
||||
AiChatRouterStrategyDeciderService,
|
||||
AiChatRouterPlanGeneratorService,
|
||||
],
|
||||
exports: [AiChatRouterService],
|
||||
})
|
||||
export class AiChatRouterModule {}
|
||||
+12
-12
@@ -4,24 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { type UIDataTypes, type UIMessage, type UITools } from 'ai';
|
||||
import { IsNull, type Repository } from 'typeorm';
|
||||
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
|
||||
import { HELPER_AGENT } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/agents/helper-agent';
|
||||
|
||||
import { AiRouterPlanGeneratorService } from './services/ai-router-plan-generator.service';
|
||||
import { AiChatRouterPlanGeneratorService } from './services/ai-chat-router-plan-generator.service';
|
||||
import {
|
||||
AiRouterStrategyDeciderService,
|
||||
AiChatRouterStrategyDeciderService,
|
||||
type StrategyDecision,
|
||||
} from './services/ai-router-strategy-decider.service';
|
||||
} from './services/ai-chat-router-strategy-decider.service';
|
||||
import {
|
||||
type RouterDebugInfo,
|
||||
type UnifiedRouterResult,
|
||||
} from './types/router-result.interface';
|
||||
import { type ToolHints } from './types/tool-hints.interface';
|
||||
|
||||
export interface AiRouterContext {
|
||||
export interface AiChatRouterContext {
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
workspaceId: string;
|
||||
fastModel: ModelId;
|
||||
@@ -29,19 +29,19 @@ export interface AiRouterContext {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AiRouterService {
|
||||
private readonly logger = new Logger(AiRouterService.name);
|
||||
export class AiChatRouterService {
|
||||
private readonly logger = new Logger(AiChatRouterService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly strategyDecider: AiRouterStrategyDeciderService,
|
||||
private readonly planGenerator: AiRouterPlanGeneratorService,
|
||||
private readonly strategyDecider: AiChatRouterStrategyDeciderService,
|
||||
private readonly planGenerator: AiChatRouterPlanGeneratorService,
|
||||
private readonly objectMetadataService: ObjectMetadataServiceV2,
|
||||
) {}
|
||||
|
||||
async routeMessage(
|
||||
context: AiRouterContext,
|
||||
context: AiChatRouterContext,
|
||||
includeDebugInfo = false,
|
||||
): Promise<UnifiedRouterResult> {
|
||||
try {
|
||||
+7
-7
@@ -8,18 +8,18 @@ import {
|
||||
} from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type ExecutionPlan } from 'src/engine/metadata-modules/ai/ai-chat-router/types/router-result.interface';
|
||||
import {
|
||||
DEFAULT_SMART_MODEL,
|
||||
type ModelId,
|
||||
} from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { type ExecutionPlan } from 'src/engine/metadata-modules/ai-router/types/router-result.interface';
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AiRouterPlanGeneratorService {
|
||||
private readonly logger = new Logger(AiRouterPlanGeneratorService.name);
|
||||
export class AiChatRouterPlanGeneratorService {
|
||||
private readonly logger = new Logger(AiChatRouterPlanGeneratorService.name);
|
||||
|
||||
constructor(
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
+7
-7
@@ -9,14 +9,14 @@ import {
|
||||
} from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-system-prompts.const';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import {
|
||||
DEFAULT_FAST_MODEL,
|
||||
type ModelId,
|
||||
} from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai-agent/constants/agent-system-prompts.const';
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
export type StrategyDecision = {
|
||||
strategy: 'simple' | 'planned';
|
||||
@@ -28,8 +28,8 @@ export type StrategyDecision = {
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AiRouterStrategyDeciderService {
|
||||
private readonly logger = new Logger(AiRouterStrategyDeciderService.name);
|
||||
export class AiChatRouterStrategyDeciderService {
|
||||
private readonly logger = new Logger(AiChatRouterStrategyDeciderService.name);
|
||||
|
||||
constructor(
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
|
||||
import { type ToolHints } from './tool-hints.interface';
|
||||
|
||||
+17
-19
@@ -9,35 +9,31 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.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 { AiAgentModule } from 'src/engine/metadata-modules/ai-agent/ai-agent.module';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai-billing/ai-billing.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai-models/ai-models.module';
|
||||
import { AiRouterModule } from 'src/engine/metadata-modules/ai-router/ai-router.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 { AiChatRouterModule } from 'src/engine/metadata-modules/ai/ai-chat-router/ai-chat-router.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
|
||||
import { AgentChatController } from './controllers/agent-chat.controller';
|
||||
import { AgentChatMessagePartEntity } from './entities/agent-chat-message-part.entity';
|
||||
import { AgentChatMessageEntity } from './entities/agent-chat-message.entity';
|
||||
import { AgentChatThreadEntity } from './entities/agent-chat-thread.entity';
|
||||
import { AgentChatResolver } from './resolvers/agent-chat.resolver';
|
||||
import { AgentChatRoutingService } from './services/agent-chat-routing.service';
|
||||
import { AgentChatStreamingService } from './services/agent-chat-streaming.service';
|
||||
import { AgentChatService } from './services/agent-chat.service';
|
||||
import { AgentStreamingService } from './services/agent-streaming.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
AgentChatMessageEntity,
|
||||
AgentChatMessagePartEntity,
|
||||
AgentChatThreadEntity,
|
||||
FileEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
AiModelsModule,
|
||||
AiBillingModule,
|
||||
AiRouterModule,
|
||||
AiAgentModule,
|
||||
AiAgentExecutionModule,
|
||||
AiChatRouterModule,
|
||||
ThrottlerModule,
|
||||
FeatureFlagModule,
|
||||
FileUploadModule,
|
||||
@@ -47,17 +43,19 @@ import { AgentStreamingService } from './services/agent-streaming.service';
|
||||
WorkspaceCacheStorageModule,
|
||||
TokenModule,
|
||||
UserWorkspaceModule,
|
||||
AiBillingModule,
|
||||
],
|
||||
controllers: [AgentChatController],
|
||||
providers: [AgentChatResolver, AgentChatService, AgentStreamingService],
|
||||
providers: [
|
||||
AgentChatResolver,
|
||||
AgentChatService,
|
||||
AgentChatStreamingService,
|
||||
AgentChatRoutingService,
|
||||
],
|
||||
exports: [
|
||||
AgentChatService,
|
||||
AgentStreamingService,
|
||||
TypeOrmModule.forFeature([
|
||||
AgentChatMessageEntity,
|
||||
AgentChatMessagePartEntity,
|
||||
AgentChatThreadEntity,
|
||||
]),
|
||||
AgentChatStreamingService,
|
||||
TypeOrmModule.forFeature([AgentChatThreadEntity]),
|
||||
],
|
||||
})
|
||||
export class AiChatModule {}
|
||||
+5
-7
@@ -7,8 +7,8 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { UIDataTypes, UIMessage, UITools } from 'ai';
|
||||
import { Response } from 'express';
|
||||
import { type ExtendedUIMessage } from 'twenty-shared/ai';
|
||||
|
||||
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -17,18 +17,16 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { AgentChatStreamingService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai-chat/services/agent-chat.service';
|
||||
import { AgentStreamingService } from 'src/engine/metadata-modules/ai-chat/services/agent-streaming.service';
|
||||
|
||||
@Controller('rest/agent-chat')
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
export class AgentChatController {
|
||||
constructor(
|
||||
private readonly agentChatService: AgentChatService,
|
||||
private readonly agentStreamingService: AgentStreamingService,
|
||||
private readonly agentStreamingService: AgentChatStreamingService,
|
||||
) {}
|
||||
|
||||
@Post('stream')
|
||||
@@ -37,7 +35,7 @@ export class AgentChatController {
|
||||
@Body()
|
||||
body: {
|
||||
threadId: string;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
messages: ExtendedUIMessage[];
|
||||
recordIdsByObjectMetadataNameSingular?: RecordIdsByObjectMetadataNameSingularType;
|
||||
},
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||
+7
-3
@@ -13,7 +13,8 @@ import {
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { AgentChatMessageEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message.entity';
|
||||
import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
|
||||
@Entity('agentChatThread')
|
||||
export class AgentChatThreadEntity {
|
||||
@@ -33,8 +34,11 @@ export class AgentChatThreadEntity {
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
title: string;
|
||||
|
||||
@OneToMany(() => AgentChatMessageEntity, (message) => message.thread)
|
||||
messages: Relation<AgentChatMessageEntity[]>;
|
||||
@OneToMany(() => AgentTurnEntity, (turn) => turn.thread)
|
||||
turns: Relation<AgentTurnEntity[]>;
|
||||
|
||||
@OneToMany(() => AgentMessageEntity, (message) => message.thread)
|
||||
messages: Relation<AgentMessageEntity[]>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
+4
-4
@@ -10,10 +10,10 @@ import {
|
||||
} from 'src/engine/guards/feature-flag.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai-chat/services/agent-chat.service';
|
||||
import { AgentMessageDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/agent-message.dto';
|
||||
import { AgentChatThreadDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-thread.dto';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { AgentChatMessageDTO } from 'src/engine/metadata-modules/ai-chat/dtos/agent-chat-message.dto';
|
||||
import { AgentChatThreadDTO } from 'src/engine/metadata-modules/ai-chat/dtos/agent-chat-thread.dto';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
@@ -39,7 +39,7 @@ export class AgentChatResolver {
|
||||
return this.agentChatService.getThreadById(id, userWorkspaceId);
|
||||
}
|
||||
|
||||
@Query(() => [AgentChatMessageDTO])
|
||||
@Query(() => [AgentMessageDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async chatMessages(
|
||||
@Args('threadId', { type: () => UUIDScalarType }) threadId: string,
|
||||
+69
-71
@@ -1,32 +1,18 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import {
|
||||
createUIMessageStream,
|
||||
pipeUIMessageStreamToResponse,
|
||||
type UIDataTypes,
|
||||
type UIMessage,
|
||||
type UITools,
|
||||
} from 'ai';
|
||||
import { createUIMessageStream, pipeUIMessageStreamToResponse } from 'ai';
|
||||
import { type Response } from 'express';
|
||||
import { type ExtendedUIMessage } from 'twenty-shared/ai';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai-billing/services/ai-billing.service';
|
||||
import { convertCentsToBillingCredits } from 'src/engine/metadata-modules/ai-billing/utils/convert-cents-to-billing-credits.util';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentChatMessageRole } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message.entity';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-thread.entity';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai-chat/services/agent-chat.service';
|
||||
import { AgentExecutionService } from 'src/engine/metadata-modules/ai-agent/services/agent-execution.service';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
import { AgentPlanExecutorService } from 'src/engine/metadata-modules/ai-agent/services/agent-plan-executor.service';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { AiRouterService } from 'src/engine/metadata-modules/ai-router/ai-router.service';
|
||||
import { AgentMessageRole } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
import { AgentExecutionService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service';
|
||||
import { AgentPlanExecutorService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-plan-executor.service';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/services/ai-billing.service';
|
||||
import { convertCentsToBillingCredits } from 'src/engine/metadata-modules/ai/ai-billing/utils/convert-cents-to-billing-credits.util';
|
||||
import { AiChatRouterService } from 'src/engine/metadata-modules/ai/ai-chat-router/ai-chat-router.service';
|
||||
import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
export type TokenUsage = {
|
||||
promptTokens: number;
|
||||
@@ -34,53 +20,50 @@ export type TokenUsage = {
|
||||
totalTokens: number;
|
||||
};
|
||||
|
||||
export type StreamAgentChatOptions = {
|
||||
threadId: string;
|
||||
export type MessagePersistenceCallbacks = {
|
||||
saveSystemMessage?: (message: Omit<ExtendedUIMessage, 'id'>) => Promise<{
|
||||
turnId: string;
|
||||
}>;
|
||||
saveUserMessage: (
|
||||
message: Omit<ExtendedUIMessage, 'id'>,
|
||||
turnId?: string,
|
||||
) => Promise<{ turnId: string }>;
|
||||
saveAssistantMessage: (
|
||||
message: Omit<ExtendedUIMessage, 'id'>,
|
||||
turnId: string,
|
||||
agentId: string,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
export type StreamAgentExecutionOptions = {
|
||||
userWorkspaceId: string;
|
||||
workspace: WorkspaceEntity;
|
||||
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||
response: Response;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
messages: ExtendedUIMessage[];
|
||||
persistenceCallbacks: MessagePersistenceCallbacks;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AgentStreamingService {
|
||||
private readonly logger = new Logger(AgentStreamingService.name);
|
||||
export class AgentChatRoutingService {
|
||||
private readonly logger = new Logger(AgentChatRoutingService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(AgentChatThreadEntity)
|
||||
private readonly threadRepository: Repository<AgentChatThreadEntity>,
|
||||
private readonly agentChatService: AgentChatService,
|
||||
private readonly agentExecutionService: AgentExecutionService,
|
||||
private readonly agentPlanExecutorService: AgentPlanExecutorService,
|
||||
private readonly aiRouterService: AiRouterService,
|
||||
private readonly aiChatRouterService: AiChatRouterService,
|
||||
private readonly aiBillingService: AIBillingService,
|
||||
) {}
|
||||
|
||||
async streamAgentChat({
|
||||
threadId,
|
||||
async streamAgentExecution({
|
||||
userWorkspaceId,
|
||||
workspace,
|
||||
messages,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
response,
|
||||
}: StreamAgentChatOptions) {
|
||||
persistenceCallbacks,
|
||||
}: StreamAgentExecutionOptions) {
|
||||
try {
|
||||
const thread = await this.threadRepository.findOne({
|
||||
where: {
|
||||
id: threadId,
|
||||
userWorkspaceId,
|
||||
},
|
||||
relations: ['messages'],
|
||||
});
|
||||
|
||||
if (!thread) {
|
||||
throw new AgentException(
|
||||
'Thread not found',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = createUIMessageStream<ExtendedUIMessage>({
|
||||
execute: async ({ writer }) => {
|
||||
const startTime = Date.now();
|
||||
@@ -96,7 +79,7 @@ export class AgentStreamingService {
|
||||
|
||||
const routingStart = Date.now();
|
||||
const includeDebugInfo = true;
|
||||
const routeResult = await this.aiRouterService.routeMessage(
|
||||
const routeResult = await this.aiChatRouterService.routeMessage(
|
||||
{
|
||||
messages,
|
||||
workspaceId: workspace.id,
|
||||
@@ -199,10 +182,22 @@ export class AgentStreamingService {
|
||||
},
|
||||
});
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: {
|
||||
role: AgentChatMessageRole.USER,
|
||||
const systemMessage = messages.find((msg) => msg.role === 'system');
|
||||
let turnId: string | undefined;
|
||||
|
||||
if (systemMessage && persistenceCallbacks.saveSystemMessage) {
|
||||
const savedSystemMessage =
|
||||
await persistenceCallbacks.saveSystemMessage({
|
||||
role: AgentMessageRole.SYSTEM,
|
||||
parts: systemMessage.parts,
|
||||
});
|
||||
|
||||
turnId = savedSystemMessage.turnId;
|
||||
}
|
||||
|
||||
const userMessage = await persistenceCallbacks.saveUserMessage(
|
||||
{
|
||||
role: AgentMessageRole.USER,
|
||||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
@@ -213,12 +208,12 @@ export class AgentStreamingService {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
turnId,
|
||||
);
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: {
|
||||
role: AgentChatMessageRole.ASSISTANT,
|
||||
await persistenceCallbacks.saveAssistantMessage(
|
||||
{
|
||||
role: AgentMessageRole.ASSISTANT,
|
||||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
@@ -226,7 +221,9 @@ export class AgentStreamingService {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
userMessage.turnId,
|
||||
'',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -353,10 +350,9 @@ export class AgentStreamingService {
|
||||
|
||||
writer.write(updatedRoutedStatusPart);
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: {
|
||||
role: AgentChatMessageRole.USER,
|
||||
const userMessage = await persistenceCallbacks.saveUserMessage(
|
||||
{
|
||||
role: AgentMessageRole.USER,
|
||||
parts: [
|
||||
{
|
||||
type: 'text',
|
||||
@@ -367,15 +363,17 @@ export class AgentStreamingService {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
undefined,
|
||||
);
|
||||
|
||||
await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: {
|
||||
await persistenceCallbacks.saveAssistantMessage(
|
||||
{
|
||||
...responseMessage,
|
||||
parts: [updatedRoutedStatusPart, ...responseMessage.parts],
|
||||
},
|
||||
});
|
||||
userMessage.turnId,
|
||||
agent.id,
|
||||
);
|
||||
},
|
||||
sendReasoning: true,
|
||||
}),
|
||||
@@ -386,7 +384,7 @@ export class AgentStreamingService {
|
||||
pipeUIMessageStreamToResponse({ stream, response });
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'Failed to stream agent chat:',
|
||||
'Failed to stream agent execution:',
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
response.end();
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { type ExtendedUIMessage } from 'twenty-shared/ai';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
import { AgentChatRoutingService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat-routing.service';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service';
|
||||
|
||||
export type StreamAgentChatOptions = {
|
||||
threadId: string;
|
||||
userWorkspaceId: string;
|
||||
workspace: WorkspaceEntity;
|
||||
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||
response: Response;
|
||||
messages: ExtendedUIMessage[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AgentChatStreamingService {
|
||||
constructor(
|
||||
@InjectRepository(AgentChatThreadEntity)
|
||||
private readonly threadRepository: Repository<AgentChatThreadEntity>,
|
||||
private readonly agentChatService: AgentChatService,
|
||||
private readonly agentChatRoutingService: AgentChatRoutingService,
|
||||
) {}
|
||||
|
||||
async streamAgentChat({
|
||||
threadId,
|
||||
userWorkspaceId,
|
||||
workspace,
|
||||
messages,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
response,
|
||||
}: StreamAgentChatOptions) {
|
||||
const thread = await this.threadRepository.findOne({
|
||||
where: {
|
||||
id: threadId,
|
||||
userWorkspaceId,
|
||||
},
|
||||
relations: ['messages'],
|
||||
});
|
||||
|
||||
if (!thread) {
|
||||
throw new AgentException(
|
||||
'Thread not found',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
await this.agentChatRoutingService.streamAgentExecution({
|
||||
userWorkspaceId,
|
||||
workspace,
|
||||
messages,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
response,
|
||||
persistenceCallbacks: {
|
||||
saveSystemMessage: async (message) => {
|
||||
const savedMessage = await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: message,
|
||||
});
|
||||
|
||||
return { turnId: savedMessage.turnId };
|
||||
},
|
||||
saveUserMessage: async (message, turnId) => {
|
||||
const savedMessage = await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: message,
|
||||
turnId,
|
||||
});
|
||||
|
||||
return { turnId: savedMessage.turnId };
|
||||
},
|
||||
saveAssistantMessage: async (message, turnId, agentId) => {
|
||||
await this.agentChatService.addMessage({
|
||||
threadId,
|
||||
uiMessage: message,
|
||||
agentId,
|
||||
turnId,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
+36
-14
@@ -6,28 +6,31 @@ import { Repository } from 'typeorm';
|
||||
|
||||
import type { UIDataTypes, UIMessagePart, UITools } from 'ai';
|
||||
|
||||
import { AgentChatMessagePartEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message-part.entity';
|
||||
import { AgentMessagePartEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity';
|
||||
import {
|
||||
AgentChatMessageEntity,
|
||||
AgentChatMessageRole,
|
||||
} from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message.entity';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-thread.entity';
|
||||
AgentMessageEntity,
|
||||
AgentMessageRole,
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { mapUIMessagePartsToDBParts } from 'src/engine/metadata-modules/ai/ai-agent-execution/utils/mapUIMessagePartsToDBParts';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai-agent/agent.exception';
|
||||
import { mapUIMessagePartsToDBParts } from 'src/engine/metadata-modules/ai-chat/utils/mapUIMessagePartsToDBParts';
|
||||
import { AgentTitleGenerationService } from 'src/engine/metadata-modules/ai-agent/services/agent-title-generation.service';
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AgentTitleGenerationService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-title-generation.service';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AgentChatService {
|
||||
constructor(
|
||||
@InjectRepository(AgentChatThreadEntity)
|
||||
private readonly threadRepository: Repository<AgentChatThreadEntity>,
|
||||
@InjectRepository(AgentChatMessageEntity)
|
||||
private readonly messageRepository: Repository<AgentChatMessageEntity>,
|
||||
@InjectRepository(AgentChatMessagePartEntity)
|
||||
private readonly messagePartRepository: Repository<AgentChatMessagePartEntity>,
|
||||
@InjectRepository(AgentTurnEntity)
|
||||
private readonly turnRepository: Repository<AgentTurnEntity>,
|
||||
@InjectRepository(AgentMessageEntity)
|
||||
private readonly messageRepository: Repository<AgentMessageEntity>,
|
||||
@InjectRepository(AgentMessagePartEntity)
|
||||
private readonly messagePartRepository: Repository<AgentMessagePartEntity>,
|
||||
private readonly titleGenerationService: AgentTitleGenerationService,
|
||||
) {}
|
||||
|
||||
@@ -69,14 +72,33 @@ export class AgentChatService {
|
||||
async addMessage({
|
||||
threadId,
|
||||
uiMessage,
|
||||
agentId,
|
||||
turnId,
|
||||
}: {
|
||||
threadId: string;
|
||||
uiMessage: Omit<ExtendedUIMessage, 'id'>;
|
||||
uiMessageParts?: UIMessagePart<UIDataTypes, UITools>[];
|
||||
agentId?: string;
|
||||
turnId?: string;
|
||||
}) {
|
||||
let actualTurnId = turnId;
|
||||
|
||||
if (!actualTurnId) {
|
||||
const turn = this.turnRepository.create({
|
||||
threadId,
|
||||
agentId: agentId ?? null,
|
||||
});
|
||||
|
||||
const savedTurn = await this.turnRepository.save(turn);
|
||||
|
||||
actualTurnId = savedTurn.id;
|
||||
}
|
||||
|
||||
const message = this.messageRepository.create({
|
||||
threadId,
|
||||
role: uiMessage.role as AgentChatMessageRole,
|
||||
turnId: actualTurnId,
|
||||
role: uiMessage.role as AgentMessageRole,
|
||||
agentId: agentId ?? null,
|
||||
});
|
||||
|
||||
const savedMessage = await this.messageRepository.save(message);
|
||||
@@ -90,7 +112,7 @@ export class AgentChatService {
|
||||
await this.messagePartRepository.save(dbParts);
|
||||
}
|
||||
|
||||
if (uiMessage.role === AgentChatMessageRole.USER) {
|
||||
if (uiMessage.role === AgentMessageRole.USER) {
|
||||
const messageContent = uiMessage.parts.find(
|
||||
(part) => part.type === 'text',
|
||||
)?.text;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { AiService } from 'src/engine/metadata-modules/ai-models/services/ai.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { AiService } from 'src/engine/metadata-modules/ai/ai-models/services/ai.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
import {
|
||||
AI_MODELS,
|
||||
+2
-2
@@ -5,14 +5,14 @@ import { createOpenAI, openai } from '@ai-sdk/openai';
|
||||
import { xai } from '@ai-sdk/xai';
|
||||
import { type LanguageModel } from 'ai';
|
||||
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import {
|
||||
AI_MODELS,
|
||||
DEFAULT_FAST_MODEL,
|
||||
DEFAULT_SMART_MODEL,
|
||||
ModelProvider,
|
||||
type AIModelConfig,
|
||||
} from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
export interface RegisteredAIModel {
|
||||
modelId: string;
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { LanguageModel, type ModelMessage, streamText } from 'ai';
|
||||
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai-models/services/ai-model-registry.service';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
+2
-2
@@ -1,8 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai-tools/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai-tools/services/tool.service';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
@@ -10,6 +8,8 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { RecordCrudModule } from 'src/engine/core-modules/record-crud/record-crud.module';
|
||||
import { ToolModule } from 'src/engine/core-modules/tool/tool.module';
|
||||
import { SearchArticlesTool } from 'src/engine/core-modules/tool/tools/search-articles-tool/search-articles-tool';
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool.service';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
+1
-1
@@ -2,11 +2,11 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { jsonSchema } from 'ai';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai-tools/services/tool-adapter.service';
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service';
|
||||
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { ToolAdapterService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai-tools/services/tool.service';
|
||||
import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service';
|
||||
import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service';
|
||||
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
|
||||
import { UpdateRecordService } from 'src/engine/core-modules/record-crud/services/update-record.service';
|
||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||
import { ToolService } from 'src/engine/metadata-modules/ai/ai-tools/services/tool.service';
|
||||
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
+3
-3
@@ -10,14 +10,14 @@ import { UpdateRecordService } from 'src/engine/core-modules/record-crud/service
|
||||
import { generateCreateRecordInputSchema } from 'src/engine/core-modules/record-crud/utils/generate-create-record-input-schema.util';
|
||||
import { generateUpdateRecordInputSchema } from 'src/engine/core-modules/record-crud/utils/generate-update-record-input-schema.util';
|
||||
import { BulkDeleteToolInputSchema } from 'src/engine/core-modules/record-crud/zod-schemas/bulk-delete-tool.zod-schema';
|
||||
import { FindOneToolInputSchema } from 'src/engine/core-modules/record-crud/zod-schemas/find-one-tool.zod-schema';
|
||||
import { generateFindToolInputSchema } from 'src/engine/core-modules/record-crud/zod-schemas/find-tool.zod-schema';
|
||||
import { SoftDeleteToolInputSchema } from 'src/engine/core-modules/record-crud/zod-schemas/soft-delete-tool.zod-schema';
|
||||
import { FindOneToolInputSchema } from 'src/engine/core-modules/record-crud/zod-schemas/find-one-tool.zod-schema';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai/ai-agent/utils/is-workflow-related-object.util';
|
||||
import {
|
||||
type ToolHints,
|
||||
type ToolOperation,
|
||||
} from 'src/engine/metadata-modules/ai-router/types/tool-hints.interface';
|
||||
} from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
|
||||
|
||||
export const agentEntityRelationProperties = [
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
|
||||
export const transformAgentEntityToFlatAgent = (
|
||||
@@ -19,5 +19,6 @@ export const transformAgentEntityToFlatAgent = (
|
||||
universalIdentifier: agentEntity.standardId || agentEntity.id,
|
||||
applicationId: agentEntity.applicationId,
|
||||
modelConfiguration: agentEntity.modelConfiguration,
|
||||
evaluationInputs: agentEntity.evaluationInputs,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai-agent/ai-agent.module';
|
||||
import { AiChatModule } from 'src/engine/metadata-modules/ai-chat/ai-chat.module';
|
||||
import { AiAgentMonitorModule } from 'src/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module';
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
|
||||
import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module';
|
||||
import { CronTriggerModule } from 'src/engine/metadata-modules/cron-trigger/cron-trigger.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { DatabaseEventTriggerModule } from 'src/engine/metadata-modules/database-event-trigger/database-event-trigger.module';
|
||||
@@ -27,6 +28,7 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
|
||||
ServerlessFunctionModule,
|
||||
ServerlessFunctionLayerModule,
|
||||
AiAgentModule,
|
||||
AiAgentMonitorModule,
|
||||
AiChatModule,
|
||||
ViewModule,
|
||||
WorkspaceMetadataVersionModule,
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export enum PermissionFlagType {
|
||||
UPLOAD_FILE = 'UPLOAD_FILE',
|
||||
DOWNLOAD_FILE = 'DOWNLOAD_FILE',
|
||||
SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL',
|
||||
HTTP_REQUEST_TOOL = 'HTTP_REQUEST_TOOL',
|
||||
IMPORT_CSV = 'IMPORT_CSV',
|
||||
EXPORT_CSV = 'EXPORT_CSV',
|
||||
CONNECTED_ACCOUNTS = 'CONNECTED_ACCOUNTS',
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ export const TOOL_PERMISSION_FLAGS = [
|
||||
'UPLOAD_FILE',
|
||||
'DOWNLOAD_FILE',
|
||||
'SEND_EMAIL_TOOL',
|
||||
'HTTP_REQUEST_TOOL',
|
||||
'IMPORT_CSV',
|
||||
'EXPORT_CSV',
|
||||
'CONNECTED_ACCOUNTS',
|
||||
|
||||
@@ -109,6 +109,7 @@ export class PermissionsService {
|
||||
[PermissionFlagType.UPLOAD_FILE]: false,
|
||||
[PermissionFlagType.DOWNLOAD_FILE]: false,
|
||||
[PermissionFlagType.SEND_EMAIL_TOOL]: false,
|
||||
[PermissionFlagType.HTTP_REQUEST_TOOL]: false,
|
||||
[PermissionFlagType.IMPORT_CSV]: false,
|
||||
[PermissionFlagType.EXPORT_CSV]: false,
|
||||
[PermissionFlagType.CONNECTED_ACCOUNTS]: false,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Relation } from 'typeorm';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai-agent/dtos/agent.dto';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { FileModule } from 'src/engine/core-modules/file/file.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 { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai-agent-role/ai-agent-role.module';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module';
|
||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
|
||||
@@ -24,8 +24,8 @@ import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai-agent-role/ai-agent-role.service';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai-agent/dtos/agent.dto';
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input';
|
||||
|
||||
Reference in New Issue
Block a user