docs(01-03): complete pipeline LLM + API routers plan
Tasks completed: 2/2 - Task 1: LLMService, CSVBuilder, GenerationPipeline - Task 2: API routers (calendar, generate, export, settings) e wiring main.py SUMMARY: .planning/phases/01-core-generation-pipeline/01-03-SUMMARY.md
This commit is contained in:
@@ -93,7 +93,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
|
|||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Core Generation Pipeline | 0/4 | Planned (3 waves) | - |
|
| 1. Core Generation Pipeline | 3/4 | In progress | - |
|
||||||
| 2. Prompt Control + Output Review | 0/2 | Not started | - |
|
| 2. Prompt Control + Output Review | 0/2 | Not started | - |
|
||||||
| 3. Organization Layer | 0/2 | Not started | - |
|
| 3. Organization Layer | 0/2 | Not started | - |
|
||||||
| 4. Enrichment | 0/1 | Not started | - |
|
| 4. Enrichment | 0/1 | Not started | - |
|
||||||
|
|||||||
@@ -10,28 +10,28 @@ See: .planning/PROJECT.md (updated 2026-03-07)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 1 of 4 (Core Generation Pipeline)
|
Phase: 1 of 4 (Core Generation Pipeline)
|
||||||
Plan: 2 of 4 in current phase
|
Plan: 3 of 4 in current phase
|
||||||
Status: In progress — Plan 02 completato (parallelo con Plan 01)
|
Status: In progress — Plan 03 completato
|
||||||
Last activity: 2026-03-08 — Completato 01-02-PLAN.md (servizi dominio + prompt)
|
Last activity: 2026-03-08 — Completato 01-03-PLAN.md (pipeline LLM + API routers)
|
||||||
|
|
||||||
Progress: [██░░░░░░░░] 12% (2/16 piani totali stimati)
|
Progress: [███░░░░░░░] 18% (3/16 piani totali stimati)
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
- Total plans completed: 2
|
- Total plans completed: 3
|
||||||
- Average duration: ~7 min
|
- Average duration: ~7 min
|
||||||
- Total execution time: 15 min
|
- Total execution time: 23 min
|
||||||
|
|
||||||
**By Phase:**
|
**By Phase:**
|
||||||
|
|
||||||
| Phase | Plans | Total | Avg/Plan |
|
| Phase | Plans | Total | Avg/Plan |
|
||||||
|-------|-------|-------|----------|
|
|-------|-------|-------|----------|
|
||||||
| 01-core-generation-pipeline | 2/4 | 15 min | 7 min |
|
| 01-core-generation-pipeline | 3/4 | 23 min | 7 min |
|
||||||
|
|
||||||
**Recent Trend:**
|
**Recent Trend:**
|
||||||
- Last 5 plans: 6 min, 9 min
|
- Last 3 plans: 6 min, 9 min, 8 min
|
||||||
- Trend: baseline stabilita
|
- Trend: baseline stabile ~7-8 min/piano
|
||||||
|
|
||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
|
|
||||||
@@ -42,33 +42,26 @@ Progress: [██░░░░░░░░] 12% (2/16 piani totali stimati)
|
|||||||
Decisions are logged in PROJECT.md Key Decisions table.
|
Decisions are logged in PROJECT.md Key Decisions table.
|
||||||
Recent decisions affecting current work:
|
Recent decisions affecting current work:
|
||||||
|
|
||||||
- [Setup]: Tutti i 9 critical pitfalls identificati dalla research sono concentrati in Phase 1 — affrontarli subito e' la priorita' assoluta
|
- [Setup]: FastAPI root_path SOLO via Uvicorn (--root-path), mai nel costruttore FastAPI()
|
||||||
- [Setup]: FastAPI root_path SOLO via Uvicorn (--root-path), mai nel costruttore FastAPI() — altrimenti doppio path bug
|
- [Setup]: CSV encoding = utf-8-sig (BOM) sempre; CANVA_FIELDS locked come costante
|
||||||
- [Setup]: CSV encoding = utf-8-sig (BOM) sempre; CANVA_FIELDS locked come costante prima di qualsiasi codice di generazione
|
- [Setup]: Prompt di sistema scritti IN italiano
|
||||||
- [Setup]: Prompt di sistema scritti IN italiano (non inglese + "scrivi in italiano")
|
- [Setup]: Per-item error isolation dal primo loop di generazione
|
||||||
- [Setup]: Per-item error isolation dal primo loop di generazione — un fallimento non blocca il batch
|
- [01-01]: root_path SOLO via Uvicorn --root-path nel Dockerfile CMD
|
||||||
- [01-01]: root_path SOLO via Uvicorn --root-path nel Dockerfile CMD, VERIFICATO e funzionante
|
- [01-01]: API_BASE='/postgenerator/api' nel frontend — Pitfall #9 risolto
|
||||||
- [01-01]: API_BASE='/postgenerator/api' nel frontend — Pitfall #9 risolto nella configurazione base
|
|
||||||
- [01-01]: SPAStaticFiles montato come ultima operazione in main.py — pattern stabilito
|
|
||||||
- [01-01]: fastapi[standard]==0.135.1, anthropic==0.84.0 pinned in requirements.txt
|
|
||||||
- [01-01]: docker-compose NO porte esposte, named volume postgenerator-data per persistenza
|
|
||||||
- [01-02]: CANVA_FIELDS 33 colonne con _image_keyword (non URL) — URL Unsplash in Phase 4
|
- [01-02]: CANVA_FIELDS 33 colonne con _image_keyword (non URL) — URL Unsplash in Phase 4
|
||||||
- [01-02]: Distribuzione L3 split: valore-L3 in Cattura, riprova_sociale-L3 in Coinvolgi
|
- [01-02]: PromptService usa ValueError per variabili mancanti
|
||||||
- [01-02]: PromptService usa ValueError per variabili mancanti — fail esplicito non silenzioso
|
- [01-03]: LLMService._parse_retry_after() legge header HTTP per wait esatto (fallback 60s)
|
||||||
- [01-02]: Nicchie 50/50: slot pari=generico, slot dispari=verticale in rotazione
|
- [01-03]: asyncio.to_thread wrappa LLM sync calls nel background task async
|
||||||
|
- [01-03]: Settings.api_key merge: PUT non sovrascrive con None se non inclusa nel body
|
||||||
### Pending Todos
|
- [01-03]: Pipeline singleton in generate.py per mantenere _jobs tra request, con fallback da disco
|
||||||
|
|
||||||
None.
|
|
||||||
|
|
||||||
### Blockers/Concerns
|
### Blockers/Concerns
|
||||||
|
|
||||||
- [Phase 1 RISOLTO in 01-02]: CANVA_FIELDS locked con 33 colonne e assert a load-time
|
- [Phase 1]: Validare token usage reale per batch 13 post con claude-sonnet-4-5 — inter_request_delay=2.0s configurato, da validare in produzione
|
||||||
- [Phase 1]: Validare token usage reale per batch 13 post con claude-sonnet-4-5 contro limite Tier 1 (8,000 OTPM) — necessario inter-request delay configurabile (2-3s) (da affrontare in 01-03)
|
- [Phase 1]: Baseline qualita' prompt italiani da validare dopo prima generazione reale
|
||||||
- [Phase 1]: Baseline qualita' prompt italiani da validare dopo prima generazione reale (post 01-03)
|
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-08T01:00:34Z
|
Last session: 2026-03-08T01:15:06Z
|
||||||
Stopped at: Completato 01-02-PLAN.md — Servizi dominio + prompt .txt italiani
|
Stopped at: Completato 01-03-PLAN.md — Pipeline LLM + CSVBuilder + 4 router API
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
141
.planning/phases/01-core-generation-pipeline/01-03-SUMMARY.md
Normal file
141
.planning/phases/01-core-generation-pipeline/01-03-SUMMARY.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
phase: 01-core-generation-pipeline
|
||||||
|
plan: 03
|
||||||
|
subsystem: api
|
||||||
|
tags: [python, fastapi, anthropic, pydantic, csv, asyncio, retry, backoff, rate-limit]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 01-core-generation-pipeline (plan 01)
|
||||||
|
provides: backend/config.py con OUTPUTS_PATH, CONFIG_PATH, PROMPTS_PATH
|
||||||
|
- phase: 01-core-generation-pipeline (plan 02)
|
||||||
|
provides: CalendarService, PromptService, CANVA_FIELDS, GeneratedPost schema, TopicResult schema
|
||||||
|
provides:
|
||||||
|
- LLMService con retry 3x, RateLimitError con lettura retry-after header, backoff esponenziale 5xx, validazione Pydantic con correzione automatica
|
||||||
|
- CSVBuilder con encoding utf-8-sig, header CANVA_FIELDS locked, mapping GeneratedPost+CalendarSlot -> 33 colonne
|
||||||
|
- GenerationPipeline con background task asyncio, _jobs dict real-time, per-item error isolation, persistenza JSON
|
||||||
|
- API router calendar: POST /api/calendar/generate, GET /api/calendar/formats
|
||||||
|
- API router generate: POST /api/generate/bulk (202+job_id), GET /job/{id}/status (polling), GET /job/{id}, POST /single
|
||||||
|
- API router export: GET /api/export/{id}/csv (originale), POST /api/export/{id}/csv (modifiche inline)
|
||||||
|
- API router settings: GET /api/settings/status, GET /api/settings, PUT /api/settings
|
||||||
|
affects:
|
||||||
|
- 01-04 (frontend usa tutti questi endpoint API)
|
||||||
|
- fase deploy (tutti gli endpoint da testare via HTTP)
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added:
|
||||||
|
- anthropic.Anthropic client (già in requirements.txt)
|
||||||
|
- asyncio.create_task per background generazione
|
||||||
|
patterns:
|
||||||
|
- "Retry pattern: RateLimitError con retry-after header esatto, 5xx con backoff esponenziale + jitter"
|
||||||
|
- "Per-item isolation: try/except individuale per slot dentro il loop, non attorno al loop"
|
||||||
|
- "Background task pattern: generate_bulk_async ritorna job_id, _run_generation gira in background"
|
||||||
|
- "Job polling pattern: _jobs dict in-memory + file JSON su disco per resume post-restart"
|
||||||
|
- "Settings masking: api_key mai inviata al frontend, solo ultimi 4 caratteri mostrati"
|
||||||
|
- "CSV BOM: encoding utf-8-sig garantisce compatibilità Excel con caratteri italiani"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- backend/services/llm_service.py
|
||||||
|
- backend/services/csv_builder.py
|
||||||
|
- backend/services/generation_pipeline.py
|
||||||
|
- backend/routers/calendar.py
|
||||||
|
- backend/routers/generate.py
|
||||||
|
- backend/routers/export.py
|
||||||
|
- backend/routers/settings.py
|
||||||
|
- backend/schemas/settings.py
|
||||||
|
modified:
|
||||||
|
- backend/main.py (aggiunto include_router x4, copia prompt default al primo avvio)
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "LLMService._parse_retry_after() legge l'header 'retry-after' dalla response HTTP per wait esatto (non hardcoded)"
|
||||||
|
- "GenerationPipeline usa asyncio.to_thread per wrap dei metodi LLM sincroni (time.sleep non blocca event loop)"
|
||||||
|
- "Settings.api_key non sovrascritta con None se non inviata nel PUT body (merge con esistente)"
|
||||||
|
- "POST /export/{job_id}/csv usa build_csv_content() che produce stringa, GET usa FileResponse da disco"
|
||||||
|
- "Pipeline singleton in-memory in generate.py per mantenere _jobs tra request, con fallback da disco"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Thin routers: nessuna logica di business nei router, solo validazione + chiamata service + return"
|
||||||
|
- "API key loading: legge da settings.json con fallback a env var ANTHROPIC_API_KEY"
|
||||||
|
- "Job lifecycle: running -> completed (con CSV su disco) | running -> failed (con error)"
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 8min
|
||||||
|
completed: 2026-03-08
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 1 Plan 03: Pipeline LLM, CSVBuilder e API routers completi
|
||||||
|
|
||||||
|
**LLMService con retry specifico per 429/5xx/ValidationError, CSVBuilder utf-8-sig con CANVA_FIELDS locked, GenerationPipeline async con per-item isolation, e 4 router FastAPI (calendar, generate, export, settings) cablati in main.py**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 8 min
|
||||||
|
- **Started:** 2026-03-08T01:06:19Z
|
||||||
|
- **Completed:** 2026-03-08T01:15:06Z
|
||||||
|
- **Tasks:** 2/2
|
||||||
|
- **Files modified:** 9
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- LLMService: retry loop 3x con gestione specifica RateLimitError (legge retry-after header), backoff esponenziale+jitter per 5xx, ValidationError riprova con istruzione correttiva, inter_request_delay post-call, logging strutturato tokens/elapsed
|
||||||
|
- CSVBuilder: encoding utf-8-sig (BOM), header CANVA_FIELDS locked, mappa GeneratedPost+CalendarSlot -> 33 colonne, build_csv() scrive su disco, build_csv_content() ritorna stringa per export inline
|
||||||
|
- GenerationPipeline: generate_bulk_async ritorna job_id subito (asyncio.create_task), _run_generation in background con try/except PER SLOT (non attorno al loop), _jobs dict real-time, persistenza JSON su disco per resume
|
||||||
|
- 4 router API completi e thin (nessuna logica di business inline): calendar, generate, export, settings
|
||||||
|
- main.py aggiornato: include_router x4 prima di SPAStaticFiles, copia prompt default al primo avvio
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Ogni task committato atomicamente:
|
||||||
|
|
||||||
|
1. **Task 1: LLMService, CSVBuilder, GenerationPipeline** - `083621a` (feat)
|
||||||
|
2. **Task 2: API routers e wiring main.py** - `e06edde` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** (questo commit) (docs)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `backend/services/llm_service.py` — LLMService: retry, backoff RateLimitError, validation Pydantic, generate_topic con TopicResult
|
||||||
|
- `backend/services/csv_builder.py` — CSVBuilder: utf-8-sig, CANVA_FIELDS, mapping 33 colonne
|
||||||
|
- `backend/services/generation_pipeline.py` — GenerationPipeline: background task, _jobs dict, per-item isolation, persistenza JSON
|
||||||
|
- `backend/routers/calendar.py` — POST /api/calendar/generate, GET /api/calendar/formats
|
||||||
|
- `backend/routers/generate.py` — POST /api/generate/bulk (202), GET /job/{id}/status, GET /job/{id}, POST /single
|
||||||
|
- `backend/routers/export.py` — GET /api/export/{id}/csv, POST /api/export/{id}/csv (con modifiche inline)
|
||||||
|
- `backend/routers/settings.py` — GET /status, GET /, PUT / (api_key mascherata)
|
||||||
|
- `backend/schemas/settings.py` — Settings pydantic model
|
||||||
|
- `backend/main.py` — aggiunto include_router x4, copia prompt default al primo avvio
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **LLMService._parse_retry_after()** legge header 'retry-after' dalla response HTTP per wait esatto invece di hardcodare 60s. Se l'header non è disponibile, fallback a 60s.
|
||||||
|
- **asyncio.to_thread** wrappa le chiamate LLM sincrone (che usano time.sleep per inter_request_delay) per non bloccare l'event loop FastAPI durante la generazione background.
|
||||||
|
- **Settings merge**: PUT /api/settings non sovrascrive api_key con None se il body non la include — evita perdita accidentale della chiave quando si aggiornano altri parametri.
|
||||||
|
- **Pipeline singleton**: la GenerationPipeline è un singleton in-memory nel router generate.py per mantenere il _jobs dict tra request HTTP diverse. I job vengono anche salvati su disco per resume dopo restart.
|
||||||
|
- **POST export con stringa**: POST /api/export/{id}/csv usa build_csv_content() che produce una stringa con BOM manuale (\ufeff), poi risponde con Response(content=..., encoding="utf-8") per non raddoppiare il BOM.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
Nessuna — piano eseguito esattamente come scritto.
|
||||||
|
|
||||||
|
Unica nota tecnica: asyncio.to_thread è stato usato per wrappare le chiamate LLM (sync con time.sleep) nel background task async — questo è un dettaglio implementativo necessario per non bloccare l'event loop, non una deviazione dal piano.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
Nessun problema durante l'implementazione. Tutti i moduli importano senza errori e tutte le 10 verifiche del piano passano.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
Nessuno per questo piano — la pipeline è pronta ma richiede ANTHROPIC_API_KEY per funzionare. La key può essere configurata via PUT /api/settings o via variabile d'ambiente ANTHROPIC_API_KEY.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- LLMService, CSVBuilder, GenerationPipeline pronti per Plan 04 (frontend)
|
||||||
|
- Tutti gli endpoint API documentati e verificati — il frontend può iniziare a integrarli
|
||||||
|
- Pitfall risolti: #1 (soft failures via per-item isolation), #3 (CSV encoding utf-8-sig), #5 (all-or-nothing via per-item try/except), #6 (rate limit via retry-after header)
|
||||||
|
- Job polling pattern definito: POST /bulk -> job_id -> GET /job/{id}/status ogni 2s finché completed/failed
|
||||||
|
- Nessun blocco: Plan 04 (frontend UI) può proseguire con questi endpoint
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 01-core-generation-pipeline*
|
||||||
|
*Completed: 2026-03-08*
|
||||||
Reference in New Issue
Block a user