feat: add --model-provider flag for keyless Bedrock LLM judge evaluators - #1828
Conversation
…Eval + Autoevals)
…support - DeepEval template: add /tmp workaround + DEEPEVAL_TELEMETRY_OPT_OUT for Lambda - Autoevals template: fix scorer= → metric= to match SDK API - Rename --from-3p-library → --3p-library - Add --param key=value (repeatable) for metric constructor kwargs - Add --parameters-file for JSON file of kwargs - Add parseParamFlags() utility + unit tests - --param and --parameters-file are mutually exclusive
- Add e2e-tests/third-party-eval-lifecycle.test.ts: full lifecycle test for DeepEval + Autoevals 3P evaluators (create → add → deploy → invoke → run eval) - Fix autoevals template: params go to AutoevalsAdapter (not metric constructor) - Add openai>=1.0.0 to autoevals pyproject.toml (required by LLM-based scorers)
Add validation guard so that explicitly passing --type llm-as-a-judge with --3p-library fails with a clear error message instead of silently proceeding into an invalid state.
Adds --model-provider <openai|bedrock> flag to `add evaluator` command.
When bedrock is selected:
- DeepEval: uses AmazonBedrockModel (native Bedrock via aiobotocore)
- Autoevals: uses LiteLLMClient routing to Bedrock Converse API
Templates are conditional via Handlebars {{#if ModelProviderBedrock}}.
Dependencies are dynamic: aiobotocore for DeepEval, litellm for Autoevals
(only included when bedrock is selected).
Default remains openai for backward compatibility.
Tested end-to-end on Lambda:
- DeepEval + Bedrock: 1.0, Pass (AnswerRelevancy, Claude Haiku)
- Autoevals + Bedrock: 0.6, Pass (Factuality, Claude Sonnet 4)
- No OpenAI API key, IAM-only auth
There was a problem hiding this comment.
Pull request overview
This PR extends the CLI’s third-party evaluator scaffolding to support Bedrock-backed “LLM-as-judge” execution via a new --model-provider <openai|bedrock> flag on agentcore add evaluator, generating provider-specific Python Lambda handlers and template dependencies.
Changes:
- Add third-party evaluator library registry + CLI flags (
--3p-library,--metric,--param/--parameters-file,--model-provider,--memory) and render Bedrock-aware templates. - Introduce new evaluator template assets for DeepEval and Autoevals (handler, pyproject, execution-role policy) with Handlebars conditionals for Bedrock paths.
- Extend evaluator managed code config schema/type to support
memorySizeMb.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/schema/schemas/primitives/evaluator.ts | Adds optional memorySizeMb to managed evaluator code config schema. |
| src/schema/llm-compacted/agentcore.ts | Mirrors memorySizeMb in the compacted schema interface. |
| src/cli/templates/EvaluatorRenderer.ts | Adds a renderer and template data shape for third-party evaluator templates. |
| src/cli/primitives/EvaluatorPrimitive.ts | Implements third-party evaluator registry/flag parsing and Bedrock/OpenAI template rendering. |
| src/cli/primitives/tests/EvaluatorPrimitive.test.ts | Adds unit tests covering third-party render context and param parsing helpers. |
| src/assets/evaluators/deepeval-lambda/pyproject.toml | New DeepEval Lambda pyproject with conditional Bedrock dependency. |
| src/assets/evaluators/deepeval-lambda/lambda_function.py | New DeepEval Lambda handler template with conditional Bedrock judge model wiring. |
| src/assets/evaluators/deepeval-lambda/execution-role-policy.json | New execution role policy template including Bedrock invoke permission. |
| src/assets/evaluators/autoevals-lambda/pyproject.toml | New Autoevals Lambda pyproject with conditional Bedrock (litellm) vs OpenAI deps. |
| src/assets/evaluators/autoevals-lambda/lambda_function.py | New Autoevals Lambda handler template with conditional Bedrock judge wiring. |
| src/assets/evaluators/autoevals-lambda/execution-role-policy.json | New execution role policy template including Bedrock invoke permission. |
| src/assets/tests/snapshots/assets.snapshot.test.ts.snap | Updates asset snapshot listing to include new evaluator template files. |
| e2e-tests/third-party-eval-lifecycle.test.ts | Adds an E2E lifecycle test for third-party evaluators (skipped unless AWS-capable). |
| .gitignore | Ignores a new E2E output directory. |
Comments suppressed due to low confidence (1)
src/assets/evaluators/autoevals-lambda/lambda_function.py:34
- When
EvaluatorParamsis empty (default when no --param/--parameters-file is provided), this rendersAutoEvalsAdapter(..., ), which is invalid Python syntax due to the dangling comma. Remove the unconditional comma so the no-params case renders valid code.
adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(), {{{ EvaluatorParams }}})
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| REGION = os.environ.get("AWS_REGION", "us-west-2") | ||
|
|
||
| model = AmazonBedrockModel(model=MODEL_ID, region=REGION) | ||
| adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model, {{{ EvaluatorParams }}})) |
| JUDGE_MODEL = os.environ.get("BEDROCK_MODEL_ID", "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0") | ||
| init(client=LiteLLMClient(), default_model=JUDGE_MODEL) | ||
|
|
||
| adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=LiteLLMClient(), model=JUDGE_MODEL), {{{ EvaluatorParams }}}) |
| export function jsonToKwargs(json: string): string { | ||
| const obj = JSON.parse(json) as Record<string, unknown>; | ||
| return Object.entries(obj) | ||
| .map(([key, value]) => `${key}=${jsonToPythonValue(value)}`) | ||
| .join(', '); | ||
| } |
| export function parseParamFlags(params: string[]): string { | ||
| return params | ||
| .map(param => { | ||
| const eqIndex = param.indexOf('='); | ||
| if (eqIndex === -1) { | ||
| throw new Error(`"${param}" is not in key=value format`); | ||
| } | ||
| const key = param.slice(0, eqIndex); | ||
| const rawValue = param.slice(eqIndex + 1); | ||
| let value: unknown; | ||
| try { | ||
| value = JSON.parse(rawValue); | ||
| } catch { | ||
| value = rawValue; | ||
| } | ||
| return `${key}=${jsonToPythonValue(value)}`; | ||
| }) | ||
| .join(', '); | ||
| } |
| if (cliOptions.memory) { | ||
| const memVal = parseInt(cliOptions.memory, 10); | ||
| if (isNaN(memVal) || memVal < 128 || memVal > 10240) { | ||
| fail('--memory must be an integer between 128 and 10240'); | ||
| } | ||
| } |
| # Autoevals grades via an OpenAI-compatible client. LiteLLMClient routes to Bedrock; | ||
| # litellm auto-routes Anthropic Claude models through the Converse API. Cross-region | ||
| # inference profiles (us.*/eu.*/apac.*) are required for on-demand invocation. | ||
| JUDGE_MODEL = os.environ.get("BEDROCK_MODEL_ID", "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0") |
There was a problem hiding this comment.
Do we need to use bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0 as the default model? This model is not available in all aws regions. Can we instead add a validation check to ensure --model is provided when the user selects Bedrock as the model provider?
| from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter | ||
|
|
||
| {{#if ModelProviderBedrock}} | ||
| MODEL_ID = os.environ.get("BEDROCK_MODEL_ID", "anthropic.claude-3-haiku-20240307-v1:0") |
There was a problem hiding this comment.
we can modify and reuse the existing cli input option('--model <model>', '[LLM] Bedrock model ID for LLM-as-a-Judge').
Where possible, avoid relying on environment variables. The goal of the intern project is to reduce the customer effort required to set up environment configuration as much as possible.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
src/cli/primitives/EvaluatorPrimitive.ts:154
jsonToKwargsassumes the parsed JSON is an object and emits keys verbatim as Python kwarg names. If the JSON is an array/null/non-object or contains keys that are not valid Python identifiers, the generated handler will be invalid Python. Validate the top-level type and kwarg names and fail with a clear error.
export function jsonToKwargs(json: string): string {
const obj = JSON.parse(json) as Record<string, unknown>;
return Object.entries(obj)
.map(([key, value]) => `${key}=${jsonToPythonValue(value)}`)
.join(', ');
}
src/cli/primitives/EvaluatorPrimitive.ts:173
parseParamFlagsemits the left-hand side ofkey=valuedirectly as a Python kwarg name. If the key contains spaces/dashes/etc, the generated handler will not compile. Validate the key as a Python identifier and surface a user-friendly error.
export function parseParamFlags(params: string[]): string {
return params
.map(param => {
const eqIndex = param.indexOf('=');
if (eqIndex === -1) {
throw new Error(`"${param}" is not in key=value format`);
}
const key = param.slice(0, eqIndex);
const rawValue = param.slice(eqIndex + 1);
let value: unknown;
try {
value = JSON.parse(rawValue);
} catch {
value = rawValue;
}
return `${key}=${jsonToPythonValue(value)}`;
})
.join(', ');
| function isSupportedLibrary(value: string): value is ThirdPartyLibrary { | ||
| return value in THIRD_PARTY_EVALUATOR_LIBRARIES; | ||
| } |
| init(client=LiteLLMClient(), default_model="{{ Model }}") | ||
|
|
||
| adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=LiteLLMClient(), model="{{ Model }}"), {{{ EvaluatorParams }}}) |
| if (cliOptions.modelProvider === 'bedrock' && !cliOptions.model) { | ||
| fail('--model is required when using --model-provider bedrock'); | ||
| } |
…faults Per Irene's review feedback: - Remove hardcoded default model IDs (not available in all regions) - Require --model flag when --model-provider bedrock is selected - Bake model ID into generated template at scaffold time (no env vars) - Reuse existing --model CLI flag for Bedrock model selection
2cc3c4e to
1a860d9
Compare
- Reuse single LiteLLMClient instance (avoid duplicate instantiation) - Fix dangling comma when EvaluatorParams is empty (invalid Python) - Validate Python identifier format in jsonToKwargs and parseParamFlags - Add --timeout validation (1-300 range check)
deepeval>=2.0.0,<3.0.0 and autoevals>=0.0.80,<1.0.0 to prevent breaking changes from major version bumps. Same pattern as strands-agents-evals>=1.0.3,<2.0.0.
- Remove memorySizeMb from schema, config, and tests (per TJ) - Fix ESLint errors (template literal type safety) - Update test descriptions
- Fix templateObj 'used before assigned' by using throw directly - Remove remaining memorySizeMb from test config objects
…1891) `run eval` against a code-based evaluator fails with AccessDeniedException for callers using the documented user policy: Access denied when invoking Lambda function: ... User: ... is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:...:function:<Agent>-eval-<evaluator> `bedrock-agentcore:Evaluate` invokes the evaluator's Lambda under the *caller's* identity, so the caller needs `lambda:InvokeFunction`. A resource-based policy on the function alone does not cover it. Neither iam-policy-user.json nor the PERMISSIONS.md Evaluation table granted or mentioned it, because until the DeepEval/Autoevals tests added in #1828 every eval path exercised was either `Builtin.*` or `llm-as-a-judge` -- neither of which touches Lambda. Scoped to `arn:*:lambda:*:*:function:*-eval-*` (partition wildcard per the multi-partition rules in AGENTS.md) rather than `*`. The same statement was added to the e2e-github-actions CI role, which had no lambda:InvokeFunction on any of its 9 policies -- that is what broke e2e shard 5 on main: https://github.com/aws/agentcore-cli/actions/runs/30663047263/job/91263525976 Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
Description
Adds
--model-provider <openai|bedrock>flag to theadd evaluatorcommand for third-party evaluationlibraries. When
bedrockis selected, the CLI generates Lambda handlers that use Amazon Bedrock as the LLMjudge instead of OpenAI — fully keyless, IAM-only auth (SigV4 via the Lambda execution role; the existing
bedrock:InvokeModelpolicy suffices).DeepEval (bedrock): Uses
AmazonBedrockModelfromdeepeval.modelsfor native Bedrock access. Judge modelfrom
BEDROCK_MODEL_IDenv var (defaultanthropic.claude-3-haiku-20240307-v1:0).Autoevals (bedrock): Uses
LiteLLMClientfromautoevals.litellmrouting to the Bedrock Converse API.Judge model from
BEDROCK_MODEL_IDenv var (defaultbedrock/us.anthropic.claude-sonnet-4-20250514-v1:0, across-region inference profile).
Default remains
openai— the default path generates byte-identical output to before this change; nobreaking change. Templates use Handlebars
{{#if ModelProviderBedrock}}conditionals. Dependencies aredynamic:
aiobotocore>=2.13.0(DeepEval) /litellm>=1.60,<1.85(Autoevals) only when bedrock is selected;openai>=1.0.0only on the Autoevals default path.Usage:
E2E tested on Lambda (us-west-2), scaffolded with the built CLI and deployed via agentcore deploy:
rationale
dependency install, e.g. pip --platform manylinux2014_aarch64 --only-binary=:all:)
Related Issue
Depends on #1779 (third-party eval library support). Runtime E2E requires the SDK third_party adapters from
bedrock-agentcore-sdk-python PR #568 (not yet on PyPI; tested against the locally built wheel).
Documentation PR
N/A — flag documented via --help text.
Type of Change
Testing
How have you tested the change?
Additionally:
pass py_compile
Checklist
needed
---By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this
contribution, under the terms of your choice.