n4nAI

Comparing models with Haystack evaluation pipelines

Practical guide to using Haystack evaluation pipelines to compare models head-to-head on cost, latency, and quality with reproducible code.

n4n Team5 min read998 words

Audio narration

Coming soon — every post will get a voice note here.

When you need to compare models haystack evaluation pipeline offers a structured alternative to ad-hoc prompting. Instead of eyeballing outputs, you define a dataset, attach evaluators, and let the framework run the same prompts against each candidate. This post walks through a head-to-head of three representative LLMs using Haystack 2.x evaluation primitives, and shows exactly where the variables live in your code.

Why build an evaluation pipeline

Throwing a few prompts at a model and reading the answers does not scale past two candidates. You forget what you asked, the temperature drifts, and the second model gets a slightly different system prompt. Haystack’s evaluation pipeline fixes the inputs, wraps the model in a component, and emits scored results you can diff in a dataframe.

The core primitives are Pipeline, a generator component, and an evaluator component (LLMEvaluator, FaithfulnessEvaluator, AnswerExactMatch). You run the pipeline over a list of EvaluationExample objects and collect EvaluationRunResult. The compare models haystack evaluation pipeline approach removes every variable except the model name, which is the only thing you should be testing.

The contenders

We compare three models that cover distinct deployment styles:

  • GPT-4o (OpenAI): proprietary flagship, multimodal, strong general reasoning, 128K context.
  • Claude 3.5 Sonnet (Anthropic): proprietary, 200K context, strong coding and long-doc reasoning.
  • Llama 3 70B (open weights, served via an OpenAI-compatible gateway): self-hostable or cheaply hosted, good enough for many RAG tasks.

If you want a single endpoint to address all three without juggling separate SDKs, an OpenAI-compatible gateway like n4n.ai lets you keep the same OpenAIChatGenerator code and only swap the model string and api_base_url. That keeps the setup honest: the only variable is the model behind the endpoint.

Head-to-head dimensions

Before writing code, lock the comparison axes. The ones that matter to engineers shipping production systems:

  • Capabilities: what tasks the model handles reliably (multimodal, long context, function calling).
  • Price/cost model: per-token cost, not just sticker price—include cached input discounts if the provider honors them.
  • Latency/throughput: time to first token and tokens/sec under realistic concurrency.
  • Ergonomics: how painful it is to call from your stack (SDKs, OpenAI compatibility, streaming).
  • Ecosystem: first-class Haystack components, observability hooks, community eval recipes.
  • Limits: rate limits, max context, regional availability, content filters.

Here is the summary table:

Model Capabilities Cost model (public, per MTok) Latency/throughput Ergonomics Ecosystem Limits
GPT-4o Text+vision, function calling, 128K ctx ~$5 in / $15 out Sub-second TTFT on paid tier, high throughput OpenAI SDK, OpenAI-compatible, streaming Native OpenAIChatGenerator, huge tooling Rate tiers, geo restrictions
Claude 3.5 Sonnet 200K ctx, strong code/doc reasoning ~$3 in / $15 out Similar TTFT, moderate throughput Anthropic SDK or OpenAI-compatible proxy AnthropicChatGenerator, growing Haystack support 200K cap, rate limits
Llama 3 70B (served) Text only, 8K–128K depending on impl $0.5–$1 typical hosted, free if self-host GPU-dependent; can be slower OpenAI-compatible chat completions OpenAIChatGenerator via base URL No native vision, varies by host

The table is a snapshot; verify current prices on provider sites. The point is to make those columns explicit in your own eval config so you are not surprised in production.

Building the Haystack evaluation pipeline

Install the framework and the eval extras:

pip install haystack-ai[eval]

Define a small QA dataset with known contexts so faithfulness can be measured. Keep it domain-specific; generic trivia hides real gaps.

[
  {
    "question": "What is the max context of GPT-4o?",
    "context": "GPT-4o supports a 128K token context window.",
    "reference": "128K tokens"
  },
  {
    "question": "Which model has 200K context?",
    "context": "Claude 3.5 Sonnet provides a 200K token context window.",
    "reference": "Claude 3.5 Sonnet"
  },
  {
    "question": "What is Llama 3 70B missing natively?",
    "context": "Llama 3 70B is text-only and does not support vision inputs.",
    "reference": "Vision support"
  }
]

