Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/core/eval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ export class EvalClient implements CoreEvalClient {
dataSourceConfig,
evaluationMetadata: input.groundTruth ? { sessionMetadata: input.groundTruth } : undefined,
kmsKeyArn: input.kmsKeyArn,
outputConfig: input.outputConfig,
}),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"batchEvaluationId": "golden_batch_evaluate_outputconfig1-936a801ab2",
"batchEvaluationArn": "arn:aws:bedrock-agentcore:us-west-2:685197708687:batch-evaluate/golden_batch_evaluate_outputconfig1-936a801ab2",
"batchEvaluationName": "golden_batch_evaluate_outputconfig1",
"status": "PENDING",
"createdAt": {
"$date": "2026-09-11T20:52:41.450Z"
},
"evaluators": [
{
"evaluatorId": "Builtin.Helpfulness"
}
],
"outputConfig": {
"cloudWatchConfig": {
"logStreamName": "run-golden_batch_evaluate_outputconfig1-936a801ab2",
"metricsNamespace": "Company/AgentEvaluations",
"resultDestination": "SOURCE_LOG_GROUP"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"batchEvaluationId": "golden_batch_simulate_endpoint1-120c119e26",
"batchEvaluationArn": "arn:aws:bedrock-agentcore:us-west-2:685197708687:batch-evaluate/golden_batch_simulate_endpoint1-120c119e26",
"batchEvaluationName": "golden_batch_simulate_endpoint1",
"status": "PENDING",
"createdAt": {
"$date": "2026-09-11T20:52:44.223Z"
},
"evaluators": [
{
"evaluatorId": "Builtin.Helpfulness"
}
],
"outputConfig": {
"cloudWatchConfig": {
"logStreamName": "run-golden_batch_simulate_endpoint1-120c119e26",
"metricsNamespace": "Bedrock-AgentCore/Evaluations",
"resultDestination": "SOURCE_LOG_GROUP"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"batchEvaluationId": "golden_batch_evaluate_outputconfig1-936a801ab2",
"batchEvaluationArn": "arn:aws:bedrock-agentcore:us-west-2:685197708687:batch-evaluate/golden_batch_evaluate_outputconfig1-936a801ab2",
"batchEvaluationName": "golden_batch_evaluate_outputconfig1",
"status": "PENDING",
"createdAt": "2026-09-11T20:52:41.450Z",
"evaluators": [
{
"evaluatorId": "Builtin.Helpfulness"
}
],
"outputConfig": {
"cloudWatchConfig": {
"logStreamName": "run-golden_batch_evaluate_outputconfig1-936a801ab2",
"metricsNamespace": "Company/AgentEvaluations",
"resultDestination": "SOURCE_LOG_GROUP"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"batchEvaluationId": "golden_batch_simulate_endpoint1-120c119e26",
"status": "PENDING",
"examplesInvoked": 1,
"examplesFailed": 0,
"sessions": [
{
"exampleId": "e1",
"sessionId": "00000000-0000-4000-8000-000000000001"
}
],
"failures": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const FIXTURE_EVAL_AGENT = "asdf_MyAgent-3s5axvBC6Q";
const FIXTURE_EVAL_NAME = "golden_batch_evaluate_fixture685";

const FIXTURE_SIMULATE_NAME = "golden_batch_simulate_fixture1";
const FIXTURE_OUTPUT_CONFIG_NAME = "golden_batch_evaluate_outputconfig1";
const FIXTURE_SIMULATE_ENDPOINT_NAME = "golden_batch_simulate_endpoint1";
const FIXTURE_SIMULATE_DATASET = join(FIXTURES, "simulate-ds.jsonl");

function createFixtureCore(): CoreClient {
Expand Down Expand Up @@ -185,4 +187,72 @@ describe("eval batch-evaluation (fixture-backed)", () => {

matchGolden(FIXTURES, "simulate.golden.json", io.stdout());
}, 180_000);

test("evaluate submits a job with a customer-supplied output config", async () => {
const stdout = await run([
"eval",
"batch-evaluation",
"evaluate",
"--agent",
FIXTURE_EVAL_AGENT,
"--evaluators",
"Builtin.Helpfulness",
"--name",
FIXTURE_OUTPUT_CONFIG_NAME,
"--output-config",
'{"cloudWatchConfig":{"resultDestination":"SOURCE_LOG_GROUP","metricsNamespace":"Company/AgentEvaluations"}}',
"--json",
]);

matchGolden(FIXTURES, "evaluate-output-config.golden.json", stdout);
});

test("simulate passes --endpoint through and submits with an output config", async () => {
let n = 0;
const { createControlClient, createDataClient, createIamClient, createLogsClient } =
fixtureFactories(FIXTURES);
const core = new CoreClient({
createControlClient,
createDataClient,
createIamClient,
createLogsClient,
logger: createSilentLogger(),
newSessionId: () => `00000000-0000-4000-8000-${String(++n).padStart(12, "0")}`,
});
const io = testIO();
const root = createRootHandler(core, {
io: io.io,
logger: createSilentLogger(),
globalConfigAccessor: new TestGlobalConfigAccessor(),
});

await root.route([
"node",
"agentcore",
"eval",
"batch-evaluation",
"simulate",
"--runtime-id",
FIXTURE_EVAL_AGENT,
"--endpoint",
"DEFAULT",
"--payload-template",
'{"prompt":"{input}"}',
"--dataset",
FIXTURE_SIMULATE_DATASET,
"--evaluators",
"Builtin.Helpfulness",
"--name",
FIXTURE_SIMULATE_ENDPOINT_NAME,
"--output-config",
'{"cloudWatchConfig":{"resultDestination":"SOURCE_LOG_GROUP"}}',
"--ingestion-wait-ms",
"0",
"--json",
"--region",
REGION,
]);

matchGolden(FIXTURES, "simulate-endpoint-output-config.golden.json", io.stdout());
}, 180_000);
});
117 changes: 115 additions & 2 deletions src/handlers/eval/batch-evaluation/batch-evaluation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ const RESULTS: BatchEvaluationResultEntry[] = [
{ evaluatorId: "Builtin.Helpfulness", level: "Session", sessionId: "s1", score: 5 },
];

async function run(args: string[], configure?: (core: TestCoreClient) => void) {
async function run(
args: string[],
configure?: (core: TestCoreClient) => void,
ioOptions?: { stdin?: string },
) {
const core = new TestCoreClient();
configure?.(core);
const io = testIO();
const io = testIO(ioOptions);
const root = createRootHandler(core, {
io: io.io,
logger: createSilentLogger(),
Expand Down Expand Up @@ -262,3 +266,112 @@ describe("eval batch-evaluation simulate", () => {
expect(JSON.parse(stdout).failures).toEqual([{ exampleId: "bad", error: "HTTP 500" }]);
});
});

describe("eval batch-evaluation evaluate --output-config", () => {
const BASE = [
"eval",
"batch-evaluation",
"evaluate",
"--agent",
"r-1",
"--evaluators",
"Builtin.Helpfulness",
"--name",
"eval-1",
];
test("rejects malformed JSON before any SDK call", async () => {
const core = new TestCoreClient();
const io = testIO();
const root = createRootHandler(core, {
io: io.io,
logger: createSilentLogger(),
globalConfigAccessor: new TestGlobalConfigAccessor(),
});
await expect(
root.route([
"node",
"agentcore",
...BASE,
"--output-config",
"{not json",
"--region",
"us-west-2",
]),
).rejects.toThrow(/Invalid JSON for option '--output-config'/);
expect(core.eval.calls).toEqual([]);
});

test("rejects a second option reading from stdin, before any SDK call", async () => {
// --ground-truth and --output-config share one resolver, so the second `-`
// is a conflict rather than an empty read after the first drains stdin.
const core = new TestCoreClient();
const io = testIO({ stdin: "{}" });
const root = createRootHandler(core, {
io: io.io,
logger: createSilentLogger(),
globalConfigAccessor: new TestGlobalConfigAccessor(),
});
await expect(
root.route([
"node",
"agentcore",
...BASE,
"--ground-truth",
"-",
"--output-config",
"-",
"--region",
"us-west-2",
]),
).rejects.toThrow(/only one option may read from stdin.*'--output-config'.*'--ground-truth'/);
expect(core.eval.calls).toEqual([]);
});
});

describe("eval batch-evaluation simulate --endpoint and --output-config", () => {
const BASE = [
"eval",
"batch-evaluation",
"simulate",
"--runtime-id",
"r-1",
"--payload-template",
'{"prompt":"{input}"}',
"--dataset",
"/tmp/ds.jsonl",
"--evaluators",
"Builtin.Helpfulness",
"--name",
"sim-1",
];
const invoked = (c: TestCoreClient) =>
c.eval.setInvokeDatasetResponse({
sessions: [{ exampleId: "e1", sessionId: "s1" }],
invoked: 1,
failed: 0,
failures: [],
});

test("malformed --output-config aborts before any Runtime is invoked", async () => {
const core = new TestCoreClient();
invoked(core);
const io = testIO();
const root = createRootHandler(core, {
io: io.io,
logger: createSilentLogger(),
globalConfigAccessor: new TestGlobalConfigAccessor(),
});
await expect(
root.route([
"node",
"agentcore",
...BASE,
"--output-config",
"{not json",
"--region",
"us-west-2",
]),
).rejects.toThrow(/Invalid JSON for option '--output-config'/);
expect(core.eval.calls.map((c) => c.method)).not.toContain("invokeDataset");
});
});
12 changes: 10 additions & 2 deletions src/handlers/eval/batch-evaluation/evaluate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Core } from "../../../types";
import type { SessionMetadataShape } from "@aws-sdk/client-bedrock-agentcore";
import { coreOptsFromCtx, parseJsonFlag } from "../../../utils";
import { SessionSource } from "../../sessionSource";
import { BatchOutputConfig } from "../outputConfig";

const CONFIGURATION = "Configuration:";
const EVALUATION = "Evaluation:";
Expand Down Expand Up @@ -75,6 +76,7 @@ export const createEvaluateBatchEvaluationHandler = (core: Core, io: AppIO) =>
z.string().optional(),
{ group: EVALUATION, help: groundTruthHelp },
),
...BatchOutputConfig.flags,
],
handle: async (ctx, flags) => {
if (!flags["name"]) {
Expand All @@ -86,14 +88,19 @@ export const createEvaluateBatchEvaluationHandler = (core: Core, io: AppIO) =>
);
}

const source = await SessionSource.resolve(flags, io);

// One resolver shared across every stdin-capable flag, so a second `-`
// (e.g. --ground-truth - --output-config -) is rejected rather than
// silently reading an empty string after the first drains stdin.
const resolver = new SourceResolver({ stdin: io.stdin });
const source = await SessionSource.resolve(flags, resolver);

const groundTruth = parseJsonFlag<SessionMetadataShape[]>(
"ground-truth",
await resolver.resolveText("ground-truth", flags["ground-truth"]),
);

const outputConfig = await BatchOutputConfig.resolve(flags["output-config"], resolver);

const response = await core.eval.startBatchEvaluation(
{
name: flags["name"],
Expand All @@ -102,6 +109,7 @@ export const createEvaluateBatchEvaluationHandler = (core: Core, io: AppIO) =>
source,
groundTruth,
kmsKeyArn: flags["kms-key-arn"],
outputConfig,
},
coreOptsFromCtx(ctx),
);
Expand Down
Loading
Loading