SourceScore

Blog · Architecture · 2026-05-17

Multi-LLM grounding in 2026 — build once, deploy across OpenAI, Anthropic, Google, and open-weight

Single-provider lock-in is fragile in 2026. Pricing shifts, capability changes, and outages all argue for portability. Here's an architecture pattern that keeps evidence retrieval and citation review LLM-agnostic across providers.

The single-provider problem

A provider that fits one workload today may not fit its next workload, geography, budget, or reliability target. Without an adapter boundary, changing providers also means rewriting routing, evidence, and citation behavior.

Model deprecations, pricing changes, outages, and capability gaps can all make a single-provider design expensive to change. Aportable evidence layer reduces that coupling. This post shows the architecture; measure the switching value in your own stack.

Why portability matters

  • Pricing changes. Token prices and billing units differ by provider and model. Use current provider price sheets and your own token mix before making a routing decision.
  • Capability depends on the workload. Evaluate coding, extraction, long-context, reasoning, and structured-output tasks separately on your own examples.
  • Availability differs. A second tested provider can offer a fallback, but failover only helps when prompts, tools, safety rules, and output validation work on both paths.
  • Regulatory and data-residency needs vary. Check each provider's current contract, processing region, retention controls, and certifications against your own obligations; a model name alone does not establish compliance.
  • Hosted and self-managed options have different trade-offs. Compare measured quality, operations work, privacy controls, throughput, and total cost rather than assuming either route wins.

The architecture pattern

Three layers, each provider-agnostic:

  1. Router — selects which LLM provider to call per request based on rules (task type, cost budget, latency SLA, user tier).
  2. Adapter — normalizes input + output across provider APIs. OpenAI tools, Anthropic tool-use, Google function-calling, Llama function-calling all have different request shapes; the adapter hides this.
  3. Evidence layer — candidate-record retrieval and citation review applied to LLM output regardless of which provider produced it. The same catalog and canonical URLs are available after a provider switch.
# Python — multi-LLM grounding skeleton
from openai import OpenAI
from anthropic import Anthropic
import google.generativeai as genai
import os, requests

# Pin models in deployment configuration and retest before changing them.
OPENAI_MODEL = os.environ["OPENAI_MODEL"]
ANTHROPIC_MODEL = os.environ["ANTHROPIC_MODEL"]
GEMINI_MODEL = os.environ["GEMINI_MODEL"]
GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]

class LlmRouter:
    def __init__(self):
        self.openai = OpenAI()
        self.anthropic = Anthropic()
        genai.configure(api_key=GEMINI_API_KEY)
        self.gemini = genai.GenerativeModel(GEMINI_MODEL)

    def call(self, task: str, provider: str = None) -> str:
        # Router: pick provider based on task + budget + SLA
        provider = provider or self._pick_provider(task)
        if provider == "openai":
            r = self.openai.chat.completions.create(
                model=OPENAI_MODEL,
                messages=[{"role": "user", "content": task}],
                temperature=0,
            )
            return r.choices[0].message.content
        elif provider == "anthropic":
            r = self.anthropic.messages.create(
                model=ANTHROPIC_MODEL,
                max_tokens=1024,
                messages=[{"role": "user", "content": task}],
            )
            return r.content[0].text
        elif provider == "gemini":
            r = self.gemini.generate_content(task)
            return r.text
        raise ValueError(f"unknown provider: {provider}")

    def _pick_provider(self, task: str) -> str:
        # Toy heuristic — real router uses cost + quality data
        if "code" in task.lower(): return "anthropic"
        if "long" in task.lower(): return "gemini"
        return "openai"

def find_evidence_candidates(response: str) -> dict:
    """Retrieve SourceScore catalog candidates for human or programmatic
    comparison with each assertion. A match is not a truth verdict."""
    assertions = extract_assertions(response)  # split into atomic claims
    candidates = []
    for a in assertions:
        r = requests.post(
            "https://sourcescore.org/api/v1/verify",
            json={"claim": a, "minConfidence": 0.85},
            timeout=8,
        )
        match = r.json().get("bestMatch")
        if match:
            candidates.append({"assertion": a, "candidate": match})
    return {"response": response, "candidates_for_review": candidates}

# Usage
router = LlmRouter()
raw = router.call("When was Llama 3.1 released?", provider="anthropic")
review_packet = find_evidence_candidates(raw)

The grounding layer is provider-agnostic — it sees only the response text. Switch the router's provider per request (cost, capability, latency, failover) without changing the candidate-retrieval logic. Compare each assertion with the returned record and its cited evidence before publishing it.

Adapter options to evaluate

  • Vercel AI SDK — a TypeScript option for normalizing provider calls and streaming; verify current provider support in its documentation.
  • DSPy — Python; signature programming + provider abstraction.
  • LangChain — broad provider support; sometimes heavy abstraction.
  • Instructor — structured output across providers via Pydantic models.
  • Proxy-style adapters — centralize routing and credentials, at the cost of another operational dependency.
  • Hosted model gateways — offer one integration surface across vendors; review data handling, routing transparency, pricing, and failure behavior before adopting one.

Routing rules to validate in production

  1. Task type → evaluated model. Maintain a small, versioned test set for each task family and route only after a model clears your quality and safety threshold.
  2. User tier → cost budget. Set explicit per-request and monthly limits, then select the least expensive model that passes the relevant task evaluation.
  3. Latency target → measured route. Benchmark first token and completion time from your deployment regions. Streaming improves perceived responsiveness but does not guarantee a target.
  4. Provider error → tested fallback. Bound retries, preserve safety and evidence checks across routes, and tell users when the system returns a reduced-capability result.
  5. Regulatory zone → approved deployment. Route only to configurations your legal and security review has approved for the relevant data, region, and customer.

Why grounding-layer portability matters

The temptation is to use a provider's built-in grounding feature: Anthropic Citations API, OpenAI's search-grounded responses, or built-in retrieval. These can preserve useful provider-native context and citation metadata.

But when you switch providers (cost, capability, outage), the grounding layer disappears with the provider. Your users see citations on Monday and none on Tuesday because you failed over to a different model. That's a trust collapse.

A provider-agnostic grounding layer (like SourceScore VERITAS, or a self-built RAG pipeline against a shared knowledge store) survives provider switches. The review workflow can stay consistent — same catalog and same canonical URLs — regardless of which LLM produced the raw response. A returned candidate still needs an entailment or editorial check.

Provider-locked grounding still has its place

Anthropic Citations API and OpenAI's search-grounded responses excel at user-uploaded-doc citations within a single provider. The clean pattern: use both.

  • User-supplied document RAG → use the provider's native citation API
  • Shared knowledge base + cross-provider facts (model release dates, paper authorship, organizational facts) → use a portable grounding layer like VERITAS

See VERITAS vs Anthropic Citations API for the full head-to-head.

Getting started

  1. Pick an adapter library that fits your stack (Vercel AI SDK for TS, DSPy or LiteLLM for Python).
  2. Wrap your LLM calls behind the adapter. No business logic should hit provider SDKs directly.
  3. Add a router with at minimum 2 providers + automatic failover.
  4. Add a grounding layer that doesn't care which provider produced the response. Run the same 5-min quickstart regardless of backend.
  5. Test the failover by killing each provider in turn and confirming citations still render.

Related