aeval-framework 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. aeval_framework-0.1.0/.coverage +0 -0
  2. aeval_framework-0.1.0/.gitignore +23 -0
  3. aeval_framework-0.1.0/PKG-INFO +42 -0
  4. aeval_framework-0.1.0/README.md +5 -0
  5. aeval_framework-0.1.0/pyproject.toml +80 -0
  6. aeval_framework-0.1.0/src/agent_eval/__init__.py +14 -0
  7. aeval_framework-0.1.0/src/agent_eval/api/__init__.py +14 -0
  8. aeval_framework-0.1.0/src/agent_eval/api/app.py +82 -0
  9. aeval_framework-0.1.0/src/agent_eval/api/events.py +96 -0
  10. aeval_framework-0.1.0/src/agent_eval/api/routes/__init__.py +0 -0
  11. aeval_framework-0.1.0/src/agent_eval/api/routes/datasets.py +441 -0
  12. aeval_framework-0.1.0/src/agent_eval/api/routes/graders.py +19 -0
  13. aeval_framework-0.1.0/src/agent_eval/api/routes/metrics.py +49 -0
  14. aeval_framework-0.1.0/src/agent_eval/api/routes/runs.py +573 -0
  15. aeval_framework-0.1.0/src/agent_eval/api/routes/suites.py +84 -0
  16. aeval_framework-0.1.0/src/agent_eval/api/routes/tasks.py +114 -0
  17. aeval_framework-0.1.0/src/agent_eval/api/standalone.py +105 -0
  18. aeval_framework-0.1.0/src/agent_eval/cli.py +455 -0
  19. aeval_framework-0.1.0/src/agent_eval/core/__init__.py +48 -0
  20. aeval_framework-0.1.0/src/agent_eval/core/contract.py +296 -0
  21. aeval_framework-0.1.0/src/agent_eval/core/metrics.py +184 -0
  22. aeval_framework-0.1.0/src/agent_eval/core/runner.py +868 -0
  23. aeval_framework-0.1.0/src/agent_eval/core/suite.py +60 -0
  24. aeval_framework-0.1.0/src/agent_eval/core/types.py +227 -0
  25. aeval_framework-0.1.0/src/agent_eval/dataset/__init__.py +31 -0
  26. aeval_framework-0.1.0/src/agent_eval/dataset/models.py +199 -0
  27. aeval_framework-0.1.0/src/agent_eval/dataset/quality.py +194 -0
  28. aeval_framework-0.1.0/src/agent_eval/dataset/sources/__init__.py +45 -0
  29. aeval_framework-0.1.0/src/agent_eval/dataset/sources/llm_generator.py +219 -0
  30. aeval_framework-0.1.0/src/agent_eval/dataset/sources/manual.py +172 -0
  31. aeval_framework-0.1.0/src/agent_eval/dataset/sources/regression.py +201 -0
  32. aeval_framework-0.1.0/src/agent_eval/dataset/sources/trace_mining.py +277 -0
  33. aeval_framework-0.1.0/src/agent_eval/dataset/storage.py +342 -0
  34. aeval_framework-0.1.0/src/agent_eval/dataset/version.py +72 -0
  35. aeval_framework-0.1.0/src/agent_eval/examples/__init__.py +0 -0
  36. aeval_framework-0.1.0/src/agent_eval/examples/basic_usage.py +175 -0
  37. aeval_framework-0.1.0/src/agent_eval/examples/mock_runner.py +195 -0
  38. aeval_framework-0.1.0/src/agent_eval/graders/__init__.py +91 -0
  39. aeval_framework-0.1.0/src/agent_eval/graders/artifact_check.py +114 -0
  40. aeval_framework-0.1.0/src/agent_eval/graders/code_based.py +101 -0
  41. aeval_framework-0.1.0/src/agent_eval/graders/human.py +77 -0
  42. aeval_framework-0.1.0/src/agent_eval/graders/metric.py +142 -0
  43. aeval_framework-0.1.0/src/agent_eval/graders/model_based.py +179 -0
  44. aeval_framework-0.1.0/src/agent_eval/graders/state_check.py +106 -0
  45. aeval_framework-0.1.0/src/agent_eval/graders/step_level.py +116 -0
  46. aeval_framework-0.1.0/src/agent_eval/graders/tool_calls.py +102 -0
  47. aeval_framework-0.1.0/src/agent_eval/graders/transcript.py +86 -0
  48. aeval_framework-0.1.0/src/agent_eval/metrics/__init__.py +110 -0
  49. aeval_framework-0.1.0/src/agent_eval/metrics/answer_relevancy.py +57 -0
  50. aeval_framework-0.1.0/src/agent_eval/metrics/base.py +155 -0
  51. aeval_framework-0.1.0/src/agent_eval/metrics/batch_evaluation.py +267 -0
  52. aeval_framework-0.1.0/src/agent_eval/metrics/context_precision.py +62 -0
  53. aeval_framework-0.1.0/src/agent_eval/metrics/context_recall.py +71 -0
  54. aeval_framework-0.1.0/src/agent_eval/metrics/faithfulness.py +72 -0
  55. aeval_framework-0.1.0/src/agent_eval/metrics/llm_judge.py +100 -0
  56. aeval_framework-0.1.0/src/agent_eval/metrics/prompt_metric.py +150 -0
  57. aeval_framework-0.1.0/src/agent_eval/metrics/pytest_plugin.py +308 -0
  58. aeval_framework-0.1.0/src/agent_eval/metrics/report.py +149 -0
  59. aeval_framework-0.1.0/src/agent_eval/metrics/synthetic_data.py +203 -0
  60. aeval_framework-0.1.0/src/agent_eval/storage/__init__.py +17 -0
  61. aeval_framework-0.1.0/src/agent_eval/storage/memory.py +95 -0
  62. aeval_framework-0.1.0/src/agent_eval/storage/sqlite.py +240 -0
  63. aeval_framework-0.1.0/src/agent_eval/trace/__init__.py +16 -0
  64. aeval_framework-0.1.0/src/agent_eval/trace/phoenix.py +144 -0
  65. aeval_framework-0.1.0/tests/conftest.py +9 -0
  66. aeval_framework-0.1.0/tests/test_api.py +325 -0
  67. aeval_framework-0.1.0/tests/test_builtin_graders.py +298 -0
  68. aeval_framework-0.1.0/tests/test_cli.py +280 -0
  69. aeval_framework-0.1.0/tests/test_e2e.py +243 -0
  70. aeval_framework-0.1.0/tests/test_eval_batch_evaluation.py +298 -0
  71. aeval_framework-0.1.0/tests/test_eval_dataset_api.py +409 -0
  72. aeval_framework-0.1.0/tests/test_eval_dataset_sources.py +596 -0
  73. aeval_framework-0.1.0/tests/test_eval_dataset_storage.py +185 -0
  74. aeval_framework-0.1.0/tests/test_eval_metric_pipeline.py +319 -0
  75. aeval_framework-0.1.0/tests/test_eval_metrics_api.py +157 -0
  76. aeval_framework-0.1.0/tests/test_eval_metrics_module.py +348 -0
  77. aeval_framework-0.1.0/tests/test_eval_prompt_metric.py +159 -0
  78. aeval_framework-0.1.0/tests/test_eval_pytest_plugin.py +218 -0
  79. aeval_framework-0.1.0/tests/test_eval_report.py +206 -0
  80. aeval_framework-0.1.0/tests/test_eval_task_history.py +167 -0
  81. aeval_framework-0.1.0/tests/test_graders.py +204 -0
  82. aeval_framework-0.1.0/tests/test_import_isolation.py +83 -0
  83. aeval_framework-0.1.0/tests/test_metrics.py +130 -0
  84. aeval_framework-0.1.0/tests/test_runner.py +555 -0
  85. aeval_framework-0.1.0/tests/test_sse.py +173 -0
  86. aeval_framework-0.1.0/tests/test_standalone_api.py +127 -0
  87. aeval_framework-0.1.0/tests/test_suite.py +209 -0
