Files
twenty/packages/twenty-server/src/engine/metadata-modules/agent/agent.entity.ts
T
Félix Malfait a281f2a773 feat: add configurable response format for AI agents (text/JSON) (#15953)
## Summary
This PR adds configurable response format support for AI agents,
allowing them to return either plain text or structured JSON data based
on a defined schema.

## Key Features

### 1. Agent Response Format Configuration
- Added `AgentResponseFormat` type supporting:
  - `text`: Returns plain text responses (default)
  - `json`: Returns structured JSON based on defined schema
- New `AgentResponseSchema` type moved to `twenty-shared/ai` for sharing
between frontend/backend

### 2. Settings UI
- New `SettingsAgentResponseFormat` component for configuring response
format
- Visual schema builder for defining JSON output structure
- Real-time validation and preview
- Integrated into agent settings tab

### 3. Workflow Integration
- AI Agent workflow action automatically uses agent's configured
response format
- Output schema dynamically generated from agent's response format
- Workflow variable picker shows structured fields for JSON responses
- Backward compatible with existing text-only agents

### 4. Backend Implementation
- Added `convertAgentSchemaToZod` utility to validate JSON responses
- Agent executor service handles both text and JSON generation
- Automatic agent creation/cloning when adding AI agent steps to
workflows
- Unique agent naming with conflict resolution

### 5. Database Migration
- Migration `1763622159656-update-agent-response-format.ts` 
- Sets default `responseFormat` to `{"type":"text"}` for existing agents
- Updated all standard agents with proper response format

## Changes by Module

### Frontend (`twenty-front`)
- 🆕 `AgentResponseFormat` type
- 🆕 `SettingsAgentResponseFormat` component
- ✏️ Updated `WorkflowEditActionAiAgent` to support response format
configuration
- 🗑️ Removed deprecated `useAiAgentOutputSchema` hook and
`AiAgentOutputSchema` type

### Backend (`twenty-server`)
- 🆕 `AgentResponseFormat` type in agent entity
- 🆕 `convertAgentSchemaToZod` utility for schema validation
- ✏️ Updated `AiAgentExecutorService` to handle both text and JSON
generation
- ✏️ Updated `WorkflowSchemaWorkspaceService` to generate output schema
from agent config
- ✏️ Enhanced `WorkflowVersionStepOperationsWorkspaceService` with agent
creation/cloning
- 🆕 Agent naming constants for conflict resolution

### Shared (`twenty-shared`)
- 🆕 `AgentResponseSchema` type
- 🆕 `ModelConfiguration` type moved to shared package
- Updated exports in `ai/index.ts`

## Code Quality
- Removed useless comments following code style guidelines
- All linter checks passed
- Type-safe implementation with proper TypeScript types

## Testing
-  Database migration tested
-  Agent creation/cloning in workflows verified
-  Response format switching (text ↔ JSON) validated
-  Backward compatibility with existing agents confirmed

## Migration Notes
- Existing agents will have `responseFormat: {type: 'text'}` set
automatically
- No breaking changes - all existing functionality preserved
- Agents can be updated to use JSON format through settings UI
2025-11-20 18:32:44 +01:00

91 lines
2.5 KiB
TypeScript

import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentResponseFormat } from 'src/engine/metadata-modules/agent/types/agent-response-format.type';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
import { AgentHandoffEntity } from './agent-handoff.entity';
@Entity('agent')
@Index('IDX_AGENT_ID_DELETED_AT', ['id', 'deletedAt'])
@Index('IDX_AGENT_NAME_WORKSPACE_ID_UNIQUE', ['name', 'workspaceId'], {
unique: true,
where: '"deletedAt" IS NULL',
})
export class AgentEntity
extends SyncableEntity
implements Required<AgentEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
standardId: string | null;
@Column({ nullable: false })
name: string;
@Column({ nullable: false })
label: string;
@Column({ nullable: true })
icon: string;
@Column({ nullable: true })
description: string;
@Column({ nullable: false, type: 'text' })
prompt: string;
@Column({ nullable: false, type: 'varchar', default: 'auto' })
modelId: ModelId;
@Column({ nullable: true, type: 'jsonb', default: { type: 'text' } })
responseFormat: AgentResponseFormat;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@Column({ default: false })
isCustom: boolean;
@ManyToOne(() => WorkspaceEntity, (workspace) => workspace.agents, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'workspaceId' })
workspace: Relation<WorkspaceEntity>;
@OneToMany(() => AgentHandoffEntity, (handoff) => handoff.fromAgent)
outgoingHandoffs: Relation<AgentHandoffEntity[]>;
@OneToMany(() => AgentHandoffEntity, (handoff) => handoff.toAgent)
incomingHandoffs: Relation<AgentHandoffEntity[]>;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt: Date | null;
@Column({ nullable: true, type: 'jsonb' })
modelConfiguration: ModelConfiguration;
}