Feat: native model capabilities (#14787)

This commit is contained in:
Abdul Rahman
2025-10-01 22:07:21 +05:30
committed by GitHub
parent d715533a90
commit 20403664e3
29 changed files with 523 additions and 59 deletions
@@ -19,7 +19,6 @@ import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-
import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing.service';
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 { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentHandoffToolService } from 'src/engine/metadata-modules/agent/agent-handoff-tool.service';
@@ -30,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 { AgentModelConfigService } from './agent-model-config.service';
import { AgentToolGeneratorService } from './agent-tool-generator.service';
import { AgentEntity } from './agent.entity';
import { AgentException, AgentExceptionCode } from './agent.exception';
@@ -51,11 +51,10 @@ export class AgentExecutionService {
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly aiModelRegistryService: AiModelRegistryService,
private readonly agentToolGeneratorService: AgentToolGeneratorService,
private readonly agentModelConfigService: AgentModelConfigService,
private readonly aiBillingService: AIBillingService,
@InjectRepository(AgentEntity)
private readonly agentRepository: Repository<AgentEntity>,
@InjectRepository(FileEntity)
private readonly fileRepository: Repository<FileEntity>,
) {}
async prepareAIRequestConfig({
@@ -78,6 +77,7 @@ export class AgentExecutionService {
await this.aiModelRegistryService.resolveModelForAgent(agent);
let tools: ToolSet = {};
let providerOptions;
if (agent) {
const baseTools =
@@ -91,8 +91,18 @@ export class AgentExecutionService {
agent.id,
agent.workspaceId,
);
const nativeModelTools =
this.agentModelConfigService.getNativeModelTools(
registeredModel,
agent,
);
tools = { ...baseTools, ...handoffTools };
tools = { ...baseTools, ...handoffTools, ...nativeModelTools };
providerOptions = this.agentModelConfigService.getProviderOptions(
registeredModel,
agent,
);
}
this.logger.log(`Generated ${Object.keys(tools).length} tools for agent`);
@@ -103,16 +113,7 @@ export class AgentExecutionService {
model: registeredModel.model,
messages: convertToModelMessages(messages),
stopWhen: stepCountIs(AGENT_CONFIG.MAX_STEPS),
...(registeredModel.doesSupportThinking && {
providerOptions: {
anthropic: {
thinking: {
type: 'enabled',
budgetTokens: AGENT_CONFIG.REASONING_BUDGET_TOKENS,
},
},
},
}),
providerOptions,
};
} catch (error) {
this.logger.error(
@@ -0,0 +1,100 @@
import { Injectable } from '@nestjs/common';
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { ProviderOptions } from '@ai-sdk/provider-utils';
import { ToolSet } from 'ai';
import { ModelProvider } from 'src/engine/core-modules/ai/constants/ai-models.const';
import { RegisteredAIModel } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
import { AGENT_CONFIG } from 'src/engine/metadata-modules/agent/constants/agent-config.const';
import { AgentEntity } from './agent.entity';
@Injectable()
export class AgentModelConfigService {
constructor() {}
getProviderOptions(
model: RegisteredAIModel,
agent: AgentEntity,
): ProviderOptions {
switch (model.provider) {
case ModelProvider.XAI:
return this.getXaiProviderOptions(agent);
case ModelProvider.ANTHROPIC:
return this.getAnthropicProviderOptions(model);
default:
return {};
}
}
getNativeModelTools(model: RegisteredAIModel, agent: AgentEntity): ToolSet {
const tools: ToolSet = {};
if (!agent.modelConfiguration) {
return tools;
}
switch (model.provider) {
case ModelProvider.ANTHROPIC:
if (agent.modelConfiguration.webSearch?.enabled) {
tools.web_search = anthropic.tools.webSearch_20250305();
}
break;
case ModelProvider.OPENAI:
if (agent.modelConfiguration.webSearch?.enabled) {
tools.web_search = openai.tools.webSearch();
}
break;
}
return tools;
}
private getXaiProviderOptions(agent: AgentEntity): ProviderOptions {
if (
!agent.modelConfiguration ||
(!agent.modelConfiguration.webSearch?.enabled &&
!agent.modelConfiguration.twitterSearch?.enabled)
) {
return {};
}
const sources: Array<{ type: string }> = [];
if (agent.modelConfiguration.webSearch?.enabled) {
sources.push({ type: 'web' });
}
if (agent.modelConfiguration.twitterSearch?.enabled) {
sources.push({ type: 'x' });
}
return {
xai: {
searchParameters: {
mode: 'auto',
...(sources.length > 0 && { sources }),
},
},
};
}
private getAnthropicProviderOptions(
model: RegisteredAIModel,
): ProviderOptions {
if (!model.doesSupportThinking) {
return {};
}
return {
anthropic: {
thinking: {
type: 'enabled',
budgetTokens: AGENT_CONFIG.REASONING_BUDGET_TOKENS,
},
},
};
}
}
@@ -16,6 +16,7 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
import { AgentChatThreadEntity } from './agent-chat-thread.entity';
import { AgentHandoffEntity } from './agent-handoff.entity';
@@ -93,4 +94,7 @@ export class AgentEntity {
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt?: Date;
@Column({ nullable: true, type: 'jsonb' })
modelConfiguration: ModelConfiguration;
}
@@ -31,6 +31,7 @@ import { AgentHandoffExecutorService } from './agent-handoff-executor.service';
import { AgentHandoffToolService } from './agent-handoff-tool.service';
import { AgentHandoffEntity } from './agent-handoff.entity';
import { AgentHandoffService } from './agent-handoff.service';
import { AgentModelConfigService } from './agent-model-config.service';
import { AgentStreamingService } from './agent-streaming.service';
import { AgentTitleGenerationService } from './agent-title-generation.service';
import { AgentToolGeneratorService } from './agent-tool-generator.service';
@@ -72,6 +73,7 @@ import { AgentService } from './agent.service';
AgentChatResolver,
AgentService,
AgentExecutionService,
AgentModelConfigService,
AgentToolGeneratorService,
AgentHandoffToolService,
AgentChatService,
@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { AgentRoleService } from 'src/engine/metadata-modules/agent-role/agent-role.service';
@@ -133,7 +132,7 @@ export class AgentService {
name: updatedName,
});
if (!isDefined(input.roleId)) {
if (!('roleId' in input)) {
return updatedAgent;
}
@@ -11,6 +11,7 @@ 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';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
@ObjectType('Agent')
export class AgentDTO {
@@ -66,4 +67,7 @@ export class AgentDTO {
@IsDateString()
@Field()
updatedAt: Date;
@Field(() => GraphQLJSON, { nullable: true })
modelConfiguration: ModelConfiguration;
}
@@ -11,6 +11,7 @@ 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';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
@InputType()
export class CreateAgentInput {
@@ -54,6 +55,11 @@ export class CreateAgentInput {
@Field(() => GraphQLJSON, { nullable: true })
responseFormat?: object;
@IsObject()
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
modelConfiguration?: ModelConfiguration;
@HideField()
standardId?: string;
@@ -11,6 +11,7 @@ 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';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
@InputType()
export class UpdateAgentInput {
@@ -58,4 +59,9 @@ export class UpdateAgentInput {
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
responseFormat?: object;
@IsObject()
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
modelConfiguration?: ModelConfiguration;
}
@@ -0,0 +1,10 @@
export type ModelConfiguration = {
webSearch?: {
enabled: boolean;
configuration: object;
};
twitterSearch?: {
enabled: boolean;
configuration: object;
};
};
@@ -18,5 +18,6 @@ export const transformAgentEntityToFlatAgent = (
isCustom: agentEntity.isCustom,
universalIdentifier: agentEntity.standardId || agentEntity.id,
applicationId: agentEntity.applicationId,
modelConfiguration: agentEntity.modelConfiguration,
};
};