Skip to content

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)