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.
Files changed (402) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +1 -1
  3. package/bin/claude-smart.js +2 -2
  4. package/package.json +9 -3
  5. package/plugin/.claude-plugin/plugin.json +9 -3
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +23 -3
  8. package/plugin/pyproject.toml +3 -3
  9. package/plugin/scripts/_lib.sh +91 -0
  10. package/plugin/scripts/backend-service.sh +51 -4
  11. package/plugin/scripts/cli.sh +3 -1
  12. package/plugin/scripts/codex-hook.js +72 -4
  13. package/plugin/scripts/dashboard-build.sh +1 -0
  14. package/plugin/scripts/dashboard-service.sh +1 -0
  15. package/plugin/scripts/ensure-plugin-root.sh +1 -0
  16. package/plugin/scripts/hook_entry.sh +6 -3
  17. package/plugin/scripts/smart-install.sh +3 -2
  18. package/plugin/src/README.md +57 -0
  19. package/plugin/src/claude_smart/context_format.py +11 -12
  20. package/plugin/src/claude_smart/cs_cite.py +26 -12
  21. package/plugin/src/claude_smart/ids.py +13 -5
  22. package/plugin/uv.lock +126 -5
  23. package/plugin/vendor/reflexio/.env.example +62 -0
  24. package/plugin/vendor/reflexio/LICENSE +201 -0
  25. package/plugin/vendor/reflexio/README.md +338 -0
  26. package/plugin/vendor/reflexio/pyproject.toml +274 -0
  27. package/plugin/vendor/reflexio/reflexio/README.md +184 -0
  28. package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
  29. package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
  30. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
  31. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
  32. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
  33. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
  34. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
  35. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
  36. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
  37. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
  38. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
  39. package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
  40. package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
  41. package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
  42. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
  43. package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
  44. package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
  45. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +265 -0
  46. package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
  47. package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
  48. package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
  49. package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
  50. package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
  51. package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
  52. package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
  53. package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
  54. package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +964 -0
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
  62. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
  63. package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
  64. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
  65. package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
  66. package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
  67. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
  68. package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
  71. package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
  72. package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
  73. package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
  74. package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
  145. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
  146. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
  147. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
  148. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
  149. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
  150. package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
  151. package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
  152. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
  153. package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
  154. package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
  155. package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
  156. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
  157. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
  158. package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
  159. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
  160. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
  161. package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
  162. package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
  163. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
  164. package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
  165. package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
  166. package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
  167. package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
  168. package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
  169. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
  170. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1112 -0
  171. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
  172. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
  173. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
  174. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
  175. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +768 -0
  176. package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
  177. package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
  178. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
  179. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
  180. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
  181. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
  182. package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +333 -0
  183. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
  184. package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
  185. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
  186. package/plugin/vendor/reflexio/reflexio/server/README.md +622 -0
  187. package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
  188. package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
  189. package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
  190. package/plugin/vendor/reflexio/reflexio/server/api.py +2868 -0
  191. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
  192. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
  193. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
  194. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
  195. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
  196. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +562 -0
  197. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
  198. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
  199. package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
  200. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
  201. package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
  202. package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
  203. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +359 -0
  204. package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
  205. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1871 -0
  206. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +140 -0
  207. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +479 -0
  208. package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
  209. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
  210. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
  211. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +338 -0
  212. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
  213. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +288 -0
  214. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
  215. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +187 -0
  216. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
  217. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +716 -0
  218. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
  219. package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
  220. package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
  221. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
  222. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
  223. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
  224. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
  225. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
  226. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
  227. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
  228. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
  229. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
  230. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
  231. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
  232. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
  233. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
  234. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
  235. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
  236. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
  237. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
  238. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
  239. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
  240. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
  241. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
  242. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
  243. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
  244. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
  245. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
  246. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
  247. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
  248. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +283 -0
  249. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
  250. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
  251. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
  252. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
  253. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
  254. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
  255. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
  256. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +137 -0
  257. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
  258. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
  259. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
  260. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
  261. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
  262. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
  263. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
  264. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
  265. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
  266. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
  267. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
  268. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
  269. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
  270. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
  271. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
  272. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
  273. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
  274. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
  275. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
  276. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
  277. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
  278. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
  279. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
  280. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
  281. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
  282. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
  283. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
  289. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
  290. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +336 -0
  292. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1668 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
  296. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
  297. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
  311. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
  312. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
  313. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
  318. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +358 -0
  319. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
  320. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
  321. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +535 -0
  322. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
  323. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +779 -0
  324. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
  325. package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +94 -0
  326. package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
  327. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +702 -0
  328. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
  329. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
  330. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
  331. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +1045 -0
  332. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
  333. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
  334. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
  335. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
  336. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
  337. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
  338. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
  339. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
  340. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
  341. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
  342. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
  343. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
  344. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
  345. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
  346. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
  347. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
  348. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +772 -0
  349. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
  350. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +737 -0
  351. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
  352. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
  353. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
  354. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +803 -0
  355. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
  356. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
  357. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +80 -0
  358. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
  359. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +756 -0
  360. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
  361. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
  362. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
  363. package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
  364. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
  365. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
  366. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
  367. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
  368. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1298 -0
  369. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
  370. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
  371. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
  372. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
  373. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
  374. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
  375. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
  376. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
  377. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
  378. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
  379. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +384 -0
  380. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
  381. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
  382. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
  383. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
  384. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
  385. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
  386. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
  387. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
  388. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
  389. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +572 -0
  390. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
  391. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
  392. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
  393. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
  394. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
  395. package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
  396. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
  397. package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
  398. package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
  399. package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
  400. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
  401. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
  402. 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