Skip to content

Commit 245d1f9

Browse files
paultancre-btPaul Tancreclaude
authored
fix(dev): honor request max_concurrency (#2730)
Co-authored-by: Paul Tancre <paultancre@p-tancre.home> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f42d9dc commit 245d1f9

4 files changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"braintrust": patch
3+
---
4+
5+
fix(dev): honor max_concurrency from remote eval requests, capped by the evaluator's maxConcurrency

‎js/dev/server.test.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,56 @@ describe("remote eval data", () => {
1919
});
2020
});
2121
});
22+
23+
describe("remote eval max concurrency", () => {
24+
test.each([
25+
{ evaluator: 5, request: 2, expected: 2 },
26+
{ evaluator: 5, request: 10, expected: 5 },
27+
{ evaluator: undefined, request: 3, expected: 3 },
28+
{ evaluator: 5, request: null, expected: 5 },
29+
{ evaluator: 5, request: undefined, expected: 5 },
30+
{ evaluator: undefined, request: undefined, expected: undefined },
31+
])(
32+
"evaluator $evaluator with request $request resolves to $expected",
33+
({ evaluator, request, expected }) => {
34+
expect(
35+
_exportsForTestingOnly.resolveMaxConcurrency(evaluator, request),
36+
).toBe(expected);
37+
},
38+
);
39+
40+
test("parses max_concurrency from the request body", () => {
41+
expect(
42+
evalBodySchema.parse({
43+
name: "remote-evaluator",
44+
data: { experiment_name: "source-experiment" },
45+
max_concurrency: 2,
46+
}).max_concurrency,
47+
).toBe(2);
48+
});
49+
50+
test("rounds down a fractional max_concurrency", () => {
51+
expect(
52+
evalBodySchema.parse({
53+
name: "remote-evaluator",
54+
data: { experiment_name: "source-experiment" },
55+
max_concurrency: 2.5,
56+
}).max_concurrency,
57+
).toBe(2);
58+
});
59+
60+
test.each([0, -1, 0.5, "2"])(
61+
"rejects invalid max_concurrency %s",
62+
(maxConcurrency) => {
63+
const result = evalBodySchema.safeParse({
64+
name: "remote-evaluator",
65+
data: { experiment_name: "source-experiment" },
66+
max_concurrency: maxConcurrency,
67+
});
68+
expect(result.success).toBe(false);
69+
expect(result.error?.issues.map((issue) => issue.path)).toEqual([
70+
["max_concurrency"],
71+
]);
72+
},
73+
);
74+
});

‎js/dev/server.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export function runDevServer(
146146
data,
147147
scores,
148148
stream,
149+
max_concurrency,
149150
} = evalBodySchema.parse(req.body);
150151

151152
if (!req.ctx?.state) {
@@ -229,6 +230,10 @@ export function runDevServer(
229230
state,
230231
experimentName: experiment_name ?? undefined,
231232
projectId: project_id ?? undefined,
233+
maxConcurrency: resolveMaxConcurrency(
234+
evaluator.maxConcurrency,
235+
max_concurrency,
236+
),
232237
},
233238
{
234239
// Avoid printing the bar to the console.
@@ -340,6 +345,21 @@ async function getDataset(
340345
}
341346
}
342347

348+
// The request can lower the evaluator's maxConcurrency but never raise it, so
349+
// the playground's default slider value can't exceed a limit set in code.
350+
function resolveMaxConcurrency(
351+
evaluatorMaxConcurrency: number | undefined,
352+
requestMaxConcurrency: number | null | undefined,
353+
): number | undefined {
354+
if (requestMaxConcurrency == null) {
355+
return evaluatorMaxConcurrency;
356+
}
357+
if (evaluatorMaxConcurrency === undefined) {
358+
return requestMaxConcurrency;
359+
}
360+
return Math.min(evaluatorMaxConcurrency, requestMaxConcurrency);
361+
}
362+
343363
function makeScorer(
344364
state: BraintrustState,
345365
name: string,
@@ -379,4 +399,5 @@ function makeScorer(
379399

380400
export const _exportsForTestingOnly = {
381401
getDataset,
402+
resolveMaxConcurrency,
382403
};

‎js/dev/types.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const evalBodySchema = z.object({
2424
project_id: z.string().nullish(),
2525
parent: invokeParentSchema.optional(),
2626
stream: z.boolean().optional(),
27+
max_concurrency: z.number().min(1).transform(Math.floor).nullish(),
2728
});
2829

2930
export type EvaluatorManifest = Record<

0 commit comments

Comments
 (0)