Add new Agents (#14883)
Still experimenting --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
+15
-6
@@ -29,6 +29,7 @@ import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modu
|
||||
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 { AgentExecutionContext } from './agent-handoff-executor.service';
|
||||
import { AgentModelConfigService } from './agent-model-config.service';
|
||||
import { AgentToolGeneratorService } from './agent-tool-generator.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
@@ -40,7 +41,7 @@ export interface AgentExecutionResult {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AgentExecutionService {
|
||||
export class AgentExecutionService implements AgentExecutionContext {
|
||||
private readonly logger = new Logger(AgentExecutionService.name);
|
||||
|
||||
constructor(
|
||||
@@ -61,10 +62,12 @@ export class AgentExecutionService {
|
||||
messages,
|
||||
system,
|
||||
agent,
|
||||
excludeHandoffTools = false,
|
||||
}: {
|
||||
system: string;
|
||||
agent: AgentEntity | null;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
excludeHandoffTools?: boolean;
|
||||
}) {
|
||||
try {
|
||||
if (agent) {
|
||||
@@ -86,11 +89,17 @@ export class AgentExecutionService {
|
||||
agent.workspaceId,
|
||||
);
|
||||
|
||||
const handoffTools =
|
||||
await this.agentHandoffToolService.generateHandoffTools(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
);
|
||||
let handoffTools = {};
|
||||
|
||||
if (!excludeHandoffTools) {
|
||||
handoffTools =
|
||||
await this.agentHandoffToolService.generateHandoffTools(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
this, // Pass execution context
|
||||
);
|
||||
}
|
||||
|
||||
const nativeModelTools =
|
||||
this.agentModelConfigService.getNativeModelTools(
|
||||
registeredModel,
|
||||
|
||||
+75
-41
@@ -1,13 +1,21 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ModelMessage, generateText } from 'ai';
|
||||
import { ProviderOptions } from '@ai-sdk/provider-utils';
|
||||
import {
|
||||
generateText,
|
||||
LanguageModel,
|
||||
ModelMessage,
|
||||
StopCondition,
|
||||
streamText,
|
||||
ToolSet,
|
||||
UIDataTypes,
|
||||
UIMessage,
|
||||
UITools,
|
||||
} from 'ai';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
|
||||
import { AgentHandoffService } from './agent-handoff.service';
|
||||
import { AgentToolGeneratorService } from './agent-tool-generator.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
|
||||
@@ -15,9 +23,26 @@ export type HandoffRequest = {
|
||||
fromAgentId: string;
|
||||
toAgentId: string;
|
||||
workspaceId: string;
|
||||
messages: ModelMessage[];
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
isStreaming?: boolean;
|
||||
};
|
||||
|
||||
export interface AgentExecutionContext {
|
||||
prepareAIRequestConfig: (params: {
|
||||
system: string;
|
||||
agent: AgentEntity | null;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
excludeHandoffTools?: boolean; // Prevent infinite recursion
|
||||
}) => Promise<{
|
||||
system: string;
|
||||
tools: ToolSet;
|
||||
model: LanguageModel;
|
||||
messages: ModelMessage[];
|
||||
stopWhen?: StopCondition<ToolSet>;
|
||||
providerOptions?: ProviderOptions;
|
||||
}>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AgentHandoffExecutorService {
|
||||
private readonly logger = new Logger(AgentHandoffExecutorService.name);
|
||||
@@ -26,13 +51,20 @@ export class AgentHandoffExecutorService {
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly agentHandoffService: AgentHandoffService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
private readonly agentToolGeneratorService: AgentToolGeneratorService,
|
||||
) {}
|
||||
|
||||
async executeHandoff(handoffRequest: HandoffRequest) {
|
||||
async executeHandoff(
|
||||
handoffRequest: HandoffRequest,
|
||||
executionContext: AgentExecutionContext,
|
||||
) {
|
||||
try {
|
||||
const { fromAgentId, toAgentId, workspaceId, messages } = handoffRequest;
|
||||
const {
|
||||
fromAgentId,
|
||||
toAgentId,
|
||||
workspaceId,
|
||||
messages,
|
||||
isStreaming = false,
|
||||
} = handoffRequest;
|
||||
|
||||
const canHandoff = await this.agentHandoffService.canHandoffTo({
|
||||
fromAgentId,
|
||||
@@ -58,51 +90,53 @@ export class AgentHandoffExecutorService {
|
||||
);
|
||||
}
|
||||
|
||||
const registeredModel =
|
||||
await this.aiModelRegistryService.resolveModelForAgent(targetAgent);
|
||||
|
||||
if (!registeredModel) {
|
||||
throw new AgentException(
|
||||
`Model ${targetAgent.modelId} not found in registry`,
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const tools = await this.agentToolGeneratorService.generateToolsForAgent(
|
||||
toAgentId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const aiRequestConfig = {
|
||||
// Prepare AI request config using the execution context
|
||||
const aiRequestConfig = await executionContext.prepareAIRequestConfig({
|
||||
system: targetAgent.prompt,
|
||||
agent: targetAgent,
|
||||
messages,
|
||||
tools,
|
||||
model: registeredModel.model,
|
||||
};
|
||||
excludeHandoffTools: true, // Prevent infinite recursion
|
||||
});
|
||||
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
if (isStreaming) {
|
||||
// Return stream for streaming contexts
|
||||
const stream = streamText(aiRequestConfig);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully executed handoff to agent ${toAgentId} with response length: ${textResponse.text.length}`,
|
||||
);
|
||||
this.logger.log(`Started streaming handoff to agent ${toAgentId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully executed handoff to agent ${targetAgent.name}`,
|
||||
result: {
|
||||
response: textResponse.text,
|
||||
targetAgentName: targetAgent.name,
|
||||
},
|
||||
};
|
||||
return stream;
|
||||
} else {
|
||||
// Use generateText for non-streaming contexts (workflows)
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully executed handoff to agent ${toAgentId} with response length: ${textResponse.text.length}`,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully executed handoff to agent ${targetAgent.name}`,
|
||||
result: {
|
||||
response: textResponse.text,
|
||||
targetAgentName: targetAgent.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Handoff execution failed: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
|
||||
const { isStreaming = false, toAgentId } = handoffRequest;
|
||||
|
||||
if (isStreaming) {
|
||||
throw error; // Let streaming context handle the error
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to execute handoff to agent ${handoffRequest.toAgentId}`,
|
||||
message: `Failed to execute handoff to agent ${toAgentId}`,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
|
||||
+13
-4
@@ -2,7 +2,11 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ToolSet } from 'ai';
|
||||
|
||||
import { AgentHandoffExecutorService } from 'src/engine/metadata-modules/agent/agent-handoff-executor.service';
|
||||
import {
|
||||
AgentExecutionContext,
|
||||
AgentHandoffExecutorService,
|
||||
HandoffRequest,
|
||||
} from 'src/engine/metadata-modules/agent/agent-handoff-executor.service';
|
||||
import { AgentHandoffService } from 'src/engine/metadata-modules/agent/agent-handoff.service';
|
||||
import { AGENT_HANDOFF_DESCRIPTION_TEMPLATE } from 'src/engine/metadata-modules/agent/constants/agent-handoff-description.const';
|
||||
import { AGENT_HANDOFF_SCHEMA } from 'src/engine/metadata-modules/agent/constants/agent-handoff-schema.const';
|
||||
@@ -18,6 +22,7 @@ export class AgentHandoffToolService {
|
||||
public async generateHandoffTools(
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
executionContext: AgentExecutionContext,
|
||||
): Promise<ToolSet> {
|
||||
const handoffs = await this.agentHandoffService.getAgentHandoffs({
|
||||
fromAgentId: agentId,
|
||||
@@ -37,14 +42,18 @@ export class AgentHandoffToolService {
|
||||
),
|
||||
inputSchema: AGENT_HANDOFF_SCHEMA,
|
||||
execute: async ({ input }) => {
|
||||
const result = await this.agentHandoffExecutorService.executeHandoff({
|
||||
const handoffRequest: HandoffRequest = {
|
||||
fromAgentId: agentId,
|
||||
toAgentId: handoff.toAgent.id,
|
||||
workspaceId,
|
||||
messages: input.messages,
|
||||
});
|
||||
isStreaming: true, // Tools are executed during streaming
|
||||
};
|
||||
|
||||
return result;
|
||||
return this.agentHandoffExecutorService.executeHandoff(
|
||||
handoffRequest,
|
||||
executionContext,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -213,6 +213,9 @@ export class RoleService {
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
isEditable: true,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user