claude-smart 0.2.44 → 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 +77 -29
- package/plugin/scripts/backend-service.sh +27 -5
- package/plugin/scripts/dashboard-service.sh +7 -5
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +3 -3
- 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
|
@@ -1,1125 +0,0 @@
|
|
|
1
|
-
"""Atomic tool handlers for the agentic-v2 extraction + search pipelines.
|
|
2
|
-
|
|
3
|
-
Each handler:
|
|
4
|
-
- Receives args (Pydantic model validated by ToolRegistry)
|
|
5
|
-
- Receives (storage, ctx)
|
|
6
|
-
- Calls an existing BaseStorage method
|
|
7
|
-
- Returns a dict projection suitable for the LLM
|
|
8
|
-
|
|
9
|
-
Read handlers populate ctx.known_ids (for invariant B) and ctx.search_count
|
|
10
|
-
(for invariant A). Mutating handlers (Task 5) append PlanOps to ctx.plan
|
|
11
|
-
without hitting storage; commit_plan applies them via apply_plan_op after
|
|
12
|
-
invariants pass.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import logging
|
|
18
|
-
import uuid
|
|
19
|
-
from datetime import UTC, datetime
|
|
20
|
-
from typing import Annotated, Any, Literal
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
from pydantic import BaseModel, Field
|
|
25
|
-
|
|
26
|
-
from reflexio.models.api_schema.domain.entities import (
|
|
27
|
-
PlaybookStatus,
|
|
28
|
-
Status,
|
|
29
|
-
UserPlaybook,
|
|
30
|
-
UserProfile,
|
|
31
|
-
)
|
|
32
|
-
from reflexio.models.api_schema.domain.enums import ProfileTimeToLive
|
|
33
|
-
from reflexio.models.api_schema.retriever_schema import (
|
|
34
|
-
SearchAgentPlaybookRequest,
|
|
35
|
-
SearchMode,
|
|
36
|
-
SearchUserPlaybookRequest,
|
|
37
|
-
SearchUserProfileRequest,
|
|
38
|
-
)
|
|
39
|
-
from reflexio.models.config_schema import SINGLETON_USER_PLAYBOOK_NAME, SearchOptions
|
|
40
|
-
from reflexio.server.services.extraction.plan import (
|
|
41
|
-
CreateUserPlaybookOp,
|
|
42
|
-
CreateUserProfileOp,
|
|
43
|
-
DeleteUserPlaybookOp,
|
|
44
|
-
DeleteUserProfileOp,
|
|
45
|
-
ExtractionCtx,
|
|
46
|
-
)
|
|
47
|
-
from reflexio.server.services.profile.profile_generation_service_utils import (
|
|
48
|
-
calculate_expiration_timestamp,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
TOP_K_CAP = 25
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# ====================================================================
|
|
55
|
-
# Arg schemas (what the LLM emits)
|
|
56
|
-
# ====================================================================
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class SearchUserProfilesArgs(BaseModel):
|
|
60
|
-
"""Semantic/keyword search the current user's profiles, with optional
|
|
61
|
-
cross-encoder rerank.
|
|
62
|
-
|
|
63
|
-
Default behaviour (``rerank=False``): hybrid retrieval (BM25 + vector
|
|
64
|
-
via RRF) only, return top ``top_k``. Cheap.
|
|
65
|
-
|
|
66
|
-
Set ``rerank=True`` to add a cross-encoder reranking pass on top of the
|
|
67
|
-
hybrid retrieval. The server over-fetches a wider candidate pool, scores
|
|
68
|
-
each (query, content) pair with the cross-encoder, and returns the top
|
|
69
|
-
``top_k`` by descending rerank score. More accurate ranking when the
|
|
70
|
-
bi-encoder near-duplicate confuses the LLM, but adds ~5-15 ms latency
|
|
71
|
-
and the encoder may not weight all facets the same as the LLM would.
|
|
72
|
-
|
|
73
|
-
Optional ``refine_with``: when set, used as the rerank query INSTEAD of
|
|
74
|
-
``query``. Only takes effect when ``rerank=True`` or ``llm_rerank=True``.
|
|
75
|
-
Lets you broadly fetch ("bike maintenance") then narrow on a specific
|
|
76
|
-
facet ("dollar amounts spent") without round-tripping candidate ids
|
|
77
|
-
through the agent.
|
|
78
|
-
|
|
79
|
-
Set ``llm_rerank=True`` to use an LLM relevance-judge instead of the
|
|
80
|
-
cross-encoder. The LLM brings world knowledge that lexical/semantic
|
|
81
|
-
cross-encoders lack — bridges brand→category gaps (e.g. "Thrive Market"
|
|
82
|
-
= grocery service, "Italian designer boots" = luxury footwear) that
|
|
83
|
-
sink the relevant profile under literal-keyword matches. Costs ~1 s
|
|
84
|
-
of latency and one LLM call per search; use it on the FIRST pass when
|
|
85
|
-
the query is brand/category/synonym-prone, NOT as a refinement step.
|
|
86
|
-
Mutually exclusive with ``rerank``: when both are True, ``llm_rerank``
|
|
87
|
-
wins.
|
|
88
|
-
|
|
89
|
-
Each hit includes a ``session_id`` field — pass that to
|
|
90
|
-
``read_session_text`` if you need the verbatim source turns behind a
|
|
91
|
-
profile.
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
query: Annotated[str, Field(min_length=1)]
|
|
95
|
-
top_k: int = 10
|
|
96
|
-
rerank: bool = False
|
|
97
|
-
llm_rerank: bool = False
|
|
98
|
-
refine_with: str | None = None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class GetUserProfileArgs(BaseModel):
|
|
102
|
-
"""Retrieve a single UserProfile by id."""
|
|
103
|
-
|
|
104
|
-
id: Annotated[str, Field(min_length=1)]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class SearchUserPlaybooksArgs(BaseModel):
|
|
108
|
-
"""Search the current user's playbooks. See SearchUserProfilesArgs for
|
|
109
|
-
the rerank semantics — same toggles, same handler shape.
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
query: Annotated[str, Field(min_length=1)]
|
|
113
|
-
top_k: int = 10
|
|
114
|
-
status: Literal["current", "pending", "archived"] = "current"
|
|
115
|
-
rerank: bool = False
|
|
116
|
-
llm_rerank: bool = False
|
|
117
|
-
refine_with: str | None = None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class GetUserPlaybookArgs(BaseModel):
|
|
121
|
-
"""Retrieve a single UserPlaybook by id."""
|
|
122
|
-
|
|
123
|
-
id: Annotated[str, Field(min_length=1)]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
class SearchAgentPlaybooksArgs(BaseModel):
|
|
127
|
-
"""Search agent-version-scoped playbooks (read-only; search pipeline only).
|
|
128
|
-
See SearchUserProfilesArgs for the rerank semantics.
|
|
129
|
-
"""
|
|
130
|
-
|
|
131
|
-
query: Annotated[str, Field(min_length=1)]
|
|
132
|
-
top_k: int = 10
|
|
133
|
-
status: Literal["current", "pending", "archived"] = "current"
|
|
134
|
-
rerank: bool = False
|
|
135
|
-
llm_rerank: bool = False
|
|
136
|
-
refine_with: str | None = None
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class GetAgentPlaybookArgs(BaseModel):
|
|
140
|
-
"""Retrieve a single AgentPlaybook by id."""
|
|
141
|
-
|
|
142
|
-
id: Annotated[str, Field(min_length=1)]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class ReadSessionTextArgs(BaseModel):
|
|
146
|
-
"""Retrieve and compress the verbatim turns of one or more sessions.
|
|
147
|
-
|
|
148
|
-
Fetches raw interactions for each ``session_id`` and runs an in-tool
|
|
149
|
-
compression pass against ``query`` so the returned text is a denoised
|
|
150
|
-
excerpt focused on the question — not the full transcript. Sessions are
|
|
151
|
-
concatenated with ``=== session <id> ===`` headers. Falls back to
|
|
152
|
-
role-prefixed raw turns (truncated at ``max_chars_per_session``) when
|
|
153
|
-
compression is unavailable or fails.
|
|
154
|
-
|
|
155
|
-
Use after ``search_user_profiles`` when the profile content compresses
|
|
156
|
-
away detail you need (multi-action structure, exact dates buried in
|
|
157
|
-
narrative, prior assistant statements). Pass 1–4 session_ids in a single
|
|
158
|
-
call when the question requires data from multiple sessions (e.g.
|
|
159
|
-
ordering 4 events, summing per-event amounts across 3 transactions).
|
|
160
|
-
|
|
161
|
-
The ``query`` field is what compression scores against — typically the
|
|
162
|
-
user's question or a focused subquery. Pass an empty string to skip
|
|
163
|
-
compression and receive raw role-prefixed turns instead.
|
|
164
|
-
|
|
165
|
-
The ``session_ids`` come from profile hits' ``session_id`` field (the
|
|
166
|
-
same value the search response exposes). ``max_chars_per_session``
|
|
167
|
-
bounds the raw-fallback output; compressed output is bounded by the
|
|
168
|
-
compressor's own discretion (typically much smaller).
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
session_ids: Annotated[list[str], Field(min_length=1, max_length=4)]
|
|
172
|
-
query: str = ""
|
|
173
|
-
max_chars_per_session: int = 16000
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
class RerankUserProfilesArgs(BaseModel):
|
|
177
|
-
"""Rerank a list of profile ids by query relevance using a cross-encoder.
|
|
178
|
-
|
|
179
|
-
Use after `search_user_profiles` when the initial results are noisy and
|
|
180
|
-
you need to surface the most semantically relevant ones to the question.
|
|
181
|
-
"""
|
|
182
|
-
|
|
183
|
-
query: Annotated[str, Field(min_length=1)]
|
|
184
|
-
profile_ids: list[str]
|
|
185
|
-
top_k: int = 10
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
class StorageStatsArgs(BaseModel):
|
|
189
|
-
"""Get a quick count of how many profiles/playbooks the user has and the date range.
|
|
190
|
-
|
|
191
|
-
Useful for sizing search top_k appropriately before retrieval.
|
|
192
|
-
"""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
# ====================================================================
|
|
196
|
-
# Helpers
|
|
197
|
-
# ====================================================================
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def _cap_top_k(k: int) -> int:
|
|
201
|
-
return min(max(1, k), TOP_K_CAP)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _maybe_embed_query(storage: Any, query: str) -> list[float] | None:
|
|
205
|
-
"""Compute a query embedding via the storage backend's embedder.
|
|
206
|
-
|
|
207
|
-
Returns ``None`` on any failure (backend doesn't expose ``_get_embedding``,
|
|
208
|
-
embedding provider unavailable, or embed call raises). Without an embedding,
|
|
209
|
-
storage downgrades HYBRID/VECTOR search to FTS-only — the classic search
|
|
210
|
-
path (``unified_search_service.py:151-158``) uses the same helper pattern.
|
|
211
|
-
|
|
212
|
-
Args:
|
|
213
|
-
storage (Any): BaseStorage instance.
|
|
214
|
-
query (str): The search query to embed.
|
|
215
|
-
|
|
216
|
-
Returns:
|
|
217
|
-
list[float] | None: The embedding vector, or ``None`` when unavailable.
|
|
218
|
-
"""
|
|
219
|
-
embed_fn = getattr(storage, "_get_embedding", None)
|
|
220
|
-
if embed_fn is None:
|
|
221
|
-
return None
|
|
222
|
-
try:
|
|
223
|
-
return embed_fn(query)
|
|
224
|
-
except Exception: # noqa: BLE001 — embedder failures must not break search
|
|
225
|
-
return None
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _status_from_str(s: str) -> Status | None:
|
|
229
|
-
return {"current": None, "pending": Status.PENDING, "archived": Status.ARCHIVED}[s]
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _agent_playbook_statuses_from_ctx(ctx: ExtractionCtx) -> list[PlaybookStatus]:
|
|
233
|
-
values = ctx.agent_playbook_status_filter or ["approved", "pending"]
|
|
234
|
-
out: list[PlaybookStatus] = []
|
|
235
|
-
for value in values:
|
|
236
|
-
try:
|
|
237
|
-
status = PlaybookStatus(value)
|
|
238
|
-
except ValueError:
|
|
239
|
-
continue
|
|
240
|
-
if status not in out:
|
|
241
|
-
out.append(status)
|
|
242
|
-
return out
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
RERANK_POOL_SIZE = 30
|
|
246
|
-
"""How many candidates we hand the reranker when ``rerank=True`` or
|
|
247
|
-
``llm_rerank=True``.
|
|
248
|
-
|
|
249
|
-
If ``final_k`` exceeds this constant (e.g. agent asks for top_k=50 with
|
|
250
|
-
rerank=True), we still fetch ``final_k`` so we have a full set to return.
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def _fetch_k_for_rerank(final_k: int, rerank: bool, llm_rerank: bool = False) -> int:
|
|
255
|
-
"""Pick the initial-fetch size given whether any rerank stage is enabled.
|
|
256
|
-
|
|
257
|
-
Without rerank: trust the hybrid retrieval order, fetch exactly ``final_k``.
|
|
258
|
-
With rerank (cross-encoder OR LLM): fetch ``RERANK_POOL_SIZE`` candidates
|
|
259
|
-
so the reranker has headroom to reorder. If ``final_k`` already exceeds
|
|
260
|
-
the pool size, use ``final_k`` (agent asked for more than we'd otherwise
|
|
261
|
-
fetch).
|
|
262
|
-
|
|
263
|
-
Args:
|
|
264
|
-
final_k (int): Number of hits the agent asked for.
|
|
265
|
-
rerank (bool): Whether the cross-encoder rerank stage is enabled.
|
|
266
|
-
llm_rerank (bool): Whether the LLM rerank stage is enabled.
|
|
267
|
-
|
|
268
|
-
Returns:
|
|
269
|
-
int: The initial-fetch size for hybrid retrieval.
|
|
270
|
-
"""
|
|
271
|
-
if not (rerank or llm_rerank):
|
|
272
|
-
return final_k
|
|
273
|
-
return max(final_k, RERANK_POOL_SIZE)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def _maybe_rerank_hits(
|
|
277
|
-
hits: list[Any],
|
|
278
|
-
rerank: bool,
|
|
279
|
-
rerank_query: str,
|
|
280
|
-
final_k: int,
|
|
281
|
-
llm_rerank: bool = False,
|
|
282
|
-
llm_client: Any | None = None,
|
|
283
|
-
prompt_manager: Any | None = None,
|
|
284
|
-
) -> list[Any]:
|
|
285
|
-
"""Apply rerank if any rerank flag is set and we have headroom.
|
|
286
|
-
|
|
287
|
-
Dispatch order: ``llm_rerank`` (if True and infra available) takes
|
|
288
|
-
precedence over ``rerank`` (cross-encoder). Both fall back to hybrid
|
|
289
|
-
order on any failure. ``llm_rerank`` further falls back to the
|
|
290
|
-
cross-encoder if the LLM call fails AND ``rerank`` was also requested.
|
|
291
|
-
|
|
292
|
-
Args:
|
|
293
|
-
hits: Candidates from the initial hybrid retrieval. Each must expose
|
|
294
|
-
a ``content`` attribute (profile, playbook, etc).
|
|
295
|
-
rerank: Cross-encoder toggle from the agent's tool args.
|
|
296
|
-
rerank_query: The string to score candidates against. The caller
|
|
297
|
-
decides whether this is the original query or a refinement
|
|
298
|
-
(e.g. ``args.refine_with or args.query``).
|
|
299
|
-
final_k: Number of hits to return after reranking.
|
|
300
|
-
llm_rerank: LLM-rerank toggle. When True (and ``llm_client`` and
|
|
301
|
-
``prompt_manager`` are wired in), the LLM judges relevance.
|
|
302
|
-
Use this for synonym/brand/category-knowledge gaps where the
|
|
303
|
-
cross-encoder misses semantic equivalents.
|
|
304
|
-
llm_client: A ``LiteLLMClient`` for the LLM rerank call. Required
|
|
305
|
-
when ``llm_rerank=True``.
|
|
306
|
-
prompt_manager: A ``PromptManager`` for rendering the rerank prompt.
|
|
307
|
-
Required when ``llm_rerank=True``.
|
|
308
|
-
|
|
309
|
-
Returns:
|
|
310
|
-
The re-ordered hits, capped at ``final_k``. If neither flag is set
|
|
311
|
-
or ``len(hits) <= final_k``, returns ``hits[:final_k]`` unchanged.
|
|
312
|
-
"""
|
|
313
|
-
if not (rerank or llm_rerank) or len(hits) <= final_k:
|
|
314
|
-
return hits[:final_k]
|
|
315
|
-
|
|
316
|
-
if llm_rerank:
|
|
317
|
-
ordered = _try_llm_rerank(
|
|
318
|
-
hits, rerank_query, final_k, llm_client, prompt_manager
|
|
319
|
-
)
|
|
320
|
-
if ordered is not None:
|
|
321
|
-
return ordered
|
|
322
|
-
# LLM rerank failed — fall through to cross-encoder ONLY if also requested.
|
|
323
|
-
if not rerank:
|
|
324
|
-
return hits[:final_k]
|
|
325
|
-
|
|
326
|
-
return _try_cross_encoder_rerank(hits, rerank_query, final_k)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def _try_llm_rerank(
|
|
330
|
-
hits: list[Any],
|
|
331
|
-
rerank_query: str,
|
|
332
|
-
final_k: int,
|
|
333
|
-
llm_client: Any | None,
|
|
334
|
-
prompt_manager: Any | None,
|
|
335
|
-
) -> list[Any] | None:
|
|
336
|
-
"""Run the LLM rerank stage; return ``None`` on any failure.
|
|
337
|
-
|
|
338
|
-
Args:
|
|
339
|
-
hits: Candidate hits to rerank.
|
|
340
|
-
rerank_query: Query to score against.
|
|
341
|
-
final_k: How many to return.
|
|
342
|
-
llm_client: ``LiteLLMClient`` instance, or ``None``.
|
|
343
|
-
prompt_manager: ``PromptManager`` instance, or ``None``.
|
|
344
|
-
|
|
345
|
-
Returns:
|
|
346
|
-
Re-ordered top-``final_k`` on success; ``None`` on failure so the
|
|
347
|
-
caller can chain to the cross-encoder fallback.
|
|
348
|
-
"""
|
|
349
|
-
try:
|
|
350
|
-
from reflexio.server.llm.rerank import score_pairs_llm
|
|
351
|
-
except ImportError:
|
|
352
|
-
return None
|
|
353
|
-
scores = score_pairs_llm(
|
|
354
|
-
rerank_query, [h.content for h in hits], llm_client, prompt_manager
|
|
355
|
-
)
|
|
356
|
-
if scores is None:
|
|
357
|
-
return None
|
|
358
|
-
ranked = sorted(
|
|
359
|
-
zip(hits, scores, strict=True), key=lambda pair: pair[1], reverse=True
|
|
360
|
-
)
|
|
361
|
-
return [h for h, _ in ranked[:final_k]]
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def _try_cross_encoder_rerank(
|
|
365
|
-
hits: list[Any], rerank_query: str, final_k: int
|
|
366
|
-
) -> list[Any]:
|
|
367
|
-
"""Run the cross-encoder rerank stage; fall back to hybrid order on failure.
|
|
368
|
-
|
|
369
|
-
Args:
|
|
370
|
-
hits: Candidate hits to rerank.
|
|
371
|
-
rerank_query: Query to score against.
|
|
372
|
-
final_k: How many to return.
|
|
373
|
-
|
|
374
|
-
Returns:
|
|
375
|
-
Re-ordered top-``final_k`` on success; ``hits[:final_k]`` on failure.
|
|
376
|
-
"""
|
|
377
|
-
try:
|
|
378
|
-
from reflexio.server.llm.rerank import score_pairs
|
|
379
|
-
|
|
380
|
-
scores = score_pairs(rerank_query, [h.content for h in hits])
|
|
381
|
-
ranked = sorted(
|
|
382
|
-
zip(hits, scores, strict=True), key=lambda pair: pair[1], reverse=True
|
|
383
|
-
)
|
|
384
|
-
return [h for h, _ in ranked[:final_k]]
|
|
385
|
-
except Exception: # noqa: BLE001 — fall back to hybrid order on failure
|
|
386
|
-
return hits[:final_k]
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def _project_profile_for_llm(p: Any) -> dict[str, Any]:
|
|
390
|
-
# ``session_id`` is the agent-facing name for the storage-internal
|
|
391
|
-
# ``generated_from_request_id``. Exposing it lets the agent chain
|
|
392
|
-
# ``search_user_profiles`` → ``read_session_text(session_ids=[...])``
|
|
393
|
-
# without needing a separate lookup. The internal field name stays as
|
|
394
|
-
# ``generated_from_request_id`` everywhere else (storage, schemas);
|
|
395
|
-
# the rename is purely a cognitive affordance for the LLM.
|
|
396
|
-
return {
|
|
397
|
-
"id": getattr(p, "profile_id", "") or "",
|
|
398
|
-
"content": p.content,
|
|
399
|
-
"session_id": getattr(p, "generated_from_request_id", "") or "",
|
|
400
|
-
"ttl": p.profile_time_to_live,
|
|
401
|
-
"last_modified": p.last_modified_timestamp,
|
|
402
|
-
"source_span": getattr(p, "source_span", None),
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
def _project_user_playbook_for_llm(pb: Any) -> dict[str, Any]:
|
|
407
|
-
return {
|
|
408
|
-
"id": str(pb.user_playbook_id),
|
|
409
|
-
"trigger": pb.trigger,
|
|
410
|
-
"content": pb.content,
|
|
411
|
-
"rationale": pb.rationale,
|
|
412
|
-
"last_modified": getattr(pb, "created_at", 0),
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
def _project_agent_playbook_for_llm(pb: Any) -> dict[str, Any]:
|
|
417
|
-
return {
|
|
418
|
-
"id": str(pb.agent_playbook_id),
|
|
419
|
-
"trigger": pb.trigger,
|
|
420
|
-
"content": pb.content,
|
|
421
|
-
"rationale": pb.rationale,
|
|
422
|
-
"playbook_status": getattr(pb, "playbook_status", None),
|
|
423
|
-
"last_modified": getattr(pb, "created_at", 0),
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
# ====================================================================
|
|
428
|
-
# Read handlers
|
|
429
|
-
# ====================================================================
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
def _handle_search_user_profiles(
|
|
433
|
-
args: SearchUserProfilesArgs,
|
|
434
|
-
storage: Any,
|
|
435
|
-
ctx: ExtractionCtx,
|
|
436
|
-
llm_client: Any | None = None,
|
|
437
|
-
prompt_manager: Any | None = None,
|
|
438
|
-
) -> dict[str, Any]:
|
|
439
|
-
"""Search the current user's profiles and bump search_count.
|
|
440
|
-
|
|
441
|
-
Two-stage retrieval: hybrid (BM25 + vector via RRF) over-fetches a wider
|
|
442
|
-
candidate pool, then an optional rerank scores ``(query, content)`` pairs
|
|
443
|
-
and returns the top ``args.top_k``. ``args.rerank`` selects the local
|
|
444
|
-
cross-encoder; ``args.llm_rerank`` selects an LLM relevance-judge that
|
|
445
|
-
has world knowledge for brand→category gaps the cross-encoder can't
|
|
446
|
-
bridge. ``llm_rerank`` wins when both are set.
|
|
447
|
-
|
|
448
|
-
The over-fetch + rerank pattern fixes the class of failures where the
|
|
449
|
-
bi-encoder ranks the right profile at #2-#15 by cosine but the top-1
|
|
450
|
-
is a near-duplicate or literal-keyword match that the answer LLM picks
|
|
451
|
-
first.
|
|
452
|
-
|
|
453
|
-
Args:
|
|
454
|
-
args (SearchUserProfilesArgs): Query, top_k, rerank toggles, refine_with.
|
|
455
|
-
storage (Any): BaseStorage instance.
|
|
456
|
-
ctx (ExtractionCtx): Per-run state; search_count incremented in place.
|
|
457
|
-
llm_client (Any | None): ``LiteLLMClient`` for ``llm_rerank=True``.
|
|
458
|
-
prompt_manager (Any | None): ``PromptManager`` for ``llm_rerank=True``.
|
|
459
|
-
|
|
460
|
-
Returns:
|
|
461
|
-
dict[str, Any]: ``{"hits": [...]}`` with LLM-facing profile projections.
|
|
462
|
-
"""
|
|
463
|
-
final_k = _cap_top_k(args.top_k)
|
|
464
|
-
fetch_k = _fetch_k_for_rerank(final_k, args.rerank, args.llm_rerank)
|
|
465
|
-
|
|
466
|
-
request = SearchUserProfileRequest(
|
|
467
|
-
query=args.query,
|
|
468
|
-
user_id=ctx.user_id,
|
|
469
|
-
top_k=fetch_k,
|
|
470
|
-
)
|
|
471
|
-
hits = storage.search_user_profile(
|
|
472
|
-
request,
|
|
473
|
-
query_embedding=_maybe_embed_query(storage, args.query),
|
|
474
|
-
)
|
|
475
|
-
ctx.search_count += 1
|
|
476
|
-
|
|
477
|
-
hits = _maybe_rerank_hits(
|
|
478
|
-
hits=hits,
|
|
479
|
-
rerank=args.rerank,
|
|
480
|
-
rerank_query=args.refine_with or args.query,
|
|
481
|
-
final_k=final_k,
|
|
482
|
-
llm_rerank=args.llm_rerank,
|
|
483
|
-
llm_client=llm_client,
|
|
484
|
-
prompt_manager=prompt_manager,
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
for h in hits:
|
|
488
|
-
pid = getattr(h, "profile_id", "") or ""
|
|
489
|
-
if pid:
|
|
490
|
-
ctx.known_ids.add(pid)
|
|
491
|
-
return {"hits": [_project_profile_for_llm(h) for h in hits]}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
def _handle_get_user_profile(
|
|
495
|
-
args: GetUserProfileArgs, storage: Any, ctx: ExtractionCtx
|
|
496
|
-
) -> dict[str, Any]:
|
|
497
|
-
"""Retrieve a single UserProfile by id without bumping search_count.
|
|
498
|
-
|
|
499
|
-
Args:
|
|
500
|
-
args (GetUserProfileArgs): Profile id to look up.
|
|
501
|
-
storage (Any): BaseStorage instance.
|
|
502
|
-
ctx (ExtractionCtx): Per-run state; known_ids updated on hit.
|
|
503
|
-
|
|
504
|
-
Returns:
|
|
505
|
-
dict[str, Any]: ``{"profile": {...}}`` on hit, ``{"error": "not found"}`` on miss.
|
|
506
|
-
"""
|
|
507
|
-
all_profiles = storage.get_user_profile(ctx.user_id)
|
|
508
|
-
for p in all_profiles:
|
|
509
|
-
if (getattr(p, "profile_id", "") or "") == args.id:
|
|
510
|
-
ctx.known_ids.add(args.id)
|
|
511
|
-
return {"profile": _project_profile_for_llm(p)}
|
|
512
|
-
return {"error": "not found"}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
def _handle_search_user_playbooks(
|
|
516
|
-
args: SearchUserPlaybooksArgs,
|
|
517
|
-
storage: Any,
|
|
518
|
-
ctx: ExtractionCtx,
|
|
519
|
-
llm_client: Any | None = None,
|
|
520
|
-
prompt_manager: Any | None = None,
|
|
521
|
-
) -> dict[str, Any]:
|
|
522
|
-
"""Search the current user's playbooks and bump search_count.
|
|
523
|
-
|
|
524
|
-
Args:
|
|
525
|
-
args (SearchUserPlaybooksArgs): Query, top_k, status filter, rerank toggles.
|
|
526
|
-
storage (Any): BaseStorage instance.
|
|
527
|
-
ctx (ExtractionCtx): Per-run state; search_count and known_ids updated.
|
|
528
|
-
llm_client (Any | None): ``LiteLLMClient`` for ``llm_rerank=True``.
|
|
529
|
-
prompt_manager (Any | None): ``PromptManager`` for ``llm_rerank=True``.
|
|
530
|
-
|
|
531
|
-
Returns:
|
|
532
|
-
dict[str, Any]: ``{"hits": [...]}`` with LLM-facing playbook projections.
|
|
533
|
-
"""
|
|
534
|
-
final_k = _cap_top_k(args.top_k)
|
|
535
|
-
fetch_k = _fetch_k_for_rerank(final_k, args.rerank, args.llm_rerank)
|
|
536
|
-
request = SearchUserPlaybookRequest(
|
|
537
|
-
query=args.query,
|
|
538
|
-
user_id=ctx.user_id,
|
|
539
|
-
agent_version=ctx.agent_version,
|
|
540
|
-
top_k=fetch_k,
|
|
541
|
-
status_filter=[_status_from_str(args.status)],
|
|
542
|
-
search_mode=SearchMode.HYBRID,
|
|
543
|
-
threshold=0.4,
|
|
544
|
-
)
|
|
545
|
-
hits = storage.search_user_playbooks(
|
|
546
|
-
request,
|
|
547
|
-
options=SearchOptions(query_embedding=_maybe_embed_query(storage, args.query)),
|
|
548
|
-
)
|
|
549
|
-
ctx.search_count += 1
|
|
550
|
-
hits = _maybe_rerank_hits(
|
|
551
|
-
hits=hits,
|
|
552
|
-
rerank=args.rerank,
|
|
553
|
-
rerank_query=args.refine_with or args.query,
|
|
554
|
-
final_k=final_k,
|
|
555
|
-
llm_rerank=args.llm_rerank,
|
|
556
|
-
llm_client=llm_client,
|
|
557
|
-
prompt_manager=prompt_manager,
|
|
558
|
-
)
|
|
559
|
-
for h in hits:
|
|
560
|
-
ctx.known_ids.add(str(h.user_playbook_id))
|
|
561
|
-
return {"hits": [_project_user_playbook_for_llm(h) for h in hits]}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
def _handle_get_user_playbook(
|
|
565
|
-
args: GetUserPlaybookArgs, storage: Any, ctx: ExtractionCtx
|
|
566
|
-
) -> dict[str, Any]:
|
|
567
|
-
"""Retrieve a single UserPlaybook by id without bumping search_count.
|
|
568
|
-
|
|
569
|
-
Args:
|
|
570
|
-
args (GetUserPlaybookArgs): Playbook id to look up.
|
|
571
|
-
storage (Any): BaseStorage instance.
|
|
572
|
-
ctx (ExtractionCtx): Per-run state; known_ids updated on hit.
|
|
573
|
-
|
|
574
|
-
Returns:
|
|
575
|
-
dict[str, Any]: ``{"playbook": {...}}`` on hit, ``{"error": "not found"}`` on miss.
|
|
576
|
-
"""
|
|
577
|
-
candidates = storage.get_user_playbooks(
|
|
578
|
-
user_id=ctx.user_id, agent_version=ctx.agent_version
|
|
579
|
-
)
|
|
580
|
-
for pb in candidates:
|
|
581
|
-
if str(pb.user_playbook_id) == args.id:
|
|
582
|
-
ctx.known_ids.add(args.id)
|
|
583
|
-
return {"playbook": _project_user_playbook_for_llm(pb)}
|
|
584
|
-
return {"error": "not found"}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def _handle_search_agent_playbooks(
|
|
588
|
-
args: SearchAgentPlaybooksArgs,
|
|
589
|
-
storage: Any,
|
|
590
|
-
ctx: ExtractionCtx,
|
|
591
|
-
llm_client: Any | None = None,
|
|
592
|
-
prompt_manager: Any | None = None,
|
|
593
|
-
) -> dict[str, Any]:
|
|
594
|
-
"""Search agent-version-scoped playbooks and bump search_count.
|
|
595
|
-
|
|
596
|
-
Args:
|
|
597
|
-
args (SearchAgentPlaybooksArgs): Query, top_k, status filter, rerank toggles.
|
|
598
|
-
storage (Any): BaseStorage instance.
|
|
599
|
-
ctx (ExtractionCtx): Per-run state; search_count and known_ids updated.
|
|
600
|
-
llm_client (Any | None): ``LiteLLMClient`` for ``llm_rerank=True``.
|
|
601
|
-
prompt_manager (Any | None): ``PromptManager`` for ``llm_rerank=True``.
|
|
602
|
-
|
|
603
|
-
Returns:
|
|
604
|
-
dict[str, Any]: ``{"hits": [...]}`` with LLM-facing agent playbook projections.
|
|
605
|
-
"""
|
|
606
|
-
final_k = _cap_top_k(args.top_k)
|
|
607
|
-
fetch_k = _fetch_k_for_rerank(final_k, args.rerank, args.llm_rerank)
|
|
608
|
-
options = SearchOptions(query_embedding=_maybe_embed_query(storage, args.query))
|
|
609
|
-
hits: list[Any] = []
|
|
610
|
-
seen_ids: set[str] = set()
|
|
611
|
-
for playbook_status in _agent_playbook_statuses_from_ctx(ctx):
|
|
612
|
-
request = SearchAgentPlaybookRequest(
|
|
613
|
-
query=args.query,
|
|
614
|
-
agent_version=ctx.agent_version,
|
|
615
|
-
top_k=fetch_k,
|
|
616
|
-
status_filter=[_status_from_str(args.status)],
|
|
617
|
-
playbook_status_filter=playbook_status,
|
|
618
|
-
search_mode=SearchMode.HYBRID,
|
|
619
|
-
threshold=0.4,
|
|
620
|
-
)
|
|
621
|
-
for hit in storage.search_agent_playbooks(request, options=options):
|
|
622
|
-
hit_id = str(getattr(hit, "agent_playbook_id", ""))
|
|
623
|
-
if hit_id and hit_id not in seen_ids:
|
|
624
|
-
seen_ids.add(hit_id)
|
|
625
|
-
hits.append(hit)
|
|
626
|
-
if len(hits) >= fetch_k:
|
|
627
|
-
break
|
|
628
|
-
if len(hits) >= fetch_k:
|
|
629
|
-
break
|
|
630
|
-
ctx.search_count += 1
|
|
631
|
-
hits = _maybe_rerank_hits(
|
|
632
|
-
hits=hits,
|
|
633
|
-
rerank=args.rerank,
|
|
634
|
-
rerank_query=args.refine_with or args.query,
|
|
635
|
-
final_k=final_k,
|
|
636
|
-
llm_rerank=args.llm_rerank,
|
|
637
|
-
llm_client=llm_client,
|
|
638
|
-
prompt_manager=prompt_manager,
|
|
639
|
-
)
|
|
640
|
-
for h in hits:
|
|
641
|
-
ctx.known_ids.add(str(h.agent_playbook_id))
|
|
642
|
-
return {"hits": [_project_agent_playbook_for_llm(h) for h in hits]}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
def _handle_get_agent_playbook(
|
|
646
|
-
args: GetAgentPlaybookArgs, storage: Any, ctx: ExtractionCtx
|
|
647
|
-
) -> dict[str, Any]:
|
|
648
|
-
"""Retrieve a single AgentPlaybook by id without bumping search_count.
|
|
649
|
-
|
|
650
|
-
Args:
|
|
651
|
-
args (GetAgentPlaybookArgs): Agent playbook id to look up.
|
|
652
|
-
storage (Any): BaseStorage instance.
|
|
653
|
-
ctx (ExtractionCtx): Per-run state; known_ids updated on hit.
|
|
654
|
-
|
|
655
|
-
Returns:
|
|
656
|
-
dict[str, Any]: ``{"playbook": {...}}`` on hit, ``{"error": "not found"}`` on miss.
|
|
657
|
-
"""
|
|
658
|
-
candidates = storage.get_agent_playbooks(agent_version=ctx.agent_version)
|
|
659
|
-
for pb in candidates:
|
|
660
|
-
if str(pb.agent_playbook_id) == args.id:
|
|
661
|
-
ctx.known_ids.add(args.id)
|
|
662
|
-
return {"playbook": _project_agent_playbook_for_llm(pb)}
|
|
663
|
-
return {"error": "not found"}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
_COMPRESS_PROMPT_ID = "compress_session_for_query"
|
|
667
|
-
# 5s was far too aggressive: under 10-worker benchmark concurrency every
|
|
668
|
-
# gpt-5-mini compression call timed out, silently degrading every
|
|
669
|
-
# rehydration to raw turns. 30s gives gpt-5-mini room under load while
|
|
670
|
-
# still failing fast on genuine model outages (the raw-turns fallback
|
|
671
|
-
# kicks in beyond that).
|
|
672
|
-
_COMPRESS_LLM_TIMEOUT = 30
|
|
673
|
-
_COMPRESS_LLM_MAX_RETRIES = 1
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
def _format_raw_turns(
|
|
677
|
-
session_ids: list[str],
|
|
678
|
-
by_session: dict[str, list[Any]],
|
|
679
|
-
max_chars_per_session: int,
|
|
680
|
-
) -> str:
|
|
681
|
-
"""Format role-prefixed raw turns with per-session caps.
|
|
682
|
-
|
|
683
|
-
Used both as the compressor's input and as the fallback output when
|
|
684
|
-
compression is unavailable or fails.
|
|
685
|
-
|
|
686
|
-
Args:
|
|
687
|
-
session_ids (list[str]): Session ids in the order requested by the agent.
|
|
688
|
-
by_session (dict[str, list[Any]]): Interactions grouped by session id.
|
|
689
|
-
max_chars_per_session (int): Per-session truncation cap (chars).
|
|
690
|
-
|
|
691
|
-
Returns:
|
|
692
|
-
str: Concatenated ``=== session <id> ===\\n[role] content`` blocks.
|
|
693
|
-
"""
|
|
694
|
-
blocks: list[str] = []
|
|
695
|
-
for sid in session_ids:
|
|
696
|
-
sess_interactions = by_session.get(sid, [])
|
|
697
|
-
if not sess_interactions:
|
|
698
|
-
blocks.append(f"=== session {sid} ===\n(no interactions found)")
|
|
699
|
-
continue
|
|
700
|
-
# Derive the session date from the earliest interaction timestamp.
|
|
701
|
-
# Including this in the header lets the downstream answer LLM resolve
|
|
702
|
-
# relative-time phrases ("yesterday", "last week", "X days ago") in
|
|
703
|
-
# the raw turns against an absolute anchor — without it, the LLM ends
|
|
704
|
-
# up quoting the relative phrase verbatim instead of computing the
|
|
705
|
-
# date (the multi-hop temporal failure pattern).
|
|
706
|
-
timestamps: list[float] = []
|
|
707
|
-
for interaction in sess_interactions:
|
|
708
|
-
created_at = getattr(interaction, "created_at", None)
|
|
709
|
-
if isinstance(created_at, datetime):
|
|
710
|
-
timestamps.append(created_at.timestamp())
|
|
711
|
-
elif isinstance(created_at, int | float):
|
|
712
|
-
timestamps.append(float(created_at))
|
|
713
|
-
date_suffix = ""
|
|
714
|
-
if timestamps:
|
|
715
|
-
try:
|
|
716
|
-
date_iso = datetime.fromtimestamp(min(timestamps), tz=UTC).strftime(
|
|
717
|
-
"%Y-%m-%d"
|
|
718
|
-
)
|
|
719
|
-
date_suffix = f" (date: {date_iso})"
|
|
720
|
-
except (OverflowError, OSError, ValueError):
|
|
721
|
-
pass
|
|
722
|
-
lines = [
|
|
723
|
-
f"[{getattr(i, 'role', '?')}] {i.content}"
|
|
724
|
-
for i in sess_interactions
|
|
725
|
-
if getattr(i, "content", None)
|
|
726
|
-
]
|
|
727
|
-
body = "\n".join(lines)
|
|
728
|
-
if len(body) > max_chars_per_session:
|
|
729
|
-
body = body[:max_chars_per_session] + "…"
|
|
730
|
-
blocks.append(f"=== session {sid}{date_suffix} ===\n{body}")
|
|
731
|
-
return "\n\n".join(blocks)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
def _compress_raw_turns(
|
|
735
|
-
raw_turns: str,
|
|
736
|
-
query: str,
|
|
737
|
-
llm_client: Any,
|
|
738
|
-
prompt_manager: Any,
|
|
739
|
-
) -> str | None:
|
|
740
|
-
"""Run the compress_session_for_query prompt against raw_turns + query.
|
|
741
|
-
|
|
742
|
-
Returns the compressed text on success, or ``None`` on any failure
|
|
743
|
-
(timeout, exception, empty/whitespace output). Caller falls back to
|
|
744
|
-
raw_turns when ``None`` is returned.
|
|
745
|
-
|
|
746
|
-
Args:
|
|
747
|
-
raw_turns (str): The role-prefixed raw transcript (output of
|
|
748
|
-
``_format_raw_turns``).
|
|
749
|
-
query (str): The query the compressor scores against.
|
|
750
|
-
llm_client (Any): LiteLLMClient with a ``generate_response`` method.
|
|
751
|
-
prompt_manager (Any): PromptManager with a ``render_prompt`` method.
|
|
752
|
-
|
|
753
|
-
Returns:
|
|
754
|
-
str | None: Compressed transcript, or None to indicate fallback.
|
|
755
|
-
"""
|
|
756
|
-
# Diagnostic: hard-disable compression to measure its net impact vs raw
|
|
757
|
-
# turns. r60 measured working v1.2.0 cost ~4pp; r65 confirmed v1.3.0
|
|
758
|
-
# near-pass-through still cost. Returning None here forces the raw-turns
|
|
759
|
-
# fallback path — same behavior as the original 5s-timeout era at r45.
|
|
760
|
-
return None
|
|
761
|
-
|
|
762
|
-
try: # noqa pragma: no cover — disabled compression path
|
|
763
|
-
prompt = prompt_manager.render_prompt(
|
|
764
|
-
_COMPRESS_PROMPT_ID,
|
|
765
|
-
variables={"query": query, "raw_turns": raw_turns},
|
|
766
|
-
)
|
|
767
|
-
result = llm_client.generate_response(
|
|
768
|
-
prompt,
|
|
769
|
-
timeout=_COMPRESS_LLM_TIMEOUT,
|
|
770
|
-
max_retries=_COMPRESS_LLM_MAX_RETRIES,
|
|
771
|
-
)
|
|
772
|
-
except Exception as e: # noqa: BLE001 — broad: LLM stack raises diverse exception types
|
|
773
|
-
logger.warning(
|
|
774
|
-
"read_session_text compression failed, using raw fallback: %s", e
|
|
775
|
-
)
|
|
776
|
-
return None
|
|
777
|
-
|
|
778
|
-
if not isinstance(result, str) or not result.strip():
|
|
779
|
-
logger.warning(
|
|
780
|
-
"read_session_text compression returned empty, using raw fallback"
|
|
781
|
-
)
|
|
782
|
-
return None
|
|
783
|
-
return result.strip()
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
def _handle_read_session_text(
|
|
787
|
-
args: ReadSessionTextArgs,
|
|
788
|
-
storage: Any,
|
|
789
|
-
ctx: ExtractionCtx,
|
|
790
|
-
llm_client: Any = None,
|
|
791
|
-
prompt_manager: Any = None,
|
|
792
|
-
) -> dict[str, Any]:
|
|
793
|
-
"""Return a denoised excerpt of one or more sessions, scored against ``query``.
|
|
794
|
-
|
|
795
|
-
Fetches raw interactions for ``session_ids`` and — when ``query`` is
|
|
796
|
-
non-empty and the compression layer is wired — runs an in-tool LLM call
|
|
797
|
-
that compresses the raw turns into the smallest excerpt preserving every
|
|
798
|
-
operand relevant to the query. Falls back to role-prefixed raw turns
|
|
799
|
-
(truncated at ``max_chars_per_session``) on:
|
|
800
|
-
|
|
801
|
-
- empty ``query``
|
|
802
|
-
- no ``llm_client`` / ``prompt_manager`` available (test paths or
|
|
803
|
-
backends that don't pass them through ``HandlerBundle``)
|
|
804
|
-
- any compression-LLM exception
|
|
805
|
-
- empty/whitespace compression output
|
|
806
|
-
|
|
807
|
-
Args:
|
|
808
|
-
args (ReadSessionTextArgs): Session ids (1–4), query, and per-session char cap.
|
|
809
|
-
storage (Any): BaseStorage instance; must implement
|
|
810
|
-
``get_interactions_by_request_ids(request_ids: list[str])``.
|
|
811
|
-
ctx (ExtractionCtx): Per-run state (unused for reads, present for consistency).
|
|
812
|
-
llm_client (Any | None): LiteLLMClient for compression. Defaults to None
|
|
813
|
-
so callers that don't enable compression still get raw turns.
|
|
814
|
-
prompt_manager (Any | None): PromptManager for rendering the compression
|
|
815
|
-
prompt. Defaults to None.
|
|
816
|
-
|
|
817
|
-
Returns:
|
|
818
|
-
dict[str, Any]: ``{"text": str}`` with compressed-or-raw session text,
|
|
819
|
-
or ``{"error": str}`` when the storage backend doesn't support batch
|
|
820
|
-
fetch or no interactions exist for any of the requested sessions.
|
|
821
|
-
"""
|
|
822
|
-
try:
|
|
823
|
-
interactions = storage.get_interactions_by_request_ids(args.session_ids)
|
|
824
|
-
except AttributeError:
|
|
825
|
-
return {"error": "read_session_text requires get_interactions_by_request_ids"}
|
|
826
|
-
if not interactions:
|
|
827
|
-
return {"error": "no interactions found for any of the requested sessions"}
|
|
828
|
-
|
|
829
|
-
by_session: dict[str, list[Any]] = {}
|
|
830
|
-
for i in interactions:
|
|
831
|
-
sid = getattr(i, "request_id", None)
|
|
832
|
-
if sid is None:
|
|
833
|
-
continue
|
|
834
|
-
by_session.setdefault(sid, []).append(i)
|
|
835
|
-
|
|
836
|
-
# Clamp agent-supplied max_chars_per_session to a 16000 floor: agents
|
|
837
|
-
# have been observed to pick values as low as 3000-4000 inconsistently,
|
|
838
|
-
# which truncates Pattern G (recall-prior-statement) sessions before the
|
|
839
|
-
# answer turn appears. The handler enforces the floor regardless of the
|
|
840
|
-
# arg.
|
|
841
|
-
effective_max_chars = max(args.max_chars_per_session, 16000)
|
|
842
|
-
raw_text = _format_raw_turns(args.session_ids, by_session, effective_max_chars)
|
|
843
|
-
|
|
844
|
-
# Skip compression on empty query or missing wiring; raw turns are still useful.
|
|
845
|
-
if not args.query.strip() or llm_client is None or prompt_manager is None:
|
|
846
|
-
ctx.rehydrated_excerpts.append(raw_text)
|
|
847
|
-
return {"text": raw_text}
|
|
848
|
-
|
|
849
|
-
compressed = _compress_raw_turns(raw_text, args.query, llm_client, prompt_manager)
|
|
850
|
-
final_text = raw_text if compressed is None else compressed
|
|
851
|
-
ctx.rehydrated_excerpts.append(final_text)
|
|
852
|
-
return {"text": final_text}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
def _handle_rerank_user_profiles(
|
|
856
|
-
args: RerankUserProfilesArgs, storage: Any, ctx: ExtractionCtx
|
|
857
|
-
) -> dict[str, Any]:
|
|
858
|
-
"""Rerank known profile ids with a local cross-encoder.
|
|
859
|
-
|
|
860
|
-
Fetches the candidate profiles (scoped to ``ctx.user_id``), scores
|
|
861
|
-
``(query, content)`` pairs, and returns the top_k by descending score.
|
|
862
|
-
Bumps ``search_count`` so reranking still counts against the search
|
|
863
|
-
budget enforced by invariant A.
|
|
864
|
-
|
|
865
|
-
Args:
|
|
866
|
-
args (RerankUserProfilesArgs): Query, candidate ids, and top_k.
|
|
867
|
-
storage (Any): BaseStorage instance.
|
|
868
|
-
ctx (ExtractionCtx): Per-run state; ``search_count`` and
|
|
869
|
-
``known_ids`` updated in place.
|
|
870
|
-
|
|
871
|
-
Returns:
|
|
872
|
-
dict[str, Any]: ``{"hits": [...]}`` with LLM-facing profile
|
|
873
|
-
projections sorted by descending relevance.
|
|
874
|
-
"""
|
|
875
|
-
if not args.profile_ids:
|
|
876
|
-
ctx.search_count += 1
|
|
877
|
-
return {"hits": []}
|
|
878
|
-
all_profiles = storage.get_user_profile(ctx.user_id)
|
|
879
|
-
wanted = set(args.profile_ids)
|
|
880
|
-
candidates = [
|
|
881
|
-
p for p in all_profiles if (getattr(p, "profile_id", "") or "") in wanted
|
|
882
|
-
]
|
|
883
|
-
ctx.search_count += 1
|
|
884
|
-
if not candidates:
|
|
885
|
-
return {"hits": []}
|
|
886
|
-
# Lazy import — keeps unit-test collection fast and avoids loading
|
|
887
|
-
# torch when no rerank tool call is made in a given run.
|
|
888
|
-
from reflexio.server.llm.rerank import score_pairs
|
|
889
|
-
|
|
890
|
-
scores = score_pairs(args.query, [p.content for p in candidates])
|
|
891
|
-
ranked = sorted(
|
|
892
|
-
zip(candidates, scores, strict=True),
|
|
893
|
-
key=lambda pair: pair[1],
|
|
894
|
-
reverse=True,
|
|
895
|
-
)
|
|
896
|
-
top = [profile for profile, _score in ranked[: _cap_top_k(args.top_k)]]
|
|
897
|
-
for h in top:
|
|
898
|
-
pid = getattr(h, "profile_id", "") or ""
|
|
899
|
-
if pid:
|
|
900
|
-
ctx.known_ids.add(pid)
|
|
901
|
-
return {"hits": [_project_profile_for_llm(h) for h in top]}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
def _handle_storage_stats(
|
|
905
|
-
args: StorageStatsArgs, # noqa: ARG001
|
|
906
|
-
storage: Any,
|
|
907
|
-
ctx: ExtractionCtx,
|
|
908
|
-
) -> dict[str, Any]:
|
|
909
|
-
"""Return profile/playbook counts and modified-time range for ``ctx.user_id``.
|
|
910
|
-
|
|
911
|
-
Does not bump ``search_count`` — this is metadata, not retrieval.
|
|
912
|
-
|
|
913
|
-
Args:
|
|
914
|
-
args (StorageStatsArgs): No fields (sentinel call).
|
|
915
|
-
storage (Any): BaseStorage instance.
|
|
916
|
-
ctx (ExtractionCtx): Per-run state; only ``user_id`` is read.
|
|
917
|
-
|
|
918
|
-
Returns:
|
|
919
|
-
dict[str, Any]: Counts and ISO 8601 timestamps. Timestamps are
|
|
920
|
-
``None`` when the user has no profiles.
|
|
921
|
-
"""
|
|
922
|
-
profiles = storage.get_user_profile(ctx.user_id)
|
|
923
|
-
if profiles:
|
|
924
|
-
timestamps = [p.last_modified_timestamp for p in profiles]
|
|
925
|
-
oldest_ts = datetime.fromtimestamp(min(timestamps), tz=UTC).isoformat()
|
|
926
|
-
newest_ts = datetime.fromtimestamp(max(timestamps), tz=UTC).isoformat()
|
|
927
|
-
else:
|
|
928
|
-
oldest_ts = None
|
|
929
|
-
newest_ts = None
|
|
930
|
-
playbook_count = storage.count_user_playbooks(user_id=ctx.user_id)
|
|
931
|
-
return {
|
|
932
|
-
"profile_count": len(profiles),
|
|
933
|
-
"playbook_count": playbook_count,
|
|
934
|
-
"oldest_profile_modified": oldest_ts,
|
|
935
|
-
"newest_profile_modified": newest_ts,
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
def new_profile_id() -> str:
|
|
940
|
-
"""Generate a short (12-char hex) profile id.
|
|
941
|
-
|
|
942
|
-
Format chosen for LLM tool-call reliability: full ``str(uuid.uuid4())``
|
|
943
|
-
is 36 characters of hex+dashes, error-prone for smaller LLMs to copy
|
|
944
|
-
verbatim from a search result back into a delete/update tool arg.
|
|
945
|
-
Twelve hex chars is short enough for high-fidelity copy and long enough
|
|
946
|
-
that birthday-paradox collision probability is vanishingly small at any
|
|
947
|
-
realistic per-user scale (16^12 ≈ 2.8e14 unique values; PRIMARY KEY
|
|
948
|
-
constraint catches the rare collision).
|
|
949
|
-
|
|
950
|
-
Profile ids are LLM-facing because the agent receives them in
|
|
951
|
-
``search_user_profiles`` results and must echo them back when calling
|
|
952
|
-
``delete_user_profile`` / ``update_user_profile``. Playbook ids are
|
|
953
|
-
INTEGER autoincrements and don't have this problem.
|
|
954
|
-
|
|
955
|
-
Returns:
|
|
956
|
-
str: 12 lowercase hex characters, e.g. ``"b8a3f74e2c91"``.
|
|
957
|
-
"""
|
|
958
|
-
return uuid.uuid4().hex[:12]
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
# ====================================================================
|
|
962
|
-
# Commit-stage: apply a PlanOp to storage
|
|
963
|
-
# ====================================================================
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
def apply_plan_op(op: Any, storage: Any, ctx: ExtractionCtx) -> None:
|
|
967
|
-
"""Deterministically apply one PlanOp to storage. Called by commit_plan.
|
|
968
|
-
|
|
969
|
-
Args:
|
|
970
|
-
op (Any): A PlanOp variant (CreateUserProfileOp, DeleteUserProfileOp,
|
|
971
|
-
CreateUserPlaybookOp, DeleteUserPlaybookOp).
|
|
972
|
-
storage (Any): BaseStorage handle.
|
|
973
|
-
ctx (ExtractionCtx): Per-run state providing user_id and agent_version.
|
|
974
|
-
|
|
975
|
-
Raises:
|
|
976
|
-
TypeError: If ``op`` is not a recognised PlanOp type.
|
|
977
|
-
"""
|
|
978
|
-
if isinstance(op, CreateUserProfileOp):
|
|
979
|
-
now_ts = int(datetime.now(UTC).timestamp())
|
|
980
|
-
ttl = ProfileTimeToLive(op.ttl)
|
|
981
|
-
storage.add_user_profile(
|
|
982
|
-
ctx.user_id,
|
|
983
|
-
[
|
|
984
|
-
UserProfile(
|
|
985
|
-
user_id=ctx.user_id,
|
|
986
|
-
profile_id=new_profile_id(),
|
|
987
|
-
content=op.content,
|
|
988
|
-
profile_time_to_live=ttl,
|
|
989
|
-
last_modified_timestamp=now_ts,
|
|
990
|
-
expiration_timestamp=calculate_expiration_timestamp(now_ts, ttl),
|
|
991
|
-
source="agentic_v2",
|
|
992
|
-
source_span=op.source_span,
|
|
993
|
-
generated_from_request_id=ctx.request_id,
|
|
994
|
-
)
|
|
995
|
-
],
|
|
996
|
-
)
|
|
997
|
-
elif isinstance(op, DeleteUserProfileOp):
|
|
998
|
-
storage.delete_profiles_by_ids([op.id])
|
|
999
|
-
elif isinstance(op, CreateUserPlaybookOp):
|
|
1000
|
-
new_playbook = UserPlaybook(
|
|
1001
|
-
user_playbook_id=0, # storage assigns
|
|
1002
|
-
user_id=ctx.user_id,
|
|
1003
|
-
agent_version=ctx.agent_version,
|
|
1004
|
-
request_id=ctx.request_id,
|
|
1005
|
-
playbook_name=SINGLETON_USER_PLAYBOOK_NAME,
|
|
1006
|
-
content=op.content,
|
|
1007
|
-
trigger=op.trigger,
|
|
1008
|
-
rationale=op.rationale,
|
|
1009
|
-
source_span=op.source_span,
|
|
1010
|
-
)
|
|
1011
|
-
storage.save_user_playbooks([new_playbook])
|
|
1012
|
-
elif isinstance(op, DeleteUserPlaybookOp):
|
|
1013
|
-
try:
|
|
1014
|
-
playbook_id = int(op.id)
|
|
1015
|
-
except (TypeError, ValueError) as e:
|
|
1016
|
-
raise TypeError(
|
|
1017
|
-
f"DeleteUserPlaybookOp.id must be a numeric string, got {op.id!r}"
|
|
1018
|
-
) from e
|
|
1019
|
-
storage.delete_user_playbooks_by_ids([playbook_id])
|
|
1020
|
-
else:
|
|
1021
|
-
raise TypeError(f"Unknown PlanOp: {type(op).__name__}")
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
# ====================================================================
|
|
1025
|
-
# Bundle adapter + Tool registries
|
|
1026
|
-
# ====================================================================
|
|
1027
|
-
|
|
1028
|
-
from collections.abc import Callable # noqa: E402
|
|
1029
|
-
|
|
1030
|
-
from reflexio.server.llm.tools import Tool # noqa: E402
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
def _bundle_handler(
|
|
1034
|
-
inner: Callable[[Any, Any, Any], dict[str, Any]],
|
|
1035
|
-
) -> Callable[[Any, Any], dict[str, Any]]:
|
|
1036
|
-
"""Adapt a (args, storage, ctx)-style handler to (args, bundle) for run_tool_loop.
|
|
1037
|
-
|
|
1038
|
-
Callers build a HandlerBundle with .storage and .ctx attributes; this
|
|
1039
|
-
adapter unpacks them so the registry accepts our 3-arg handlers.
|
|
1040
|
-
|
|
1041
|
-
Args:
|
|
1042
|
-
inner (Callable[[Any, Any, Any], dict[str, Any]]): A handler callable
|
|
1043
|
-
with signature ``(args, storage, ctx) -> dict``.
|
|
1044
|
-
|
|
1045
|
-
Returns:
|
|
1046
|
-
Callable[[Any, Any], dict[str, Any]]: A 2-arg callable
|
|
1047
|
-
``(args, bundle) -> dict`` compatible with ``Tool.handler``.
|
|
1048
|
-
"""
|
|
1049
|
-
|
|
1050
|
-
def wrapped(args: Any, bundle: Any) -> dict[str, Any]:
|
|
1051
|
-
return inner(args, bundle.storage, bundle.ctx)
|
|
1052
|
-
|
|
1053
|
-
return wrapped
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
def _bundle_handler_with_llm(
|
|
1057
|
-
inner: Callable[..., dict[str, Any]],
|
|
1058
|
-
) -> Callable[[Any, Any], dict[str, Any]]:
|
|
1059
|
-
"""Adapter variant for handlers that also need ``llm_client`` and
|
|
1060
|
-
``prompt_manager`` from the bundle (e.g., the compression-enabled
|
|
1061
|
-
rehydration tool).
|
|
1062
|
-
|
|
1063
|
-
The inner handler signature is
|
|
1064
|
-
``(args, storage, ctx, llm_client=None, prompt_manager=None) -> dict``.
|
|
1065
|
-
Both LLM-side fields default to ``None`` so the handler degrades to a
|
|
1066
|
-
no-LLM path when callers don't wire them through.
|
|
1067
|
-
|
|
1068
|
-
Args:
|
|
1069
|
-
inner (Callable[..., dict[str, Any]]): Handler accepting
|
|
1070
|
-
``(args, storage, ctx, llm_client, prompt_manager)``.
|
|
1071
|
-
|
|
1072
|
-
Returns:
|
|
1073
|
-
Callable[[Any, Any], dict[str, Any]]: A 2-arg callable compatible
|
|
1074
|
-
with ``Tool.handler``.
|
|
1075
|
-
"""
|
|
1076
|
-
|
|
1077
|
-
def wrapped(args: Any, bundle: Any) -> dict[str, Any]:
|
|
1078
|
-
return inner(
|
|
1079
|
-
args,
|
|
1080
|
-
bundle.storage,
|
|
1081
|
-
bundle.ctx,
|
|
1082
|
-
llm_client=getattr(bundle, "llm_client", None),
|
|
1083
|
-
prompt_manager=getattr(bundle, "prompt_manager", None),
|
|
1084
|
-
)
|
|
1085
|
-
|
|
1086
|
-
return wrapped
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
_READ_TOOLS = [
|
|
1090
|
-
Tool(
|
|
1091
|
-
name="search_user_profiles",
|
|
1092
|
-
args_model=SearchUserProfilesArgs,
|
|
1093
|
-
handler=_bundle_handler_with_llm(_handle_search_user_profiles),
|
|
1094
|
-
),
|
|
1095
|
-
Tool(
|
|
1096
|
-
name="get_user_profile",
|
|
1097
|
-
args_model=GetUserProfileArgs,
|
|
1098
|
-
handler=_bundle_handler(_handle_get_user_profile),
|
|
1099
|
-
),
|
|
1100
|
-
Tool(
|
|
1101
|
-
name="search_user_playbooks",
|
|
1102
|
-
args_model=SearchUserPlaybooksArgs,
|
|
1103
|
-
handler=_bundle_handler_with_llm(_handle_search_user_playbooks),
|
|
1104
|
-
),
|
|
1105
|
-
Tool(
|
|
1106
|
-
name="get_user_playbook",
|
|
1107
|
-
args_model=GetUserPlaybookArgs,
|
|
1108
|
-
handler=_bundle_handler(_handle_get_user_playbook),
|
|
1109
|
-
),
|
|
1110
|
-
Tool(
|
|
1111
|
-
name="search_agent_playbooks",
|
|
1112
|
-
args_model=SearchAgentPlaybooksArgs,
|
|
1113
|
-
handler=_bundle_handler_with_llm(_handle_search_agent_playbooks),
|
|
1114
|
-
),
|
|
1115
|
-
Tool(
|
|
1116
|
-
name="get_agent_playbook",
|
|
1117
|
-
args_model=GetAgentPlaybookArgs,
|
|
1118
|
-
handler=_bundle_handler(_handle_get_agent_playbook),
|
|
1119
|
-
),
|
|
1120
|
-
Tool(
|
|
1121
|
-
name="read_session_text",
|
|
1122
|
-
args_model=ReadSessionTextArgs,
|
|
1123
|
-
handler=_bundle_handler_with_llm(_handle_read_session_text),
|
|
1124
|
-
),
|
|
1125
|
-
]
|