Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
"@opentelemetry/api": "^1.9.0"
},
"devDependencies": {
"@types/mustache": "^4.2.6"
"@types/mustache": "^4.2.6",
"@types/progress": "^2.0.7"
},
"optionalDependencies": {
"progress": "^2.0.3"
}
}
34 changes: 30 additions & 4 deletions packages/client/src/experiment/ExperimentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export class ExperimentManager {
metadata,
maxConcurrency: batchSize = 50,
runEvaluators,
progress: progressOption,
} = config;

const runName = this.createExperimentRunName({
Expand All @@ -200,6 +201,29 @@ export class ExperimentManager {
);
}

let progressBar: { tick: (n?: number) => void } | null = null;
const showProgress =
progressOption ??
(typeof process !== "undefined" && process.stderr?.isTTY === true);
const canShowBar =
showProgress &&
typeof process !== "undefined" &&
data.length > 0 &&
(process.stderr?.isTTY === true || progressOption === true);
if (canShowBar) {
const Progress = (await import("progress")).default as new (
format: string,
options: { total: number; stream: NodeJS.WritableStream },
) => { tick: (n?: number) => void };
progressBar = new Progress(
"Experiment [:bar] :current/:total :percent :eta",
{
total: data.length,
stream: process.stderr,
},
);
}

const itemResults: ExperimentItemResult<Input, ExpectedOutput, Metadata>[] =
[];

Expand All @@ -208,8 +232,8 @@ export class ExperimentManager {

const promises: Promise<
ExperimentItemResult<Input, ExpectedOutput, Metadata>
>[] = batch.map(async (item) => {
return this.runItem({
>[] = batch.map((item) =>
this.runItem({
item,
evaluators,
task,
Expand All @@ -218,8 +242,10 @@ export class ExperimentManager {
experimentDescription: description,
experimentMetadata: metadata,
datasetVersion: config.datasetVersion,
});
});
}).finally(() => {
progressBar?.tick(1);
}),
);

const settledResults = await Promise.allSettled(promises);
const results = settledResults.reduce(
Expand Down
8 changes: 8 additions & 0 deletions packages/client/src/experiment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ export type ExperimentParams<
* If not provided, returns the latest version.
*/
datasetVersion?: string;

/**
* Whether to show a terminal progress bar (tqdm-style) when running in Node with a TTY.
*
* Default: true when stderr is a TTY, false otherwise (e.g. browser, CI, piped output).
* Set to false to disable the bar.
*/
progress?: boolean;
};

export type ExperimentItemResult<
Expand Down
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions tests/e2e/experiments.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,42 @@ describe("Langfuse Datasets E2E", () => {
expect(result.datasetRunId).toBeUndefined();
});

describe("Progress bar", () => {
it("should run experiment with progress: false (no bar)", async () => {
const result = await langfuse.experiment.run({
name: "Progress disabled",
description: "Experiment with progress bar disabled",
data: dataset.slice(0, 2),
task,
evaluators: [createEvaluatorFromAutoevals(Factuality)],
progress: false,
});

await testEnv.spanProcessor.forceFlush();
await waitForServerIngestion(1000);

expect(result.itemResults).toHaveLength(2);
expect(result.runName).toContain("Progress disabled");
});

it("should run experiment with progress: true (bar when TTY)", async () => {
const result = await langfuse.experiment.run({
name: "Progress enabled",
description: "Experiment with progress bar enabled",
data: dataset.slice(0, 2),
task,
evaluators: [createEvaluatorFromAutoevals(Factuality)],
progress: true,
});

await testEnv.spanProcessor.forceFlush();
await waitForServerIngestion(1000);

expect(result.itemResults).toHaveLength(2);
expect(result.runName).toContain("Progress enabled");
});
});

// Error Handling Tests
describe("Error Handling", () => {
it("should handle evaluator failures gracefully", async () => {
Expand Down