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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[autoevals](https://github.com/braintrustdata/autoevals) — scores each session
with autoevals' `{{ EvaluatorClass }}` scorer.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in an `AutoEvalsAdapter`
behind the standard `@custom_code_based_evaluator()` handler. With a Bedrock
judge model set, autoevals grades via a LiteLLM client → Bedrock.
- `pyproject.toml` — autoevals + judge dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model.

## Customize

- Change the scorer or its arguments in `lambda_function.py`.
- Scorers like `Factuality` / `ClosedQA` / `SQL` need an expected/reference
output — provide it when you invoke the evaluator.

`agentcore project deploy` packages this directory into the evaluator Lambda.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
{
Comment thread
jariy17 marked this conversation as resolved.
"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": "*"
}
]
}
37 changes: 37 additions & 0 deletions src/assets/evaluators/autoevals-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
{{#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

client = LiteLLMClient()
init(client=client, default_model="bedrock/{{ Model }}")

adapter = AutoEvalsAdapter(metric={{ EvaluatorClass }}(client=client, model="bedrock/{{ Model }}"){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{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 }}({{#if Model}}model="{{ Model }}"{{/if}}){{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}})
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
23 changes: 23 additions & 0 deletions src/assets/evaluators/autoevals-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
[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 = [
# 1.20.0 is the first release shipping third_party.autoevals.AutoEvalsAdapter;
# the extra owns the autoevals version, so pinning it here only conflicts.
"bedrock-agentcore[autoevals]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# autoevals grades via LiteLLMClient -> Bedrock (Converse); litellm replaces the openai judge
"litellm>=1.60,<1.85",
{{else}}
"openai>=1.0.0,<2.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
23 changes: 23 additions & 0 deletions src/assets/evaluators/deepeval-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
# {{ Name }}

An AgentCore **code-based evaluator** backed by
[DeepEval](https://docs.confident-ai.com/) — scores each session with DeepEval's
`{{ EvaluatorClass }}` metric, judged by Amazon Bedrock.

## What's here

- `lambda_function.py` — wraps `{{ EvaluatorClass }}` in a `DeepEvalAdapter`
behind the standard `@custom_code_based_evaluator()` handler.
- `pyproject.toml` — DeepEval + Bedrock dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — grants the Lambda `bedrock:InvokeModel` for the
judge model; add more if your metric needs it.

## Customize

- Swap the metric or tune its threshold in `lambda_function.py`.
- Some DeepEval metrics need retrieval context or a reference/expected output —
supply those when you invoke the evaluator, or the metric returns
`MISSING_REQUIRED_FIELD`.

`agentcore project deploy` packages this directory into the evaluator Lambda.
15 changes: 15 additions & 0 deletions src/assets/evaluators/deepeval-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff 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": "*"
}
]
}
29 changes: 29 additions & 0 deletions src/assets/evaluators/deepeval-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 = AmazonBedrockModel(model="{{ Model }}", region=os.environ.get("AWS_REGION", "us-west-2"))
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}(model=model{{#if EvaluatorParams}}, {{{ EvaluatorParams }}}{{/if}}))
{{else}}
adapter = DeepEvalAdapter(metric={{ EvaluatorClass }}({{{ EvaluatorParams }}}))
{{/if}}


@custom_code_based_evaluator()
def handler(evaluator_input: EvaluatorInput, context) -> EvaluatorOutput:
return adapter(evaluator_input, context)
22 changes: 22 additions & 0 deletions src/assets/evaluators/deepeval-lambda/pyproject.toml
Original file line numberDiff line numberDiff 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 (DeepEval)"
requires-python = ">=3.10"
dependencies = [
# 1.20.0 is the first release shipping third_party.deepeval.DeepEvalAdapter;
# the extra owns the deepeval version, so pinning it here only conflicts.
"bedrock-agentcore[deepeval]>=1.20.0,<2.0.0",
{{#if ModelProviderBedrock}}
# deepeval's AmazonBedrockModel imports aiobotocore at runtime; only 3.x
# allows the botocore that bedrock-agentcore requires.
"aiobotocore>=3.0.0,<4.0.0",
{{/if}}
]

[tool.hatch.build.targets.wheel]
packages = ["."]
28 changes: 28 additions & 0 deletions src/assets/evaluators/python-lambda/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
# {{ Name }}

An AgentCore **code-based evaluator** — a Lambda that scores an agent session
with your own logic.

## What's here

- `lambda_function.py` — the evaluator. The `@custom_code_based_evaluator()`
handler receives an `EvaluatorInput` (the session / trace / tool-call to
grade) and returns an `EvaluatorOutput` (`value` + `label`, or an error). It
ships as a stub that returns `Pass` for everything — replace the `TODO` with
your scoring logic.
- `pyproject.toml` — Python dependencies, managed with
[uv](https://docs.astral.sh/uv/).
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
Add statements here for anything your logic calls (DynamoDB, S3, …).

## Write your evaluator

```python
@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
# inspect input.session_spans / input.target_trace_id / input.target_span_id
return EvaluatorOutput(value=1.0, label="Pass", explanation="…")
```

Then `agentcore project deploy` packages this directory into the evaluator
Lambda and registers the evaluator.
10 changes: 10 additions & 0 deletions src/assets/evaluators/python-lambda/execution-role-policy.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*"
}
]
}
19 changes: 19 additions & 0 deletions src/assets/evaluators/python-lambda/lambda_function.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
from bedrock_agentcore.evaluation.custom_code_based_evaluators import (
Comment thread
jariy17 marked this conversation as resolved.
custom_code_based_evaluator,
EvaluatorInput,
EvaluatorOutput,
)


@custom_code_based_evaluator()
def handler(input: EvaluatorInput, context) -> EvaluatorOutput:
"""Evaluate agent behavior with custom logic.

Args:
input: Contains evaluation_level, session_spans, target_trace_id, target_span_id

Returns:
EvaluatorOutput with value/label for success, or errorCode/errorMessage for failure.
"""
# TODO: Replace with your evaluation logic
return EvaluatorOutput(value=1.0, label="Pass", explanation="Evaluation passed")
15 changes: 15 additions & 0 deletions src/assets/evaluators/python-lambda/pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{ Name }}"
version = "0.1.0"
description = "AgentCore Code-Based Evaluator"
requires-python = ">=3.10"
dependencies = [
"bedrock-agentcore>=1.6.0,<2.0.0",
]

[tool.hatch.build.targets.wheel]
packages = ["."]
7 changes: 7 additions & 0 deletions src/core/project/fsUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,3 +20,10 @@ export function enclosingProjectRoot(directory: string): string | undefined {
}
}
}

export function toPythonPackageName(name: string): string {
return name
.replace(/[^a-zA-Z0-9._-]/g, "-")
.replace(/^[^a-zA-Z0-9]+/, "")
.replace(/[^a-zA-Z0-9]+$/, "");
}
19 changes: 18 additions & 1 deletion src/core/project/manager.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ import {
} from "./templates/export";
import { HarnessSpecSchema } from "../../projectSchemas/harness";
import { FsTreeNode } from "./templates/fsTree";
import { getEvaluatorTemplateResolver } from "./templates/evaluator";
import { ProjectSpecSchema, type ManagedBy } from "../../projectSchemas/project";
import { ConfigBundleSchema } from "../../projectSchemas/config-bundle";
import {
Expand DownExpand Up@@ -336,7 +337,23 @@ export class FsProjectManager implements ProjectManager {
break;
}
case "evaluator": {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
if (input.scaffold) {
yield { message: "Scaffolding evaluator in project" };
const outputPath = join(project.rootPath, "app", input.scaffold.name);
if (existsSync(outputPath))
throw new InputValidationError(
`cannot scaffold evaluator '${input.scaffold.name}': 'app/${input.scaffold.name}' already exists (another resource may use this name, or a previous scaffold was left behind)`,
);
scaffoldedPaths.push(outputPath);
const result = await getEvaluatorTemplateResolver({
assetSource: this.assetSource,
templateRenderer: this.templateRenderer,
}).resolve(input.scaffold);
await result.tree.write(dirname(outputPath));
projectSpec.evaluators.push(...(result.spec.evaluators ?? []));
} else {
projectSpec.evaluators.push(parseResource(EvaluatorSchema, input.resourceConfig));
}
break;
}
case "gateway":
Expand Down
81 changes: 81 additions & 0 deletions src/core/project/templates/evaluator.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import { FsTreeNode } from "./fsTree";
import type { AssetSource } from "../source";
import type { Evaluator } from "../../../projectSchemas/evaluator";
import type { TemplateRenderer, TemplateResolver } from "./types";
import { toPythonPackageName } from "../fsUtils";
import type {
EvaluatorLibrary,
ManagedEvaluatorScaffoldInput,
} from "../../../handlers/project/types";

const DEFAULT_TIMEOUT = 60;

const EVALUATOR_ASSETS: Record<
EvaluatorLibrary,
{ assetDir: string; defaultTimeoutSeconds: number }
> = {
deepeval: { assetDir: "evaluators/deepeval-lambda", defaultTimeoutSeconds: 300 },
autoevals: { assetDir: "evaluators/autoevals-lambda", defaultTimeoutSeconds: DEFAULT_TIMEOUT },
};

const EMPTY_ASSET_DIR = "evaluators/python-lambda";

function buildManagedEvaluatorSpec(input: ManagedEvaluatorScaffoldInput): Evaluator {
const timeoutSeconds =
input.timeoutSeconds ??
(input.metric ? EVALUATOR_ASSETS[input.metric.library].defaultTimeoutSeconds : DEFAULT_TIMEOUT);
return {
name: input.name,
level: input.level,
...(input.description && { description: input.description }),
config: {
codeBased: {
managed: {
codeLocation: `app/${input.name}`,
entrypoint: "lambda_function.handler",
timeoutSeconds,
additionalPolicies: ["execution-role-policy.json"],
},
},
},
...(input.kmsKeyArn && { kmsKeyArn: input.kmsKeyArn }),
...(input.tags && { tags: input.tags }),
};
}

function buildRenderContext(input: ManagedEvaluatorScaffoldInput): Record<string, unknown> {
const context: Record<string, unknown> = { Name: toPythonPackageName(input.name) };
if (input.metric) {
context["EvaluatorClass"] = input.metric.metricClass;
context["Model"] = input.model ?? "";
context["ModelProviderBedrock"] = input.model !== undefined;
context["EvaluatorParams"] = "";
}
return context;
}

type GetEvaluatorTemplateResolverConfig = {
assetSource: AssetSource;
templateRenderer: TemplateRenderer;
};

export function getEvaluatorTemplateResolver(
config: GetEvaluatorTemplateResolverConfig,
): TemplateResolver<ManagedEvaluatorScaffoldInput> {
return {
async resolve(input) {
const assetDir = input.metric
? EVALUATOR_ASSETS[input.metric.library].assetDir
: EMPTY_ASSET_DIR;
const tree = await FsTreeNode.fromAssetSource(
{ assetSource: config.assetSource },
{ assetDir },
{
rootDirName: input.name,
transformContent: (raw) => config.templateRenderer.render(raw, buildRenderContext(input)),
},
);
return { tree, spec: { evaluators: [buildManagedEvaluatorSpec(input)] } };
},
};
}
Loading
Loading