claude-smart 0.2.42 → 0.2.44
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 +3 -3
- package/README.md +1 -1
- package/bin/claude-smart.js +2 -2
- package/package.json +9 -3
- package/plugin/.claude-plugin/plugin.json +9 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +23 -3
- package/plugin/pyproject.toml +3 -3
- package/plugin/scripts/_lib.sh +91 -0
- package/plugin/scripts/backend-service.sh +51 -4
- package/plugin/scripts/cli.sh +3 -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 +1 -0
- package/plugin/scripts/hook_entry.sh +6 -3
- package/plugin/scripts/smart-install.sh +3 -2
- package/plugin/src/README.md +57 -0
- 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 +126 -5
- package/plugin/vendor/reflexio/.env.example +62 -0
- package/plugin/vendor/reflexio/LICENSE +201 -0
- package/plugin/vendor/reflexio/README.md +338 -0
- package/plugin/vendor/reflexio/pyproject.toml +274 -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 +265 -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 +964 -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 +1112 -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 +768 -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 +333 -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 +622 -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 +2868 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -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 +562 -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 +359 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1871 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +140 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +479 -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 +338 -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 +288 -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 +187 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +716 -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_consolidation/v2.3.1.prompt.md +69 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -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 +283 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -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 +137 -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/README.md +58 -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 +336 -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 +1668 -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/embedding_text.py +62 -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 +358 -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 +535 -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 +779 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +94 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +702 -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 +1045 -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 +772 -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 +737 -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 +803 -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 +80 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +756 -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 +1298 -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 +384 -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 +572 -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,213 @@
|
|
|
1
|
+
"""Local in-process embedder using Chroma's ONNX all-MiniLM-L6-v2.
|
|
2
|
+
|
|
3
|
+
Lets reflexio run without any external embedding API key. Activation is
|
|
4
|
+
opt-in via ``CLAUDE_SMART_USE_LOCAL_EMBEDDING=1`` and requires the
|
|
5
|
+
``chromadb`` pip package to be installed (we re-use its packaged ONNX
|
|
6
|
+
model + tokenizer rather than re-bundling them).
|
|
7
|
+
|
|
8
|
+
The model natively produces 384-dim vectors; reflexio's storage schema
|
|
9
|
+
expects 512 dims (``EMBEDDING_DIMENSIONS`` in the vec0 virtual tables).
|
|
10
|
+
We zero-pad each vector to 512 inside this module so the rest of
|
|
11
|
+
reflexio is unchanged. Cosine similarity is preserved on the 384-dim
|
|
12
|
+
subspace — safe as long as *all* embeddings in a given DB come from
|
|
13
|
+
this provider (mixing providers has always required a DB wipe).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib.util
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import threading
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
_ENV_ENABLE = "CLAUDE_SMART_USE_LOCAL_EMBEDDING"
|
|
27
|
+
_MODEL_KEY = "local/minilm-l6-v2"
|
|
28
|
+
|
|
29
|
+
# Reflexio's storage schema (vec0 virtual tables) expects this dimension.
|
|
30
|
+
# MiniLM-L6-v2 natively produces 384; we pad with zeros to _TARGET_DIM.
|
|
31
|
+
_NATIVE_DIM = 384
|
|
32
|
+
_TARGET_DIM = 512
|
|
33
|
+
|
|
34
|
+
# Conservative character budget to stay under MiniLM's 256-token hard cap.
|
|
35
|
+
# ~4 chars/token in English prose; leave headroom so we never raise the
|
|
36
|
+
# ValueError that ONNXMiniLM_L6_V2 throws on over-length input.
|
|
37
|
+
_MAX_CHARS = 800
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LocalEmbedderError(RuntimeError):
|
|
41
|
+
"""Raised when the local embedder is called without chromadb installed."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LocalEmbedder:
|
|
45
|
+
"""Lazily-loaded singleton wrapping Chroma's ONNXMiniLM_L6_V2."""
|
|
46
|
+
|
|
47
|
+
_instance: LocalEmbedder | None = None
|
|
48
|
+
_lock = threading.Lock()
|
|
49
|
+
|
|
50
|
+
def __init__(self) -> None:
|
|
51
|
+
self._ef: Any | None = None
|
|
52
|
+
self._ef_lock = threading.Lock()
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get(cls) -> LocalEmbedder:
|
|
56
|
+
"""Return the process-wide singleton, constructing it on first use."""
|
|
57
|
+
if cls._instance is None:
|
|
58
|
+
with cls._lock:
|
|
59
|
+
if cls._instance is None:
|
|
60
|
+
cls._instance = cls()
|
|
61
|
+
return cls._instance
|
|
62
|
+
|
|
63
|
+
def _load(self) -> Any:
|
|
64
|
+
"""Lazy-import and instantiate the ONNX embedding function."""
|
|
65
|
+
if self._ef is not None:
|
|
66
|
+
return self._ef
|
|
67
|
+
with self._ef_lock:
|
|
68
|
+
if self._ef is not None:
|
|
69
|
+
return self._ef
|
|
70
|
+
try:
|
|
71
|
+
from chromadb.utils.embedding_functions import ( # type: ignore[import-not-found]
|
|
72
|
+
ONNXMiniLM_L6_V2,
|
|
73
|
+
)
|
|
74
|
+
except ImportError as exc:
|
|
75
|
+
raise LocalEmbedderError(
|
|
76
|
+
f"{_ENV_ENABLE}=1 but `chromadb` is not installed. "
|
|
77
|
+
"Install with `uv add chromadb` or `pip install chromadb`."
|
|
78
|
+
) from exc
|
|
79
|
+
self._ef = ONNXMiniLM_L6_V2()
|
|
80
|
+
_LOGGER.info(
|
|
81
|
+
"Initialized local ONNX embedder (model=%s, cache=%s)",
|
|
82
|
+
ONNXMiniLM_L6_V2.MODEL_NAME,
|
|
83
|
+
ONNXMiniLM_L6_V2.DOWNLOAD_PATH,
|
|
84
|
+
)
|
|
85
|
+
return self._ef
|
|
86
|
+
|
|
87
|
+
def embed(self, texts: list[str]) -> list[list[float]]:
|
|
88
|
+
"""Embed a batch of documents, returning 512-dim padded vectors.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
texts: Documents to embed. Each is truncated to ``_MAX_CHARS``
|
|
92
|
+
characters to stay under the 256-token cap of MiniLM-L6-v2.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
list[list[float]]: One vector per input, each exactly
|
|
96
|
+
``_TARGET_DIM`` (512) floats with the last 128 positions
|
|
97
|
+
zero-padded.
|
|
98
|
+
"""
|
|
99
|
+
ef = self._load()
|
|
100
|
+
safe_inputs = [(text or "")[:_MAX_CHARS] for text in texts]
|
|
101
|
+
raw = ef(safe_inputs)
|
|
102
|
+
return [_pad(vec) for vec in raw]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _pad(vec: Any) -> list[float]:
|
|
106
|
+
"""Zero-pad a 384-dim vector to ``_TARGET_DIM`` as a plain list[float]."""
|
|
107
|
+
as_list = list(vec) if not isinstance(vec, list) else vec
|
|
108
|
+
floats = [float(x) for x in as_list]
|
|
109
|
+
if len(floats) == _TARGET_DIM:
|
|
110
|
+
return floats
|
|
111
|
+
if len(floats) > _TARGET_DIM:
|
|
112
|
+
return floats[:_TARGET_DIM]
|
|
113
|
+
return floats + [0.0] * (_TARGET_DIM - len(floats))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_REGISTERED = False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def is_chromadb_importable() -> bool:
|
|
120
|
+
"""Return True when the ``chromadb`` package is importable.
|
|
121
|
+
|
|
122
|
+
Independent of :data:`_ENV_ENABLE` (``CLAUDE_SMART_USE_LOCAL_EMBEDDING``).
|
|
123
|
+
Used by callers that want to know whether the local fallback is
|
|
124
|
+
*possible* regardless of whether claude-smart has explicitly opted in.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
bool: True if ``importlib.util.find_spec("chromadb")`` finds the
|
|
128
|
+
package, False otherwise.
|
|
129
|
+
"""
|
|
130
|
+
return importlib.util.find_spec("chromadb") is not None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def register_if_chromadb_available() -> bool:
|
|
134
|
+
"""Make the local embedder available whenever ``chromadb`` imports.
|
|
135
|
+
|
|
136
|
+
This is the env-var-independent companion to
|
|
137
|
+
:func:`register_if_enabled`. Called once from ``litellm_client`` at
|
|
138
|
+
module import so the local-embedder dispatch is wired up regardless
|
|
139
|
+
of whether claude-smart's opt-in env var is set. Idempotent — safe
|
|
140
|
+
to call more than once per process.
|
|
141
|
+
|
|
142
|
+
The actual routing is done by a prefix check in
|
|
143
|
+
``LiteLLMClient.get_embedding(s)``; this function's job is to
|
|
144
|
+
eagerly probe for ``chromadb`` and log a clear message.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True if the embedder is usable after this call, False
|
|
148
|
+
when ``chromadb`` is not importable.
|
|
149
|
+
"""
|
|
150
|
+
global _REGISTERED
|
|
151
|
+
if _REGISTERED:
|
|
152
|
+
return True
|
|
153
|
+
if not is_chromadb_importable():
|
|
154
|
+
_LOGGER.debug("Local embedder not registered: `chromadb` is not installed.")
|
|
155
|
+
return False
|
|
156
|
+
_REGISTERED = True
|
|
157
|
+
_LOGGER.debug("Registered local MiniLM embedding handler (model=%s)", _MODEL_KEY)
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def register_if_enabled() -> bool:
|
|
162
|
+
"""Backwards-compatible alias for :func:`register_if_chromadb_available`.
|
|
163
|
+
|
|
164
|
+
Historically gated on ``CLAUDE_SMART_USE_LOCAL_EMBEDDING=1``; now
|
|
165
|
+
delegates to the chromadb-only check so the local embedder is also
|
|
166
|
+
available as a silent fallback when no cloud embedder is configured.
|
|
167
|
+
The claude-smart opt-in env var continues to drive
|
|
168
|
+
:func:`is_local_embedder_available` (provider-priority ordering),
|
|
169
|
+
which is the public contract the env var actually owns.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
bool: True if the embedder is usable after this call, False
|
|
173
|
+
otherwise.
|
|
174
|
+
"""
|
|
175
|
+
return register_if_chromadb_available()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_enabled() -> bool:
|
|
179
|
+
"""Return True when a previous registration call has succeeded.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
bool: True if the provider is currently registered and usable in
|
|
183
|
+
this process, False otherwise.
|
|
184
|
+
"""
|
|
185
|
+
return _REGISTERED
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def is_local_embedder_available() -> bool:
|
|
189
|
+
"""Return True iff both the env flag is set and ``chromadb`` imports.
|
|
190
|
+
|
|
191
|
+
Unlike :func:`is_enabled`, this does not require
|
|
192
|
+
:func:`register_if_enabled` to have run. It is the predicate
|
|
193
|
+
``model_defaults.detect_available_providers`` uses to decide whether
|
|
194
|
+
to surface ``"local"`` as an option.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
bool: True when ``CLAUDE_SMART_USE_LOCAL_EMBEDDING=1``
|
|
198
|
+
AND ``chromadb`` is importable.
|
|
199
|
+
"""
|
|
200
|
+
if os.environ.get(_ENV_ENABLE) != "1":
|
|
201
|
+
return False
|
|
202
|
+
return is_chromadb_importable()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
__all__ = [
|
|
206
|
+
"LocalEmbedder",
|
|
207
|
+
"LocalEmbedderError",
|
|
208
|
+
"is_chromadb_importable",
|
|
209
|
+
"is_enabled",
|
|
210
|
+
"is_local_embedder_available",
|
|
211
|
+
"register_if_chromadb_available",
|
|
212
|
+
"register_if_enabled",
|
|
213
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Local in-process embedder using ``nomic-ai/nomic-embed-text-v1.5``.
|
|
2
|
+
|
|
3
|
+
A higher-quality alternative to the chromadb-bundled MiniLM-L6-v2: 137M
|
|
4
|
+
parameters, 768-dim native, supports Matryoshka representation (64–768
|
|
5
|
+
dimensions without retraining), 8192-token context, Apache-2.0 licensed.
|
|
6
|
+
Performs comparably to OpenAI's ``text-embedding-3-small`` on MTEB
|
|
7
|
+
retrieval at a fraction of the latency cost when run locally on CPU or
|
|
8
|
+
Apple Silicon.
|
|
9
|
+
|
|
10
|
+
Activation
|
|
11
|
+
----------
|
|
12
|
+
|
|
13
|
+
- Set ``CLAUDE_SMART_USE_LOCAL_EMBEDDING=1`` in the process environment.
|
|
14
|
+
- Pass model name ``local/nomic-embed-v1.5`` (or ``local/nomic-embed-text-v1.5``)
|
|
15
|
+
to :func:`LiteLLMClient.get_embedding`/``get_embeddings``.
|
|
16
|
+
- Requires the ``sentence-transformers`` pip dependency.
|
|
17
|
+
|
|
18
|
+
Storage compatibility
|
|
19
|
+
---------------------
|
|
20
|
+
|
|
21
|
+
Reflexio's vec0 tables expect 512-dim vectors (``EMBEDDING_DIMENSIONS``).
|
|
22
|
+
Nomic's native 768 dim is reduced via Matryoshka — slice the first 512
|
|
23
|
+
floats, then re-normalize to unit length so cosine similarity remains
|
|
24
|
+
comparable. Quality on retrieval tasks at 512 dim is ~95% of the full
|
|
25
|
+
768 (per Nomic's own evaluation).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import importlib.util
|
|
31
|
+
import logging
|
|
32
|
+
import math
|
|
33
|
+
import os
|
|
34
|
+
import threading
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from reflexio.server.llm.llm_utils import positive_int_env
|
|
38
|
+
|
|
39
|
+
_LOGGER = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
_ENV_ENABLE = "CLAUDE_SMART_USE_LOCAL_EMBEDDING"
|
|
42
|
+
_ENV_PROVIDER = "REFLEXIO_EMBEDDING_PROVIDER"
|
|
43
|
+
_ENV_DAEMON = "REFLEXIO_EMBEDDING_DAEMON"
|
|
44
|
+
_MODEL_KEYS = {"local/nomic-embed-v1.5", "local/nomic-embed-text-v1.5"}
|
|
45
|
+
_HF_MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5"
|
|
46
|
+
|
|
47
|
+
# Reflexio's vec0 schema dim. Nomic v1.5 outputs 768 natively; we slice
|
|
48
|
+
# to 512 (Matryoshka) and re-normalize.
|
|
49
|
+
_TARGET_DIM = 512
|
|
50
|
+
# Nomic v1.5 was trained with task-prefixed inputs; "search_document"
|
|
51
|
+
# vs "search_query" prefixes give better asymmetric retrieval. Reflexio's
|
|
52
|
+
# storage layer already passes a "search_document: " / "search_query: "
|
|
53
|
+
# prefix when calling _get_embedding(purpose=...), so we don't add another
|
|
54
|
+
# prefix here — the input arrives correctly tagged.
|
|
55
|
+
# The model has a 8192 token context window; we still cap chars
|
|
56
|
+
# defensively to avoid pathological multi-MB inputs.
|
|
57
|
+
_MAX_CHARS = 32_000
|
|
58
|
+
|
|
59
|
+
# Encode in small mini-batches so a single large request can't spike memory:
|
|
60
|
+
# peak activation memory scales with batch_size, not with the total number of
|
|
61
|
+
# texts in the request. A small batch is what makes the daemon's bounded
|
|
62
|
+
# concurrency (see ``embedding_service``) safe to raise above 1.
|
|
63
|
+
_DEFAULT_ENCODE_BATCH_SIZE = 4
|
|
64
|
+
_ENV_BATCH_SIZE = "REFLEXIO_EMBED_BATCH_SIZE"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _encode_batch_size() -> int:
|
|
68
|
+
"""Resolve the encode mini-batch size from env, defaulting to 4."""
|
|
69
|
+
return positive_int_env(_ENV_BATCH_SIZE, _DEFAULT_ENCODE_BATCH_SIZE, _LOGGER)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NomicEmbedderError(RuntimeError):
|
|
73
|
+
"""Raised when the Nomic embedder is requested but its deps are missing."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _huggingface_cache_path() -> str:
|
|
77
|
+
return os.environ.get("HF_HOME") or "~/.cache/huggingface"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class NomicEmbedder:
|
|
81
|
+
"""Lazily-loaded singleton wrapping a sentence-transformers model.
|
|
82
|
+
|
|
83
|
+
Loading the underlying ``nomic-embed-text-v1.5`` model takes ~5–10 s on
|
|
84
|
+
first call (downloads ~550 MB on cold start, then cached under
|
|
85
|
+
``~/.cache/huggingface/``). After that, embedding latency on CPU is
|
|
86
|
+
~30–60 ms per single text and ~200 ms per batch of 32 (Apple M-series).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
_instance: NomicEmbedder | None = None
|
|
90
|
+
_lock = threading.Lock()
|
|
91
|
+
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
self._model: Any | None = None
|
|
94
|
+
self._model_lock = threading.Lock()
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def get(cls) -> NomicEmbedder:
|
|
98
|
+
"""Return the process-wide singleton, constructing it on first use."""
|
|
99
|
+
if cls._instance is None:
|
|
100
|
+
with cls._lock:
|
|
101
|
+
if cls._instance is None:
|
|
102
|
+
cls._instance = cls()
|
|
103
|
+
return cls._instance
|
|
104
|
+
|
|
105
|
+
def _load(self) -> Any:
|
|
106
|
+
"""Lazy-import sentence-transformers and load the Nomic model."""
|
|
107
|
+
if self._model is not None:
|
|
108
|
+
return self._model
|
|
109
|
+
with self._model_lock:
|
|
110
|
+
if self._model is not None:
|
|
111
|
+
return self._model
|
|
112
|
+
try:
|
|
113
|
+
from sentence_transformers import (
|
|
114
|
+
SentenceTransformer, # type: ignore[import-not-found]
|
|
115
|
+
)
|
|
116
|
+
except ImportError as exc:
|
|
117
|
+
raise NomicEmbedderError(
|
|
118
|
+
"sentence-transformers is required for the Nomic local "
|
|
119
|
+
"embedder. Install with `uv add sentence-transformers`."
|
|
120
|
+
) from exc
|
|
121
|
+
_LOGGER.info(
|
|
122
|
+
"Loading Nomic embedding model %s — first call may download "
|
|
123
|
+
"~550 MB to %s",
|
|
124
|
+
_HF_MODEL_NAME,
|
|
125
|
+
_huggingface_cache_path(),
|
|
126
|
+
)
|
|
127
|
+
# Force CPU device — MPS init has been observed to hang on some
|
|
128
|
+
# Apple Silicon + macOS combos for several minutes during model
|
|
129
|
+
# load. CPU is fast enough for our use case (137M params) and
|
|
130
|
+
# behaves predictably. Set NOMIC_EMBED_DEVICE=mps|cuda|cpu to
|
|
131
|
+
# override.
|
|
132
|
+
device = os.environ.get("NOMIC_EMBED_DEVICE", "cpu")
|
|
133
|
+
self._model = SentenceTransformer(
|
|
134
|
+
_HF_MODEL_NAME,
|
|
135
|
+
trust_remote_code=True, # Nomic v1.5 ships custom code
|
|
136
|
+
device=device,
|
|
137
|
+
)
|
|
138
|
+
_LOGGER.info(
|
|
139
|
+
"Nomic embedder ready (model=%s, target_dim=%d, native_dim=%d)",
|
|
140
|
+
_HF_MODEL_NAME,
|
|
141
|
+
_TARGET_DIM,
|
|
142
|
+
_embedding_dimension(self._model),
|
|
143
|
+
)
|
|
144
|
+
return self._model
|
|
145
|
+
|
|
146
|
+
def embed(self, texts: list[str]) -> list[list[float]]:
|
|
147
|
+
"""Embed a batch of texts, returning ``_TARGET_DIM``-sized unit vectors.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
texts: Inputs to encode. Each is char-truncated to ``_MAX_CHARS``
|
|
151
|
+
as a defensive cap; Nomic itself supports 8192 tokens.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
list[list[float]]: One vector per input, each exactly
|
|
155
|
+
``_TARGET_DIM`` (512) floats and L2-normalised so cosine
|
|
156
|
+
similarity equals dot product.
|
|
157
|
+
"""
|
|
158
|
+
model = self._load()
|
|
159
|
+
safe = [(t or "")[:_MAX_CHARS] for t in texts]
|
|
160
|
+
# show_progress_bar=False so server logs stay clean during ingest
|
|
161
|
+
# batches. convert_to_numpy=True returns a numpy ndarray; we slice
|
|
162
|
+
# and renormalise per-row before converting to plain Python lists.
|
|
163
|
+
raw = model.encode(
|
|
164
|
+
safe,
|
|
165
|
+
batch_size=_encode_batch_size(),
|
|
166
|
+
show_progress_bar=False,
|
|
167
|
+
convert_to_numpy=True,
|
|
168
|
+
)
|
|
169
|
+
return [_truncate_and_renormalise(vec.tolist()) for vec in raw]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _truncate_and_renormalise(vec: list[float]) -> list[float]:
|
|
173
|
+
"""Slice to ``_TARGET_DIM`` and L2-renormalise for valid Matryoshka use.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
vec (list[float]): Native-dim Nomic embedding (typically 768 floats,
|
|
177
|
+
already L2-unit on the full 768).
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
list[float]: Exactly ``_TARGET_DIM`` floats, L2-normalised in the
|
|
181
|
+
truncated subspace so cosine similarity remains a valid metric.
|
|
182
|
+
Zero-padded if the input is shorter than ``_TARGET_DIM``.
|
|
183
|
+
"""
|
|
184
|
+
if len(vec) >= _TARGET_DIM:
|
|
185
|
+
sliced = vec[:_TARGET_DIM]
|
|
186
|
+
else:
|
|
187
|
+
sliced = vec + [0.0] * (_TARGET_DIM - len(vec))
|
|
188
|
+
norm = math.sqrt(sum(x * x for x in sliced))
|
|
189
|
+
if norm <= 0:
|
|
190
|
+
return sliced
|
|
191
|
+
return [x / norm for x in sliced]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _embedding_dimension(model: Any) -> int:
|
|
195
|
+
get_embedding_dimension = getattr(model, "get_embedding_dimension", None)
|
|
196
|
+
if callable(get_embedding_dimension):
|
|
197
|
+
dimension = get_embedding_dimension()
|
|
198
|
+
else:
|
|
199
|
+
dimension = model.get_sentence_embedding_dimension()
|
|
200
|
+
return int(dimension) # type: ignore[arg-type]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
_REGISTERED = False
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def register_if_enabled() -> bool:
|
|
207
|
+
"""Make the Nomic embedder available when env + deps allow it.
|
|
208
|
+
|
|
209
|
+
Idempotent. Returns ``True`` when the embedder is usable after this
|
|
210
|
+
call. Routing happens via prefix-match on the model name in
|
|
211
|
+
``LiteLLMClient.get_embedding(s)``.
|
|
212
|
+
|
|
213
|
+
Eagerly pre-warms the model in a daemon thread so the first request
|
|
214
|
+
doesn't pay the ~30 s cold-start cost. The thread is fire-and-forget;
|
|
215
|
+
callers either land mid-load (and block briefly) or after-load (and
|
|
216
|
+
proceed immediately).
|
|
217
|
+
"""
|
|
218
|
+
global _REGISTERED
|
|
219
|
+
if _REGISTERED:
|
|
220
|
+
return True
|
|
221
|
+
if os.environ.get(_ENV_ENABLE) != "1":
|
|
222
|
+
return False
|
|
223
|
+
provider = os.environ.get(_ENV_PROVIDER, "").strip().lower()
|
|
224
|
+
if provider in {"local_service", "internal_service", "off"}:
|
|
225
|
+
_LOGGER.info(
|
|
226
|
+
"Nomic in-process prewarm skipped because %s=%s",
|
|
227
|
+
_ENV_PROVIDER,
|
|
228
|
+
provider,
|
|
229
|
+
)
|
|
230
|
+
return False
|
|
231
|
+
if not provider and os.environ.get(_ENV_DAEMON) != "1":
|
|
232
|
+
_LOGGER.info(
|
|
233
|
+
"Nomic in-process prewarm skipped; %s=1 now defaults to the "
|
|
234
|
+
"shared embedding service.",
|
|
235
|
+
_ENV_ENABLE,
|
|
236
|
+
)
|
|
237
|
+
return False
|
|
238
|
+
if importlib.util.find_spec("sentence_transformers") is None:
|
|
239
|
+
_LOGGER.warning(
|
|
240
|
+
"%s=1 set but `sentence-transformers` not installed; the Nomic "
|
|
241
|
+
"local embedder will not be available.",
|
|
242
|
+
_ENV_ENABLE,
|
|
243
|
+
)
|
|
244
|
+
return False
|
|
245
|
+
_REGISTERED = True
|
|
246
|
+
_LOGGER.info(
|
|
247
|
+
"Nomic local embedding provider enabled (models=%s)", sorted(_MODEL_KEYS)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _prewarm() -> None:
|
|
251
|
+
"""Background load + dummy inference so the first real request is fast."""
|
|
252
|
+
try:
|
|
253
|
+
embedder = NomicEmbedder.get()
|
|
254
|
+
embedder.embed(["warmup"])
|
|
255
|
+
_LOGGER.info("Nomic embedder pre-warmed")
|
|
256
|
+
except Exception: # noqa: BLE001
|
|
257
|
+
_LOGGER.exception(
|
|
258
|
+
"Nomic embedder pre-warm failed; first call will pay the cost"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
threading.Thread(target=_prewarm, daemon=True, name="nomic-prewarm").start()
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def is_enabled() -> bool:
|
|
266
|
+
"""Return True after a successful :func:`register_if_enabled`."""
|
|
267
|
+
return _REGISTERED
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def is_nomic_model(model: str) -> bool:
|
|
271
|
+
"""Predicate used by ``LiteLLMClient`` to route by model name.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
model (str): The embedding model name passed by the caller.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
bool: True when the model resolves to the Nomic provider.
|
|
278
|
+
"""
|
|
279
|
+
return model in _MODEL_KEYS
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
__all__ = [
|
|
283
|
+
"NomicEmbedder",
|
|
284
|
+
"NomicEmbedderError",
|
|
285
|
+
"is_enabled",
|
|
286
|
+
"is_nomic_model",
|
|
287
|
+
"register_if_enabled",
|
|
288
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Reranking helpers — local cross-encoder + LLM relevance judge."""
|
|
2
|
+
|
|
3
|
+
from reflexio.server.llm.rerank.cross_encoder_reranker import prewarm, score_pairs
|
|
4
|
+
from reflexio.server.llm.rerank.llm_reranker import score_pairs_llm
|
|
5
|
+
|
|
6
|
+
__all__ = ["prewarm", "score_pairs", "score_pairs_llm"]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Local cross-encoder reranker for ``(query, document)`` pairs.
|
|
2
|
+
|
|
3
|
+
Wraps ``cross-encoder/ms-marco-MiniLM-L-6-v2`` (~25M params) from
|
|
4
|
+
``sentence-transformers``. The model is lazy-loaded on first call and
|
|
5
|
+
held as a process-wide singleton — load takes ~3 s but only happens
|
|
6
|
+
once per server start. Scoring K=30 pairs takes ~50 ms on CPU.
|
|
7
|
+
|
|
8
|
+
Usage
|
|
9
|
+
-----
|
|
10
|
+
|
|
11
|
+
>>> from reflexio.server.llm.rerank import score_pairs
|
|
12
|
+
>>> scores = score_pairs("italian food", ["pasta lover", "weather report"])
|
|
13
|
+
>>> scores[0] > scores[1]
|
|
14
|
+
True
|
|
15
|
+
|
|
16
|
+
The helper is intentionally side-effect free at import time: building
|
|
17
|
+
the singleton happens only when ``score_pairs`` is called, so importing
|
|
18
|
+
this module never triggers a model download.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import threading
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
_LOGGER = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# HuggingFace identifier for the cross-encoder. Chosen for the
|
|
31
|
+
# size/quality trade-off: 22M parameters, ~50 ms for K=30 on CPU,
|
|
32
|
+
# well-known MS-MARCO benchmark performance.
|
|
33
|
+
_MODEL_NAME = "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
|
34
|
+
|
|
35
|
+
# Device override for the cross-encoder. Defaults to CPU: without an
|
|
36
|
+
# explicit device sentence-transformers auto-selects MPS on Apple
|
|
37
|
+
# Silicon, where torch's caching allocator accumulates gigabytes of
|
|
38
|
+
# GPU buffers it never returns to the OS — and the 22M-param model
|
|
39
|
+
# gains nothing from GPU at K=30 pairs. Mirrors NOMIC_EMBED_DEVICE.
|
|
40
|
+
_DEVICE_ENV_VAR = "REFLEXIO_RERANK_DEVICE"
|
|
41
|
+
|
|
42
|
+
# Singleton state — never accessed directly outside ``_get_model``.
|
|
43
|
+
_MODEL: Any | None = None
|
|
44
|
+
_MODEL_LOCK = threading.Lock()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CrossEncoderUnavailableError(RuntimeError):
|
|
48
|
+
"""Raised when the cross-encoder model cannot be loaded.
|
|
49
|
+
|
|
50
|
+
The most common cause is ``sentence-transformers`` being absent from
|
|
51
|
+
the runtime environment. Callers should treat this as a soft failure
|
|
52
|
+
(log + skip rerank) rather than a 500.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _import_cross_encoder() -> Any:
|
|
57
|
+
"""Robustly import ``sentence_transformers.CrossEncoder``.
|
|
58
|
+
|
|
59
|
+
The ``sentence_transformers`` package is loaded both by the Nomic
|
|
60
|
+
local-embedding pre-warm thread (kicked off by ``LiteLLMClient.__init__``
|
|
61
|
+
when ``CLAUDE_SMART_USE_LOCAL_EMBEDDING=1``) and by this reranker. When
|
|
62
|
+
those concurrent imports race, one thread can see a half-loaded
|
|
63
|
+
``sentence_transformers`` module in ``sys.modules`` whose ``CrossEncoder``
|
|
64
|
+
attribute was never bound — Python's import machinery hands back the
|
|
65
|
+
partial module without re-running ``__init__``. This helper detects that
|
|
66
|
+
case, drops the stale entry, and re-imports cleanly.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Any: The ``CrossEncoder`` class.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
CrossEncoderUnavailableError: When the package genuinely isn't
|
|
73
|
+
installed, or every retry yields a partial module.
|
|
74
|
+
"""
|
|
75
|
+
import sys
|
|
76
|
+
|
|
77
|
+
for _attempt in range(2):
|
|
78
|
+
try:
|
|
79
|
+
from sentence_transformers import CrossEncoder
|
|
80
|
+
except ImportError:
|
|
81
|
+
# Partial import — drop the stale entry and try once more.
|
|
82
|
+
sys.modules.pop("sentence_transformers", None)
|
|
83
|
+
continue
|
|
84
|
+
return CrossEncoder
|
|
85
|
+
try:
|
|
86
|
+
from sentence_transformers import (
|
|
87
|
+
CrossEncoder, # noqa: F401 — final attempt for the error path
|
|
88
|
+
)
|
|
89
|
+
except ImportError as e:
|
|
90
|
+
raise CrossEncoderUnavailableError(
|
|
91
|
+
"sentence-transformers is not installed; cannot use the "
|
|
92
|
+
"cross-encoder reranker"
|
|
93
|
+
) from e
|
|
94
|
+
return CrossEncoder
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_model() -> Any:
|
|
98
|
+
"""Return the lazy-loaded cross-encoder singleton.
|
|
99
|
+
|
|
100
|
+
The first caller pays the load cost (~3 s, weights cached under
|
|
101
|
+
``~/.cache/huggingface/`` after first download). Subsequent callers
|
|
102
|
+
get the warm instance immediately.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Any: A ``sentence_transformers.CrossEncoder`` instance.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
CrossEncoderUnavailableError: If ``sentence-transformers`` is not
|
|
109
|
+
importable, or if the underlying model fails to load.
|
|
110
|
+
"""
|
|
111
|
+
global _MODEL # noqa: PLW0603 — singleton-pattern intentional
|
|
112
|
+
if _MODEL is not None:
|
|
113
|
+
return _MODEL
|
|
114
|
+
with _MODEL_LOCK:
|
|
115
|
+
if _MODEL is not None:
|
|
116
|
+
return _MODEL
|
|
117
|
+
cross_encoder_cls = _import_cross_encoder()
|
|
118
|
+
device = os.environ.get(_DEVICE_ENV_VAR, "cpu")
|
|
119
|
+
try:
|
|
120
|
+
_LOGGER.info("Loading reranker model %s (device=%s)", _MODEL_NAME, device)
|
|
121
|
+
_MODEL = cross_encoder_cls(_MODEL_NAME, device=device)
|
|
122
|
+
except Exception as e: # noqa: BLE001 — surface as a typed failure
|
|
123
|
+
raise CrossEncoderUnavailableError(
|
|
124
|
+
f"Failed to load cross-encoder model {_MODEL_NAME!r}: {e}"
|
|
125
|
+
) from e
|
|
126
|
+
_LOGGER.info("Reranker model ready (model=%s)", _MODEL_NAME)
|
|
127
|
+
return _MODEL
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def score_pairs(query: str, docs: list[str]) -> list[float]:
|
|
131
|
+
"""Score ``(query, doc)`` pairs with the cross-encoder.
|
|
132
|
+
|
|
133
|
+
Higher score means more relevant. Scores are not bounded to a fixed
|
|
134
|
+
range — they are raw model logits — so callers should treat them as
|
|
135
|
+
opaque relative-ranking signal, not as probabilities.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
query (str): The reranking query.
|
|
139
|
+
docs (list[str]): Documents to score against ``query``.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
list[float]: One score per document, in the same order as
|
|
143
|
+
``docs``. Empty list when ``docs`` is empty.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
CrossEncoderUnavailableError: If the cross-encoder cannot be
|
|
147
|
+
loaded (re-raised from :func:`_get_model`).
|
|
148
|
+
"""
|
|
149
|
+
if not docs:
|
|
150
|
+
return []
|
|
151
|
+
model = _get_model()
|
|
152
|
+
pairs = [(query, doc) for doc in docs]
|
|
153
|
+
raw_scores = model.predict(pairs, show_progress_bar=False)
|
|
154
|
+
# ``predict`` returns a numpy array; convert to plain Python floats so
|
|
155
|
+
# the caller can serialise the result without numpy as a dependency.
|
|
156
|
+
return [float(s) for s in raw_scores]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def prewarm() -> bool:
|
|
160
|
+
"""Force the cross-encoder model to load at startup.
|
|
161
|
+
|
|
162
|
+
Call once during app startup so the ~3s model load never lands on a user
|
|
163
|
+
query (and never serializes a concurrent burst behind the model lock).
|
|
164
|
+
Never raises — startup must not crash if the cross-encoder is unavailable
|
|
165
|
+
or fails to score the smoke input.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
True if the model loaded and scored a smoke input; False otherwise
|
|
169
|
+
(callers should treat the floor as degraded, not crash).
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
score_pairs("warmup", ["warmup"])
|
|
173
|
+
except CrossEncoderUnavailableError:
|
|
174
|
+
_LOGGER.warning(
|
|
175
|
+
"Cross-encoder unavailable at startup; relevance floor will degrade "
|
|
176
|
+
"to unfiltered results until the model is available."
|
|
177
|
+
)
|
|
178
|
+
return False
|
|
179
|
+
except Exception: # noqa: BLE001 — pre-warm must never crash startup
|
|
180
|
+
_LOGGER.warning(
|
|
181
|
+
"Cross-encoder pre-warm failed unexpectedly; relevance floor will "
|
|
182
|
+
"degrade to unfiltered results until the model is available.",
|
|
183
|
+
exc_info=True,
|
|
184
|
+
)
|
|
185
|
+
return False
|
|
186
|
+
_LOGGER.info("Cross-encoder pre-warmed.")
|
|
187
|
+
return True
|