Now build a pipeline that generates an answer and scores it with an LLM-based faithfulness evaluator. We loop over model configs to reuse the same harness.

from haystack import Pipeline
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.evaluators import FaithfulnessEvaluator
from haystack.evaluation import EvaluationHarness
import json

configs = {
    "gpt-4o": {"base": "https://api.openai.com/v1", "key": "sk-..."},
    "claude-3-5-sonnet": {"base": "https://api.openai.com/v1", "key": "sk-..."},
    "llama-3-70b": {"base": "https://gateway.n4n.ai/v1", "key": "n4n-..."},
}

with open("qa.json") as f:
    data = json.load(f)

for name, cfg in configs.items():
    gen = OpenAIChatGenerator(
        model=name,
        api_base_url=cfg["base"],
        api_key=cfg["key"],
        generation_kwargs={"temperature": 0}
    )
    eval_pipe = Pipeline()
    eval_pipe.add_component("gen", gen)
    eval_pipe.add_component("faith", FaithfulnessEvaluator(
        api_base_url=cfg["base"], api_key=cfg["key"], model=name
    ))
    eval_pipe.connect("gen.replies", "faith.responses")
    eval_pipe.connect("gen.meta", "faith.meta")
    harness = EvaluationHarness(pipeline=eval_pipe, data=data)
    result = harness.run()
    print(name, result.score)

The wiring step is standard Haystack; the snippet focuses on the model swap. Because temperature is pinned to 0, the compare models haystack evaluation pipeline is deterministic enough to trust deltas. If you use a gateway that honors provider cache-control hints, prefix caching kicks in automatically when your contexts repeat across examples.

Reading the results

EvaluationRunResult gives per-example scores and an aggregate. Export to CSV for offline analysis:

result.to_csv(f"{name}_scores.csv")

Look at faithfulness mean, but also variance. A model that scores 0.95 average but 0.6 on one question is riskier than one at 0.90 flat. Add a second evaluator (LLMEvaluator with a custom rubric) to capture style or correctness beyond faithfulness. For example, a rubric that penalizes verbose answers changes the ranking when throughput matters.

from haystack.components.evaluators import LLMEvaluator

rubric = {
    "score_points": [1, 2, 3, 4, 5],
    "template": "Rate conciseness from 1-5 for: {{ answer }}"
}
concise_eval = LLMEvaluator(model=name, api_base_url=cfg["base"], api_key=cfg["key"], rubric=rubric)

Run both evaluators in the same pipeline and merge the scores. The model that wins on faithfulness but loses on conciseness may cost more in token billing than you expect.

Gotchas when comparing models

First, never compare a model behind a gateway with one called directly unless the gateway adds zero transformation. A proxy that rewrites system prompts invalidates the test. Second, watch context window differences: if your dataset exceeds 8K tokens and you test Llama on a host capped at 8K, you are measuring truncation, not capability. Third, rate limits will skew latency numbers; run evaluations serially with a sleep, or parallelize within the documented tier.

Finally, evaluators themselves are LLMs. If you use GPT-4o to judge Llama, you import its biases. Rotate the judge or use a deterministic metric (AnswerExactMatch) on a subset to sanity-check.

Which to choose

Verdict by use case:

High-stakes document analysis with long inputs — Claude 3.5 Sonnet. The 200K context and strong reasoning on dense text win when you cannot chunk aggressively.

Multimodal product features (image + text) — GPT-4o. It is the only one in the trio with native vision, and the Haystack generator is battle-tested.

Cost-sensitive RAG at scale — Llama 3 70B served via a gateway. If your eval shows faithfulness within 2% of the proprietary models on your domain data, the per-token savings compound fast. Self-hosting removes per-call API cost entirely if you already have GPUs.

Fast prototyping with minimal code changes — Any of them behind one OpenAI-compatible base URL. The compare models haystack evaluation pipeline pattern means you change one string, not your integration.

Pick the model that wins on the dimensions you weighted, not the one with the best marketing. Run the pipeline on your own data before committing.

Tagshaystackevaluationmodel-comparisonbenchmarking

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All haystack evaluation pipelines posts →