claude-smart 0.2.45 → 0.2.46
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 +34 -5
- package/bin/claude-smart.js +295 -5
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +22 -2
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +158 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +174 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +20 -3
- package/plugin/scripts/_lib.sh +5 -1
- package/plugin/scripts/backend-service.sh +22 -2
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +1 -1
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +10 -8
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +304 -6
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +14 -8
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +1 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
|
@@ -20,6 +20,13 @@ def validate_publish_user_interaction_request(
|
|
|
20
20
|
if not request.interaction_data_list:
|
|
21
21
|
return False, "No interaction data provided"
|
|
22
22
|
|
|
23
|
+
# Defense-in-depth: session_id is a required NonEmptyStr on
|
|
24
|
+
# PublishUserInteractionRequest, so the normal validated API path already
|
|
25
|
+
# rejects empty/missing values with a 422. This guard additionally covers
|
|
26
|
+
# paths that bypass Pydantic validation (e.g. ``model_construct``).
|
|
27
|
+
if not request.session_id or not request.session_id.strip():
|
|
28
|
+
return False, "session_id is required and cannot be empty"
|
|
29
|
+
|
|
23
30
|
for interaction_data in request.interaction_data_list:
|
|
24
31
|
if (
|
|
25
32
|
interaction_data.user_action != UserActionType.NONE
|
|
@@ -17,7 +17,9 @@ class RequestContext:
|
|
|
17
17
|
self.storage_base_dir = storage_base_dir
|
|
18
18
|
cls = get_configurator_class()
|
|
19
19
|
self.configurator = configurator or cls(org_id, base_dir=storage_base_dir)
|
|
20
|
-
self.prompt_manager = PromptManager(
|
|
20
|
+
self.prompt_manager = PromptManager(
|
|
21
|
+
extra_prompt_bank_paths=self.configurator.get_prompt_bank_paths()
|
|
22
|
+
)
|
|
21
23
|
self.storage = self.configurator.create_storage(
|
|
22
24
|
storage_config=self.configurator.get_current_storage_configuration(),
|
|
23
25
|
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""OSS emission helpers: translate billing signals into usage_events.
|
|
2
|
+
|
|
3
|
+
The single source of truth for each billing event's name/category/fields. Plain
|
|
4
|
+
primitive signatures (no reflexio_ext types) so OSS call sites — the generation
|
|
5
|
+
service and search endpoints — can use them directly. Each is a thin, non-blocking
|
|
6
|
+
wrapper over the ``record_usage_event`` hook (which only enqueues). No DB I/O.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from reflexio.server.usage_metrics import record_usage_event
|
|
12
|
+
|
|
13
|
+
_INTERNAL = (
|
|
14
|
+
"internal" # == BillingCallerType.INTERNAL.value (kept literal; OSS stays clean)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def record_extraction_tokens(
|
|
19
|
+
*,
|
|
20
|
+
org_id: str,
|
|
21
|
+
billing_input_tokens: int,
|
|
22
|
+
prompt_tokens: int,
|
|
23
|
+
completion_tokens: int,
|
|
24
|
+
platform_llm: bool | None,
|
|
25
|
+
platform_storage: bool | None,
|
|
26
|
+
pipeline: str | None = None,
|
|
27
|
+
request_id: str | None = None,
|
|
28
|
+
session_id: str | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Emit the Learning cost facet — call only when extraction fired.
|
|
31
|
+
|
|
32
|
+
No-op when ``billing_input_tokens <= 0``.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
org_id: Organisation identifier.
|
|
36
|
+
billing_input_tokens: Input-anchored token count (the metered basis).
|
|
37
|
+
prompt_tokens: Real provider prompt tokens (COGS; not billed to customer).
|
|
38
|
+
completion_tokens: Real provider completion tokens (COGS; not billed).
|
|
39
|
+
platform_llm: True iff the platform supplies the LLM for this org.
|
|
40
|
+
platform_storage: True iff the platform supplies storage; None defers to rollup.
|
|
41
|
+
pipeline: Optional pipeline tag (e.g. ``"profile"``).
|
|
42
|
+
request_id: Optional request correlation ID.
|
|
43
|
+
session_id: Optional session ID.
|
|
44
|
+
"""
|
|
45
|
+
if billing_input_tokens <= 0:
|
|
46
|
+
return
|
|
47
|
+
record_usage_event(
|
|
48
|
+
org_id=org_id,
|
|
49
|
+
event_name="extraction_tokens",
|
|
50
|
+
event_category="learning",
|
|
51
|
+
pipeline=pipeline,
|
|
52
|
+
request_id=request_id,
|
|
53
|
+
session_id=session_id,
|
|
54
|
+
count_value=billing_input_tokens,
|
|
55
|
+
prompt_tokens=prompt_tokens,
|
|
56
|
+
completion_tokens=completion_tokens,
|
|
57
|
+
billing_input_tokens=billing_input_tokens,
|
|
58
|
+
platform_llm=platform_llm,
|
|
59
|
+
platform_storage=platform_storage,
|
|
60
|
+
caller_type=_INTERNAL,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def record_learnings_generated(
|
|
65
|
+
*,
|
|
66
|
+
org_id: str,
|
|
67
|
+
count: int,
|
|
68
|
+
platform_llm: bool | None,
|
|
69
|
+
platform_storage: bool | None,
|
|
70
|
+
pipeline: str | None = None,
|
|
71
|
+
request_id: str | None = None,
|
|
72
|
+
session_id: str | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Emit the Learning value facet — number of profiles/playbooks generated.
|
|
75
|
+
|
|
76
|
+
No-op when ``count <= 0``.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
org_id: Organisation identifier.
|
|
80
|
+
count: Number of learnings generated in this run.
|
|
81
|
+
platform_llm: True iff the platform supplies the LLM for this org.
|
|
82
|
+
platform_storage: True iff the platform supplies storage; None defers to rollup.
|
|
83
|
+
pipeline: Optional pipeline tag (e.g. ``"playbook"``).
|
|
84
|
+
request_id: Optional request correlation ID.
|
|
85
|
+
session_id: Optional session ID.
|
|
86
|
+
"""
|
|
87
|
+
if count <= 0:
|
|
88
|
+
return
|
|
89
|
+
record_usage_event(
|
|
90
|
+
org_id=org_id,
|
|
91
|
+
event_name="learnings_generated",
|
|
92
|
+
event_category="learning",
|
|
93
|
+
pipeline=pipeline,
|
|
94
|
+
request_id=request_id,
|
|
95
|
+
session_id=session_id,
|
|
96
|
+
count_value=count,
|
|
97
|
+
platform_llm=platform_llm,
|
|
98
|
+
platform_storage=platform_storage,
|
|
99
|
+
caller_type=_INTERNAL,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def record_applied_learnings(
|
|
104
|
+
*,
|
|
105
|
+
org_id: str,
|
|
106
|
+
surfaced_count: int,
|
|
107
|
+
caller_type: str,
|
|
108
|
+
platform_llm: bool | None,
|
|
109
|
+
platform_storage: bool | None,
|
|
110
|
+
pipeline: str | None = None,
|
|
111
|
+
request_id: str | None = None,
|
|
112
|
+
session_id: str | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Emit the Application line — surfaced top-K learnings.
|
|
115
|
+
|
|
116
|
+
No-op unless ``caller_type == "production_agent"`` AND ``surfaced_count > 0``.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
org_id: Organisation identifier.
|
|
120
|
+
surfaced_count: Number of learnings surfaced in the search response.
|
|
121
|
+
caller_type: Caller classification string (e.g. ``"production_agent"``).
|
|
122
|
+
platform_llm: True iff the platform supplies the LLM for this org.
|
|
123
|
+
platform_storage: True iff the platform supplies storage; None defers to rollup.
|
|
124
|
+
pipeline: Optional pipeline tag.
|
|
125
|
+
request_id: Optional request correlation ID.
|
|
126
|
+
session_id: Optional session ID.
|
|
127
|
+
"""
|
|
128
|
+
if caller_type != "production_agent" or surfaced_count <= 0:
|
|
129
|
+
return
|
|
130
|
+
record_usage_event(
|
|
131
|
+
org_id=org_id,
|
|
132
|
+
event_name="learning_applied",
|
|
133
|
+
event_category="application",
|
|
134
|
+
pipeline=pipeline,
|
|
135
|
+
request_id=request_id,
|
|
136
|
+
session_id=session_id,
|
|
137
|
+
count_value=surfaced_count,
|
|
138
|
+
platform_llm=platform_llm,
|
|
139
|
+
platform_storage=platform_storage,
|
|
140
|
+
caller_type=caller_type,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def record_search_request(
|
|
145
|
+
*,
|
|
146
|
+
org_id: str,
|
|
147
|
+
caller_type: str,
|
|
148
|
+
request_id: str | None = None,
|
|
149
|
+
session_id: str | None = None,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Emit one analytics-only search request count for production agents.
|
|
152
|
+
|
|
153
|
+
No-op unless ``caller_type == "production_agent"``. Unlike
|
|
154
|
+
:func:`record_applied_learnings`, empty search responses still count because
|
|
155
|
+
this measures requests made, not learnings surfaced.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
org_id: Organisation identifier.
|
|
159
|
+
caller_type: Caller classification string.
|
|
160
|
+
request_id: Optional request correlation ID.
|
|
161
|
+
session_id: Optional session ID.
|
|
162
|
+
"""
|
|
163
|
+
if caller_type != "production_agent":
|
|
164
|
+
return
|
|
165
|
+
record_usage_event(
|
|
166
|
+
org_id=org_id,
|
|
167
|
+
event_name="search_request",
|
|
168
|
+
event_category="application",
|
|
169
|
+
request_id=request_id,
|
|
170
|
+
session_id=session_id,
|
|
171
|
+
count_value=1,
|
|
172
|
+
caller_type=caller_type,
|
|
173
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""OSS-pure billing signal helpers (no reflexio_ext imports).
|
|
2
|
+
|
|
3
|
+
Single source of truth for whether the platform supplies the LLM, so the OSS
|
|
4
|
+
generation service and the enterprise attribution resolver never diverge.
|
|
5
|
+
Also provides the one canonical input-anchored tokenizer for the whole platform.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import tiktoken
|
|
14
|
+
|
|
15
|
+
# One fixed canonical encoding for the whole platform. Changing this is a
|
|
16
|
+
# price-book-affecting decision; do not make it model-dependent.
|
|
17
|
+
_CANONICAL_ENCODING_NAME = "cl100k_base"
|
|
18
|
+
_encoding = tiktoken.get_encoding(_CANONICAL_ENCODING_NAME)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def count_input_tokens(text: str) -> int:
|
|
22
|
+
"""Count billable input tokens for ``text`` under the canonical cl100k_base encoding.
|
|
23
|
+
|
|
24
|
+
This is the single canonical tokenizer for the whole platform. Enterprise
|
|
25
|
+
code delegates here so OSS and enterprise always use the same encoding.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
text (str): The input text to tokenize.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
int: Number of tokens under cl100k_base, or 0 for empty input.
|
|
32
|
+
"""
|
|
33
|
+
if not text:
|
|
34
|
+
return 0
|
|
35
|
+
return len(_encoding.encode(text, disallowed_special=()))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def platform_llm_from_config(config: Any) -> bool:
|
|
39
|
+
"""Return True iff Reflexio (not the customer) supplies the LLM for ``config``.
|
|
40
|
+
|
|
41
|
+
A populated ``api_key_config`` provider sub-config means BYO-LLM → False.
|
|
42
|
+
A missing/empty ``api_key_config`` means platform-supplied → True.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
config: The org's resolved ``Config`` object, or None.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
bool: True when the platform supplies the LLM; False when the customer
|
|
49
|
+
has configured a BYO provider key.
|
|
50
|
+
"""
|
|
51
|
+
api_key_config = getattr(config, "api_key_config", None)
|
|
52
|
+
if api_key_config is None:
|
|
53
|
+
return True
|
|
54
|
+
data = (
|
|
55
|
+
api_key_config.model_dump(exclude_none=True)
|
|
56
|
+
if hasattr(api_key_config, "model_dump")
|
|
57
|
+
else {}
|
|
58
|
+
)
|
|
59
|
+
# Guard against a model_dump that returns a non-mapping: without this,
|
|
60
|
+
# `.values()` would raise. Default to platform-supplied (True) on a shape
|
|
61
|
+
# we don't understand rather than misclassifying the org as BYO-LLM.
|
|
62
|
+
if not isinstance(data, Mapping):
|
|
63
|
+
return True
|
|
64
|
+
return not any(bool(v) for v in data.values())
|
|
@@ -8,6 +8,7 @@ from typing import Any, Final
|
|
|
8
8
|
from cachetools import TTLCache
|
|
9
9
|
|
|
10
10
|
from reflexio.lib.reflexio_lib import Reflexio
|
|
11
|
+
from reflexio.server.tracing import profile_step
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -112,8 +113,14 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
|
|
|
112
113
|
|
|
113
114
|
# Cache lookup — held briefly to extract the entry, then released
|
|
114
115
|
# before doing any I/O (file stat, DB query) for the version probe.
|
|
115
|
-
with
|
|
116
|
-
|
|
116
|
+
with profile_step("reflexio.cache.lookup") as span:
|
|
117
|
+
with _reflexio_cache_lock:
|
|
118
|
+
entry: _CacheEntry | None = _reflexio_cache.get(cache_key)
|
|
119
|
+
span.set_data("cache_hit", entry is not None)
|
|
120
|
+
span.set_data(
|
|
121
|
+
"has_cached_version",
|
|
122
|
+
entry is not None and entry.cached_version is not None,
|
|
123
|
+
)
|
|
117
124
|
|
|
118
125
|
if entry is not None:
|
|
119
126
|
cached_version = entry.cached_version
|
|
@@ -122,7 +129,13 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
|
|
|
122
129
|
# rely on TTL + explicit invalidation instead.
|
|
123
130
|
if cached_version is None:
|
|
124
131
|
return entry.reflexio
|
|
125
|
-
|
|
132
|
+
with profile_step("reflexio.cache.version_probe", cache_state="hit") as span:
|
|
133
|
+
current_version = _probe_version_safe(entry.reflexio)
|
|
134
|
+
span.set_data("probe_failed", current_version is _PROBE_FAILED)
|
|
135
|
+
span.set_data(
|
|
136
|
+
"probe_supported",
|
|
137
|
+
current_version is not None and current_version is not _PROBE_FAILED,
|
|
138
|
+
)
|
|
126
139
|
# Transient probe failure: keep the cached instance for this
|
|
127
140
|
# request but DON'T mutate the stamp — the next request will
|
|
128
141
|
# try again, so a brief outage doesn't permanently disable
|
|
@@ -134,15 +147,27 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
|
|
|
134
147
|
# Stale entry. Evict only if the cached version still matches
|
|
135
148
|
# the one we just compared against — another thread may have
|
|
136
149
|
# already replaced the entry while we were probing.
|
|
137
|
-
with
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
with profile_step("reflexio.cache.evict_stale") as span:
|
|
151
|
+
with _reflexio_cache_lock:
|
|
152
|
+
existing = _reflexio_cache.get(cache_key)
|
|
153
|
+
evicted = (
|
|
154
|
+
existing is not None and existing.cached_version == cached_version
|
|
155
|
+
)
|
|
156
|
+
if evicted:
|
|
157
|
+
del _reflexio_cache[cache_key]
|
|
158
|
+
span.set_data("evicted", evicted)
|
|
141
159
|
|
|
142
160
|
# Cache miss (or just-evicted stale entry) - create a new instance
|
|
143
161
|
# outside the lock to avoid blocking concurrent requests for other orgs.
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
with profile_step("reflexio.cache.construct"):
|
|
163
|
+
reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
|
|
164
|
+
with profile_step("reflexio.cache.version_probe", cache_state="miss") as span:
|
|
165
|
+
new_version = _probe_version_safe(reflexio)
|
|
166
|
+
span.set_data("probe_failed", new_version is _PROBE_FAILED)
|
|
167
|
+
span.set_data(
|
|
168
|
+
"probe_supported",
|
|
169
|
+
new_version is not None and new_version is not _PROBE_FAILED,
|
|
170
|
+
)
|
|
146
171
|
|
|
147
172
|
# Construction-time probe failure: serve this request from the
|
|
148
173
|
# newly-built instance but DON'T cache it. Caching with
|
|
@@ -159,13 +184,18 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
|
|
|
159
184
|
cached_version=new_version, # type: ignore[arg-type]
|
|
160
185
|
)
|
|
161
186
|
|
|
162
|
-
with
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
with profile_step("reflexio.cache.store") as span:
|
|
188
|
+
with _reflexio_cache_lock:
|
|
189
|
+
# Double-check in case another thread populated while we were constructing.
|
|
190
|
+
existing = _reflexio_cache.get(cache_key)
|
|
191
|
+
stored = existing is None
|
|
192
|
+
if stored:
|
|
193
|
+
_reflexio_cache[cache_key] = new_entry
|
|
194
|
+
result = reflexio
|
|
195
|
+
else:
|
|
196
|
+
result = existing.reflexio
|
|
197
|
+
span.set_data("stored", stored)
|
|
198
|
+
return result
|
|
169
199
|
|
|
170
200
|
|
|
171
201
|
def invalidate_reflexio_cache(org_id: str, storage_base_dir: str | None = None) -> bool:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Small helpers for parsing environment-backed settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def env_get(env: Mapping[str, str], name: str) -> str:
|
|
10
|
+
"""Return a stripped environment value or an empty string."""
|
|
11
|
+
return str(env.get(name, "")).strip()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def env_str(
|
|
15
|
+
name: str,
|
|
16
|
+
default: str = "",
|
|
17
|
+
*,
|
|
18
|
+
env: Mapping[str, str] | None = None,
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Return an environment value, treating unset OR blank as the default.
|
|
21
|
+
|
|
22
|
+
Unlike ``os.getenv(name, default)``, an empty / whitespace-only value
|
|
23
|
+
resolves to ``default`` rather than passing the empty string through. This
|
|
24
|
+
closes the "set to blank behaves differently from unset" gap: a ``KEY=``
|
|
25
|
+
line (which python-dotenv exports as ``""``) and an absent key both yield
|
|
26
|
+
the default, so callers never silently receive ``""`` where they expected a
|
|
27
|
+
real fallback (e.g. an ``int()``/``float()`` parse that would crash on ``""``).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: Environment variable name.
|
|
31
|
+
default: Value returned when the variable is unset or blank.
|
|
32
|
+
env: Mapping to read from; defaults to ``os.environ``.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The stripped environment value, or ``default`` when unset/blank.
|
|
36
|
+
"""
|
|
37
|
+
raw = (env if env is not None else os.environ).get(name)
|
|
38
|
+
return raw.strip() if raw and raw.strip() else default
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def env_required(env: Mapping[str, str], name: str) -> str:
|
|
42
|
+
"""Return a required environment value or raise a startup error."""
|
|
43
|
+
value = env_get(env, name)
|
|
44
|
+
if not value:
|
|
45
|
+
raise RuntimeError(f"{name} is required for enterprise startup")
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def env_required_literal(
|
|
50
|
+
env: Mapping[str, str],
|
|
51
|
+
name: str,
|
|
52
|
+
*,
|
|
53
|
+
allowed: tuple[str, ...],
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Return a required lowercased value constrained to allowed literals."""
|
|
56
|
+
value = env_required(env, name).lower()
|
|
57
|
+
if value not in allowed:
|
|
58
|
+
allowed_text = " or ".join(repr(item) for item in allowed)
|
|
59
|
+
raise RuntimeError(f"{name} must be {allowed_text} (got {value!r})")
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def env_truthy(value: str) -> bool:
|
|
64
|
+
"""Return whether a string environment value is truthy."""
|
|
65
|
+
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
@@ -34,6 +34,16 @@ _SUPPORTED_MODELS = {
|
|
|
34
34
|
_ACTIVE_MODEL: str | None = None
|
|
35
35
|
_ACTIVE_MODEL_LOCK = threading.Lock()
|
|
36
36
|
|
|
37
|
+
# The underlying sentence-transformers / ONNX embedders share internal buffers
|
|
38
|
+
# and are NOT thread-safe: concurrent ``.embed()`` calls interleave and corrupt
|
|
39
|
+
# each other's padding/attention tensors (observed under concurrent publishes as
|
|
40
|
+
# ``RuntimeError: The size of tensor a (62) must match the size of tensor b (61)
|
|
41
|
+
# at non-singleton dimension 1``). The ``_ENCODE_SEMAPHORE`` below only caps how
|
|
42
|
+
# many requests run at once (for memory), it does NOT serialize them, so this
|
|
43
|
+
# lock guards the actual model inference. Throughput comes from micro-batch
|
|
44
|
+
# coalescing (many texts per encode), not from parallel encodes on one model.
|
|
45
|
+
_MODEL_ENCODE_LOCK = threading.Lock()
|
|
46
|
+
|
|
37
47
|
DEFAULT_OSS_EMBEDDING_MODEL = MINILM_MODEL
|
|
38
48
|
|
|
39
49
|
# Bound how many embed/encode calls run at once. The endpoint is a sync ``def``
|
|
@@ -314,10 +324,15 @@ def _encode_texts_now(model: str, texts: list[str]) -> list[list[float]]:
|
|
|
314
324
|
)
|
|
315
325
|
semaphore.acquire()
|
|
316
326
|
try:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
327
|
+
# Serialize the actual inference: the shared embedder models are not
|
|
328
|
+
# thread-safe, so concurrent encodes race and produce tensor-shape
|
|
329
|
+
# errors. The semaphore (above) bounds memory; this lock bounds the
|
|
330
|
+
# model to one in-flight encode at a time.
|
|
331
|
+
with _MODEL_ENCODE_LOCK:
|
|
332
|
+
if is_nomic_model(model):
|
|
333
|
+
return NomicEmbedder.get().embed(texts)
|
|
334
|
+
if model == MINILM_MODEL:
|
|
335
|
+
return LocalEmbedder.get().embed(texts)
|
|
321
336
|
raise HTTPException(status_code=400, detail=f"Unsupported model: {model}")
|
|
322
337
|
finally:
|
|
323
338
|
semaphore.release()
|