Milestone 6 — Generated Answer Pipeline (AGWMP)
Overview
Milestone 6 implements the Automated Generated-answer With Memory and Pipeline (AGWMP) service — the Flow B generation pipeline that automatically produces draft answers for unanswered customer questions using LLMs and retrieval-augmented generation.
Architecture
The pipeline follows the layered architecture established in previous milestones:
API (FastAPI)
└─ AGWMPService (application)
├─ WorkItemRepository (domain protocol → PostgresWorkItemRepository)
├─ SearchAdapter[] (domain protocol → ConfluenceAdapter, JiraAdapter)
├─ VectorRepository (domain protocol → WeaviateAdapter)
├─ LLMAdapter (domain protocol → OpenAIAdapter)
└─ RAGContextBuilder (pure domain service)
Key Design Decisions
Known-Answer Detection (Weaviate, similarity ≥ 0.92)
Before calling the LLM, the service queries Weaviate for semantically similar approved answers. If any existing answer has a cosine similarity ≥ 0.92 to the question, it is surfaced as a GeneratedCandidate without an LLM call. This prevents duplicate generation and leverages the growing knowledge base.
No-Content Escalation (< 2 RAG sources)
If fewer than 2 RAG sources are assembled, the work item is moved to ESCALATED state and no LLM call is made. This avoids generating hallucinated answers when there is insufficient grounding material. Escalated items optionally create a Jira issue for human review.
Circuit Breaker (in-process, 5 failures / 60 s recovery)
An in-process circuit breaker wraps the LLM call. After 5 consecutive failures, the circuit opens for 60 seconds to prevent cascading timeouts. In OPEN state, new work items are immediately escalated rather than queued behind a failing LLM.
Note: The circuit breaker maintains state per-process. Production multi-worker Celery deployments should persist circuit state in Redis for shared circuit behaviour.
RAG Context Assembly
The RAGContextBuilder sorts and trims sources within a configurable token budget (default 4,000 tokens). Sources are ranked by:
1. Staleness (fresh sources first; > 12 months old = stale)
2. Authority weight (1.0 for primary docs, 0.5 for community content)
3. Relevance score (keyword overlap with the question)
4. Last-modified date (most recent first)
Token estimation uses a 4 chars ≈ 1 token heuristic to avoid a heavy ML dependency in the domain layer.
F2 Score (Recall-Weighted, β = 2)
RAG retrieval quality is evaluated using the F2 score (β = 2), which weights recall 4× more than precision. This reflects the asymmetric cost of missing a relevant source (answers may be incomplete) vs. including a marginally relevant source (over-long prompts). The quality gate is F2 ≥ 0.78.
WorkItem State Machine
NEW
├─→ ESCALATED (no-content / circuit open / LLM error)
└─→ GENERATING
├─→ PENDING_VALIDATION (known-answer bypass or successful generation)
└─→ ESCALATED (no-content after GENERATING)
PENDING_VALIDATION → IN_CLASSIFICATION → READY_FOR_REVIEW
├─→ VALIDATED
└─→ NEEDS_REWORK
VALIDATED → CLOSED
ESCALATED → CLOSED
Files Added
Domain
| File | Description |
|---|---|
src/raw_to_knowledge/domain/enums/generation.py |
WorkItemState, CandidateOrigin, HornType enums |
src/raw_to_knowledge/domain/models/generation.py |
RAGSource, RAGContext, Citation, LLMResponse, WorkItem, GeneratedCandidate, KnownAnswerMatch |
src/raw_to_knowledge/domain/protocols/llm.py |
LLMAdapter protocol |
src/raw_to_knowledge/domain/protocols/search.py |
SearchAdapter, VectorRepository protocols |
src/raw_to_knowledge/domain/services/rag_context_builder.py |
RAGContextBuilder — pure domain, no I/O |
src/raw_to_knowledge/domain/services/circuit_breaker.py |
CircuitBreaker state machine |
src/raw_to_knowledge/domain/services/f2_scorer.py |
compute_f2_score, meets_quality_gate |
Infrastructure
| File | Description |
|---|---|
src/raw_to_knowledge/infrastructure/llm/openai_adapter.py |
OpenAIAdapter — OpenAI-compatible LLM |
src/raw_to_knowledge/infrastructure/confluence/confluence_adapter.py |
ConfluenceAdapter — Confluence CQL search |
src/raw_to_knowledge/infrastructure/jira/jira_adapter.py |
JiraAdapter — Jira JQL search + ticket creation |
src/raw_to_knowledge/infrastructure/vector/weaviate_adapter.py |
WeaviateAdapter — known-answer detection + indexing |
src/raw_to_knowledge/infrastructure/celery_app.py |
Celery app instance (raw_to_knowledge_generation queue) |
src/raw_to_knowledge/infrastructure/persistence/models.py |
Added WorkItemORM, GeneratedCandidateORM |
src/raw_to_knowledge/infrastructure/persistence/repositories.py |
Added PostgresWorkItemRepository |
Application
| File | Description |
|---|---|
src/raw_to_knowledge/application/generation/agwmp_service.py |
AGWMPService — pipeline orchestrator |
src/raw_to_knowledge/application/generation/tasks.py |
Celery task process_work_item |
API
| File | Description |
|---|---|
src/raw_to_knowledge/api/v1/generation.py |
4 FastAPI routes (POST enqueue, GET list, GET single, GET candidate) |
Migration
| File | Description |
|---|---|
alembic/versions/005_generation_schema.py |
work_items + generated_candidates tables |
API Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/v1/generation/work-items |
Enqueue question for generation (202 Accepted) |
GET |
/v1/generation/work-items |
List work items (optional ?state= filter) |
GET |
/v1/generation/work-items/{id} |
Get a single work item |
GET |
/v1/generation/work-items/{id}/generated-candidate |
Get the generated answer |
Configuration
New settings in src/raw_to_knowledge/config.py:
| Setting | Default | Description |
|---|---|---|
OPENAI_API_KEY |
"" |
OpenAI (or compatible) API key |
LLM_MODEL |
gpt-4o-mini |
Default LLM model |
RAG_TOKEN_LIMIT |
4000 |
Max token budget for RAG prompt |
WEAVIATE_URL |
http://localhost:8080 |
Weaviate URL |
WEAVIATE_API_KEY |
"" |
Weaviate API key (empty = unauthenticated) |
CONFLUENCE_BASE_URL |
"" |
Confluence base URL |
CONFLUENCE_USERNAME |
"" |
Atlassian email |
CONFLUENCE_API_TOKEN |
"" |
Atlassian API token |
CONFLUENCE_SPACE_KEYS |
"" |
Comma-separated space keys |
JIRA_BASE_URL |
"" |
Jira base URL |
JIRA_USERNAME |
"" |
Atlassian email |
JIRA_API_TOKEN |
"" |
Atlassian API token |
JIRA_PROJECT_KEY |
"" |
Default project for escalation tickets |
Quality Gates
| Gate | Threshold | Measurement |
|---|---|---|
| RAG F2 score | ≥ 0.78 | Recall-weighted F2 over golden question set |
| Known-answer detection | Similarity ≥ 0.92 | Weaviate cosine similarity threshold |
| No-content escalation | < 2 sources | RAG source count after assembly |
Running the Worker
# Start the Celery worker
celery -A raw_to_knowledge.infrastructure.celery_app worker --loglevel=info -Q raw_to_knowledge_generation
# Or with concurrency
celery -A raw_to_knowledge.infrastructure.celery_app worker --loglevel=info -Q raw_to_knowledge_generation --concurrency=4
Testing
# Unit tests (no external deps)
pytest tests/unit/test_rag_context_builder.py tests/unit/test_f2_scorer.py \
tests/unit/test_circuit_breaker.py tests/unit/test_agwmp_service.py
# Integration tests (requires DATABASE_URL)
DATABASE_URL=postgresql+asyncpg://... pytest tests/integration/test_generation_roundtrip.py -m integration
# RAG quality evaluation (requires rag_labels.jsonl)
python tests/golden/evaluate.py --rag-labels tests/golden/rag_labels.jsonl