claude-smart 0.2.42 → 0.2.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +1 -1
- package/bin/claude-smart.js +2 -2
- package/package.json +9 -3
- package/plugin/.claude-plugin/plugin.json +9 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +23 -3
- package/plugin/pyproject.toml +3 -3
- package/plugin/scripts/_lib.sh +91 -0
- package/plugin/scripts/backend-service.sh +51 -4
- package/plugin/scripts/cli.sh +3 -1
- package/plugin/scripts/codex-hook.js +72 -4
- package/plugin/scripts/dashboard-build.sh +1 -0
- package/plugin/scripts/dashboard-service.sh +1 -0
- package/plugin/scripts/ensure-plugin-root.sh +1 -0
- package/plugin/scripts/hook_entry.sh +6 -3
- package/plugin/scripts/smart-install.sh +3 -2
- package/plugin/src/README.md +57 -0
- package/plugin/src/claude_smart/context_format.py +11 -12
- package/plugin/src/claude_smart/cs_cite.py +26 -12
- package/plugin/src/claude_smart/ids.py +13 -5
- package/plugin/uv.lock +126 -5
- package/plugin/vendor/reflexio/.env.example +62 -0
- package/plugin/vendor/reflexio/LICENSE +201 -0
- package/plugin/vendor/reflexio/README.md +338 -0
- package/plugin/vendor/reflexio/pyproject.toml +274 -0
- package/plugin/vendor/reflexio/reflexio/README.md +184 -0
- package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
- package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
- package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
- package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +265 -0
- package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +964 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
- package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
- package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
- package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
- package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
- package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
- package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
- package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
- package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
- package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
- package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
- package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
- package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
- package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1112 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +768 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +333 -0
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
- package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +622 -0
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +2868 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +562 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
- package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +359 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1871 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +140 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +479 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +338 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +187 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +716 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +283 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +336 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1668 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
- package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +358 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +535 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +779 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +94 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +702 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +1045 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +772 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +737 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +803 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +80 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +756 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +384 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +572 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
- package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
- package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
- package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"""Finish-tool runner for resumable classic extraction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
|
|
13
|
+
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
14
|
+
from reflexio.server.llm.model_defaults import ModelRole
|
|
15
|
+
from reflexio.server.llm.tools import Tool, ToolLoopTrace, ToolRegistry, run_tool_loop
|
|
16
|
+
from reflexio.server.services.extraction.agent_run_records import (
|
|
17
|
+
build_extractor_agent_run_record,
|
|
18
|
+
)
|
|
19
|
+
from reflexio.server.services.extraction.pending_tool_call_dispatch import (
|
|
20
|
+
PendingToolCallToolContext,
|
|
21
|
+
create_ask_human_tool,
|
|
22
|
+
create_attach_pending_info_request_tool,
|
|
23
|
+
)
|
|
24
|
+
from reflexio.server.services.extraction.prior_answer_search import (
|
|
25
|
+
append_prior_knowledge_context,
|
|
26
|
+
)
|
|
27
|
+
from reflexio.server.services.storage.storage_base import (
|
|
28
|
+
AgentRunRecord,
|
|
29
|
+
AgentRunStatus,
|
|
30
|
+
BaseStorage,
|
|
31
|
+
PendingToolCallRecord,
|
|
32
|
+
)
|
|
33
|
+
from reflexio.server.site_var.feature_flags import (
|
|
34
|
+
is_resumable_extraction_agent_enabled as is_resumable_extraction_agent_feature_enabled,
|
|
35
|
+
)
|
|
36
|
+
from reflexio.server.usage_metrics import record_usage_event
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
FINISH_EXTRACTION_TOOL_NAME = "finish_extraction"
|
|
44
|
+
PROFILE_EXTRACTOR_KIND = "profile"
|
|
45
|
+
|
|
46
|
+
# Max in-process retries when the model returns a plain-text turn with no tool
|
|
47
|
+
# call (finished_reason="no_tool_call"). tool_choice="required" is not always
|
|
48
|
+
# honored by every provider/model, so a single bad turn would otherwise drop the
|
|
49
|
+
# extraction entirely. Retries re-issue the same prompt from scratch.
|
|
50
|
+
_NO_TOOL_CALL_MAX_RETRIES = 2
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _record_agent_usage_event(
|
|
54
|
+
*,
|
|
55
|
+
run: AgentRunRecord,
|
|
56
|
+
event_name: str,
|
|
57
|
+
outcome: str | None = None,
|
|
58
|
+
error_kind: str | None = None,
|
|
59
|
+
count_value: int = 1,
|
|
60
|
+
metadata: dict[str, Any] | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
record_usage_event(
|
|
63
|
+
org_id=run.binding.org_id,
|
|
64
|
+
event_name=event_name,
|
|
65
|
+
event_category="extraction_agent",
|
|
66
|
+
user_id=run.binding.user_id,
|
|
67
|
+
request_id=run.binding.request_id,
|
|
68
|
+
pipeline=run.binding.extractor_kind,
|
|
69
|
+
source=run.binding.source,
|
|
70
|
+
agent_version=run.binding.agent_version,
|
|
71
|
+
outcome=outcome,
|
|
72
|
+
error_kind=error_kind,
|
|
73
|
+
count_value=count_value,
|
|
74
|
+
metadata={"run_id": run.id, **(metadata or {})},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(slots=True)
|
|
79
|
+
class AgentRunResult:
|
|
80
|
+
run_id: str
|
|
81
|
+
output: BaseModel | None
|
|
82
|
+
pending_tool_call_ids: list[str]
|
|
83
|
+
messages: list[dict[str, Any]]
|
|
84
|
+
trace: ToolLoopTrace
|
|
85
|
+
finished_reason: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class _FinishExtractionContext:
|
|
90
|
+
output: BaseModel | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(slots=True)
|
|
94
|
+
class _ExtractionAgentToolContext:
|
|
95
|
+
finish_context: _FinishExtractionContext
|
|
96
|
+
extra_tool_context: Any | None = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _format_resolved_tool_result(record: PendingToolCallRecord) -> str:
|
|
100
|
+
resolved_at = record.resolved_at.isoformat() if record.resolved_at else "unknown"
|
|
101
|
+
return (
|
|
102
|
+
"Resolved tool result for extraction follow-up\n"
|
|
103
|
+
f"Tool: {record.tool_name}\n"
|
|
104
|
+
f"Question: {record.question_text}\n"
|
|
105
|
+
f"Resolved at: {resolved_at}\n"
|
|
106
|
+
f"Result: {record.result or {}}\n\n"
|
|
107
|
+
"Use this Agent Builder feedback only if it is relevant to the "
|
|
108
|
+
"current extraction window. If it adds or corrects durable profile or "
|
|
109
|
+
"playbook information, include that in finish_extraction."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def append_resolved_tool_result_context(
|
|
114
|
+
messages: list[dict[str, Any]],
|
|
115
|
+
resolved_tool_calls: list[PendingToolCallRecord],
|
|
116
|
+
) -> list[dict[str, Any]]:
|
|
117
|
+
"""Append resolved async tool results as user-role Agent Builder context."""
|
|
118
|
+
ordered = sorted(
|
|
119
|
+
resolved_tool_calls,
|
|
120
|
+
key=lambda record: record.resolved_at or datetime.max.replace(tzinfo=UTC),
|
|
121
|
+
)
|
|
122
|
+
return [
|
|
123
|
+
*messages,
|
|
124
|
+
*[
|
|
125
|
+
{"role": "user", "content": _format_resolved_tool_result(record)}
|
|
126
|
+
for record in ordered
|
|
127
|
+
],
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _finish_handler(args: BaseModel, ctx: Any) -> dict[str, Any]:
|
|
132
|
+
ctx = getattr(ctx, "finish_context", ctx)
|
|
133
|
+
if not isinstance(ctx, _FinishExtractionContext):
|
|
134
|
+
raise TypeError(f"Expected _FinishExtractionContext, got {type(ctx).__name__}")
|
|
135
|
+
ctx.output = args
|
|
136
|
+
return {"status": "completed"}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def create_finish_extraction_tool(output_schema: type[BaseModel]) -> Tool:
|
|
140
|
+
return Tool(
|
|
141
|
+
name=FINISH_EXTRACTION_TOOL_NAME,
|
|
142
|
+
args_model=output_schema,
|
|
143
|
+
handler=_finish_handler,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _pending_tool_call_config(request_context: RequestContext) -> Any | None:
|
|
148
|
+
root_config = request_context.configurator.get_config()
|
|
149
|
+
return (
|
|
150
|
+
getattr(root_config, "pending_tool_call_config", None)
|
|
151
|
+
if root_config is not None
|
|
152
|
+
else None
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def pending_tool_calls_enabled(request_context: RequestContext) -> bool:
|
|
157
|
+
"""Gate whether pending-info tools are offered.
|
|
158
|
+
|
|
159
|
+
This does NOT gate whether the extraction loop runs — the loop is always
|
|
160
|
+
the extraction path. It only governs whether the resumable human-in-the-loop
|
|
161
|
+
and prior-knowledge tools may be registered alongside ``finish_extraction``.
|
|
162
|
+
"""
|
|
163
|
+
pending_config = _pending_tool_call_config(request_context)
|
|
164
|
+
return bool(
|
|
165
|
+
pending_config
|
|
166
|
+
and pending_config.enabled
|
|
167
|
+
and is_resumable_extraction_agent_feature_enabled(request_context.org_id)
|
|
168
|
+
and request_context.storage is not None
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def create_pending_info_tools_for_extractor_kind(extractor_kind: str) -> list[Tool]:
|
|
173
|
+
"""Return pending-info tools available to a given extractor kind."""
|
|
174
|
+
attach_tool = create_attach_pending_info_request_tool()
|
|
175
|
+
if extractor_kind == PROFILE_EXTRACTOR_KIND:
|
|
176
|
+
return [attach_tool]
|
|
177
|
+
return [create_ask_human_tool(), attach_tool]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def run_resumable_extraction_agent(
|
|
181
|
+
*,
|
|
182
|
+
request_context: RequestContext,
|
|
183
|
+
client: LiteLLMClient,
|
|
184
|
+
extractor_kind: str,
|
|
185
|
+
user_id: str | None,
|
|
186
|
+
request_id: str,
|
|
187
|
+
agent_version: str | None,
|
|
188
|
+
source: str | None,
|
|
189
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
190
|
+
extractor_config: BaseModel,
|
|
191
|
+
service_config: Any,
|
|
192
|
+
agent_context: str,
|
|
193
|
+
messages: list[dict[str, Any]],
|
|
194
|
+
output_schema: type[BaseModel],
|
|
195
|
+
log_label: str,
|
|
196
|
+
) -> AgentRunResult:
|
|
197
|
+
"""Run and finalize a config-gated classic extraction agent pass."""
|
|
198
|
+
pending_config = _pending_tool_call_config(request_context)
|
|
199
|
+
storage = request_context.storage
|
|
200
|
+
if storage is None:
|
|
201
|
+
raise RuntimeError(f"Resumable {extractor_kind} extraction requires storage")
|
|
202
|
+
|
|
203
|
+
pending_tools_active = pending_tool_calls_enabled(request_context)
|
|
204
|
+
|
|
205
|
+
run = build_extractor_agent_run_record(
|
|
206
|
+
org_id=request_context.org_id,
|
|
207
|
+
extractor_kind=extractor_kind,
|
|
208
|
+
user_id=user_id,
|
|
209
|
+
request_id=request_id,
|
|
210
|
+
agent_version=agent_version,
|
|
211
|
+
source=source,
|
|
212
|
+
request_interaction_data_models=request_interaction_data_models,
|
|
213
|
+
extractor_config=extractor_config,
|
|
214
|
+
service_config=service_config,
|
|
215
|
+
agent_context=agent_context,
|
|
216
|
+
)
|
|
217
|
+
extra_tools: list[Tool] = []
|
|
218
|
+
extra_tool_context = None
|
|
219
|
+
if pending_tools_active and pending_config is not None:
|
|
220
|
+
messages = append_prior_knowledge_context(
|
|
221
|
+
messages=messages,
|
|
222
|
+
storage=storage,
|
|
223
|
+
org_id=request_context.org_id,
|
|
224
|
+
extractor_kind=extractor_kind,
|
|
225
|
+
extractor_config=extractor_config,
|
|
226
|
+
source=source,
|
|
227
|
+
agent_version=agent_version,
|
|
228
|
+
similarity_threshold=pending_config.for_tool(
|
|
229
|
+
"attach_pending_info_request"
|
|
230
|
+
if extractor_kind == PROFILE_EXTRACTOR_KIND
|
|
231
|
+
else "ask_human"
|
|
232
|
+
).similarity_threshold,
|
|
233
|
+
)
|
|
234
|
+
extra_tool_context = PendingToolCallToolContext(
|
|
235
|
+
storage=storage,
|
|
236
|
+
run_id=run.id,
|
|
237
|
+
org_id=request_context.org_id,
|
|
238
|
+
extractor_kind=extractor_kind,
|
|
239
|
+
user_id=user_id,
|
|
240
|
+
config=pending_config,
|
|
241
|
+
)
|
|
242
|
+
extra_tools.extend(create_pending_info_tools_for_extractor_kind(extractor_kind))
|
|
243
|
+
|
|
244
|
+
return ResumableExtractionAgent(client=client, storage=storage).start(
|
|
245
|
+
run=run,
|
|
246
|
+
messages=messages,
|
|
247
|
+
output_schema=output_schema,
|
|
248
|
+
extra_tools=extra_tools,
|
|
249
|
+
extra_tool_context=extra_tool_context,
|
|
250
|
+
log_label=log_label,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ResumableExtractionAgent:
|
|
255
|
+
"""Run a classic extractor prompt through a durable finish-tool loop."""
|
|
256
|
+
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
*,
|
|
260
|
+
client: LiteLLMClient,
|
|
261
|
+
storage: BaseStorage,
|
|
262
|
+
max_steps: int = 8,
|
|
263
|
+
model_role: ModelRole = ModelRole.EXTRACTION_AGENT,
|
|
264
|
+
) -> None:
|
|
265
|
+
self.client = client
|
|
266
|
+
self.storage = storage
|
|
267
|
+
self.max_steps = max_steps
|
|
268
|
+
self.model_role = model_role
|
|
269
|
+
|
|
270
|
+
def start(
|
|
271
|
+
self,
|
|
272
|
+
*,
|
|
273
|
+
run: AgentRunRecord,
|
|
274
|
+
messages: list[dict[str, Any]],
|
|
275
|
+
output_schema: type[BaseModel],
|
|
276
|
+
extra_tools: list[Tool] | None = None,
|
|
277
|
+
extra_tool_context: Any | None = None,
|
|
278
|
+
log_label: str | None = None,
|
|
279
|
+
) -> AgentRunResult:
|
|
280
|
+
"""Create the run row, execute the tool loop, and store completed output."""
|
|
281
|
+
run = replace(
|
|
282
|
+
run,
|
|
283
|
+
max_steps_remaining=(
|
|
284
|
+
self.max_steps
|
|
285
|
+
if run.max_steps_remaining is None
|
|
286
|
+
else min(run.max_steps_remaining, self.max_steps)
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
self.storage.create_agent_run(run)
|
|
290
|
+
logger.info(
|
|
291
|
+
"event=extraction_agent_started org_id=%s user_id=%s extractor_kind=%s "
|
|
292
|
+
"run_id=%s request_id=%s",
|
|
293
|
+
run.binding.org_id,
|
|
294
|
+
run.binding.user_id,
|
|
295
|
+
run.binding.extractor_kind,
|
|
296
|
+
run.id,
|
|
297
|
+
run.binding.request_id,
|
|
298
|
+
)
|
|
299
|
+
_record_agent_usage_event(run=run, event_name="extraction_agent_started")
|
|
300
|
+
return self._run(
|
|
301
|
+
run=run,
|
|
302
|
+
messages=messages,
|
|
303
|
+
output_schema=output_schema,
|
|
304
|
+
extra_tools=extra_tools,
|
|
305
|
+
extra_tool_context=extra_tool_context,
|
|
306
|
+
log_label=log_label,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def resume(
|
|
310
|
+
self,
|
|
311
|
+
*,
|
|
312
|
+
run: AgentRunRecord,
|
|
313
|
+
messages: list[dict[str, Any]],
|
|
314
|
+
output_schema: type[BaseModel],
|
|
315
|
+
resolved_tool_calls: list[PendingToolCallRecord],
|
|
316
|
+
extra_tools: list[Tool] | None = None,
|
|
317
|
+
extra_tool_context: Any | None = None,
|
|
318
|
+
log_label: str | None = None,
|
|
319
|
+
) -> AgentRunResult:
|
|
320
|
+
"""Resume a claimed run with resolved async tool results in context."""
|
|
321
|
+
logger.info(
|
|
322
|
+
"event=extraction_agent_resumed org_id=%s user_id=%s extractor_kind=%s "
|
|
323
|
+
"run_id=%s request_id=%s resolved_tool_calls=%d",
|
|
324
|
+
run.binding.org_id,
|
|
325
|
+
run.binding.user_id,
|
|
326
|
+
run.binding.extractor_kind,
|
|
327
|
+
run.id,
|
|
328
|
+
run.binding.request_id,
|
|
329
|
+
len(resolved_tool_calls),
|
|
330
|
+
)
|
|
331
|
+
_record_agent_usage_event(
|
|
332
|
+
run=run,
|
|
333
|
+
event_name="extraction_agent_resumed",
|
|
334
|
+
count_value=len(resolved_tool_calls),
|
|
335
|
+
metadata={"resolved_tool_calls": len(resolved_tool_calls)},
|
|
336
|
+
)
|
|
337
|
+
resumed_messages = append_resolved_tool_result_context(
|
|
338
|
+
messages,
|
|
339
|
+
resolved_tool_calls,
|
|
340
|
+
)
|
|
341
|
+
return self._run(
|
|
342
|
+
run=run,
|
|
343
|
+
messages=resumed_messages,
|
|
344
|
+
output_schema=output_schema,
|
|
345
|
+
extra_tools=extra_tools,
|
|
346
|
+
extra_tool_context=extra_tool_context,
|
|
347
|
+
log_label=log_label,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def _run(
|
|
351
|
+
self,
|
|
352
|
+
*,
|
|
353
|
+
run: AgentRunRecord,
|
|
354
|
+
messages: list[dict[str, Any]],
|
|
355
|
+
output_schema: type[BaseModel],
|
|
356
|
+
extra_tools: list[Tool] | None = None,
|
|
357
|
+
extra_tool_context: Any | None = None,
|
|
358
|
+
log_label: str | None = None,
|
|
359
|
+
) -> AgentRunResult:
|
|
360
|
+
max_steps = self.max_steps
|
|
361
|
+
if run.max_steps_remaining is not None:
|
|
362
|
+
max_steps = min(max_steps, max(0, run.max_steps_remaining))
|
|
363
|
+
|
|
364
|
+
# When only finish_extraction is registered (no human/prior-knowledge
|
|
365
|
+
# tools), force that tool so the degenerate one-call loop is strictly
|
|
366
|
+
# equivalent to the old single-shot response_format extraction —
|
|
367
|
+
# the model cannot return a no-tool turn and yield empty output.
|
|
368
|
+
#
|
|
369
|
+
# When the async-info tools are also registered we cannot force a single
|
|
370
|
+
# tool (the model must be free to pick ask_human vs finish_extraction),
|
|
371
|
+
# but we still require *some* tool call via "required". This prevents a
|
|
372
|
+
# weak tool-caller (e.g. MiniMax) from emitting the answer as plain text
|
|
373
|
+
# with zero tool_calls, which the loop would otherwise treat as a no-op
|
|
374
|
+
# finish and drop the output.
|
|
375
|
+
tool_choice: str | dict[str, Any] = (
|
|
376
|
+
{
|
|
377
|
+
"type": "function",
|
|
378
|
+
"function": {"name": FINISH_EXTRACTION_TOOL_NAME},
|
|
379
|
+
}
|
|
380
|
+
if not extra_tools
|
|
381
|
+
else "required"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Retry only the no_tool_call termination: the model emitted plain text
|
|
385
|
+
# with zero tool calls, so nothing was committed and re-issuing the same
|
|
386
|
+
# prompt is safe. Each attempt uses a fresh finish_ctx/registry so no
|
|
387
|
+
# partial state leaks across attempts. We do NOT retry once the model has
|
|
388
|
+
# already registered async tool calls (e.g. attach_pending_info_request)
|
|
389
|
+
# — restarting from scratch would discard them.
|
|
390
|
+
for attempt in range(_NO_TOOL_CALL_MAX_RETRIES + 1):
|
|
391
|
+
finish_ctx = _FinishExtractionContext()
|
|
392
|
+
ctx: Any = (
|
|
393
|
+
_ExtractionAgentToolContext(
|
|
394
|
+
finish_context=finish_ctx,
|
|
395
|
+
extra_tool_context=extra_tool_context,
|
|
396
|
+
)
|
|
397
|
+
if extra_tool_context is not None
|
|
398
|
+
else finish_ctx
|
|
399
|
+
)
|
|
400
|
+
registry = ToolRegistry(
|
|
401
|
+
[*(extra_tools or []), create_finish_extraction_tool(output_schema)]
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
result = run_tool_loop(
|
|
405
|
+
client=self.client,
|
|
406
|
+
messages=messages,
|
|
407
|
+
registry=registry,
|
|
408
|
+
model_role=self.model_role,
|
|
409
|
+
max_steps=max_steps,
|
|
410
|
+
ctx=ctx,
|
|
411
|
+
finish_tool_name=FINISH_EXTRACTION_TOOL_NAME,
|
|
412
|
+
tool_choice=tool_choice,
|
|
413
|
+
log_label=log_label,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
should_retry = (
|
|
417
|
+
result.finished_reason == "no_tool_call"
|
|
418
|
+
and not result.pending_tool_call_ids
|
|
419
|
+
and attempt < _NO_TOOL_CALL_MAX_RETRIES
|
|
420
|
+
)
|
|
421
|
+
if not should_retry:
|
|
422
|
+
break
|
|
423
|
+
logger.warning(
|
|
424
|
+
"event=extraction_agent_retry org_id=%s user_id=%s "
|
|
425
|
+
"extractor_kind=%s run_id=%s request_id=%s "
|
|
426
|
+
"finished_reason=%s attempt=%d max_retries=%d",
|
|
427
|
+
run.binding.org_id,
|
|
428
|
+
run.binding.user_id,
|
|
429
|
+
run.binding.extractor_kind,
|
|
430
|
+
run.id,
|
|
431
|
+
run.binding.request_id,
|
|
432
|
+
result.finished_reason,
|
|
433
|
+
attempt + 1,
|
|
434
|
+
_NO_TOOL_CALL_MAX_RETRIES,
|
|
435
|
+
)
|
|
436
|
+
_record_agent_usage_event(
|
|
437
|
+
run=run,
|
|
438
|
+
event_name="extraction_agent_retry",
|
|
439
|
+
error_kind=result.finished_reason,
|
|
440
|
+
metadata={"attempt": attempt + 1},
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
committed_output = (
|
|
444
|
+
finish_ctx.output.model_dump() if finish_ctx.output is not None else None
|
|
445
|
+
)
|
|
446
|
+
active_statuses = (AgentRunStatus.RUNNING, AgentRunStatus.RESUMING)
|
|
447
|
+
if result.finished_reason == "finish_tool" and committed_output is not None:
|
|
448
|
+
stored_run = self.storage.update_agent_run_status(
|
|
449
|
+
run.id,
|
|
450
|
+
AgentRunStatus.AGENT_COMPLETED,
|
|
451
|
+
committed_output=committed_output,
|
|
452
|
+
pending_tool_call_ids=result.pending_tool_call_ids,
|
|
453
|
+
max_steps_remaining=result.max_steps_remaining,
|
|
454
|
+
expected_statuses=active_statuses,
|
|
455
|
+
)
|
|
456
|
+
if (
|
|
457
|
+
stored_run is None
|
|
458
|
+
or stored_run.status != AgentRunStatus.AGENT_COMPLETED
|
|
459
|
+
):
|
|
460
|
+
logger.warning(
|
|
461
|
+
"event=extraction_agent_late_output_discarded org_id=%s "
|
|
462
|
+
"user_id=%s extractor_kind=%s run_id=%s request_id=%s "
|
|
463
|
+
"stored_status=%s",
|
|
464
|
+
run.binding.org_id,
|
|
465
|
+
run.binding.user_id,
|
|
466
|
+
run.binding.extractor_kind,
|
|
467
|
+
run.id,
|
|
468
|
+
run.binding.request_id,
|
|
469
|
+
stored_run.status if stored_run is not None else None,
|
|
470
|
+
)
|
|
471
|
+
return AgentRunResult(
|
|
472
|
+
run_id=run.id,
|
|
473
|
+
output=None,
|
|
474
|
+
pending_tool_call_ids=[],
|
|
475
|
+
messages=result.messages,
|
|
476
|
+
trace=result.trace,
|
|
477
|
+
finished_reason="late_output_discarded",
|
|
478
|
+
)
|
|
479
|
+
logger.info(
|
|
480
|
+
"event=extraction_agent_finished org_id=%s user_id=%s "
|
|
481
|
+
"extractor_kind=%s run_id=%s request_id=%s "
|
|
482
|
+
"pending_tool_calls=%d",
|
|
483
|
+
run.binding.org_id,
|
|
484
|
+
run.binding.user_id,
|
|
485
|
+
run.binding.extractor_kind,
|
|
486
|
+
run.id,
|
|
487
|
+
run.binding.request_id,
|
|
488
|
+
len(result.pending_tool_call_ids),
|
|
489
|
+
)
|
|
490
|
+
_record_agent_usage_event(
|
|
491
|
+
run=run,
|
|
492
|
+
event_name="extraction_agent_finished",
|
|
493
|
+
outcome="completed",
|
|
494
|
+
metadata={
|
|
495
|
+
"pending_tool_calls": len(result.pending_tool_call_ids),
|
|
496
|
+
"finished_reason": result.finished_reason,
|
|
497
|
+
},
|
|
498
|
+
)
|
|
499
|
+
else:
|
|
500
|
+
last_error = f"Extraction agent did not finish: {result.finished_reason}"
|
|
501
|
+
self.storage.update_agent_run_status(
|
|
502
|
+
run.id,
|
|
503
|
+
AgentRunStatus.FAILED,
|
|
504
|
+
max_steps_remaining=result.max_steps_remaining,
|
|
505
|
+
last_error=last_error,
|
|
506
|
+
expected_statuses=active_statuses,
|
|
507
|
+
)
|
|
508
|
+
logger.warning(
|
|
509
|
+
"event=extraction_agent_failed org_id=%s user_id=%s "
|
|
510
|
+
"extractor_kind=%s run_id=%s request_id=%s "
|
|
511
|
+
"finished_reason=%s has_output=%s",
|
|
512
|
+
run.binding.org_id,
|
|
513
|
+
run.binding.user_id,
|
|
514
|
+
run.binding.extractor_kind,
|
|
515
|
+
run.id,
|
|
516
|
+
run.binding.request_id,
|
|
517
|
+
result.finished_reason,
|
|
518
|
+
finish_ctx.output is not None,
|
|
519
|
+
)
|
|
520
|
+
_record_agent_usage_event(
|
|
521
|
+
run=run,
|
|
522
|
+
event_name="extraction_agent_failed",
|
|
523
|
+
outcome="failed",
|
|
524
|
+
error_kind=result.finished_reason,
|
|
525
|
+
metadata={"has_output": finish_ctx.output is not None},
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
return AgentRunResult(
|
|
529
|
+
run_id=run.id,
|
|
530
|
+
output=finish_ctx.output,
|
|
531
|
+
pending_tool_call_ids=result.pending_tool_call_ids,
|
|
532
|
+
messages=result.messages,
|
|
533
|
+
trace=result.trace,
|
|
534
|
+
finished_reason=result.finished_reason,
|
|
535
|
+
)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Process-local scheduler for resumable extraction follow-up work.
|
|
2
|
+
|
|
3
|
+
The scheduler is intentionally multi-tenant: each tick it discovers every org
|
|
4
|
+
that has actionable resumable-extraction work (a run ready to resume, a run
|
|
5
|
+
awaiting finalization retry, or a pending tool call due to expire) and drives a
|
|
6
|
+
per-org :class:`ExtractionResumeWorker` for each. Worker claims are org-scoped,
|
|
7
|
+
so a worker only ever resumes runs belonging to the org context it was built
|
|
8
|
+
with — never another tenant's runs.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import threading
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from datetime import UTC, datetime
|
|
17
|
+
|
|
18
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
19
|
+
from reflexio.server.services.extraction.resumable_agent import (
|
|
20
|
+
pending_tool_calls_enabled,
|
|
21
|
+
)
|
|
22
|
+
from reflexio.server.services.extraction.resume_worker import ExtractionResumeWorker
|
|
23
|
+
from reflexio.server.tracing import sentry_tags
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_DEFAULT_POLL_INTERVAL_SECONDS = 5.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ExtractionResumeScheduler:
|
|
31
|
+
"""Small polling wrapper that drives :class:`ExtractionResumeWorker` per org."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
request_context_factory: Callable[[str], RequestContext],
|
|
37
|
+
bootstrap_org_id: str,
|
|
38
|
+
max_runs_per_tick: int = 10,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.request_context_factory = request_context_factory
|
|
41
|
+
self.bootstrap_org_id = bootstrap_org_id
|
|
42
|
+
self.max_runs_per_tick = max_runs_per_tick
|
|
43
|
+
self._stop_event = threading.Event()
|
|
44
|
+
self._thread: threading.Thread | None = None
|
|
45
|
+
|
|
46
|
+
def start(self) -> None:
|
|
47
|
+
if self._thread is not None and self._thread.is_alive():
|
|
48
|
+
return
|
|
49
|
+
self._stop_event.clear()
|
|
50
|
+
self._thread = threading.Thread(
|
|
51
|
+
target=self._run_loop,
|
|
52
|
+
name="reflexio-extraction-resume-scheduler",
|
|
53
|
+
daemon=True,
|
|
54
|
+
)
|
|
55
|
+
self._thread.start()
|
|
56
|
+
logger.info("event=extraction_resume_scheduler_started")
|
|
57
|
+
|
|
58
|
+
def stop(self, *, timeout_seconds: float = 5.0) -> None:
|
|
59
|
+
self._stop_event.set()
|
|
60
|
+
if self._thread is not None:
|
|
61
|
+
self._thread.join(timeout=timeout_seconds)
|
|
62
|
+
self._thread = None
|
|
63
|
+
logger.info("event=extraction_resume_scheduler_stopped")
|
|
64
|
+
|
|
65
|
+
def _discover_org_ids(self, bootstrap_ctx: RequestContext) -> list[str]:
|
|
66
|
+
"""Return every org with actionable work, always including the bootstrap org."""
|
|
67
|
+
org_ids: list[str] = []
|
|
68
|
+
storage = getattr(bootstrap_ctx, "storage", None)
|
|
69
|
+
if storage is not None:
|
|
70
|
+
try:
|
|
71
|
+
org_ids = storage.list_resumable_work_org_ids(now=datetime.now(UTC))
|
|
72
|
+
except NotImplementedError:
|
|
73
|
+
org_ids = []
|
|
74
|
+
# Always sweep the bootstrap org so the maintenance loop runs even when
|
|
75
|
+
# the cross-org discovery query is empty or unsupported.
|
|
76
|
+
if bootstrap_ctx.org_id not in org_ids:
|
|
77
|
+
org_ids = [bootstrap_ctx.org_id, *org_ids]
|
|
78
|
+
return org_ids
|
|
79
|
+
|
|
80
|
+
def _expire_pending_tool_calls(self, bootstrap_ctx: RequestContext) -> None:
|
|
81
|
+
storage = getattr(bootstrap_ctx, "storage", None)
|
|
82
|
+
if storage is None:
|
|
83
|
+
return
|
|
84
|
+
# ``expire_pending_tool_calls`` is not org-scoped, so a single call
|
|
85
|
+
# sweeps every tenant's overdue pending rows for this tick.
|
|
86
|
+
try:
|
|
87
|
+
expired = storage.expire_pending_tool_calls(now=datetime.now(UTC))
|
|
88
|
+
except NotImplementedError:
|
|
89
|
+
return
|
|
90
|
+
if expired:
|
|
91
|
+
logger.info("event=pending_tool_calls_expired expired=%d", expired)
|
|
92
|
+
|
|
93
|
+
def _drain_org(self, org_id: str) -> None:
|
|
94
|
+
try:
|
|
95
|
+
ctx = self.request_context_factory(org_id)
|
|
96
|
+
if not pending_tool_calls_enabled(ctx):
|
|
97
|
+
return
|
|
98
|
+
resumed = ExtractionResumeWorker(request_context=ctx).drain(
|
|
99
|
+
max_runs=self.max_runs_per_tick
|
|
100
|
+
)
|
|
101
|
+
if resumed:
|
|
102
|
+
logger.info(
|
|
103
|
+
"event=extraction_resume_scheduler_tick org_id=%s resumed=%d",
|
|
104
|
+
org_id,
|
|
105
|
+
resumed,
|
|
106
|
+
)
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
with sentry_tags(
|
|
109
|
+
subsystem="extraction",
|
|
110
|
+
op="scheduler_org_drain",
|
|
111
|
+
org_id=org_id,
|
|
112
|
+
error_type=type(exc).__name__,
|
|
113
|
+
):
|
|
114
|
+
logger.exception(
|
|
115
|
+
"event=extraction_resume_scheduler_org_failed org_id=%s",
|
|
116
|
+
org_id,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _run_loop(self) -> None:
|
|
120
|
+
while not self._stop_event.is_set():
|
|
121
|
+
poll_interval = _DEFAULT_POLL_INTERVAL_SECONDS
|
|
122
|
+
try:
|
|
123
|
+
bootstrap_ctx = self.request_context_factory(self.bootstrap_org_id)
|
|
124
|
+
config = bootstrap_ctx.configurator.get_config()
|
|
125
|
+
poll_interval = (
|
|
126
|
+
config.pending_tool_call_config.resume_poll_interval_seconds
|
|
127
|
+
)
|
|
128
|
+
self._expire_pending_tool_calls(bootstrap_ctx)
|
|
129
|
+
for org_id in self._discover_org_ids(bootstrap_ctx):
|
|
130
|
+
if self._stop_event.is_set():
|
|
131
|
+
break
|
|
132
|
+
self._drain_org(org_id)
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
with sentry_tags(
|
|
135
|
+
subsystem="extraction",
|
|
136
|
+
op="scheduler_tick",
|
|
137
|
+
error_type=type(exc).__name__,
|
|
138
|
+
):
|
|
139
|
+
logger.exception("event=extraction_resume_scheduler_tick_failed")
|
|
140
|
+
self._stop_event.wait(poll_interval)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def maybe_start_resume_scheduler(
|
|
144
|
+
request_context_factory: Callable[[str], RequestContext],
|
|
145
|
+
*,
|
|
146
|
+
bootstrap_org_id: str,
|
|
147
|
+
) -> ExtractionResumeScheduler | None:
|
|
148
|
+
"""Start the scheduler only when the bootstrap-org config enables the feature.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
request_context_factory: Builds an org-scoped :class:`RequestContext`.
|
|
152
|
+
bootstrap_org_id: Org used to read config and to seed cross-org discovery.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
ctx = request_context_factory(bootstrap_org_id)
|
|
156
|
+
if not pending_tool_calls_enabled(ctx):
|
|
157
|
+
return None
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
logger.warning(
|
|
160
|
+
"event=extraction_resume_scheduler_start_skipped error_type=%s error=%s",
|
|
161
|
+
type(exc).__name__,
|
|
162
|
+
exc,
|
|
163
|
+
)
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
scheduler = ExtractionResumeScheduler(
|
|
167
|
+
request_context_factory=request_context_factory,
|
|
168
|
+
bootstrap_org_id=bootstrap_org_id,
|
|
169
|
+
)
|
|
170
|
+
scheduler.start()
|
|
171
|
+
return scheduler
|