-
Notifications
You must be signed in to change notification settings - Fork 66
feat: add --model-provider flag for keyless Bedrock LLM judge evaluators #1828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
6fba5c8
d44430a
485000e
0bff7a8
9153b3c
7b5348c
56158e3
1a860d9
591760a
a100276
f92635c
27ba5a2
f1cf617
1aa9152
11537a1
e9cb8f6
565eeda
978a29c
73376a3
de7768b
315fa1d
802a95f
e740815
59aa8b8
ec5c264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| import { parseJsonOutput, retry } from '../src/test-utils/index.js'; | ||
| import { | ||
| baseCanRun, | ||
| hasAws, | ||
| installCdkTarball, | ||
| runAgentCoreCLI, | ||
| teardownE2EProject, | ||
| writeAwsTargets, | ||
| } from './e2e-helper.js'; | ||
| import { randomUUID } from 'node:crypto'; | ||
| import { mkdir, rm } from 'node:fs/promises'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { join } from 'node:path'; | ||
| import { afterAll, beforeAll, describe, expect, it } from 'vitest'; | ||
|
|
||
| const canRun = baseCanRun && hasAws; | ||
|
|
||
| describe.sequential('e2e: third-party evaluator lifecycle (DeepEval + Autoevals)', () => { | ||
| let testDir: string; | ||
| let projectPath: string; | ||
| const agentName = `E2e3pEval${String(Date.now()).slice(-8)}`; | ||
| const deepevalEvalName = 'answer_relevancy'; | ||
| const autoevalsEvalName = 'exact_match'; | ||
|
|
||
| beforeAll(async () => { | ||
| if (!canRun) return; | ||
|
|
||
| testDir = join(tmpdir(), `agentcore-e2e-3p-eval-${randomUUID()}`); | ||
| await mkdir(testDir, { recursive: true }); | ||
|
|
||
| const result = await runAgentCoreCLI( | ||
| [ | ||
| 'create', | ||
| '--name', | ||
| agentName, | ||
| '--language', | ||
| 'Python', | ||
| '--framework', | ||
| 'Strands', | ||
| '--model-provider', | ||
| 'Bedrock', | ||
| '--memory', | ||
| 'none', | ||
| '--json', | ||
| ], | ||
| testDir | ||
| ); | ||
| expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0); | ||
| projectPath = (parseJsonOutput(result.stdout) as { projectPath: string }).projectPath; | ||
|
|
||
| await writeAwsTargets(projectPath); | ||
| installCdkTarball(projectPath); | ||
| }, 300000); | ||
|
|
||
| afterAll(async () => { | ||
| if (projectPath && hasAws) { | ||
| await teardownE2EProject(projectPath, agentName, 'Bedrock'); | ||
| } | ||
| if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); | ||
| }, 600000); | ||
|
|
||
| const run = (args: string[]) => runAgentCoreCLI(args, projectPath); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'adds a DeepEval 3P evaluator with --3p-library and --param', | ||
| async () => { | ||
| const result = await run([ | ||
| 'add', | ||
| 'evaluator', | ||
| '--name', | ||
| deepevalEvalName, | ||
| '--level', | ||
| 'TRACE', | ||
| '--3p-library', | ||
| 'deepeval', | ||
| '--metric', | ||
| 'AnswerRelevancyMetric', | ||
| '--param', | ||
| 'threshold=0.5', | ||
| '--json', | ||
| ]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please update all the tests |
||
| expect(result.exitCode, `Add DeepEval evaluator failed: ${result.stdout}`).toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as { success: boolean; evaluatorName: string; codePath: string }; | ||
| expect(json.success).toBe(true); | ||
| expect(json.evaluatorName).toBe(deepevalEvalName); | ||
| expect(json.codePath).toContain(deepevalEvalName); | ||
| }, | ||
| 60000 | ||
| ); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'adds an Autoevals 3P evaluator with --3p-library', | ||
| async () => { | ||
| const result = await run([ | ||
| 'add', | ||
| 'evaluator', | ||
| '--name', | ||
| autoevalsEvalName, | ||
| '--level', | ||
| 'TRACE', | ||
| '--3p-library', | ||
| 'autoevals', | ||
| '--metric', | ||
| 'ExactMatch', | ||
| '--json', | ||
| ]); | ||
| expect(result.exitCode, `Add Autoevals evaluator failed: ${result.stdout}`).toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as { success: boolean; evaluatorName: string; codePath: string }; | ||
| expect(json.success).toBe(true); | ||
| expect(json.evaluatorName).toBe(autoevalsEvalName); | ||
| expect(json.codePath).toContain(autoevalsEvalName); | ||
| }, | ||
| 60000 | ||
| ); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'deploys agent with 3P evaluators', | ||
| async () => { | ||
| const result = await run(['deploy', '--yes', '--json']); | ||
| if (result.exitCode !== 0) { | ||
| console.log('Deploy stdout:', result.stdout); | ||
| console.log('Deploy stderr:', result.stderr); | ||
| } | ||
| expect(result.exitCode, 'Deploy failed').toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as { success: boolean }; | ||
| expect(json.success).toBe(true); | ||
| }, | ||
| 600000 | ||
| ); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'invokes the deployed agent to generate traces', | ||
| async () => { | ||
| await retry( | ||
| async () => { | ||
| const result = await run(['invoke', '--prompt', 'What is 2+2?', '--runtime', agentName, '--json']); | ||
| expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as { success: boolean }; | ||
| expect(json.success).toBe(true); | ||
| }, | ||
| 3, | ||
| 15000 | ||
| ); | ||
| }, | ||
| 180000 | ||
| ); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'runs on-demand evaluation with DeepEval 3P evaluator', | ||
| async () => { | ||
| await retry( | ||
| async () => { | ||
| const result = await run([ | ||
| 'run', | ||
| 'eval', | ||
| '--runtime', | ||
| agentName, | ||
| '--evaluator', | ||
| deepevalEvalName, | ||
| '--days', | ||
| '1', | ||
| '--json', | ||
| ]); | ||
| expect(result.exitCode, `Run eval failed (stdout: ${result.stdout}, stderr: ${result.stderr})`).toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as Record<string, unknown>; | ||
| expect(json).toHaveProperty('success', true); | ||
| expect(json).toHaveProperty('run'); | ||
| }, | ||
| 18, | ||
| 10000 | ||
| ); | ||
| }, | ||
| 300000 | ||
| ); | ||
|
|
||
| it.skipIf(!canRun)( | ||
| 'runs on-demand evaluation with Autoevals 3P evaluator', | ||
| async () => { | ||
| await retry( | ||
| async () => { | ||
| const result = await run([ | ||
| 'run', | ||
| 'eval', | ||
| '--runtime', | ||
| agentName, | ||
| '--evaluator', | ||
| autoevalsEvalName, | ||
| '--days', | ||
| '1', | ||
| '--json', | ||
| ]); | ||
| expect(result.exitCode, `Run eval failed (stdout: ${result.stdout}, stderr: ${result.stderr})`).toBe(0); | ||
| const json = parseJsonOutput(result.stdout) as Record<string, unknown>; | ||
| expect(json).toHaveProperty('success', true); | ||
| expect(json).toHaveProperty('run'); | ||
| }, | ||
| 18, | ||
| 10000 | ||
| ); | ||
| }, | ||
| 300000 | ||
| ); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "Version": "2012-10-17", | ||
| "Statement": [ | ||
| { | ||
| "Effect": "Allow", | ||
| "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], | ||
| "Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*" | ||
| }, | ||
| { | ||
| "Effect": "Allow", | ||
| "Action": ["bedrock:InvokeModel"], | ||
| "Resource": "*" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| {{#if ModelProviderBedrock}} | ||
| import os | ||
|
|
||
| # litellm's Bedrock provider reads AWS_REGION_NAME; Lambda only sets AWS_REGION/AWS_DEFAULT_REGION. | ||
| os.environ.setdefault("AWS_REGION_NAME", os.environ.get("AWS_REGION", "us-west-2")) | ||
|
|
||
| from autoevals import {{ EvaluatorClass }}, init | ||
| from autoevals.litellm import LiteLLMClient | ||
|
|
||
| from bedrock_agentcore.evaluation.custom_code_based_evaluators import ( | ||
| EvaluatorInput, | ||
| EvaluatorOutput, | ||
| custom_code_based_evaluator, | ||
| ) | ||
| from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter | ||
|
|
||
| # 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to use |
||
| init(client=LiteLLMClient(), default_model=JUDGE_MODEL) | ||
|
|
||
| adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=LiteLLMClient(), model=JUDGE_MODEL), {{{ EvaluatorParams }}}) | ||
|
|
||
| {{else}} | ||
| from autoevals import {{ EvaluatorClass }} | ||
|
|
||
| from bedrock_agentcore.evaluation.custom_code_based_evaluators import ( | ||
| EvaluatorInput, | ||
| EvaluatorOutput, | ||
| custom_code_based_evaluator, | ||
| ) | ||
| from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter | ||
|
|
||
| adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(), {{{ EvaluatorParams }}}) | ||
| {{/if}} | ||
|
|
||
|
|
||
| @custom_code_based_evaluator() | ||
| def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput: | ||
| return adapter(evaluator_input, context) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "{{ Name }}" | ||
| version = "0.1.0" | ||
| description = "AgentCore Code-Based Evaluator (Autoevals)" | ||
| requires-python = ">=3.10" | ||
| dependencies = [ | ||
| "bedrock-agentcore[autoevals]", | ||
| "autoevals>=0.0.80", | ||
| {{#if ModelProviderBedrock}} | ||
| # autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge | ||
| "litellm>=1.60,<1.85", | ||
| {{else}} | ||
| "openai>=1.0.0", | ||
| {{/if}} | ||
| ] | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["."] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "Version": "2012-10-17", | ||
| "Statement": [ | ||
| { | ||
| "Effect": "Allow", | ||
| "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], | ||
| "Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*" | ||
| }, | ||
| { | ||
| "Effect": "Allow", | ||
| "Action": ["bedrock:InvokeModel"], | ||
| "Resource": "*" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import os | ||
|
|
||
| os.environ.setdefault("DEEPEVAL_RESULTS_FOLDER", "/tmp/.deepeval") | ||
| os.environ.setdefault("DEEPEVAL_TELEMETRY_OPT_OUT", "YES") | ||
| os.chdir("/tmp") | ||
|
|
||
| {{#if ModelProviderBedrock}} | ||
| from deepeval.models import AmazonBedrockModel | ||
| {{/if}} | ||
| from deepeval.metrics import {{ EvaluatorClass }} | ||
|
|
||
| from bedrock_agentcore.evaluation.custom_code_based_evaluators import ( | ||
| EvaluatorInput, | ||
| EvaluatorOutput, | ||
| custom_code_based_evaluator, | ||
| ) | ||
| 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can modify and reuse the existing cli input |
||
| REGION = os.environ.get("AWS_REGION", "us-west-2") | ||
|
|
||
| model = AmazonBedrockModel(model=MODEL_ID, region=REGION) | ||
| adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model, {{{ EvaluatorParams }}})) | ||
|
|
||
| {{else}} | ||
| adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}})) | ||
| {{/if}} | ||
|
|
||
|
|
||
| @custom_code_based_evaluator() | ||
| def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput: | ||
| return adapter(evaluator_input, context) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "{{ Name }}" | ||
| version = "0.1.0" | ||
| description = "AgentCore Code-Based Evaluator (DeepEval)" | ||
| requires-python = ">=3.10" | ||
| dependencies = [ | ||
| "bedrock-agentcore[deepeval]", | ||
| "deepeval>=2.0.0", | ||
| {{#if ModelProviderBedrock}} | ||
| "aiobotocore>=2.13.0", | ||
| {{/if}} | ||
| ] | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["."] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please run typecheck, linter, and prettier on this PR.