agentevals-cli 0.9.6__tar.gz → 0.9.7__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 (293) hide show
  1. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/PKG-INFO +1 -1
  2. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/agentevals_example_custom_sink/sink.py +1 -1
  3. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/app.py +7 -3
  4. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/debug_routes.py +1 -1
  5. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/models.py +18 -0
  6. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_processing.py +11 -4
  7. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/routes.py +19 -10
  8. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/streaming_routes.py +32 -19
  9. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/cli.py +2 -2
  10. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/eval_config_loader.py +1 -1
  11. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/sources.py +3 -2
  12. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/venv.py +6 -2
  13. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/extraction.py +72 -3
  14. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/auto.py +1 -1
  15. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/jaeger.py +1 -1
  16. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/otlp.py +9 -3
  17. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/mcp_server.py +1 -1
  18. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/fetcher.py +2 -2
  19. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/sinks.py +1 -1
  20. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/runner.py +1 -1
  21. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/config.py +1 -1
  22. agentevals_cli-0.9.7/src/agentevals/streaming/exports.py +41 -0
  23. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/processor.py +3 -0
  24. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/ws_server.py +4 -3
  25. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/trace_attrs.py +8 -0
  26. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/trace_metrics.py +7 -0
  27. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_sinks.py +6 -6
  28. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_config.py +1 -1
  29. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_api.py +328 -13
  30. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_cli.py +6 -4
  31. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_eval_config_loader.py +2 -1
  32. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_extraction.py +141 -0
  33. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_jaeger_loader.py +7 -7
  34. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_loader_auto.py +1 -1
  35. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_openai_eval_backend.py +2 -1
  36. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_otlp_loader.py +4 -4
  37. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_runner.py +40 -2
  38. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/eval/SKILL.md +0 -0
  39. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/eval/evals/evals.json +0 -0
  40. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/inspect/SKILL.md +0 -0
  41. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/inspect/evals/evals.json +0 -0
  42. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.dockerignore +0 -0
  43. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  44. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  45. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  46. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/ci.yml +0 -0
  47. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/publish-evaluator-sdk.yml +0 -0
  48. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/release.yml +0 -0
  49. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.gitignore +0 -0
  50. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.mcp.json +0 -0
  51. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/CONTRIBUTING.md +0 -0
  52. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/DEVELOPMENT.md +0 -0
  53. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/Dockerfile +0 -0
  54. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/LICENSE +0 -0
  55. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/Makefile +0 -0
  56. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/README.md +0 -0
  57. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/Chart.yaml +0 -0
  58. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/NOTES.txt +0 -0
  59. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/_helpers.tpl +0 -0
  60. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/deployment.yaml +0 -0
  61. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/postgresql-secret.yaml +0 -0
  62. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/postgresql.yaml +0 -0
  63. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/rbac.yaml +0 -0
  64. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/service.yaml +0 -0
  65. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/serviceaccount.yaml +0 -0
  66. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/values.yaml +0 -0
  67. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-color-on-transparent.svg +0 -0
  68. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-color.png +0 -0
  69. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-dark-on-transparent.svg +0 -0
  70. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/custom-evaluators.md +0 -0
  71. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/eval-set-format.md +0 -0
  72. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/otel-compatibility.md +0 -0
  73. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/run-history.md +0 -0
  74. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/streaming.md +0 -0
  75. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/README.md +0 -0
  76. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/eval_config.yaml +0 -0
  77. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/eval_config_openai_eval.yaml +0 -0
  78. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/response_quality.py +0 -0
  79. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/tool_call_checker.py +0 -0
  80. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/README.md +0 -0
  81. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/agentevals_example_custom_sink/__init__.py +0 -0
  82. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/pyproject.toml +0 -0
  83. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/README.md +0 -0
  84. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/agent.py +0 -0
  85. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/eval_set.json +0 -0
  86. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/main.py +0 -0
  87. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/test_streaming.py +0 -0
  88. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/kubernetes/README.md +0 -0
  89. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/README.md +0 -0
  90. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/agent.py +0 -0
  91. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/eval_set.json +0 -0
  92. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/main.py +0 -0
  93. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/requirements.txt +0 -0
  94. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/test_streaming.py +0 -0
  95. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/async_example.py +0 -0
  96. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/context_manager_example.py +0 -0
  97. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/decorator_example.py +0 -0
  98. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/requirements.txt +0 -0
  99. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/agent.py +0 -0
  100. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/eval_set.json +0 -0
  101. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/main.py +0 -0
  102. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/requirements.txt +0 -0
  103. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/adk/requirements.txt +0 -0
  104. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/adk/run.py +0 -0
  105. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/langchain/requirements.txt +0 -0
  106. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/langchain/run.py +0 -0
  107. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/ollama/requirements.txt +0 -0
  108. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/ollama/run.py +0 -0
  109. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/openai-agents/requirements.txt +0 -0
  110. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/openai-agents/run.py +0 -0
  111. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/pydantic-ai/requirements.txt +0 -0
  112. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/pydantic-ai/run.py +0 -0
  113. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/strands/requirements.txt +0 -0
  114. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/strands/run.py +0 -0
  115. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/flake.lock +0 -0
  116. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/flake.nix +0 -0
  117. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/README.md +0 -0
  118. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/pyproject.toml +0 -0
  119. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/__init__.py +0 -0
  120. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/decorator.py +0 -0
  121. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/types.py +0 -0
  122. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/pyproject.toml +0 -0
  123. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/eval_set_helm.json +0 -0
  124. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/evalset_helm_3_2026-02-23.json +0 -0
  125. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/evalset_k8s_2026-02-20.json +0 -0
  126. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm.json +0 -0
  127. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm_2.json +0 -0
  128. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm_3.json +0 -0
  129. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/k8s.json +0 -0
  130. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/tempo_export_with_batches.json +0 -0
  131. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/__init__.py +0 -0
  132. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_protocol.py +0 -0
  133. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/assets/index-4Q_gYF8x.js +0 -0
  134. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/assets/index-BqibLiHO.css +0 -0
  135. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/index.html +0 -0
  136. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/logo.svg +0 -0
  137. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/vite.svg +0 -0
  138. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/__init__.py +0 -0
  139. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/dependencies.py +0 -0
  140. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_app.py +0 -0
  141. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_grpc.py +0 -0
  142. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_routes.py +0 -0
  143. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/runs_routes.py +0 -0
  144. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/builtin_metrics.py +0 -0
  145. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/config.py +0 -0
  146. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/converter.py +0 -0
  147. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/custom_evaluators.py +0 -0
  148. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/__init__.py +0 -0
  149. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/resolver.py +0 -0
  150. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/templates.py +0 -0
  151. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/genai_converter.py +0 -0
  152. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/__init__.py +0 -0
  153. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/base.py +0 -0
  154. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/openai_eval_backend.py +0 -0
  155. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/output.py +0 -0
  156. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/resolvers/__init__.py +0 -0
  157. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/resolvers/kubernetes.py +0 -0
  158. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/__init__.py +0 -0
  159. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/result_builder.py +0 -0
  160. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/service.py +0 -0
  161. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/worker.py +0 -0
  162. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/sdk.py +0 -0
  163. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/__init__.py +0 -0
  164. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/models.py +0 -0
  165. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/__init__.py +0 -0
  166. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrations/000001_init.down.sql +0 -0
  167. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrations/000001_init.up.sql +0 -0
  168. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrator.py +0 -0
  169. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/pool.py +0 -0
  170. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/__init__.py +0 -0
  171. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/memory.py +0 -0
  172. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/postgres.py +0 -0
  173. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/__init__.py +0 -0
  174. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/incremental_processor.py +0 -0
  175. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/session.py +0 -0
  176. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/__init__.py +0 -0
  177. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/genai_messages.py +0 -0
  178. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/log_buffer.py +0 -0
  179. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/log_enrichment.py +0 -0
  180. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/__init__.py +0 -0
  181. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/test_evaluate_persistence.py +0 -0
  182. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/test_runs_routes.py +0 -0
  183. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/__init__.py +0 -0
  184. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/conftest.py +0 -0
  185. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_evaluation_pipeline.py +0 -0
  186. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_live_agents.py +0 -0
  187. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_otlp_grpc_receiver.py +0 -0
  188. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_session_grouping.py +0 -0
  189. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_timing_stress.py +0 -0
  190. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/__init__.py +0 -0
  191. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/test_kubernetes.py +0 -0
  192. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/test_registry.py +0 -0
  193. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/__init__.py +0 -0
  194. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_fetcher.py +0 -0
  195. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_result_builder.py +0 -0
  196. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_service.py +0 -0
  197. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/__init__.py +0 -0
  198. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_memory_repos.py +0 -0
  199. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_migrator.py +0 -0
  200. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_models.py +0 -0
  201. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_converter.py +0 -0
  202. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_credential_injection.py +0 -0
  203. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_genai_converter.py +0 -0
  204. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_log_enrichment.py +0 -0
  205. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_mcp_server.py +0 -0
  206. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_otlp_receiver.py +0 -0
  207. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_output.py +0 -0
  208. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_protocol.py +0 -0
  209. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_sdk.py +0 -0
  210. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_trace_metrics.py +0 -0
  211. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/.gitignore +0 -0
  212. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/README.md +0 -0
  213. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/eslint.config.js +0 -0
  214. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/index.html +0 -0
  215. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/package-lock.json +0 -0
  216. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/package.json +0 -0
  217. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/public/logo.svg +0 -0
  218. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/public/vite.svg +0 -0
  219. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/App.css +0 -0
  220. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/App.tsx +0 -0
  221. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/api/client.ts +0 -0
  222. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/assets/react.svg +0 -0
  223. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationDetailPanel.tsx +0 -0
  224. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationQueueView.tsx +0 -0
  225. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationTable.tsx +0 -0
  226. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/bug-report/BugReportModal.tsx +0 -0
  227. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/BuilderHeader.tsx +0 -0
  228. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/BuilderView.tsx +0 -0
  229. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/EvalCaseCard.tsx +0 -0
  230. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/EvalCasesList.tsx +0 -0
  231. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/InvocationEditor.tsx +0 -0
  232. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/JsonPreview.tsx +0 -0
  233. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/MetadataEditor.tsx +0 -0
  234. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/TraceUploadZone.tsx +0 -0
  235. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/index.ts +0 -0
  236. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/DashboardView.tsx +0 -0
  237. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/MetricScoreCard.tsx +0 -0
  238. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/PerformanceCard.tsx +0 -0
  239. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/PerformanceCharts.tsx +0 -0
  240. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/SummaryStats.tsx +0 -0
  241. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/TraceCard.tsx +0 -0
  242. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/TraceTable.tsx +0 -0
  243. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/ComparisonPanel.tsx +0 -0
  244. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/DataSection.tsx +0 -0
  245. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorHeader.tsx +0 -0
  246. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorLayout.tsx +0 -0
  247. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorView.tsx +0 -0
  248. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InvocationCard.tsx +0 -0
  249. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InvocationSummaryPanel.tsx +0 -0
  250. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/MetricResultsSection.tsx +0 -0
  251. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/MetricsComparisonSection.tsx +0 -0
  252. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/PerformanceSection.tsx +0 -0
  253. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/ToolCallList.tsx +0 -0
  254. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/TrajectoryComparisonDetails.tsx +0 -0
  255. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/PassRateTrendChart.tsx +0 -0
  256. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/PerMetricTrendChart.tsx +0 -0
  257. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunDetailView.tsx +0 -0
  258. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunsHistoryTable.tsx +0 -0
  259. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunsView.tsx +0 -0
  260. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/runHistory.ts +0 -0
  261. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/sidebar/Sidebar.tsx +0 -0
  262. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveConversationPanel.tsx +0 -0
  263. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveMessage.tsx +0 -0
  264. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveStreamingView.tsx +0 -0
  265. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/SessionCard.tsx +0 -0
  266. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/SessionMetadata.tsx +0 -0
  267. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/EvalSetEditorDrawer.tsx +0 -0
  268. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/FileDropZone.tsx +0 -0
  269. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/MetricSelector.tsx +0 -0
  270. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/RawJsonPreview.tsx +0 -0
  271. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/TraceEditorDrawer.tsx +0 -0
  272. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/UploadView.tsx +0 -0
  273. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/welcome/WelcomeView.tsx +0 -0
  274. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/config.ts +0 -0
  275. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/context/TraceContext.tsx +0 -0
  276. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/context/TraceProvider.tsx +0 -0
  277. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/index.css +0 -0
  278. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/console-capture.ts +0 -0
  279. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/eval-config.ts +0 -0
  280. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/evalset-builder.ts +0 -0
  281. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/network-capture.ts +0 -0
  282. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-helpers.ts +0 -0
  283. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-loader.ts +0 -0
  284. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-metadata.ts +0 -0
  285. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-patcher.ts +0 -0
  286. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/types.ts +0 -0
  287. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/utils.ts +0 -0
  288. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/main.tsx +0 -0
  289. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.app.json +0 -0
  290. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.json +0 -0
  291. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.node.json +0 -0
  292. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/vite.config.ts +0 -0
  293. {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentevals-cli
3
- Version: 0.9.6
3
+ Version: 0.9.7
4
4
  Summary: Standalone framework to evaluate agent correctness based on portable OpenTelemetry traces
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -38,7 +38,7 @@ class DemoNdjsonSink(ResultSink):
38
38
  async def _write(self, payload: dict) -> None:
39
39
  async with self._lock:
40
40
  self._path.parent.mkdir(parents=True, exist_ok=True)
41
- with self._path.open("a") as f: # noqa: ASYNC230
41
+ with self._path.open("a", encoding="utf-8") as f: # noqa: ASYNC230
42
42
  f.write(json.dumps(payload) + "\n")
43
43
 
44
44
  async def emit_partial(self, run_id: UUID, results: list[Result], attempt: int) -> None:
@@ -141,6 +141,7 @@ def create_app(
141
141
  *,
142
142
  trace_manager: StreamingTraceManager | None = None,
143
143
  enable_streaming: bool = False,
144
+ static_dir: Path | None = None,
144
145
  ) -> FastAPI:
145
146
  """Create the main agentevals API app."""
146
147
  app = FastAPI(
@@ -216,7 +217,8 @@ def create_app(
216
217
  },
217
218
  )
218
219
 
219
- static_dir = Path(__file__).parent.parent / "_static"
220
+ if static_dir is None:
221
+ static_dir = Path(__file__).parent.parent / "_static"
220
222
  has_ui = static_dir.is_dir() and (static_dir / "index.html").exists()
221
223
 
222
224
  if has_ui and not os.getenv("AGENTEVALS_HEADLESS"):
@@ -229,10 +231,12 @@ def create_app(
229
231
  async def root():
230
232
  return FileResponse(static_dir / "index.html")
231
233
 
234
+ static_root = static_dir.resolve()
235
+
232
236
  @app.get("/{path:path}")
233
237
  async def spa_fallback(path: str):
234
- file_path = static_dir / path
235
- if file_path.is_file():
238
+ file_path = (static_dir / path).resolve()
239
+ if file_path.is_relative_to(static_root) and file_path.is_file():
236
240
  return FileResponse(file_path)
237
241
  return FileResponse(static_dir / "index.html")
238
242
 
@@ -114,7 +114,7 @@ def _collect_temp_files(session_ids: set[str] | None = None) -> dict[str, str]:
114
114
  if sid not in session_ids:
115
115
  continue
116
116
  try:
117
- with open(path) as f:
117
+ with open(path, encoding="utf-8") as f:
118
118
  files[basename] = f.read()
119
119
  except OSError:
120
120
  logger.debug("Could not read temp file %s", path)
@@ -119,6 +119,24 @@ class TraceConversionMetadata(CamelModel):
119
119
  model: str | None = None
120
120
  response_model: str | None = None
121
121
  provider: str | None = None
122
+ schema_version: str | None = Field(
123
+ default=None,
124
+ description=(
125
+ "Version segment parsed from the emitting scope's schema URL "
126
+ "(the path component after the schema family prefix). Per the "
127
+ "OpenTelemetry schema URL spec, this version is only meaningful "
128
+ "within its schema family — the host/path prefix before the "
129
+ "version. It is comparable across traces only if they share a "
130
+ "family, and equals the OpenTelemetry Semantic Conventions "
131
+ "version only for the OTel family "
132
+ "(https://opentelemetry.io/schemas/<version>). For traces from "
133
+ "a custom or mirrored schema family, this is that family's own "
134
+ "version number, not an OTel semconv version. The full schema "
135
+ "URL (otel.schema_url) is preserved separately wherever the "
136
+ "family needs to be disambiguated; this field is informational "
137
+ "only and does not drive extraction or alias-resolution logic."
138
+ ),
139
+ )
122
140
  start_time: int | None = None
123
141
  user_input_preview: str | None = None
124
142
  final_output_preview: str | None = None
@@ -19,6 +19,7 @@ from ..trace_attrs import (
19
19
  OTEL_GENAI_CONVERSATION_ID,
20
20
  OTEL_GENAI_INPUT_MESSAGES,
21
21
  OTEL_GENAI_OUTPUT_MESSAGES,
22
+ OTEL_SCHEMA_URL,
22
23
  OTEL_SCOPE,
23
24
  OTEL_SCOPE_VERSION,
24
25
  )
@@ -38,6 +39,7 @@ async def process_traces(body: dict, manager: StreamingTraceManager) -> None:
38
39
  for resource_span in body.get("resourceSpans", []):
39
40
  resource_attrs = resource_span.get("resource", {}).get("attributes", [])
40
41
  metadata = _extract_agentevals_metadata(resource_attrs)
42
+ resource_schema_url = resource_span.get("schemaUrl", "")
41
43
 
42
44
  if not metadata.get("conversation_id"):
43
45
  metadata["conversation_id"] = _prescan_conversation_id(resource_span)
@@ -46,9 +48,10 @@ async def process_traces(body: dict, manager: StreamingTraceManager) -> None:
46
48
  scope = scope_span.get("scope", {})
47
49
  scope_name = scope.get("name", "")
48
50
  scope_version = scope.get("version", "")
51
+ schema_url = scope_span.get("schemaUrl", "") or resource_schema_url
49
52
 
50
53
  for span_data in scope_span.get("spans", []):
51
- span = _normalize_span(span_data, scope_name, scope_version)
54
+ span = _normalize_span(span_data, scope_name, scope_version, schema_url)
52
55
  trace_id = span.get("traceId", "")
53
56
 
54
57
  if not trace_id:
@@ -200,12 +203,13 @@ def fix_protobuf_id_fields(data) -> None:
200
203
  _GENAI_EVENT_KEYS = {OTEL_GENAI_INPUT_MESSAGES, OTEL_GENAI_OUTPUT_MESSAGES}
201
204
 
202
205
 
203
- def _normalize_span(span_data: dict, scope_name: str, scope_version: str) -> dict:
206
+ def _normalize_span(span_data: dict, scope_name: str, scope_version: str, schema_url: str = "") -> dict:
204
207
  """Normalize an OTLP span for the downstream pipeline.
205
208
 
206
209
  Performs two transformations:
207
- 1. Injects otel.scope.name/version from the scopeSpans level into span
208
- attributes (the pipeline expects them there).
210
+ 1. Injects otel.scope.name/version (and otel.schema_url) from the
211
+ scopeSpans level into span attributes (the pipeline expects them
212
+ there).
209
213
  2. Promotes gen_ai.input.messages and gen_ai.output.messages from span
210
214
  events to span attributes. Some SDKs (e.g. Strands with
211
215
  OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental) store
@@ -222,6 +226,9 @@ def _normalize_span(span_data: dict, scope_name: str, scope_version: str) -> dic
222
226
  if scope_version and OTEL_SCOPE_VERSION not in existing_keys:
223
227
  attrs.append({"key": OTEL_SCOPE_VERSION, "value": {"stringValue": scope_version}})
224
228
  existing_keys.add(OTEL_SCOPE_VERSION)
229
+ if schema_url and OTEL_SCHEMA_URL not in existing_keys:
230
+ attrs.append({"key": OTEL_SCHEMA_URL, "value": {"stringValue": schema_url}})
231
+ existing_keys.add(OTEL_SCHEMA_URL)
225
232
 
226
233
  for event in span.get("events", []):
227
234
  for attr in event.get("attributes", []):
@@ -119,6 +119,15 @@ def _camel_keys(obj: Any) -> Any:
119
119
  return obj
120
120
 
121
121
 
122
+ def _safe_upload_path(temp_dir: str, filename: str, idx: int = 0) -> str:
123
+ """Build a save path inside ``temp_dir`` from a client-controlled filename.
124
+
125
+ ``basename`` drops any directory components, and the ``idx`` prefix keeps
126
+ same-named uploads from clobbering each other.
127
+ """
128
+ return os.path.join(temp_dir, f"{idx}_{os.path.basename(filename)}")
129
+
130
+
122
131
  def _load_eval_set_dict(path: str | None) -> dict | None:
123
132
  """Read the uploaded eval set file back into a dict for persistence.
124
133
 
@@ -129,7 +138,7 @@ def _load_eval_set_dict(path: str | None) -> dict | None:
129
138
  if not path:
130
139
  return None
131
140
  try:
132
- with open(path) as f:
141
+ with open(path, encoding="utf-8") as f:
133
142
  return json.load(f)
134
143
  except (OSError, json.JSONDecodeError):
135
144
  logger.warning("could not re-read eval_set file at %s for persistence", path)
@@ -361,7 +370,7 @@ async def validate_eval_set(
361
370
  ):
362
371
  temp_dir = tempfile.mkdtemp()
363
372
  try:
364
- eval_set_path = os.path.join(temp_dir, eval_set_file.filename or "eval_set.json")
373
+ eval_set_path = os.path.join(temp_dir, f"evalset_{os.path.basename(eval_set_file.filename or 'eval_set.json')}")
365
374
  with open(eval_set_path, "wb") as f: # noqa: ASYNC230
366
375
  content = await eval_set_file.read()
367
376
  f.write(content)
@@ -433,8 +442,7 @@ async def convert_trace_files(
433
442
  detail=f"Invalid file extension for {original}. Only .json and .jsonl files are allowed.",
434
443
  )
435
444
 
436
- safe_name = f"{idx}_{os.path.basename(original)}"
437
- trace_path = os.path.join(temp_dir, safe_name)
445
+ trace_path = _safe_upload_path(temp_dir, original, idx)
438
446
  with open(trace_path, "wb") as f: # noqa: ASYNC230
439
447
  content = await trace_file.read()
440
448
 
@@ -487,6 +495,7 @@ async def convert_trace_files(
487
495
  meta = TraceConversionMetadata(
488
496
  agent_name=meta_dict.get("agent_name"),
489
497
  model=meta_dict.get("model"),
498
+ schema_version=meta_dict.get("schema_version"),
490
499
  start_time=meta_dict.get("start_time"),
491
500
  user_input_preview=meta_dict.get("user_input_preview"),
492
501
  final_output_preview=meta_dict.get("final_output_preview"),
@@ -547,7 +556,7 @@ async def evaluate_traces(
547
556
  raise HTTPException(status_code=400, detail=f"Invalid credentialRefs: {exc}") from exc
548
557
 
549
558
  trace_paths = []
550
- for trace_file in trace_files:
559
+ for idx, trace_file in enumerate(trace_files):
551
560
  if not trace_file.filename:
552
561
  continue
553
562
 
@@ -557,7 +566,7 @@ async def evaluate_traces(
557
566
  detail=f"Invalid file extension for {trace_file.filename}. Only .json and .jsonl files are allowed.",
558
567
  )
559
568
 
560
- trace_path = os.path.join(temp_dir, trace_file.filename)
569
+ trace_path = _safe_upload_path(temp_dir, trace_file.filename, idx)
561
570
  with open(trace_path, "wb") as f: # noqa: ASYNC230
562
571
  content = await trace_file.read()
563
572
 
@@ -586,7 +595,7 @@ async def evaluate_traces(
586
595
  detail="Invalid file extension for eval set. Only .json files are allowed.",
587
596
  )
588
597
 
589
- eval_set_path = os.path.join(temp_dir, eval_set_file.filename)
598
+ eval_set_path = os.path.join(temp_dir, f"evalset_{os.path.basename(eval_set_file.filename)}")
590
599
  with open(eval_set_path, "wb") as f: # noqa: ASYNC230
591
600
  content = await eval_set_file.read()
592
601
  if len(content) > 10 * 1024 * 1024:
@@ -668,7 +677,7 @@ async def evaluate_traces_stream(
668
677
  return
669
678
 
670
679
  trace_paths = []
671
- for trace_file in trace_files:
680
+ for idx, trace_file in enumerate(trace_files):
672
681
  if not trace_file.filename:
673
682
  continue
674
683
 
@@ -676,7 +685,7 @@ async def evaluate_traces_stream(
676
685
  yield f"data: {SSEErrorEvent(error=f'Invalid file extension for {trace_file.filename}').model_dump_json(by_alias=True)}\n\n"
677
686
  return
678
687
 
679
- trace_path = os.path.join(temp_dir, trace_file.filename)
688
+ trace_path = _safe_upload_path(temp_dir, trace_file.filename, idx)
680
689
  with open(trace_path, "wb") as f: # noqa: ASYNC230
681
690
  content = await trace_file.read()
682
691
 
@@ -699,7 +708,7 @@ async def evaluate_traces_stream(
699
708
  yield f"data: {SSEErrorEvent(error='Invalid file extension for eval set').model_dump_json(by_alias=True)}\n\n"
700
709
  return
701
710
 
702
- eval_set_path = os.path.join(temp_dir, eval_set_file.filename)
711
+ eval_set_path = os.path.join(temp_dir, f"evalset_{os.path.basename(eval_set_file.filename)}")
703
712
  with open(eval_set_path, "wb") as f: # noqa: ASYNC230
704
713
  content = await eval_set_file.read()
705
714
  if len(content) > 10 * 1024 * 1024:
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import json
7
7
  import logging
8
+ import os
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  from fastapi import APIRouter, Depends, HTTPException, Request
@@ -15,6 +16,7 @@ from ..config import BuiltinMetricDef, EvalParams, EvalRunConfig, EvaluatorDef
15
16
  from ..converter import convert_traces
16
17
  from ..loader.otlp import OtlpJsonLoader
17
18
  from ..runner import RunResult, run_evaluation
19
+ from ..streaming.exports import EXPORT_DIR, export_name
18
20
  from ..trace_attrs import OTEL_GENAI_INPUT_MESSAGES, OTEL_GENAI_REQUEST_MODEL
19
21
  from .dependencies import require_trace_manager
20
22
  from .models import (
@@ -214,6 +216,7 @@ async def evaluate_sessions(
214
216
  if not golden_session:
215
217
  raise HTTPException(status_code=404, detail="Golden session not found")
216
218
 
219
+ eval_set_file = None
217
220
  try:
218
221
  eval_set_response = await _do_create_eval_set(
219
222
  CreateEvalSetRequest(
@@ -225,7 +228,9 @@ async def evaluate_sessions(
225
228
 
226
229
  import tempfile
227
230
 
228
- eval_set_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
231
+ eval_set_file = tempfile.NamedTemporaryFile(
232
+ mode="w", suffix=".json", delete=False, dir=str(EXPORT_DIR), encoding="utf-8"
233
+ )
229
234
  json.dump(eval_set_response.data.eval_set, eval_set_file)
230
235
  eval_set_file.close()
231
236
 
@@ -307,6 +312,14 @@ async def evaluate_sessions(
307
312
  except Exception as exc:
308
313
  logger.exception("Failed to evaluate sessions")
309
314
  raise HTTPException(status_code=500, detail=str(exc)) from exc
315
+ finally:
316
+ # This eval set is consumed only by run_evaluation above and is never
317
+ # served for download, so remove it once evaluation completes.
318
+ if eval_set_file is not None:
319
+ try:
320
+ os.unlink(eval_set_file.name)
321
+ except OSError:
322
+ pass
310
323
 
311
324
 
312
325
  @streaming_router.post("/prepare-evaluation", response_model=StandardResponse[PrepareEvaluationData])
@@ -328,13 +341,8 @@ async def prepare_evaluation(
328
341
  manager,
329
342
  )
330
343
 
331
- import os
332
- import tempfile
333
-
334
- temp_dir = tempfile.gettempdir()
335
-
336
- eval_set_file = os.path.join(temp_dir, f"eval_set_{request.golden_session_id}.json")
337
- with open(eval_set_file, "w") as f: # noqa: ASYNC230
344
+ eval_set_file = EXPORT_DIR / export_name(request.golden_session_id, prefix="eval_set_", suffix=".json")
345
+ with open(eval_set_file, "w", encoding="utf-8") as f: # noqa: ASYNC230
338
346
  json.dump(eval_set_response.data.eval_set, f)
339
347
 
340
348
  trace_files = []
@@ -353,7 +361,7 @@ async def prepare_evaluation(
353
361
 
354
362
  return StandardResponse(
355
363
  data=PrepareEvaluationData(
356
- eval_set_url=f"/api/streaming/download/{os.path.basename(eval_set_file)}",
364
+ eval_set_url=f"/api/streaming/download/{eval_set_file.name}",
357
365
  trace_urls=[f"/api/streaming/download/{os.path.basename(tf['file_path'])}" for tf in trace_files],
358
366
  num_traces=len(trace_files),
359
367
  )
@@ -368,20 +376,25 @@ async def prepare_evaluation(
368
376
 
369
377
  @streaming_router.get("/download/{filename}")
370
378
  async def download_file(filename: str):
371
- """Download a prepared trace or eval set file."""
372
- import os
373
- import tempfile
379
+ """Download a prepared trace or eval set file from the export directory.
380
+
381
+ Only bare filenames are accepted, and the resolved path must stay inside
382
+ the export directory. Anything that resolves outside it returns the same
383
+ 404 as a missing file.
384
+ """
385
+ if filename in {"", ".", ".."} or filename != os.path.basename(filename):
386
+ raise HTTPException(status_code=400, detail="Invalid filename")
374
387
 
375
- temp_dir = tempfile.gettempdir()
376
- file_path = os.path.join(temp_dir, filename)
388
+ export_root = EXPORT_DIR.resolve()
389
+ candidate = (export_root / filename).resolve()
377
390
 
378
- if not os.path.exists(file_path): # noqa: ASYNC240
391
+ if not candidate.is_relative_to(export_root):
379
392
  raise HTTPException(status_code=404, detail="File not found")
380
393
 
381
- if not file_path.startswith(temp_dir):
382
- raise HTTPException(status_code=400, detail="Invalid file path")
394
+ if not candidate.is_file(): # noqa: ASYNC240
395
+ raise HTTPException(status_code=404, detail="File not found")
383
396
 
384
- return FileResponse(file_path, media_type="application/json", filename=filename)
397
+ return FileResponse(candidate, media_type="application/json", filename=filename)
385
398
 
386
399
 
387
400
  @streaming_router.post("/get-trace", response_model=StandardResponse[GetTraceData])
@@ -415,7 +428,7 @@ async def get_trace(
415
428
  # evaluate-sessions path. Serializing spans here independently would
416
429
  # drop the resource attribute and lose agent identity on the run.
417
430
  trace_file = await manager._save_spans_to_temp_file(session)
418
- with open(trace_file) as f: # noqa: ASYNC230
431
+ with open(trace_file, encoding="utf-8") as f: # noqa: ASYNC230
419
432
  trace_content = f.read()
420
433
  num_spans = sum(1 for line in trace_content.splitlines() if line.strip())
421
434
 
@@ -840,8 +840,8 @@ def migrate_create(name: str, output_dir: str | None) -> None:
840
840
  f"-- Migration {next_version:06d}: {name}\n"
841
841
  "-- Once tagged in a release this file is immutable. Fix bugs by adding a NEW migration.\n\n"
842
842
  )
843
- up_path.write_text(header)
844
- down_path.write_text(header)
843
+ up_path.write_text(header, encoding="utf-8")
844
+ down_path.write_text(header, encoding="utf-8")
845
845
  click.echo(f"Created {up_path}")
846
846
  click.echo(f"Created {down_path}")
847
847
 
@@ -58,7 +58,7 @@ def load_eval_config(path: str | Path) -> EvalRunConfig:
58
58
  if not path.exists():
59
59
  raise FileNotFoundError(f"Eval config file not found: {path}")
60
60
 
61
- with open(path) as f:
61
+ with open(path, encoding="utf-8") as f:
62
62
  data = yaml.safe_load(f)
63
63
 
64
64
  if not isinstance(data, dict):
@@ -66,7 +66,7 @@ def _read_cache(key: str, ttl: int = _CACHE_TTL_SECONDS) -> list[EvaluatorInfo]
66
66
  if not cache_file.exists():
67
67
  return None
68
68
  try:
69
- data = json.loads(cache_file.read_text())
69
+ data = json.loads(cache_file.read_text(encoding="utf-8"))
70
70
  if time.time() - data.get("ts", 0) > ttl:
71
71
  return None
72
72
  return [EvaluatorInfo(**item) for item in data["evaluators"]]
@@ -83,7 +83,8 @@ def _write_cache(key: str, evaluators: list[EvaluatorInfo]) -> None:
83
83
  "ts": time.time(),
84
84
  "evaluators": [asdict(g) for g in evaluators],
85
85
  }
86
- )
86
+ ),
87
+ encoding="utf-8",
87
88
  )
88
89
  except Exception:
89
90
  pass
@@ -42,7 +42,11 @@ def _venv_key(evaluator_path: Path) -> str:
42
42
 
43
43
  def _is_venv_valid(venv_dir: Path, req_hash: str) -> bool:
44
44
  hash_file = venv_dir / _HASH_FILE
45
- return _venv_python(venv_dir).exists() and hash_file.exists() and hash_file.read_text().strip() == req_hash
45
+ return (
46
+ _venv_python(venv_dir).exists()
47
+ and hash_file.exists()
48
+ and hash_file.read_text(encoding="utf-8").strip() == req_hash
49
+ )
46
50
 
47
51
 
48
52
  def _create_venv(venv_dir: Path, uv: str | None) -> None:
@@ -104,7 +108,7 @@ def ensure_venv(evaluator_path: Path) -> Path | None:
104
108
  stderr = exc.stderr.decode() if isinstance(exc.stderr, bytes) else (exc.stderr or "")
105
109
  raise RuntimeError(f"Failed to set up environment for evaluator '{evaluator_path.stem}': {stderr}") from exc
106
110
 
107
- (venv_dir / _HASH_FILE).write_text(req_hash)
111
+ (venv_dir / _HASH_FILE).write_text(req_hash, encoding="utf-8")
108
112
  logger.info("Environment ready for '%s'", evaluator_path.stem)
109
113
  return _venv_python(venv_dir)
110
114
 
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import json
15
15
  import logging
16
+ import re
16
17
  from typing import Any, Protocol, TypedDict, TypeVar
17
18
 
18
19
  from .loader.base import Span, Trace
@@ -22,6 +23,7 @@ from .trace_attrs import (
22
23
  ADK_SCOPE_VALUE,
23
24
  ADK_TOOL_CALL_ARGS,
24
25
  ADK_TOOL_RESPONSE,
26
+ GENAI_ATTRIBUTE_ALIASES,
25
27
  OTEL_ERROR_TYPE,
26
28
  OTEL_GENAI_INPUT_MESSAGES,
27
29
  OTEL_GENAI_OP,
@@ -44,6 +46,7 @@ from .trace_attrs import (
44
46
  OTEL_GENAI_USAGE_CACHE_READ_TOKENS,
45
47
  OTEL_GENAI_USAGE_INPUT_TOKENS,
46
48
  OTEL_GENAI_USAGE_OUTPUT_TOKENS,
49
+ OTEL_SCHEMA_URL,
47
50
  OTEL_SCOPE,
48
51
  )
49
52
  from .utils.genai_messages import (
@@ -58,6 +61,61 @@ logger = logging.getLogger(__name__)
58
61
 
59
62
  FORMAT_DETECTION_SPAN_LIMIT = 10
60
63
 
64
+ # ---------------------------------------------------------------------------
65
+ # Alias-aware attribute resolution
66
+ # ---------------------------------------------------------------------------
67
+
68
+ # Per the OTel schema URL spec (https://opentelemetry.io/docs/specs/otel/schemas/#schema-url),
69
+ # a schema URL has the form `http[s]://server[:port]/path/<version>`.
70
+ _SCHEMA_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$")
71
+
72
+
73
+ def resolve_attr(attrs: dict[str, Any], canonical_key: str) -> Any | None:
74
+ """Look up *canonical_key* in *attrs*, falling back to older aliased names.
75
+
76
+ Resolution order:
77
+ 1. The canonical (current/newest) key, if present with a truthy value.
78
+ 2. Each alias in ``GENAI_ATTRIBUTE_ALIASES[canonical_key]``, in order,
79
+ first truthy value wins.
80
+ 3. ``None`` if neither the canonical key nor any alias is present.
81
+
82
+ The canonical value always takes precedence over aliases, even if both
83
+ are present on the same span. This resolves only against the flat attrs
84
+ dict at read time - it never mutates or rewrites stored/transmitted
85
+ attributes.
86
+ """
87
+ value = attrs.get(canonical_key)
88
+ if value:
89
+ return value
90
+
91
+ for alias in GENAI_ATTRIBUTE_ALIASES.get(canonical_key, []):
92
+ alias_value = attrs.get(alias)
93
+ if alias_value:
94
+ return alias_value
95
+
96
+ return None
97
+
98
+
99
+ def resolve_schema_version(schema_url: str | None) -> str | None:
100
+ """Extract the version segment from a schema URL.
101
+
102
+ Per the OTel schema URL spec, the version is the last path segment
103
+ (e.g. "1.37.0" in "https://opentelemetry.io/schemas/1.37.0").
104
+
105
+ It's scoped to that URL's schema family and only equals the OTel semconv
106
+ version for the official OTel family. Degrades gracefully to None for
107
+ missing, empty, or malformed input - never raises.
108
+ """
109
+ if not schema_url or not isinstance(schema_url, str):
110
+ return None
111
+
112
+ last_segment = schema_url.rstrip("/").rsplit("/", 1)[-1]
113
+ if _SCHEMA_VERSION_RE.match(last_segment):
114
+ return last_segment
115
+
116
+ return None
117
+
118
+
61
119
  # ---------------------------------------------------------------------------
62
120
  # Pure extraction functions (operate on flat attribute dicts)
63
121
  # ---------------------------------------------------------------------------
@@ -189,18 +247,28 @@ class ExtendedModelInfo(TypedDict):
189
247
  cache_creation_tokens: int
190
248
  cache_read_tokens: int
191
249
  error_type: str | None
250
+ schema_version: str | None
192
251
 
193
252
 
194
253
  def extract_extended_model_info_from_attrs(attrs: dict[str, Any]) -> ExtendedModelInfo:
195
254
  """Extract extended model and provider metadata from span attributes.
196
255
 
197
- Uses gen_ai.system as fallback for provider when gen_ai.provider.name is
198
- absent (backward compat with pre-v1.37.0 instrumentors).
256
+ Uses the alias-resolving lookup for attributes with known historical
257
+ renames (e.g. provider falls back to gen_ai.system when
258
+ gen_ai.provider.name - the canonical, current name - is absent, for
259
+ backward compat with pre-v1.37.0 instrumentors).
260
+
261
+ ``schema_version`` is resolved from the scope's ``schema_url`` (captured
262
+ at ingest as the ``otel.schema_url`` attribute) and is ``None`` when
263
+ absent or malformed. This is purely informational metadata about which
264
+ schema version emitted the span - it is never used to detect whether a
265
+ span is GenAI (that remains the sole responsibility of the existing
266
+ gen_ai.* presence checks).
199
267
  """
200
268
  return {
201
269
  "request_model": attrs.get(OTEL_GENAI_REQUEST_MODEL),
202
270
  "response_model": attrs.get(OTEL_GENAI_RESPONSE_MODEL),
203
- "provider": attrs.get(OTEL_GENAI_PROVIDER_NAME) or attrs.get(OTEL_GENAI_SYSTEM),
271
+ "provider": resolve_attr(attrs, OTEL_GENAI_PROVIDER_NAME),
204
272
  "finish_reasons": _parse_finish_reasons(attrs.get(OTEL_GENAI_RESPONSE_FINISH_REASONS)),
205
273
  "response_id": attrs.get(OTEL_GENAI_RESPONSE_ID),
206
274
  "temperature": _safe_cast(attrs.get(OTEL_GENAI_REQUEST_TEMPERATURE), float),
@@ -208,6 +276,7 @@ def extract_extended_model_info_from_attrs(attrs: dict[str, Any]) -> ExtendedMod
208
276
  "cache_creation_tokens": _safe_cast(attrs.get(OTEL_GENAI_USAGE_CACHE_CREATION_TOKENS), int, 0),
209
277
  "cache_read_tokens": _safe_cast(attrs.get(OTEL_GENAI_USAGE_CACHE_READ_TOKENS), int, 0),
210
278
  "error_type": attrs.get(OTEL_ERROR_TYPE),
279
+ "schema_version": resolve_schema_version(attrs.get(OTEL_SCHEMA_URL)),
211
280
  }
212
281
 
213
282
 
@@ -51,7 +51,7 @@ def detect_format(path: str) -> str | None:
51
51
  load can fall back to a default.
52
52
  """
53
53
  try:
54
- with open(path) as f:
54
+ with open(path, encoding="utf-8") as f:
55
55
  if path.lower().endswith(".jsonl"):
56
56
  return OTLP_JSON
57
57
  data = json.load(f)
@@ -43,7 +43,7 @@ class JaegerJsonLoader(TraceLoader):
43
43
  return "jaeger-json"
44
44
 
45
45
  def load(self, source: str) -> list[Trace]:
46
- with open(source) as f:
46
+ with open(source, encoding="utf-8") as f:
47
47
  raw = json.load(f)
48
48
 
49
49
  if not isinstance(raw, dict) or "data" not in raw:
@@ -8,6 +8,7 @@ import logging
8
8
  from ..trace_attrs import (
9
9
  OTEL_GENAI_INPUT_MESSAGES,
10
10
  OTEL_GENAI_OUTPUT_MESSAGES,
11
+ OTEL_SCHEMA_URL,
11
12
  OTEL_SCOPE,
12
13
  OTEL_SCOPE_VERSION,
13
14
  )
@@ -35,7 +36,7 @@ class OtlpJsonLoader(TraceLoader):
35
36
 
36
37
  def load(self, source: str) -> list[Trace]:
37
38
  """Load OTLP JSON file or JSONL (one span per line)."""
38
- with open(source) as f:
39
+ with open(source, encoding="utf-8") as f:
39
40
  content = f.read().strip()
40
41
 
41
42
  if not content:
@@ -93,21 +94,23 @@ class OtlpJsonLoader(TraceLoader):
93
94
  all_spans = []
94
95
  for resource_span in resource_spans:
95
96
  resource_attrs = self._extract_attributes(resource_span.get("resource", {}).get("attributes", []))
97
+ resource_schema_url = resource_span.get("schemaUrl", "")
96
98
  scope_spans = resource_span.get("scopeSpans") or resource_span.get("instrumentationLibrarySpans", [])
97
99
  for scope_span in scope_spans:
98
100
  scope = scope_span.get("scope") or scope_span.get("instrumentationLibrary") or {}
99
101
  scope_name = scope.get("name", "")
100
102
  scope_version = scope.get("version", "")
103
+ schema_url = scope_span.get("schemaUrl", "") or resource_schema_url
101
104
 
102
105
  for span_data in scope_span.get("spans", []):
103
- span = self._parse_span(span_data, resource_attrs, scope_name, scope_version)
106
+ span = self._parse_span(span_data, resource_attrs, scope_name, scope_version, schema_url)
104
107
  all_spans.append(span)
105
108
 
106
109
  return self._build_traces(all_spans)
107
110
 
108
111
  def _parse_otlp_spans(self, spans_data: list[dict]) -> list[Trace]:
109
112
  """Parse flat list of OTLP spans (JSONL format for streaming)."""
110
- all_spans = [self._parse_span(span_data, {}, "", "") for span_data in spans_data]
113
+ all_spans = [self._parse_span(span_data, {}, "", "", "") for span_data in spans_data]
111
114
  return self._build_traces(all_spans)
112
115
 
113
116
  _GENAI_EVENT_KEYS = {OTEL_GENAI_INPUT_MESSAGES, OTEL_GENAI_OUTPUT_MESSAGES}
@@ -118,6 +121,7 @@ class OtlpJsonLoader(TraceLoader):
118
121
  resource_attrs: dict,
119
122
  scope_name: str,
120
123
  scope_version: str,
124
+ schema_url: str = "",
121
125
  ) -> Span:
122
126
  """Convert OTLP span to normalized Span object."""
123
127
  attributes = self._extract_attributes(span_data.get("attributes", []))
@@ -126,6 +130,8 @@ class OtlpJsonLoader(TraceLoader):
126
130
  attributes[OTEL_SCOPE] = scope_name
127
131
  if scope_version:
128
132
  attributes[OTEL_SCOPE_VERSION] = scope_version
133
+ if schema_url:
134
+ attributes[OTEL_SCHEMA_URL] = schema_url
129
135
 
130
136
  self._promote_genai_event_attributes(span_data, attributes)
131
137
 
@@ -378,7 +378,7 @@ def create_server(server_url: str | None = None, **fastmcp_kwargs: Any) -> FastM
378
378
 
379
379
  raw = await _post("/api/streaming/get-trace", {"session_id": session_id})
380
380
 
381
- with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f:
381
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False, encoding="utf-8") as f:
382
382
  f.write(raw["traceContent"])
383
383
  tmp_path = f.name
384
384