agentevals-cli 0.9.9__tar.gz → 0.9.10__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.10/.github/workflows/ci.yml +139 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/Dockerfile +1 -1
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/Makefile +8 -1
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/PKG-INFO +1 -1
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/otlp_processing.py +12 -31
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/extraction.py +7 -14
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/loader/otlp.py +6 -21
- agentevals_cli-0.9.10/src/agentevals/otlp_anyvalue.py +118 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/trace_attrs.py +30 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_extraction.py +124 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_otlp_loader.py +156 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_otlp_receiver.py +54 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/package-lock.json +87 -87
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/package.json +7 -7
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/uv.lock +6 -9
- agentevals_cli-0.9.9/.github/workflows/ci.yml +0 -86
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.claude/skills/eval/SKILL.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.claude/skills/eval/evals/evals.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.claude/skills/inspect/SKILL.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.claude/skills/inspect/evals/evals.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.dockerignore +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/dependabot.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/workflows/publish-evaluator-sdk.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.github/workflows/release.yml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.gitignore +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/.mcp.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/CONTRIBUTING.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/DEVELOPMENT.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/LICENSE +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/Chart.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/NOTES.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/_helpers.tpl +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/deployment.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/postgresql-secret.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/postgresql.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/rbac.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/service.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/templates/serviceaccount.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/charts/agentevals/values.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/assets/logo-color-on-transparent.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/assets/logo-color.png +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/assets/logo-dark-on-transparent.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/custom-evaluators.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/eval-set-format.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/otel-compatibility.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/run-history.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/docs/streaming.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_evaluators/eval_config.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_evaluators/eval_config_openai_eval.yaml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_evaluators/response_quality.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_evaluators/tool_call_checker.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_sink/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_sink/agentevals_example_custom_sink/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_sink/agentevals_example_custom_sink/sink.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/custom_sink/pyproject.toml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/dice_agent/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/dice_agent/agent.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/dice_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/dice_agent/main.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/dice_agent/test_streaming.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/kubernetes/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/agent.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/main.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/langchain_agent/test_streaming.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/sdk_example/async_example.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/sdk_example/context_manager_example.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/sdk_example/decorator_example.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/sdk_example/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/strands_agent/agent.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/strands_agent/eval_set.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/strands_agent/main.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/strands_agent/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/adk/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/adk/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/langchain/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/langchain/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/ollama/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/ollama/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/openai-agents/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/openai-agents/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/pydantic-ai/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/pydantic-ai/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/strands/requirements.txt +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/examples/zero-code-examples/strands/run.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/flake.lock +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/flake.nix +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/packages/evaluator-sdk-py/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/packages/evaluator-sdk-py/pyproject.toml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/decorator.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/packages/evaluator-sdk-py/src/agentevals_evaluator_sdk/types.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/pyproject.toml +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/eval_set_helm.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/evalset_helm_3_2026-02-23.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/evalset_k8s_2026-02-20.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/helm.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/helm_2.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/helm_3.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/k8s.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/samples/tempo_export_with_batches.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_protocol.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_static/assets/index-BqibLiHO.css +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_static/assets/index-ClNj3OAl.js +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_static/index.html +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_static/logo.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/_static/vite.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/app.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/debug_routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/dependencies.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/models.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/otlp_app.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/otlp_grpc.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/otlp_routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/runs_routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/api/streaming_routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/builtin_metrics.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/cli.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/config.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/converter.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/custom_evaluators.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/eval_config_loader.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/evaluator/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/evaluator/resolver.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/evaluator/sources.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/evaluator/templates.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/evaluator/venv.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/genai_converter.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/loader/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/loader/auto.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/loader/base.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/loader/jaeger.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/mcp_server.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/openai_eval_backend.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/output.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/resolvers/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/resolvers/kubernetes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/fetcher.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/result_builder.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/service.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/sinks.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/run/worker.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/runner.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/sdk.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/config.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/models.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/postgres/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/postgres/migrations/000001_init.down.sql +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/postgres/migrations/000001_init.up.sql +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/postgres/migrator.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/postgres/pool.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/repos/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/repos/memory.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/storage/repos/postgres.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/exports.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/incremental_processor.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/processor.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/session.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/streaming/ws_server.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/trace_metrics.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/utils/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/utils/genai_messages.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/utils/log_buffer.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/src/agentevals/utils/log_enrichment.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/api/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/api/test_evaluate_persistence.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/api/test_runs_routes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/conftest.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/test_evaluation_pipeline.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/test_live_agents.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/test_otlp_grpc_receiver.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/test_session_grouping.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/integration/test_timing_stress.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/resolvers/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/resolvers/test_kubernetes.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/resolvers/test_registry.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/run/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/run/test_fetcher.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/run/test_result_builder.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/run/test_service.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/run/test_sinks.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/storage/__init__.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/storage/test_config.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/storage/test_memory_repos.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/storage/test_migrator.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/storage/test_models.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_api.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_cli.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_converter.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_credential_injection.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_eval_config_loader.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_genai_converter.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_incremental_processor.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_jaeger_loader.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_loader_auto.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_log_enrichment.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_mcp_server.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_openai_eval_backend.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_output.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_protocol.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_runner.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_sdk.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/tests/test_trace_metrics.py +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/.gitignore +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/README.md +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/eslint.config.js +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/index.html +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/public/logo.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/public/vite.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/App.css +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/App.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/api/client.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/assets/react.svg +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/annotation-queue/AnnotationDetailPanel.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/annotation-queue/AnnotationQueueView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/annotation-queue/AnnotationTable.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/bug-report/BugReportModal.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/BuilderHeader.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/BuilderView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/EvalCaseCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/EvalCasesList.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/InvocationEditor.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/JsonPreview.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/MetadataEditor.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/TraceUploadZone.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/builder/index.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/DashboardView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/MetricScoreCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/PerformanceCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/PerformanceCharts.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/SummaryStats.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/TraceCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/dashboard/TraceTable.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/ComparisonPanel.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/DataSection.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/InspectorHeader.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/InspectorLayout.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/InspectorView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/InvocationCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/InvocationSummaryPanel.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/MetricResultsSection.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/MetricsComparisonSection.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/PerformanceSection.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/ToolCallList.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/inspector/TrajectoryComparisonDetails.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/PassRateTrendChart.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/PerMetricTrendChart.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/RunDetailView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/RunsHistoryTable.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/RunsView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/runs/runHistory.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/sidebar/Sidebar.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/streaming/LiveConversationPanel.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/streaming/LiveMessage.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/streaming/LiveStreamingView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/streaming/SessionCard.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/streaming/SessionMetadata.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/EvalSetEditorDrawer.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/FileDropZone.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/MetricSelector.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/RawJsonPreview.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/TraceEditorDrawer.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/upload/UploadView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/components/welcome/WelcomeView.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/config.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/context/TraceContext.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/context/TraceProvider.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/index.css +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/console-capture.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/eval-config.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/evalset-builder.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/network-capture.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/trace-helpers.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/trace-loader.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/trace-metadata.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/trace-patcher.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/types.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/lib/utils.ts +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/src/main.tsx +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/tsconfig.app.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/tsconfig.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/tsconfig.node.json +0 -0
- {agentevals_cli-0.9.9 → agentevals_cli-0.9.10}/ui/vite.config.ts +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
helm-chart:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
+
|
|
18
|
+
- name: Lint + template
|
|
19
|
+
run: make helm-test
|
|
20
|
+
|
|
21
|
+
lint:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
|
|
28
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
|
|
32
|
+
- name: Verify uv.lock matches pyproject.toml
|
|
33
|
+
run: uv lock --check
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: |
|
|
37
|
+
uv venv
|
|
38
|
+
uv pip install setuptools
|
|
39
|
+
uv sync --dev
|
|
40
|
+
|
|
41
|
+
- name: Ruff check
|
|
42
|
+
run: uv run ruff check .
|
|
43
|
+
|
|
44
|
+
- name: Ruff format check
|
|
45
|
+
run: uv run ruff format --check .
|
|
46
|
+
|
|
47
|
+
ui:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
51
|
+
|
|
52
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
53
|
+
with:
|
|
54
|
+
node-version: '22'
|
|
55
|
+
cache: npm
|
|
56
|
+
cache-dependency-path: ui/package-lock.json
|
|
57
|
+
|
|
58
|
+
# tsc -b && vite build, same as the release workflow bundles.
|
|
59
|
+
- name: Build UI
|
|
60
|
+
run: make build-ui
|
|
61
|
+
|
|
62
|
+
docker:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
permissions:
|
|
65
|
+
contents: read
|
|
66
|
+
env:
|
|
67
|
+
IMAGE_REF: agentevals:ci
|
|
68
|
+
IMAGE_VERSION: 0.0.0.dev0
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
|
+
|
|
72
|
+
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
73
|
+
|
|
74
|
+
# The release workflow is the only other place this Dockerfile gets built, and it
|
|
75
|
+
# runs after the GitHub release is already published. Build it here so a break
|
|
76
|
+
# lands on the PR instead of halfway through a release.
|
|
77
|
+
# Single-arch keeps it native; the multi-arch build stays release-only because
|
|
78
|
+
# emulating arm64 through the node and uv layers is an order of magnitude slower.
|
|
79
|
+
- name: Build image
|
|
80
|
+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
81
|
+
with:
|
|
82
|
+
context: .
|
|
83
|
+
platforms: linux/amd64
|
|
84
|
+
build-args: VERSION=${{ env.IMAGE_VERSION }}
|
|
85
|
+
tags: ${{ env.IMAGE_REF }}
|
|
86
|
+
push: false
|
|
87
|
+
load: true
|
|
88
|
+
cache-from: type=gha
|
|
89
|
+
# Pull requests read main's layers but never write, so a branch cannot grow
|
|
90
|
+
# or displace the cache the other jobs share.
|
|
91
|
+
cache-to: ${{ github.event_name == 'push' && 'type=gha,mode=max' || '' }}
|
|
92
|
+
|
|
93
|
+
- name: Smoke test
|
|
94
|
+
run: |
|
|
95
|
+
docker run -d --name agentevals-ci -p 127.0.0.1:8001:8001 "$IMAGE_REF"
|
|
96
|
+
for _ in $(seq 1 30); do
|
|
97
|
+
if curl -fsS --max-time 3 http://127.0.0.1:8001/api/health -o "$RUNNER_TEMP/health.json"; then
|
|
98
|
+
break
|
|
99
|
+
fi
|
|
100
|
+
sleep 2
|
|
101
|
+
done
|
|
102
|
+
test -s "$RUNNER_TEMP/health.json" || { echo "server never became healthy"; exit 1; }
|
|
103
|
+
cat "$RUNNER_TEMP/health.json"
|
|
104
|
+
grep -q '"status":"ok"' "$RUNNER_TEMP/health.json"
|
|
105
|
+
# Proves the SETUPTOOLS_SCM_PRETEND_VERSION path still resolves; a silent
|
|
106
|
+
# break there would ship a mis-versioned image.
|
|
107
|
+
grep -q "\"version\":\"$IMAGE_VERSION\"" "$RUNNER_TEMP/health.json"
|
|
108
|
+
curl -fsS http://127.0.0.1:8001/ | grep -q '<div id="root">'
|
|
109
|
+
test "$(docker exec agentevals-ci id -u)" = "1000"
|
|
110
|
+
|
|
111
|
+
- name: Container logs
|
|
112
|
+
if: failure()
|
|
113
|
+
run: docker logs agentevals-ci
|
|
114
|
+
|
|
115
|
+
test:
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
strategy:
|
|
118
|
+
matrix:
|
|
119
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
122
|
+
with:
|
|
123
|
+
fetch-depth: 0
|
|
124
|
+
|
|
125
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
126
|
+
with:
|
|
127
|
+
enable-cache: true
|
|
128
|
+
|
|
129
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
130
|
+
run: uv python install ${{ matrix.python-version }}
|
|
131
|
+
|
|
132
|
+
- name: Install dependencies
|
|
133
|
+
run: |
|
|
134
|
+
uv venv --python ${{ matrix.python-version }}
|
|
135
|
+
uv pip install setuptools
|
|
136
|
+
uv sync --dev --all-extras --frozen --python ${{ matrix.python-version }}
|
|
137
|
+
|
|
138
|
+
- name: Run tests
|
|
139
|
+
run: uv run pytest -m "not integration and not e2e" --tb=short -q
|
|
@@ -9,13 +9,17 @@ DOCKER_IMAGE_REF := $(if $(DOCKER_REGISTRY),$(DOCKER_REGISTRY:%/=%)/$(DOCKER_IMA
|
|
|
9
9
|
# Multi-arch build (requires docker buildx). Manifest lists must be pushed — use build-docker-local for a single-arch --load.
|
|
10
10
|
PLATFORMS ?= linux/amd64,linux/arm64
|
|
11
11
|
|
|
12
|
+
# LOCAL_VERSION falls back to a stub so build-docker-local works without uv or git history.
|
|
13
|
+
LOCAL_VERSION ?= $(if $(VERSION),$(VERSION),0.0.0.dev0)
|
|
14
|
+
LOCAL_TAG ?= local
|
|
15
|
+
|
|
12
16
|
HELM_REPO ?= oci://ghcr.io/agentevals-dev/agentevals
|
|
13
17
|
HELM_DIST_FOLDER ?= dist/helm
|
|
14
18
|
HELM_CHART_DIR ?= charts/agentevals
|
|
15
19
|
HELM_CHART_OCI_URL ?= $(HELM_REPO)/helm
|
|
16
20
|
HELM_CHART_VERSION ?= $(VERSION)
|
|
17
21
|
|
|
18
|
-
.PHONY: build build-bundle build-docker build-ui release clean dev-backend dev-backend-pg dev-frontend dev-bundle pg-up pg-down migrate test test-unit test-integration test-e2e helm-lint helm-template helm-test helm-cleanup helm-package helm-publish
|
|
22
|
+
.PHONY: build build-bundle build-docker build-docker-local build-ui release clean dev-backend dev-backend-pg dev-frontend dev-bundle pg-up pg-down migrate test test-unit test-integration test-e2e helm-lint helm-template helm-test helm-cleanup helm-package helm-publish
|
|
19
23
|
|
|
20
24
|
PG_CONTAINER ?= agentevals-pg
|
|
21
25
|
PG_PORT ?= 5432
|
|
@@ -31,6 +35,9 @@ build-docker:
|
|
|
31
35
|
@test -n "$(VERSION)" || { echo "ERROR: VERSION is empty. Pass VERSION=x.y.z explicitly, or install uv so hatch-vcs can resolve it."; exit 1; }
|
|
32
36
|
docker buildx build --platform $(PLATFORMS) --build-arg VERSION=$(VERSION) -t $(DOCKER_IMAGE_REF):$(DOCKER_TAG) --push .
|
|
33
37
|
|
|
38
|
+
build-docker-local:
|
|
39
|
+
docker buildx build --build-arg VERSION=$(LOCAL_VERSION) -t $(DOCKER_IMAGE_REF):$(LOCAL_TAG) --load .
|
|
40
|
+
|
|
34
41
|
build-ui:
|
|
35
42
|
cd ui && npm ci && npm run build
|
|
36
43
|
|
|
@@ -15,7 +15,10 @@ from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
from ..extraction import flatten_otlp_attributes
|
|
18
|
+
from ..otlp_anyvalue import decode_any_value
|
|
18
19
|
from ..trace_attrs import (
|
|
20
|
+
AGENTEVALS_EVAL_SET_ID,
|
|
21
|
+
AGENTEVALS_SESSION_NAME,
|
|
19
22
|
OTEL_GENAI_CONVERSATION_ID,
|
|
20
23
|
OTEL_GENAI_INPUT_MESSAGES,
|
|
21
24
|
OTEL_GENAI_OUTPUT_MESSAGES,
|
|
@@ -30,9 +33,6 @@ if TYPE_CHECKING:
|
|
|
30
33
|
|
|
31
34
|
logger = logging.getLogger(__name__)
|
|
32
35
|
|
|
33
|
-
AGENTEVALS_EVAL_SET_ID = "agentevals.eval_set_id"
|
|
34
|
-
AGENTEVALS_SESSION_NAME = "agentevals.session_name"
|
|
35
|
-
|
|
36
36
|
|
|
37
37
|
async def process_traces(body: dict, manager: StreamingTraceManager) -> None:
|
|
38
38
|
"""Parse ExportTraceServiceRequest and feed spans to the pipeline."""
|
|
@@ -242,7 +242,13 @@ def _normalize_span(span_data: dict, scope_name: str, scope_version: str, schema
|
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
def _extract_agentevals_metadata(resource_attrs: list[dict]) -> dict:
|
|
245
|
-
"""Extract agentevals-specific metadata from OTLP resource attributes.
|
|
245
|
+
"""Extract agentevals-specific metadata from OTLP resource attributes.
|
|
246
|
+
|
|
247
|
+
``eval_set_id`` and ``session_name`` are used as dict keys downstream
|
|
248
|
+
(``_active_session_for_name``) and typed ``str | None`` on the session
|
|
249
|
+
models. Neither is in ``SPEC_CONTAINER_ATTRS``, so the shared decoder never
|
|
250
|
+
hands them a list or dict in the first place.
|
|
251
|
+
"""
|
|
246
252
|
flat = flatten_otlp_attributes(resource_attrs)
|
|
247
253
|
return {
|
|
248
254
|
"eval_set_id": flat.get(AGENTEVALS_EVAL_SET_ID),
|
|
@@ -310,37 +316,12 @@ def _convert_otlp_log_record(log_record: dict) -> dict | None:
|
|
|
310
316
|
return result
|
|
311
317
|
|
|
312
318
|
|
|
313
|
-
def _parse_otlp_any_value(value_obj: dict):
|
|
314
|
-
"""Recursively parse an OTLP AnyValue to native Python types.
|
|
315
|
-
|
|
316
|
-
Handles the full AnyValue union: stringValue, intValue, doubleValue,
|
|
317
|
-
boolValue, kvlistValue (→ dict), arrayValue (→ list), bytesValue.
|
|
318
|
-
"""
|
|
319
|
-
if "stringValue" in value_obj:
|
|
320
|
-
return value_obj["stringValue"]
|
|
321
|
-
if "intValue" in value_obj:
|
|
322
|
-
return int(value_obj["intValue"])
|
|
323
|
-
if "doubleValue" in value_obj:
|
|
324
|
-
return float(value_obj["doubleValue"])
|
|
325
|
-
if "boolValue" in value_obj:
|
|
326
|
-
return value_obj["boolValue"]
|
|
327
|
-
if "kvlistValue" in value_obj:
|
|
328
|
-
kv = value_obj["kvlistValue"]
|
|
329
|
-
return {item.get("key", ""): _parse_otlp_any_value(item.get("value", {})) for item in kv.get("values", [])}
|
|
330
|
-
if "arrayValue" in value_obj:
|
|
331
|
-
arr = value_obj["arrayValue"]
|
|
332
|
-
return [_parse_otlp_any_value(v) for v in arr.get("values", [])]
|
|
333
|
-
if "bytesValue" in value_obj:
|
|
334
|
-
return value_obj["bytesValue"]
|
|
335
|
-
return value_obj
|
|
336
|
-
|
|
337
|
-
|
|
338
319
|
def _parse_otlp_body(body_raw: dict) -> dict | str:
|
|
339
320
|
"""Parse OTLP log record body value.
|
|
340
321
|
|
|
341
322
|
Top-level stringValue bodies are JSON-decoded (Strands-style logs store
|
|
342
323
|
message content as JSON strings). All other AnyValue types are parsed
|
|
343
|
-
recursively via ``
|
|
324
|
+
recursively via ``decode_any_value`` (handles the nested kvlistValue /
|
|
344
325
|
arrayValue structures used by the OpenAI instrumentor).
|
|
345
326
|
"""
|
|
346
327
|
if "stringValue" in body_raw:
|
|
@@ -351,4 +332,4 @@ def _parse_otlp_body(body_raw: dict) -> dict | str:
|
|
|
351
332
|
return json.loads(raw)
|
|
352
333
|
except (json.JSONDecodeError, TypeError):
|
|
353
334
|
return raw
|
|
354
|
-
return
|
|
335
|
+
return decode_any_value(body_raw)
|
|
@@ -17,6 +17,7 @@ import re
|
|
|
17
17
|
from typing import Any, Protocol, TypedDict, TypeVar
|
|
18
18
|
|
|
19
19
|
from .loader.base import Span, Trace
|
|
20
|
+
from .otlp_anyvalue import decode_attributes
|
|
20
21
|
from .trace_attrs import (
|
|
21
22
|
ADK_LLM_REQUEST,
|
|
22
23
|
ADK_LLM_RESPONSE,
|
|
@@ -523,20 +524,12 @@ def is_invocation_span(span: Span) -> bool:
|
|
|
523
524
|
|
|
524
525
|
|
|
525
526
|
def flatten_otlp_attributes(attrs_list: list[dict]) -> dict[str, Any]:
|
|
526
|
-
"""Convert OTLP attributes array [{key, value: {stringValue|...}}] to flat dict.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
result[key] = value_obj["stringValue"]
|
|
533
|
-
elif "intValue" in value_obj:
|
|
534
|
-
result[key] = int(value_obj["intValue"])
|
|
535
|
-
elif "doubleValue" in value_obj:
|
|
536
|
-
result[key] = float(value_obj["doubleValue"])
|
|
537
|
-
elif "boolValue" in value_obj:
|
|
538
|
-
result[key] = value_obj["boolValue"]
|
|
539
|
-
return result
|
|
527
|
+
"""Convert OTLP attributes array [{key, value: {stringValue|...}}] to flat dict.
|
|
528
|
+
|
|
529
|
+
Delegates to the shared ``AnyValue`` decoder so array/kvlist/bytes
|
|
530
|
+
attributes survive instead of being dropped.
|
|
531
|
+
"""
|
|
532
|
+
return decode_attributes(attrs_list)
|
|
540
533
|
|
|
541
534
|
|
|
542
535
|
# ---------------------------------------------------------------------------
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
|
+
from ..otlp_anyvalue import decode_attribute, decode_attributes, is_any_value
|
|
8
9
|
from ..trace_attrs import (
|
|
9
10
|
OTEL_GENAI_INPUT_MESSAGES,
|
|
10
11
|
OTEL_GENAI_OUTPUT_MESSAGES,
|
|
@@ -175,8 +176,10 @@ class OtlpJsonLoader(TraceLoader):
|
|
|
175
176
|
key = attr.get("key", "")
|
|
176
177
|
if key in self._GENAI_EVENT_KEYS and key not in attributes:
|
|
177
178
|
value_obj = attr.get("value", {})
|
|
178
|
-
if
|
|
179
|
-
|
|
179
|
+
if is_any_value(value_obj):
|
|
180
|
+
keep, value = decode_attribute(key, value_obj)
|
|
181
|
+
if keep:
|
|
182
|
+
attributes[key] = value
|
|
180
183
|
|
|
181
184
|
def _extract_attributes(self, attrs) -> dict:
|
|
182
185
|
"""Convert attributes to a flat ``{key: value}`` dict.
|
|
@@ -192,25 +195,7 @@ class OtlpJsonLoader(TraceLoader):
|
|
|
192
195
|
if isinstance(attrs, dict):
|
|
193
196
|
return self._flatten_nested_dict(attrs)
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
for attr in attrs:
|
|
197
|
-
key = attr.get("key", "")
|
|
198
|
-
value_obj = attr.get("value", {})
|
|
199
|
-
|
|
200
|
-
if "stringValue" in value_obj:
|
|
201
|
-
result[key] = value_obj["stringValue"]
|
|
202
|
-
elif "intValue" in value_obj:
|
|
203
|
-
result[key] = int(value_obj["intValue"])
|
|
204
|
-
elif "doubleValue" in value_obj:
|
|
205
|
-
result[key] = float(value_obj["doubleValue"])
|
|
206
|
-
elif "boolValue" in value_obj:
|
|
207
|
-
result[key] = value_obj["boolValue"]
|
|
208
|
-
elif "arrayValue" in value_obj:
|
|
209
|
-
result[key] = json.dumps(value_obj["arrayValue"])
|
|
210
|
-
elif "kvlistValue" in value_obj:
|
|
211
|
-
result[key] = json.dumps(value_obj["kvlistValue"])
|
|
212
|
-
|
|
213
|
-
return result
|
|
198
|
+
return decode_attributes(attrs)
|
|
214
199
|
|
|
215
200
|
@staticmethod
|
|
216
201
|
def _flatten_nested_dict(d: dict, prefix: str = "") -> dict:
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Shared decoder for the OTLP ``AnyValue`` union.
|
|
2
|
+
|
|
3
|
+
OTLP encodes every attribute value, log body and nested element as an
|
|
4
|
+
``AnyValue``: a one-of wrapper such as ``{"stringValue": "chat"}`` or
|
|
5
|
+
``{"arrayValue": {"values": [...]}}``. The protobuf receiver (via
|
|
6
|
+
``MessageToDict``) and OTLP/JSON payloads deliver that same dict shape, so
|
|
7
|
+
every consumer needs identical decoding rules.
|
|
8
|
+
|
|
9
|
+
This module depends only on the standard library and ``trace_attrs`` (a leaf
|
|
10
|
+
constants module), so ``extraction``, ``loader.otlp`` and ``api.otlp_processing``
|
|
11
|
+
can all use it without creating an import cycle.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from .trace_attrs import SPEC_CONTAINER_ATTRS
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
ANY_VALUE_FIELDS = (
|
|
24
|
+
"stringValue",
|
|
25
|
+
"intValue",
|
|
26
|
+
"doubleValue",
|
|
27
|
+
"boolValue",
|
|
28
|
+
"kvlistValue",
|
|
29
|
+
"arrayValue",
|
|
30
|
+
"bytesValue",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode_any_value(value_obj: dict) -> Any:
|
|
35
|
+
"""Recursively decode an OTLP ``AnyValue`` to a native Python value.
|
|
36
|
+
|
|
37
|
+
Handles the full union: stringValue, intValue (OTLP sends it as a
|
|
38
|
+
string), doubleValue, boolValue, kvlistValue (→ dict), arrayValue
|
|
39
|
+
(→ list), bytesValue.
|
|
40
|
+
|
|
41
|
+
``bytesValue`` is returned unchanged. ``MessageToDict`` base64-encodes
|
|
42
|
+
protobuf bytes fields and OTLP/JSON does the same, so callers already
|
|
43
|
+
receive a str; decoding it here would change the value they see today.
|
|
44
|
+
|
|
45
|
+
A value carrying none of the union fields is returned as-is.
|
|
46
|
+
"""
|
|
47
|
+
if "stringValue" in value_obj:
|
|
48
|
+
return value_obj["stringValue"]
|
|
49
|
+
if "intValue" in value_obj:
|
|
50
|
+
return int(value_obj["intValue"])
|
|
51
|
+
if "doubleValue" in value_obj:
|
|
52
|
+
return float(value_obj["doubleValue"])
|
|
53
|
+
if "boolValue" in value_obj:
|
|
54
|
+
return value_obj["boolValue"]
|
|
55
|
+
if "kvlistValue" in value_obj:
|
|
56
|
+
kv = value_obj["kvlistValue"]
|
|
57
|
+
return {item.get("key", ""): decode_any_value(item.get("value", {})) for item in kv.get("values", [])}
|
|
58
|
+
if "arrayValue" in value_obj:
|
|
59
|
+
arr = value_obj["arrayValue"]
|
|
60
|
+
return [decode_any_value(v) for v in arr.get("values", [])]
|
|
61
|
+
if "bytesValue" in value_obj:
|
|
62
|
+
return value_obj["bytesValue"]
|
|
63
|
+
return value_obj
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_any_value(value_obj: dict) -> bool:
|
|
67
|
+
"""Return True when *value_obj* carries one of the ``AnyValue`` fields."""
|
|
68
|
+
for field in ANY_VALUE_FIELDS:
|
|
69
|
+
if field in value_obj:
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def decode_attribute(key: str, value_obj: dict) -> tuple[bool, Any]:
|
|
75
|
+
"""Decode one attribute, applying the container allowlist.
|
|
76
|
+
|
|
77
|
+
Returns ``(keep, value)``. Scalars are always kept. A list or dict is kept
|
|
78
|
+
only when *key* is in :data:`SPEC_CONTAINER_ATTRS`; otherwise it is dropped,
|
|
79
|
+
which is what ``extraction.py`` did with containers before this decoder was
|
|
80
|
+
shared.
|
|
81
|
+
|
|
82
|
+
Dropping rather than serialising is deliberate: JSON-dumping the value would
|
|
83
|
+
put a blob back into user-visible output, which is the symptom #173 is
|
|
84
|
+
about. What the default *should* be is tracked in #208.
|
|
85
|
+
"""
|
|
86
|
+
value = decode_any_value(value_obj)
|
|
87
|
+
if isinstance(value, (list, dict)) and key not in SPEC_CONTAINER_ATTRS:
|
|
88
|
+
logger.warning(
|
|
89
|
+
"Dropping container value for %s (got %s); only spec container attributes are kept",
|
|
90
|
+
key,
|
|
91
|
+
type(value).__name__,
|
|
92
|
+
)
|
|
93
|
+
return False, None
|
|
94
|
+
return True, value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def decode_attributes(attrs_list: list[dict]) -> dict[str, Any]:
|
|
98
|
+
"""Decode an OTLP attributes array to a flat ``{key: value}`` dict.
|
|
99
|
+
|
|
100
|
+
Entries whose value carries no ``AnyValue`` field are skipped, matching the
|
|
101
|
+
behaviour every call site had before they shared this decoder.
|
|
102
|
+
|
|
103
|
+
Container values survive only for the keys in
|
|
104
|
+
:data:`~agentevals.trace_attrs.SPEC_CONTAINER_ATTRS`. Keeping the allowlist
|
|
105
|
+
here rather than narrowing per consumer means an attribute nobody has
|
|
106
|
+
thought about cannot become an unhashable dict key downstream - there is
|
|
107
|
+
nothing to remember, because it was never widened in the first place.
|
|
108
|
+
"""
|
|
109
|
+
result: dict[str, Any] = {}
|
|
110
|
+
for attr in attrs_list:
|
|
111
|
+
value_obj = attr.get("value", {})
|
|
112
|
+
if not is_any_value(value_obj):
|
|
113
|
+
continue
|
|
114
|
+
key = attr.get("key", "")
|
|
115
|
+
keep, value = decode_attribute(key, value_obj)
|
|
116
|
+
if keep:
|
|
117
|
+
result[key] = value
|
|
118
|
+
return result
|
|
@@ -84,3 +84,33 @@ GENAI_ATTRIBUTE_ALIASES: dict[str, list[str]] = {
|
|
|
84
84
|
|
|
85
85
|
# agentevals custom attributes (repository-specific, outside OTel semconv)
|
|
86
86
|
AGENTEVALS_SESSION_ID = "agentevals.session_id"
|
|
87
|
+
AGENTEVALS_EVAL_SET_ID = "agentevals.eval_set_id"
|
|
88
|
+
AGENTEVALS_SESSION_NAME = "agentevals.session_name"
|
|
89
|
+
|
|
90
|
+
# Attributes the GenAI semconv types as arrays or structured values. These are
|
|
91
|
+
# the only keys the shared AnyValue decoder returns as a native list or dict;
|
|
92
|
+
# every other key is decoded to a scalar or dropped, which is what
|
|
93
|
+
# ``extraction.py`` did before the decoder was shared.
|
|
94
|
+
#
|
|
95
|
+
# The set is an allowlist on purpose. A missing entry drops one value, matching
|
|
96
|
+
# the pre-existing extraction behaviour; a denylist would instead let an
|
|
97
|
+
# unhashable value reach a dict key or set member on an unauthenticated
|
|
98
|
+
# receiver port, which crashes ingestion. See the discussion on #187.
|
|
99
|
+
#
|
|
100
|
+
# Hand-maintained: opentelemetry-semantic-conventions exposes constants and
|
|
101
|
+
# prose docstrings only, so attribute value types are not machine-readable and
|
|
102
|
+
# this set cannot be derived from the package.
|
|
103
|
+
#
|
|
104
|
+
# What should happen to container values on keys outside this set is not
|
|
105
|
+
# settled; today they are dropped. Tracked in #208.
|
|
106
|
+
SPEC_CONTAINER_ATTRS: frozenset[str] = frozenset(
|
|
107
|
+
{
|
|
108
|
+
OTEL_GENAI_RESPONSE_FINISH_REASONS,
|
|
109
|
+
OTEL_GENAI_INPUT_MESSAGES,
|
|
110
|
+
OTEL_GENAI_OUTPUT_MESSAGES,
|
|
111
|
+
OTEL_GENAI_TOOL_DEFINITIONS,
|
|
112
|
+
OTEL_GENAI_SYSTEM_INSTRUCTIONS,
|
|
113
|
+
OTEL_GENAI_TOOL_CALL_ARGUMENTS,
|
|
114
|
+
OTEL_GENAI_TOOL_CALL_RESULT,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
+
from agentevals import trace_attrs
|
|
9
10
|
from agentevals.extraction import (
|
|
10
11
|
AdkExtractor,
|
|
11
12
|
GenAIExtractor,
|
|
@@ -400,6 +401,129 @@ class TestFlattenOtlpAttributes:
|
|
|
400
401
|
)
|
|
401
402
|
assert result == {"str": "hello", "num": 3.14, "flag": True}
|
|
402
403
|
|
|
404
|
+
def test_array_value(self):
|
|
405
|
+
result = flatten_otlp_attributes(
|
|
406
|
+
[
|
|
407
|
+
{
|
|
408
|
+
"key": "gen_ai.response.finish_reasons",
|
|
409
|
+
"value": {"arrayValue": {"values": [{"stringValue": "stop"}]}},
|
|
410
|
+
},
|
|
411
|
+
]
|
|
412
|
+
)
|
|
413
|
+
assert result == {"gen_ai.response.finish_reasons": ["stop"]}
|
|
414
|
+
|
|
415
|
+
def test_kvlist_value(self):
|
|
416
|
+
result = flatten_otlp_attributes(
|
|
417
|
+
[
|
|
418
|
+
{
|
|
419
|
+
"key": "gen_ai.tool.call.arguments",
|
|
420
|
+
"value": {
|
|
421
|
+
"kvlistValue": {
|
|
422
|
+
"values": [
|
|
423
|
+
{"key": "city", "value": {"stringValue": "Berlin"}},
|
|
424
|
+
{"key": "metric", "value": {"boolValue": False}},
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
]
|
|
430
|
+
)
|
|
431
|
+
assert result == {"gen_ai.tool.call.arguments": {"city": "Berlin", "metric": False}}
|
|
432
|
+
|
|
433
|
+
def test_array_of_kvlist(self):
|
|
434
|
+
"""Messages arrive as an arrayValue of kvlistValue."""
|
|
435
|
+
result = flatten_otlp_attributes(
|
|
436
|
+
[
|
|
437
|
+
{
|
|
438
|
+
"key": "gen_ai.input.messages",
|
|
439
|
+
"value": {
|
|
440
|
+
"arrayValue": {
|
|
441
|
+
"values": [
|
|
442
|
+
{"kvlistValue": {"values": [{"key": "role", "value": {"stringValue": "user"}}]}},
|
|
443
|
+
]
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
]
|
|
448
|
+
)
|
|
449
|
+
assert result == {"gen_ai.input.messages": [{"role": "user"}]}
|
|
450
|
+
|
|
451
|
+
def test_finish_reasons_survive_to_extracted_model_info(self):
|
|
452
|
+
"""The symptom #173 names: gen_ai.response.finish_reasons reaching the
|
|
453
|
+
consumer as ["stop"] rather than a literal blob or nothing.
|
|
454
|
+
|
|
455
|
+
Asserting through extract_extended_model_info_from_attrs rather than at
|
|
456
|
+
the decoder keeps the whole path covered - decoding it correctly is not
|
|
457
|
+
the same as it arriving correctly.
|
|
458
|
+
"""
|
|
459
|
+
attrs = flatten_otlp_attributes(
|
|
460
|
+
[
|
|
461
|
+
{
|
|
462
|
+
"key": "gen_ai.response.finish_reasons",
|
|
463
|
+
"value": {"arrayValue": {"values": [{"stringValue": "stop"}]}},
|
|
464
|
+
},
|
|
465
|
+
{"key": "gen_ai.response.model", "value": {"stringValue": "claude-opus-5"}},
|
|
466
|
+
]
|
|
467
|
+
)
|
|
468
|
+
info = extract_extended_model_info_from_attrs(attrs)
|
|
469
|
+
assert info["finish_reasons"] == ["stop"]
|
|
470
|
+
assert info["response_model"] == "claude-opus-5"
|
|
471
|
+
|
|
472
|
+
def test_multiple_finish_reasons_survive(self):
|
|
473
|
+
attrs = flatten_otlp_attributes(
|
|
474
|
+
[
|
|
475
|
+
{
|
|
476
|
+
"key": "gen_ai.response.finish_reasons",
|
|
477
|
+
"value": {"arrayValue": {"values": [{"stringValue": "stop"}, {"stringValue": "length"}]}},
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
)
|
|
481
|
+
assert extract_extended_model_info_from_attrs(attrs)["finish_reasons"] == [
|
|
482
|
+
"stop",
|
|
483
|
+
"length",
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
def test_unlisted_key_drops_container_value(self):
|
|
487
|
+
"""Containers survive only for SPEC_CONTAINER_ATTRS. Everything else is
|
|
488
|
+
dropped, which is what extraction did before the decoder was shared."""
|
|
489
|
+
attrs = flatten_otlp_attributes(
|
|
490
|
+
[
|
|
491
|
+
{
|
|
492
|
+
"key": "gen_ai.response.model",
|
|
493
|
+
"value": {"arrayValue": {"values": [{"stringValue": "claude-opus-5"}]}},
|
|
494
|
+
},
|
|
495
|
+
{"key": "gen_ai.request.model", "value": {"stringValue": "claude-sonnet-5"}},
|
|
496
|
+
]
|
|
497
|
+
)
|
|
498
|
+
assert "gen_ai.response.model" not in attrs
|
|
499
|
+
info = extract_extended_model_info_from_attrs(attrs)
|
|
500
|
+
assert info["response_model"] is None
|
|
501
|
+
assert info["request_model"] == "claude-sonnet-5"
|
|
502
|
+
|
|
503
|
+
def test_no_unlisted_key_can_yield_an_unhashable_value(self):
|
|
504
|
+
"""The property the allowlist exists for: nothing outside
|
|
505
|
+
SPEC_CONTAINER_ATTRS can reach a consumer as a dict key or set member
|
|
506
|
+
and raise TypeError. Covers every attribute constant we declare, so a
|
|
507
|
+
new one cannot quietly reopen the hazard."""
|
|
508
|
+
container = {"arrayValue": {"values": [{"stringValue": "x"}]}}
|
|
509
|
+
for name in dir(trace_attrs):
|
|
510
|
+
if not name.isupper():
|
|
511
|
+
continue
|
|
512
|
+
key = getattr(trace_attrs, name)
|
|
513
|
+
if not isinstance(key, str):
|
|
514
|
+
continue
|
|
515
|
+
value = flatten_otlp_attributes([{"key": key, "value": container}]).get(key)
|
|
516
|
+
if key in trace_attrs.SPEC_CONTAINER_ATTRS:
|
|
517
|
+
assert value == ["x"], f"{key} should keep its container"
|
|
518
|
+
else:
|
|
519
|
+
assert value is None, f"{key} leaked a container"
|
|
520
|
+
hash(value)
|
|
521
|
+
|
|
522
|
+
def test_bytes_value(self):
|
|
523
|
+
"""MessageToDict base64-encodes bytes fields, so the decoder sees a str."""
|
|
524
|
+
result = flatten_otlp_attributes([{"key": "payload", "value": {"bytesValue": "AP9oaQ=="}}])
|
|
525
|
+
assert result == {"payload": "AP9oaQ=="}
|
|
526
|
+
|
|
403
527
|
def test_empty(self):
|
|
404
528
|
assert flatten_otlp_attributes([]) == {}
|
|
405
529
|
|