Binary file
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ *.db
11
+ *.sqlite3
12
+
13
+ # Node / Next.js (dashboard)
14
+ node_modules/
15
+ .next/
16
+ out/
17
+ *.tsbuildinfo
18
+
19
+ # Env & OS
20
+ .env
21
+ .env.*
22
+ .DS_Store
23
+ Thumbs.db
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.5
2
+ Name: aeval-framework
3
+ Version: 0.1.0
4
+ Summary: Aeval — open-source agent evaluation framework driven by OTel traces (suites, graders, metrics, storage, API, CLI)
5
+ Project-URL: Homepage, https://github.com/QiYuyyds/Aeval
6
+ Project-URL: Repository, https://github.com/QiYuyyds/Aeval
7
+ Project-URL: Documentation, https://github.com/QiYuyyds/Aeval/tree/main/docs
8
+ Project-URL: Changelog, https://github.com/QiYuyyds/Aeval/releases
9
+ Author: Aeval contributors
10
+ License: MIT
11
+ Keywords: agents,evaluation,llm,otel,testing,tracing
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: aiosqlite>=0.20.0
21
+ Requires-Dist: pydantic>=2.10.0
22
+ Requires-Dist: pyyaml>=6.0.0
23
+ Provides-Extra: api
24
+ Requires-Dist: fastapi>=0.115.0; extra == 'api'
25
+ Requires-Dist: sse-starlette>=2.2.0; extra == 'api'
26
+ Requires-Dist: uvicorn[standard]>=0.32.0; extra == 'api'
27
+ Provides-Extra: cli
28
+ Requires-Dist: rich>=13.0.0; extra == 'cli'
29
+ Requires-Dist: typer>=0.12.0; extra == 'cli'
30
+ Provides-Extra: dev
31
+ Requires-Dist: httpx>=0.28.0; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
34
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # agent-eval
39
+
40
+ Aeval — open-source agent evaluation framework driven by OTel traces.
41
+
42
+ See the repository root [README](../../README.md) (English) / [中文说明](../../README.zh-CN.md) and the [docs](../../docs/) for full documentation.
@@ -0,0 +1,5 @@
1
+ # agent-eval
2
+
3
+ Aeval — open-source agent evaluation framework driven by OTel traces.
4
+
5
+ See the repository root [README](../../README.md) (English) / [中文说明](../../README.zh-CN.md) and the [docs](../../docs/) for full documentation.
@@ -0,0 +1,80 @@
1
+ [project]
2
+ # Distribution name: aeval-framework (PyPI). Python module: agent_eval, CLI: eval-suite.
3
+ # (`agent-eval` on PyPI belongs to an unrelated project — UK AISI agenteval.)
4
+ name = "aeval-framework"
5
+ version = "0.1.0"
6
+ description = "Aeval — open-source agent evaluation framework driven by OTel traces (suites, graders, metrics, storage, API, CLI)"
7
+ readme = "README.md"
8
+ requires-python = ">=3.11"
9
+ license = { text = "MIT" }
10
+ authors = [{ name = "Aeval contributors" }]
11
+ keywords = ["agents", "evaluation", "llm", "otel", "tracing", "testing"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development :: Testing",
20
+ ]
21
+
22
+ dependencies = [
23
+ "pydantic>=2.10.0",
24
+ "pyyaml>=6.0.0",
25
+ "aiosqlite>=0.20.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ # REST API / SSE service (create_app / create_standalone_app)
30
+ api = [
31
+ "fastapi>=0.115.0",
32
+ "sse-starlette>=2.2.0",
33
+ "uvicorn[standard]>=0.32.0",
34
+ ]
35
+ # eval-suite command line
36
+ cli = [
37
+ "typer>=0.12.0",
38
+ "rich>=13.0.0",
39
+ ]
40
+ dev = [
41
+ "pytest>=8.3.0",
42
+ "pytest-asyncio>=0.24.0",
43
+ "pytest-cov>=6.0.0",
44
+ "httpx>=0.28.0",
45
+ "ruff>=0.8.0",
46
+ ]
47
+
48
+ [project.scripts]
49
+ eval-suite = "agent_eval.cli:main"
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/QiYuyyds/Aeval"
53
+ Repository = "https://github.com/QiYuyyds/Aeval"
54
+ Documentation = "https://github.com/QiYuyyds/Aeval/tree/main/docs"
55
+ Changelog = "https://github.com/QiYuyyds/Aeval/releases"
56
+
57
+ [build-system]
58
+ requires = ["hatchling"]
59
+ build-backend = "hatchling.build"
60
+
61
+ [tool.hatch.build.targets.wheel]
62
+ packages = ["src/agent_eval"]
63
+
64
+ [tool.pytest.ini_options]
65
+ asyncio_mode = "auto"
66
+ testpaths = ["tests"]
67
+
68
+ [tool.ruff]
69
+ target-version = "py311"
70
+ line-length = 100
71
+
72
+ [tool.ruff.lint]
73
+ select = ["E", "F", "I", "UP", "B", "SIM"]
74
+ ignore = [
75
+ "E501",
76
+ "B008",
77
+ # (str, Enum) mixins are intentional: StrEnum would change str(member) /
78
+ # f-string serialization to the bare value and break API/storage payloads.
79
+ "UP042",
80
+ ]
@@ -0,0 +1,14 @@
1
+ """
2
+ Aeval — Agent Evaluation Framework
3
+
4
+ A reusable, open-source evaluation framework for AI agents, driven by OTel traces.
5
+
6
+ Usage:
7
+ from agent_eval import EvalRunner, EvalSuite
8
+
9
+ runner = EvalRunner(agent_runner=my_runner)
10
+ suite = EvalSuite.from_yaml("suite.yaml")
11
+ result = await runner.run_suite(suite)
12
+ """
13
+
14
+ __version__ = "0.1.0"
@@ -0,0 +1,14 @@
1
+ """
2
+ REST API for the Aeval evaluation framework.
3
+
4
+ Provides FastAPI routes for managing suites, runs, and viewing results.
5
+
6
+ Usage:
7
+ from agent_eval.api import create_app
8
+
9
+ app = create_app(runner=my_eval_runner)
10
+ """
11
+
12
+ from agent_eval.api.app import create_app
13
+
14
+ __all__ = ["create_app"]
@@ -0,0 +1,82 @@
1
+ """
2
+ FastAPI application factory for Aeval.
3
+
4
+ Creates a FastAPI app with all eval routes mounted.
5
+ Can be used standalone or mounted in an existing app.
6
+
7
+ Usage:
8
+ # Standalone
9
+ app = create_app(runner=my_runner)
10
+
11
+ # Mounted in existing FastAPI app
12
+ from fastapi import FastAPI
13
+ app = FastAPI()
14
+ eval_app = create_app(runner=my_runner)
15
+ app.mount("/api/eval", eval_app)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from fastapi import FastAPI
21
+
22
+ from agent_eval.api.routes import datasets, graders, metrics, runs, suites, tasks
23
+ from agent_eval.core.runner import EvalRunner
24
+
25
+ # Global runner reference (set by create_app)
26
+ _runner: EvalRunner | None = None
27
+
28
+
29
+ def _get_runner() -> EvalRunner | None:
30
+ """Get the global EvalRunner instance"""
31
+ return _runner
32
+
33
+
34
+ def set_runner(runner: EvalRunner | None) -> None:
35
+ """Set the global EvalRunner instance.
36
+
37
+ Used by the host app to inject the real runner during async startup
38
+ (e.g. main.py lifespan → eval_integration.config.create_aeval_runner).
39
+ """
40
+ global _runner
41
+ _runner = runner
42
+
43
+
44
+ def create_app(runner: EvalRunner | None = None) -> FastAPI:
45
+ """
46
+ Create a FastAPI app with Aeval routes.
47
+
48
+ Args:
49
+ runner: EvalRunner instance. If None, routes will return 503.
50
+
51
+ Returns:
52
+ FastAPI application
53
+ """
54
+ app = FastAPI(
55
+ title="Aeval API",
56
+ version="0.1.0",
57
+ description="Agent Evaluation Framework API",
58
+ )
59
+
60
+ # Store runner in app state and global reference
61
+ global _runner
62
+ _runner = runner
63
+ app.state.runner = runner
64
+
65
+ # Include routers
66
+ app.include_router(suites.router, prefix="/suites", tags=["suites"])
67
+ app.include_router(tasks.router, prefix="/tasks", tags=["tasks"])
68
+ app.include_router(runs.router, prefix="/runs", tags=["runs"])
69
+ # compare 挂在 /compare (spec §REST API), 不带 /runs 前缀
70
+ app.include_router(runs.compare_router, tags=["compare"])
71
+ app.include_router(graders.router, prefix="/graders", tags=["graders"])
72
+ # 数据集管理 (change ③: 数据集构建闭环 — CRUD/导入/挖掘/生成/质量/to-suite)
73
+ app.include_router(datasets.router, prefix="/datasets", tags=["datasets"])
74
+ # 批量评测 (change ④: 对已有输出直接批量打分 — POST /metrics/batch)
75
+ app.include_router(metrics.router, prefix="/metrics", tags=["metrics"])
76
+
77
+ @app.get("/health")
78
+ async def health():
79
+ # 动态读取全局 runner — host app 会在 startup 阶段注入真实 runner
80
+ return {"status": "ok", "runner_configured": _get_runner() is not None}
81
+
82
+ return app
@@ -0,0 +1,96 @@
1
+ """Run 事件总线 — SSE 实时进度的进程内 fan-out (任务 3.1)。
2
+
3
+ 协议 (设计文档 D3 / §17.3):
4
+ - EvalRunner 进度回调 → per-run 订阅者队列扇出; 事件不落库, 断线恢复
5
+ 完全依赖快照重拉 (GET /runs/{run_id}) + 重订阅
6
+ - 事件类型: task_start / trial_start / trial_complete / task_complete /
7
+ run_complete / error; 载荷含 task_id 与 trial_index (适用时), 按
8
+ (task_id, trial_index) 幂等 — 重复事件由客户端快照自愈兜底
9
+ - run 生命周期由服务端后台任务持有, 与观察连接解耦
10
+
11
+ 终态保留: run_complete 事件缓存于进程内 (有界, LIFO 淘汰), 供 run 结束后
12
+ 订阅的客户端立即收终态并关流; 进程重启后缓存消失, 客户端以快照为准。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import contextlib
19
+ import time
20
+ from collections import OrderedDict
21
+ from typing import Any
22
+
23
+ # 有界队列: 慢订阅者丢最旧事件 (快照可自愈), 不阻塞 run 主流程
24
+ _QUEUE_MAXSIZE = 256
25
+ # 终态事件缓存上限 (防止长进程内存无界增长)
26
+ _TERMINAL_CACHE_MAX = 500
27
+
28
+ TERMINAL_EVENT_TYPES = {"run_complete"}
29
+
30
+
31
+ class RunEventBus:
32
+ """run_id → 订阅者队列集合的事件扇出枢纽 (单事件循环, 无锁)。"""
33
+
34
+ def __init__(self) -> None:
35
+ self._subscribers: dict[str, set[asyncio.Queue]] = {}
36
+ self._terminal: OrderedDict[str, dict[str, Any]] = OrderedDict()
37
+
38
+ # ── 发布 ─────────────────────────────────────────────────────────────
39
+
40
+ def publish(self, run_id: str, event_type: str, data: dict[str, Any] | None = None) -> None:
41
+ """发布一条 run 事件 (非阻塞)。"""
42
+ event: dict[str, Any] = {
43
+ "type": event_type,
44
+ "run_id": run_id,
45
+ "timestamp": time.time() * 1000,
46
+ **(data or {}),
47
+ }
48
+ if event_type in TERMINAL_EVENT_TYPES:
49
+ self._terminal[run_id] = event
50
+ self._terminal.move_to_end(run_id)
51
+ while len(self._terminal) > _TERMINAL_CACHE_MAX:
52
+ self._terminal.popitem(last=False)
53
+
54
+ for queue in self._subscribers.get(run_id, set()):
55
+ _offer(queue, event)
56
+
57
+ # ── 订阅 ─────────────────────────────────────────────────────────────
58
+
59
+ def subscribe(self, run_id: str) -> asyncio.Queue:
60
+ """注册订阅者队列; run 已有缓存终态时立即注入终态事件。"""
61
+ queue: asyncio.Queue = asyncio.Queue(maxsize=_QUEUE_MAXSIZE)
62
+ self._subscribers.setdefault(run_id, set()).add(queue)
63
+ terminal = self._terminal.get(run_id)
64
+ if terminal is not None:
65
+ _offer(queue, terminal)
66
+ return queue
67
+
68
+ def unsubscribe(self, run_id: str, queue: asyncio.Queue) -> None:
69
+ """注销订阅者 (SSE 断开时调用, 防泄漏)。"""
70
+ subscribers = self._subscribers.get(run_id)
71
+ if subscribers is None:
72
+ return
73
+ subscribers.discard(queue)
74
+ if not subscribers:
75
+ self._subscribers.pop(run_id, None)
76
+
77
+ def subscriber_count(self, run_id: str) -> int:
78
+ return len(self._subscribers.get(run_id, set()))
79
+
80
+ def terminal_event(self, run_id: str) -> dict[str, Any] | None:
81
+ return self._terminal.get(run_id)
82
+
83
+
84
+ def _offer(queue: asyncio.Queue, event: dict[str, Any]) -> None:
85
+ """put_nowait + 最旧丢弃溢出策略 (与 AChat event_bus 行为一致)。"""
86
+ try:
87
+ queue.put_nowait(event)
88
+ except asyncio.QueueFull:
89
+ with contextlib.suppress(asyncio.QueueEmpty):
90
+ queue.get_nowait()
91
+ with contextlib.suppress(asyncio.QueueFull): # pragma: no cover - 理论不可达
92
+ queue.put_nowait(event)
93
+
94
+
95
+ # 全局单例 (与 eval API 子应用同生命周期)
96
+ run_event_bus = RunEventBus()