Skip to main content

Langfuse Activities

Two activities connect a workflow to Langfuse for LLM evaluation:

  • langfuse.run_experimentoffline evaluation. Replays a Langfuse dataset where the task and each evaluator are themselves moco workflows.
  • langfuse.create_scoreonline evaluation. Attaches scores to the trace of the workflow that is running right now.

Setup

Credentials come from the activity input or, when omitted, from the environment:

Input fieldEnvironment variable
public_keyLANGFUSE_PUBLIC_KEY
secret_keyLANGFUSE_SECRET_KEY
base_urlLANGFUSE_BASE_URL

A missing value is a hard error, not a silent fallback. The worker needs the langfuse package.

These are raw credentials, not secret names

This is the only provider that takes credentials inline rather than as a *_secret_key. A key written into a workflowspec passes through workflow context and history in plaintext — prefer configuring the environment variables on the worker and omitting the fields entirely.

Scoring also relies on tracing: langfuse.create_score uses the run's trace_id as the Langfuse trace id, which is correct when the deployment exports OpenTelemetry traces to Langfuse (MOCO_OTEL_ENABLE_LANGFUSE=true).

Defaults

ActivityTimeoutMax attempts
langfuse.run_experiment3600 s1 — an experiment must not be replayed
langfuse.create_score30 s3 — safe, thanks to the deterministic score id

langfuse.run_experiment

Runs every item of a Langfuse dataset through a task workflow, then scores each result with one or more evaluator workflows, and records the run in Langfuse.

Each dataset item's input is handed to the task workflow as its input_data, unchanged — no schema is imposed, so the dataset must be shaped to match the workflow's inputs. Each evaluator receives {input, output, expected_output, metadata} and must return a dict, or a list of dicts, carrying name and value; a missing name defaults to the evaluator's workflowspec name.

Input

FieldTypeRequiredDefaultDescription
dataset_namestryesLangfuse dataset to run
taskWorkflowSpecInfoyesWorkflow run for each dataset item
evaluatorslist[WorkflowSpecInfo]no[]Workflows that score each result
experiment_namestrnonullExperiment name in Langfuse
run_namestrnonullName of this run
descriptionstrnonullRun description
metadatadictnonullMetadata attached to the run
max_concurrencyintnonullDataset items processed in parallel
public_keystrnoLANGFUSE_PUBLIC_KEYLangfuse public key
secret_keystrnoLANGFUSE_SECRET_KEYLangfuse secret key
base_urlstrnoLANGFUSE_BASE_URLLangfuse endpoint

WorkflowSpecInfo

FieldTypeRequiredDefaultDescription
namestrnonullName of a deployed workflowspec
versionstrnolatestVersion to run
contentstr | list[str]nonullThe workflowspec YAML inline, instead of a deployed name

Output

FieldTypeDescription
namestr | nullExperiment name
run_namestr | nullRun name
descriptionstr | nullRun description
experiment_idstr | nullLangfuse experiment id
dataset_run_idstr | nullLangfuse dataset run id
dataset_run_urlstr | nullLink to the run in the Langfuse UI
item_resultslist[object]One per dataset item: {output, evaluations, trace_id, dataset_run_id}
run_evaluationslist[object]Run-level evaluations, each {name, value, comment, data_type, metadata}

Example

From moco-examples/moco-agent-evaluation/src/moco-agent-evaluation.yaml:

- activity:
type: langfuse.run_experiment
input_data:
dataset_name: '{{dataset_name}}'
task:
name: moco-agent/moco-agent-cli
evaluators:
- name: is_valid-wfspec
- name: is_runnable-wfspec
- name: llm-judge
output_name: experiment_result
Each dataset item starts a top-level workflow

On the Temporal runtime, every task and evaluator is launched as its own top-level workflow, not as a child of the caller. A large dataset is therefore a large fan-out — set max_concurrency to bound it, and size the worker fleet accordingly.


langfuse.create_score

Attaches one or more scores to the currently running workflow's trace. There are no trace, observation or session fields: the target is always the caller's own trace.

Supply either the single-score shorthand (name / value / …) or a scores list — never both.

data_type must agree with the value: CATEGORICAL and TEXT need a string, NUMERIC and BOOLEAN a number (booleans are stored as 1.0 / 0.0). A mismatch is rejected at parse time.

Input

FieldTypeRequiredDefaultDescription
scoreslist[ScoreInput]nonullSeveral scores at once
namestrnonullScore name — single-score shorthand
valuenumber | str | boolnonullScore value — single-score shorthand
data_typeenumnonullNUMERIC, CATEGORICAL, BOOLEAN or TEXT
commentstrnonullFree-text rationale
metadatadictnonullExtra metadata
config_idstrnonullLangfuse score config to validate against
score_idstrno<trace_id>-<name>Explicit score id
public_keystrnoLANGFUSE_PUBLIC_KEYLangfuse public key
secret_keystrnoLANGFUSE_SECRET_KEYLangfuse secret key
base_urlstrnoLANGFUSE_BASE_URLLangfuse endpoint

ScoreInput

FieldTypeRequiredDefaultDescription
namestryesScore name
valuenumber | str | boolyesScore value
data_typeenumnonullNUMERIC, CATEGORICAL, BOOLEAN or TEXT
commentstrnonullFree-text rationale
metadatadictnonullExtra metadata
config_idstrnonullLangfuse score config
score_idstrno<trace_id>-<name>Explicit score id

Output

FieldTypeDescription
trace_idstrTrace the scores were attached to
scoreslist[object]The scores written, each {score_id, name}

Example

From moco-examples/moco-agent-evaluation/src/online-score.yaml:

- activity:
type: langfuse.create_score
name: record_scores
condition: "{{ len(verdict) > 0 }}"
input_data:
scores:
- name: correctness
value: "{{ float(verdict.get('correctness', 0.0)) }}"
data_type: NUMERIC
comment: "{{ verdict.get('rationale', '') }}"
- name: tone
value: "{{ verdict.get('tone', 'unknown') }}"
data_type: CATEGORICAL
output_name: score_result
Retries upsert rather than duplicate

The score id defaults to <trace_id>-<name>, so a retry overwrites the previous score instead of adding a second one. Override score_id only if you actually want several scores of the same name on one trace.

It needs a trace

langfuse.create_score fails when the run has no trace id. Scores also only land where they can be found if the deployment exports traces to Langfuse.