feat: unified prompt system migration schema (Issue #28 Phase 1)
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

- 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
This commit is contained in:
Lars 2026-03-25 10:43:10 +01:00
parent 1b7fdb1739
commit 2e0838ca08
2 changed files with 178 additions and 0 deletions

View File

@ -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

View File

@ -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