Architecture Diagrams¶
Visual reference for Graffold's system architecture, query pipeline, security model, and evaluation framework.
TwoPhaseAgent — Deterministic Retrieval Pipeline¶
flowchart TB
Q[User Query] --> CLASS
subgraph "Classification Layer — All Deterministic, No LLM"
CLASS[Query Classification]
CLASS --> MODE[Query Mode Router<br/>naive / local / global / hybrid / mix]
CLASS --> INTENT[Intent Classifier<br/>biomarker / protein / disease<br/>pathway / evidence / exploration]
CLASS --> ENTITY[Entity Type Classifier<br/>disease-centric vs protein-centric]
end
MODE --> DEPTH{Search Depth?}
DEPTH -->|fast| P1F[Phase 1 Only]
DEPTH -->|balanced| P1B[Phase 1 + Phase 1.5 + Phase 2 lite]
DEPTH -->|deep| P1D[Phase 1 + Phase 1.5 + Phase 2 full]
subgraph "Phase 1 — Discovery"
direction TB
P1[Parallel Tool Dispatch]
P1 --> T1[Entity Lookup<br/>by name]
P1 --> T2[Vector Search<br/>HNSW embeddings]
P1 --> T3[Fulltext Search<br/>keyword matching]
P1 --> T4[Graph Traversal<br/>explore relationships]
P1 --> T5[Dynamic Cypher<br/>generated query]
P1 --> T6[Domain Tools<br/>from config]
T1 & T2 & T3 & T4 & T5 & T6 --> HARVEST[Harvest Entities<br/>collect IDs + scores]
end
subgraph "Phase 1.5 — Domain Enrichment"
direction TB
ENR[KG Tool Dispatch<br/>based on intent groups]
ENR --> E1[Protein Details]
ENR --> E2[Disease Hierarchy]
ENR --> E3[Pathways]
ENR --> E4[Biomarkers]
ENR --> E5[Evidence]
ENR --> E6[Community Summaries]
end
subgraph "Phase 2 — Expansion"
direction TB
EXP[Top Entity IDs from Phase 1]
EXP --> N1[Neighbor Traversal<br/>2-hop graph expansion]
N1 --> BOOST[Walk-Boost Scoring<br/>session history priority]
BOOST --> CHECK{≥3 targets?}
CHECK -->|No| DEEP[Deep Expansion<br/>+ Cypher Fallback]
CHECK -->|Yes| DONE2[Expansion Complete]
end
subgraph "Phase 3 — Relevance Filtering"
direction TB
FILT[All Gathered Results]
FILT --> COS[Cosine Similarity<br/>query vs result embeddings]
FILT --> CROSS[Cross-Encoder Re-Ranking<br/>ms-marco-MiniLM]
COS & CROSS --> REFRAG[REFRAG Compression<br/>3-5x context reduction]
end
subgraph "Phase 4 — LLM Synthesis"
direction TB
SYN[Filtered Evidence + Citations]
SYN --> LLM[LLM generates answer<br/>Bedrock / SageMaker / Ollama]
LLM --> SSE[SSE Stream<br/>token-by-token to frontend]
end
HARVEST --> ENR
ENR --> EXP
DONE2 & DEEP --> FILT
REFRAG --> SYN
style CLASS fill:#e1f5fe
style P1 fill:#fff3e0
style ENR fill:#fff3e0
style EXP fill:#fff3e0
style FILT fill:#e8f5e9
style SYN fill:#fce4ec
Cross-Encoder Re-Ranking¶
The bi-encoder (Phase 1) encodes query and documents independently — fast but shallow. The cross-encoder (Phase 3) reads query + document together as a pair — slower but catches relevance the bi-encoder misses.
flowchart LR
subgraph "Bi-Encoder — Phase 1"
direction TB
QE[Query] --> ENC1[Encoder]
ENC1 --> VQ[Query Vector]
D1[Doc A] --> ENC2[Encoder]
ENC2 --> VD1[Doc A Vector]
D2[Doc B] --> ENC3[Encoder]
ENC3 --> VD2[Doc B Vector]
VQ -.->|cosine similarity| VD1
VQ -.->|cosine similarity| VD2
end
subgraph "Cross-Encoder — Phase 3"
direction TB
QD1["(Query, Doc A)"] --> CE1[Cross-Encoder<br/>ms-marco-MiniLM]
CE1 --> S1[Score: 0.92<br/>genuinely relevant]
QD2["(Query, Doc B)"] --> CE2[Cross-Encoder<br/>ms-marco-MiniLM]
CE2 --> S2[Score: 0.31<br/>keyword match only]
end
VD1 -->|"Top 50 candidates"| QD1
VD2 -->|"Top 50 candidates"| QD2
style ENC1 fill:#fff3e0
style CE1 fill:#e8f5e9
style CE2 fill:#e8f5e9
Query Mode Router — Multi-Strategy Dispatch¶
Priority: naive → mix → global → local → LLM fallback → hybrid default. LLM fallback rarely fires — most queries hit a keyword/regex rule first.
flowchart TD
Q[User Query] --> KW{Keyword Check}
KW -->|"hello, help, thanks"| NAIVE[Naive Mode<br/>Direct vector + keyword]
KW -->|No match| COMBO{Both local<br/>AND global signals?}
COMBO -->|Yes| MIX[Mix Mode<br/>All strategies in parallel<br/>weighted merge + dedup]
COMBO -->|No| GLOBAL{Global keywords?<br/>"overview, trend, landscape"}
GLOBAL -->|Yes| GLOB[Global Mode<br/>Community summary aggregation]
GLOBAL -->|No| LOCAL{Local patterns?<br/>"what is X, tell me about X"}
LOCAL -->|Yes| LOC[Local Mode<br/>Entity-focused graph traversal]
LOCAL -->|No| LLM{LLM Fallback<br/>classify via prompt}
LLM -->|Success| RESULT[Classified Mode]
LLM -->|Failure| HYBRID[Hybrid Mode<br/>Local + Global combined]
style KW fill:#e1f5fe
style COMBO fill:#e1f5fe
style GLOBAL fill:#e1f5fe
style LOCAL fill:#e1f5fe
style LLM fill:#fce4ec
style HYBRID fill:#fff3e0
Security — Defense in Depth¶
Even if detection misses something, CypherBuilder uses $parameter placeholders — user values never touch the query string.
flowchart TD
INPUT[User Input] --> EMPTY{Empty?}
EMPTY -->|Yes| REJECT1[400 Bad Request]
EMPTY -->|No| LENGTH{> 10k chars?}
LENGTH -->|Yes| REJECT2[400 Too Long]
LENGTH -->|No| PROMPT{Prompt Injection<br/>13 regex patterns}
PROMPT -->|"confidence ≥ 0.6"| REJECT3[400 Injection Detected<br/>+ audit log]
PROMPT -->|Pass| CYPHER{Cypher Injection<br/>UNION, chaining,<br/>comments, tautology}
CYPHER -->|Match| REJECT4[400 Cypher Injection<br/>+ audit log]
CYPHER -->|Pass| PII[PII Scan<br/>Presidio Analyzer]
PII --> LOG[Log PII entities<br/>if found]
LOG --> AGENT[Query reaches Agent]
AGENT --> BUILDER[CypherBuilder<br/>parameterized queries<br/>label validation]
BUILDER --> DB[(Neo4j / FalkorDB)]
style REJECT1 fill:#ffcdd2
style REJECT2 fill:#ffcdd2
style REJECT3 fill:#ffcdd2
style REJECT4 fill:#ffcdd2
style BUILDER fill:#e8f5e9
style AGENT fill:#e1f5fe
Evaluation Framework — Three Layers¶
flowchart LR
subgraph "Layer 1 — Golden Datasets"
GD[YAML Test Queries<br/>by category] --> ER[EvaluationRunner]
ER --> SC[Pluggable Scorers<br/>keyword, LLM judge]
SC --> MET[Per-Category Metrics<br/>+ mode accuracy]
end
subgraph "Layer 2 — Agent Comparison"
Q2[Same Queries] --> A1[Agent A<br/>TwoPhase]
Q2 --> A2[Agent B<br/>LangGraph]
A1 & A2 --> GLICKO[Glicko Rating<br/>with uncertainty bands]
end
subgraph "Layer 3 — Property Testing"
HYP[Hypothesis<br/>random inputs] --> PROP[Correctness Properties<br/>round-trips, invariants]
PROP --> EDGE[Edge Cases<br/>unicode, empty, overflow]
end
MET --> CATCHES1[Catches regressions]
GLICKO --> CATCHES2[Catches agent quality<br/>differences]
EDGE --> CATCHES3[Catches edge cases<br/>golden sets miss]
style GD fill:#e1f5fe
style Q2 fill:#fff3e0
style HYP fill:#e8f5e9
End-to-End Query Flow¶
sequenceDiagram
participant U as User / Frontend
participant API as FastAPI
participant SEC as SecurityGuardrails
participant RL as RateLimiter
participant QS as QueryService
participant TPA as TwoPhaseAgent
participant TOOLS as Tool Registry
participant DB as Neo4j / FalkorDB
participant CE as Cross-Encoder
participant LLM as LLM (Bedrock)
participant CACHE as Redis Cache
U->>API: POST /v1/query
API->>SEC: validate_input(question)
SEC-->>API: pass (no injection/PII)
API->>RL: check_rate_limit(user_id)
RL->>CACHE: INCR rate:{user}:{minute}
RL-->>API: allowed (under limit)
API->>QS: execute_query(question, session_id)
QS->>CACHE: check query cache
CACHE-->>QS: cache miss
QS->>TPA: invoke(question, search_depth)
Note over TPA: Phase 1 — Discovery
TPA->>TPA: classify query (regex)
TPA->>TOOLS: activate tool groups
par Parallel tool execution
TOOLS->>DB: entity lookup
TOOLS->>DB: vector search (HNSW)
TOOLS->>DB: fulltext search
TOOLS->>DB: graph traversal
end
DB-->>TPA: scored results
Note over TPA: Phase 1.5 — Domain Enrichment
TPA->>TOOLS: KG domain tools (pathways, evidence...)
TOOLS->>DB: enrichment queries
DB-->>TPA: enriched entities
Note over TPA: Phase 2 — Expansion
TPA->>DB: neighbor traversal (2-hop)
TPA->>TPA: walk-boost scoring
DB-->>TPA: expanded graph neighborhood
Note over TPA: Phase 3 — Relevance Filtering
TPA->>CE: re-rank (query, result) pairs
CE-->>TPA: relevance scores
TPA->>TPA: REFRAG context compression
Note over TPA: Phase 4 — LLM Synthesis
TPA->>LLM: filtered evidence + question
LLM-->>TPA: answer + citations (streaming)
TPA-->>QS: AgentResponse
QS->>CACHE: cache result
QS-->>API: response
API-->>U: SSE stream (token-by-token)