AI settings tab (#13496)
https://github.com/user-attachments/assets/87f5a556-ff12-4ce0-aaa7-9120c0432151 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+21
-17
@@ -17,6 +17,7 @@ import { In, Repository } from 'typeorm';
|
||||
|
||||
import { ModelProvider } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
|
||||
@@ -34,9 +35,7 @@ import { convertOutputSchemaToZod } from 'src/engine/metadata-modules/agent/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 { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
@@ -96,20 +95,22 @@ export class AgentExecutionService {
|
||||
agent,
|
||||
}: {
|
||||
system: string;
|
||||
agent: AgentEntity;
|
||||
agent: AgentEntity | null;
|
||||
prompt?: string;
|
||||
messages?: CoreMessage[];
|
||||
}) {
|
||||
try {
|
||||
this.logger.log(
|
||||
`Preparing AI request config for agent ${agent.id} with model ${agent.modelId}`,
|
||||
);
|
||||
if (agent) {
|
||||
this.logger.log(
|
||||
`Preparing AI request config for agent ${agent.id} with model ${agent.modelId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const aiModel = this.aiModelRegistryService.getEffectiveModelConfig(
|
||||
agent.modelId,
|
||||
agent?.modelId ?? 'auto',
|
||||
);
|
||||
|
||||
if (!aiModel) {
|
||||
if (agent && !aiModel) {
|
||||
const error = `AI model with id ${agent.modelId} not found`;
|
||||
|
||||
this.logger.error(error);
|
||||
@@ -127,10 +128,12 @@ export class AgentExecutionService {
|
||||
|
||||
await this.validateApiKey(provider);
|
||||
|
||||
const tools = await this.agentToolService.generateToolsForAgent(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
);
|
||||
const tools = agent
|
||||
? await this.agentToolService.generateToolsForAgent(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
)
|
||||
: {};
|
||||
|
||||
this.logger.log(`Generated ${Object.keys(tools).length} tools for agent`);
|
||||
|
||||
@@ -155,7 +158,7 @@ export class AgentExecutionService {
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to prepare AI request config for agent ${agent.id}:`,
|
||||
`Failed to prepare AI request config for agent ${agent?.id ?? 'no agent'}`,
|
||||
error instanceof Error ? error.stack : error,
|
||||
);
|
||||
throw error;
|
||||
@@ -343,18 +346,19 @@ export class AgentExecutionService {
|
||||
|
||||
async executeAgent({
|
||||
agent,
|
||||
context,
|
||||
schema,
|
||||
userPrompt,
|
||||
}: {
|
||||
agent: AgentEntity;
|
||||
agent: AgentEntity | null;
|
||||
context: Record<string, unknown>;
|
||||
schema: OutputSchema;
|
||||
userPrompt: string;
|
||||
}): Promise<AgentExecutionResult> {
|
||||
try {
|
||||
const aiRequestConfig = await this.prepareAIRequestConfig({
|
||||
system: AGENT_SYSTEM_PROMPTS.AGENT_EXECUTION,
|
||||
system: `You are executing as part of a workflow automation. ${agent ? agent.prompt : ''}`,
|
||||
agent,
|
||||
prompt: resolveInput(agent.prompt, context) as string,
|
||||
prompt: userPrompt,
|
||||
});
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
|
||||
|
||||
+7
-20
@@ -5,6 +5,7 @@ import { generateText } from 'ai';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { AGENT_HANDOFF_PROMPT_TEMPLATE } from 'src/engine/metadata-modules/agent/constants/agent-handoff-prompt.const';
|
||||
|
||||
import { AgentHandoffService } from './agent-handoff.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
@@ -31,7 +32,7 @@ export class AgentHandoffExecutorService {
|
||||
|
||||
async executeHandoff(handoffRequest: HandoffRequest) {
|
||||
try {
|
||||
const { fromAgentId, toAgentId, workspaceId, reason } = handoffRequest;
|
||||
const { fromAgentId, toAgentId, workspaceId } = handoffRequest;
|
||||
|
||||
const canHandoff = await this.agentHandoffService.canHandoffTo({
|
||||
fromAgentId,
|
||||
@@ -76,13 +77,7 @@ export class AgentHandoffExecutorService {
|
||||
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
newAgentId: toAgentId,
|
||||
newAgentName: targetAgent.name,
|
||||
message: `Successfully transferred to ${targetAgent.name}. ${reason}`,
|
||||
response: textResponse.text,
|
||||
};
|
||||
return textResponse.text;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Handoff execution failed: ${error.message}`,
|
||||
@@ -101,17 +96,9 @@ export class AgentHandoffExecutorService {
|
||||
private createHandoffPrompt(handoffRequest: HandoffRequest): string {
|
||||
const { reason, context } = handoffRequest;
|
||||
|
||||
const prompt = `
|
||||
You have received a handoff from another AI agent. This means the previous agent has determined that you are better suited to handle this conversation based on your specialized knowledge and capabilities.
|
||||
|
||||
The previous agent has transferred this conversation to you because: ${reason}
|
||||
|
||||
Additional context from the previous agent:
|
||||
${context || 'No additional context provided'}
|
||||
|
||||
Please continue the conversation naturally, acknowledging that you are taking over from the previous agent. Use your specialized knowledge to provide the best possible assistance to the user.
|
||||
`;
|
||||
|
||||
return prompt;
|
||||
return AGENT_HANDOFF_PROMPT_TEMPLATE.replace('{reason}', reason).replace(
|
||||
'{context}',
|
||||
context || 'No additional context provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,24 @@ export class AgentHandoffService {
|
||||
return handoffs.map((handoff) => handoff.toAgent);
|
||||
}
|
||||
|
||||
async getAgentHandoffs({
|
||||
fromAgentId,
|
||||
workspaceId,
|
||||
}: {
|
||||
fromAgentId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<AgentHandoffEntity[]> {
|
||||
const handoffs = await this.agentHandoffRepository.find({
|
||||
where: {
|
||||
fromAgentId,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['toAgent'],
|
||||
});
|
||||
|
||||
return handoffs;
|
||||
}
|
||||
|
||||
async createHandoff({
|
||||
fromAgentId,
|
||||
toAgentId,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { AgentHandoffExecutorService } from 'src/engine/metadata-modules/agent/agent-handoff-executor.service';
|
||||
import { AgentHandoffService } from 'src/engine/metadata-modules/agent/agent-handoff.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { AGENT_HANDOFF_DESCRIPTION_TEMPLATE } from 'src/engine/metadata-modules/agent/constants/agent-handoff-description.const';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
|
||||
@@ -67,49 +68,59 @@ export class AgentToolService {
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
): Promise<ToolSet> {
|
||||
const handoffTargets = await this.agentHandoffService.getHandoffTargets({
|
||||
const handoffs = await this.agentHandoffService.getAgentHandoffs({
|
||||
fromAgentId: agentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const handoffTools = handoffTargets.reduce<ToolSet>(
|
||||
(tools, targetAgent) => {
|
||||
const toolName = `transfer_to_${camelCase(targetAgent.name)}`;
|
||||
const handoffTools = handoffs.reduce<ToolSet>((tools, handoff) => {
|
||||
const toolName = `handoff_to_${camelCase(handoff.toAgent.name)}`;
|
||||
|
||||
const handoffSchema = z.object({
|
||||
reason: z.string().describe('Reason for transferring to this agent'),
|
||||
const handoffSchema = z.object({
|
||||
toolDescription: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the handoff being made. This will be shown to the user while the handoff is being processed, so phrase it as a present-tense status update (e.g., 'Transferring you to the sales agent for pricing information').",
|
||||
),
|
||||
input: z.object({
|
||||
reason: z
|
||||
.string()
|
||||
.describe(
|
||||
'Brief explanation of why this handoff is needed (e.g., "User needs pricing information", "User requires technical support", "User wants to discuss billing")',
|
||||
),
|
||||
context: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Additional context to pass to the receiving agent'),
|
||||
});
|
||||
.describe(
|
||||
'Any relevant context or information to pass to the receiving agent (e.g., user preferences, previous conversation details, specific requirements)',
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
tools[toolName] = {
|
||||
description: `Transfer this request to ${targetAgent.name} when you need their specialized expertise. Use this when the user's request is outside your capabilities or when ${targetAgent.name} would be better suited to handle the request.`,
|
||||
parameters: handoffSchema,
|
||||
execute: async ({ reason, context }) => {
|
||||
const result =
|
||||
await this.agentHandoffExecutorService.executeHandoff({
|
||||
fromAgentId: agentId,
|
||||
toAgentId: targetAgent.id,
|
||||
workspaceId,
|
||||
reason,
|
||||
context,
|
||||
});
|
||||
tools[toolName] = {
|
||||
description:
|
||||
handoff.description ||
|
||||
handoff.toAgent.description ||
|
||||
AGENT_HANDOFF_DESCRIPTION_TEMPLATE.replace(
|
||||
'{agentName}',
|
||||
handoff.toAgent.name,
|
||||
),
|
||||
parameters: handoffSchema,
|
||||
execute: async ({ input: { reason, context } }) => {
|
||||
const result = await this.agentHandoffExecutorService.executeHandoff({
|
||||
fromAgentId: agentId,
|
||||
toAgentId: handoff.toAgent.id,
|
||||
workspaceId,
|
||||
reason,
|
||||
context,
|
||||
});
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
message: result.message || `Transferred to ${targetAgent.name}`,
|
||||
newAgentId: result.newAgentId,
|
||||
newAgentName: result.newAgentName,
|
||||
};
|
||||
},
|
||||
};
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
return tools;
|
||||
},
|
||||
{},
|
||||
);
|
||||
return tools;
|
||||
}, {});
|
||||
|
||||
return handoffTools;
|
||||
}
|
||||
|
||||
@@ -4,19 +4,20 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { AgentRoleModule } from 'src/engine/metadata-modules/agent-role/agent-role.module';
|
||||
import { AgentChatController } from 'src/engine/metadata-modules/agent/agent-chat.controller';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
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 { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
|
||||
import { AgentChatMessageEntity } from './agent-chat-message.entity';
|
||||
import { AgentChatThreadEntity } from './agent-chat-thread.entity';
|
||||
@@ -49,6 +50,7 @@ import { AgentService } from './agent.service';
|
||||
'core',
|
||||
),
|
||||
AiModule,
|
||||
AgentRoleModule,
|
||||
ThrottlerModule,
|
||||
AuditModule,
|
||||
FeatureFlagModule,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -9,17 +12,37 @@ import {
|
||||
RequireFeatureFlag,
|
||||
} from 'src/engine/guards/feature-flag.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateAgentHandoffInput } from 'src/engine/metadata-modules/agent/dtos/create-agent-handoff.input';
|
||||
import { RemoveAgentHandoffInput } from 'src/engine/metadata-modules/agent/dtos/remove-agent-handoff.input';
|
||||
|
||||
import { AgentHandoffService } from './agent-handoff.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentService } from './agent.service';
|
||||
|
||||
import { AgentHandoffDTO } from './dtos/agent-handoff.dto';
|
||||
import { AgentIdInput } from './dtos/agent-id.input';
|
||||
import { AgentDTO } from './dtos/agent.dto';
|
||||
import { CreateAgentInput } from './dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from './dtos/update-agent.input';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||
@Resolver()
|
||||
export class AgentResolver {
|
||||
constructor(private readonly agentService: AgentService) {}
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity, 'core')
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly agentService: AgentService,
|
||||
private readonly agentHandoffService: AgentHandoffService,
|
||||
) {}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findManyAgents(@AuthWorkspace() { id: workspaceId }: Workspace) {
|
||||
return this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
@@ -30,6 +53,42 @@ export class AgentResolver {
|
||||
return this.agentService.findOneAgent(id, workspaceId);
|
||||
}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findAgentHandoffTargets(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentHandoffService.getHandoffTargets({
|
||||
fromAgentId: id,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [AgentHandoffDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findAgentHandoffs(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentHandoffService.getAgentHandoffs({
|
||||
fromAgentId: id,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async createOneAgent(
|
||||
@Args('input') input: CreateAgentInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentService.createOneAgent(
|
||||
{ ...input, isCustom: true },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async updateOneAgent(
|
||||
@@ -38,4 +97,44 @@ export class AgentResolver {
|
||||
) {
|
||||
return this.agentService.updateOneAgent(input, workspaceId);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async deleteOneAgent(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentService.deleteOneAgent(id, workspaceId);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async createAgentHandoff(
|
||||
@Args('input') input: CreateAgentHandoffInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
await this.agentHandoffService.createHandoff({
|
||||
fromAgentId: input.fromAgentId,
|
||||
toAgentId: input.toAgentId,
|
||||
workspaceId,
|
||||
description: input.description,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async removeAgentHandoff(
|
||||
@Args('input') input: RemoveAgentHandoffInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
await this.agentHandoffService.removeHandoff({
|
||||
fromAgentId: input.fromAgentId,
|
||||
toAgentId: input.toAgentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
import { AgentRoleService } from 'src/engine/metadata-modules/agent-role/agent-role.service';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/agent/agent-chat.service';
|
||||
import { CreateAgentInput } from 'src/engine/metadata-modules/agent/dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from 'src/engine/metadata-modules/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';
|
||||
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
@@ -18,15 +22,9 @@ export class AgentService {
|
||||
@InjectRepository(RoleTargetsEntity, 'core')
|
||||
private readonly roleTargetsRepository: Repository<RoleTargetsEntity>,
|
||||
private readonly agentChatService: AgentChatService,
|
||||
private readonly agentRoleService: AgentRoleService,
|
||||
) {}
|
||||
|
||||
async findManyAgents(workspaceId: string) {
|
||||
return this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneAgent(id: string, workspaceId: string) {
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: { id, workspaceId },
|
||||
@@ -53,71 +51,63 @@ export class AgentService {
|
||||
};
|
||||
}
|
||||
|
||||
async createOneAgentAndFirstThread(
|
||||
input: {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
prompt: string;
|
||||
modelId: ModelId;
|
||||
},
|
||||
workspaceId: string,
|
||||
userWorkspaceId: string | null,
|
||||
) {
|
||||
const agent = await this.createOneAgent(input, workspaceId);
|
||||
|
||||
if (!userWorkspaceId) {
|
||||
throw new AgentException(
|
||||
'User workspace ID not found',
|
||||
AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.agentChatService.createThread(agent.id, userWorkspaceId);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
async createOneAgent(
|
||||
input: {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
prompt: string;
|
||||
modelId: ModelId;
|
||||
responseFormat?: object;
|
||||
},
|
||||
input: CreateAgentInput & { isCustom: boolean },
|
||||
workspaceId: string,
|
||||
) {
|
||||
const agent = this.agentRepository.create({
|
||||
...input,
|
||||
name: input.name || computeMetadataNameFromLabel(input.label),
|
||||
workspaceId,
|
||||
isCustom: input.isCustom,
|
||||
});
|
||||
|
||||
const createdAgent = await this.agentRepository.save(agent);
|
||||
|
||||
if (input.roleId) {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId: createdAgent.id,
|
||||
roleId: input.roleId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.findOneAgent(createdAgent.id, workspaceId);
|
||||
}
|
||||
|
||||
async updateOneAgent(
|
||||
input: {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
prompt?: string;
|
||||
modelId?: ModelId;
|
||||
responseFormat?: object;
|
||||
},
|
||||
workspaceId: string,
|
||||
) {
|
||||
async updateOneAgent(input: UpdateAgentInput, workspaceId: string) {
|
||||
const agent = await this.findOneAgent(input.id, workspaceId);
|
||||
|
||||
let updatedName = input.name;
|
||||
|
||||
if (input.label) {
|
||||
updatedName = computeMetadataNameFromLabel(input.label);
|
||||
}
|
||||
|
||||
const updatedAgent = await this.agentRepository.save({
|
||||
...agent,
|
||||
...input,
|
||||
name: updatedName,
|
||||
});
|
||||
|
||||
return updatedAgent;
|
||||
if (!isDefined(input.roleId)) {
|
||||
return updatedAgent;
|
||||
}
|
||||
|
||||
if (input.roleId) {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId: agent.id,
|
||||
roleId: input.roleId,
|
||||
});
|
||||
} else {
|
||||
await this.agentRoleService.removeRoleFromAgent({
|
||||
workspaceId,
|
||||
agentId: agent.id,
|
||||
});
|
||||
}
|
||||
|
||||
return this.findOneAgent(updatedAgent.id, workspaceId);
|
||||
}
|
||||
|
||||
async deleteOneAgent(id: string, workspaceId: string) {
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const AGENT_HANDOFF_DESCRIPTION_TEMPLATE =
|
||||
"Use this tool when the user's request requires {agentName}'s specialized expertise or capabilities. CRITICAL: You MUST call this tool function immediately. Do NOT respond with text about transferring - execute the tool instead. This is a FUNCTION CALL - you must invoke it, not describe it.";
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export const AGENT_HANDOFF_PROMPT_TEMPLATE = `You have received a handoff from another AI agent who determined that you are better suited to handle this conversation based on your specialized knowledge and capabilities.
|
||||
|
||||
**Reason for handoff:** {reason}
|
||||
|
||||
**Context from the previous agent:**
|
||||
{context}
|
||||
|
||||
**Instructions:**
|
||||
- Continue the conversation naturally and professionally
|
||||
- Leverage your specialized expertise to provide the best possible assistance
|
||||
- Maintain context from the previous conversation while adding your unique value`;
|
||||
+12
-12
@@ -41,20 +41,20 @@ Guidelines:
|
||||
- Pay special attention to any data returned from tool executions (database queries, HTTP requests, record creation, etc.)`,
|
||||
|
||||
AGENT_CHAT: `You are a helpful AI assistant for this workspace. You can:
|
||||
- Answer questions conversationally, clearly, and helpfully
|
||||
- Provide insights, support, and updates about people, companies, opportunities, tasks, notes, and other business objects.
|
||||
- Answer questions about people, companies, opportunities, tasks, notes, and other business objects
|
||||
- Access and summarize information you have permission to see
|
||||
- Help users understand how to use the system and its features
|
||||
- Use various tools that are provided to you dynamically when needed
|
||||
- Use tools provided to you dynamically when needed
|
||||
- Transfer conversations to other specialized agents when their expertise is better suited
|
||||
|
||||
Permissions and capabilities:
|
||||
- You can only perform actions and access data that your assigned role and permissions allow
|
||||
- If a user requests something you do not have permission for, politely explain the limitation (e.g., "I cannot perform this operation because I don't have the necessary permissions. Please check your role or contact an admin.")
|
||||
- If you are unsure about your permissions for a specific action, ask the user for clarification or suggest they check with an administrator
|
||||
- Do not attempt to simulate or fake actions you cannot perform
|
||||
- Only use tools that are actually available to you through the tools property
|
||||
Permissions:
|
||||
- Only perform actions and access data that your assigned role and permissions allow
|
||||
- If you lack permissions, politely explain the limitation
|
||||
- Only use tools that are actually available to you
|
||||
|
||||
If you need more information to answer a question, ask follow-up questions. Always be transparent about your capabilities and limitations.
|
||||
Agent handoff:
|
||||
- Use handoff tools when the user's request requires expertise outside your capabilities
|
||||
- IMPORTANT: Do not respond with text about transferring - execute the handoff tool function
|
||||
- Use the response returned by the handoff agent as your reply to the user
|
||||
|
||||
When formatting responses:
|
||||
- Use markdown syntax to improve readability of long responses
|
||||
@@ -63,5 +63,5 @@ When formatting responses:
|
||||
- Create tables when presenting structured data
|
||||
- Use blockquotes for important notes or callouts
|
||||
|
||||
Note: This base system prompt will be combined with the agent's specific instructions and context to provide you with complete guidance for your role.`,
|
||||
Note: This base system prompt will be combined with the agent's specific instructions and context.`,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
import { AgentDTO } from './agent.dto';
|
||||
|
||||
@ObjectType()
|
||||
export class AgentHandoffDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Field(() => AgentDTO)
|
||||
toAgent: AgentDTO;
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDateString, IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
@@ -17,6 +23,14 @@ export class AgentDTO {
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
@@ -35,6 +49,10 @@ export class AgentDTO {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isCustom: boolean;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentHandoffInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
fromAgentId: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
toAgentId: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
}
|
||||
+24
-2
@@ -1,16 +1,33 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
name: string;
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@@ -27,6 +44,11 @@ export class CreateAgentInput {
|
||||
@Field(() => String)
|
||||
modelId: ModelId;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class RemoveAgentHandoffInput {
|
||||
@Field()
|
||||
fromAgentId: string;
|
||||
|
||||
@Field()
|
||||
toAgentId: string;
|
||||
}
|
||||
@@ -24,6 +24,16 @@ export class UpdateAgentInput {
|
||||
@Field()
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field()
|
||||
label?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
@@ -39,6 +49,11 @@ export class UpdateAgentInput {
|
||||
@Field(() => String)
|
||||
modelId?: ModelId;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
|
||||
Reference in New Issue
Block a user