Skill 20 · Langsmith Online Eval Engineering
Subchapter 20.1
references/evaluator-design.mdMarkdown6 KBView on GitHub
Best practices for designing LangSmith online evaluators. One quality dimension per evaluator.
| Factor | LLM-as-judge | Code evaluator |
|---|
| Use when | Success is subjective or semantic | Success is objective or deterministic |
| Examples | Relevance, helpfulness, safety, tone, factual grounding | Output exists, format validation, length thresholds, JSON structure, keyword presence |
| Speed | Slower (LLM inference per trace) | Fast (direct execution) |
| Cost | LLM token cost per evaluation | No additional cost |
| Determinism | Non-deterministic; may vary across runs | Fully deterministic |
Rules of thumb:
Always put a reasoning field before the score field in the Pydantic schema. This forces the LLM to explain before committing to a score, improving accuracy.
class ResponseSchema(BaseModel):
reasoning: str = Field(
description="Step-by-step reasoning for the evaluation."
)
score: bool = Field(
description="Whether the response meets the criterion."
)Write the system prompt as a clear rubric. The evaluator should assess the result, not whether it matches a reference answer or preferred process.
Good: “Does the response answer the user’s question using information from the provided context?”
Bad: “Does the response match the expected answer?” (there is no expected answer in online evaluation)
Each evaluator should measure exactly one quality dimension. Combining multiple criteria (e.g., relevance AND tone AND safety) in a single evaluator makes scores uninterpretable and harder to debug.
Create separate evaluators for separate concerns:
relevance-evaluator – Does the response address the question?safety-evaluator – Is the response free of harmful content?tone-evaluator – Is the tone appropriate for the context?Map only the trace fields the prompt actually uses. Passing unused fields adds noise. See trace-inspection.md for how to discover available fields.
bool): pass/fail – simplest, use when the criterion is binary.float, 0–1): granular scoring – use when partial credit matters.str): labels – use when the result is a category, not a scale.Use the simplest type that captures the distinction you need. Boolean is usually sufficient for online evaluation.
run is a plain dict at runtime, not an object. Use run.get("inputs") and run.get("outputs") – never run.inputs or run.outputs.
run.get("outputs") can be None if the run errored. Always check:
def perform_eval(run, example=None):
outputs = run.get("outputs")
if outputs is None:
return {"key": "my_check", "score": 0, "comment": "No output"}
# ... evaluate ...Code evaluators cannot import external packages. All logic must be in the perform_eval function body using only Python builtins and the standard library. No import numpy, no import requests, no import langchain.
The "key" in the return dict appears as the score name in the LangSmith UI. Use descriptive, lowercase, underscore-separated names:
"has_output" – not "check1""response_length" – not "len""contains_greeting" – not "g"# Boolean (pass/fail)
return {"key": "has_output", "score": True, "comment": "Output exists"}
# Numeric (0-1 scale)
return {"key": "response_length", "score": 0.75, "comment": "375 of 500 char target"}
# Integer
return {"key": "word_count", "score": 42, "comment": "42 words in response"}| Anti-pattern | Problem | Fix |
|---|---|---|
| Guessing field names | Evaluator silently gets empty data | Inspect traces first |
Using run.inputs (attribute access) | AttributeError – run is a dict | Use run.get("inputs") |
def perform_eval(run, example) without default | TypeError – runtime passes one arg | Use example=None |
| Multiple criteria in one evaluator | Ambiguous scores | One quality per evaluator |
| Scoring against a reference answer | No reference exists in online eval | Score against a rubric |
| External imports in code evaluator | Runtime error | Use only builtins/stdlib |
Ignoring None outputs | Crash on errored runs | Check run.get("outputs") is None |
Generic score names ("score", "result") | Indistinguishable in UI | Use descriptive keys |
| Reasoning after score | LLM rationalizes instead of reasons | Put reasoning field first |
Before deploying an evaluator, mentally verify it against three cases:
If any case produces an unexpected result, revise the evaluator before deploying.