Interop proposal: EvalPort-compatible export for TestCase/Result (JSON boundary)
- Dominant language
- Elixir
- Stars
- 4
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Hi Holsee — really nice port. The behaviour-based metric architecture (ADR 0001/0007) and the Peri-schema-without-Ecto choice (ADR 0002/0008) make this one of the cleaner eval frameworks I've looked at, in any language.
I maintain [EvalPort](https://github.com/adhabnr-ux/evalport), an Apache-2.0 interchange format for portable LLM eval test cases, graders, and results — a JSON Schema-backed spec plus a Python/TS SDK (`evalport-sdk`), with 37 adapters already merged for frameworks like DeepEval, Ragas, MLflow, LangSmith, Phoenix, etc. The goal is: run an eval suite written for tool A, and grade/report it with tool B, without hand-translating every record.
Opening this as a proposal, not a PR, since I don't know your appetite for it — figured a concrete sketch is more useful than an abstract pitch.
## Why deep_eval_ex maps cleanly
I read `lib/deep_eval_ex/schemas/test_case.ex`, `result.ex`, and `tool_call.ex`. The field-level mapping onto EvalPort's `TestCase` / `Result` is nearly 1:1:
| `DeepEvalEx.TestCase` | EvalPort `TestCase` |
|---|---|
| `input` | `input` |
| `actual_output` | `actual_output` |
| `expected_output` | `expected_output` |
| `retrieval_context` / `context` | `retrieval_context` |
| `tools_called` / `expected_tools` (`ToolCall{name, input_parameters, output, reasoning}`) | `tools_called` / `expected_tools` |
| `metadata`, `name`, `tags` | `metadata`, `id`/`name`, `tags` |
And `DeepEvalEx.Result` (`metric`, `score`, `success`, `reason`, `threshold`, `metadata`, `evaluation_cost`, `latency_ms`) maps directly onto EvalPort `Result` — you're already emitting the exact shape EvalPort's grader-result objects expect, just as an Elixir struct instead of a JSON envelope.
Because BEAM/Elixir has no native SDK today, the natural integration point isn't a library dependency — it's a **serialization boundary**: two functions that walk the existing structs into/out of plain maps matching the EvalPort JSON Schema, using `Jason.encode!/1` (already a dep per mix.lock) at the edge. No new runtime dependency, no change to the metric behaviour.
## Sketch (illustrative, using real field names)
```elixir
defmodule DeepEvalEx.EvalPort do
@moduledoc "Serialize DeepEvalEx structs to/from the EvalPort JSON interchange format."
alias DeepEvalEx.{TestCase, Result}
@spec test_case_to_evalport(TestCase.t()) :: map()
def test_case_to_evalport(%TestCase{} = tc) do
%{
"input" => tc.input,
"actual_output" => tc.actual_output,
"expected_output" => tc.expected_output,
"retrieval_context" => TestCase.get_retrieval_context(tc),
"tools_called" => Enum.map(tc.tools_called, &tool_call_to_evalport/1),
"expected_tools" => Enum.map(tc.expected_tools, &tool_call_to_evalport/1),
"metadata" => tc.metadata,
"name" => tc.name,
"tags" => tc.tags
}
end
@spec result_to_evalport(Result.t()) :: map()
def result_to_evalport(%Result{} = r) do
%{
"grader" => r.metric,
"score" => r.score,
"success" => r.success,
"reason" => r.reason,
"threshold" => r.threshold,
"metadata" => r.metadata,
"evaluation_cost" => r.evaluation_cost,
"latency_ms" => r.latency_ms
}
end
defp tool_call_to_evalport(tc) do
%{
"name" => tc.name,
"description" => tc.description,
"reasoning" => tc.reasoning,
"input_parameters" => tc.input_parameters,
"output" => tc.output
}
end
end
```
That alone would let a `ResultSet` (a batch from `DeepEvalEx.evaluate_batch/3`) round-trip through EvalPort's format — e.g. eval suites authored against the Python DeepEval/Ragas ecosystem could be run through `deep_eval_ex`'s metrics and reported with whatever EvalPort-compatible tooling someone's already using, or vice versa.
## Comparable adapters already in EvalPort
For reference/comparison, two real, merged adapters that do the same kind of struct↔JSON bridging for adjacent tools:
- [`adapters/deepeval-openeval-adapter`](https://github.com/adhabnr-ux/evalport/tree/main/adapters/deepeval-openeval-adapter) — for the upstream Python DeepEval this project ports from, which makes it a nearly direct sibling
- [`adapters/ragas-openeval-adapter`](https://github.com/adhabnr-ux/evalport/tree/main/adapters/ragas-openeval-adapter) — relevant given your `ContextualPrecision`/`ContextualRecall`/`Faithfulness` metrics are the same RAG-eval family Ragas covers
Happy to draft the actual PR (module + tests + a `mix.exs` note, no new deps) if this is something you'd want, or to hear if the project's direction doesn't need this. No pressure either way — mostly wanted to flag the fit given how deliberately you've already structured the schemas around JSON-schema output (ADR 0006) and a database-free boundary (ADR 0002).
— Sahi, independent contributor (not affiliated with this project)
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with lib/deep_eval_ex/schemas/test_case.ex, result.ex, and tool_call.ex, then compare their fields with the EvalPort JSON Schema and the existing Jason dependency noted in mix.lock. The proposed EvalPort boundary should serialize and deserialize TestCase and Result data without changing metric behaviour; add the module and tests described in the issue and verify ResultSet round-tripping.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- elixir, json
- Domain
- backend, testing
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100