- Add context config endpoints (GET/PUT /api/ai/prompts/:type/:version/context-config) - Add execution profile endpoints (CRUD /api/ai/execution-profiles) - Add sandbox RAG Prep endpoint (POST /api/ai/admin/sandbox/rag-prep) - Create Prompt Management UI with multi-type support - Add ContextConfigEditor, PromptEditor, RuntimeParametersPanel components - Add SandboxTabs for 3-step workflow (OCR, Extract, RAG Prep) - Add database deltas for ai_execution_profiles and additional prompt types - Update quickstart.md with production backend URLs - Add comprehensive test coverage for new features
8.0 KiB
Data Model: Unified Prompt Management UX/UI
Feature: 237-unified-prompt-management-ux-ui
Date: 2026-06-14
Purpose: Define data entities, relationships, and validation rules
Entities
AiPrompt
Represents a prompt version with template, context config, and activation status.
Table: ai_prompts (extends existing ADR-029 schema)
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INT AUTO_INCREMENT | PRIMARY KEY | Internal ID (not exposed) |
public_id |
UUID | UNIQUE, NOT NULL | Public identifier (ADR-019) |
prompt_type |
VARCHAR(50) | NOT NULL, INDEX | Prompt type: ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt |
version_number |
INT | NOT NULL, INDEX | Version number (per prompt_type) |
template |
TEXT | NOT NULL | Prompt template with placeholders |
context_config |
JSON | NULL | Context configuration (filter, pageSize, language) |
is_active |
TINYINT(1) | NOT NULL, DEFAULT 0 | Active flag (1 = active) |
manual_note |
VARCHAR(500) | NULL | Manual annotation |
created_by |
INT | NOT NULL, FK → users.id | Creator user ID |
created_at |
TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE | Update timestamp |
Unique Constraint: (prompt_type, version_number) - ensures version numbers are unique per type
Validation Rules:
prompt_typemust be one of: ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompttemplatemust contain required placeholders based onprompt_type:- ocr_extraction: {{ocr_text}}, {{master_data_context}}
- rag_query_prompt: {{query}}, {{context}}
- rag_prep_prompt: {{text}}
- classification_prompt: {{document_text}}
context_configJSON structure:{ "filter": { "projectId": "uuid|null", "contractId": "uuid|null" }, "pageSize": 3, "language": "th", "outputLanguage": "th" }is_activecan only be true for one version perprompt_typeat a time
Relationships:
created_by→users.id(many-to-one)
AiExecutionProfile
Represents runtime parameters for AI model behavior (global per profile, not per prompt version).
Table: ai_execution_profiles (new table per ADR-036)
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INT AUTO_INCREMENT | PRIMARY KEY | Internal ID (not exposed) |
public_id |
UUID | UNIQUE, NOT NULL | Public identifier (ADR-019) |
profile_name |
VARCHAR(100) | NOT NULL, UNIQUE | Profile name (e.g., "default", "fast", "accurate") |
temperature |
DECIMAL(3,2) | NOT NULL, DEFAULT 0.7 | Temperature (0.0 - 1.0) |
top_p |
DECIMAL(3,2) | NOT NULL, DEFAULT 0.9 | Top-P (0.0 - 1.0) |
repeat_penalty |
DECIMAL(3,2) | NOT NULL, DEFAULT 1.0 | Repeat penalty (1.0 - 2.0) |
max_tokens |
INT | NOT NULL, DEFAULT 2048 | Max tokens (1 - 8192) |
ctx_size |
INT | NOT NULL, DEFAULT 4096 | Context size (1 - 16384) |
keep_alive |
INT | NOT NULL, DEFAULT 300 | Keep-alive seconds (0 - 3600) |
is_default |
TINYINT(1) | NOT NULL, DEFAULT 0 | Default profile flag |
created_by |
INT | NOT NULL, FK → users.id | Creator user ID |
created_at |
TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Creation timestamp |
updated_at |
TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE | Update timestamp |
Validation Rules:
temperaturemust be between 0.0 and 1.0top_pmust be between 0.0 and 1.0repeat_penaltymust be between 1.0 and 2.0max_tokensmust be between 1 and 8192ctx_sizemust be between 1 and 16384keep_alivemust be between 0 and 3600is_defaultcan only be true for one profile at a time
Relationships:
created_by→users.id(many-to-one)
SandboxJob
Represents a sandbox test execution (transient, stored in Redis, not in database).
Redis Key: sandbox:job:{jobId} (TTL: 1 hour)
Structure:
{
"jobId": "uuid",
"jobType": "ocr|ai-extract|rag-prep",
"status": "pending|processing|completed|failed",
"result": {
"ocrText": "string|null",
"extractedMetadata": "object|null",
"ragChunks": "array|null",
"ragVectors": "array|null",
"error": "string|null"
},
"createdAt": "timestamp",
"completedAt": "timestamp|null"
}
Validation Rules:
jobTypemust be one of: ocr, ai-extract, rag-prepstatustransitions: pending → processing → completed/failedresultstructure depends onjobType:- ocr: { ocrText, error }
- ai-extract: { extractedMetadata, error }
- rag-prep: { ragChunks, ragVectors, error }
Relationships: None (transient data)
Entity Relationships
users (1) ──────── (∞) ai_prompts
│
└── (1) ──────── (∞) ai_execution_profiles
ai_prompts (independent from ai_execution_profiles)
- Runtime parameters are global (ai_execution_profiles)
- Context config is per version (ai_prompts.context_config)
Indexes
ai_prompts
idx_prompt_type_version:(prompt_type, version_number)- UNIQUEidx_prompt_type_active:(prompt_type, is_active)- for finding active versionidx_public_id:(public_id)- UNIQUE
ai_execution_profiles
idx_profile_name:(profile_name)- UNIQUEidx_is_default:(is_default)- for finding default profileidx_public_id:(public_id)- UNIQUE
Database Schema Changes (ADR-009)
New Table: ai_execution_profiles
CREATE TABLE ai_execution_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
public_id UUID NOT NULL,
profile_name VARCHAR(100) NOT NULL UNIQUE,
temperature DECIMAL(3,2) NOT NULL DEFAULT 0.7,
top_p DECIMAL(3,2) NOT NULL DEFAULT 0.9,
repeat_penalty DECIMAL(3,2) NOT NULL DEFAULT 1.0,
max_tokens INT NOT NULL DEFAULT 2048,
ctx_size INT NOT NULL DEFAULT 4096,
keep_alive INT NOT NULL DEFAULT 300,
is_default TINYINT(1) NOT NULL DEFAULT 0,
created_by INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE,
UNIQUE KEY uk_public_id (public_id),
KEY idx_is_default (is_default),
CONSTRAINT fk_execution_profile_user FOREIGN KEY (created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Seed Data: ai_execution_profiles
INSERT INTO ai_execution_profiles (public_id, profile_name, temperature, top_p, repeat_penalty, max_tokens, ctx_size, keep_alive, is_default, created_by)
VALUES
(UUID(), 'default', 0.7, 0.9, 1.0, 2048, 4096, 300, 1, 1),
(UUID(), 'fast', 0.5, 0.8, 1.0, 1024, 2048, 0, 0, 1),
(UUID(), 'accurate', 0.8, 0.95, 1.1, 4096, 8192, 600, 0, 1);
Seed Data: ai_prompts (additional types)
-- RAG Query Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'rag_query_prompt',
1,
'Answer the following question based on the provided context:\n\nQuestion: {{query}}\n\nContext: {{context}}\n\nAnswer:',
'{"filter": null, "language": "th"}',
1,
1
);
-- RAG Prep Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'rag_prep_prompt',
1,
'Split the following text into semantic chunks for RAG indexing:\n\nText: {{text}}\n\nOutput JSON array of chunks with "text" and "summary" fields.',
'{"filter": null, "language": "th"}',
1,
1
);
-- Classification Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'classification_prompt',
1,
'Classify the following document into one of these categories: Correspondence, RFA, Transmittal, Circulation, Shop Drawing, Contract Drawing\n\nDocument: {{document_text}}\n\nOutput JSON with "category" and "confidence" fields.',
'{"filter": null, "language": "th"}',
1,
1
);