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.
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/PKG-INFO +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/agentevals_example_custom_sink/sink.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/app.py +7 -3
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/debug_routes.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/models.py +18 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_processing.py +11 -4
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/routes.py +19 -10
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/streaming_routes.py +32 -19
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/cli.py +2 -2
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/eval_config_loader.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/sources.py +3 -2
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/venv.py +6 -2
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/extraction.py +72 -3
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/auto.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/jaeger.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/otlp.py +9 -3
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/mcp_server.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/fetcher.py +2 -2
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/sinks.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/runner.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/config.py +1 -1
- agentevals_cli-0.9.7/src/agentevals/streaming/exports.py +41 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/processor.py +3 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/ws_server.py +4 -3
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/trace_attrs.py +8 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/trace_metrics.py +7 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_sinks.py +6 -6
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_config.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_api.py +328 -13
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_cli.py +6 -4
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_eval_config_loader.py +2 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_extraction.py +141 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_jaeger_loader.py +7 -7
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_loader_auto.py +1 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_openai_eval_backend.py +2 -1
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_otlp_loader.py +4 -4
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_runner.py +40 -2
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/eval/SKILL.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/eval/evals/evals.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/inspect/SKILL.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.claude/skills/inspect/evals/evals.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.dockerignore +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/ci.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/publish-evaluator-sdk.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.github/workflows/release.yml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.gitignore +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/.mcp.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/CONTRIBUTING.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/DEVELOPMENT.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/Dockerfile +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/LICENSE +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/Makefile +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/Chart.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/NOTES.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/_helpers.tpl +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/deployment.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/postgresql-secret.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/postgresql.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/rbac.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/service.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/templates/serviceaccount.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/charts/agentevals/values.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-color-on-transparent.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-color.png +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/assets/logo-dark-on-transparent.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/custom-evaluators.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/eval-set-format.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/otel-compatibility.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/run-history.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/docs/streaming.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/eval_config.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/eval_config_openai_eval.yaml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/response_quality.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_evaluators/tool_call_checker.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/agentevals_example_custom_sink/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/custom_sink/pyproject.toml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/agent.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/main.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/dice_agent/test_streaming.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/kubernetes/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/agent.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/main.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/langchain_agent/test_streaming.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/async_example.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/context_manager_example.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/decorator_example.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/sdk_example/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/agent.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/main.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/strands_agent/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/adk/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/adk/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/langchain/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/langchain/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/ollama/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/ollama/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/openai-agents/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/openai-agents/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/pydantic-ai/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/pydantic-ai/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/strands/requirements.txt +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/examples/zero-code-examples/strands/run.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/flake.lock +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/flake.nix +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/pyproject.toml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/decorator.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/types.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/pyproject.toml +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/eval_set_helm.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/evalset_helm_3_2026-02-23.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/evalset_k8s_2026-02-20.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm_2.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/helm_3.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/k8s.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/samples/tempo_export_with_batches.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_protocol.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/assets/index-4Q_gYF8x.js +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/assets/index-BqibLiHO.css +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/index.html +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/logo.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/_static/vite.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/dependencies.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_app.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_grpc.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/otlp_routes.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/api/runs_routes.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/builtin_metrics.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/config.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/converter.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/custom_evaluators.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/resolver.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/evaluator/templates.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/genai_converter.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/loader/base.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/openai_eval_backend.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/output.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/resolvers/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/resolvers/kubernetes.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/result_builder.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/service.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/run/worker.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/sdk.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/models.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrations/000001_init.down.sql +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrations/000001_init.up.sql +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/migrator.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/postgres/pool.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/memory.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/storage/repos/postgres.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/incremental_processor.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/streaming/session.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/genai_messages.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/log_buffer.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/src/agentevals/utils/log_enrichment.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/test_evaluate_persistence.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/api/test_runs_routes.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/conftest.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_evaluation_pipeline.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_live_agents.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_otlp_grpc_receiver.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_session_grouping.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/integration/test_timing_stress.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/test_kubernetes.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/resolvers/test_registry.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_fetcher.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_result_builder.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/run/test_service.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/__init__.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_memory_repos.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_migrator.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/storage/test_models.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_converter.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_credential_injection.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_genai_converter.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_log_enrichment.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_mcp_server.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_otlp_receiver.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_output.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_protocol.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_sdk.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/tests/test_trace_metrics.py +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/.gitignore +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/README.md +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/eslint.config.js +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/index.html +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/package-lock.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/package.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/public/logo.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/public/vite.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/App.css +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/App.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/api/client.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/assets/react.svg +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationDetailPanel.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationQueueView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/annotation-queue/AnnotationTable.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/bug-report/BugReportModal.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/BuilderHeader.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/BuilderView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/EvalCaseCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/EvalCasesList.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/InvocationEditor.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/JsonPreview.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/MetadataEditor.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/TraceUploadZone.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/builder/index.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/DashboardView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/MetricScoreCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/PerformanceCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/PerformanceCharts.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/SummaryStats.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/TraceCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/dashboard/TraceTable.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/ComparisonPanel.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/DataSection.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorHeader.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorLayout.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InspectorView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InvocationCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/InvocationSummaryPanel.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/MetricResultsSection.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/MetricsComparisonSection.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/PerformanceSection.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/ToolCallList.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/inspector/TrajectoryComparisonDetails.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/PassRateTrendChart.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/PerMetricTrendChart.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunDetailView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunsHistoryTable.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/RunsView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/runs/runHistory.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/sidebar/Sidebar.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveConversationPanel.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveMessage.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/LiveStreamingView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/SessionCard.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/streaming/SessionMetadata.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/EvalSetEditorDrawer.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/FileDropZone.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/MetricSelector.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/RawJsonPreview.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/TraceEditorDrawer.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/upload/UploadView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/components/welcome/WelcomeView.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/config.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/context/TraceContext.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/context/TraceProvider.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/index.css +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/console-capture.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/eval-config.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/evalset-builder.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/network-capture.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-helpers.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-loader.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-metadata.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/trace-patcher.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/types.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/lib/utils.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/src/main.tsx +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.app.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/tsconfig.node.json +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/ui/vite.config.ts +0 -0
- {agentevals_cli-0.9.6 → agentevals_cli-0.9.7}/uv.lock +0 -0
|
@@ -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
|
|
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
|
|
208
|
-
attributes (the pipeline expects them
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
332
|
-
|
|
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/{
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
388
|
+
export_root = EXPORT_DIR.resolve()
|
|
389
|
+
candidate = (export_root / filename).resolve()
|
|
377
390
|
|
|
378
|
-
if not
|
|
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
|
|
382
|
-
raise HTTPException(status_code=
|
|
394
|
+
if not candidate.is_file(): # noqa: ASYNC240
|
|
395
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
383
396
|
|
|
384
|
-
return FileResponse(
|
|
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
|
|
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
|
|
198
|
-
|
|
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
|
|
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
|
|
|
@@ -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
|
|