Files
Michele 60b46cb5c1 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
2026-03-08 02:16:49 +01:00

8.1 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established duration completed
01-core-generation-pipeline 03 api
python
fastapi
anthropic
pydantic
csv
asyncio
retry
backoff
rate-limit
phase provides
01-core-generation-pipeline (plan 01) backend/config.py con OUTPUTS_PATH, CONFIG_PATH, PROMPTS_PATH
phase provides
01-core-generation-pipeline (plan 02) CalendarService, PromptService, CANVA_FIELDS, GeneratedPost schema, TopicResult schema
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
01-04 (frontend usa tutti questi endpoint API)
fase deploy (tutti gli endpoint da testare via HTTP)
added patterns
anthropic.Anthropic client (già in requirements.txt)
asyncio.create_task per background generazione
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
created modified
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
backend/main.py (aggiunto include_router x4, copia prompt default al primo avvio)
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
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)
8min 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