Eval Job Execution#
Overview#
Eval job execution in Langfuse is a two-phase pipeline: job creation (deciding which traces/observations to evaluate and persisting JobExecution records) followed by job execution (fetching data, calling the LLM judge, and writing score events back through the ingestion queue). Both phases are handled in the worker service under worker/src/features/evaluation/.
The primary source files are:
evalService.ts— job creation and LLM-as-a-judge execution logicevalScoreEvent.ts— score event constructionevalExecutionDeps.ts— dependency injection for LLM calls, S3 uploads, queue enqueueevalRuntime.ts— prompt compilation and message building utilities
Phase 1: Job Creation (createEvalJobs)#
createEvalJobs is triggered by three event types:
| Source event | Description |
|---|---|
trace-upsert | Live trace data; enforcedJobTimeScope=NEW |
dataset-run-item-upsert | Live dataset run items; enforcedJobTimeScope=NEW |
ui-create-eval | Historical batch (no enforced time scope) |
Steps:
- Fetch active job configs from Postgres (
jobType=EVAL,status=ACTIVE,blockedAt=null) filtered by project and optionally byconfigIdortimeScope. - Anti-loop guard: skip job creation for traces whose environment starts with
"langfuse-"when the source istrace-upsert, preventing eval → eval trace → eval infinite loops . - Cache optimization: with multiple configs, pre-fetch the trace once (excluding input/output, including metadata for in-memory filter evaluation) and pre-fetch dataset item IDs from ClickHouse .
- Dedup existing jobs: batch-query existing
JobExecutionrows matched by(jobConfigurationId, jobInputTraceId, jobInputDatasetItemId, jobInputObservationId). - Per-config loop: for each active config, evaluate trace filters (in-memory if possible, DB fallback otherwise), validate observation existence if an
observationIdis set, apply sampling (config.samplingprobability), and deduplicate . - Persist
JobExecutionwith the following identifier fields :jobInputTraceId— the trace being evaluatedjobInputTraceTimestamp— used for ClickHouse time-range queries during executionjobInputDatasetItemId/jobInputDatasetItemValidFrom— dataset item context (if applicable)jobInputObservationId— observation-level targeting (if applicable)
- Enqueue to
EvalExecutionQueuewith a sharding key{projectId}-{jobExecutionId}and an optionaldelay(in ms) .
If a second trace event "deselects" an already-queued job (filter no longer matches), the existing job is set to CANCELLED .
Phase 2: Job Execution (evaluate / executeLLMAsJudgeEvaluation)#
The BullMQ worker picks up the queued event and calls evaluate, which:
- Loads the
JobExecution,JobConfiguration, andEvalTemplatefrom Postgres. - Checks
isJobConfigExecutable— cancels the job if the config is blocked or inactive. - Calls
extractVariablesFromTracingDatato resolve template variables:dataset_item→ Postgres lookup byjobInputDatasetItemId+validFromtrace→ ClickHouse lookup byjobInputTraceId+jobInputTraceTimestamp- observation types → ClickHouse lookup by trace ID + observation name (
mapping.objectName) - Results are internally cached per function call to avoid redundant lookups.
- Calls
executeLLMAsJudgeEvaluation.
Inside executeLLMAsJudgeEvaluation#
This is the shared core for both trace-level and observation-level evaluations :
- Compile prompt:
compileEvalPromptinterpolates extracted variables into the template string;buildEvalMessageswraps the result into a singleChatMessageType.Usermessage. - Validate output definition:
compilePersistedEvalOutputDefinitionnormalizes the template'soutputDefinitionand produces a Zod schema for structured output. - Fetch model config: via
deps.fetchModelConfig(backed byDefaultEvalModelService). On invalid config, blocks the evaluator config with reasonINVALID_MODEL_CONFIG. - Generate
executionTraceId: a W3C-format trace ID derived fromjobExecutionId, used to link the internal LLM call trace back to the eval job . - Build execution metadata:
buildEvalExecutionMetadataproduces a snake_case key/value record containingjob_execution_id,job_configuration_id,target_trace_id,target_observation_id, andtarget_dataset_item_id. - Call LLM:
deps.callLLMpasses the structured output schema tofetchLLMCompletion(viawithStructuredOutput). The call is traced underLangfuseInternalTraceEnvironment.LLMJudgeto avoid triggering further eval jobs . - Validate LLM output:
validateEvalOutputResultrunssafeParseagainst the compiled schema. - Build and persist score events:
buildEvalScoreWritePayloads→ upload to S3 → enqueue toIngestionQueue. - Mark job complete: updates
status=COMPLETED, recordsjobOutputScoreIdandexecutionTraceId.
Score Event Construction (buildEvalScoreWritePayloads)#
buildEvalScoreWritePayloads converts an EvalOutputResult into one or more EvalScoreWritePayload objects:
| Output type | Score payloads |
|---|---|
NUMERIC | 1 payload; scoreValue: number |
BOOLEAN | 1 payload; scoreValue: 0 | 1 (boolean coerced) |
CATEGORICAL (single match) | 1 payload; scoreValue: string |
CATEGORICAL (multi-match) | N payloads — one per match; first uses primaryScoreId, rest get randomUUID() |
Each ScoreEventType body includes :
traceId/observationId— identifiers from theJobExecutionrecordexecutionTraceId— links the score back to the internal LLM judge tracemetadata— the snake_case execution metadata recordsource: ScoreSourceEnum.EVALcomment— populated with the LLM'sreasoningoutput
Score events are written to S3 via deps.uploadScore and enqueued to the IngestionQueue via deps.enqueueScoreIngestion , then processed by the standard ingestion pipeline where validateAndInflateScore handles configId resolution.
Key Identifiers Flowing Through the Pipeline#
| Field | Set in | Used in |
|---|---|---|
jobInputTraceId | createEvalJobs | Variable extraction, score traceId |
jobInputTraceTimestamp | createEvalJobs | ClickHouse time-range queries |
jobInputObservationId | createEvalJobs | Variable extraction, score observationId |
jobInputDatasetItemId | createEvalJobs | Dataset variable extraction |
jobInputDatasetItemValidFrom | createEvalJobs | Versioned dataset item lookup |
executionTraceId | executeLLMAsJudgeEvaluation | LLM call observability, score executionTraceId |
primaryScoreId | executeLLMAsJudgeEvaluation | jobOutputScoreId, first score payload |
Related Articles#
- LLM-as-a-Judge Evaluation — supported providers, structured output, model validation
- Eval Output Schema — output definition types, schema compilation, score write path
- Evaluator Configuration and Status Management — blocking, deactivation, config guards
- BullMQ Worker Lifecycle — queue registration, stall handling, metrics