Feat: native model capabilities (#14787)
This commit is contained in:
@@ -57,6 +57,7 @@ export type Agent = {
|
||||
id: Scalars['UUID'];
|
||||
isCustom: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: Maybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
@@ -485,6 +486,7 @@ export type ClientAiModelConfig = {
|
||||
inputCostPer1kTokensInCredits: Scalars['Float'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
nativeCapabilities?: Maybe<NativeModelCapabilities>;
|
||||
outputCostPer1kTokensInCredits: Scalars['Float'];
|
||||
provider: ModelProvider;
|
||||
};
|
||||
@@ -697,6 +699,7 @@ export type CreateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: InputMaybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
prompt: Scalars['String'];
|
||||
@@ -2629,6 +2632,12 @@ export type MutationVerifyTwoFactorAuthenticationMethodForAuthenticatedUserArgs
|
||||
otp: Scalars['String'];
|
||||
};
|
||||
|
||||
export type NativeModelCapabilities = {
|
||||
__typename?: 'NativeModelCapabilities';
|
||||
twitterSearch?: Maybe<Scalars['Boolean']>;
|
||||
webSearch?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type NumberChartConfiguration = {
|
||||
__typename?: 'NumberChartConfiguration';
|
||||
aggregateFieldMetadataId: Scalars['UUID'];
|
||||
@@ -3802,6 +3811,7 @@ export type UpdateAgentInput = {
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: InputMaybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
@@ -4459,7 +4469,7 @@ export type WorkspaceUrlsAndId = {
|
||||
workspaceUrls: WorkspaceUrls;
|
||||
};
|
||||
|
||||
export type AgentFieldsFragment = { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string };
|
||||
export type AgentFieldsFragment = { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string };
|
||||
|
||||
export type AssignRoleToAgentMutationVariables = Exact<{
|
||||
agentId: Scalars['UUID'];
|
||||
@@ -4488,14 +4498,14 @@ export type CreateOneAgentMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type DeleteOneAgentMutationVariables = Exact<{
|
||||
input: AgentIdInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type RemoveAgentHandoffMutationVariables = Exact<{
|
||||
input: RemoveAgentHandoffInput;
|
||||
@@ -4516,7 +4526,7 @@ export type UpdateOneAgentMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type FindAgentHandoffTargetsQueryVariables = Exact<{
|
||||
input: AgentIdInput;
|
||||
@@ -4535,14 +4545,14 @@ export type FindAgentHandoffsQuery = { __typename?: 'Query', findAgentHandoffs:
|
||||
export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string }> };
|
||||
export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type FindOneAgentQueryVariables = Exact<{
|
||||
id: Scalars['UUID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type GetAgentChatMessagesQueryVariables = Exact<{
|
||||
threadId: Scalars['UUID'];
|
||||
@@ -5296,7 +5306,7 @@ export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPer
|
||||
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> };
|
||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> };
|
||||
|
||||
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
||||
input: CreateApprovedAccessDomainInput;
|
||||
@@ -5903,6 +5913,7 @@ export const AgentFieldsFragmentDoc = gql`
|
||||
responseFormat
|
||||
roleId
|
||||
isCustom
|
||||
modelConfiguration
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export type Agent = {
|
||||
id: Scalars['UUID'];
|
||||
isCustom: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: Maybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
@@ -485,6 +486,7 @@ export type ClientAiModelConfig = {
|
||||
inputCostPer1kTokensInCredits: Scalars['Float'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
nativeCapabilities?: Maybe<NativeModelCapabilities>;
|
||||
outputCostPer1kTokensInCredits: Scalars['Float'];
|
||||
provider: ModelProvider;
|
||||
};
|
||||
@@ -697,6 +699,7 @@ export type CreateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: InputMaybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
prompt: Scalars['String'];
|
||||
@@ -2486,6 +2489,12 @@ export type MutationVerifyTwoFactorAuthenticationMethodForAuthenticatedUserArgs
|
||||
otp: Scalars['String'];
|
||||
};
|
||||
|
||||
export type NativeModelCapabilities = {
|
||||
__typename?: 'NativeModelCapabilities';
|
||||
twitterSearch?: Maybe<Scalars['Boolean']>;
|
||||
webSearch?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type NumberChartConfiguration = {
|
||||
__typename?: 'NumberChartConfiguration';
|
||||
aggregateFieldMetadataId: Scalars['UUID'];
|
||||
@@ -3580,6 +3589,7 @@ export type UpdateAgentInput = {
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
label: Scalars['String'];
|
||||
modelConfiguration?: InputMaybe<Scalars['JSON']>;
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
|
||||
+15
-12
@@ -3,11 +3,14 @@ import { IconDotsVertical } from 'twenty-ui/display';
|
||||
|
||||
import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer';
|
||||
import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer';
|
||||
import { type ToolInput } from '@/ai/types/ToolInput';
|
||||
import { type ToolOutput } from '@/ai/types/ToolOutput';
|
||||
import { keyframes, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import type { ToolUIPart, UIDataTypes, UIMessagePart, UITools } from 'ai';
|
||||
import {
|
||||
isToolUIPart,
|
||||
type UIDataTypes,
|
||||
type UIMessagePart,
|
||||
type UITools,
|
||||
} from 'ai';
|
||||
|
||||
const StyledStepsContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -64,29 +67,29 @@ export const AIChatAssistantMessageRenderer = ({
|
||||
isLastMessageStreaming: boolean;
|
||||
}) => {
|
||||
const renderStep = (
|
||||
step: UIMessagePart<UIDataTypes, UITools>,
|
||||
part: UIMessagePart<UIDataTypes, UITools>,
|
||||
index: number,
|
||||
) => {
|
||||
switch (step.type) {
|
||||
switch (part.type) {
|
||||
case 'reasoning':
|
||||
return (
|
||||
<ReasoningSummaryDisplay
|
||||
key={index}
|
||||
content={step.text}
|
||||
isThinking={step.state === 'streaming'}
|
||||
content={part.text}
|
||||
isThinking={part.state === 'streaming'}
|
||||
/>
|
||||
);
|
||||
case 'text':
|
||||
return <LazyMarkdownRenderer key={index} text={step.text} />;
|
||||
return <LazyMarkdownRenderer key={index} text={part.text} />;
|
||||
default:
|
||||
{
|
||||
if (step.type.includes('tool-')) {
|
||||
const { output, input, type } = step as ToolUIPart;
|
||||
if (isToolUIPart(part)) {
|
||||
const { output, input, type } = part;
|
||||
return (
|
||||
<ToolStepRenderer
|
||||
key={index}
|
||||
input={input as ToolInput}
|
||||
output={output as ToolOutput}
|
||||
input={input}
|
||||
output={output}
|
||||
toolName={type.split('-')[1]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,8 +7,9 @@ import { AnimatedExpandableContainer } from 'twenty-ui/layout';
|
||||
|
||||
import { ShimmeringText } from '@/ai/components/ShimmeringText';
|
||||
import { type ToolInput } from '@/ai/types/ToolInput';
|
||||
import { type ToolOutput } from '@/ai/types/ToolOutput';
|
||||
import { getToolIcon } from '@/ai/utils/getToolIcon';
|
||||
import { getToolDisplayMessage } from '@/ai/utils/getWebSearchToolDisplayMessage';
|
||||
import { type ToolUIPart } from 'ai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -73,7 +74,7 @@ export const ToolStepRenderer = ({
|
||||
toolName,
|
||||
}: {
|
||||
input: ToolInput;
|
||||
output: ToolOutput;
|
||||
output: ToolUIPart['output'];
|
||||
toolName: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@@ -86,7 +87,9 @@ export const ToolStepRenderer = ({
|
||||
<StyledContainer>
|
||||
<StyledLoadingContainer>
|
||||
<ShimmeringText>
|
||||
<StyledDisplayMessage>{input?.loadingMessage}</StyledDisplayMessage>
|
||||
<StyledDisplayMessage>
|
||||
{getToolDisplayMessage(input, toolName, false)}
|
||||
</StyledDisplayMessage>
|
||||
</ShimmeringText>
|
||||
</StyledLoadingContainer>
|
||||
</StyledContainer>
|
||||
@@ -94,9 +97,12 @@ export const ToolStepRenderer = ({
|
||||
}
|
||||
|
||||
const displayMessage =
|
||||
output && typeof output === 'object' && 'message' in output
|
||||
? (output as { message: string }).message
|
||||
: undefined;
|
||||
output &&
|
||||
typeof output === 'object' &&
|
||||
'message' in output &&
|
||||
typeof output.message === 'string'
|
||||
? output.message
|
||||
: getToolDisplayMessage(input, toolName, true);
|
||||
|
||||
const result =
|
||||
output && typeof output === 'object' && 'result' in output
|
||||
|
||||
@@ -12,6 +12,7 @@ export const AGENT_FRAGMENT = gql`
|
||||
responseFormat
|
||||
roleId
|
||||
isCustom
|
||||
modelConfiguration
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export type ToolInput = {
|
||||
loadingMessage: string;
|
||||
input: Record<string, unknown>;
|
||||
};
|
||||
import { type ToolUIPart } from 'ai';
|
||||
|
||||
export type ToolInput = ToolUIPart['input'];
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type ToolOutput = Record<string, unknown>;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { type ToolInput } from '@/ai/types/ToolInput';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const extractSearchQuery = (input: ToolInput): string => {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (
|
||||
typeof input === 'object' &&
|
||||
'query' in input &&
|
||||
typeof input.query === 'string'
|
||||
) {
|
||||
return input.query;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof input === 'object' &&
|
||||
'action' in input &&
|
||||
isDefined(input.action) &&
|
||||
typeof input.action === 'object' &&
|
||||
'query' in input.action &&
|
||||
typeof input.action.query === 'string'
|
||||
) {
|
||||
return input.action.query;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const extractLoadingMessage = (input: ToolInput): string => {
|
||||
if (
|
||||
isDefined(input) &&
|
||||
typeof input === 'object' &&
|
||||
'loadingMessage' in input &&
|
||||
typeof input.loadingMessage === 'string'
|
||||
) {
|
||||
return input.loadingMessage;
|
||||
}
|
||||
|
||||
return 'Processing...';
|
||||
};
|
||||
|
||||
export const getToolDisplayMessage = (
|
||||
input: ToolInput,
|
||||
toolName: string,
|
||||
isFinished?: boolean,
|
||||
): string => {
|
||||
if (toolName === 'web_search') {
|
||||
const query = extractSearchQuery(input);
|
||||
const action = isFinished ? 'Searched' : 'Searching';
|
||||
return query ? `${action} the web for '${query}'` : `${action} the web`;
|
||||
}
|
||||
|
||||
return extractLoadingMessage(input);
|
||||
};
|
||||
@@ -71,9 +71,10 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
|
||||
description: agent.description,
|
||||
icon: agent.icon || 'IconRobot',
|
||||
modelId: agent.modelId,
|
||||
role: agent.roleId ?? undefined,
|
||||
role: agent.roleId,
|
||||
prompt: agent.prompt,
|
||||
isCustom: agent.isCustom,
|
||||
modelConfiguration: agent.modelConfiguration || {},
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({
|
||||
@@ -118,8 +119,9 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
|
||||
description: formValues.description,
|
||||
icon: formValues.icon,
|
||||
modelId: formValues.modelId,
|
||||
roleId: formValues.role || undefined,
|
||||
roleId: formValues.role,
|
||||
prompt: formValues.prompt,
|
||||
modelConfiguration: formValues.modelConfiguration,
|
||||
};
|
||||
|
||||
await createAgent({
|
||||
@@ -142,8 +144,9 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
|
||||
description: formValues.description,
|
||||
icon: formValues.icon,
|
||||
modelId: formValues.modelId,
|
||||
roleId: formValues.role || undefined,
|
||||
roleId: formValues.role,
|
||||
prompt: formValues.prompt,
|
||||
modelConfiguration: formValues.modelConfiguration,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { aiModelsState } from '@/client-config/states/aiModelsState';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconBrandX, IconWorld } from 'twenty-ui/display';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
const StyledCheckboxContainer = styled.div<{ disabled: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
transition: background-color
|
||||
${({ theme }) => theme.animation.duration.normal}s ease;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) =>
|
||||
disabled ? 'transparent' : theme.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCheckboxLabel = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type ModelConfiguration = {
|
||||
webSearch?: {
|
||||
enabled: boolean;
|
||||
configuration?: Record<string, unknown>;
|
||||
};
|
||||
twitterSearch?: {
|
||||
enabled: boolean;
|
||||
configuration?: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
type SettingsAgentModelCapabilitiesProps = {
|
||||
selectedModelId: string;
|
||||
modelConfiguration: ModelConfiguration;
|
||||
onConfigurationChange: (configuration: ModelConfiguration) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const SettingsAgentModelCapabilities = ({
|
||||
selectedModelId,
|
||||
modelConfiguration,
|
||||
onConfigurationChange,
|
||||
disabled = false,
|
||||
}: SettingsAgentModelCapabilitiesProps) => {
|
||||
const theme = useTheme();
|
||||
const aiModels = useRecoilValue(aiModelsState);
|
||||
|
||||
const selectedModel = aiModels.find((m) => m.modelId === selectedModelId);
|
||||
const nativeCapabilities = selectedModel?.nativeCapabilities;
|
||||
|
||||
if (!isDefined(nativeCapabilities)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nativeCapabilities.webSearch && !nativeCapabilities.twitterSearch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleCapabilityToggle = (
|
||||
capability: 'webSearch' | 'twitterSearch',
|
||||
enabled: boolean,
|
||||
) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfigurationChange({
|
||||
...modelConfiguration,
|
||||
[capability]: {
|
||||
enabled,
|
||||
configuration: modelConfiguration[capability]?.configuration || {},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const capabilities = [
|
||||
...(nativeCapabilities.webSearch
|
||||
? [
|
||||
{
|
||||
key: 'webSearch' as const,
|
||||
label: t`Web Search`,
|
||||
Icon: IconWorld,
|
||||
enabled: modelConfiguration.webSearch?.enabled || false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(nativeCapabilities.twitterSearch
|
||||
? [
|
||||
{
|
||||
key: 'twitterSearch' as const,
|
||||
label: t`Twitter/X Search`,
|
||||
Icon: IconBrandX,
|
||||
enabled: modelConfiguration.twitterSearch?.enabled || false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<InputLabel>{t`Enable model-specific features`}</InputLabel>
|
||||
<div>
|
||||
{capabilities.map((capability) => (
|
||||
<StyledCheckboxContainer
|
||||
disabled={disabled}
|
||||
key={capability.key}
|
||||
onClick={() =>
|
||||
handleCapabilityToggle(capability.key, !capability.enabled)
|
||||
}
|
||||
>
|
||||
<StyledCheckboxLabel>
|
||||
<capability.Icon size={theme.icon.size.sm} />
|
||||
<span>{capability.label}</span>
|
||||
</StyledCheckboxLabel>
|
||||
<Checkbox
|
||||
checked={capability.enabled}
|
||||
onChange={(event) => {
|
||||
event.stopPropagation();
|
||||
handleCapabilityToggle(capability.key, event.target.checked);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
+22
-7
@@ -9,6 +9,7 @@ import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useGetRolesQuery } from '~/generated-metadata/graphql';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
import { SettingsAgentModelCapabilities } from '../../components/SettingsAgentModelCapabilities';
|
||||
import { type SettingsAIAgentFormValues } from '../../hooks/useSettingsAgentFormState';
|
||||
|
||||
const StyledFormContainer = styled.div`
|
||||
@@ -52,13 +53,18 @@ export const SettingsAIAgentForm = ({
|
||||
const modelOptions = useAiModelOptions();
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
|
||||
const rolesOptions =
|
||||
rolesData?.getRoles
|
||||
const rolesOptions = [
|
||||
{
|
||||
label: t`None`,
|
||||
value: null,
|
||||
},
|
||||
...(rolesData?.getRoles
|
||||
?.filter((role) => role.canBeAssignedToAgents)
|
||||
.map((role) => ({
|
||||
label: role.label,
|
||||
value: role.id,
|
||||
})) || [];
|
||||
})) || []),
|
||||
];
|
||||
|
||||
const noModelsAvailable = modelOptions.length === 0;
|
||||
|
||||
@@ -122,6 +128,19 @@ export const SettingsAIAgentForm = ({
|
||||
)}
|
||||
</StyledFormContainer>
|
||||
|
||||
{formValues.modelId && (
|
||||
<StyledFormContainer>
|
||||
<SettingsAgentModelCapabilities
|
||||
selectedModelId={formValues.modelId}
|
||||
modelConfiguration={formValues.modelConfiguration || {}}
|
||||
onConfigurationChange={(configuration) =>
|
||||
onFieldChange('modelConfiguration', configuration)
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
)}
|
||||
|
||||
<StyledFormContainer>
|
||||
<Select
|
||||
dropdownId="ai-role-select"
|
||||
@@ -129,10 +148,6 @@ export const SettingsAIAgentForm = ({
|
||||
value={formValues.role || ''}
|
||||
onChange={(value) => onFieldChange('role', value)}
|
||||
options={rolesOptions}
|
||||
emptyOption={{
|
||||
label: t`Select a role`,
|
||||
value: '',
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
|
||||
@@ -14,9 +14,10 @@ export const useSettingsAgentFormState = (mode: 'create' | 'edit') => {
|
||||
description: '',
|
||||
icon: 'IconRobot',
|
||||
modelId: mode === 'edit' ? '' : 'auto',
|
||||
role: '',
|
||||
role: null,
|
||||
prompt: '',
|
||||
isCustom: true,
|
||||
modelConfiguration: {},
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -50,6 +51,7 @@ export const useSettingsAgentFormState = (mode: 'create' | 'edit') => {
|
||||
role: '',
|
||||
prompt: '',
|
||||
isCustom: true,
|
||||
modelConfiguration: {},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+17
-1
@@ -7,9 +7,25 @@ export const settingsAIAgentFormSchema = z.object({
|
||||
description: z.string().nullish(),
|
||||
icon: z.string().optional(),
|
||||
modelId: z.union([z.string().min(1, 'Model is required'), z.literal('auto')]),
|
||||
role: z.string().optional(),
|
||||
role: z.string().nullish(),
|
||||
prompt: zodNonEmptyString,
|
||||
isCustom: z.boolean().default(true),
|
||||
modelConfiguration: z
|
||||
.object({
|
||||
webSearch: z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
configuration: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
twitterSearch: z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
configuration: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type SettingsAIAgentFormValues = z.infer<
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddNativeCapabilitesToAgent1759200603485
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddNativeCapabilitesToAgent1759200603485';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."agent" ADD "modelConfiguration" jsonb`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."agent" DROP COLUMN "modelConfiguration"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ export interface AIModelConfig {
|
||||
inputCostPer1kTokensInCents: number;
|
||||
outputCostPer1kTokensInCents: number;
|
||||
doesSupportThinking?: boolean;
|
||||
nativeCapabilities?: {
|
||||
webSearch?: boolean;
|
||||
twitterSearch?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const AI_MODELS: AIModelConfig[] = [
|
||||
@@ -35,6 +39,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.OPENAI,
|
||||
inputCostPer1kTokensInCents: 0.25,
|
||||
outputCostPer1kTokensInCents: 1.0,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'gpt-4o-mini',
|
||||
@@ -42,6 +49,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.OPENAI,
|
||||
inputCostPer1kTokensInCents: 0.015,
|
||||
outputCostPer1kTokensInCents: 0.06,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'gpt-4-turbo',
|
||||
@@ -49,6 +59,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.OPENAI,
|
||||
inputCostPer1kTokensInCents: 1.0,
|
||||
outputCostPer1kTokensInCents: 3.0,
|
||||
nativeCapabilities: {
|
||||
webSearch: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'claude-opus-4-20250514',
|
||||
@@ -57,6 +70,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
inputCostPer1kTokensInCents: 1.5,
|
||||
outputCostPer1kTokensInCents: 7.5,
|
||||
doesSupportThinking: true,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'claude-sonnet-4-20250514',
|
||||
@@ -65,6 +81,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
inputCostPer1kTokensInCents: 0.3,
|
||||
outputCostPer1kTokensInCents: 1.5,
|
||||
doesSupportThinking: true,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'claude-3-5-haiku-20241022',
|
||||
@@ -72,7 +91,10 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.ANTHROPIC,
|
||||
inputCostPer1kTokensInCents: 0.08,
|
||||
outputCostPer1kTokensInCents: 0.4,
|
||||
doesSupportThinking: true,
|
||||
doesSupportThinking: false,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'grok-3',
|
||||
@@ -80,6 +102,10 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.XAI,
|
||||
inputCostPer1kTokensInCents: 0.3,
|
||||
outputCostPer1kTokensInCents: 1.5,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
twitterSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'grok-3-mini',
|
||||
@@ -87,6 +113,10 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.XAI,
|
||||
inputCostPer1kTokensInCents: 0.03,
|
||||
outputCostPer1kTokensInCents: 0.05,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
twitterSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: 'grok-4',
|
||||
@@ -94,5 +124,9 @@ export const AI_MODELS: AIModelConfig[] = [
|
||||
provider: ModelProvider.XAI,
|
||||
inputCostPer1kTokensInCents: 0.5,
|
||||
outputCostPer1kTokensInCents: 2.5,
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
twitterSearch: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -19,6 +19,15 @@ registerEnumType(ModelProvider, {
|
||||
name: 'ModelProvider',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
class NativeModelCapabilities {
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
webSearch?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
twitterSearch?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ClientAIModelConfig {
|
||||
@Field(() => String)
|
||||
@@ -35,6 +44,9 @@ export class ClientAIModelConfig {
|
||||
|
||||
@Field(() => Number)
|
||||
outputCostPer1kTokensInCredits: number;
|
||||
|
||||
@Field(() => NativeModelCapabilities, { nullable: true })
|
||||
nativeCapabilities?: NativeModelCapabilities;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ export class ClientConfigService {
|
||||
modelId: registeredModel.modelId,
|
||||
label: builtInModel?.label || registeredModel.modelId,
|
||||
provider: registeredModel.provider,
|
||||
nativeCapabilities: builtInModel?.nativeCapabilities,
|
||||
inputCostPer1kTokensInCredits: builtInModel
|
||||
? convertCentsToBillingCredits(
|
||||
builtInModel.inputCostPer1kTokensInCents,
|
||||
|
||||
+15
-14
@@ -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(
|
||||
|
||||
+100
@@ -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;
|
||||
};
|
||||
};
|
||||
+1
@@ -18,5 +18,6 @@ export const transformAgentEntityToFlatAgent = (
|
||||
isCustom: agentEntity.isCustom,
|
||||
universalIdentifier: agentEntity.standardId || agentEntity.id,
|
||||
applicationId: agentEntity.applicationId,
|
||||
modelConfiguration: agentEntity.modelConfiguration,
|
||||
};
|
||||
};
|
||||
|
||||
+1
@@ -47,4 +47,5 @@ Be helpful, thorough, and always prioritize user understanding and workflow effe
|
||||
responseFormat: {},
|
||||
isCustom: false,
|
||||
standardRoleId: WORKFLOW_MANAGER_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
};
|
||||
|
||||
+1
@@ -191,6 +191,7 @@ export const createAgentToolTestModule =
|
||||
chatThreads: [],
|
||||
incomingHandoffs: [],
|
||||
outgoingHandoffs: [],
|
||||
modelConfiguration: {},
|
||||
};
|
||||
|
||||
const testRole: RoleEntity = {
|
||||
|
||||
Reference in New Issue
Block a user