Skip to content

feat: add --model-provider flag for keyless Bedrock LLM judge evaluators - #1828

Merged
jariy17 merged 25 commits into
aws:mainfrom
stone-coding:feature/bedrock-model-templates
Jul 31, 2026
Merged

feat: add --model-provider flag for keyless Bedrock LLM judge evaluators#1828
jariy17 merged 25 commits into
aws:mainfrom
stone-coding:feature/bedrock-model-templates

Conversation

@stone-coding

@stone-coding stone-coding commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Description

Adds --model-provider <openai|bedrock> flag to the add evaluator command for third-party evaluation
libraries. When bedrock is selected, the CLI generates Lambda handlers that use Amazon Bedrock as the LLM
judge instead of OpenAI — fully keyless, IAM-only auth (SigV4 via the Lambda execution role; the existing
bedrock:InvokeModel policy suffices).

DeepEval (bedrock): Uses AmazonBedrockModel from deepeval.models for native Bedrock access. Judge model
from BEDROCK_MODEL_ID env var (default anthropic.claude-3-haiku-20240307-v1:0).

Autoevals (bedrock): Uses LiteLLMClient from autoevals.litellm routing to the Bedrock Converse API.
Judge model from BEDROCK_MODEL_ID env var (default bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0, a
cross-region inference profile).

Default remains openai — the default path generates byte-identical output to before this change; no
breaking change. Templates use Handlebars {{#if ModelProviderBedrock}} conditionals. Dependencies are
dynamic: aiobotocore>=2.13.0 (DeepEval) / litellm>=1.60,<1.85 (Autoevals) only when bedrock is selected;
openai>=1.0.0 only on the Autoevals default path.

Usage:

agentcore add evaluator --name fact --level TRACE --3p-library autoevals --metric Factuality --model-provider
bedrock --param threshold=0.5

E2E tested on Lambda (us-west-2), scaffolded with the built CLI and deployed via agentcore deploy:

  • DeepEval + Bedrock (AnswerRelevancyMetric, Claude 3 Haiku judge): {"value": 1.0, "label": "Pass"} with
    rationale
  • Autoevals + Bedrock (Factuality, Claude Sonnet 4 judge): {"value": 0.6, "label": "Pass"} with rationale
  • Deployed Lambdas have no environment variables — no OPENAI_API_KEY anywhere
  • Regression: evaluator scaffolded without the flag is byte-identical to pre-change output
  • Bundle size with litellm: 212–221 MB unzipped, under Lambda's 250 MB limit (needs platform-targeted
    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

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe):

Testing

How have you tested the change?

  • I ran npm run test:unit and npm run test:integ
  • I ran npm run typecheck
  • I ran npm run lint
  • If I modified src/assets/, I ran npm run test:update-snapshots and committed the updated snapshots

Additionally:

  • 3 new unit tests for the render-context behavior (bedrock set / openai explicit / flag omitted)
  • Verified all 8 template variants (2 libraries × 2 providers × 2 files) render correctly; all Python outputs
    pass py_compile
  • Deployed both Bedrock handlers to Lambda and confirmed valid Claude-judged scores with no OpenAI API key
  • Regression tested: OpenAI path scaffolds identical output to before

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly (N/A — no separate docs; flag is self-documented in --help)
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are
    needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

---By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this
contribution, under the terms of your choice.

…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
@stone-coding
stone-coding requested review from a team and Copilot July 24, 2026 16:42
@github-actions github-actions Bot added the size/xl PR size: XL label Jul 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 EvaluatorParams is empty (default when no --param/--parameters-file is provided), this renders AutoEvalsAdapter(..., ), 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 }}})
Comment on lines +147 to +152
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(', ');
}
Comment on lines +154 to +172
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(', ');
}
Comment on lines +451 to +456
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');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we fix this?

@stone-coding stone-coding changed the title Feature/bedrock model templates feat: Add --model-provider flag for keyless Bedrock LLM judge evaluators Jul 24, 2026
# 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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI review requested due to automatic review settings July 24, 2026 20:40
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • jsonToKwargs assumes 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

  • parseParamFlags emits the left-hand side of key=value directly 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(', ');

Comment on lines +91 to +93
function isSupportedLibrary(value: string): value is ThirdPartyLibrary {
return value in THIRD_PARTY_EVALUATOR_LIBRARIES;
}
Comment on lines +17 to +19
init(client=LiteLLMClient(), default_model="{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=LiteLLMClient(), model="{{ Model }}"), {{{ EvaluatorParams }}})
Comment on lines +453 to +455
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
Copilot AI review requested due to automatic review settings July 24, 2026 21:18
@stone-coding
stone-coding force-pushed the feature/bedrock-model-templates branch from 2cc3c4e to 1a860d9 Compare July 24, 2026 21:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 24, 2026
- 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)
Copilot AI review requested due to automatic review settings July 24, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot removed the size/xl PR size: XL label Jul 24, 2026
Copilot AI review requested due to automatic review settings July 30, 2026 16:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 30, 2026
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.
Copilot AI review requested due to automatic review settings July 30, 2026 19:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 30, 2026
- Remove memorySizeMb from schema, config, and tests (per TJ)
- Fix ESLint errors (template literal type safety)
- Update test descriptions
Copilot AI review requested due to automatic review settings July 30, 2026 22:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 30, 2026
jariy17
jariy17 previously approved these changes Jul 31, 2026
- Fix templateObj 'used before assigned' by using throw directly
- Remove remaining memorySizeMb from test config objects
Copilot AI review requested due to automatic review settings July 31, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 31, 2026
Copilot AI review requested due to automatic review settings July 31, 2026 19:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Jul 31, 2026
@jariy17
jariy17 merged commit 8fba816 into aws:main Jul 31, 2026
29 checks passed
jariy17 added a commit that referenced this pull request Aug 3, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants