No description
Find a file
DJP efda962637 Add OVERVIEW.md — stakeholder-facing project summary
Companion to the developer README. Includes:
- Executive summary at the top with the 3 business-relevant points
  (reusable across regions, in-browser review, runtime extensibility)
- What it is / what it does / how it works (full architecture diagram)
- The 6 built-in deliverable types and why the data-driven design
  means new types need no code change
- Build cost (~$40-$80 in AI spend vs. agency equivalent ~$32-$100k)
- Operating cost per brief ($1-$3) and at typical monthly volumes
- Cost-control levers already baked in (prompt caching, telemetry)
- Roadmap of sensible next steps
- Quick-reference operating info (URLs, deploy command)
2026-05-12 12:37:36 -04:00
backend Add reset_password CLI for admin password recovery 2026-05-06 09:17:41 -04:00
deploy deploy.sh: patch sites-enabled too (it's diverged, not a symlink) 2026-04-22 17:09:05 -04:00
frontend api: 401 redirect now respects VITE_BASE_PATH 2026-05-06 09:35:04 -04:00
.env.example Initial import — HP Studios AI Content Agent 2026-04-17 17:11:25 -04:00
.gitignore Initial import — HP Studios AI Content Agent 2026-04-17 17:11:25 -04:00
docker-compose.prod.yml Prod deploy binds to 127.0.0.1 only + drops postgres/redis host publish 2026-04-22 15:49:29 -04:00
docker-compose.yml Add deploy script, prod compose override, and Apache subpath proxy 2026-04-22 15:23:51 -04:00
OVERVIEW.md Add OVERVIEW.md — stakeholder-facing project summary 2026-05-12 12:37:36 -04:00
README.md Initial import — HP Studios AI Content Agent 2026-04-17 17:11:25 -04:00
SPEC-DELIVERABLE-TYPES.md Initial import — HP Studios AI Content Agent 2026-04-17 17:11:25 -04:00
SPEC.md Initial import — HP Studios AI Content Agent 2026-04-17 17:11:25 -04:00

HP Studios AI Content Agent

A content-generation tool that turns HP customer briefs (one master asset + regional supporting docs) into a full set of branded, region-specific Word deliverables — leadership themes, LinkedIn posts, webinar specs, infographic specs, ABM enablement packs, and regional enrichments — in one click.

Replaces a previously semi-manual process where Claude produced markdown and someone copied it into bespoke Python scripts per run. Now: upload → select deliverables → review in-browser → export branded .docx. Admins can add entirely new deliverable types at runtime (prompt + JSON schema + Word template, no code).


Architecture

 ┌─────────────────────────────────────────────────────────────────────────┐
 │                             Browser (user)                              │
 │                   http://localhost:5178  (React + Vite)                 │
 │   /briefs  /briefs/new  /briefs/:id  /briefs/:id/deliverables/:type     │
 │   /admin/users  /admin/deliverables  /help                              │
 └────────────┬───────────────────────────────────────────────┬────────────┘
              │ REST (JWT cookie + bearer)                    │ blob download
              ▼                                               ▼
 ┌────────────────────────────────────────┐       ┌────────────────────────┐
 │  api  (FastAPI, uvicorn)  :8008        │       │  GET /exports/:id/     │
 │  ─────────────────────────────────────  │       │        download        │
 │  /auth     login / me / logout         │       │  FileResponse(.docx)   │
 │  /users    admin CRUD                  │       └─────────────▲──────────┘
 │  /briefs   CRUD + duplicate + PATCH    │                     │
 │  /briefs/:id/documents  upload+kind    │                     │
 │  /briefs/:id/generations  enqueue      │                     │
 │  /generations/:id       PATCH / rerun  │                     │
 │  /generations/:id/export  ────────────►│ hp_branding.render ─┘
 │  /deliverable-types (admin CRUD)       │
 └───┬────────────────────────────────┬───┘
     │ SQLAlchemy (sync)              │ RQ enqueue
     ▼                                ▼
 ┌────────────────────────────┐   ┌──────────────────────────────┐
 │  PostgreSQL 16 + pgvector  │   │  redis:7-alpine   :56379     │
 │  :55432                    │   │  queue: "default"            │
 │  users                     │   └───────────────┬──────────────┘
 │  briefs                    │                   │
 │  documents                 │                   │ blpop
 │  doc_chunks (vector(1024)) │                   ▼
 │  generations (jsonb)       │   ┌──────────────────────────────┐
 │  exports                   │   │  worker  (RQ + same image)   │
 │  deliverable_types         │   │  ─────────────────────────── │
 │    (prompt + schema +      │   │  ingest_document_task:       │
 │     template_json)         │   │    ↓ extract (pypdf / docx / │
 └────────────────────────────┘   │      Claude-Haiku vision OCR)│
                                  │    ↓ detect language         │
                                  │    ↓ translate (Haiku 4.5)   │
                                  │    ↓ chunk (2000-char +      │
                                  │      overlap)                │
                                  │    ↓ embed (Voyage or        │
                                  │      OpenAI 3-small)         │
                                  │    ↓ upsert doc_chunks       │
                                  │                              │
                                  │  generate_deliverable_task:  │
                                  │    ↓ load brief + type row   │
                                  │    ↓ master doc text + query │
                                  │      supporting chunks       │
                                  │      via pgvector cosine     │
                                  │    ↓ assemble system prompt  │
                                  │      (cacheable: system.md   │
                                  │      + per-deliverable       │
                                  │      prompt + master text)   │
                                  │    ↓ Anthropic Messages API  │
                                  │      Claude Opus 4.7         │
                                  │      tools=[submit_<slug>]   │
                                  │      tool_choice=forced      │
                                  │    ↓ validate against        │
                                  │      schema_json (jsonschema │
                                  │      + pydantic for built-ins│
                                  │    ↓ save structured_content │
                                  │      (jsonb)                 │
                                  └──────────────────────────────┘

         ┌─────────────────────────────────────────────────────┐
         │   hp_branding  (python-docx, in-process)            │
         │   ────────────────────────────────────────────────  │
         │   palette.py       HP #0096D6, #005A8C, greys       │
         │   typography.py    Montserrat, 10pt body            │
         │   primitives.py    styled_table, scenario_box,      │
         │                    post_block, bullet, heading…     │
         │   template_renderer.py   walks template_json →      │
         │                          dispatches ~14 block types │
         │                          ({{path}} + $item in loops)│
         │   render.py        dispatch: template_json if set,  │
         │                    else legacy _RENDERERS[slug]     │
         └─────────────────────────────────────────────────────┘

Five containers (docker compose up -d):

Service Image Host port Role
frontend Node 20 + Vite dev 5178 React SPA served with HMR
api Python 3.12 + uvicorn 8008 REST API, auth, admin endpoints
worker Python 3.12 + RQ Background ingestion + generation jobs
postgres pgvector/pgvector:pg16 55432 DB + vector store (vector(1024) column, HNSW-ready)
redis redis:7-alpine 56379 Job queue

Quick start

Prereqs: Docker Desktop, an Anthropic API key, and one of Voyage or OpenAI for embeddings.

git clone git@bitbucket.org:zlalani/hp-studios-ai-content-agent.git
cd hp-studios-ai-content-agent
cp .env.example .env
# Edit .env — fill ANTHROPIC_API_KEY and VOYAGE_API_KEY or OPENAI_API_KEY
# Generate a JWT secret: openssl rand -hex 32

docker compose up -d --build
docker compose exec api alembic upgrade head
docker compose exec api python -m app.cli.seed   # seeds admin user

Seed output prints the admin credentials once. Visit http://localhost:5178, log in, change the password.

Host ports (remapped to avoid common collisions)

Purpose Host port
Frontend 5178
API 8008
Postgres 55432
Redis 56379

Override any of them via .env (FRONTEND_HOST_PORT, API_HOST_PORT, POSTGRES_HOST_PORT, REDIS_HOST_PORT). Container-side ports stay default.


How a run works

  1. Create a brief — name, region, audience, brief text (/briefs/new).
  2. Upload documents — drag-drop one master doc + N supporting docs. Each upload fires ingest_document_task: text extraction (PDF / DOCX / image-vision), language detection, translation to English (if non-English, via Claude Haiku), chunking, embedding (Voyage voyage-3 preferred, OpenAI text-embedding-3-small fallback truncated + renormalised to 1024 dims), upsert to doc_chunks.
  3. Select deliverables — multi-select any active type (built-in or admin-authored). Click Generate → one generate_deliverable_task is enqueued per type.
  4. Worker — loads master text, pulls top-K relevant chunks from supporting docs via pgvector cosine, assembles system prompt (system.md + per-deliverable prompt + master text, all marked cacheable), calls Claude Opus 4.7 with tool_choice={type:"tool", name:"submit_<slug>"} whose input_schema is the type's schema_json. Tool-use output is validated against both JSON Schema and (for built-ins) the pydantic model before being persisted.
  5. Review & edit — UI renders a recursive <SchemaForm> driven by the type's schema. Every field editable; save via PATCH /generations/:id which re-validates.
  6. Exportrender_to_bytes(slug, content, template_json) dispatches to the generic template renderer. Template is a JSON array of blocks (title_page, heading1..3, bullets, styled_table, loop, scenario_box, post_block, conditional, key_value_table, divider, page_break, etc.) with {{path.to.field}} and {{$item.x}} interpolation. Returns branded .docx bytes, saved to data/exports/<uuid>.docx, streamed via GET /exports/:id/download.
  7. Re-roll / retryPOST /generations/:id/rerun resets status and re-enqueues. Keeps prior structured_content until the new one lands, so a failed rerun never loses the last good draft.

Data-driven deliverable types

Every type — built-in or admin-authored — lives in the deliverable_types table:

Column Purpose
slug stable identifier (e.g. leadership_themes)
prompt_md per-deliverable system-prompt suffix appended to system.md
schema_json JSON Schema — drives Claude's tool input_schema AND the React review form
template_json ordered list of layout blocks for the generic Word renderer
query_hint short string seeding the vector retrieval query
is_builtin true for the six seeded types — locks slug + schema_json in the admin UI

Admins can add new types at /admin/deliverables/new with a "Start from built-in" dropdown that pre-fills prompt + schema + template from any existing type. A test-render button on the edit page produces a .docx preview from sample JSON.


Authentication & roles

  • MVP: email + password, bcrypt hashes, JWT in an httpOnly cookie plus bearer token.
  • Planned: Oliver MSFT Entra SSO (OIDC). The backend already exposes a pluggable AuthProvider ABC — swapping providers is one adapter change, not a rewrite.
  • Two roles seeded:
    • admin — manages users, creates / edits deliverable types, sees all briefs, sees cost telemetry.
    • user — creates and edits their own briefs, generates and exports deliverables.
  • Users only see their own briefs; admins see all.

Repo layout

app/
├── docker-compose.yml
├── .env.example                     # secrets template — real .env is gitignored
├── README.md                        # you are here
├── SPEC.md                          # original build contract for the parallel agents
├── SPEC-DELIVERABLE-TYPES.md        # data-driven types architecture
├── backend/
│   ├── Dockerfile
│   ├── pyproject.toml
│   ├── alembic/                     # migrations (001 schema, 002 deliverable_types)
│   ├── db-init/                     # pgvector + pgcrypto extension SQL
│   └── app/
│       ├── main.py
│       ├── core/                    # config, security, auth provider, deps
│       ├── api/                     # auth, users, briefs, documents, generations,
│       │                            #   exports, deliverable_types
│       ├── db/                      # SQLAlchemy models + session
│       ├── schemas/                 # pydantic models for the six built-in types
│       ├── agents/                  # Claude client, tool schemas, retrieval, prompts
│       │   └── prompts/             # system.md + per_deliverable/*.md
│       ├── hp_branding/             # palette, typography, primitives,
│       │                            #   renderers/, template_renderer.py,
│       │                            #   builtin_templates.py, render.py
│       ├── ingestion/               # extractors, translator, chunker, embeddings
│       ├── workers/                 # RQ tasks + worker entrypoint
│       └── cli/                     # seed admin
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   ├── vite.config.ts
│   └── src/
│       ├── main.tsx
│       ├── App.tsx
│       ├── pages/                   # Login, Briefs, NewBrief, BriefDetail,
│       │                            #   DeliverableEditor, UsersAdmin,
│       │                            #   Deliverables*, HelpPage
│       ├── components/              # ui/, SchemaForm, ErrorBoundary,
│       │                            #   DocumentUploadZone, JsonEditor, Layout
│       ├── editors/                 # legacy per-type editors (still in tree)
│       ├── api/                     # React Query hooks per resource
│       └── lib/                     # types, auth, api client, utils
└── data/                            # gitignored — uploads + exports

Stack

Backend: Python 3.12, FastAPI, SQLAlchemy 2 (sync), Alembic, psycopg3, pgvector, pydantic v2, pydantic-settings, RQ + Redis, Anthropic Python SDK, Voyage AI / OpenAI clients, pypdf + pytesseract + pdf2image (OCR fallback), python-docx, langdetect, jsonschema, bcrypt, python-jose.

Frontend: React 18 + Vite + TypeScript (strict), Tailwind CSS, hand-built shadcn-style components, React Router v6, TanStack Query, React Hook Form + Zod, axios, lucide-react.

Infra: Docker Compose, Postgres 16 (pgvector image), Redis 7, nginx (prod build target only).


Testing

# Backend
docker compose exec api python -m pytest -v

# Frontend (typecheck)
cd frontend && npx tsc --noEmit

As of the initial backup: 82 backend tests passing, 18 skipped (skipped = live Anthropic / Voyage API calls, gated behind RUN_LIVE_AGENT_TESTS=1).


Configuration

See .env.example. Required:

Var Purpose
ANTHROPIC_API_KEY Claude (Opus for generation, Haiku for translation / OCR vision)
VOYAGE_API_KEY or OPENAI_API_KEY Embeddings provider (Voyage voyage-3 preferred; OpenAI fallback truncated + renormalised to 1024)
JWT_SECRET openssl rand -hex 32
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB DB creds (also composed into DATABASE_URL)
REDIS_URL Default redis://redis:6379/0 inside the compose network

Troubleshooting

  • Port conflicts on startup — override *_HOST_PORT in .env. Defaults remap off the standard 5432 / 6379 / 5173 / 8000 to avoid collisions with other local Docker projects.
  • max_tokens truncation on generationMAX_RESPONSE_TOKENS in app/agents/generate.py is 16k. Raise if a new deliverable schema is extra-verbose; the worker surfaces a clear error when it hits.
  • Migration SQL errors — keep CAST(:x AS JSONB) inside raw sa.text(), not :x::jsonb. SQLAlchemy + psycopg3 don't handle :: cast syntax through named params.
  • Login fails with "password too long"app/core/security.py uses bcrypt directly (not passlib) and truncates at 72 bytes. passlib's wrap-bug detector crashes on bcrypt ≥ 4 + Python 3.14.
  • API 500 on delete brief — resolved via cascade="all, delete-orphan", passive_deletes=True on Brief → Documents / Generations. Keep this pattern on any new child table.

Roadmap

  • Entra SSO swap-in (pluggable AuthProvider already in place)
  • Per-generation token / cost dashboard (data already captured on generations)
  • Incremental re-embedding when a document is re-ingested
  • Golden-output regression: commit small fixture briefs + expected JSON so agent-prompt tweaks don't silently change output shape
  • Section-level comments on review forms for team collaboration

Credits

Built by the Oliver Agency AI team, leveraging Anthropic Claude (Opus 4.7 for content, Haiku 4.5 for ingestion), pgvector for retrieval, and python-docx for branded Word output.