claude-smart 0.2.41 → 0.2.43
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.
- package/.claude-plugin/marketplace.json +17 -0
- package/README.md +1 -1
- package/bin/claude-smart.js +86 -48
- package/package.json +10 -3
- package/plugin/.claude-plugin/plugin.json +9 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +2 -2
- package/plugin/dashboard/next.config.ts +9 -1
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +91 -0
- package/plugin/scripts/backend-service.sh +46 -15
- package/plugin/scripts/cli.sh +29 -1
- package/plugin/scripts/codex-hook.js +72 -4
- package/plugin/scripts/dashboard-build.sh +1 -0
- package/plugin/scripts/dashboard-service.sh +1 -0
- package/plugin/scripts/ensure-plugin-root.sh +7 -14
- package/plugin/scripts/hook_entry.sh +1 -0
- package/plugin/scripts/smart-install.sh +18 -2
- package/plugin/src/claude_smart/cli.py +72 -38
- package/plugin/src/claude_smart/context_format.py +11 -12
- package/plugin/src/claude_smart/cs_cite.py +26 -12
- package/plugin/src/claude_smart/ids.py +13 -5
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +53 -0
- package/plugin/vendor/reflexio/LICENSE +201 -0
- package/plugin/vendor/reflexio/README.md +338 -0
- package/plugin/vendor/reflexio/pyproject.toml +271 -0
- package/plugin/vendor/reflexio/reflexio/README.md +184 -0
- package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
- package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
- package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
- package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +266 -0
- package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +961 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
- package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
- package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
- package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
- package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
- package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
- package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
- package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
- package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
- package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
- package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
- package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
- package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
- package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1103 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +766 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +280 -0
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
- package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +616 -0
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +2714 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +540 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
- package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1595 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +112 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +469 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +255 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +699 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +279 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +127 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +340 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1626 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +351 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +468 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +777 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +689 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +960 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +741 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +734 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +800 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +70 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +671 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1253 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +372 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +568 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
- package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
- package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
- package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
"""Aggregator that composes hero, tiles, rule attribution, and distribution.
|
|
2
|
+
|
|
3
|
+
The service does three reads (results, citations, playbook stats)
|
|
4
|
+
and returns a GetEvaluationOverviewResponse. It's invoked from the FastAPI
|
|
5
|
+
route handler; the storage is the same BaseStorage the rest of the server
|
|
6
|
+
uses, so the same instance is reused via request_context.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import UTC, datetime
|
|
15
|
+
|
|
16
|
+
from reflexio.models.api_schema.braintrust_schema import ImportedScore
|
|
17
|
+
from reflexio.models.api_schema.domain.entities import (
|
|
18
|
+
AgentSuccessEvaluationResult,
|
|
19
|
+
Request,
|
|
20
|
+
)
|
|
21
|
+
from reflexio.models.api_schema.eval_overview_schema import (
|
|
22
|
+
BraintrustTileRow,
|
|
23
|
+
BucketLiteral,
|
|
24
|
+
ContextTile,
|
|
25
|
+
GetEvaluationOverviewRequest,
|
|
26
|
+
GetEvaluationOverviewResponse,
|
|
27
|
+
HeroBlock,
|
|
28
|
+
HeroBucket,
|
|
29
|
+
NumberWithDelta,
|
|
30
|
+
PercentWithDelta,
|
|
31
|
+
RuleAttributionRow,
|
|
32
|
+
ScoreDistribution,
|
|
33
|
+
ShadowWinRateTrend,
|
|
34
|
+
SuccessRateTrendByGroup,
|
|
35
|
+
)
|
|
36
|
+
from reflexio.models.config_schema import Config
|
|
37
|
+
from reflexio.server.services.evaluation_overview.distribution import (
|
|
38
|
+
BUCKET_LABELS,
|
|
39
|
+
bucket_corrections,
|
|
40
|
+
)
|
|
41
|
+
from reflexio.server.services.evaluation_overview.group_aggregation import (
|
|
42
|
+
compute_trend_by_group,
|
|
43
|
+
)
|
|
44
|
+
from reflexio.server.services.evaluation_overview.hero_state import (
|
|
45
|
+
compute_hero_state,
|
|
46
|
+
)
|
|
47
|
+
from reflexio.server.services.evaluation_overview.rule_attribution import (
|
|
48
|
+
compute_net_sessions,
|
|
49
|
+
)
|
|
50
|
+
from reflexio.server.services.evaluation_overview.shadow_aggregation import (
|
|
51
|
+
compute_shadow_win_rate_trend,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
_DAY_SECONDS = 24 * 60 * 60
|
|
55
|
+
_WEEK_SECONDS = 7 * 24 * 60 * 60
|
|
56
|
+
_TOP_N_RULES = 5
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class EvaluationOverviewService:
|
|
61
|
+
"""Builds the full /api/get_evaluation_overview payload.
|
|
62
|
+
|
|
63
|
+
The service holds a storage handle and the org's current Config. Each
|
|
64
|
+
call to `run` performs the three reads and returns a fresh response.
|
|
65
|
+
Stateless across calls — safe to reuse the instance across requests.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
storage: object
|
|
69
|
+
config: Config
|
|
70
|
+
|
|
71
|
+
def run(
|
|
72
|
+
self, request: GetEvaluationOverviewRequest
|
|
73
|
+
) -> GetEvaluationOverviewResponse:
|
|
74
|
+
"""Build the overview payload for the requested window.
|
|
75
|
+
|
|
76
|
+
Time windows used here:
|
|
77
|
+
- ``[request.from_ts, request.to_ts]`` is the *trend* window. The hero
|
|
78
|
+
chart, rule attribution, and the rule-attribution session set are
|
|
79
|
+
all computed over it. The frontend sends an 8-week range so the
|
|
80
|
+
trend chart has shape.
|
|
81
|
+
- The *tile baseline* and *distribution baseline* are tighter:
|
|
82
|
+
``last_7d`` (≤ to_ts) vs ``prior_7d`` (the 7d before that).
|
|
83
|
+
Tying these to ``request.from_ts`` is wrong — with an 8-week
|
|
84
|
+
request, ``prior`` would land 9 weeks back and always be empty,
|
|
85
|
+
so every tile would display "no baseline" regardless of how much
|
|
86
|
+
data the org has.
|
|
87
|
+
"""
|
|
88
|
+
all_results = self.storage.get_agent_success_evaluation_results( # type: ignore[attr-defined]
|
|
89
|
+
agent_version=None, limit=10_000
|
|
90
|
+
)
|
|
91
|
+
results = [
|
|
92
|
+
r for r in all_results if request.from_ts <= r.created_at <= request.to_ts
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Tile + distribution baselines are always last-7d-vs-prior-7d, anchored
|
|
96
|
+
# to ``request.to_ts`` (which is "now" from the frontend's perspective).
|
|
97
|
+
cur_7d_from = max(request.from_ts, request.to_ts - _WEEK_SECONDS)
|
|
98
|
+
prev_to = cur_7d_from
|
|
99
|
+
prev_from = max(0, prev_to - _WEEK_SECONDS)
|
|
100
|
+
results_current_7d = [
|
|
101
|
+
r for r in all_results if cur_7d_from <= r.created_at <= request.to_ts
|
|
102
|
+
]
|
|
103
|
+
results_prev_7d = [
|
|
104
|
+
r for r in all_results if prev_from <= r.created_at < prev_to
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
earliest_eval_ts = min((r.created_at for r in all_results), default=None)
|
|
108
|
+
hero = self._build_hero(request, results, earliest_eval_ts)
|
|
109
|
+
tiles = self._build_tiles(results_current_7d, results_prev_7d)
|
|
110
|
+
attribution = self._build_attribution(results)
|
|
111
|
+
distribution = self._build_distribution(results_current_7d, results_prev_7d)
|
|
112
|
+
braintrust_tiles = self._build_braintrust_tiles(
|
|
113
|
+
cur_7d_from, request.to_ts, prev_from, prev_to
|
|
114
|
+
)
|
|
115
|
+
# F2: group-aware success-rate trend curves. Joins each eval result
|
|
116
|
+
# with its session's first request metadata to bucket sessions into
|
|
117
|
+
# treatment / control / untagged, then time-buckets each curve at
|
|
118
|
+
# the same granularity as the hero chart.
|
|
119
|
+
success_rate_trend_by_group = self._build_group_trend(results, request.bucket)
|
|
120
|
+
# F1: per-turn shadow win-rate trend. Filters verdicts to the org's
|
|
121
|
+
# pinned judge prompt version so a future rubric bump doesn't
|
|
122
|
+
# silently mix epochs into the headline.
|
|
123
|
+
shadow_win_rate_trend = self._build_shadow_win_rate_trend(
|
|
124
|
+
request.from_ts, request.to_ts
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return GetEvaluationOverviewResponse(
|
|
128
|
+
hero=hero,
|
|
129
|
+
context_tiles=tiles,
|
|
130
|
+
rule_attribution=attribution,
|
|
131
|
+
score_distribution=distribution,
|
|
132
|
+
braintrust_tiles=braintrust_tiles,
|
|
133
|
+
success_rate_trend_by_group=success_rate_trend_by_group,
|
|
134
|
+
shadow_win_rate_trend=shadow_win_rate_trend,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# --- private helpers ---
|
|
138
|
+
|
|
139
|
+
def _build_hero(
|
|
140
|
+
self,
|
|
141
|
+
request: GetEvaluationOverviewRequest,
|
|
142
|
+
results: list[AgentSuccessEvaluationResult],
|
|
143
|
+
earliest_eval_ts: int | None,
|
|
144
|
+
) -> HeroBlock:
|
|
145
|
+
if earliest_eval_ts is None:
|
|
146
|
+
days_since = None
|
|
147
|
+
else:
|
|
148
|
+
days_since = (
|
|
149
|
+
int(datetime.now(UTC).timestamp()) - earliest_eval_ts
|
|
150
|
+
) // 86_400
|
|
151
|
+
# Shadow direct-grade has been removed; counterfactual measurement is
|
|
152
|
+
# pending a methodologically sound replacement (see the validity spec).
|
|
153
|
+
# The state machine still runs so the EMPTY / SHADOW_OFF surfaces work,
|
|
154
|
+
# but the shadow-side fields are always None.
|
|
155
|
+
state = compute_hero_state(
|
|
156
|
+
shadow_enabled=False,
|
|
157
|
+
days_since_first_eval=days_since,
|
|
158
|
+
n_shadow_in_window=0,
|
|
159
|
+
total_results=len(results),
|
|
160
|
+
)
|
|
161
|
+
success_rate = _success_rate(results) * 100
|
|
162
|
+
return HeroBlock(
|
|
163
|
+
state=state.value, # type: ignore[arg-type]
|
|
164
|
+
regular_success_rate_pp=success_rate,
|
|
165
|
+
shadow_success_rate_pp=None,
|
|
166
|
+
delta_pp=None,
|
|
167
|
+
buckets=_buckets(results, request.bucket),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _build_tiles(
|
|
171
|
+
self,
|
|
172
|
+
current: list[AgentSuccessEvaluationResult],
|
|
173
|
+
previous: list[AgentSuccessEvaluationResult],
|
|
174
|
+
) -> ContextTile:
|
|
175
|
+
cur_success = _success_rate(current) * 100
|
|
176
|
+
prev_success = _success_rate(previous) * 100
|
|
177
|
+
cur_corr = _mean(r.number_of_correction_per_session for r in current)
|
|
178
|
+
prev_corr = _mean(r.number_of_correction_per_session for r in previous)
|
|
179
|
+
cur_turns = _mean(
|
|
180
|
+
r.user_turns_to_resolution
|
|
181
|
+
for r in current
|
|
182
|
+
if r.user_turns_to_resolution is not None
|
|
183
|
+
)
|
|
184
|
+
prev_turns = _mean(
|
|
185
|
+
r.user_turns_to_resolution
|
|
186
|
+
for r in previous
|
|
187
|
+
if r.user_turns_to_resolution is not None
|
|
188
|
+
)
|
|
189
|
+
cur_esc = _escalation_rate(current) * 100
|
|
190
|
+
prev_esc = _escalation_rate(previous) * 100
|
|
191
|
+
return ContextTile(
|
|
192
|
+
success=PercentWithDelta(
|
|
193
|
+
current=cur_success, delta_pp=cur_success - prev_success
|
|
194
|
+
),
|
|
195
|
+
corrections=NumberWithDelta(current=cur_corr, delta=cur_corr - prev_corr),
|
|
196
|
+
turns=NumberWithDelta(current=cur_turns, delta=cur_turns - prev_turns),
|
|
197
|
+
escalation=PercentWithDelta(current=cur_esc, delta_pp=cur_esc - prev_esc),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _build_attribution(
|
|
201
|
+
self, results: list[AgentSuccessEvaluationResult]
|
|
202
|
+
) -> list[RuleAttributionRow]:
|
|
203
|
+
is_success_by_session = {r.session_id: r.is_success for r in results}
|
|
204
|
+
citations_by_session, rule_titles = self._load_citations(
|
|
205
|
+
list(is_success_by_session.keys())
|
|
206
|
+
)
|
|
207
|
+
rows = compute_net_sessions(
|
|
208
|
+
citations_by_session=citations_by_session,
|
|
209
|
+
is_success_by_session=is_success_by_session,
|
|
210
|
+
rule_titles=rule_titles,
|
|
211
|
+
top_n=_TOP_N_RULES,
|
|
212
|
+
)
|
|
213
|
+
return [
|
|
214
|
+
RuleAttributionRow(
|
|
215
|
+
rule_id=r.rule_id,
|
|
216
|
+
kind=r.kind, # type: ignore[arg-type]
|
|
217
|
+
title=r.title,
|
|
218
|
+
successes_with=r.successes_with,
|
|
219
|
+
failures_with=r.failures_with,
|
|
220
|
+
net_sessions=r.net_sessions,
|
|
221
|
+
cited_session_ids=list(r.cited_session_ids),
|
|
222
|
+
)
|
|
223
|
+
for r in rows
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
def _load_citations(
|
|
227
|
+
self, session_ids: list[str]
|
|
228
|
+
) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str], str]]:
|
|
229
|
+
"""Pull `Interaction.citations` keyed by session, with title lookup.
|
|
230
|
+
|
|
231
|
+
Falls back to empty data when the underlying storage method returns
|
|
232
|
+
no interactions (default behavior on backends that haven't yet
|
|
233
|
+
implemented `get_interactions_by_session`).
|
|
234
|
+
"""
|
|
235
|
+
citations_by_session: dict[str, list[tuple[str, str]]] = defaultdict(list)
|
|
236
|
+
for sid in session_ids:
|
|
237
|
+
interactions = self.storage.get_interactions_by_session(sid) # type: ignore[attr-defined]
|
|
238
|
+
for interaction in interactions:
|
|
239
|
+
for cite in getattr(interaction, "citations", []) or []:
|
|
240
|
+
# Citations may arrive as Pydantic Citation objects (from
|
|
241
|
+
# the normal storage path) or as plain dicts (e.g. when
|
|
242
|
+
# tests stub the storage). Handle both shapes.
|
|
243
|
+
if isinstance(cite, dict):
|
|
244
|
+
kind = cite.get("kind")
|
|
245
|
+
rid = cite.get("real_id")
|
|
246
|
+
else:
|
|
247
|
+
kind = getattr(cite, "kind", None)
|
|
248
|
+
rid = getattr(cite, "real_id", None)
|
|
249
|
+
if kind and rid:
|
|
250
|
+
citations_by_session[sid].append((kind, str(rid)))
|
|
251
|
+
# Titles via existing playbook_application_stats lookup
|
|
252
|
+
stats = self.storage.get_playbook_application_stats(days_back=30) # type: ignore[attr-defined]
|
|
253
|
+
rule_titles = {(s.kind, s.real_id): s.title for s in stats}
|
|
254
|
+
return citations_by_session, rule_titles
|
|
255
|
+
|
|
256
|
+
def _build_braintrust_tiles(
|
|
257
|
+
self, from_ts: int, to_ts: int, prev_from: int, prev_to: int
|
|
258
|
+
) -> list[BraintrustTileRow]:
|
|
259
|
+
"""Aggregate imported_score rows per scorer_name for current + prior windows.
|
|
260
|
+
|
|
261
|
+
Returns [] when the org has no Braintrust connection (default no-op
|
|
262
|
+
storage returns []). The frontend treats an empty list as "not
|
|
263
|
+
connected" and hides the Braintrust strip entirely.
|
|
264
|
+
"""
|
|
265
|
+
org_id = self._org_id()
|
|
266
|
+
if not org_id:
|
|
267
|
+
return []
|
|
268
|
+
current = self.storage.get_imported_scores(org_id, from_ts, to_ts) # type: ignore[attr-defined]
|
|
269
|
+
if not current:
|
|
270
|
+
return []
|
|
271
|
+
prior = self.storage.get_imported_scores(org_id, prev_from, prev_to) # type: ignore[attr-defined]
|
|
272
|
+
cur_agg = _aggregate_imported_scores(current)
|
|
273
|
+
prior_agg = _aggregate_imported_scores(prior)
|
|
274
|
+
rows: list[BraintrustTileRow] = []
|
|
275
|
+
for scorer_name, (mean, n) in sorted(cur_agg.items()):
|
|
276
|
+
# No prior data → set delta = current so the frontend's
|
|
277
|
+
# `delta == current` check renders "no baseline" honestly.
|
|
278
|
+
# With prior data → real difference.
|
|
279
|
+
prior_entry = prior_agg.get(scorer_name)
|
|
280
|
+
delta = mean if prior_entry is None else mean - prior_entry[0]
|
|
281
|
+
rows.append(
|
|
282
|
+
BraintrustTileRow(
|
|
283
|
+
scorer_name=scorer_name,
|
|
284
|
+
current=mean,
|
|
285
|
+
n=n,
|
|
286
|
+
delta=delta,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
return rows
|
|
290
|
+
|
|
291
|
+
def _org_id(self) -> str:
|
|
292
|
+
"""Resolve org_id from request_context when available; else empty string."""
|
|
293
|
+
# The service is constructed with `storage` + `config`; org_id isn't a
|
|
294
|
+
# direct attribute. For Plan C-overview, we read it via the storage
|
|
295
|
+
# instance's org_id when present (every BaseStorage carries one).
|
|
296
|
+
return str(getattr(self.storage, "org_id", "") or "")
|
|
297
|
+
|
|
298
|
+
def _build_shadow_win_rate_trend(
|
|
299
|
+
self,
|
|
300
|
+
from_ts: int,
|
|
301
|
+
to_ts: int,
|
|
302
|
+
) -> ShadowWinRateTrend:
|
|
303
|
+
"""Fetch shadow verdicts in the window and aggregate them per day.
|
|
304
|
+
|
|
305
|
+
Filters verdicts to the org's pinned ``shadow_comparison`` prompt
|
|
306
|
+
version (``Config.shadow_comparison_judge_prompt_version``) so a
|
|
307
|
+
future rubric bump never silently mixes epochs into the headline.
|
|
308
|
+
Backends that don't yet implement the verdicts table raise
|
|
309
|
+
``NotImplementedError``; we degrade to the empty trend default so
|
|
310
|
+
the dashboard still renders the rest of the overview.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
from_ts (int): Window start, Unix epoch seconds (UTC).
|
|
314
|
+
to_ts (int): Window end, Unix epoch seconds (UTC).
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
ShadowWinRateTrend: Daily buckets + window total. Empty when
|
|
318
|
+
the window has no verdicts or the backend doesn't support
|
|
319
|
+
verdict storage.
|
|
320
|
+
"""
|
|
321
|
+
pinned_version = self.config.shadow_comparison_judge_prompt_version
|
|
322
|
+
try:
|
|
323
|
+
verdicts = self.storage.get_shadow_comparison_verdicts( # type: ignore[attr-defined]
|
|
324
|
+
from_ts=from_ts,
|
|
325
|
+
to_ts=to_ts,
|
|
326
|
+
judge_prompt_version=pinned_version,
|
|
327
|
+
)
|
|
328
|
+
except NotImplementedError:
|
|
329
|
+
return ShadowWinRateTrend(judge_prompt_version=pinned_version)
|
|
330
|
+
return compute_shadow_win_rate_trend(
|
|
331
|
+
verdicts, judge_prompt_version=pinned_version
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _build_group_trend(
|
|
335
|
+
self,
|
|
336
|
+
results: list[AgentSuccessEvaluationResult],
|
|
337
|
+
bucket: str,
|
|
338
|
+
) -> SuccessRateTrendByGroup:
|
|
339
|
+
"""Build the dual+untagged-curve trend payload for the window.
|
|
340
|
+
|
|
341
|
+
Joins each eval result with the first request of its session to read
|
|
342
|
+
``metadata.reflexio_retrieval_enabled``, then delegates to the pure
|
|
343
|
+
``compute_trend_by_group`` aggregator. Returns the default empty
|
|
344
|
+
``SuccessRateTrendByGroup`` when ``results`` is empty.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
results (list[AgentSuccessEvaluationResult]): Eval results in the
|
|
348
|
+
trend window.
|
|
349
|
+
bucket (str): Bucket granularity literal (``"week"`` or ``"day"``).
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
SuccessRateTrendByGroup: Three curves (any of which may be empty).
|
|
353
|
+
"""
|
|
354
|
+
if not results:
|
|
355
|
+
return SuccessRateTrendByGroup()
|
|
356
|
+
bucket_seconds = _WEEK_SECONDS if bucket == "week" else _DAY_SECONDS
|
|
357
|
+
outcomes = self._build_group_outcomes(results)
|
|
358
|
+
return compute_trend_by_group(outcomes, bucket_seconds)
|
|
359
|
+
|
|
360
|
+
def _build_group_outcomes(
|
|
361
|
+
self,
|
|
362
|
+
results: list[AgentSuccessEvaluationResult],
|
|
363
|
+
) -> list[tuple[str, int, bool, dict]]:
|
|
364
|
+
"""Pair each eval result with its session's first-request metadata.
|
|
365
|
+
|
|
366
|
+
Caches ``session_id → first_request_metadata`` so storage is hit
|
|
367
|
+
once per distinct session, not once per result. Sessions without a
|
|
368
|
+
matching request fall through with an empty-dict metadata, which the
|
|
369
|
+
downstream aggregator routes to the UNTAGGED bucket.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
results (list[AgentSuccessEvaluationResult]): Eval results to
|
|
373
|
+
annotate with their session's first-request metadata.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
list[tuple[str, int, bool, dict]]: Tuples of
|
|
377
|
+
``(session_id, created_at, is_success, first_request_metadata)``
|
|
378
|
+
suitable for :func:`compute_trend_by_group`.
|
|
379
|
+
"""
|
|
380
|
+
session_ids = {r.session_id for r in results if r.session_id}
|
|
381
|
+
first_request_metadata: dict[str, dict] = {}
|
|
382
|
+
for sid in session_ids:
|
|
383
|
+
reqs = self._get_session_requests(sid)
|
|
384
|
+
if reqs:
|
|
385
|
+
first = min(reqs, key=lambda r: r.created_at)
|
|
386
|
+
first_request_metadata[sid] = first.metadata or {}
|
|
387
|
+
else:
|
|
388
|
+
first_request_metadata[sid] = {}
|
|
389
|
+
|
|
390
|
+
return [
|
|
391
|
+
(
|
|
392
|
+
r.session_id or "",
|
|
393
|
+
r.created_at,
|
|
394
|
+
r.is_success,
|
|
395
|
+
first_request_metadata.get(r.session_id or "", {}),
|
|
396
|
+
)
|
|
397
|
+
for r in results
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
def _get_session_requests(self, session_id: str) -> list[Request]:
|
|
401
|
+
"""Return every request in ``session_id`` regardless of user.
|
|
402
|
+
|
|
403
|
+
Uses ``BaseStorage.get_sessions(session_id=...)`` because the
|
|
404
|
+
per-session, user-id-required ``get_requests_by_session`` doesn't
|
|
405
|
+
fit our caller (eval results don't carry ``user_id``). All locally
|
|
406
|
+
testable backends ignore the ``user_id`` filter when it's ``None``,
|
|
407
|
+
and ``top_k`` is raised well above realistic per-session counts so
|
|
408
|
+
we don't truncate.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
session_id (str): The session whose requests to fetch.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
list[Request]: All requests in the session, in storage order.
|
|
415
|
+
"""
|
|
416
|
+
grouped = self.storage.get_sessions( # type: ignore[attr-defined]
|
|
417
|
+
session_id=session_id, top_k=1000
|
|
418
|
+
)
|
|
419
|
+
return [
|
|
420
|
+
entry.request
|
|
421
|
+
for entries in grouped.values()
|
|
422
|
+
for entry in entries
|
|
423
|
+
if entry.request is not None
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
def _build_distribution(
|
|
427
|
+
self,
|
|
428
|
+
current: list[AgentSuccessEvaluationResult],
|
|
429
|
+
previous: list[AgentSuccessEvaluationResult],
|
|
430
|
+
) -> ScoreDistribution:
|
|
431
|
+
cur_bins = bucket_corrections(
|
|
432
|
+
r.number_of_correction_per_session for r in current
|
|
433
|
+
)
|
|
434
|
+
prev_bins = bucket_corrections(
|
|
435
|
+
r.number_of_correction_per_session for r in previous
|
|
436
|
+
)
|
|
437
|
+
return ScoreDistribution(
|
|
438
|
+
current_bins=list(cur_bins),
|
|
439
|
+
baseline_bins=list(prev_bins),
|
|
440
|
+
labels=list(BUCKET_LABELS),
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# --- module-level helpers (pure) ---
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _success_rate(results: list[AgentSuccessEvaluationResult]) -> float:
|
|
448
|
+
if not results:
|
|
449
|
+
return 0.0
|
|
450
|
+
return sum(1 for r in results if r.is_success) / len(results)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _escalation_rate(results: list[AgentSuccessEvaluationResult]) -> float:
|
|
454
|
+
if not results:
|
|
455
|
+
return 0.0
|
|
456
|
+
return sum(1 for r in results if r.is_escalated) / len(results)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _mean(values: Iterable[float | int | None]) -> float:
|
|
460
|
+
nums = [float(v) for v in values if v is not None]
|
|
461
|
+
if not nums:
|
|
462
|
+
return 0.0
|
|
463
|
+
return sum(nums) / len(nums)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _aggregate_imported_scores(
|
|
467
|
+
scores: list[ImportedScore],
|
|
468
|
+
) -> dict[str, tuple[float, int]]:
|
|
469
|
+
"""Group imported scores by scorer_name → (mean, count)."""
|
|
470
|
+
bucket: dict[str, list[float]] = defaultdict(list)
|
|
471
|
+
for s in scores:
|
|
472
|
+
bucket[s.scorer_name].append(s.value)
|
|
473
|
+
return {name: (sum(vs) / len(vs), len(vs)) for name, vs in bucket.items() if vs}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _buckets(
|
|
477
|
+
results: list[AgentSuccessEvaluationResult], bucket: BucketLiteral
|
|
478
|
+
) -> list[HeroBucket]:
|
|
479
|
+
"""Build day- or week-sized buckets across the given results.
|
|
480
|
+
|
|
481
|
+
Bucket granularity follows the request: ``"day"`` for the frontend's
|
|
482
|
+
daily trend mode (tooltip activates on every X position, smoother
|
|
483
|
+
curves on narrow ranges), ``"week"`` otherwise. Each bucket carries
|
|
484
|
+
success rate, average corrections, and escalation rate so the metric
|
|
485
|
+
mini-trends stay aligned with the headline numbers.
|
|
486
|
+
"""
|
|
487
|
+
if not results:
|
|
488
|
+
return []
|
|
489
|
+
buckets: dict[int, list[AgentSuccessEvaluationResult]] = defaultdict(list)
|
|
490
|
+
step = _DAY_SECONDS if bucket == "day" else _WEEK_SECONDS
|
|
491
|
+
for r in results:
|
|
492
|
+
# Anchor each result to the START of its bucket (epoch-aligned).
|
|
493
|
+
bucket_start = (r.created_at // step) * step
|
|
494
|
+
buckets[bucket_start].append(r)
|
|
495
|
+
out: list[HeroBucket] = []
|
|
496
|
+
for ts in sorted(buckets):
|
|
497
|
+
bucket_results = buckets[ts]
|
|
498
|
+
avg_corr = _mean(r.number_of_correction_per_session for r in bucket_results)
|
|
499
|
+
# is_escalated may be None on legacy rows; coerce to False so the
|
|
500
|
+
# bucket mean reflects "fraction of sessions we know escalated".
|
|
501
|
+
escalation_rate = _mean(
|
|
502
|
+
(1.0 if (r.is_escalated is True) else 0.0) for r in bucket_results
|
|
503
|
+
)
|
|
504
|
+
out.append(
|
|
505
|
+
HeroBucket(
|
|
506
|
+
ts=ts,
|
|
507
|
+
regular_rate=_success_rate(bucket_results),
|
|
508
|
+
shadow_rate=None,
|
|
509
|
+
regular_n=len(bucket_results),
|
|
510
|
+
shadow_n=0,
|
|
511
|
+
avg_corrections=avg_corr,
|
|
512
|
+
escalation_rate=escalation_rate,
|
|
513
|
+
)
|
|
514
|
+
)
|
|
515
|
+
return out
|
package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""F1 shadow win-rate aggregator: ShadowComparisonVerdict[] → ShadowWinRateTrend.
|
|
2
|
+
|
|
3
|
+
Pure: DB-free, LLM-free. Bucketed by UTC calendar date; the window total is
|
|
4
|
+
computed by summing the daily buckets, so it is consistent with what the
|
|
5
|
+
dashboard renders by construction.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from datetime import UTC
|
|
13
|
+
|
|
14
|
+
from reflexio.models.api_schema.eval_overview_schema import (
|
|
15
|
+
ShadowComparisonVerdict,
|
|
16
|
+
ShadowWinRateTrend,
|
|
17
|
+
ShadowWinRateTrendPoint,
|
|
18
|
+
ShadowWinRateTrendWindowTotal,
|
|
19
|
+
)
|
|
20
|
+
from reflexio.server.services.shadow_comparison.outcome import (
|
|
21
|
+
Outcome,
|
|
22
|
+
derive_reflexio_outcome,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Map outcome enum → bucket key used in the per-day accumulator below.
|
|
26
|
+
_OUTCOME_TO_KEY: dict[Outcome, str] = {
|
|
27
|
+
Outcome.WIN: "wins",
|
|
28
|
+
Outcome.LOSS: "losses",
|
|
29
|
+
Outcome.TIE: "ties",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def compute_shadow_win_rate_trend(
|
|
34
|
+
verdicts: Iterable[ShadowComparisonVerdict],
|
|
35
|
+
judge_prompt_version: str = "v1.0.0",
|
|
36
|
+
) -> ShadowWinRateTrend:
|
|
37
|
+
"""
|
|
38
|
+
Bucket verdicts by UTC date, derive win/loss/tie per verdict, sum.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
verdicts (Iterable[ShadowComparisonVerdict]): Position-randomized
|
|
42
|
+
verdicts in the trend window. Caller is expected to have
|
|
43
|
+
already filtered to the pinned prompt version.
|
|
44
|
+
judge_prompt_version (str): Informational; echoed on the response
|
|
45
|
+
so the dashboard knows which rubric epoch the numbers came
|
|
46
|
+
from. Defaults to ``"v1.0.0"``.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
ShadowWinRateTrend: Daily buckets sorted ascending by date, plus
|
|
50
|
+
the window total computed from those same buckets.
|
|
51
|
+
"""
|
|
52
|
+
by_day: dict[str, dict[str, int]] = defaultdict(
|
|
53
|
+
lambda: {"n": 0, "wins": 0, "losses": 0, "ties": 0}
|
|
54
|
+
)
|
|
55
|
+
for verdict in verdicts:
|
|
56
|
+
date_key = verdict.created_at.astimezone(UTC).strftime("%Y-%m-%d")
|
|
57
|
+
bucket = by_day[date_key]
|
|
58
|
+
bucket["n"] += 1
|
|
59
|
+
bucket[_OUTCOME_TO_KEY[derive_reflexio_outcome(verdict)]] += 1
|
|
60
|
+
|
|
61
|
+
daily = [
|
|
62
|
+
ShadowWinRateTrendPoint(
|
|
63
|
+
date=date,
|
|
64
|
+
n=bucket["n"],
|
|
65
|
+
wins=bucket["wins"],
|
|
66
|
+
losses=bucket["losses"],
|
|
67
|
+
ties=bucket["ties"],
|
|
68
|
+
)
|
|
69
|
+
for date, bucket in sorted(by_day.items())
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
total_n = sum(p.n for p in daily)
|
|
73
|
+
total_wins = sum(p.wins for p in daily)
|
|
74
|
+
total_losses = sum(p.losses for p in daily)
|
|
75
|
+
total_ties = sum(p.ties for p in daily)
|
|
76
|
+
win_rate = total_wins / total_n if total_n > 0 else 0.0
|
|
77
|
+
net_win = (total_wins - total_losses) / total_n if total_n > 0 else 0.0
|
|
78
|
+
|
|
79
|
+
return ShadowWinRateTrend(
|
|
80
|
+
daily=daily,
|
|
81
|
+
window_total=ShadowWinRateTrendWindowTotal(
|
|
82
|
+
n=total_n,
|
|
83
|
+
wins=total_wins,
|
|
84
|
+
losses=total_losses,
|
|
85
|
+
ties=total_ties,
|
|
86
|
+
win_rate=win_rate,
|
|
87
|
+
net_win=net_win,
|
|
88
|
+
),
|
|
89
|
+
judge_prompt_version=judge_prompt_version,
|
|
90
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Helpers for constructing durable extraction agent run records."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import asdict, is_dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
|
|
12
|
+
from reflexio.server.services.storage.storage_base import (
|
|
13
|
+
AgentBinding,
|
|
14
|
+
AgentRunRecord,
|
|
15
|
+
AgentRunStatus,
|
|
16
|
+
build_scope_hash,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _jsonable(value: Any) -> Any:
|
|
21
|
+
if isinstance(value, BaseModel):
|
|
22
|
+
return value.model_dump(mode="json")
|
|
23
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
24
|
+
return _jsonable(asdict(value))
|
|
25
|
+
if isinstance(value, dict):
|
|
26
|
+
return {str(k): _jsonable(v) for k, v in value.items()}
|
|
27
|
+
if isinstance(value, (list, tuple, set)):
|
|
28
|
+
return [_jsonable(item) for item in value]
|
|
29
|
+
if value is None or isinstance(value, str | int | float | bool):
|
|
30
|
+
return value
|
|
31
|
+
return str(value)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def extract_source_interaction_ids(
|
|
35
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
36
|
+
) -> list[int]:
|
|
37
|
+
return [
|
|
38
|
+
interaction.interaction_id
|
|
39
|
+
for data_model in request_interaction_data_models
|
|
40
|
+
for interaction in data_model.interactions
|
|
41
|
+
if interaction.interaction_id is not None
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build_extractor_agent_run_record(
|
|
46
|
+
*,
|
|
47
|
+
org_id: str,
|
|
48
|
+
extractor_kind: str,
|
|
49
|
+
user_id: str | None,
|
|
50
|
+
request_id: str,
|
|
51
|
+
agent_version: str | None,
|
|
52
|
+
source: str | None,
|
|
53
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
54
|
+
extractor_config: BaseModel,
|
|
55
|
+
service_config: Any,
|
|
56
|
+
agent_context: str,
|
|
57
|
+
) -> AgentRunRecord:
|
|
58
|
+
source_interaction_ids = extract_source_interaction_ids(
|
|
59
|
+
request_interaction_data_models
|
|
60
|
+
)
|
|
61
|
+
extractor_config_snapshot = extractor_config.model_dump(mode="json")
|
|
62
|
+
|
|
63
|
+
return AgentRunRecord(
|
|
64
|
+
id=f"ar_{uuid.uuid4().hex}",
|
|
65
|
+
binding=AgentBinding(
|
|
66
|
+
org_id=org_id,
|
|
67
|
+
extractor_kind=extractor_kind,
|
|
68
|
+
user_id=user_id,
|
|
69
|
+
request_id=request_id,
|
|
70
|
+
agent_version=agent_version,
|
|
71
|
+
source=source,
|
|
72
|
+
source_interaction_ids=source_interaction_ids,
|
|
73
|
+
window_start_interaction_id=(
|
|
74
|
+
min(source_interaction_ids) if source_interaction_ids else None
|
|
75
|
+
),
|
|
76
|
+
window_end_interaction_id=(
|
|
77
|
+
max(source_interaction_ids) if source_interaction_ids else None
|
|
78
|
+
),
|
|
79
|
+
extractor_config_hash=build_scope_hash(extractor_config_snapshot),
|
|
80
|
+
),
|
|
81
|
+
status=AgentRunStatus.RUNNING,
|
|
82
|
+
generation_request_snapshot={
|
|
83
|
+
"request_id": request_id,
|
|
84
|
+
"source": source,
|
|
85
|
+
"source_interaction_ids": source_interaction_ids,
|
|
86
|
+
"session_count": len(request_interaction_data_models),
|
|
87
|
+
"extractor_config": extractor_config_snapshot,
|
|
88
|
+
},
|
|
89
|
+
service_config_snapshot=_jsonable(service_config),
|
|
90
|
+
agent_context_snapshot=agent_context,
|
|
91
|
+
)
|