feat: Add Agent Evaluation System and Refactor AI Modules (#16111)

## Summary

This PR introduces a comprehensive agent evaluation system and refactors
the AI module structure for better organization.

## Key Changes

### 🎯 Agent Evaluation System
- Added **Agent Turn Evaluation** entities, DTOs, and database schema
- New GraphQL mutations: `evaluateAgentTurn` and `runEvaluationInput`
- Added `evaluationInputs` field to Agent entity for storing test inputs
- New `AgentTurnGraderService` for automatic turn evaluation
- Added evaluation UI with new **Evals** and **Logs** tabs in agent
detail pages

### 🏗️ Entity & Module Refactoring
- Renamed `AgentChatMessage` → `AgentMessage` for clarity
- Consolidated chat entities: `AgentMessage`, `AgentTurn`, and
`AgentChatThread`
- Reorganized AI modules under `ai/` subdirectory structure
- Updated imports across codebase to reflect new module paths

### 🤖 New Agents & Roles
- Added **Dashboard Builder Agent** for dashboard creation and
management
- Added **Dashboard Manager Role** with appropriate permissions
- Updated role permissions to be more granular (users vs agents vs API
keys)

### 🔐 Permission System Updates
- Added `HTTP_REQUEST_TOOL` permission flag
- Updated Workflow Manager role permissions (restricted tool access)
- Enhanced permission flag types to differentiate between user/agent/API
key contexts
- Added `isRelevantForAgents`, `isRelevantForApiKeys`,
`isRelevantForUsers` to permission flags

### 📨 Message Role Enhancement
- Added `system` role to `AgentMessageRole` enum (alongside
user/assistant)
- Updated message handling to support system prompts

### 🎨 UI/UX Improvements
- New tabs in agent detail: **Evals** and **Logs**
- Added turn detail page: `/ai/agents/:agentId/turns/:turnId`
- Fixed text overflow in `SettingsListItemCardContent`
- Updated role applicability labels ("Assignable to Workspace Members")

### 🛠️ Technical Improvements
- Fixed Zod schema validation for UUID and Date fields (use string
validators)
- Updated `ToolRegistryService` to properly register HTTP tool with
permission flag
- Enhanced error handling in agent execution services
- Updated database migrations for new entity schema

## Database Migrations
- `1764210000000-add-system-role-to-agent-message.ts`
- `1764220000000-add-evaluation-inputs-to-agent.ts`
- `1764200000000-add-agent-turn-evaluation.ts`
- `1764100000000-refactor-agent-chat-entities.ts`

## Testing
- [ ] Agent evaluation flow tested
- [ ] Dashboard Builder agent tested
- [ ] Permission system validated
- [ ] UI tabs and navigation tested
- [ ] Database migrations run successfully

## Breaking Changes
⚠️ **Entity Rename**: `AgentChatMessage` renamed to `AgentMessage` -
GraphQL queries need updating

## Related Issues
<!-- Link any related issues here -->

## Screenshots
<!-- Add screenshots if applicable -->
This commit is contained in:
Félix Malfait
2025-11-27 08:25:40 +01:00
committed by GitHub
parent 35f81805b8
commit 4f20fd35c5
158 changed files with 2954 additions and 556 deletions
@@ -1,6 +1,6 @@
import { type QueryRunner } from 'typeorm';
import { AgentChatMessageRole } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message.entity';
import { AgentMessageRole } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_YCOMBINATOR_WORKSPACE_ID,
@@ -8,8 +8,9 @@ import {
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
const agentChatThreadTableName = 'agentChatThread';
const agentChatMessageTableName = 'agentChatMessage';
const agentChatMessagePartTableName = 'agentChatMessagePart';
const agentTurnTableName = 'agentTurn';
const agentMessageTableName = 'agentMessage';
const agentMessagePartTableName = 'agentMessagePart';
export const AGENT_DATA_SEED_IDS = {
APPLE_DEFAULT_AGENT: '20202020-0000-4000-8000-000000000001',
@@ -109,10 +110,12 @@ const seedChatMessages = async ({
}: SeedChatMessagesArgs) => {
let messageIds: string[];
let partIds: string[];
let turnIds: string[];
let messages: Array<{
id: string;
threadId: string;
role: AgentChatMessageRole;
turnId: string;
role: AgentMessageRole;
createdAt: Date;
}>;
let messageParts: Array<{
@@ -140,29 +143,37 @@ const seedChatMessages = async ({
AGENT_CHAT_MESSAGE_PART_DATA_SEED_IDS.APPLE_MESSAGE_3_PART_1,
AGENT_CHAT_MESSAGE_PART_DATA_SEED_IDS.APPLE_MESSAGE_4_PART_1,
];
turnIds = [
'20202020-0000-4000-8000-000000000061',
'20202020-0000-4000-8000-000000000062',
];
messages = [
{
id: messageIds[0],
threadId,
role: AgentChatMessageRole.USER,
turnId: turnIds[0],
role: AgentMessageRole.USER,
createdAt: new Date(baseTime.getTime()),
},
{
id: messageIds[1],
threadId,
role: AgentChatMessageRole.ASSISTANT,
turnId: turnIds[0],
role: AgentMessageRole.ASSISTANT,
createdAt: new Date(baseTime.getTime() + 5 * 60 * 1000),
},
{
id: messageIds[2],
threadId,
role: AgentChatMessageRole.USER,
turnId: turnIds[1],
role: AgentMessageRole.USER,
createdAt: new Date(baseTime.getTime() + 10 * 60 * 1000),
},
{
id: messageIds[3],
threadId,
role: AgentChatMessageRole.ASSISTANT,
turnId: turnIds[1],
role: AgentMessageRole.ASSISTANT,
createdAt: new Date(baseTime.getTime() + 15 * 60 * 1000),
},
];
@@ -217,29 +228,37 @@ const seedChatMessages = async ({
AGENT_CHAT_MESSAGE_PART_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_3_PART_1,
AGENT_CHAT_MESSAGE_PART_DATA_SEED_IDS.YCOMBINATOR_MESSAGE_4_PART_1,
];
turnIds = [
'20202020-0000-4000-8000-000000000071',
'20202020-0000-4000-8000-000000000072',
];
messages = [
{
id: messageIds[0],
threadId,
role: AgentChatMessageRole.USER,
turnId: turnIds[0],
role: AgentMessageRole.USER,
createdAt: new Date(baseTime.getTime()),
},
{
id: messageIds[1],
threadId,
role: AgentChatMessageRole.ASSISTANT,
turnId: turnIds[0],
role: AgentMessageRole.ASSISTANT,
createdAt: new Date(baseTime.getTime() + 3 * 60 * 1000),
},
{
id: messageIds[2],
threadId,
role: AgentChatMessageRole.USER,
turnId: turnIds[1],
role: AgentMessageRole.USER,
createdAt: new Date(baseTime.getTime() + 8 * 60 * 1000),
},
{
id: messageIds[3],
threadId,
role: AgentChatMessageRole.ASSISTANT,
turnId: turnIds[1],
role: AgentMessageRole.ASSISTANT,
createdAt: new Date(baseTime.getTime() + 12 * 60 * 1000),
},
];
@@ -287,12 +306,32 @@ const seedChatMessages = async ({
);
}
// Create turns first
const turns = turnIds.map((id, index) => ({
id,
threadId,
createdAt: messages[index * 2].createdAt,
}));
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(`${schemaName}.${agentChatMessageTableName}`, [
.into(`${schemaName}.${agentTurnTableName}`, [
'id',
'threadId',
'createdAt',
])
.orIgnore()
.values(turns)
.execute();
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(`${schemaName}.${agentMessageTableName}`, [
'id',
'threadId',
'turnId',
'role',
'createdAt',
])
@@ -303,7 +342,7 @@ const seedChatMessages = async ({
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(`${schemaName}.${agentChatMessagePartTableName}`, [
.into(`${schemaName}.${agentMessagePartTableName}`, [
'id',
'messageId',
'orderIndex',