Fix ai agent node prompt and variables (#18275)
- prompt stored on workflow lvl so input variables can be resolved and it can evolves with versions - make ai agent node output available as variables
This commit is contained in:
+3
-1
@@ -13,6 +13,7 @@ import { SettingsAgentModelCapabilities } from '~/pages/settings/ai/components/S
|
||||
import { SettingsAgentResponseFormat } from '~/pages/settings/ai/components/SettingsAgentResponseFormat';
|
||||
|
||||
type WorkflowAiAgentPromptTabProps = {
|
||||
prompt: string;
|
||||
readonly: boolean;
|
||||
aiModelOptions: SelectOption[];
|
||||
onPromptChange: (value: string) => void;
|
||||
@@ -25,6 +26,7 @@ type WorkflowAiAgentPromptTabProps = {
|
||||
};
|
||||
|
||||
export const WorkflowAiAgentPromptTab = ({
|
||||
prompt,
|
||||
readonly,
|
||||
aiModelOptions,
|
||||
onPromptChange,
|
||||
@@ -42,7 +44,7 @@ export const WorkflowAiAgentPromptTab = ({
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
label={t`Instructions for AI`}
|
||||
placeholder={t`Describe what you want the AI to do...`}
|
||||
defaultValue={workflowAiAgentActionAgent?.prompt || ''}
|
||||
defaultValue={prompt}
|
||||
onChange={onPromptChange}
|
||||
readonly={readonly}
|
||||
/>
|
||||
|
||||
+25
-24
@@ -4,19 +4,21 @@ import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||
import { type WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { useUpdateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionStep';
|
||||
import { WorkflowAiAgentPermissionsTab } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowAiAgentPermissionsTab';
|
||||
import { WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentTabListComponentId';
|
||||
import { WORKFLOW_AI_AGENT_TABS } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentTabs';
|
||||
import { useResetWorkflowAiAgentPermissionsStateOnCommandMenuClose } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useResetWorkflowAiAgentPermissionsStateOnCommandMenuClose';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
|
||||
import { workflowAiAgentPermissionsIsAddingPermissionState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
type AgentResponseSchema,
|
||||
type ModelConfiguration,
|
||||
@@ -32,7 +34,6 @@ import {
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
|
||||
import { WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentTabListComponentId';
|
||||
import { WorkflowAiAgentPromptTab } from './WorkflowAiAgentPromptTab';
|
||||
|
||||
export type WorkflowAiAgentTabId =
|
||||
@@ -82,31 +83,30 @@ export const WorkflowEditActionAiAgent = ({
|
||||
const { updateWorkflowVersionStep } = useUpdateWorkflowVersionStep();
|
||||
const flow = useFlowOrThrow();
|
||||
|
||||
const handleAgentPromptChange = useDebouncedCallback(
|
||||
async (newPrompt: string) => {
|
||||
if (
|
||||
actionOptions.readonly === true ||
|
||||
!isDefined(workflowAiAgentActionAgent)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const actionPrompt = action.settings.input.prompt || '';
|
||||
const [prompt, setPrompt] = useState(actionPrompt);
|
||||
|
||||
const response = await updateAgent({
|
||||
variables: {
|
||||
input: {
|
||||
id: workflowAiAgentActionAgent.id,
|
||||
prompt: newPrompt,
|
||||
},
|
||||
const savePrompt = useDebouncedCallback((newPrompt: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
settings: {
|
||||
...action.settings,
|
||||
input: {
|
||||
...action.settings.input,
|
||||
prompt: newPrompt,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
setWorkflowAiAgentActionAgent({
|
||||
...workflowAiAgentActionAgent,
|
||||
...response.data?.updateOneAgent,
|
||||
});
|
||||
},
|
||||
500,
|
||||
);
|
||||
const handleAgentPromptChange = (newPrompt: string) => {
|
||||
setPrompt(newPrompt);
|
||||
savePrompt(newPrompt);
|
||||
};
|
||||
|
||||
const handleAgentModelChange = async (modelId: string) => {
|
||||
if (
|
||||
@@ -291,6 +291,7 @@ export const WorkflowEditActionAiAgent = ({
|
||||
) : (
|
||||
<WorkflowStepBody>
|
||||
<WorkflowAiAgentPromptTab
|
||||
prompt={prompt}
|
||||
readonly={actionOptions.readonly === true}
|
||||
aiModelOptions={aiModelOptions}
|
||||
onPromptChange={handleAgentPromptChange}
|
||||
|
||||
+20
-11
@@ -44,15 +44,6 @@ describe('computeStepOutputSchema', () => {
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for AI_AGENT step type', () => {
|
||||
const result = computeStepOutputSchema({
|
||||
step: { type: 'AI_AGENT', settings: {} } as any,
|
||||
objectMetadataItems: [],
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for WEBHOOK step type', () => {
|
||||
const result = computeStepOutputSchema({
|
||||
step: { type: 'WEBHOOK', settings: {} } as any,
|
||||
@@ -417,6 +408,24 @@ describe('computeStepOutputSchema', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('AI_AGENT step', () => {
|
||||
it('should return response schema', () => {
|
||||
const result = computeStepOutputSchema({
|
||||
step: { type: 'AI_AGENT', settings: {} } as any,
|
||||
objectMetadataItems: [],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Response',
|
||||
value: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Empty output schema steps', () => {
|
||||
it.each(['FILTER', 'DELAY', 'EMPTY'])(
|
||||
'should return empty object for %s step type',
|
||||
@@ -452,8 +461,8 @@ describe('shouldComputeOutputSchemaOnFrontend', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('HTTP_REQUEST')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for AI_AGENT', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('AI_AGENT')).toBe(false);
|
||||
it('should return true for AI_AGENT', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('AI_AGENT')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for WEBHOOK', () => {
|
||||
|
||||
+11
-1
@@ -16,7 +16,6 @@ import { DatabaseEventAction } from '~/generated-metadata/graphql';
|
||||
const PERSISTED_OUTPUT_SCHEMA_TYPES = [
|
||||
'CODE',
|
||||
'HTTP_REQUEST',
|
||||
'AI_AGENT',
|
||||
'WEBHOOK',
|
||||
'ITERATOR',
|
||||
];
|
||||
@@ -191,6 +190,17 @@ export const computeStepOutputSchema = ({
|
||||
return generateFormOutputSchema(formFields, objectMetadataItems);
|
||||
}
|
||||
|
||||
case 'AI_AGENT': {
|
||||
return {
|
||||
response: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Response',
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'SEND_EMAIL':
|
||||
case 'DRAFT_EMAIL': {
|
||||
return {
|
||||
|
||||
+1
-7
@@ -1,17 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkflowCommonModule,
|
||||
FeatureFlagModule,
|
||||
TypeOrmModule.forFeature([AgentEntity]),
|
||||
],
|
||||
imports: [WorkflowCommonModule, FeatureFlagModule],
|
||||
providers: [WorkflowSchemaWorkspaceService],
|
||||
exports: [WorkflowSchemaWorkspaceService],
|
||||
})
|
||||
|
||||
+9
-40
@@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isString } from '@sniptt/guards';
|
||||
import { isDefined, isValidVariable } from 'twenty-shared/utils';
|
||||
@@ -12,11 +11,9 @@ import {
|
||||
SingleRecordAvailability,
|
||||
TRIGGER_STEP_ID,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { DEFAULT_ITERATOR_CURRENT_ITEM } from 'src/modules/workflow/workflow-builder/workflow-schema/constants/default-iterator-current-item.const';
|
||||
@@ -45,8 +42,6 @@ import {
|
||||
export class WorkflowSchemaWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
) {}
|
||||
|
||||
async computeStepOutputSchema({
|
||||
@@ -128,37 +123,14 @@ export class WorkflowSchemaWorkspaceService {
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const agentId = step.settings.input.agentId;
|
||||
|
||||
if (!isDefined(agentId) || agentId === '') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: { id: agentId, workspaceId },
|
||||
});
|
||||
|
||||
if (
|
||||
!isDefined(agent) ||
|
||||
agent.responseFormat?.type !== 'json' ||
|
||||
!isDefined(agent.responseFormat.schema)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(agent.responseFormat.schema.properties).map(
|
||||
([key, field]) => [
|
||||
key,
|
||||
{
|
||||
isLeaf: true,
|
||||
type: field.type,
|
||||
label: field.description || key,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
),
|
||||
) as OutputSchema;
|
||||
return {
|
||||
response: {
|
||||
label: 'Response',
|
||||
isLeaf: true,
|
||||
type: 'string',
|
||||
value: 'Response of the agent',
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.CODE: // StepOutput schema is computed on logicFunction draft execution
|
||||
default:
|
||||
@@ -175,10 +147,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
const BACKEND_ENRICHED_TYPES = [
|
||||
WorkflowActionType.AI_AGENT,
|
||||
WorkflowActionType.ITERATOR,
|
||||
];
|
||||
const BACKEND_ENRICHED_TYPES = [WorkflowActionType.ITERATOR];
|
||||
|
||||
if (!BACKEND_ENRICHED_TYPES.includes(step.type)) {
|
||||
return step;
|
||||
|
||||
Reference in New Issue
Block a user