From dff8d39eba9ee17017e6706d1b2a6476a334dd47 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 11:31:52 +0200 Subject: [PATCH 01/17] docs: add experiment CI/CD integration guide --- .../experiments/experiments-in-ci-cd.mdx | 571 ++++++++++++++++++ .../experiments/experiments-via-sdk.mdx | 230 +------ content/docs/evaluation/experiments/meta.json | 3 +- 3 files changed, 576 insertions(+), 228 deletions(-) create mode 100644 content/docs/evaluation/experiments/experiments-in-ci-cd.mdx diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx new file mode 100644 index 0000000000..117cf64c76 --- /dev/null +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -0,0 +1,571 @@ +--- +title: CI/CD Integration +description: Run Langfuse experiments in CI and gate changes from production. +--- + +# CI/CD Integration + +Use Langfuse experiments in your CI/CD pipeline to catch quality regressions before they ship. + +The workflow is: + +1. Store your test cases in a [Langfuse dataset](/docs/evaluation/experiments/datasets). +2. Write an experiment with the [Python or JS/TS SDK](/docs/evaluation/experiments/experiments-via-sdk) that tests your application against the dataset. +3. Add [evaluators](/docs/evaluation/experiments/experiments-via-sdk#evaluators) to score the experiment results. +4. Raise `RegressionError` when a score violates your threshold. +5. Create a GitHub Actions workflow that runs the script with `langfuse/experiment-action`. + +## GitHub Actions workflow + +Create a workflow with the [trigger](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) you need, for example `pull_request` or `release`. +Pin the action to a release from the [`langfuse/experiment-action` releases](https://github.com/langfuse/experiment-action/releases). + +`RunnerContext` requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS SDK v5.3.0 or newer. The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` if you pin SDK versions. + +```yaml +name: Langfuse experiment gate + +on: + # Run the gate for every pull request. Change this to `push`, `release`, or another + # trigger if you want to run experiments at a different point in your workflow. + pull_request: + +permissions: + # Required to check out the repository. + contents: read + # Required to post or update the experiment result comment on pull requests. + pull-requests: write + # Optional: lets the result link to this specific job's logs. + # Without this permission, the action falls back to the workflow-run URL. + actions: read + +jobs: + experiment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # Required only if you run Python experiments + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + + # Required only if you run TypeScript or JavaScript experiments + - uses: actions/setup-node@v6 + with: + node-version: "24" + + - uses: langfuse/experiment-action@ + with: + # the credentials for Langfuse + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + langfuse_base_url: https://cloud.langfuse.com + + # the location of your experiment scripts + experiment_path: experiments/support-agent-gate + + # the dataset to run the experiment against + dataset_name: support-agent-regression-set + dataset_version: "2026-04-27T00:00:00Z" + + # GitHub token so that the action can comment on PRs + github_token: ${{ github.token }} + + # additional metadata to store with the experiment and show in the Langfuse UI + experiment_metadata: | + gate=pr + suite=support-agent +``` + +### Action inputs and outputs + +| Input | Required | Description | +| ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a GitHub secret. | +| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a GitHub secret. | +| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; change this for self-hosted Langfuse. | +| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | +| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | +| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | +| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | +| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | +| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | +| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | +| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | +| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | +| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. Defaults to `false`. | +| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | + +See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). + +| Output | Description | +| ------------- | ---------------------------------------------------------------------------------- | +| `result_json` | Normalized JSON result for downstream workflow steps. | +| `failed` | `true` if any experiment script errored or raised a regression; otherwise `false`. | + +### Additional secrets + +If your experiment needs provider keys or other secrets, set them as environment variables on the action step. The experiment subprocess inherits the step environment. + +```yaml +- uses: langfuse/experiment-action@ + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + with: + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + experiment_path: experiments/support-agent-gate + dataset_name: support-agent-regression-set +``` + +Your experiment can read these values from `os.environ[...]` in Python or `process.env...` in TypeScript and JavaScript. See the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#how-do-i-pass-extra-secrets-openai-keys-etc-to-my-experiment) for details. + +### Experiment script definition + +Each script must define an `experiment(context)` function. The action creates a `RunnerContext` and passes it to this function. + +`RunnerContext` handles the CI-specific setup for you: + +- initializes the Langfuse SDK client from the action inputs +- loads the dataset items from `dataset_name` and applies `dataset_version` +- adds default metadata under `langfuse.*`, such as commit SHA, branch, job URL, and actor. These values are visible in the Langfuse UI. + +Use `context.runExperiment` (JS/TS) or `context.run_experiment` (Python) to run the experiment with these defaults. + + + +{/* JS/TS */} + +```ts +import type { RunnerContext } from "@langfuse/client"; + +export async function experiment(context: RunnerContext) { + return await context.runExperiment({ + name: "PR gate", + task: async (item) => item.input, + }); +} +``` + + + +{/* Python */} + +```python +from langfuse import RunnerContext + + +def experiment(context: RunnerContext): + return context.run_experiment( + name="PR gate", + task=lambda item, **_: item.input, + ) +``` + + + + +Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as metadata. + +### Failing on regressions + +Raise `RegressionError` when a result should block the workflow. The example below fails when average exact-match accuracy is below the threshold. + + + +{/* JS/TS */} + +```ts +import { + RegressionError, + type Evaluation, + type RunnerContext, +} from "@langfuse/client"; + +const THRESHOLD = 0.95; + +export async function experiment(context: RunnerContext) { + const result = await context.runExperiment({ + name: "PR gate: support agent", + task: answerSupportQuestion, + evaluators: [exactMatch], + runEvaluators: [avgAccuracy], + metadata: { suite: "support-agent" }, + }); + + const accuracy = result.runEvaluations.find( + (evaluation) => evaluation.name === "avg_accuracy", + )?.value; + + if (typeof accuracy !== "number" || accuracy < THRESHOLD) { + throw new RegressionError({ + result, + metric: "avg_accuracy", + value: typeof accuracy === "number" ? accuracy : 0, + threshold: THRESHOLD, + }); + } + + return result; +} + +async function answerSupportQuestion(item: { input?: unknown }) { + const { question } = item.input as { question: string }; + // Replace this stub with your application logic. + return question; +} + +async function exactMatch({ + output, + expectedOutput, +}: { + output: string; + expectedOutput?: string; +}): Promise { + const passed = output.trim() === expectedOutput?.trim(); + + return { + name: "exact_match", + value: passed ? 1 : 0, + comment: passed ? "match" : "mismatch", + }; +} + +async function avgAccuracy({ + itemResults, +}: { + itemResults: Array<{ evaluations: Evaluation[] }>; +}): Promise { + const scores = itemResults + .flatMap((item) => + item.evaluations + .filter((evaluation) => evaluation.name === "exact_match") + .map((evaluation) => Number(evaluation.value)), + ) + .filter((score) => Number.isFinite(score)); + const value = scores.length + ? scores.reduce((sum, score) => sum + score, 0) / scores.length + : 0; + + return { name: "avg_accuracy", value }; +} +``` + + + +{/* Python */} + +```python +from langfuse import Evaluation, RegressionError, RunnerContext + + +THRESHOLD = 0.95 + + +def experiment(context: RunnerContext): + result = context.run_experiment( + name="PR gate: support agent", + task=answer_support_question, + evaluators=[exact_match], + run_evaluators=[avg_accuracy], + metadata={"suite": "support-agent"}, + ) + + accuracy = next( + ( + evaluation.value + for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" + ), + None, + ) + + if not isinstance(accuracy, (int, float)) or accuracy < THRESHOLD: + raise RegressionError( + result=result, + metric="avg_accuracy", + value=float(accuracy) if isinstance(accuracy, (int, float)) else 0.0, + threshold=THRESHOLD, + ) + + return result + + +def answer_support_question(item, **kwargs): + # Replace this stub with your application logic. + return item.input["question"] + + +def exact_match(*, output, expected_output, **kwargs): + passed = output.strip() == (expected_output or "").strip() + return Evaluation( + name="exact_match", + value=1.0 if passed else 0.0, + comment="match" if passed else "mismatch", + ) + + +def avg_accuracy(*, item_results, **kwargs): + scores = [ + evaluation.value + for item in item_results + for evaluation in item.evaluations + if evaluation.name == "exact_match" and isinstance(evaluation.value, (int, float)) + ] + return Evaluation(name="avg_accuracy", value=sum(scores) / len(scores) if scores else 0.0) +``` + + + + +### Action output + +When `github_token` is provided and the workflow has `pull-requests: write`, the action posts or updates a pull request comment with: + +- pass, regression, or script-error status per experiment script +- run-level scores such as `avg_accuracy` +- a link to the GitHub Action run +- a link to the Langfuse experiment comparison view for dataset-backed runs +- a compact table of item outputs and item-level scores + +The same normalized data is available as the `result_json` action output. Use this when a later workflow step needs to upload the result as an artifact, send a Slack notification, or feed another reporting system. The output schema is available in the [`langfuse/experiment-action` repository](https://github.com/langfuse/experiment-action/blob/main/schemas/result-json.v1.schema.json). + +```yaml +- uses: langfuse/experiment-action@ + id: experiment + with: + # ... + +- name: Store experiment result + run: echo '${{ steps.experiment.outputs.result_json }}' > experiment-result.json +``` + +## Other CI/CD systems [#other-cicd-systems] + +Integrate the experiment runner with testing frameworks like Pytest and Vitest to run automated evaluations in your CI pipeline. Use evaluators to create assertions that fail tests based on experiment results. + + + +{/* PYTHON SDK */} + +```python +# test_geography_experiment.py +import pytest +from langfuse import get_client, Evaluation +from langfuse.openai import OpenAI + +# Test data for European capitals +test_data = [ + {"input": "What is the capital of France?", "expected_output": "Paris"}, + {"input": "What is the capital of Germany?", "expected_output": "Berlin"}, + {"input": "What is the capital of Spain?", "expected_output": "Madrid"}, +] + +def geography_task(*, item, **kwargs): + """Task function that answers geography questions""" + question = item["input"] + response = OpenAI().chat.completions.create( + model="gpt-4", + messages=[{"role": "user", "content": question}] + ) + return response.choices[0].message.content + +def accuracy_evaluator(*, input, output, expected_output, **kwargs): + """Evaluator that checks if the expected answer is in the output""" + if expected_output and expected_output.lower() in output.lower(): + return Evaluation(name="accuracy", value=1.0) + + return Evaluation(name="accuracy", value=0.0) + +def average_accuracy_evaluator(*, item_results, **kwargs): + """Run evaluator that calculates average accuracy across all items""" + accuracies = [ + eval.value for result in item_results + for eval in result.evaluations if eval.name == "accuracy" + ] + + if not accuracies: + return Evaluation(name="avg_accuracy", value=None) + + avg = sum(accuracies) / len(accuracies) + + return Evaluation(name="avg_accuracy", value=avg, comment=f"Average accuracy: {avg:.2%}") + +@pytest.fixture +def langfuse_client(): + """Initialize Langfuse client for testing""" + return get_client() + +def test_geography_accuracy_passes(langfuse_client): + """Test that passes when accuracy is above threshold""" + result = langfuse_client.run_experiment( + name="Geography Test - Should Pass", + data=test_data, + task=geography_task, + evaluators=[accuracy_evaluator], + run_evaluators=[average_accuracy_evaluator] + ) + + # Access the run evaluator result directly + avg_accuracy = next( + eval.value for eval in result.run_evaluations + if eval.name == "avg_accuracy" + ) + + # Assert minimum accuracy threshold + assert avg_accuracy >= 0.8, f"Average accuracy {avg_accuracy:.2f} below threshold 0.8" + +def test_geography_accuracy_fails(langfuse_client): + """Example test that demonstrates failure conditions""" + # Use a weaker model or harder questions to demonstrate test failure + def failing_task(*, item, **kwargs): + # Simulate a task that gives wrong answers + return "I don't know" + + result = langfuse_client.run_experiment( + name="Geography Test - Should Fail", + data=test_data, + task=failing_task, + evaluators=[accuracy_evaluator], + run_evaluators=[average_accuracy_evaluator] + ) + + # Access the run evaluator result directly + avg_accuracy = next( + eval.value for eval in result.run_evaluations + if eval.name == "avg_accuracy" + ) + + # This test will fail because the task gives wrong answers + with pytest.raises(AssertionError): + assert avg_accuracy >= 0.8, f"Expected test to fail with low accuracy: {avg_accuracy:.2f}" +``` + + + +{/* JS/TS SDK */} + +```typescript +// test/geography-experiment.test.ts +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { OpenAI } from "openai"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { LangfuseClient, ExperimentItem } from "@langfuse/client"; +import { observeOpenAI } from "@langfuse/openai"; +import { LangfuseSpanProcessor } from "@langfuse/otel"; + +// Test data for European capitals +const testData: ExperimentItem[] = [ + { input: "What is the capital of France?", expectedOutput: "Paris" }, + { input: "What is the capital of Germany?", expectedOutput: "Berlin" }, + { input: "What is the capital of Spain?", expectedOutput: "Madrid" }, +]; + +let otelSdk: NodeSDK; +let langfuse: LangfuseClient; + +beforeAll(async () => { + // Initialize OpenTelemetry + otelSdk = new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }); + otelSdk.start(); + + // Initialize Langfuse client + langfuse = new LangfuseClient(); +}); + +afterAll(async () => { + // Clean shutdown + await otelSdk.shutdown(); +}); + +const geographyTask = async (item: ExperimentItem) => { + const question = item.input; + const response = await observeOpenAI(new OpenAI()).chat.completions.create({ + model: "gpt-4", + messages: [{ role: "user", content: question }], + }); + + return response.choices[0].message.content; +}; + +const accuracyEvaluator = async ({ input, output, expectedOutput }) => { + if ( + expectedOutput && + output.toLowerCase().includes(expectedOutput.toLowerCase()) + ) { + return { name: "accuracy", value: 1 }; + } + return { name: "accuracy", value: 0 }; +}; + +const averageAccuracyEvaluator = async ({ itemResults }) => { + // Calculate average accuracy across all items + const accuracies = itemResults + .flatMap((result) => result.evaluations) + .filter((evaluation) => evaluation.name === "accuracy") + .map((evaluation) => evaluation.value as number); + + if (accuracies.length === 0) { + return { name: "avg_accuracy", value: null }; + } + + const avg = accuracies.reduce((sum, val) => sum + val, 0) / accuracies.length; + return { + name: "avg_accuracy", + value: avg, + comment: `Average accuracy: ${(avg * 100).toFixed(1)}%`, + }; +}; + +describe("Geography Experiment Tests", () => { + it("should pass when accuracy is above threshold", async () => { + const result = await langfuse.experiment.run({ + name: "Geography Test - Should Pass", + data: testData, + task: geographyTask, + evaluators: [accuracyEvaluator], + runEvaluators: [averageAccuracyEvaluator], + }); + + // Access the run evaluator result directly + const avgAccuracy = result.runEvaluations.find( + (eval) => eval.name === "avg_accuracy", + )?.value as number; + + // Assert minimum accuracy threshold + expect(avgAccuracy).toBeGreaterThanOrEqual(0.8); + }, 30_000); // 30 second timeout for API calls + + it("should fail when accuracy is below threshold", async () => { + // Task that gives wrong answers to demonstrate test failure + const failingTask = async (item: ExperimentItem) => { + return "I don't know"; + }; + + const result = await langfuse.experiment.run({ + name: "Geography Test - Should Fail", + data: testData, + task: failingTask, + evaluators: [accuracyEvaluator], + runEvaluators: [averageAccuracyEvaluator], + }); + + // Access the run evaluator result directly + const avgAccuracy = result.runEvaluations.find( + (eval) => eval.name === "avg_accuracy", + )?.value as number; + + // This test will fail because the task gives wrong answers + expect(() => { + expect(avgAccuracy).toBeGreaterThanOrEqual(0.8); + }).toThrow(); + }, 30_000); +}); +``` + + + + +These examples show how to use the experiment runner's evaluation results to create meaningful test assertions in your CI pipeline. Tests can fail when accuracy drops below acceptable thresholds, ensuring model quality standards are maintained automatically. diff --git a/content/docs/evaluation/experiments/experiments-via-sdk.mdx b/content/docs/evaluation/experiments/experiments-via-sdk.mdx index 4501d5fc84..66ffb39455 100644 --- a/content/docs/evaluation/experiments/experiments-via-sdk.mdx +++ b/content/docs/evaluation/experiments/experiments-via-sdk.mdx @@ -213,7 +213,7 @@ Experiments always run on the latest dataset version at experiment time. Support Enhance your experiments with evaluators and advanced configuration options. -#### Evaluators +#### Evaluators [#evaluators] Evaluators assess the quality of task outputs at the item level. They receive the input, metadata, output, and expected output for each item and return evaluation metrics that are reported as scores on the traces in Langfuse. @@ -488,233 +488,9 @@ console.log(await result.format()); -#### Testing in CI Environments +#### Testing in CI Environments [#testing-in-ci-environments] -Integrate the experiment runner with testing frameworks like Pytest and Vitest to run automated evaluations in your CI pipeline. Use evaluators to create assertions that can fail tests based on evaluation results. - - - -{/* PYTHON SDK */} - -```python -# test_geography_experiment.py -import pytest -from langfuse import get_client, Evaluation -from langfuse.openai import OpenAI - -# Test data for European capitals -test_data = [ - {"input": "What is the capital of France?", "expected_output": "Paris"}, - {"input": "What is the capital of Germany?", "expected_output": "Berlin"}, - {"input": "What is the capital of Spain?", "expected_output": "Madrid"}, -] - -def geography_task(*, item, **kwargs): - """Task function that answers geography questions""" - question = item["input"] - response = OpenAI().chat.completions.create( - model="gpt-4", - messages=[{"role": "user", "content": question}] - ) - return response.choices[0].message.content - -def accuracy_evaluator(*, input, output, expected_output, **kwargs): - """Evaluator that checks if the expected answer is in the output""" - if expected_output and expected_output.lower() in output.lower(): - return Evaluation(name="accuracy", value=1.0) - - return Evaluation(name="accuracy", value=0.0) - -def average_accuracy_evaluator(*, item_results, **kwargs): - """Run evaluator that calculates average accuracy across all items""" - accuracies = [ - eval.value for result in item_results - for eval in result.evaluations if eval.name == "accuracy" - ] - - if not accuracies: - return Evaluation(name="avg_accuracy", value=None) - - avg = sum(accuracies) / len(accuracies) - - return Evaluation(name="avg_accuracy", value=avg, comment=f"Average accuracy: {avg:.2%}") - -@pytest.fixture -def langfuse_client(): - """Initialize Langfuse client for testing""" - return get_client() - -def test_geography_accuracy_passes(langfuse_client): - """Test that passes when accuracy is above threshold""" - result = langfuse_client.run_experiment( - name="Geography Test - Should Pass", - data=test_data, - task=geography_task, - evaluators=[accuracy_evaluator], - run_evaluators=[average_accuracy_evaluator] - ) - - # Access the run evaluator result directly - avg_accuracy = next( - eval.value for eval in result.run_evaluations - if eval.name == "avg_accuracy" - ) - - # Assert minimum accuracy threshold - assert avg_accuracy >= 0.8, f"Average accuracy {avg_accuracy:.2f} below threshold 0.8" - -def test_geography_accuracy_fails(langfuse_client): - """Example test that demonstrates failure conditions""" - # Use a weaker model or harder questions to demonstrate test failure - def failing_task(*, item, **kwargs): - # Simulate a task that gives wrong answers - return "I don't know" - - result = langfuse_client.run_experiment( - name="Geography Test - Should Fail", - data=test_data, - task=failing_task, - evaluators=[accuracy_evaluator], - run_evaluators=[average_accuracy_evaluator] - ) - - # Access the run evaluator result directly - avg_accuracy = next( - eval.value for eval in result.run_evaluations - if eval.name == "avg_accuracy" - ) - - # This test will fail because the task gives wrong answers - with pytest.raises(AssertionError): - assert avg_accuracy >= 0.8, f"Expected test to fail with low accuracy: {avg_accuracy:.2f}" -``` - - - -{/* JS/TS SDK */} - -```typescript -// test/geography-experiment.test.ts -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { OpenAI } from "openai"; -import { NodeSDK } from "@opentelemetry/sdk-node"; -import { LangfuseClient, ExperimentItem } from "@langfuse/client"; -import { observeOpenAI } from "@langfuse/openai"; -import { LangfuseSpanProcessor } from "@langfuse/otel"; - -// Test data for European capitals -const testData: ExperimentItem[] = [ - { input: "What is the capital of France?", expectedOutput: "Paris" }, - { input: "What is the capital of Germany?", expectedOutput: "Berlin" }, - { input: "What is the capital of Spain?", expectedOutput: "Madrid" }, -]; - -let otelSdk: NodeSDK; -let langfuse: LangfuseClient; - -beforeAll(async () => { - // Initialize OpenTelemetry - otelSdk = new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }); - otelSdk.start(); - - // Initialize Langfuse client - langfuse = new LangfuseClient(); -}); - -afterAll(async () => { - // Clean shutdown - await otelSdk.shutdown(); -}); - -const geographyTask = async (item: ExperimentItem) => { - const question = item.input; - const response = await observeOpenAI(new OpenAI()).chat.completions.create({ - model: "gpt-4", - messages: [{ role: "user", content: question }], - }); - - return response.choices[0].message.content; -}; - -const accuracyEvaluator = async ({ input, output, expectedOutput }) => { - if ( - expectedOutput && - output.toLowerCase().includes(expectedOutput.toLowerCase()) - ) { - return { name: "accuracy", value: 1 }; - } - return { name: "accuracy", value: 0 }; -}; - -const averageAccuracyEvaluator = async ({ itemResults }) => { - // Calculate average accuracy across all items - const accuracies = itemResults - .flatMap((result) => result.evaluations) - .filter((evaluation) => evaluation.name === "accuracy") - .map((evaluation) => evaluation.value as number); - - if (accuracies.length === 0) { - return { name: "avg_accuracy", value: null }; - } - - const avg = accuracies.reduce((sum, val) => sum + val, 0) / accuracies.length; - return { - name: "avg_accuracy", - value: avg, - comment: `Average accuracy: ${(avg * 100).toFixed(1)}%`, - }; -}; - -describe("Geography Experiment Tests", () => { - it("should pass when accuracy is above threshold", async () => { - const result = await langfuse.experiment.run({ - name: "Geography Test - Should Pass", - data: testData, - task: geographyTask, - evaluators: [accuracyEvaluator], - runEvaluators: [averageAccuracyEvaluator], - }); - - // Access the run evaluator result directly - const avgAccuracy = result.runEvaluations.find( - (eval) => eval.name === "avg_accuracy" - )?.value as number; - - // Assert minimum accuracy threshold - expect(avgAccuracy).toBeGreaterThanOrEqual(0.8); - }, 30_000); // 30 second timeout for API calls - - it("should fail when accuracy is below threshold", async () => { - // Task that gives wrong answers to demonstrate test failure - const failingTask = async (item: ExperimentItem) => { - return "I don't know"; - }; - - const result = await langfuse.experiment.run({ - name: "Geography Test - Should Fail", - data: testData, - task: failingTask, - evaluators: [accuracyEvaluator], - runEvaluators: [averageAccuracyEvaluator], - }); - - // Access the run evaluator result directly - const avgAccuracy = result.runEvaluations.find( - (eval) => eval.name === "avg_accuracy" - )?.value as number; - - // This test will fail because the task gives wrong answers - expect(() => { - expect(avgAccuracy).toBeGreaterThanOrEqual(0.8); - }).toThrow(); - }, 30_000); -}); -``` - - - - -These examples show how to use the experiment runner's evaluation results to create meaningful test assertions in your CI pipeline. Tests can fail when accuracy drops below acceptable thresholds, ensuring model quality standards are maintained automatically. +For CI/CD testing examples with Pytest and Vitest, see [Other CI/CD systems](/docs/evaluation/experiments/experiments-in-ci-cd#other-cicd-systems). For GitHub Actions, see [CI/CD Integration](/docs/evaluation/experiments/experiments-in-ci-cd). ### Autoevals Integration diff --git a/content/docs/evaluation/experiments/meta.json b/content/docs/evaluation/experiments/meta.json index 4c638b8163..155cc773af 100644 --- a/content/docs/evaluation/experiments/meta.json +++ b/content/docs/evaluation/experiments/meta.json @@ -4,6 +4,7 @@ "data-model", "datasets", "experiments-via-sdk", - "experiments-via-ui" + "experiments-via-ui", + "experiments-in-ci-cd" ] } From a2ecfaac0324351129a5d6ed543c7fe1e5569472 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 11:35:47 +0200 Subject: [PATCH 02/17] docs: clarify experiment action SDK versions --- .../docs/evaluation/experiments/experiments-in-ci-cd.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index 117cf64c76..4e789277ec 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -20,7 +20,11 @@ The workflow is: Create a workflow with the [trigger](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) you need, for example `pull_request` or `release`. Pin the action to a release from the [`langfuse/experiment-action` releases](https://github.com/langfuse/experiment-action/releases). -`RunnerContext` requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS SDK v5.3.0 or newer. The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` if you pin SDK versions. +The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` only if you pin SDK versions. + + + `RunnerContext` requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS SDK v5.3.0 or newer. + ```yaml name: Langfuse experiment gate From e29c23834dd1a9753ca5261290927ae8915902c6 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 12:05:13 +0200 Subject: [PATCH 03/17] docs: address experiment CI review feedback --- .../experiments/experiments-in-ci-cd.mdx | 91 ++++++++++--------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index 4e789277ec..ccc2938808 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -23,7 +23,8 @@ Pin the action to a release from the [`langfuse/experiment-action` releases](htt The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` only if you pin SDK versions. - `RunnerContext` requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS SDK v5.3.0 or newer. + The GitHub Action requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS + SDK v5.3.0 or newer. ```yaml @@ -84,22 +85,22 @@ jobs: ### Action inputs and outputs -| Input | Required | Description | -| ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a GitHub secret. | -| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a GitHub secret. | -| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; change this for self-hosted Langfuse. | -| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | -| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | -| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | -| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | -| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | -| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | -| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | -| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | -| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | -| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. Defaults to `false`. | -| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | +| Input | Required | Description | +| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a GitHub secret. | +| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a GitHub secret. | +| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; change this for self-hosted Langfuse. | +| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | +| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | +| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | +| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | +| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | +| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | +| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | +| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | +| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | +| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `tsx` yourself. Defaults to `false`. | +| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). @@ -171,7 +172,7 @@ def experiment(context: RunnerContext): -Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as metadata. +Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as `data` or `metadata`. ### Failing on regressions @@ -196,7 +197,6 @@ export async function experiment(context: RunnerContext) { task: answerSupportQuestion, evaluators: [exactMatch], runEvaluators: [avgAccuracy], - metadata: { suite: "support-agent" }, }); const accuracy = result.runEvaluations.find( @@ -217,7 +217,12 @@ export async function experiment(context: RunnerContext) { async function answerSupportQuestion(item: { input?: unknown }) { const { question } = item.input as { question: string }; - // Replace this stub with your application logic. + + // Replace this with your application logic, for example calling your agent. + return await supportAgent(question); +} + +async function supportAgent(question: string) { return question; } @@ -229,12 +234,7 @@ async function exactMatch({ expectedOutput?: string; }): Promise { const passed = output.trim() === expectedOutput?.trim(); - - return { - name: "exact_match", - value: passed ? 1 : 0, - comment: passed ? "match" : "mismatch", - }; + return { name: "exact_match", value: passed ? 1 : 0 }; } async function avgAccuracy({ @@ -243,17 +243,17 @@ async function avgAccuracy({ itemResults: Array<{ evaluations: Evaluation[] }>; }): Promise { const scores = itemResults - .flatMap((item) => - item.evaluations - .filter((evaluation) => evaluation.name === "exact_match") - .map((evaluation) => Number(evaluation.value)), - ) + .flatMap((item) => item.evaluations) + .filter((evaluation) => evaluation.name === "exact_match") + .map((evaluation) => Number(evaluation.value)) .filter((score) => Number.isFinite(score)); - const value = scores.length - ? scores.reduce((sum, score) => sum + score, 0) / scores.length - : 0; - return { name: "avg_accuracy", value }; + return { + name: "avg_accuracy", + value: scores.length + ? scores.reduce((sum, score) => sum + score, 0) / scores.length + : 0, + }; } ``` @@ -274,7 +274,6 @@ def experiment(context: RunnerContext): task=answer_support_question, evaluators=[exact_match], run_evaluators=[avg_accuracy], - metadata={"suite": "support-agent"}, ) accuracy = next( @@ -343,7 +342,9 @@ The same normalized data is available as the `result_json` action output. Use th # ... - name: Store experiment result - run: echo '${{ steps.experiment.outputs.result_json }}' > experiment-result.json + env: + RESULT_JSON: ${{ steps.experiment.outputs.result_json }} + run: printf '%s' "$RESULT_JSON" > experiment-result.json ``` ## Other CI/CD systems [#other-cicd-systems] @@ -386,8 +387,8 @@ def accuracy_evaluator(*, input, output, expected_output, **kwargs): def average_accuracy_evaluator(*, item_results, **kwargs): """Run evaluator that calculates average accuracy across all items""" accuracies = [ - eval.value for result in item_results - for eval in result.evaluations if eval.name == "accuracy" + evaluation.value for result in item_results + for evaluation in result.evaluations if evaluation.name == "accuracy" ] if not accuracies: @@ -414,8 +415,8 @@ def test_geography_accuracy_passes(langfuse_client): # Access the run evaluator result directly avg_accuracy = next( - eval.value for eval in result.run_evaluations - if eval.name == "avg_accuracy" + evaluation.value for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" ) # Assert minimum accuracy threshold @@ -438,8 +439,8 @@ def test_geography_accuracy_fails(langfuse_client): # Access the run evaluator result directly avg_accuracy = next( - eval.value for eval in result.run_evaluations - if eval.name == "avg_accuracy" + evaluation.value for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" ) # This test will fail because the task gives wrong answers @@ -535,7 +536,7 @@ describe("Geography Experiment Tests", () => { // Access the run evaluator result directly const avgAccuracy = result.runEvaluations.find( - (eval) => eval.name === "avg_accuracy", + (evaluation) => evaluation.name === "avg_accuracy", )?.value as number; // Assert minimum accuracy threshold @@ -558,7 +559,7 @@ describe("Geography Experiment Tests", () => { // Access the run evaluator result directly const avgAccuracy = result.runEvaluations.find( - (eval) => eval.name === "avg_accuracy", + (evaluation) => evaluation.name === "avg_accuracy", )?.value as number; // This test will fail because the task gives wrong answers From 9812eca36886b55c9905870a02b71506f643ac0d Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 12:58:59 +0200 Subject: [PATCH 04/17] docs: handle missing CI run evaluator scores --- .../experiments/experiments-in-ci-cd.mdx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index ccc2938808..3a31383527 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -415,12 +415,18 @@ def test_geography_accuracy_passes(langfuse_client): # Access the run evaluator result directly avg_accuracy = next( - evaluation.value for evaluation in result.run_evaluations - if evaluation.name == "avg_accuracy" + ( + evaluation.value + for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" + ), + None, ) # Assert minimum accuracy threshold - assert avg_accuracy >= 0.8, f"Average accuracy {avg_accuracy:.2f} below threshold 0.8" + assert isinstance(avg_accuracy, (int, float)) and avg_accuracy >= 0.8, ( + f"Average accuracy {avg_accuracy} below threshold 0.8" + ) def test_geography_accuracy_fails(langfuse_client): """Example test that demonstrates failure conditions""" @@ -439,13 +445,19 @@ def test_geography_accuracy_fails(langfuse_client): # Access the run evaluator result directly avg_accuracy = next( - evaluation.value for evaluation in result.run_evaluations - if evaluation.name == "avg_accuracy" + ( + evaluation.value + for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" + ), + None, ) # This test will fail because the task gives wrong answers with pytest.raises(AssertionError): - assert avg_accuracy >= 0.8, f"Expected test to fail with low accuracy: {avg_accuracy:.2f}" + assert isinstance(avg_accuracy, (int, float)) and avg_accuracy >= 0.8, ( + f"Expected test to fail with low accuracy: {avg_accuracy}" + ) ``` From 70ab3722f8a3b494e1d941b12e65fc3d0e39104e Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 14:19:53 +0200 Subject: [PATCH 05/17] docs: refine experiment CI guide wording --- .../experiments/experiments-in-ci-cd.mdx | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index 3a31383527..08bc9a5c0a 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -9,22 +9,21 @@ Use Langfuse experiments in your CI/CD pipeline to catch quality regressions bef The workflow is: -1. Store your test cases in a [Langfuse dataset](/docs/evaluation/experiments/datasets). +1. Create a [Langfuse dataset](/docs/evaluation/experiments/datasets) with your test cases. 2. Write an experiment with the [Python or JS/TS SDK](/docs/evaluation/experiments/experiments-via-sdk) that tests your application against the dataset. -3. Add [evaluators](/docs/evaluation/experiments/experiments-via-sdk#evaluators) to score the experiment results. +3. Add [evaluators](/docs/evaluation/experiments/experiments-via-sdk#evaluators) to score task outputs. 4. Raise `RegressionError` when a score violates your threshold. -5. Create a GitHub Actions workflow that runs the script with `langfuse/experiment-action`. +5. Create a GitHub Actions workflow that runs the script with [langfuse/experiment-action](https://github.com/langfuse/experiment-action). ## GitHub Actions workflow Create a workflow with the [trigger](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) you need, for example `pull_request` or `release`. Pin the action to a release from the [`langfuse/experiment-action` releases](https://github.com/langfuse/experiment-action/releases). -The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` only if you pin SDK versions. +The action installs the latest SDK version by default; set `python_sdk_version` or `js_sdk_version` only if you want a specific SDK version. - The GitHub Action requires Langfuse Python SDK v4.6.0 or newer, or Langfuse JS - SDK v5.3.0 or newer. + The GitHub Action requires Langfuse Python SDK v4.6.0+ or JS SDK v5.3.0+. ```yaml @@ -50,12 +49,12 @@ jobs: steps: - uses: actions/checkout@v6 - # Required only if you run Python experiments + # Add this only if your experiments use the Python SDK - uses: actions/setup-python@v6 with: python-version: "3.14" - # Required only if you run TypeScript or JavaScript experiments + # Add this only if your experiments use the JS/TS SDK - uses: actions/setup-node@v6 with: node-version: "24" @@ -72,35 +71,29 @@ jobs: # the dataset to run the experiment against dataset_name: support-agent-regression-set - dataset_version: "2026-04-27T00:00:00Z" # GitHub token so that the action can comment on PRs github_token: ${{ github.token }} - - # additional metadata to store with the experiment and show in the Langfuse UI - experiment_metadata: | - gate=pr - suite=support-agent ``` ### Action inputs and outputs -| Input | Required | Description | -| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a GitHub secret. | -| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a GitHub secret. | -| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; change this for self-hosted Langfuse. | -| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | -| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | -| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | -| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | -| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | -| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | -| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | -| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | -| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | -| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `tsx` yourself. Defaults to `false`. | -| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | +| Input | Required | Description | +| ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; see [regions and self-hosted URLs](/docs/api-and-data-platform/features/public-api#public-api) if you use another Langfuse instance. | +| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | +| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | +| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | +| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | +| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | +| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | +| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | +| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | +| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | +| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `tsx` yourself. Defaults to `false`. | +| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). @@ -144,12 +137,17 @@ Use `context.runExperiment` (JS/TS) or `context.run_experiment` (Python) to run {/* JS/TS */} ```ts -import type { RunnerContext } from "@langfuse/client"; +import type { ExperimentItem, RunnerContext } from "@langfuse/client"; + +// Define a task that calls your agent with each dataset item. +async function myTask(item: ExperimentItem) { + // ... +} export async function experiment(context: RunnerContext) { return await context.runExperiment({ name: "PR gate", - task: async (item) => item.input, + task: myTask, }); } ``` @@ -160,12 +158,18 @@ export async function experiment(context: RunnerContext) { ```python from langfuse import RunnerContext +from langfuse.api import DatasetItem + + +# Define a task that calls your agent with each dataset item. +def my_task(item: DatasetItem, **kwargs): + ... def experiment(context: RunnerContext): return context.run_experiment( name="PR gate", - task=lambda item, **_: item.input, + task=my_task, ) ``` @@ -205,6 +209,7 @@ export async function experiment(context: RunnerContext) { if (typeof accuracy !== "number" || accuracy < THRESHOLD) { throw new RegressionError({ + // Attach the result so the action can include scores in the PR comment and `result_json` output. result, metric: "avg_accuracy", value: typeof accuracy === "number" ? accuracy : 0, @@ -287,6 +292,7 @@ def experiment(context: RunnerContext): if not isinstance(accuracy, (int, float)) or accuracy < THRESHOLD: raise RegressionError( + # Attach the result so the action can include scores in the PR comment and `result_json` output. result=result, metric="avg_accuracy", value=float(accuracy) if isinstance(accuracy, (int, float)) else 0.0, From ee1977278c163a9442063e3986acd68d66d2eda3 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 15:12:59 +0200 Subject: [PATCH 06/17] docs: update experiment action contract details --- .../experiments/experiments-in-ci-cd.mdx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index 08bc9a5c0a..665cd93a18 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -78,22 +78,22 @@ jobs: ### Action inputs and outputs -| Input | Required | Description | -| ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | -| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | -| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; see [regions and self-hosted URLs](/docs/api-and-data-platform/features/public-api#public-api) if you use another Langfuse instance. | -| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | -| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | -| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | -| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | -| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | -| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | -| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | -| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | -| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | -| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `tsx` yourself. Defaults to `false`. | -| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | +| Input | Required | Description | +| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; see [regions and self-hosted URLs](/docs/api-and-data-platform/features/public-api#public-api) if you use another Langfuse instance. | +| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | +| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | +| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | +| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | +| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | +| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | +| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | +| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | +| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | +| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `@langfuse/client`, `@langfuse/tracing`, `@langfuse/otel`, `@opentelemetry/sdk-node`, and `tsx` yourself. Defaults to `false`. | +| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). @@ -122,7 +122,7 @@ Your experiment can read these values from `os.environ[...]` in Python or `proce ### Experiment script definition -Each script must define an `experiment(context)` function. The action creates a `RunnerContext` and passes it to this function. +Each script must define an `experiment(context)` function that accepts a `context` parameter. The action creates a `RunnerContext` and passes it to this function. `RunnerContext` handles the CI-specific setup for you: @@ -137,10 +137,10 @@ Use `context.runExperiment` (JS/TS) or `context.run_experiment` (Python) to run {/* JS/TS */} ```ts -import type { ExperimentItem, RunnerContext } from "@langfuse/client"; +import type { ExperimentTaskParams, RunnerContext } from "@langfuse/client"; // Define a task that calls your agent with each dataset item. -async function myTask(item: ExperimentItem) { +async function myTask(item: ExperimentTaskParams) { // ... } @@ -190,6 +190,7 @@ Raise `RegressionError` when a result should block the workflow. The example bel import { RegressionError, type Evaluation, + type ExperimentTaskParams, type RunnerContext, } from "@langfuse/client"; @@ -220,7 +221,7 @@ export async function experiment(context: RunnerContext) { return result; } -async function answerSupportQuestion(item: { input?: unknown }) { +async function answerSupportQuestion(item: ExperimentTaskParams) { const { question } = item.input as { question: string }; // Replace this with your application logic, for example calling your agent. From f29c4f2f77f6a47d9d0139402e87dd012ce28d1d Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Mon, 4 May 2026 15:26:03 +0200 Subject: [PATCH 07/17] docs: improve experiment CI action examples --- .../experiments/experiments-in-ci-cd.mdx | 167 +++++++++--------- 1 file changed, 84 insertions(+), 83 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx index 665cd93a18..5872714738 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx @@ -132,9 +132,30 @@ Each script must define an `experiment(context)` function that accepts a `contex Use `context.runExperiment` (JS/TS) or `context.run_experiment` (Python) to run the experiment with these defaults. - + + +{/* Python SDK */} + +```python +from langfuse import RunnerContext +from langfuse.api import DatasetItem + + +# Define a task that calls your agent with each dataset item. +def my_task(item: DatasetItem, **kwargs): + ... + + +def experiment(context: RunnerContext): + return context.run_experiment( + name="PR gate", + task=my_task, + ) +``` + + -{/* JS/TS */} +{/* JS/TS SDK */} ```ts import type { ExperimentTaskParams, RunnerContext } from "@langfuse/client"; @@ -153,38 +174,81 @@ export async function experiment(context: RunnerContext) { ``` + + +Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as `data` or `metadata`. + +### Failing on regressions + +Raise `RegressionError` when a result should block the workflow. The example below fails when average exact-match accuracy is below the threshold. + + -{/* Python */} +{/* Python SDK */} ```python -from langfuse import RunnerContext -from langfuse.api import DatasetItem +from langfuse import Evaluation, RegressionError, RunnerContext -# Define a task that calls your agent with each dataset item. -def my_task(item: DatasetItem, **kwargs): - ... +THRESHOLD = 0.95 def experiment(context: RunnerContext): - return context.run_experiment( - name="PR gate", - task=my_task, + result = context.run_experiment( + name="PR gate: support agent", + task=answer_support_question, + evaluators=[exact_match], + run_evaluators=[avg_accuracy], ) -``` - - + accuracy = next( + ( + evaluation.value + for evaluation in result.run_evaluations + if evaluation.name == "avg_accuracy" + ), + None, + ) -Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as `data` or `metadata`. + if not isinstance(accuracy, (int, float)) or accuracy < THRESHOLD: + raise RegressionError( + # Attach the result so the action can include scores in the PR comment and `result_json` output. + result=result, + metric="avg_accuracy", + value=float(accuracy) if isinstance(accuracy, (int, float)) else 0.0, + threshold=THRESHOLD, + ) -### Failing on regressions + return result -Raise `RegressionError` when a result should block the workflow. The example below fails when average exact-match accuracy is below the threshold. - +def answer_support_question(item, **kwargs): + # Replace this stub with your application logic. + return item.input["question"] + + +def exact_match(*, output, expected_output, **kwargs): + passed = output.strip() == (expected_output or "").strip() + return Evaluation( + name="exact_match", + value=1.0 if passed else 0.0, + comment="match" if passed else "mismatch", + ) + + +def avg_accuracy(*, item_results, **kwargs): + scores = [ + evaluation.value + for item in item_results + for evaluation in item.evaluations + if evaluation.name == "exact_match" and isinstance(evaluation.value, (int, float)) + ] + return Evaluation(name="avg_accuracy", value=sum(scores) / len(scores) if scores else 0.0) +``` + + -{/* JS/TS */} +{/* JS/TS SDK */} ```ts import { @@ -263,70 +327,6 @@ async function avgAccuracy({ } ``` - - -{/* Python */} - -```python -from langfuse import Evaluation, RegressionError, RunnerContext - - -THRESHOLD = 0.95 - - -def experiment(context: RunnerContext): - result = context.run_experiment( - name="PR gate: support agent", - task=answer_support_question, - evaluators=[exact_match], - run_evaluators=[avg_accuracy], - ) - - accuracy = next( - ( - evaluation.value - for evaluation in result.run_evaluations - if evaluation.name == "avg_accuracy" - ), - None, - ) - - if not isinstance(accuracy, (int, float)) or accuracy < THRESHOLD: - raise RegressionError( - # Attach the result so the action can include scores in the PR comment and `result_json` output. - result=result, - metric="avg_accuracy", - value=float(accuracy) if isinstance(accuracy, (int, float)) else 0.0, - threshold=THRESHOLD, - ) - - return result - - -def answer_support_question(item, **kwargs): - # Replace this stub with your application logic. - return item.input["question"] - - -def exact_match(*, output, expected_output, **kwargs): - passed = output.strip() == (expected_output or "").strip() - return Evaluation( - name="exact_match", - value=1.0 if passed else 0.0, - comment="match" if passed else "mismatch", - ) - - -def avg_accuracy(*, item_results, **kwargs): - scores = [ - evaluation.value - for item in item_results - for evaluation in item.evaluations - if evaluation.name == "exact_match" and isinstance(evaluation.value, (int, float)) - ] - return Evaluation(name="avg_accuracy", value=sum(scores) / len(scores) if scores else 0.0) -``` - @@ -349,6 +349,7 @@ The same normalized data is available as the `result_json` action output. Use th # ... - name: Store experiment result + if: always() env: RESULT_JSON: ${{ steps.experiment.outputs.result_json }} run: printf '%s' "$RESULT_JSON" > experiment-result.json From e6403f15bd671da8de3e6853abe13d4bc14657e1 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 5 May 2026 11:10:51 +0200 Subject: [PATCH 08/17] docs: add experiment CI changelog --- .../2026-05-05-experiment-ci-cd-gates.mdx | 54 ++++++++++++++++++ data/authors.json | 9 +++ public/images/people/tobiaswochinger.jpg | Bin 0 -> 40409 bytes 3 files changed, 63 insertions(+) create mode 100644 content/changelog/2026-05-05-experiment-ci-cd-gates.mdx create mode 100644 public/images/people/tobiaswochinger.jpg diff --git a/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx new file mode 100644 index 0000000000..c4f11c2386 --- /dev/null +++ b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx @@ -0,0 +1,54 @@ +--- +date: 2026-05-05 +title: "Experiments CI/CD integration" +description: Run Langfuse experiments in CI/CD with the GitHub Action, block regressions with RegressionError, and use normalized results in downstream workflow steps. +author: Tobias Wochinger +canonical: /docs/evaluation/experiments/experiments-in-ci-cd +--- + +import { ChangelogHeader } from "@/components/changelog/ChangelogHeader"; + + + +You can now run Langfuse experiments directly in CI/CD and gate changes before they ship. The new `langfuse/experiment-action` runs Python, TypeScript, or JavaScript experiment scripts in GitHub Actions, loads Langfuse datasets for reproducible runs, and reports results back to the pull request. + +Use it to block a PR when an agent's exact-match accuracy drops below a threshold, run a release gate against a versioned dataset, or upload experiment results as workflow artifacts for Slack notifications and reporting. + +## GitHub Actions [#github-actions] + +The action creates a `RunnerContext` for each experiment script. Your script defines `experiment(context)`, then calls `context.run_experiment(...)` in Python or `context.runExperiment(...)` in JS/TS. Langfuse credentials, dataset name, dataset version, GitHub metadata, and PR comment handling are configured in the workflow. + +```yaml +- uses: langfuse/experiment-action@ + with: + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + langfuse_base_url: https://cloud.langfuse.com + experiment_path: experiments/support-agent-gate + dataset_name: support-agent-regression-set + dataset_version: "2026-04-27T00:00:00Z" + github_token: ${{ github.token }} +``` + +Raise `RegressionError` from the experiment script when a score should fail the workflow. The action can also expose `result_json` for later steps, for example to store an artifact or send a Slack notification. + +## Get started [#get-started] + +Follow the CI/CD integration guide to add the GitHub Action workflow, write an experiment script, and configure regression thresholds for your pipeline. + +import { FileCode } from "lucide-react"; + + + } + arrow + /> + } + arrow + /> + diff --git a/data/authors.json b/data/authors.json index 6dd85450b4..78b710d400 100644 --- a/data/authors.json +++ b/data/authors.json @@ -35,6 +35,15 @@ "github": "hassiebp", "linkedin": "hassieb" }, + "tobiaswochinger": { + "firstName": "Tobias", + "name": "Tobias Wochinger", + "title": "Product Engineer", + "image": "/images/people/tobiaswochinger.jpg", + "twitter": "wochinge", + "github": "wochinge", + "linkedin": "tobias-wochinger" + }, "marliesmayerhofer": { "firstName": "Marlies", "name": "Marlies Mayerhofer", diff --git a/public/images/people/tobiaswochinger.jpg b/public/images/people/tobiaswochinger.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c7feb7d762d7e5a09902f85663956dc744b2a124 GIT binary patch literal 40409 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 literal 0 HcmV?d00001 From 47d905233e2b8660866dd1f2a164c6f5a040617f Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Tue, 5 May 2026 12:28:03 +0200 Subject: [PATCH 09/17] docs: refine experiment CI changelog --- .../2026-05-05-experiment-ci-cd-gates.mdx | 17 +++++++++-------- .../changelog/2026-05-05-experiment-ci-cd.png | Bin 0 -> 101322 bytes 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 public/images/changelog/2026-05-05-experiment-ci-cd.png diff --git a/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx index c4f11c2386..98f435b77a 100644 --- a/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx +++ b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx @@ -1,8 +1,9 @@ --- date: 2026-05-05 title: "Experiments CI/CD integration" -description: Run Langfuse experiments in CI/CD with the GitHub Action, block regressions with RegressionError, and use normalized results in downstream workflow steps. +description: Run langfuse experiments in GitHub Actions to catch quality regressions before releasing changes to production. author: Tobias Wochinger +ogImage: /images/changelog/2026-05-05-experiment-ci-cd.png canonical: /docs/evaluation/experiments/experiments-in-ci-cd --- @@ -10,16 +11,16 @@ import { ChangelogHeader } from "@/components/changelog/ChangelogHeader"; -You can now run Langfuse experiments directly in CI/CD and gate changes before they ship. The new `langfuse/experiment-action` runs Python, TypeScript, or JavaScript experiment scripts in GitHub Actions, loads Langfuse datasets for reproducible runs, and reports results back to the pull request. +You can now run langfuse experiments in GitHub Actions and catch quality regressions before they ship. The new [langfuse/experiment-action](https://github.com/langfuse/experiment-action) tests your application against a langfuse dataset, reports the result directly on the pull request, and tracks the experiment run in langfuse. -Use it to block a PR when an agent's exact-match accuracy drops below a threshold, run a release gate against a versioned dataset, or upload experiment results as workflow artifacts for Slack notifications and reporting. +Use it to block a PR when an agent's accuracy drops below a threshold, run a release gate against a versioned dataset, or make experiment results part of your existing CI checks. ## GitHub Actions [#github-actions] -The action creates a `RunnerContext` for each experiment script. Your script defines `experiment(context)`, then calls `context.run_experiment(...)` in Python or `context.runExperiment(...)` in JS/TS. Langfuse credentials, dataset name, dataset version, GitHub metadata, and PR comment handling are configured in the workflow. +Add the action to your workflow, point it at an experiment script, and choose the dataset that should be used for the gate. The pull request shows whether the experiment passed, regressed, or failed to run. ```yaml -- uses: langfuse/experiment-action@ +- uses: langfuse/experiment-action@v1.0.0 with: langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} @@ -30,11 +31,11 @@ The action creates a `RunnerContext` for each experiment script. Your script def github_token: ${{ github.token }} ``` -Raise `RegressionError` from the experiment script when a score should fail the workflow. The action can also expose `result_json` for later steps, for example to store an artifact or send a Slack notification. +When an experiment score misses your threshold, the workflow fails so reviewers can see the regression before merging. ## Get started [#get-started] -Follow the CI/CD integration guide to add the GitHub Action workflow, write an experiment script, and configure regression thresholds for your pipeline. +Follow the CI/CD integration guide to add the workflow, write your experiment script, and configure the thresholds to protect your production use case. import { FileCode } from "lucide-react"; @@ -46,7 +47,7 @@ import { FileCode } from "lucide-react"; arrow /> } arrow diff --git a/public/images/changelog/2026-05-05-experiment-ci-cd.png b/public/images/changelog/2026-05-05-experiment-ci-cd.png new file mode 100644 index 0000000000000000000000000000000000000000..236db137f4f54078be5b65ceb443ad1f86ec3110 GIT binary patch literal 101322 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%0AniP>sieUiFYhP5`*9>m(!t~ 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

