From 2e0838ca08bd205384aace7b821714808175d425 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 25 Mar 2026 10:43:10 +0100 Subject: [PATCH] feat: unified prompt system migration schema (Issue #28 Phase 1) - Migration 020: Add type, stages, output_format columns to ai_prompts - Migrate existing prompts to 1-stage pipeline format - Migrate pipeline_configs into ai_prompts as multi-stage pipelines - Add UnifiedPrompt Pydantic models for new API - Backup pipeline_configs table (keep during transition) Schema structure: - type: 'base' (reusable) or 'pipeline' (multi-stage) - stages: JSONB array [{stage:1, prompts:[{source, slug, template, output_key, output_format}]}] - output_format: 'text' or 'json' - output_schema: JSON validation schema (optional) Next: Backend executor + Frontend UI consolidation --- .../migrations/020_unified_prompt_system.sql | 124 ++++++++++++++++++ backend/models.py | 54 ++++++++ 2 files changed, 178 insertions(+) create mode 100644 backend/migrations/020_unified_prompt_system.sql diff --git a/backend/migrations/020_unified_prompt_system.sql b/backend/migrations/020_unified_prompt_system.sql new file mode 100644 index 0000000..1bf6f14 --- /dev/null +++ b/backend/migrations/020_unified_prompt_system.sql @@ -0,0 +1,124 @@ +-- Migration 020: Unified Prompt System (Issue #28) +-- Consolidate ai_prompts and pipeline_configs into single system +-- Type: 'base' (reusable building blocks) or 'pipeline' (workflows) + +-- Step 1: Add new columns to ai_prompts +ALTER TABLE ai_prompts +ADD COLUMN IF NOT EXISTS type VARCHAR(20) DEFAULT 'pipeline', +ADD COLUMN IF NOT EXISTS stages JSONB, +ADD COLUMN IF NOT EXISTS output_format VARCHAR(10) DEFAULT 'text', +ADD COLUMN IF NOT EXISTS output_schema JSONB; + +-- Step 2: Migrate existing single-prompts to 1-stage pipeline format +-- All existing prompts become single-stage pipelines with inline source +UPDATE ai_prompts +SET + type = 'pipeline', + stages = jsonb_build_array( + jsonb_build_object( + 'stage', 1, + 'prompts', jsonb_build_array( + jsonb_build_object( + 'source', 'inline', + 'template', template, + 'output_key', REPLACE(slug, 'pipeline_', ''), + 'output_format', 'text' + ) + ) + ) + ), + output_format = 'text' +WHERE stages IS NULL; + +-- Step 3: Migrate pipeline_configs into ai_prompts as multi-stage pipelines +-- Each pipeline_config becomes a pipeline-type prompt with multiple stages +INSERT INTO ai_prompts ( + slug, + name, + description, + type, + stages, + output_format, + active, + is_system_default, + category +) +SELECT + 'pipeline_config_' || LOWER(REPLACE(name, ' ', '_')) || '_' || SUBSTRING(id::TEXT FROM 1 FOR 8), + name, + description, + 'pipeline', + -- Build stages JSONB structure + ( + SELECT jsonb_agg( + jsonb_build_object( + 'stage', stage_num, + 'prompts', stage_prompts + ) + ORDER BY stage_num + ) + FROM ( + -- Stage 1: All parallel prompts + SELECT + 1 as stage_num, + jsonb_agg( + jsonb_build_object( + 'source', 'reference', + 'slug', stage1_slug, + 'output_key', REPLACE(stage1_slug, 'pipeline_', 'stage1_'), + 'output_format', 'json' + ) + ) as stage_prompts + FROM UNNEST(pc.stage1_prompts) as stage1_slug + + UNION ALL + + -- Stage 2: Synthesis prompt + SELECT + 2 as stage_num, + jsonb_build_array( + jsonb_build_object( + 'source', 'reference', + 'slug', pc.stage2_prompt, + 'output_key', 'synthesis', + 'output_format', 'text' + ) + ) as stage_prompts + WHERE pc.stage2_prompt IS NOT NULL + + UNION ALL + + -- Stage 3: Optional goals/analysis prompt + SELECT + 3 as stage_num, + jsonb_build_array( + jsonb_build_object( + 'source', 'reference', + 'slug', pc.stage3_prompt, + 'output_key', 'goals', + 'output_format', 'text' + ) + ) as stage_prompts + WHERE pc.stage3_prompt IS NOT NULL + ) stages_union + ) as stages, + 'text', + pc.active, + pc.is_default, + 'pipeline' +FROM pipeline_configs pc +WHERE pc.id IS NOT NULL; + +-- Step 4: Add indices for performance +CREATE INDEX IF NOT EXISTS idx_ai_prompts_type ON ai_prompts(type); +CREATE INDEX IF NOT EXISTS idx_ai_prompts_stages ON ai_prompts USING GIN (stages); + +-- Step 5: Add comment explaining stages structure +COMMENT ON COLUMN ai_prompts.stages IS 'JSONB array of stages, each with prompts array. Structure: [{"stage":1,"prompts":[{"source":"reference|inline","slug":"...","template":"...","output_key":"key","output_format":"text|json"}]}]'; + +-- Step 6: Backup pipeline_configs before eventual deletion +CREATE TABLE IF NOT EXISTS pipeline_configs_backup_pre_020 AS +SELECT * FROM pipeline_configs; + +-- Note: We keep pipeline_configs table for now during transition period +-- It can be dropped in a later migration once all code is migrated diff --git a/backend/models.py b/backend/models.py index 7566e50..4c0c39b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -158,7 +158,61 @@ class PromptGenerateRequest(BaseModel): example_output: Optional[str] = None +# ── Unified Prompt System Models (Issue #28 Phase 2) ─────────────────────── + +class StagePromptCreate(BaseModel): + """Single prompt within a stage""" + source: str # 'inline' or 'reference' + slug: Optional[str] = None # Required if source='reference' + template: Optional[str] = None # Required if source='inline' + output_key: str # Key for storing result (e.g., 'nutrition', 'stage1_body') + output_format: str = 'text' # 'text' or 'json' + output_schema: Optional[dict] = None # JSON schema if output_format='json' + + +class StageCreate(BaseModel): + """Single stage with multiple prompts""" + stage: int # Stage number (1, 2, 3, ...) + prompts: list[StagePromptCreate] + + +class UnifiedPromptCreate(BaseModel): + """Create a new unified prompt (base or pipeline type)""" + name: str + slug: str + display_name: Optional[str] = None + description: Optional[str] = None + type: str # 'base' or 'pipeline' + category: str = 'ganzheitlich' + active: bool = True + sort_order: int = 0 + + # For base prompts (single reusable template) + template: Optional[str] = None # Required if type='base' + output_format: str = 'text' + output_schema: Optional[dict] = None + + # For pipeline prompts (multi-stage workflow) + stages: Optional[list[StageCreate]] = None # Required if type='pipeline' + + +class UnifiedPromptUpdate(BaseModel): + """Update an existing unified prompt""" + name: Optional[str] = None + display_name: Optional[str] = None + description: Optional[str] = None + type: Optional[str] = None + category: Optional[str] = None + active: Optional[bool] = None + sort_order: Optional[int] = None + template: Optional[str] = None + output_format: Optional[str] = None + output_schema: Optional[dict] = None + stages: Optional[list[StageCreate]] = None + + # ── Pipeline Config Models (Issue #28) ───────────────────────────────────── +# NOTE: These will be deprecated in favor of UnifiedPrompt models above class PipelineConfigCreate(BaseModel): name: str