Module 11. Tools¶
In this module
You will learn: what jobs eval tools do, a few widely used open-source options for each job, and minimal working examples you can adapt.
Reading time: about 5 minutes.
The method is the point
Tools, ownership and licences change quickly, so check the current status of any tool before adopting it. Everything in this course can be done with another tool, or with scripts and a spreadsheet, as long as you follow the lifecycle. A team with a spreadsheet that reads traces and validates its judges will get better evals than a team with the best framework that does neither.
Tools by job¶
| Job | Examples | Notes |
|---|---|---|
| Capturing traces, annotating them and building datasets | Langfuse (self-hostable), Arize Phoenix, LangSmith, Braintrust, MLflow tracing; or OpenTelemetry plus any backend | A spreadsheet is a fine start |
| LLM-as-judge for custom criteria | DeepEval with the G-Eval method, Promptfoo llm-rubric, RAGAS |
Whatever you use, validate the judge (Module 7) |
| Red teaming and prompt-level regression tests | Promptfoo, NVIDIA garak, Microsoft PyRIT | Run red teaming on a schedule, not on every pull request |
The sections below show one example for each job.
Traces, annotation and datasets (Langfuse or similar)¶
Look for a tool whose workflow matches Modules 3 and 4. Langfuse is one example: traces are captured automatically; you filter to the interesting ones (negative feedback, errors, a random sample) and add them to an annotation queue; experts read each trace in a readable view and record a label from a pre-defined score config (for example pass/fail per failure mode) with a comment; labelled traces become a dataset; and you run experiments to rerun the dataset on each change. Langfuse can be self-hosted (the core is MIT-licensed; running it yourself means operating a database stack).
Whichever tool you choose, the data model matters more than the product. It should let you:
- Capture traces in a standard format (OpenTelemetry where possible).
- Filter and read them easily.
- Record human labels with a fixed schema.
- Turn labelled traces into versioned datasets.
- Rerun the dataset on every change and compare.
- Mask personal data and restrict access.
LLM-as-judge: DeepEval and G-Eval¶
DeepEval is a Python framework that lets you write LLM evals as pytest-style tests. G-Eval is a method (Liu et al., 2023) for turning a plain-English criterion into a judge:
- You give the criteria (or the evaluation steps directly).
- The judge LLM writes its own evaluation steps (a chain of thought).
- It fills in a score form; the score is weighted by the model's token probabilities where available.
By default G-Eval asks the judge for an integer score (1 to 5 in the paper) and probability-weights it. That graded behaviour is what Pitfall 1 discourages, so set strict_mode=True, which makes DeepEval return a binary 1 or 0 instead.
In the paper, G-Eval with GPT-4 reached only moderate agreement with human rankings on summarisation (Spearman correlation 0.514, where 1 means identical order; graded mode), another reason to validate against your labels. The example below follows the binary-judge rule (one narrow criterion, strict_mode=True, pinned evaluation steps). It was checked against DeepEval 4.3.1; names may change between versions (older versions use LLMTestCaseParams instead of SingleTurnParams).
from deepeval import assert_test
from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCase, SingleTurnParams
# One narrow criterion per failure mode. strict_mode=True makes the score binary (1 or 0).
# NOTE: the default judge is an OpenAI model and construction fails without OPENAI_API_KEY.
# To use another provider, pass your own judge via model=... (see below).
refund_policy = GEval(
name="States the current refund window",
evaluation_steps=[
"Find the refund window stated in the actual output.",
"Compare it with the refund window in the expected output.",
"Fail if the output states a different window or promises an exception.",
],
evaluation_params=[
SingleTurnParams.INPUT,
SingleTurnParams.ACTUAL_OUTPUT,
SingleTurnParams.EXPECTED_OUTPUT,
],
strict_mode=True,
model=my_judge, # required unless OPENAI_API_KEY is set
)
def test_refund_after_45_days():
question = "Can I get a refund after 45 days?"
test_case = LLMTestCase(
input=question,
actual_output=my_app(question), # call your system here
expected_output="No, refunds are only available within 30 days.",
)
assert_test(test_case, [refund_policy])
Run it with deepeval test run test_refund.py. Practical advice:
- Keep it pass/fail. Use
strict_mode=Truerather than reading a graded 0 to 1 score: nobody can say what 0.7 means. Validate the judge against your human pass/fail labels (Module 7). - Pin the evaluation steps. If you give only
criteria, the steps are regenerated on each run and scores can be inconsistent. G-Eval is not deterministic and adds an LLM call per judgement (cost and latency). It also has a documented tendency to favour LLM-written text.
Using your own judge model (for example through AWS Bedrock)
- Write a small class extending DeepEval's
DeepEvalBaseLLMthat calls your provider. On AWS Bedrock, call the Converse API with an inference profile ID (a routing ID tied to one model version), not a base model ID. - Implement
generateanda_generateso they accept the optional PydanticschemaDeepEval passes for structured output, and pass the class asmodel=. Test the wrapper on a few cases before trusting scores. - Keep
strict_mode=True. Models on Bedrock generally do not return token probabilities, so non-strict G-Eval falls back to plain integer scores. - Record the judge's exact model version with every result and keep it in your model inventory (Module 9): the judge's model will be retired too.
Red teaming and rubric tests: Promptfoo¶
Promptfoo is a command-line tool for evaluating and red-teaming LLM applications, configured in YAML. A common use is to generate adversarial attacks against your app and report which ones got through. A minimal red-team configuration for a chat endpoint (check the Promptfoo docs for the options in your version):
# promptfooconfig.yaml
targets:
- id: https
label: support-bot
config:
url: 'https://internal.example/chat' # your app's endpoint
method: POST
headers: { 'Content-Type': 'application/json' }
body: { message: '{{prompt}}' }
transformResponse: 'json.reply' # depends on your response shape
redteam:
purpose: 'Customer support assistant. Users must only see their own orders.'
numTests: 10
plugins: [pii, rbac, bola, hijacking, excessive-agency]
strategies: [jailbreak, crescendo]
npx promptfoo@latest redteam run # generates attacks and runs them against your target
npx promptfoo@latest redteam report # opens the results
Run red teaming on a schedule (nightly or weekly), not on every pull request: it is slow and costs money. Prompt injection is a vulnerability category these plugins probe, not a plugin name; indirect prompt injection has its own plugin that needs extra configuration (see the Promptfoo docs). Attack generation uses a separate model (OpenAI by default) and may send your purpose text to a hosted service, so check Promptfoo's data-handling documentation and agree it with your privacy team before pointing it at real systems.
For ordinary regression tests, Promptfoo supports model-graded assertions. Keep each llm-rubric to one narrow yes/no condition (Pitfall 1). Promptfoo returns a 0 to 1 score; set the threshold so it acts as a plain pass/fail cut-off, and validate the judge on your labels like any other:
prompts:
- 'You are a support bot. Answer the customer: {{question}}'
providers:
- id: https
config:
url: 'https://internal.example/chat'
method: POST
body: { message: '{{prompt}}' }
transformResponse: 'json.reply'
tests:
- vars: { question: 'Can I get a refund after 45 days?' }
assert:
- type: llm-rubric
value: 'States that refunds are only available within 30 days and does not promise an exception.'
threshold: 0.8 # cut-off on the 0-1 rubric score; validate this judge like any other
Key takeaways¶
- The method matters more than the tool. A spreadsheet that follows the lifecycle beats a framework that does not.
- You need tools for three jobs: traces and labels, LLM judges, and red teaming.
- Configure judges as binary, with pinned evaluation steps, and validate them whatever the tool.
- Run red teaming on a schedule, with agreement from your security and privacy teams.
Check your understanding¶
Your team has no budget for an eval platform. Can you still follow this course?
Yes. Save traces to JSONL files, label them in a spreadsheet, write code checks and judges as ordinary tests, and rerun them on every change.
Why set strict_mode=True in G-Eval?
To get a binary pass or fail instead of a graded score, which is easier to validate against expert labels and to act on.
Why pin the evaluation steps in G-Eval instead of giving only the criteria?
With only criteria, the judge regenerates its evaluation steps on each run, so the same output can get different verdicts. Pinned steps keep the judge consistent, and the judge you validated stays the judge you run.
Why run red teaming on a schedule rather than on every pull request?
It generates many attacks with a separate model, so it is slow and costs money. A nightly or weekly run still catches new weaknesses without blocking every change.
Before you point a hosted attack generator at your system, what do you check?
What data leaves your network. Attack generation may send your purpose text to a hosted service (OpenAI by default), so read the tool's data-handling documentation and agree it with your privacy team first.