SourceScore

Integration guide

LangGraph + SourceScore VERITAS

A LangGraph StateGraph with two VERITAS nodes: one retrieves curated records and another finds a candidate for review. Similarity alone never marks the answer grounded.

Install

pip install langgraph langchain-openai requests

Graph state + the retrieve node

LangGraph nodes are plain functions that take the state and return a partial update. The retrieve node turns a question into curated VERITAS records via /search, which returns a results array of claim summaries.

import requests
from typing import TypedDict, List
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

VERITAS = "https://sourcescore.org/api/v1"

class State(TypedDict):
    question: str
    claims: List[dict]
    answer: str
    candidate_found: bool

def veritas_retrieve(state: State) -> dict:
    r = requests.get(
        f"{VERITAS}/search",
        params={"q": state["question"], "limit": 5},
        timeout=8,
    )
    r.raise_for_status()
    # /search returns {"results": [...]} — each item is a catalog summary.
    return {"claims": r.json().get("results", [])}

The generate node

Build the prompt from retrieved records and tell the model to cite one only after its exact statement supports the answer.

def generate(state: State) -> dict:
    context = "\n".join(
        f"[{c['id']}] {c['statement']} (confidence {c['confidence']})"
        for c in state["claims"]
    )
    prompt = (
        "The records below are candidates, not truth verdicts. Use a record only "
        "when its exact statement supports the answer; otherwise say the supplied "
        "evidence does not cover the question.\n\n"
        f"Candidate records:\n{context}\n\nQuestion: {state['question']}\nAnswer:"
    )
    answer = ChatOpenAI(model="gpt-4o-mini").invoke(prompt).content
    return {"answer": answer}

The candidate-lookup node

A retriever returns the closest claims; it does not confirm the generated answer is consistent with them. This node POSTs the answer to /verify, which returns a bestMatch when it finds a similar catalog record after method-specific retrieval and legacy record-confidence gates. That is still not an entailment or truth verdict.

def find_answer_candidate(state: State) -> dict:
    r = requests.post(
        f"{VERITAS}/verify",
        json={"claim": state["answer"], "minConfidence": 0.85},
        timeout=8,
    ).json()
    # /verify returns {"matches": [...], "bestMatch": {...} | absent}.
    return {"candidate_found": r.get("bestMatch") is not None}

Wire the graph

Retrieve first. If no claims match, short-circuit to END (don't let the model improvise). Otherwise generate, then expose the nearest catalog candidate for an explicit evidence-review step.

def has_claims(state: State) -> str:
    return "generate" if state["claims"] else "no_claims"

g = StateGraph(State)
g.add_node("retrieve", veritas_retrieve)
g.add_node("generate", generate)
g.add_node("candidate_lookup", find_answer_candidate)

g.set_entry_point("retrieve")
g.add_conditional_edges("retrieve", has_claims, {"generate": "generate", "no_claims": END})
g.add_edge("generate", "candidate_lookup")
g.add_edge("candidate_lookup", END)

app = g.compile()

out = app.invoke({"question": "Who introduced the Transformer architecture?"})
print(out["answer"])
print("candidate found:", out["candidate_found"])

Why expose a candidate state

The two failure modes a retriever can't catch — out-of-corpus assertions and fabricated citations — both can be investigated here: if no similar catalog candidate is returned, bestMatch is absent and candidate_found is false. When a candidate exists, compare its primary sources before asserting truth; otherwise retry, hand off to a human, or label the answer as lacking a catalog match. The public API is free with no account, key, or signup.

Next steps