claude-smart 0.2.41 → 0.2.43
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 +17 -0
- package/README.md +1 -1
- package/bin/claude-smart.js +86 -48
- package/package.json +10 -3
- package/plugin/.claude-plugin/plugin.json +9 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +2 -2
- package/plugin/dashboard/next.config.ts +9 -1
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +91 -0
- package/plugin/scripts/backend-service.sh +46 -15
- package/plugin/scripts/cli.sh +29 -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 +7 -14
- package/plugin/scripts/hook_entry.sh +1 -0
- package/plugin/scripts/smart-install.sh +18 -2
- package/plugin/src/claude_smart/cli.py +72 -38
- 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 +1 -1
- package/plugin/vendor/reflexio/.env.example +53 -0
- package/plugin/vendor/reflexio/LICENSE +201 -0
- package/plugin/vendor/reflexio/README.md +338 -0
- package/plugin/vendor/reflexio/pyproject.toml +271 -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 +266 -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 +961 -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 +1103 -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 +766 -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 +280 -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 +616 -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 +2714 -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 +540 -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 +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1595 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +112 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +469 -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 +210 -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 +255 -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 +177 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +699 -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_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 +279 -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 +127 -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/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 +340 -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 +1626 -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/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 +351 -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 +468 -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 +777 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +689 -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 +960 -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 +741 -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 +734 -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 +800 -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 +70 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +671 -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 +1253 -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 +372 -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 +568 -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
package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True)
|
|
5
|
+
class PlaybookServiceConstants:
|
|
6
|
+
PLAYBOOK_EXTRACTORS_CONFIG_NAME = "user_playbook_extractor_config"
|
|
7
|
+
# ===============================
|
|
8
|
+
# prompt ids
|
|
9
|
+
# ===============================
|
|
10
|
+
PLAYBOOK_SHOULD_GENERATE_PROMPT_ID = "playbook_should_generate"
|
|
11
|
+
PLAYBOOK_EXTRACTION_CONTEXT_PROMPT_ID = "playbook_extraction_context"
|
|
12
|
+
PLAYBOOK_EXTRACTION_PROMPT_ID = "playbook_extraction_main"
|
|
13
|
+
PLAYBOOK_AGGREGATION_PROMPT_ID = "playbook_aggregation"
|
|
14
|
+
|
|
15
|
+
# ===============================
|
|
16
|
+
# expert content prompt ids
|
|
17
|
+
# ===============================
|
|
18
|
+
PLAYBOOK_SHOULD_GENERATE_EXPERT_PROMPT_ID = "playbook_should_generate_expert"
|
|
19
|
+
PLAYBOOK_EXTRACTION_CONTEXT_EXPERT_PROMPT_ID = "playbook_extraction_context_expert"
|
|
20
|
+
PLAYBOOK_EXTRACTION_EXPERT_PROMPT_ID = "playbook_extraction_main_expert"
|
|
21
|
+
|
|
22
|
+
# ===============================
|
|
23
|
+
# agent success evaluation prompt ids
|
|
24
|
+
# ===============================
|
|
25
|
+
AGENT_SUCCESS_EVALUATION_SHOULD_EVALUATE_PROMPT_ID = (
|
|
26
|
+
"agent_success_evaluation_should_evaluate"
|
|
27
|
+
)
|
|
28
|
+
AGENT_SUCCESS_EVALUATION_PROMPT_ID = "agent_success_evaluation"
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
7
|
+
|
|
8
|
+
from reflexio.models.api_schema.domain.entities import Interaction
|
|
9
|
+
from reflexio.models.api_schema.internal_schema import RequestInteractionDataModel
|
|
10
|
+
from reflexio.server.prompt.prompt_manager import PromptManager
|
|
11
|
+
from reflexio.server.services.playbook.playbook_service_constants import (
|
|
12
|
+
PlaybookServiceConstants,
|
|
13
|
+
)
|
|
14
|
+
from reflexio.server.services.service_utils import (
|
|
15
|
+
MessageConstructionConfig,
|
|
16
|
+
PromptConfig,
|
|
17
|
+
construct_messages_from_interactions,
|
|
18
|
+
extract_interactions_from_request_interaction_data_models,
|
|
19
|
+
format_sessions_to_history_string,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# ===============================
|
|
25
|
+
# Pydantic classes for playbook_extraction_main prompt output schema
|
|
26
|
+
# ===============================
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StructuredPlaybookContent(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
Structured representation of a single playbook entry from LLM output.
|
|
32
|
+
|
|
33
|
+
Field order matters for autoregressive conditioning: rationale is generated
|
|
34
|
+
first, then trigger, then content is synthesized last as a summary.
|
|
35
|
+
|
|
36
|
+
The extraction schema intentionally does not ask the model to emit a
|
|
37
|
+
polarity label. The extractor writes action rules or avoidance rules, and
|
|
38
|
+
the service derives the internal :class:`UserPlaybook` polarity from that
|
|
39
|
+
wording before storage.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
rationale: str | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="The reasoning behind this playbook entry — generated first for autoregressive conditioning",
|
|
45
|
+
)
|
|
46
|
+
trigger: str | None = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="The condition or context when this rule applies",
|
|
49
|
+
)
|
|
50
|
+
content: str | None = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="The main actionable content of the playbook entry — what to do or what to avoid",
|
|
53
|
+
)
|
|
54
|
+
source_span: str | None = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="Verbatim excerpt from the source that most directly supports this playbook entry",
|
|
57
|
+
)
|
|
58
|
+
notes: str | None = Field(
|
|
59
|
+
default=None,
|
|
60
|
+
description="Free-form extraction notes — confidence, caveats, or alternative readings",
|
|
61
|
+
)
|
|
62
|
+
reader_angle: str | None = Field(
|
|
63
|
+
default=None,
|
|
64
|
+
description="The extraction perspective or reader role that surfaced this entry",
|
|
65
|
+
)
|
|
66
|
+
model_config = ConfigDict(
|
|
67
|
+
extra="allow",
|
|
68
|
+
json_schema_extra={"additionalProperties": False},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_structured(self) -> bool:
|
|
73
|
+
"""Check if this playbook entry has both a trigger and content."""
|
|
74
|
+
return bool(
|
|
75
|
+
self.trigger
|
|
76
|
+
and self.trigger.strip()
|
|
77
|
+
and self.content
|
|
78
|
+
and self.content.strip()
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def has_content(self) -> bool:
|
|
83
|
+
"""Check if this output contains actual content."""
|
|
84
|
+
return bool(self.content and self.content.strip())
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class StructuredPlaybookList(BaseModel):
|
|
88
|
+
"""
|
|
89
|
+
Wrapper schema for extracting zero or more playbook entries in a single LLM call.
|
|
90
|
+
|
|
91
|
+
The canonical shape is ``{"playbooks": [...]}``. An empty list means the model
|
|
92
|
+
found no valid SOPs in the window. This wrapper exists because OpenAI structured
|
|
93
|
+
output requires a JSON object at the root, so ``list[StructuredPlaybookContent]``
|
|
94
|
+
cannot be used directly as ``response_format``.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
playbooks: list[StructuredPlaybookContent] = Field(
|
|
98
|
+
default_factory=list,
|
|
99
|
+
description="Extracted playbook entries — empty list when no valid SOP was found",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
model_config = ConfigDict(
|
|
103
|
+
extra="forbid",
|
|
104
|
+
json_schema_extra={"additionalProperties": False},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PlaybookAggregationOutput(BaseModel):
|
|
109
|
+
"""
|
|
110
|
+
Output schema for the playbook_aggregation prompt.
|
|
111
|
+
|
|
112
|
+
Contains the consolidated playbook entry or null if no new entry should be generated
|
|
113
|
+
(e.g., when it duplicates existing approved playbook).
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
playbook: StructuredPlaybookContent | None = Field(
|
|
117
|
+
default=None,
|
|
118
|
+
description="The consolidated playbook entry, or null if no new entry should be generated",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@model_validator(mode="before")
|
|
122
|
+
@classmethod
|
|
123
|
+
def normalize_wrapper_key(cls, data: Any) -> Any:
|
|
124
|
+
"""Accept both 'playbook' and legacy 'feedback' as the wrapper key."""
|
|
125
|
+
if isinstance(data, dict) and "feedback" in data and "playbook" not in data:
|
|
126
|
+
data["playbook"] = data.pop("feedback")
|
|
127
|
+
return data
|
|
128
|
+
|
|
129
|
+
model_config = ConfigDict(
|
|
130
|
+
extra="allow",
|
|
131
|
+
json_schema_extra={"additionalProperties": False},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def format_structured_fields_for_display(
|
|
136
|
+
structured: StructuredPlaybookContent,
|
|
137
|
+
) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Format structured metadata fields for display/debug purposes.
|
|
140
|
+
|
|
141
|
+
This is NOT for producing content values. Use ensure_playbook_content()
|
|
142
|
+
when you need to obtain the freeform content string.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
structured (StructuredPlaybookContent): The structured playbook content
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str: Formatted structured fields string for display
|
|
149
|
+
"""
|
|
150
|
+
lines = []
|
|
151
|
+
|
|
152
|
+
if structured.trigger:
|
|
153
|
+
lines.append(f'Trigger: "{structured.trigger}"')
|
|
154
|
+
|
|
155
|
+
if structured.rationale:
|
|
156
|
+
lines.append(f'Rationale: "{structured.rationale}"')
|
|
157
|
+
|
|
158
|
+
if not lines and structured.content:
|
|
159
|
+
return structured.content
|
|
160
|
+
|
|
161
|
+
return "\n".join(lines)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def ensure_playbook_content(
|
|
165
|
+
playbook_content: str | None,
|
|
166
|
+
structured: StructuredPlaybookContent,
|
|
167
|
+
) -> str:
|
|
168
|
+
"""
|
|
169
|
+
Return playbook_content if present; legacy fallback from structured fields.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
playbook_content (str | None): The freeform content from the LLM
|
|
173
|
+
structured (StructuredPlaybookContent): Structured fields for fallback
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
str: The freeform playbook_content, or a formatted fallback from structured fields.
|
|
177
|
+
"""
|
|
178
|
+
if playbook_content and playbook_content.strip():
|
|
179
|
+
return playbook_content
|
|
180
|
+
return format_structured_fields_for_display(structured)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class PlaybookGenerationRequest(BaseModel):
|
|
184
|
+
request_id: str
|
|
185
|
+
agent_version: str
|
|
186
|
+
user_id: str | None = None # for per-user playbook extraction
|
|
187
|
+
source: str | None = None
|
|
188
|
+
rerun_start_time: int | None = None # Unix timestamp for rerun flows
|
|
189
|
+
rerun_end_time: int | None = None # Unix timestamp for rerun flows
|
|
190
|
+
auto_run: bool = (
|
|
191
|
+
True # True for regular flow (checks stride_size), False for rerun/manual
|
|
192
|
+
)
|
|
193
|
+
force_extraction: bool = False # when True, bypass all extraction gates (stride_size, cheap pre-filter, LLM should_run)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class PlaybookAggregatorRequest(BaseModel):
|
|
197
|
+
agent_version: str
|
|
198
|
+
rerun: bool = False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def construct_playbook_extraction_messages_from_sessions(
|
|
202
|
+
prompt_manager: PromptManager,
|
|
203
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
204
|
+
agent_context_prompt: str,
|
|
205
|
+
extraction_definition_prompt: str,
|
|
206
|
+
tool_can_use: str | None = None,
|
|
207
|
+
) -> list[dict]:
|
|
208
|
+
"""
|
|
209
|
+
Construct LLM messages for playbook extraction from sessions.
|
|
210
|
+
|
|
211
|
+
This function uses the shared message construction interface to build messages
|
|
212
|
+
with a system prompt and a final user prompt specific to playbook extraction.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
prompt_manager: The prompt manager for rendering prompt templates
|
|
216
|
+
request_interaction_data_models: List of request interaction groups to extract playbook entries from
|
|
217
|
+
agent_context_prompt: Context about the agent for system message
|
|
218
|
+
extraction_definition_prompt: Definition of what the playbook should contain
|
|
219
|
+
tool_can_use: Optional formatted string of tools available to the agent
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
list[dict]: List of messages ready for playbook extraction
|
|
223
|
+
"""
|
|
224
|
+
# Configure system message (before interactions)
|
|
225
|
+
# Stable content (instructions, examples, definitions) goes in system message for token caching
|
|
226
|
+
system_config = PromptConfig(
|
|
227
|
+
prompt_id=PlaybookServiceConstants.PLAYBOOK_EXTRACTION_CONTEXT_PROMPT_ID,
|
|
228
|
+
variables={
|
|
229
|
+
"agent_context_prompt": agent_context_prompt,
|
|
230
|
+
"extraction_definition_prompt": extraction_definition_prompt,
|
|
231
|
+
"tool_can_use": tool_can_use or "",
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Configure final user message (after interactions)
|
|
236
|
+
# Only dynamic per-call data goes in user message
|
|
237
|
+
user_config = PromptConfig(
|
|
238
|
+
prompt_id=PlaybookServiceConstants.PLAYBOOK_EXTRACTION_PROMPT_ID,
|
|
239
|
+
variables={
|
|
240
|
+
"interactions": format_sessions_to_history_string(
|
|
241
|
+
request_interaction_data_models
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Extract flat interactions for message construction
|
|
247
|
+
interactions = extract_interactions_from_request_interaction_data_models(
|
|
248
|
+
request_interaction_data_models
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Use shared message construction
|
|
252
|
+
config = MessageConstructionConfig(
|
|
253
|
+
prompt_manager=prompt_manager,
|
|
254
|
+
system_prompt_config=system_config,
|
|
255
|
+
user_prompt_config=user_config,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return construct_messages_from_interactions(interactions, config)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ===============================
|
|
262
|
+
# Expert content utilities
|
|
263
|
+
# ===============================
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def has_expert_content(interactions: list[Interaction]) -> bool:
|
|
267
|
+
"""Check if any interaction has non-empty expert_content."""
|
|
268
|
+
return any(i.expert_content for i in interactions)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def format_expert_comparison_pairs(
|
|
272
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
273
|
+
) -> str:
|
|
274
|
+
"""
|
|
275
|
+
Format interactions with expert_content as agent-vs-expert comparison blocks.
|
|
276
|
+
|
|
277
|
+
For each agent interaction that has expert_content, includes the preceding user
|
|
278
|
+
question for context, the agent's actual response, and the expert's ideal response.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
request_interaction_data_models: Session data models containing interactions
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
str: Formatted comparison pairs string
|
|
285
|
+
"""
|
|
286
|
+
interactions = extract_interactions_from_request_interaction_data_models(
|
|
287
|
+
request_interaction_data_models
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
pairs: list[str] = []
|
|
291
|
+
pair_num = 0
|
|
292
|
+
for i, interaction in enumerate(interactions):
|
|
293
|
+
if not interaction.expert_content:
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
pair_num += 1
|
|
297
|
+
# Find the preceding user question for context
|
|
298
|
+
user_question = ""
|
|
299
|
+
for j in range(i - 1, -1, -1):
|
|
300
|
+
if interactions[j].role.lower() == "user":
|
|
301
|
+
user_question = interactions[j].content
|
|
302
|
+
break
|
|
303
|
+
|
|
304
|
+
parts = [f"=== Comparison {pair_num} ==="]
|
|
305
|
+
if user_question:
|
|
306
|
+
parts.append(f"User Question: ```{user_question}```")
|
|
307
|
+
parts.append(f"Agent Response: ```{interaction.content}```")
|
|
308
|
+
parts.append(f"Expert Response: ```{interaction.expert_content}```")
|
|
309
|
+
pairs.append("\n".join(parts))
|
|
310
|
+
|
|
311
|
+
return "\n\n".join(pairs)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def construct_expert_playbook_extraction_messages(
|
|
315
|
+
prompt_manager: PromptManager,
|
|
316
|
+
request_interaction_data_models: list[RequestInteractionDataModel],
|
|
317
|
+
agent_context_prompt: str,
|
|
318
|
+
extraction_definition_prompt: str,
|
|
319
|
+
) -> list[dict]:
|
|
320
|
+
"""
|
|
321
|
+
Construct LLM messages for expert-content playbook extraction.
|
|
322
|
+
|
|
323
|
+
Uses expert-specific prompts that compare agent responses against expert
|
|
324
|
+
responses and extract playbook entries about alignment gaps.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
prompt_manager: The prompt manager for rendering prompt templates
|
|
328
|
+
request_interaction_data_models: Session data with expert_content interactions
|
|
329
|
+
agent_context_prompt: Context about the agent for system message
|
|
330
|
+
extraction_definition_prompt: Definition of what the playbook should contain
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
list[dict]: List of messages ready for expert playbook extraction
|
|
334
|
+
"""
|
|
335
|
+
system_config = PromptConfig(
|
|
336
|
+
prompt_id=PlaybookServiceConstants.PLAYBOOK_EXTRACTION_CONTEXT_EXPERT_PROMPT_ID,
|
|
337
|
+
variables={
|
|
338
|
+
"agent_context_prompt": agent_context_prompt,
|
|
339
|
+
"extraction_definition_prompt": extraction_definition_prompt,
|
|
340
|
+
},
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
comparison_pairs = format_expert_comparison_pairs(request_interaction_data_models)
|
|
344
|
+
|
|
345
|
+
user_config = PromptConfig(
|
|
346
|
+
prompt_id=PlaybookServiceConstants.PLAYBOOK_EXTRACTION_EXPERT_PROMPT_ID,
|
|
347
|
+
variables={
|
|
348
|
+
"comparison_pairs": comparison_pairs,
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
interactions = extract_interactions_from_request_interaction_data_models(
|
|
353
|
+
request_interaction_data_models
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
config = MessageConstructionConfig(
|
|
357
|
+
prompt_manager=prompt_manager,
|
|
358
|
+
system_prompt_config=system_config,
|
|
359
|
+
user_prompt_config=user_config,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return construct_messages_from_interactions(interactions, config)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""GEPA-driven playbook content optimizer.
|
|
2
|
+
|
|
3
|
+
Top-level entry points exported from this package:
|
|
4
|
+
|
|
5
|
+
- ``PlaybookOptimizer.optimize(target)`` — runs one paired-rollout search
|
|
6
|
+
for a single playbook and persists the result.
|
|
7
|
+
- ``PlaybookOptimizationScheduler.get_instance()`` — singleton that
|
|
8
|
+
debounces enqueues from upstream services (the aggregator and the
|
|
9
|
+
user-playbook generation service) and dispatches each ``optimize`` call
|
|
10
|
+
on a worker thread.
|
|
11
|
+
- ``PlaybookOptimizationTarget`` — the (kind, target_id) pair both APIs
|
|
12
|
+
consume.
|
|
13
|
+
|
|
14
|
+
The full doc lives at ``docs/playbook_optimizer_assistant_backends.md``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .optimizer import PlaybookOptimizationTarget, PlaybookOptimizer
|
|
18
|
+
from .scheduler import PlaybookOptimizationScheduler
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"PlaybookOptimizationScheduler",
|
|
22
|
+
"PlaybookOptimizationTarget",
|
|
23
|
+
"PlaybookOptimizer",
|
|
24
|
+
]
|
package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Assistant backends for the playbook optimizer.
|
|
2
|
+
|
|
3
|
+
The playbook optimizer runs paired rollouts (incumbent vs candidate playbook)
|
|
4
|
+
against a "real" assistant. This module hosts the two pluggable backends that
|
|
5
|
+
produce the assistant's reply for a given turn:
|
|
6
|
+
|
|
7
|
+
- ``WebhookAssistant`` — POSTs JSON to a configured URL.
|
|
8
|
+
- ``LocalScriptAssistant`` — spawns a subprocess and exchanges JSON via
|
|
9
|
+
stdin/stdout.
|
|
10
|
+
|
|
11
|
+
Both satisfy ``AssistantCallable``, so ``MultiTurnRollout`` and the GEPA
|
|
12
|
+
adapter never need to know which one is in use. Selection happens once in
|
|
13
|
+
``PlaybookOptimizer._create_assistant`` based on config.
|
|
14
|
+
|
|
15
|
+
Failures from either backend raise ``AssistantFailedError`` (or a subclass);
|
|
16
|
+
the GEPA adapter catches that single base class and turns it into an
|
|
17
|
+
``aborted`` evaluation row instead of crashing the search loop.
|
|
18
|
+
|
|
19
|
+
The file is named ``assistant_webhook.py`` for historical reasons — the
|
|
20
|
+
local-script backend was added later. New code should still import from this
|
|
21
|
+
module.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import subprocess
|
|
28
|
+
import time
|
|
29
|
+
from typing import Any, Protocol
|
|
30
|
+
|
|
31
|
+
import requests
|
|
32
|
+
|
|
33
|
+
from reflexio.models.api_schema.domain import AgentPlaybook
|
|
34
|
+
|
|
35
|
+
from .models import ChatMessage
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AssistantCallable(Protocol):
|
|
39
|
+
"""Shape every assistant backend must satisfy.
|
|
40
|
+
|
|
41
|
+
A backend takes the conversation so far plus the playbook(s) to inject
|
|
42
|
+
and returns the assistant's next reply as a plain string. Errors are
|
|
43
|
+
signalled by raising ``AssistantFailedError`` (or a subclass).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __call__(
|
|
47
|
+
self, messages: list[ChatMessage], playbooks: list[AgentPlaybook]
|
|
48
|
+
) -> str: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AssistantFailedError(Exception):
|
|
52
|
+
"""Base class for any assistant-backend failure.
|
|
53
|
+
|
|
54
|
+
The GEPA adapter catches this single type to absorb backend faults
|
|
55
|
+
(network errors, subprocess crashes, malformed responses) and record
|
|
56
|
+
them as ``verdict='aborted'`` evaluations rather than aborting the
|
|
57
|
+
optimizer run.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WebhookFailedError(AssistantFailedError):
|
|
62
|
+
"""Raised when ``WebhookAssistant`` exhausts its retries."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LocalScriptFailedError(AssistantFailedError):
|
|
66
|
+
"""Raised when ``LocalScriptAssistant`` exhausts its retries."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _build_payload(
|
|
70
|
+
messages: list[ChatMessage], playbooks: list[AgentPlaybook]
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Build the wire payload shared by both backends.
|
|
73
|
+
|
|
74
|
+
Centralising this guarantees that the webhook body and the script's
|
|
75
|
+
stdin contain the *same* fields in the *same* shape — the only thing
|
|
76
|
+
that differs between the two transports is how the bytes are delivered.
|
|
77
|
+
"""
|
|
78
|
+
return {
|
|
79
|
+
"messages": [message.model_dump() for message in messages],
|
|
80
|
+
"playbooks": [
|
|
81
|
+
{
|
|
82
|
+
"id": pb.agent_playbook_id,
|
|
83
|
+
"content": pb.content,
|
|
84
|
+
"trigger": pb.trigger,
|
|
85
|
+
}
|
|
86
|
+
for pb in playbooks
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class WebhookAssistant:
|
|
92
|
+
"""HTTP assistant backend.
|
|
93
|
+
|
|
94
|
+
POSTs the rollout payload to ``url`` and expects ``{"content": str}`` in
|
|
95
|
+
the response body. Retries any exception (network error, non-2xx,
|
|
96
|
+
malformed JSON, missing field) with exponential backoff up to
|
|
97
|
+
``max_retries`` additional attempts before raising
|
|
98
|
+
``WebhookFailedError``.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
url: Destination URL. The optimizer does not validate the scheme,
|
|
102
|
+
so operators are responsible for ensuring it points at a host
|
|
103
|
+
that satisfies their data-residency requirements.
|
|
104
|
+
auth_header: Sent verbatim as the ``Authorization`` header. Never
|
|
105
|
+
logged.
|
|
106
|
+
timeout_s: Per-request timeout (passed to ``requests.post``).
|
|
107
|
+
max_retries: Number of *additional* attempts after the first try.
|
|
108
|
+
``max_retries=0`` means a single attempt.
|
|
109
|
+
backoff_base_s: Base for the exponential delay
|
|
110
|
+
``backoff_base_s * 2**attempt`` between retries.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
url: str,
|
|
116
|
+
auth_header: str | None,
|
|
117
|
+
timeout_s: int,
|
|
118
|
+
max_retries: int,
|
|
119
|
+
backoff_base_s: float,
|
|
120
|
+
) -> None:
|
|
121
|
+
self.url = url
|
|
122
|
+
self.auth_header = auth_header
|
|
123
|
+
self.timeout_s = timeout_s
|
|
124
|
+
self.max_retries = max_retries
|
|
125
|
+
self.backoff_base_s = backoff_base_s
|
|
126
|
+
|
|
127
|
+
def __call__(
|
|
128
|
+
self, messages: list[ChatMessage], playbooks: list[AgentPlaybook]
|
|
129
|
+
) -> str:
|
|
130
|
+
headers = {"Content-Type": "application/json"}
|
|
131
|
+
if self.auth_header:
|
|
132
|
+
headers["Authorization"] = self.auth_header
|
|
133
|
+
payload = _build_payload(messages, playbooks)
|
|
134
|
+
last_error: Exception | None = None
|
|
135
|
+
for attempt in range(self.max_retries + 1):
|
|
136
|
+
try:
|
|
137
|
+
response = requests.post(
|
|
138
|
+
self.url,
|
|
139
|
+
json=payload,
|
|
140
|
+
headers=headers,
|
|
141
|
+
timeout=self.timeout_s,
|
|
142
|
+
)
|
|
143
|
+
response.raise_for_status()
|
|
144
|
+
data = response.json()
|
|
145
|
+
content = data.get("content")
|
|
146
|
+
if not isinstance(content, str):
|
|
147
|
+
raise WebhookFailedError("assistant webhook returned no content")
|
|
148
|
+
return content
|
|
149
|
+
except Exception as exc: # noqa: PERF203
|
|
150
|
+
last_error = exc
|
|
151
|
+
if attempt >= self.max_retries:
|
|
152
|
+
break
|
|
153
|
+
time.sleep(self.backoff_base_s * (2**attempt))
|
|
154
|
+
# Wrap-and-rethrow ensures the adapter sees a single AssistantFailedError
|
|
155
|
+
# subclass regardless of which underlying exception triggered the failure.
|
|
156
|
+
raise WebhookFailedError(str(last_error)) from last_error
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class LocalScriptAssistant:
|
|
160
|
+
"""Subprocess assistant backend.
|
|
161
|
+
|
|
162
|
+
Spawns ``[script_path, *script_args]`` per turn and exchanges JSON over
|
|
163
|
+
stdin/stdout. The script must:
|
|
164
|
+
|
|
165
|
+
1. Read a single JSON object from stdin matching ``_build_payload``'s
|
|
166
|
+
shape (``{"messages": [...], "playbooks": [...]}``).
|
|
167
|
+
2. Print a single JSON object to stdout containing at least
|
|
168
|
+
``{"content": "<assistant reply>"}``.
|
|
169
|
+
3. Exit with code 0 on success. Any non-zero exit, malformed JSON,
|
|
170
|
+
missing/non-string ``content``, or timeout is treated as a failure
|
|
171
|
+
and counted against ``max_retries``.
|
|
172
|
+
|
|
173
|
+
The command is built as a list and passed to ``subprocess.run`` *without*
|
|
174
|
+
``shell=True``, so config values cannot inject shell metacharacters.
|
|
175
|
+
Retry/timeout/backoff semantics mirror ``WebhookAssistant``.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
script_path: str,
|
|
181
|
+
script_args: list[str],
|
|
182
|
+
timeout_s: int,
|
|
183
|
+
max_retries: int,
|
|
184
|
+
backoff_base_s: float,
|
|
185
|
+
) -> None:
|
|
186
|
+
self.command = [script_path, *script_args]
|
|
187
|
+
self.timeout_s = timeout_s
|
|
188
|
+
self.max_retries = max_retries
|
|
189
|
+
self.backoff_base_s = backoff_base_s
|
|
190
|
+
|
|
191
|
+
def __call__(
|
|
192
|
+
self, messages: list[ChatMessage], playbooks: list[AgentPlaybook]
|
|
193
|
+
) -> str:
|
|
194
|
+
payload = _build_payload(messages, playbooks)
|
|
195
|
+
stdin = json.dumps(payload, ensure_ascii=False)
|
|
196
|
+
last_error: Exception | None = None
|
|
197
|
+
for attempt in range(self.max_retries + 1):
|
|
198
|
+
try:
|
|
199
|
+
# shell=False (the default) is intentional — see class docstring.
|
|
200
|
+
result = subprocess.run( # noqa: S603
|
|
201
|
+
self.command,
|
|
202
|
+
input=stdin,
|
|
203
|
+
text=True,
|
|
204
|
+
capture_output=True,
|
|
205
|
+
timeout=self.timeout_s,
|
|
206
|
+
check=False,
|
|
207
|
+
)
|
|
208
|
+
if result.returncode != 0:
|
|
209
|
+
# Truncate stderr to keep error messages bounded — long
|
|
210
|
+
# tracebacks would otherwise blow up DB rows and logs.
|
|
211
|
+
raise LocalScriptFailedError(
|
|
212
|
+
"assistant script exited with code "
|
|
213
|
+
f"{result.returncode}: {_truncate(result.stderr)}"
|
|
214
|
+
)
|
|
215
|
+
try:
|
|
216
|
+
data = json.loads(result.stdout)
|
|
217
|
+
except json.JSONDecodeError as exc:
|
|
218
|
+
raise LocalScriptFailedError(
|
|
219
|
+
f"assistant script returned invalid JSON: {_truncate(result.stdout)}"
|
|
220
|
+
) from exc
|
|
221
|
+
content = data.get("content") if isinstance(data, dict) else None
|
|
222
|
+
if not isinstance(content, str):
|
|
223
|
+
raise LocalScriptFailedError("assistant script returned no content")
|
|
224
|
+
return content
|
|
225
|
+
except subprocess.TimeoutExpired:
|
|
226
|
+
# Timeouts come up via a different exception class than the
|
|
227
|
+
# validation errors above, but feed into the same retry budget.
|
|
228
|
+
last_error = LocalScriptFailedError(
|
|
229
|
+
f"assistant script timed out after {self.timeout_s}s"
|
|
230
|
+
)
|
|
231
|
+
if attempt >= self.max_retries:
|
|
232
|
+
break
|
|
233
|
+
time.sleep(self.backoff_base_s * (2**attempt))
|
|
234
|
+
except Exception as exc: # noqa: PERF203
|
|
235
|
+
last_error = exc
|
|
236
|
+
if attempt >= self.max_retries:
|
|
237
|
+
break
|
|
238
|
+
time.sleep(self.backoff_base_s * (2**attempt))
|
|
239
|
+
raise LocalScriptFailedError(str(last_error)) from last_error
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _truncate(value: str, limit: int = 1000) -> str:
|
|
243
|
+
"""Cap a string for inclusion in error messages and stored rows."""
|
|
244
|
+
if len(value) <= limit:
|
|
245
|
+
return value
|
|
246
|
+
return f"{value[:limit]}..."
|