Skip to content

feat: Revision third-party eval metrics adapter (DeepEval + Autoevals)#568

Open
stone-coding wants to merge 14 commits into
aws:mainfrom
stone-coding:feature/third-party-eval-adapter
Open

feat: Revision third-party eval metrics adapter (DeepEval + Autoevals)#568
stone-coding wants to merge 14 commits into
aws:mainfrom
stone-coding:feature/third-party-eval-adapter

Conversation

@stone-coding

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

Copy link
Copy Markdown

Summary

  • Add DeepEvalAdapter and AutoevalsAdapter that integrate third-party evaluation metrics with
    AgentCore's code-based evaluator framework
  • Built-in StrandsSpanMapper (class-based) auto-extracts input/output from Strands agent
    OTel spans — supports both inline events (unified ADOT) and span body (CloudWatch ADOT) formats
  • customer_mapper parameter lets customers bypass the built-in mapper and return the metric
    library's native type directly:
    • DeepEval: customer_mapper: Callable[[EvaluatorInput], LLMTestCase]
    • Autoevals: customer_mapper: Callable[[EvaluatorInput], Dict[str, Any]]
  • Simplified SpanMapResult — only fields metrics actually consume (input, actual_output,
    retrieval_context, system_prompt, expected_output)
  • Removed separate validate_fields() step — validation happens inside each adapter's _run()
    method when building the test case
  • _filter_spans_by_target applied only for built-in mapper path (not for customer mapper)
  • Never raises unhandled exceptions — all error paths return valid EvaluatorOutput with
    structured errorCode/errorMessage

Test plan

  • 83 unit tests passing (pytest tests/.../third_party/ -v)
  • E2E on-demand: built-in mapper → Score 1.0 Pass (Strands healthcare agent)
  • E2E on-demand: custom mapper → Score 1.0 Pass (non-Strands spans)
  • E2E online eval: pipeline invokes Lambda, score 0.98 Pass (CloudWatch results)
  • Error handling: 4/4 (malformed, unsupported scope, empty attrs, success schema)

Introduces a new integrations/deepeval/ module that adapts AgentCore
Lambda evaluation events into DeepEval LLMTestCase objects, runs any
BaseMetric, and returns structured score/label/explanation responses.
- Rename span_parsers → span_mappers, simplify to Strands-only
- Rename field_mapper → customer_mapper across all adapters
- customer_mapper now returns native types directly:
  - DeepEval: EvaluatorInput → LLMTestCase
  - Autoevals: EvaluatorInput → Dict[str, Any] (eval kwargs)
- Refactor BaseAdapter: remove intermediate execute(), add _run() pattern
- 83 unit tests passing
Comment on lines +27 to +31
customer_mapper=lambda ev: {
"input": ev.session_spans[0]["attributes"]["question"],
"output": ev.session_spans[0]["attributes"]["answer"],
"expected": "the expected answer",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we need to accept aws lambda?

def __init__(
self,
scorer: Any,
customer_mapper: Optional[Callable[[EvaluatorInput], Dict[str, Any]]] = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  1. We have not updated this type Dict[str, Any]?
  2. Regarding naming, "Custom mapper" would be a better choice for describing an arbitrary mapper provided by a customer?

self,
scorer: Any,
customer_mapper: Optional[Callable[[EvaluatorInput], Dict[str, Any]]] = None,
threshold: float = 0.5,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

label: Optional[str] = None label in EvaluatorOutput is optional.
We don't need to have a default value to generate a label when a metric doesn't have a label and the user doesn't provide any threshold. Can we set default as None?

mapping when provided. Expected keys: input, output, expected (optional).
threshold: Score threshold for Pass/Fail determination. Defaults to 0.5.
"""
self.scorer = scorer

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this naming is not aligned with DeepEval adaptor. Looks like you haven't updated AutoevalsAdapter.
If you haven't completed end to end tests for AutoevalsAdapter, you should not include it in your PR.

customer_mapper=lambda ev: LLMTestCase(
input=ev.session_spans[0]["attributes"]["user_query"],
actual_output=ev.session_spans[0]["attributes"]["response"],
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

using lambda? same as above

def __init__(
self,
metric: BaseMetric,
customer_mapper: Optional[Callable[[EvaluatorInput], LLMTestCase]] = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

naming? same as above

self.metric = metric
self.customer_mapper = customer_mapper
if model is not None:
self.metric.model = model

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Where do we use self.metric.model?

Raises:
ValueError: If no mapper can extract data from the spans.
"""
result = _strands_mapper.map(session_spans)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We can check the span scope name and then decide which mapper to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants