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
package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from reflexio.cli.paths import reflexio_home
|
|
10
|
+
from reflexio.models.config_schema import (
|
|
11
|
+
Config,
|
|
12
|
+
PlaybookConfig,
|
|
13
|
+
ProfileExtractorConfig,
|
|
14
|
+
StorageConfigSQLite,
|
|
15
|
+
normalize_legacy_config_shape,
|
|
16
|
+
)
|
|
17
|
+
from reflexio.server.services.configurator.config_storage import ConfigStorage
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LocalFileConfigStorage(ConfigStorage):
|
|
23
|
+
"""
|
|
24
|
+
Local JSON file-based configuration storage implementation.
|
|
25
|
+
Saves/loads configuration to/from local JSON files.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, org_id: str, base_dir: str | None = None):
|
|
29
|
+
super().__init__(org_id=org_id)
|
|
30
|
+
if base_dir:
|
|
31
|
+
# Ensure base_dir is absolute
|
|
32
|
+
base_path = Path(base_dir)
|
|
33
|
+
abs_base_dir = (
|
|
34
|
+
str(base_path.resolve()) if not base_path.is_absolute() else base_dir
|
|
35
|
+
)
|
|
36
|
+
self.base_dir = str(Path(abs_base_dir) / "configs")
|
|
37
|
+
self.config_file = str(Path(self.base_dir) / f"config_{org_id}.json")
|
|
38
|
+
print(
|
|
39
|
+
f"LocalFileConfigStorage will save config for {org_id} to a local file at {self.config_file}"
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
self.base_dir = str(reflexio_home() / "configs")
|
|
43
|
+
self.config_file = str(Path(self.base_dir) / f"config_{org_id}.json")
|
|
44
|
+
|
|
45
|
+
def _default_storage_config(self) -> StorageConfigSQLite:
|
|
46
|
+
"""Return the default storage config (always SQLite for local file storage)."""
|
|
47
|
+
return StorageConfigSQLite()
|
|
48
|
+
|
|
49
|
+
def get_default_config(self) -> Config:
|
|
50
|
+
"""
|
|
51
|
+
Returns a default configuration with SQLite storage.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Config: Default configuration with SQLite storage
|
|
55
|
+
"""
|
|
56
|
+
return Config(
|
|
57
|
+
storage_config=self._default_storage_config(),
|
|
58
|
+
profile_extractor_config=ProfileExtractorConfig(
|
|
59
|
+
extraction_definition_prompt="Extract key user information including name, role, preferences, and any other relevant profile details from the conversation.",
|
|
60
|
+
),
|
|
61
|
+
user_playbook_extractor_config=PlaybookConfig(
|
|
62
|
+
extraction_definition_prompt="Extract playbook rules about agent performance, including areas where the agent was helpful, areas for improvement, and any issues encountered during the interaction.",
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def load_config(self) -> Config:
|
|
67
|
+
"""
|
|
68
|
+
Loads the current configuration from local JSON file. If the file doesn't exist,
|
|
69
|
+
creates a default configuration and saves it.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Config: Loaded configuration object
|
|
73
|
+
"""
|
|
74
|
+
if not Path(self.config_file).exists():
|
|
75
|
+
config = self.get_default_config()
|
|
76
|
+
self._save_config_to_local_dir(config=config)
|
|
77
|
+
return config
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
with Path(self.config_file).open(encoding="utf-8") as f:
|
|
81
|
+
config_content = f.read()
|
|
82
|
+
data = json.loads(str(config_content))
|
|
83
|
+
# Upgrade retired list-valued extractor fields (e.g.
|
|
84
|
+
# agent_success_configs) to their singular replacements before
|
|
85
|
+
# validation. Without this, Config would drop the unknown legacy
|
|
86
|
+
# keys and silently lose the user's customization.
|
|
87
|
+
if isinstance(data, dict):
|
|
88
|
+
data = normalize_legacy_config_shape(data)
|
|
89
|
+
# Detect legacy on-disk configs that used the removed "disk"
|
|
90
|
+
# storage backend and rewrite only the storage_config field
|
|
91
|
+
# to default SQLite. Other persisted fields (extractors,
|
|
92
|
+
# prompts, etc.) are preserved so the migration doesn't
|
|
93
|
+
# silently lose user customizations. Without this guard,
|
|
94
|
+
# legacy configs would fail Pydantic validation and the
|
|
95
|
+
# broad-except path below would discard everything.
|
|
96
|
+
storage_cfg = (
|
|
97
|
+
data.get("storage_config") if isinstance(data, dict) else None
|
|
98
|
+
)
|
|
99
|
+
if isinstance(storage_cfg, dict) and storage_cfg.get("type") == "disk":
|
|
100
|
+
logger.warning(
|
|
101
|
+
"Legacy storage_config.type='disk' detected in %s. "
|
|
102
|
+
"The disk backend was removed; rewriting storage_config "
|
|
103
|
+
"to default SQLite and preserving all other fields.",
|
|
104
|
+
self.config_file,
|
|
105
|
+
)
|
|
106
|
+
data = dict(data)
|
|
107
|
+
data["storage_config"] = self._default_storage_config().model_dump()
|
|
108
|
+
config: Config = Config(**data)
|
|
109
|
+
return config
|
|
110
|
+
except Exception:
|
|
111
|
+
logger.exception(
|
|
112
|
+
"Failed to load config from %s; falling back to default config.",
|
|
113
|
+
self.config_file,
|
|
114
|
+
)
|
|
115
|
+
# Create a default config if anything goes wrong.
|
|
116
|
+
return self.get_default_config()
|
|
117
|
+
|
|
118
|
+
def save_config(self, config: Config) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Saves the configuration to the local JSON file.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
config (Config): Configuration object to save
|
|
124
|
+
"""
|
|
125
|
+
if self.base_dir and self.config_file:
|
|
126
|
+
self._save_config_to_local_dir(config=config)
|
|
127
|
+
else:
|
|
128
|
+
print(
|
|
129
|
+
f"Cannot save config for org {self.org_id}: no local directory configured"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def get_version(self) -> tuple[str, Any] | None:
|
|
133
|
+
"""Return the on-disk mtime of the org's config file, if it exists.
|
|
134
|
+
|
|
135
|
+
The Reflexio cache uses this to detect out-of-band edits to
|
|
136
|
+
``~/.reflexio/configs/config_<org-id>.json`` (e.g. an operator
|
|
137
|
+
editing the file directly while the server is running). If the
|
|
138
|
+
file is missing — for example, before the first ``set_config``
|
|
139
|
+
write — return None so the cache leaves the entry alone rather
|
|
140
|
+
than thrashing every request.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
tuple[str, float] | None: ``("file", mtime_seconds)`` when
|
|
144
|
+
the config file exists, ``None`` otherwise (missing file or
|
|
145
|
+
stat failure).
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
return ("file", Path(self.config_file).stat().st_mtime)
|
|
149
|
+
except OSError:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _save_config_to_local_dir(self, config: Config) -> None:
|
|
153
|
+
"""
|
|
154
|
+
Saves configuration to the local JSON file atomically.
|
|
155
|
+
|
|
156
|
+
Writes to a per-write unique ``.tmp`` file first, then renames it
|
|
157
|
+
over the final path. ``Path.replace`` is atomic on POSIX (same
|
|
158
|
+
filesystem), so concurrent ``set_config`` calls across multiple
|
|
159
|
+
workers cannot observe a partially-written file, and a crash
|
|
160
|
+
mid-write leaves the previous good file intact. The tmp filename
|
|
161
|
+
embeds the pid and a nanosecond timestamp so concurrent writers
|
|
162
|
+
do not race on the same temp path.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
config (Config): Configuration object to save
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
OSError: If the write or atomic rename fails. The tmp file is
|
|
169
|
+
cleaned up on failure (best-effort), but the original
|
|
170
|
+
exception propagates so callers see the failure rather
|
|
171
|
+
than a false success.
|
|
172
|
+
"""
|
|
173
|
+
if not (self.base_dir and self.config_file):
|
|
174
|
+
raise ValueError("base_dir and config_file must be set")
|
|
175
|
+
|
|
176
|
+
final_path = Path(self.config_file)
|
|
177
|
+
final_path.parent.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
tmp_path = final_path.with_name(
|
|
179
|
+
f"{final_path.name}.{os.getpid()}.{time.time_ns()}.tmp"
|
|
180
|
+
)
|
|
181
|
+
try:
|
|
182
|
+
tmp_path.write_text(config.model_dump_json(), encoding="utf-8")
|
|
183
|
+
tmp_path.replace(final_path)
|
|
184
|
+
except OSError:
|
|
185
|
+
with contextlib.suppress(OSError):
|
|
186
|
+
tmp_path.unlink(missing_ok=True)
|
|
187
|
+
raise
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# ruff: noqa: S101
|
|
2
|
+
#!/usr/bin/env python3
|
|
3
|
+
"""
|
|
4
|
+
Simple test script to verify the new config storage classes work correctly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from reflexio.models.config_schema import (
|
|
12
|
+
Config,
|
|
13
|
+
StorageConfigSQLite,
|
|
14
|
+
)
|
|
15
|
+
from reflexio.server.services.configurator.local_file_config_storage import (
|
|
16
|
+
LocalFileConfigStorage,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_local_file_config_storage():
|
|
21
|
+
"""Test LocalFileConfigStorage functionality."""
|
|
22
|
+
print("Testing LocalFileConfigStorage...")
|
|
23
|
+
|
|
24
|
+
# Create a temporary directory for testing
|
|
25
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
26
|
+
org_id = "test_org_123"
|
|
27
|
+
|
|
28
|
+
# Create storage instance
|
|
29
|
+
storage = LocalFileConfigStorage(org_id=org_id, base_dir=temp_dir)
|
|
30
|
+
|
|
31
|
+
# Test get_default_config
|
|
32
|
+
default_config = storage.get_default_config()
|
|
33
|
+
print(
|
|
34
|
+
f" Default config storage type: {type(default_config.storage_config).__name__}"
|
|
35
|
+
)
|
|
36
|
+
assert isinstance(default_config.storage_config, StorageConfigSQLite)
|
|
37
|
+
|
|
38
|
+
# Test load_config (should create default if file doesn't exist)
|
|
39
|
+
loaded_config = storage.load_config()
|
|
40
|
+
print(
|
|
41
|
+
f" Loaded config storage type: {type(loaded_config.storage_config).__name__}"
|
|
42
|
+
)
|
|
43
|
+
assert isinstance(loaded_config.storage_config, StorageConfigSQLite)
|
|
44
|
+
|
|
45
|
+
# Test save_config
|
|
46
|
+
test_config = Config(
|
|
47
|
+
storage_config=StorageConfigSQLite(),
|
|
48
|
+
agent_context_prompt="Test context prompt",
|
|
49
|
+
)
|
|
50
|
+
storage.save_config(test_config)
|
|
51
|
+
print(" Config saved successfully")
|
|
52
|
+
|
|
53
|
+
# Test load_config again (should load the saved config)
|
|
54
|
+
reloaded_config = storage.load_config()
|
|
55
|
+
print(
|
|
56
|
+
f" Reloaded config agent context: {reloaded_config.agent_context_prompt}"
|
|
57
|
+
)
|
|
58
|
+
assert reloaded_config.agent_context_prompt == "Test context prompt"
|
|
59
|
+
|
|
60
|
+
print(" LocalFileConfigStorage tests passed!")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_configurator_integration():
|
|
64
|
+
"""Test that the configurator works with the new storage classes."""
|
|
65
|
+
print("Testing configurator integration...")
|
|
66
|
+
|
|
67
|
+
# Test with local storage
|
|
68
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
69
|
+
from reflexio.server.services.configurator.configurator import (
|
|
70
|
+
DefaultConfigurator,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
org_id = "test_org_789"
|
|
74
|
+
configurator = DefaultConfigurator(org_id=org_id, base_dir=temp_dir)
|
|
75
|
+
|
|
76
|
+
storage_config = configurator.get_current_storage_configuration()
|
|
77
|
+
print(f" Configurator storage type: {type(storage_config).__name__}")
|
|
78
|
+
assert isinstance(storage_config, StorageConfigSQLite)
|
|
79
|
+
|
|
80
|
+
# Test setting and getting config
|
|
81
|
+
configurator.set_config_by_name(
|
|
82
|
+
"agent_context_prompt", "Integration test prompt"
|
|
83
|
+
)
|
|
84
|
+
context = configurator.get_agent_context()
|
|
85
|
+
print(f" Agent context: {context}")
|
|
86
|
+
assert context == "Integration test prompt"
|
|
87
|
+
|
|
88
|
+
print(" Configurator integration tests passed!")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_load_config_upgrades_legacy_list_shape():
|
|
92
|
+
"""Legacy list-shaped extractor fields on disk are migrated to singular fields.
|
|
93
|
+
|
|
94
|
+
Configs persisted before the single-extractor refactor store
|
|
95
|
+
``*_extractor_configs`` lists and ``agent_success_configs``. Without
|
|
96
|
+
migration at the load boundary, ``Config`` would drop these unknown keys and
|
|
97
|
+
silently lose the user's customization.
|
|
98
|
+
"""
|
|
99
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
100
|
+
org_id = "legacy_org"
|
|
101
|
+
storage = LocalFileConfigStorage(org_id=org_id, base_dir=temp_dir)
|
|
102
|
+
|
|
103
|
+
legacy_payload = {
|
|
104
|
+
"storage_config": StorageConfigSQLite().model_dump(),
|
|
105
|
+
"profile_extractor_configs": [
|
|
106
|
+
{
|
|
107
|
+
"extractor_name": "legacy_profile",
|
|
108
|
+
"extraction_definition_prompt": "Extract legacy profile data.",
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"user_playbook_extractor_configs": [
|
|
112
|
+
{
|
|
113
|
+
"extractor_name": "legacy_playbook",
|
|
114
|
+
"extraction_definition_prompt": "Extract legacy playbook rules.",
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"agent_success_configs": [
|
|
118
|
+
{
|
|
119
|
+
"evaluation_name": "legacy_success",
|
|
120
|
+
"success_definition_prompt": "Evaluate legacy agent success.",
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
Path(storage.config_file).parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
Path(storage.config_file).write_text(
|
|
126
|
+
json.dumps(legacy_payload), encoding="utf-8"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
loaded = storage.load_config()
|
|
130
|
+
|
|
131
|
+
assert loaded.profile_extractor_config is not None
|
|
132
|
+
assert loaded.profile_extractor_config.extractor_name == "legacy_profile"
|
|
133
|
+
assert loaded.user_playbook_extractor_config is not None
|
|
134
|
+
assert loaded.user_playbook_extractor_config.extractor_name == "legacy_playbook"
|
|
135
|
+
assert loaded.agent_success_config is not None
|
|
136
|
+
assert loaded.agent_success_config.evaluation_name == "legacy_success"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_load_config():
|
|
140
|
+
"""Test that the configurator loads the config correctly."""
|
|
141
|
+
print("Testing load_config...")
|
|
142
|
+
|
|
143
|
+
new_config = Config(
|
|
144
|
+
storage_config=StorageConfigSQLite(
|
|
145
|
+
db_path="/tmp/test.db",
|
|
146
|
+
),
|
|
147
|
+
profile_extractor_config=None,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
json_config = new_config.model_dump_json()
|
|
151
|
+
config = Config.model_validate_json(json_config)
|
|
152
|
+
|
|
153
|
+
assert isinstance(config.storage_config, StorageConfigSQLite)
|
|
154
|
+
assert config.storage_config.db_path == "/tmp/test.db"
|
|
155
|
+
|
|
156
|
+
print(" Load config tests passed!")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
import pytest
|
|
161
|
+
|
|
162
|
+
pytest.main([__file__])
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for deduplication services.
|
|
3
|
+
|
|
4
|
+
This module contains base classes and utility functions used by both
|
|
5
|
+
ProfileDeduplicator and PlaybookConsolidator.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from abc import ABC
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
|
|
12
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
13
|
+
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
14
|
+
from reflexio.server.llm.model_defaults import ModelRole, resolve_model_name
|
|
15
|
+
from reflexio.server.site_var.site_var_manager import SiteVarManager
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Format used for "Last Modified" timestamps shown to deduplication LLMs.
|
|
20
|
+
# Includes hours and minutes so same-day contradictions (morning vs evening)
|
|
21
|
+
# can be distinguished.
|
|
22
|
+
DEDUP_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M UTC"
|
|
23
|
+
DEDUP_TIMESTAMP_FALLBACK = "unknown"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_dedup_timestamp(ts: int) -> str:
|
|
27
|
+
"""Format a Unix timestamp as a UTC date string for deduplication prompts.
|
|
28
|
+
|
|
29
|
+
Wraps ``datetime.fromtimestamp`` in a try/except so a single malformed
|
|
30
|
+
timestamp (negative, zero, or out-of-range integer) cannot abort an entire
|
|
31
|
+
deduplication batch. Returns ``DEDUP_TIMESTAMP_FALLBACK`` on failure.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
ts (int): Unix timestamp (seconds since epoch).
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: Human-readable UTC timestamp like ``"2026-04-11 14:30 UTC"``,
|
|
38
|
+
or ``"unknown"`` if the value is unparseable.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
return datetime.fromtimestamp(ts, tz=UTC).strftime(DEDUP_TIMESTAMP_FORMAT)
|
|
42
|
+
except (OverflowError, ValueError, OSError, TypeError) as exc:
|
|
43
|
+
logger.warning("Failed to format dedup timestamp %r: %s", ts, exc)
|
|
44
|
+
return DEDUP_TIMESTAMP_FALLBACK
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_item_id(item_id: str) -> tuple[str, int] | None:
|
|
48
|
+
"""
|
|
49
|
+
Parse a prompt-format item ID like 'NEW-0' or 'EXISTING-1' into its prefix and index.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
item_id (str): Item ID string in the format 'PREFIX-N' (e.g., 'NEW-0', 'EXISTING-1')
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Optional[tuple[str, int]]: A tuple of (prefix, index) where prefix is 'NEW' or 'EXISTING',
|
|
56
|
+
or None if the item ID is invalid
|
|
57
|
+
"""
|
|
58
|
+
parts = item_id.rsplit("-", 1)
|
|
59
|
+
if len(parts) != 2:
|
|
60
|
+
logger.warning("Invalid item ID format: %s", item_id)
|
|
61
|
+
return None
|
|
62
|
+
prefix, idx_str = parts
|
|
63
|
+
prefix = prefix.upper()
|
|
64
|
+
if prefix not in ("NEW", "EXISTING"):
|
|
65
|
+
logger.warning("Invalid prefix in item ID: %s", item_id)
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
return prefix, int(idx_str)
|
|
69
|
+
except ValueError:
|
|
70
|
+
logger.warning("Invalid index in item ID: %s", item_id)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ===============================
|
|
75
|
+
# Base Deduplicator ABC
|
|
76
|
+
# ===============================
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BaseDeduplicator(ABC): # noqa: B024
|
|
80
|
+
"""
|
|
81
|
+
Abstract base class for deduplicators that use LLM-based semantic matching.
|
|
82
|
+
|
|
83
|
+
Provides shared initialization (LLM client, model name).
|
|
84
|
+
Subclasses implement their own deduplicate() method with domain-specific
|
|
85
|
+
prompt building, hybrid search, and result construction.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
request_context: RequestContext,
|
|
91
|
+
llm_client: LiteLLMClient,
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
Initialize the deduplicator.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
request_context: Request context with storage and prompt manager
|
|
98
|
+
llm_client: Unified LLM client for LLM calls
|
|
99
|
+
"""
|
|
100
|
+
self.request_context = request_context
|
|
101
|
+
self.client = llm_client
|
|
102
|
+
|
|
103
|
+
# Resolve model name: site var → auto-detect
|
|
104
|
+
model_setting = SiteVarManager().get_site_var("llm_model_setting")
|
|
105
|
+
site_var = model_setting if isinstance(model_setting, dict) else {}
|
|
106
|
+
api_key_config = self.request_context.configurator.get_config().api_key_config
|
|
107
|
+
|
|
108
|
+
self.model_name = resolve_model_name(
|
|
109
|
+
ModelRole.GENERATION,
|
|
110
|
+
site_var_value=site_var.get("default_generation_model_name"),
|
|
111
|
+
api_key_config=api_key_config,
|
|
112
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Bucket per-session correction counts into a fixed 6-bin histogram.
|
|
2
|
+
|
|
3
|
+
The spec §4.5 called for a continuous 0.0-1.0 score distribution; the actual
|
|
4
|
+
schema only has `is_success` and `number_of_correction_per_session`, so we
|
|
5
|
+
distribute by correction count instead. Six bins keep the visualization
|
|
6
|
+
readable while preserving the shape of session quality.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
|
|
13
|
+
BUCKET_LABELS: tuple[str, ...] = ("0", "1", "2", "3", "4", "5+")
|
|
14
|
+
_BUCKET_COUNT = len(BUCKET_LABELS)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def bucket_corrections(
|
|
18
|
+
corrections: Iterable[int],
|
|
19
|
+
) -> tuple[int, int, int, int, int, int]:
|
|
20
|
+
"""Return per-bin counts for the corrections histogram.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
corrections (Iterable[int]): One non-negative integer per session in
|
|
24
|
+
the window. Values >= 5 fall into the final "5+" bin.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
tuple[int, ...]: Six non-negative counts in BUCKET_LABELS order.
|
|
28
|
+
"""
|
|
29
|
+
bins = [0] * _BUCKET_COUNT
|
|
30
|
+
for c in corrections:
|
|
31
|
+
idx = min(_BUCKET_COUNT - 1, max(0, c))
|
|
32
|
+
bins[idx] += 1
|
|
33
|
+
return tuple(bins) # type: ignore[return-value]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Pure stratified sampler for the F3 eval-regen pipeline.
|
|
2
|
+
|
|
3
|
+
DB-free and LLM-free. Operates on SampleCandidate tuples emitted by the
|
|
4
|
+
regen worker's candidate-discovery step; returns the sampled subset.
|
|
5
|
+
|
|
6
|
+
Stratification key: (day_bucket, F2 group). Day buckets are aligned to
|
|
7
|
+
86400-second boundaries. F2 group is derived via the existing
|
|
8
|
+
`assign_group_from_metadata` so the per-day cap is honored separately
|
|
9
|
+
for treatment / control / untagged.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import random
|
|
15
|
+
from collections import defaultdict
|
|
16
|
+
from collections.abc import Iterable
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from reflexio.server.services.evaluation_overview.group_aggregation import (
|
|
21
|
+
GroupAssignment,
|
|
22
|
+
assign_group_from_metadata,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Day bucket width in seconds. Strata are aligned to (ts // _DAY) * _DAY.
|
|
26
|
+
_DAY = 86_400
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class SampleCandidate:
|
|
31
|
+
"""One candidate for the regen pipeline's sampling step.
|
|
32
|
+
|
|
33
|
+
Carries everything the regen worker needs to dispatch a per-session
|
|
34
|
+
evaluation, plus the first-request metadata used to assign the F2
|
|
35
|
+
group for stratification.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
session_id: The session to evaluate.
|
|
39
|
+
user_id: Owner of the session's requests.
|
|
40
|
+
agent_version: Agent version to filter on.
|
|
41
|
+
source: Optional source tag (passed through to run_group_evaluation).
|
|
42
|
+
created_at: Unix epoch seconds of the session's most relevant
|
|
43
|
+
timestamp; used for day-bucket alignment.
|
|
44
|
+
first_request_metadata: The metadata dict on the session's first
|
|
45
|
+
request, read once during candidate discovery.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
session_id: str
|
|
49
|
+
user_id: str
|
|
50
|
+
agent_version: str
|
|
51
|
+
source: str | None
|
|
52
|
+
created_at: int
|
|
53
|
+
first_request_metadata: dict[str, Any]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def stratify_by_day_and_group(
|
|
57
|
+
candidates: Iterable[SampleCandidate],
|
|
58
|
+
) -> dict[tuple[int, GroupAssignment], list[SampleCandidate]]:
|
|
59
|
+
"""Bucket candidates by (day_bucket, F2 group).
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
candidates: Candidate tuples emitted by the regen candidate-discovery
|
|
63
|
+
step.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dict from (day_bucket_start, group) to the candidate list for that
|
|
67
|
+
stratum. Empty strata are absent; the dict has only populated keys.
|
|
68
|
+
"""
|
|
69
|
+
strata: dict[tuple[int, GroupAssignment], list[SampleCandidate]] = defaultdict(list)
|
|
70
|
+
for c in candidates:
|
|
71
|
+
day_bucket = (c.created_at // _DAY) * _DAY
|
|
72
|
+
group = assign_group_from_metadata(c.first_request_metadata)
|
|
73
|
+
strata[(day_bucket, group)].append(c)
|
|
74
|
+
return dict(strata)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def sample_per_stratum(
|
|
78
|
+
candidates: list[SampleCandidate],
|
|
79
|
+
n: int,
|
|
80
|
+
rng: random.Random,
|
|
81
|
+
) -> list[SampleCandidate]:
|
|
82
|
+
"""Sample up to ``n`` candidates from a single stratum.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
candidates: All candidates in one stratum.
|
|
86
|
+
n: Cap. Strata with fewer than ``n`` items are returned whole.
|
|
87
|
+
rng: Seeded random for reproducibility.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Sampled subset. Order is ``random.Random.sample``'s implementation
|
|
91
|
+
detail — callers should not depend on it.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: When ``n`` is not positive.
|
|
95
|
+
"""
|
|
96
|
+
if n <= 0:
|
|
97
|
+
raise ValueError(f"n must be positive, got {n}")
|
|
98
|
+
if len(candidates) <= n:
|
|
99
|
+
return list(candidates)
|
|
100
|
+
return rng.sample(candidates, n)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def sample_candidates(
|
|
104
|
+
candidates: Iterable[SampleCandidate],
|
|
105
|
+
n_per_stratum: int,
|
|
106
|
+
rng: random.Random,
|
|
107
|
+
) -> list[SampleCandidate]:
|
|
108
|
+
"""Stratify by (day x group), then sample per stratum, then flatten.
|
|
109
|
+
|
|
110
|
+
Predictable cost across runs: at most ``n_per_stratum * num_strata``
|
|
111
|
+
items returned, regardless of traffic volume.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
candidates: All candidates in the regen window.
|
|
115
|
+
n_per_stratum: Per-stratum cap (typically
|
|
116
|
+
``Config.eval_sample_n_per_stratum``).
|
|
117
|
+
rng: Seeded random for reproducibility.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Flattened sampled list.
|
|
121
|
+
"""
|
|
122
|
+
strata = stratify_by_day_and_group(candidates)
|
|
123
|
+
sampled: list[SampleCandidate] = []
|
|
124
|
+
for stratum_candidates in strata.values():
|
|
125
|
+
sampled.extend(sample_per_stratum(stratum_candidates, n_per_stratum, rng))
|
|
126
|
+
return sampled
|