b1gz 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

N!Qu@nR9n0;#En3 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 zye Date: Tue, 5 May 2026 17:15:31 +0200 Subject: [PATCH 10/17] docs: remove duplicate changelog author --- data/authors.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/data/authors.json b/data/authors.json index 0e6ad3d86f..9073bf8bfc 100644 --- a/data/authors.json +++ b/data/authors.json @@ -35,15 +35,6 @@ "github": "hassiebp", "linkedin": "hassieb" }, - "tobiaswochinger": { - "firstName": "Tobias", - "name": "Tobias Wochinger", - "title": "Product Engineer", - "image": "/images/people/tobiaswochinger.jpg", - "twitter": "wochinge", - "github": "wochinge", - "linkedin": "tobias-wochinger" - }, "marliesmayerhofer": { "firstName": "Marlies", "name": "Marlies Mayerhofer", From f46f939ecb2e6a3e13fa05fe41d2a1118032fcba Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 6 May 2026 11:55:21 +0200 Subject: [PATCH 11/17] docs: move experiment ci guide --- .../2026-05-05-experiment-ci-cd-gates.mdx | 6 +-- .../experiments/experiments-via-sdk.mdx | 54 +++++++++++++++++-- content/docs/evaluation/experiments/meta.json | 3 +- .../experiments-ci-cd.mdx} | 6 +-- content/guides/index.mdx | 6 +++ content/guides/meta.json | 1 + 6 files changed, 64 insertions(+), 12 deletions(-) rename content/{docs/evaluation/experiments/experiments-in-ci-cd.mdx => guides/experiments-ci-cd.mdx} (99%) diff --git a/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx index 98f435b77a..ab354c23fa 100644 --- a/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx +++ b/content/changelog/2026-05-05-experiment-ci-cd-gates.mdx @@ -4,7 +4,7 @@ title: "Experiments CI/CD integration" description: Run langfuse experiments in GitHub Actions to catch quality regressions before releasing changes to production. author: Tobias Wochinger ogImage: /images/changelog/2026-05-05-experiment-ci-cd.png -canonical: /docs/evaluation/experiments/experiments-in-ci-cd +canonical: /guides/experiments-ci-cd --- import { ChangelogHeader } from "@/components/changelog/ChangelogHeader"; @@ -41,8 +41,8 @@ import { FileCode } from "lucide-react"; } arrow /> diff --git a/content/docs/evaluation/experiments/experiments-via-sdk.mdx b/content/docs/evaluation/experiments/experiments-via-sdk.mdx index 66ffb39455..587e9bf774 100644 --- a/content/docs/evaluation/experiments/experiments-via-sdk.mdx +++ b/content/docs/evaluation/experiments/experiments-via-sdk.mdx @@ -488,10 +488,6 @@ console.log(await result.format()); -#### Testing in CI Environments [#testing-in-ci-environments] - -For CI/CD testing examples with Pytest and Vitest, see [Other CI/CD systems](/docs/evaluation/experiments/experiments-in-ci-cd#other-cicd-systems). For GitHub Actions, see [CI/CD Integration](/docs/evaluation/experiments/experiments-in-ci-cd). - ### Autoevals Integration Access pre-built evaluation functions through the [autoevals library](https://github.com/braintrustdata/autoevals) integration. @@ -555,6 +551,56 @@ console.log(await result.format()); +## Run experiments in CI/CD [#testing-in-ci-environments] + +Use Langfuse experiments in your CI/CD pipeline to catch quality regressions before they ship. A typical workflow creates a dataset with regression test cases, runs your application against the dataset with the SDK, scores the outputs with evaluators, and fails the workflow when a score violates your threshold. + + + Follow the [CI/CD guide](/guides/experiments-ci-cd) to add experiment checks + to your release process and block changes that reduce quality before they + reach production. + + +For GitHub Actions, use [`langfuse/experiment-action`](https://github.com/langfuse/experiment-action) to run an experiment script and report the result on the pull request. + +```yaml +name: Langfuse experiment gate + +on: + pull_request: + +permissions: + # Required to check out the repository. + contents: read + # Required to post or update the experiment result comment on pull requests. + pull-requests: write + +jobs: + experiment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # Add this only if your experiments use the Python SDK. + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + + # Add this only if your experiments use the JS/TS SDK. + - uses: actions/setup-node@v6 + with: + node-version: "24" + + - uses: langfuse/experiment-action@ + with: + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + langfuse_base_url: https://cloud.langfuse.com + experiment_path: experiments/support-agent-gate + dataset_name: support-agent-regression-set + github_token: ${{ github.token }} +``` + ## Low-level SDK methods If you need more control over the dataset run, you can use the low-level SDK methods in order to loop through the dataset items and execute your application logic. diff --git a/content/docs/evaluation/experiments/meta.json b/content/docs/evaluation/experiments/meta.json index 155cc773af..4c638b8163 100644 --- a/content/docs/evaluation/experiments/meta.json +++ b/content/docs/evaluation/experiments/meta.json @@ -4,7 +4,6 @@ "data-model", "datasets", "experiments-via-sdk", - "experiments-via-ui", - "experiments-in-ci-cd" + "experiments-via-ui" ] } diff --git a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx b/content/guides/experiments-ci-cd.mdx similarity index 99% rename from content/docs/evaluation/experiments/experiments-in-ci-cd.mdx rename to content/guides/experiments-ci-cd.mdx index 5872714738..6e079e277f 100644 --- a/content/docs/evaluation/experiments/experiments-in-ci-cd.mdx +++ b/content/guides/experiments-ci-cd.mdx @@ -1,9 +1,9 @@ --- -title: CI/CD Integration -description: Run Langfuse experiments in CI and gate changes from production. +title: Run Experiments in CI/CD +description: Run Langfuse experiments in CI and gate changes before production. --- -# CI/CD Integration +# Run Experiments in CI/CD Use Langfuse experiments in your CI/CD pipeline to catch quality regressions before they ship. diff --git a/content/guides/index.mdx b/content/guides/index.mdx index 292ff66613..4a95950774 100644 --- a/content/guides/index.mdx +++ b/content/guides/index.mdx @@ -52,6 +52,12 @@ import { href="/blog/2025-10-21-testing-llm-applications" icon={} /> + } + /> Date: Wed, 6 May 2026 12:10:01 +0200 Subject: [PATCH 12/17] docs: fix ci testing section title --- content/docs/evaluation/experiments/experiments-via-sdk.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/evaluation/experiments/experiments-via-sdk.mdx b/content/docs/evaluation/experiments/experiments-via-sdk.mdx index 587e9bf774..9b96a7b192 100644 --- a/content/docs/evaluation/experiments/experiments-via-sdk.mdx +++ b/content/docs/evaluation/experiments/experiments-via-sdk.mdx @@ -551,7 +551,7 @@ console.log(await result.format()); -## Run experiments in CI/CD [#testing-in-ci-environments] +## Testing in CI Environments [#testing-in-ci-environments] Use Langfuse experiments in your CI/CD pipeline to catch quality regressions before they ship. A typical workflow creates a dataset with regression test cases, runs your application against the dataset with the SDK, scores the outputs with evaluators, and fails the workflow when a score violates your threshold. From 39ec823e0b12c0e795ab02732d343def59ccedf7 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 6 May 2026 12:10:46 +0200 Subject: [PATCH 13/17] docs: link experiment action in ci callout --- content/docs/evaluation/experiments/experiments-via-sdk.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/docs/evaluation/experiments/experiments-via-sdk.mdx b/content/docs/evaluation/experiments/experiments-via-sdk.mdx index 9b96a7b192..a760b769a0 100644 --- a/content/docs/evaluation/experiments/experiments-via-sdk.mdx +++ b/content/docs/evaluation/experiments/experiments-via-sdk.mdx @@ -558,7 +558,8 @@ Use Langfuse experiments in your CI/CD pipeline to catch quality regressions bef Follow the [CI/CD guide](/guides/experiments-ci-cd) to add experiment checks to your release process and block changes that reduce quality before they - reach production. + reach production. For GitHub Actions, use + [langfuse/experiment-action](https://github.com/langfuse/experiment-action). For GitHub Actions, use [`langfuse/experiment-action`](https://github.com/langfuse/experiment-action) to run an experiment script and report the result on the pull request. From cb5b231bd07e4de4767aa1d4a9abb299ef0b989f Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 6 May 2026 12:11:50 +0200 Subject: [PATCH 14/17] Revert "docs: link experiment action in ci callout" This reverts commit 39ec823e0b12c0e795ab02732d343def59ccedf7. --- content/docs/evaluation/experiments/experiments-via-sdk.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/docs/evaluation/experiments/experiments-via-sdk.mdx b/content/docs/evaluation/experiments/experiments-via-sdk.mdx index a760b769a0..9b96a7b192 100644 --- a/content/docs/evaluation/experiments/experiments-via-sdk.mdx +++ b/content/docs/evaluation/experiments/experiments-via-sdk.mdx @@ -558,8 +558,7 @@ Use Langfuse experiments in your CI/CD pipeline to catch quality regressions bef Follow the [CI/CD guide](/guides/experiments-ci-cd) to add experiment checks to your release process and block changes that reduce quality before they - reach production. For GitHub Actions, use - [langfuse/experiment-action](https://github.com/langfuse/experiment-action). + reach production. For GitHub Actions, use [`langfuse/experiment-action`](https://github.com/langfuse/experiment-action) to run an experiment script and report the result on the pull request. From e0e6d3da81c5e564374431c50cccc7fda29f341b Mon Sep 17 00:00:00 2001 From: Lotte Verheyden <48100308+Lotte-Verheyden@users.noreply.github.com> Date: Wed, 6 May 2026 08:31:10 -0700 Subject: [PATCH 15/17] fix: hide experiments-ci-cd from guides sidebar --- content/guides/meta.json | 1 - 1 file changed, 1 deletion(-) diff --git a/content/guides/meta.json b/content/guides/meta.json index 9c095de784..c6d47f70d8 100644 --- a/content/guides/meta.json +++ b/content/guides/meta.json @@ -3,7 +3,6 @@ "pages": [ "import", "index", - "experiments-ci-cd", "cookbook", "videos" ] From 2ed92d131a737f4a47ddfed9b117713c3262f007 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 6 May 2026 18:21:36 +0200 Subject: [PATCH 16/17] docs: clarify ci experiment definition --- content/guides/experiments-ci-cd.mdx | 94 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/content/guides/experiments-ci-cd.mdx b/content/guides/experiments-ci-cd.mdx index 6e079e277f..6be6d8d83a 100644 --- a/content/guides/experiments-ci-cd.mdx +++ b/content/guides/experiments-ci-cd.mdx @@ -76,55 +76,13 @@ jobs: github_token: ${{ github.token }} ``` -### Action inputs and outputs +### Experiment Definition -| Input | Required | Description | -| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | -| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | -| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; see [regions and self-hosted URLs](/docs/api-and-data-platform/features/public-api#public-api) if you use another Langfuse instance. | -| `experiment_path` | Yes | Path to an experiment script, directory, or glob pattern. Supports Python, TypeScript, and JavaScript. | -| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | -| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | -| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | -| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | -| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | -| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | -| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | -| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | -| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `@langfuse/client`, `@langfuse/tracing`, `@langfuse/otel`, `@opentelemetry/sdk-node`, and `tsx` yourself. Defaults to `false`. | -| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | +The action runs your experiment code from the `experiment_path` configured in the workflow. -See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). +Each script must define an `experiment(context)` function that accepts a `context` parameter. -| Output | Description | -| ------------- | ---------------------------------------------------------------------------------- | -| `result_json` | Normalized JSON result for downstream workflow steps. | -| `failed` | `true` if any experiment script errored or raised a regression; otherwise `false`. | - -### Additional secrets - -If your experiment needs provider keys or other secrets, set them as environment variables on the action step. The experiment subprocess inherits the step environment. - -```yaml -- uses: langfuse/experiment-action@ - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - with: - langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} - langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} - experiment_path: experiments/support-agent-gate - dataset_name: support-agent-regression-set -``` - -Your experiment can read these values from `os.environ[...]` in Python or `process.env...` in TypeScript and JavaScript. See the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#how-do-i-pass-extra-secrets-openai-keys-etc-to-my-experiment) for details. - -### Experiment script definition - -Each script must define an `experiment(context)` function that accepts a `context` parameter. The action creates a `RunnerContext` and passes it to this function. - -`RunnerContext` handles the CI-specific setup for you: +This context is created by the GitHub Action and handles the CI-specific setup for you: - initializes the Langfuse SDK client from the action inputs - loads the dataset items from `dataset_name` and applies `dataset_version` @@ -178,6 +136,50 @@ export async function experiment(context: RunnerContext) { Pass explicit values to `context.runExperiment` / `context.run_experiment` when you want to override action-provided defaults such as `data` or `metadata`. +### Action inputs and outputs + +| Input | Required | Description | +| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `langfuse_public_key` | Yes | Langfuse public key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_secret_key` | Yes | Langfuse secret key used by the SDK client. Store it as a [GitHub secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets). | +| `langfuse_base_url` | No | Langfuse host. Defaults to `https://cloud.langfuse.com`; see [regions and self-hosted URLs](/docs/api-and-data-platform/features/public-api#public-api) if you use another Langfuse instance. | +| `experiment_path` | Yes | Path to an experiment script file, a directory containing experiment scripts, or a glob pattern. Supports Python, TypeScript, and JavaScript. | +| `dataset_name` | No | Langfuse dataset loaded by the action and provided to the SDK via `RunnerContext`. If omitted, the script must provide its own data. | +| `dataset_version` | No | Optional timestamp to pin the dataset version for reproducible CI runs. Defaults to the latest dataset version. | +| `experiment_metadata` | No | Additional `key=value` metadata added to the experiment together with default GitHub metadata. This metadata is visible in the Langfuse UI. | +| `should_fail_on_regression` | No | Fail the CI job when an experiment raises `RegressionError`. Defaults to `true`. | +| `should_fail_on_script_error` | No | Fail the CI job when an experiment script crashes or raises a non-regression error. Defaults to `true`. | +| `should_comment_on_pr` | No | Post or update the experiment report as a pull request comment. Defaults to `true`. | +| `python_sdk_version` | No | Langfuse Python SDK version installed by the action for `.py` experiments. Defaults to `latest`; use v4.6.0 or newer. | +| `js_sdk_version` | No | `@langfuse/client` version installed by the action for TypeScript or JavaScript experiments. Defaults to `latest`; use v5.3.0 or newer. | +| `should_skip_sdk_installation` | No | Skip SDK installation when you manage the Python or Node environment yourself before this action. For TypeScript experiments, provide `@langfuse/client`, `@langfuse/tracing`, `@langfuse/otel`, `@opentelemetry/sdk-node`, and `tsx` yourself. Defaults to `false`. | +| `github_token` | No | GitHub token used to post PR comments and resolve the current job URL. Leave blank to skip both. | + +See the full input reference in the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#inputs). + +| Output | Description | +| ------------- | ---------------------------------------------------------------------------------- | +| `result_json` | Normalized JSON result for downstream workflow steps. | +| `failed` | `true` if any experiment script errored or raised a regression; otherwise `false`. | + +### Additional secrets + +If your experiment needs provider keys or other secrets, set them as environment variables on the action step. The experiment subprocess inherits the step environment. + +```yaml +- uses: langfuse/experiment-action@ + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + with: + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + experiment_path: experiments/support-agent-gate + dataset_name: support-agent-regression-set +``` + +Your experiment can read these values from `os.environ[...]` in Python or `process.env...` in TypeScript and JavaScript. See the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#how-do-i-pass-extra-secrets-openai-keys-etc-to-my-experiment) for details. + ### Failing on regressions Raise `RegressionError` when a result should block the workflow. The example below fails when average exact-match accuracy is below the threshold. From 3fda119dc45be3d9ae84e9c443db4f4a78d7e07f Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 6 May 2026 18:23:25 +0200 Subject: [PATCH 17/17] docs: reorder ci guide secrets section --- content/guides/experiments-ci-cd.mdx | 36 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/content/guides/experiments-ci-cd.mdx b/content/guides/experiments-ci-cd.mdx index 6be6d8d83a..e1cdb83206 100644 --- a/content/guides/experiments-ci-cd.mdx +++ b/content/guides/experiments-ci-cd.mdx @@ -162,24 +162,6 @@ See the full input reference in the [`langfuse/experiment-action` README](https: | `result_json` | Normalized JSON result for downstream workflow steps. | | `failed` | `true` if any experiment script errored or raised a regression; otherwise `false`. | -### Additional secrets - -If your experiment needs provider keys or other secrets, set them as environment variables on the action step. The experiment subprocess inherits the step environment. - -```yaml -- uses: langfuse/experiment-action@ - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - with: - langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} - langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} - experiment_path: experiments/support-agent-gate - dataset_name: support-agent-regression-set -``` - -Your experiment can read these values from `os.environ[...]` in Python or `process.env...` in TypeScript and JavaScript. See the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#how-do-i-pass-extra-secrets-openai-keys-etc-to-my-experiment) for details. - ### Failing on regressions Raise `RegressionError` when a result should block the workflow. The example below fails when average exact-match accuracy is below the threshold. @@ -357,6 +339,24 @@ The same normalized data is available as the `result_json` action output. Use th run: printf '%s' "$RESULT_JSON" > experiment-result.json ``` +### Additional secrets + +If your experiment needs provider keys or other secrets, set them as environment variables on the action step. The experiment subprocess inherits the step environment. + +```yaml +- uses: langfuse/experiment-action@ + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + with: + langfuse_public_key: ${{ secrets.LANGFUSE_PUBLIC_KEY }} + langfuse_secret_key: ${{ secrets.LANGFUSE_SECRET_KEY }} + experiment_path: experiments/support-agent-gate + dataset_name: support-agent-regression-set +``` + +Your experiment can read these values from `os.environ[...]` in Python or `process.env...` in TypeScript and JavaScript. See the [`langfuse/experiment-action` README](https://github.com/langfuse/experiment-action/blob/main/README.md#how-do-i-pass-extra-secrets-openai-keys-etc-to-my-experiment) for details. + ## Other CI/CD systems [#other-cicd-systems] Integrate the experiment runner with testing frameworks like Pytest and Vitest to run automated evaluations in your CI pipeline. Use evaluators to create assertions that fail tests based on experiment results.