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
|
@@ -0,0 +1,2613 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable, Coroutine, Sequence
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from typing import Any, TypeVar
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from reflexio.defaults import DEFAULT_AGENT_VERSION
|
|
17
|
+
from reflexio.models.api_schema.retriever_schema import (
|
|
18
|
+
ConversationTurn,
|
|
19
|
+
GetAgentPlaybooksRequest,
|
|
20
|
+
GetAgentPlaybooksViewResponse,
|
|
21
|
+
GetAgentSuccessEvaluationResultsRequest,
|
|
22
|
+
GetEvaluationResultsViewResponse,
|
|
23
|
+
GetInteractionsRequest,
|
|
24
|
+
GetInteractionsViewResponse,
|
|
25
|
+
GetProfilesViewResponse,
|
|
26
|
+
GetRequestsRequest,
|
|
27
|
+
GetRequestsViewResponse,
|
|
28
|
+
GetUserPlaybooksRequest,
|
|
29
|
+
GetUserPlaybooksViewResponse,
|
|
30
|
+
GetUserProfilesRequest,
|
|
31
|
+
ProfileChangeLogViewResponse,
|
|
32
|
+
RerankUserProfilesRequest,
|
|
33
|
+
SearchAgentPlaybookRequest,
|
|
34
|
+
SearchAgentPlaybooksViewResponse,
|
|
35
|
+
SearchInteractionRequest,
|
|
36
|
+
SearchInteractionsViewResponse,
|
|
37
|
+
SearchProfilesViewResponse,
|
|
38
|
+
SearchUserPlaybookRequest,
|
|
39
|
+
SearchUserPlaybooksViewResponse,
|
|
40
|
+
SearchUserProfileRequest,
|
|
41
|
+
StorageStatsResponse,
|
|
42
|
+
UnifiedSearchRequest,
|
|
43
|
+
UnifiedSearchViewResponse,
|
|
44
|
+
UpdateAgentPlaybookRequest,
|
|
45
|
+
UpdateAgentPlaybookResponse,
|
|
46
|
+
UpdatePlaybookStatusRequest,
|
|
47
|
+
UpdatePlaybookStatusResponse,
|
|
48
|
+
UpdateUserPlaybookRequest,
|
|
49
|
+
UpdateUserPlaybookResponse,
|
|
50
|
+
)
|
|
51
|
+
from reflexio.models.config_schema import SearchMode
|
|
52
|
+
|
|
53
|
+
IS_TEST_ENV = os.environ.get("IS_TEST_ENV", "false").strip() == "true"
|
|
54
|
+
|
|
55
|
+
BACKEND_URL = "http://127.0.0.1:8000" if IS_TEST_ENV else "https://www.reflexio.ai/"
|
|
56
|
+
|
|
57
|
+
from reflexio.models.api_schema.domain.entities import (
|
|
58
|
+
UpgradeProfilesRequest,
|
|
59
|
+
UpgradeProfilesResponse,
|
|
60
|
+
UpgradeUserPlaybooksRequest,
|
|
61
|
+
UpgradeUserPlaybooksResponse,
|
|
62
|
+
)
|
|
63
|
+
from reflexio.models.api_schema.pending_tool_call_schema import (
|
|
64
|
+
PendingToolCallListResponse,
|
|
65
|
+
PendingToolCallResponse,
|
|
66
|
+
)
|
|
67
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
68
|
+
AddAgentPlaybookRequest,
|
|
69
|
+
AddAgentPlaybookResponse,
|
|
70
|
+
AddUserPlaybookRequest,
|
|
71
|
+
AddUserPlaybookResponse,
|
|
72
|
+
AddUserProfileRequest,
|
|
73
|
+
AddUserProfileResponse,
|
|
74
|
+
AgentPlaybook,
|
|
75
|
+
BulkDeleteResponse,
|
|
76
|
+
ClearUserDataRequest,
|
|
77
|
+
ClearUserDataResponse,
|
|
78
|
+
DeleteAgentPlaybookRequest,
|
|
79
|
+
DeleteAgentPlaybookResponse,
|
|
80
|
+
DeleteAgentPlaybooksByIdsRequest,
|
|
81
|
+
DeleteProfilesByIdsRequest,
|
|
82
|
+
DeleteRequestRequest,
|
|
83
|
+
DeleteRequestResponse,
|
|
84
|
+
DeleteRequestsByIdsRequest,
|
|
85
|
+
DeleteSessionRequest,
|
|
86
|
+
DeleteSessionResponse,
|
|
87
|
+
DeleteUserInteractionRequest,
|
|
88
|
+
DeleteUserInteractionResponse,
|
|
89
|
+
DeleteUserPlaybookRequest,
|
|
90
|
+
DeleteUserPlaybookResponse,
|
|
91
|
+
DeleteUserPlaybooksByIdsRequest,
|
|
92
|
+
DeleteUserProfileRequest,
|
|
93
|
+
DeleteUserProfileResponse,
|
|
94
|
+
GetOperationStatusResponse,
|
|
95
|
+
InteractionData,
|
|
96
|
+
ManualPlaybookGenerationRequest,
|
|
97
|
+
ManualProfileGenerationRequest,
|
|
98
|
+
MyConfigResponse,
|
|
99
|
+
OperationStatus,
|
|
100
|
+
PlaybookStatus,
|
|
101
|
+
PublishUserInteractionRequest,
|
|
102
|
+
PublishUserInteractionResponse,
|
|
103
|
+
RerunPlaybookGenerationRequest,
|
|
104
|
+
RerunPlaybookGenerationResponse,
|
|
105
|
+
RerunProfileGenerationRequest,
|
|
106
|
+
RerunProfileGenerationResponse,
|
|
107
|
+
RunPlaybookAggregationRequest,
|
|
108
|
+
RunPlaybookAggregationResponse,
|
|
109
|
+
Status,
|
|
110
|
+
UserPlaybook,
|
|
111
|
+
UserProfile,
|
|
112
|
+
WhoamiResponse,
|
|
113
|
+
)
|
|
114
|
+
from reflexio.models.api_schema.stall_state_schema import (
|
|
115
|
+
MarkNotifiedResponse,
|
|
116
|
+
StallStateResponse,
|
|
117
|
+
)
|
|
118
|
+
from reflexio.models.config_schema import Config
|
|
119
|
+
|
|
120
|
+
from .cache import InMemoryCache
|
|
121
|
+
|
|
122
|
+
T = TypeVar("T")
|
|
123
|
+
|
|
124
|
+
logger = logging.getLogger(__name__)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ReflexioAPIError(Exception):
|
|
128
|
+
"""Raised when the API returns a successful HTTP status but the body
|
|
129
|
+
cannot be decoded as JSON.
|
|
130
|
+
|
|
131
|
+
This is the "your REFLEXIO_URL points at a non-API host / a proxy
|
|
132
|
+
returned an HTML error page with status 200" class of failure. The
|
|
133
|
+
error message includes the URL, status code, Content-Type, and a
|
|
134
|
+
truncated body preview so the user can see at a glance what went
|
|
135
|
+
wrong. The CLI's ``handle_errors`` decorator renders this as a
|
|
136
|
+
structured ``CliError`` with ``error_type="api"``.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ReflexioClient:
|
|
141
|
+
"""Client for interacting with the Reflexio API."""
|
|
142
|
+
|
|
143
|
+
# Shared thread pool for all instances to maximize efficiency
|
|
144
|
+
_thread_pool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="reflexio")
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self, api_key: str = "", url_endpoint: str = "", timeout: int = 300
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Initialize the Reflexio client.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
api_key (str): API key for authentication. Falls back to REFLEXIO_API_KEY env var.
|
|
153
|
+
url_endpoint (str): Base URL for the API. Falls back to REFLEXIO_API_URL env var,
|
|
154
|
+
then to the default backend URL.
|
|
155
|
+
timeout (int): Default request timeout in seconds (default 300)
|
|
156
|
+
"""
|
|
157
|
+
self.base_url = (
|
|
158
|
+
url_endpoint or os.environ.get("REFLEXIO_API_URL", "") or BACKEND_URL
|
|
159
|
+
)
|
|
160
|
+
self.api_key = api_key or os.environ.get("REFLEXIO_API_KEY", "")
|
|
161
|
+
self.timeout = timeout
|
|
162
|
+
self.session = requests.Session()
|
|
163
|
+
# Treat any API 3xx as an error instead of silently following.
|
|
164
|
+
# ``requests`` default would demote POST→GET on 302, which has
|
|
165
|
+
# historically turned misconfigured base URLs (e.g. pointing at a
|
|
166
|
+
# marketing site that 302s to its www subdomain) into silent
|
|
167
|
+
# publish losses: the POST became a GET to a marketing page that
|
|
168
|
+
# returned HTML 200, which then crashed ``response.json()``.
|
|
169
|
+
self.session.max_redirects = 0
|
|
170
|
+
self._cache = InMemoryCache()
|
|
171
|
+
|
|
172
|
+
def _get_auth_headers(self) -> dict:
|
|
173
|
+
"""Get authentication headers with Bearer token if api_key is configured.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
dict: Headers with Authorization bearer token, or empty dict if no api_key
|
|
177
|
+
"""
|
|
178
|
+
if self.api_key:
|
|
179
|
+
return {
|
|
180
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
}
|
|
183
|
+
return {}
|
|
184
|
+
|
|
185
|
+
def _get_headers(self) -> dict:
|
|
186
|
+
"""Get default headers for API requests.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
dict: Headers with content-type and optional authorization
|
|
190
|
+
"""
|
|
191
|
+
headers = {"Content-Type": "application/json"}
|
|
192
|
+
if self.api_key:
|
|
193
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
194
|
+
return headers
|
|
195
|
+
|
|
196
|
+
def _convert_to_model(self, data: dict | object, model_class: type[T]) -> T:
|
|
197
|
+
"""Convert dict to model instance if needed.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
data: Either a dict or already an instance of model_class
|
|
201
|
+
model_class: The target class to convert to
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
An instance of model_class
|
|
205
|
+
"""
|
|
206
|
+
if isinstance(data, dict):
|
|
207
|
+
return model_class(**data)
|
|
208
|
+
return data # type: ignore[reportReturnType]
|
|
209
|
+
|
|
210
|
+
def _build_request(
|
|
211
|
+
self,
|
|
212
|
+
request: T | dict | None,
|
|
213
|
+
model_class: type[T],
|
|
214
|
+
**kwargs: Any,
|
|
215
|
+
) -> T:
|
|
216
|
+
"""Build request object from request param or kwargs.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
request: Optional request object or dict
|
|
220
|
+
model_class: The request class to instantiate
|
|
221
|
+
**kwargs: Field values to use if request is None
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
An instance of model_class
|
|
225
|
+
"""
|
|
226
|
+
if request is not None:
|
|
227
|
+
return self._convert_to_model(request, model_class) # type: ignore[reportReturnType]
|
|
228
|
+
# Filter out None values and build from kwargs
|
|
229
|
+
filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
230
|
+
return model_class(**filtered_kwargs)
|
|
231
|
+
|
|
232
|
+
def _fire_and_forget(
|
|
233
|
+
self,
|
|
234
|
+
async_func: Callable[..., Coroutine[Any, Any, Any]],
|
|
235
|
+
*args: Any,
|
|
236
|
+
**kwargs: Any,
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Execute an async request in fire-and-forget mode.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
async_func: Asynchronous function to call
|
|
242
|
+
*args: Positional arguments to pass to the function
|
|
243
|
+
**kwargs: Keyword arguments to pass to the function
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
loop = asyncio.get_running_loop()
|
|
247
|
+
loop.create_task(async_func(*args, **kwargs))
|
|
248
|
+
except RuntimeError:
|
|
249
|
+
self._thread_pool.submit(lambda: asyncio.run(async_func(*args, **kwargs)))
|
|
250
|
+
|
|
251
|
+
async def _make_async_request(
|
|
252
|
+
self, method: str, endpoint: str, headers: dict | None = None, **kwargs: Any
|
|
253
|
+
) -> Any:
|
|
254
|
+
"""Make an async HTTP request to the API."""
|
|
255
|
+
url = urljoin(self.base_url, endpoint)
|
|
256
|
+
|
|
257
|
+
# Merge auth headers with any provided headers
|
|
258
|
+
request_headers = self._get_headers()
|
|
259
|
+
if headers:
|
|
260
|
+
request_headers.update(headers)
|
|
261
|
+
|
|
262
|
+
async with aiohttp.ClientSession() as async_session:
|
|
263
|
+
response = await async_session.request(
|
|
264
|
+
method, url, headers=request_headers, **kwargs
|
|
265
|
+
)
|
|
266
|
+
response.raise_for_status()
|
|
267
|
+
return await response.json()
|
|
268
|
+
|
|
269
|
+
def _make_request(
|
|
270
|
+
self, method: str, endpoint: str, headers: dict | None = None, **kwargs: Any
|
|
271
|
+
) -> Any:
|
|
272
|
+
"""Make an HTTP request to the API and decode the JSON response.
|
|
273
|
+
|
|
274
|
+
Beyond ``requests.raise_for_status()`` this also guards against
|
|
275
|
+
the "2xx with non-JSON body" failure mode — typically a
|
|
276
|
+
misconfigured base URL pointing at a marketing/proxy host that
|
|
277
|
+
returns an HTML error or redirect page with status 200. Those
|
|
278
|
+
cases produce a ``ReflexioAPIError`` with the URL + body
|
|
279
|
+
preview instead of an opaque ``JSONDecodeError`` deep in the
|
|
280
|
+
stack.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
method: HTTP method (GET, POST, DELETE).
|
|
284
|
+
endpoint: API endpoint path.
|
|
285
|
+
headers: Additional headers to include in the request.
|
|
286
|
+
**kwargs: Extra kwargs passed through to ``requests``.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
dict: Decoded JSON response, or ``{}`` for empty 2xx
|
|
290
|
+
bodies (e.g. 204 No Content).
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
requests.HTTPError: For 4xx/5xx status codes.
|
|
294
|
+
ReflexioAPIError: For 2xx responses whose body isn't JSON.
|
|
295
|
+
"""
|
|
296
|
+
url = urljoin(self.base_url, endpoint)
|
|
297
|
+
|
|
298
|
+
# Merge auth headers with any provided headers
|
|
299
|
+
request_headers = self._get_headers()
|
|
300
|
+
if headers:
|
|
301
|
+
request_headers.update(headers)
|
|
302
|
+
|
|
303
|
+
self.session.headers.update(request_headers)
|
|
304
|
+
kwargs.setdefault("timeout", self.timeout)
|
|
305
|
+
response = self.session.request(method, url, **kwargs)
|
|
306
|
+
response.raise_for_status()
|
|
307
|
+
|
|
308
|
+
# Empty body on a successful response (e.g. 204 No Content).
|
|
309
|
+
# Only treat genuine empty bytes as "no content" — MagicMock
|
|
310
|
+
# tests leave ``content`` as a MagicMock and should fall through.
|
|
311
|
+
content = response.content
|
|
312
|
+
if isinstance(content, (bytes, bytearray)) and not content:
|
|
313
|
+
return {}
|
|
314
|
+
|
|
315
|
+
# Content-Type guard is only enforced when the header is a real
|
|
316
|
+
# string. Test fixtures that mock ``Session.request`` without
|
|
317
|
+
# wiring headers return MagicMock values here and should keep
|
|
318
|
+
# going straight to ``.json()`` for backward compatibility.
|
|
319
|
+
content_type_raw = response.headers.get("Content-Type", "")
|
|
320
|
+
if isinstance(content_type_raw, str) and content_type_raw:
|
|
321
|
+
content_type = content_type_raw.lower()
|
|
322
|
+
if "json" not in content_type:
|
|
323
|
+
body_preview = (
|
|
324
|
+
response.text[:200] if isinstance(response.text, str) else ""
|
|
325
|
+
)
|
|
326
|
+
raise ReflexioAPIError(
|
|
327
|
+
f"Expected JSON from {method} {url} but got "
|
|
328
|
+
f"Content-Type={content_type} "
|
|
329
|
+
f"(status {response.status_code}). "
|
|
330
|
+
f"Body preview: {body_preview!r}. "
|
|
331
|
+
"Is REFLEXIO_URL pointing at the API host?"
|
|
332
|
+
)
|
|
333
|
+
try:
|
|
334
|
+
return response.json()
|
|
335
|
+
except requests.exceptions.JSONDecodeError as exc:
|
|
336
|
+
body_preview = response.text[:200] if isinstance(response.text, str) else ""
|
|
337
|
+
raise ReflexioAPIError(
|
|
338
|
+
f"{method} {url} returned status {response.status_code} but "
|
|
339
|
+
f"the body is not valid JSON: {exc}. "
|
|
340
|
+
f"Body preview: {body_preview!r}. "
|
|
341
|
+
"Is REFLEXIO_URL pointing at the API host?"
|
|
342
|
+
) from exc
|
|
343
|
+
|
|
344
|
+
def _publish_interaction_sync(
|
|
345
|
+
self,
|
|
346
|
+
request: PublishUserInteractionRequest,
|
|
347
|
+
wait_for_response: bool = False,
|
|
348
|
+
) -> PublishUserInteractionResponse:
|
|
349
|
+
"""Internal sync method to publish interaction.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
request (PublishUserInteractionRequest): The publish request
|
|
353
|
+
wait_for_response (bool): If True, server processes synchronously and returns real result
|
|
354
|
+
"""
|
|
355
|
+
params = {"wait_for_response": "true"} if wait_for_response else None
|
|
356
|
+
response = self._make_request(
|
|
357
|
+
"POST",
|
|
358
|
+
"/api/publish_interaction",
|
|
359
|
+
json=request.model_dump(),
|
|
360
|
+
params=params,
|
|
361
|
+
)
|
|
362
|
+
return PublishUserInteractionResponse(**response)
|
|
363
|
+
|
|
364
|
+
async def _publish_interaction_async(
|
|
365
|
+
self,
|
|
366
|
+
request: PublishUserInteractionRequest,
|
|
367
|
+
wait_for_response: bool = False,
|
|
368
|
+
) -> PublishUserInteractionResponse:
|
|
369
|
+
"""Internal async method to publish interaction.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
request (PublishUserInteractionRequest): The publish request
|
|
373
|
+
wait_for_response (bool): If True, server processes synchronously and returns real result
|
|
374
|
+
"""
|
|
375
|
+
params = {"wait_for_response": "true"} if wait_for_response else None
|
|
376
|
+
response = await self._make_async_request(
|
|
377
|
+
"POST",
|
|
378
|
+
"/api/publish_interaction",
|
|
379
|
+
json=request.model_dump(),
|
|
380
|
+
params=params,
|
|
381
|
+
)
|
|
382
|
+
return PublishUserInteractionResponse(**response)
|
|
383
|
+
|
|
384
|
+
def publish_interaction(
|
|
385
|
+
self,
|
|
386
|
+
user_id: str,
|
|
387
|
+
interactions: Sequence[InteractionData | dict],
|
|
388
|
+
source: str = "",
|
|
389
|
+
agent_version: str = DEFAULT_AGENT_VERSION,
|
|
390
|
+
session_id: str | None = None,
|
|
391
|
+
wait_for_response: bool = False,
|
|
392
|
+
skip_aggregation: bool = False,
|
|
393
|
+
force_extraction: bool = False,
|
|
394
|
+
override_learning_stall: bool = False,
|
|
395
|
+
metadata: dict[str, Any] | None = None,
|
|
396
|
+
) -> PublishUserInteractionResponse:
|
|
397
|
+
"""Publish user interactions.
|
|
398
|
+
|
|
399
|
+
Always blocks on the HTTP round-trip so the caller can see
|
|
400
|
+
4xx/5xx, JSON-decode errors, and network errors. The
|
|
401
|
+
``wait_for_response`` parameter controls whether the **server**
|
|
402
|
+
processes extraction synchronously; the client-side POST is
|
|
403
|
+
always synchronous.
|
|
404
|
+
|
|
405
|
+
In server-async mode (``wait_for_response=False``), the server
|
|
406
|
+
returns 200 as soon as it has registered a BackgroundTask —
|
|
407
|
+
typically ~100 ms. That's fine to block on from the CLI and
|
|
408
|
+
eliminates a prior fragility where CLI fire-and-forget relied
|
|
409
|
+
on a thread pool whose atexit handler wouldn't run under
|
|
410
|
+
SIGTERM, so publishes from a Claude Code subagent could be
|
|
411
|
+
silently lost.
|
|
412
|
+
|
|
413
|
+
Library users who need a truly non-blocking call can submit
|
|
414
|
+
the request through ``_fire_and_forget`` directly.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
user_id: The user ID.
|
|
418
|
+
interactions: List of interaction data.
|
|
419
|
+
source: The source of the interaction.
|
|
420
|
+
agent_version: The agent version.
|
|
421
|
+
session_id: Optional session ID for grouping requests.
|
|
422
|
+
wait_for_response: If True, the **server** waits for
|
|
423
|
+
extraction to complete before returning (longer HTTP
|
|
424
|
+
call, response includes real profile/playbook counts).
|
|
425
|
+
If False, the server returns immediately after queuing
|
|
426
|
+
extraction. The client blocks on the HTTP round-trip
|
|
427
|
+
in both cases.
|
|
428
|
+
skip_aggregation: If True, extract profiles/playbooks but
|
|
429
|
+
skip aggregation to agent playbooks.
|
|
430
|
+
force_extraction: If True, bypass all extraction gates
|
|
431
|
+
(stride_size, cheap pre-filter, LLM should_run) and
|
|
432
|
+
always run extractors.
|
|
433
|
+
override_learning_stall: If True, run extraction even when
|
|
434
|
+
Reflexio has recorded a provider auth/billing stall. Keep
|
|
435
|
+
this False for automatic hook publishes; use it only for an
|
|
436
|
+
explicit retry after reauth or limit reset.
|
|
437
|
+
metadata: Optional per-request annotations stamped by the
|
|
438
|
+
customer. Mirrored onto the stored ``Request`` row so the
|
|
439
|
+
eval pipeline can read them back. Conventional key:
|
|
440
|
+
``reflexio_retrieval_enabled`` (bool) for F2 group
|
|
441
|
+
assignment. Values must be JSON-encodable.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
PublishUserInteractionResponse: Server response. In
|
|
445
|
+
``wait_for_response=False`` mode this is a bare
|
|
446
|
+
acknowledgement ("Interaction queued for processing")
|
|
447
|
+
without extraction counts; in ``wait_for_response=True``
|
|
448
|
+
mode it includes request_id, storage routing, and
|
|
449
|
+
deltas.
|
|
450
|
+
"""
|
|
451
|
+
interaction_data_list = [
|
|
452
|
+
(
|
|
453
|
+
InteractionData(**interaction_request)
|
|
454
|
+
if isinstance(interaction_request, dict)
|
|
455
|
+
else interaction_request
|
|
456
|
+
)
|
|
457
|
+
for interaction_request in interactions
|
|
458
|
+
]
|
|
459
|
+
request = PublishUserInteractionRequest(
|
|
460
|
+
session_id=session_id,
|
|
461
|
+
user_id=user_id,
|
|
462
|
+
interaction_data_list=interaction_data_list,
|
|
463
|
+
source=source,
|
|
464
|
+
agent_version=agent_version,
|
|
465
|
+
skip_aggregation=skip_aggregation,
|
|
466
|
+
force_extraction=force_extraction,
|
|
467
|
+
override_learning_stall=override_learning_stall,
|
|
468
|
+
metadata=metadata or {},
|
|
469
|
+
)
|
|
470
|
+
result = self._publish_interaction_sync(
|
|
471
|
+
request, wait_for_response=wait_for_response
|
|
472
|
+
)
|
|
473
|
+
self._cache.invalidate("get_profiles")
|
|
474
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
475
|
+
return result
|
|
476
|
+
|
|
477
|
+
def search_interactions(
|
|
478
|
+
self,
|
|
479
|
+
request: SearchInteractionRequest | dict | None = None,
|
|
480
|
+
*,
|
|
481
|
+
user_id: str | None = None,
|
|
482
|
+
request_id: str | None = None,
|
|
483
|
+
query: str | None = None,
|
|
484
|
+
start_time: datetime | None = None,
|
|
485
|
+
end_time: datetime | None = None,
|
|
486
|
+
top_k: int | None = None,
|
|
487
|
+
most_recent_k: int | None = None,
|
|
488
|
+
search_mode: SearchMode | None = None,
|
|
489
|
+
) -> SearchInteractionsViewResponse:
|
|
490
|
+
"""Search for user interactions.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
request (Optional[SearchInteractionRequest]): The search request object (alternative to kwargs)
|
|
494
|
+
user_id (str): The user ID to search for
|
|
495
|
+
request_id (Optional[str]): Filter by specific request ID
|
|
496
|
+
query (Optional[str]): Search query string
|
|
497
|
+
start_time (Optional[datetime]): Filter by start time
|
|
498
|
+
end_time (Optional[datetime]): Filter by end time
|
|
499
|
+
top_k (Optional[int]): Maximum number of results to return
|
|
500
|
+
most_recent_k (Optional[int]): Return most recent k interactions
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
SearchInteractionsViewResponse: Response containing matching interactions
|
|
504
|
+
"""
|
|
505
|
+
req = self._build_request(
|
|
506
|
+
request,
|
|
507
|
+
SearchInteractionRequest,
|
|
508
|
+
user_id=user_id,
|
|
509
|
+
request_id=request_id,
|
|
510
|
+
query=query,
|
|
511
|
+
start_time=start_time,
|
|
512
|
+
end_time=end_time,
|
|
513
|
+
top_k=top_k,
|
|
514
|
+
most_recent_k=most_recent_k,
|
|
515
|
+
search_mode=search_mode,
|
|
516
|
+
)
|
|
517
|
+
response = self._make_request(
|
|
518
|
+
"POST",
|
|
519
|
+
"/api/search_interactions",
|
|
520
|
+
json=req.model_dump(),
|
|
521
|
+
)
|
|
522
|
+
return SearchInteractionsViewResponse(**response)
|
|
523
|
+
|
|
524
|
+
def search_user_profiles(
|
|
525
|
+
self,
|
|
526
|
+
request: SearchUserProfileRequest | dict | None = None,
|
|
527
|
+
*,
|
|
528
|
+
user_id: str | None = None,
|
|
529
|
+
generated_from_request_id: str | None = None,
|
|
530
|
+
query: str | None = None,
|
|
531
|
+
start_time: datetime | None = None,
|
|
532
|
+
end_time: datetime | None = None,
|
|
533
|
+
top_k: int | None = None,
|
|
534
|
+
source: str | None = None,
|
|
535
|
+
custom_feature: str | None = None,
|
|
536
|
+
extractor_name: str | None = None,
|
|
537
|
+
threshold: float | None = None,
|
|
538
|
+
enable_reformulation: bool | None = None,
|
|
539
|
+
search_mode: SearchMode | None = None,
|
|
540
|
+
) -> SearchProfilesViewResponse:
|
|
541
|
+
"""Search for user profiles.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
request (Optional[SearchUserProfileRequest]): The search request object (alternative to kwargs)
|
|
545
|
+
user_id (str): The user ID to search for
|
|
546
|
+
generated_from_request_id (Optional[str]): Filter by request ID that generated the profile
|
|
547
|
+
query (Optional[str]): Search query string
|
|
548
|
+
start_time (Optional[datetime]): Filter by start time
|
|
549
|
+
end_time (Optional[datetime]): Filter by end time
|
|
550
|
+
top_k (Optional[int]): Maximum number of results to return (default: 10)
|
|
551
|
+
source (Optional[str]): Filter by source
|
|
552
|
+
custom_feature (Optional[str]): Filter by custom feature
|
|
553
|
+
extractor_name (Optional[str]): Deprecated compatibility field. Accepted but ignored.
|
|
554
|
+
threshold (Optional[float]): Similarity threshold (default: 0.7)
|
|
555
|
+
enable_reformulation (Optional[bool]): Enable LLM query reformulation (default: False)
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
SearchProfilesViewResponse: Response containing matching profiles
|
|
559
|
+
"""
|
|
560
|
+
req = self._build_request(
|
|
561
|
+
request,
|
|
562
|
+
SearchUserProfileRequest,
|
|
563
|
+
user_id=user_id,
|
|
564
|
+
generated_from_request_id=generated_from_request_id,
|
|
565
|
+
query=query,
|
|
566
|
+
start_time=start_time,
|
|
567
|
+
end_time=end_time,
|
|
568
|
+
top_k=top_k,
|
|
569
|
+
source=source,
|
|
570
|
+
custom_feature=custom_feature,
|
|
571
|
+
extractor_name=extractor_name,
|
|
572
|
+
threshold=threshold,
|
|
573
|
+
enable_reformulation=enable_reformulation,
|
|
574
|
+
search_mode=search_mode,
|
|
575
|
+
)
|
|
576
|
+
response = self._make_request(
|
|
577
|
+
"POST", "/api/search_profiles", json=req.model_dump()
|
|
578
|
+
)
|
|
579
|
+
return SearchProfilesViewResponse(**response)
|
|
580
|
+
|
|
581
|
+
# NOTE: keep signature in sync with search_user_profiles
|
|
582
|
+
def search_profiles(
|
|
583
|
+
self,
|
|
584
|
+
request: SearchUserProfileRequest | dict | None = None,
|
|
585
|
+
*,
|
|
586
|
+
user_id: str | None = None,
|
|
587
|
+
generated_from_request_id: str | None = None,
|
|
588
|
+
query: str | None = None,
|
|
589
|
+
start_time: datetime | None = None,
|
|
590
|
+
end_time: datetime | None = None,
|
|
591
|
+
top_k: int | None = None,
|
|
592
|
+
source: str | None = None,
|
|
593
|
+
custom_feature: str | None = None,
|
|
594
|
+
extractor_name: str | None = None,
|
|
595
|
+
threshold: float | None = None,
|
|
596
|
+
enable_reformulation: bool | None = None,
|
|
597
|
+
search_mode: SearchMode | None = None,
|
|
598
|
+
) -> SearchProfilesViewResponse:
|
|
599
|
+
"""Deprecated alias for :meth:`search_user_profiles`. Will be removed in a future release."""
|
|
600
|
+
warnings.warn(
|
|
601
|
+
"ReflexioClient.search_profiles() is deprecated; use "
|
|
602
|
+
"search_user_profiles() instead.",
|
|
603
|
+
DeprecationWarning,
|
|
604
|
+
stacklevel=2,
|
|
605
|
+
)
|
|
606
|
+
return self.search_user_profiles(
|
|
607
|
+
request,
|
|
608
|
+
user_id=user_id,
|
|
609
|
+
generated_from_request_id=generated_from_request_id,
|
|
610
|
+
query=query,
|
|
611
|
+
start_time=start_time,
|
|
612
|
+
end_time=end_time,
|
|
613
|
+
top_k=top_k,
|
|
614
|
+
source=source,
|
|
615
|
+
custom_feature=custom_feature,
|
|
616
|
+
extractor_name=extractor_name,
|
|
617
|
+
threshold=threshold,
|
|
618
|
+
enable_reformulation=enable_reformulation,
|
|
619
|
+
search_mode=search_mode,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def rerank_user_profiles(
|
|
623
|
+
self,
|
|
624
|
+
request: RerankUserProfilesRequest | dict | None = None,
|
|
625
|
+
*,
|
|
626
|
+
user_id: str | None = None,
|
|
627
|
+
query: str | None = None,
|
|
628
|
+
profile_ids: list[str] | None = None,
|
|
629
|
+
top_k: int | None = None,
|
|
630
|
+
) -> SearchProfilesViewResponse:
|
|
631
|
+
"""Rerank a list of profile ids by query relevance using a cross-encoder.
|
|
632
|
+
|
|
633
|
+
The server fetches each candidate's full content (filtered by
|
|
634
|
+
``user_id``), scores ``(query, content)`` pairs with a CPU
|
|
635
|
+
cross-encoder, and returns the top_k profiles sorted by descending
|
|
636
|
+
score. Profile ids that don't exist for the user are silently dropped.
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
request (Optional[RerankUserProfilesRequest]): The rerank request
|
|
640
|
+
object (alternative to kwargs)
|
|
641
|
+
user_id (Optional[str]): The user whose profiles to rerank
|
|
642
|
+
query (Optional[str]): The reranking query
|
|
643
|
+
profile_ids (Optional[list[str]]): Candidate profile ids to score
|
|
644
|
+
top_k (Optional[int]): Maximum profiles to return (default: 10)
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
SearchProfilesViewResponse: Reranked profiles, top_k entries
|
|
648
|
+
"""
|
|
649
|
+
req = self._build_request(
|
|
650
|
+
request,
|
|
651
|
+
RerankUserProfilesRequest,
|
|
652
|
+
user_id=user_id,
|
|
653
|
+
query=query,
|
|
654
|
+
profile_ids=profile_ids,
|
|
655
|
+
top_k=top_k,
|
|
656
|
+
)
|
|
657
|
+
response = self._make_request(
|
|
658
|
+
"POST", "/api/rerank_user_profiles", json=req.model_dump()
|
|
659
|
+
)
|
|
660
|
+
return SearchProfilesViewResponse(**response)
|
|
661
|
+
|
|
662
|
+
def storage_stats(
|
|
663
|
+
self,
|
|
664
|
+
user_id: str,
|
|
665
|
+
) -> StorageStatsResponse:
|
|
666
|
+
"""Get a quick count of how many profiles/playbooks the user has.
|
|
667
|
+
|
|
668
|
+
Returns counts and the modified-time range across every status —
|
|
669
|
+
useful for sizing ``top_k`` before retrieval.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
user_id (str): The user to inspect.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
StorageStatsResponse: Counts and timestamp range for the user.
|
|
676
|
+
"""
|
|
677
|
+
response = self._make_request(
|
|
678
|
+
"GET", "/api/storage_stats", params={"user_id": user_id}
|
|
679
|
+
)
|
|
680
|
+
return StorageStatsResponse(**response)
|
|
681
|
+
|
|
682
|
+
def search_user_playbooks(
|
|
683
|
+
self,
|
|
684
|
+
request: SearchUserPlaybookRequest | dict | None = None,
|
|
685
|
+
*,
|
|
686
|
+
query: str | None = None,
|
|
687
|
+
user_id: str | None = None,
|
|
688
|
+
agent_version: str | None = None,
|
|
689
|
+
playbook_name: str | None = None,
|
|
690
|
+
start_time: datetime | None = None,
|
|
691
|
+
end_time: datetime | None = None,
|
|
692
|
+
status_filter: list[Status | None] | None = None,
|
|
693
|
+
top_k: int | None = None,
|
|
694
|
+
threshold: float | None = None,
|
|
695
|
+
enable_reformulation: bool | None = None,
|
|
696
|
+
search_mode: SearchMode | None = None,
|
|
697
|
+
) -> SearchUserPlaybooksViewResponse:
|
|
698
|
+
"""Search for user playbooks with semantic/text search and filtering.
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
request (Optional[SearchUserPlaybookRequest]): The search request object (alternative to kwargs)
|
|
702
|
+
query (Optional[str]): Query for semantic/text search
|
|
703
|
+
user_id (Optional[str]): Filter by user (via request_id linkage to requests table)
|
|
704
|
+
agent_version (Optional[str]): Filter by agent version
|
|
705
|
+
playbook_name (Optional[str]): Filter by playbook name
|
|
706
|
+
start_time (Optional[datetime]): Start time for created_at filter
|
|
707
|
+
end_time (Optional[datetime]): End time for created_at filter
|
|
708
|
+
status_filter (Optional[list[Optional[Status]]]): Filter by status (None for CURRENT, PENDING, ARCHIVED)
|
|
709
|
+
top_k (Optional[int]): Maximum number of results to return (default: 10)
|
|
710
|
+
threshold (Optional[float]): Similarity threshold for vector search (default: 0.4)
|
|
711
|
+
enable_reformulation (Optional[bool]): Enable LLM query reformulation (default: False)
|
|
712
|
+
|
|
713
|
+
Returns:
|
|
714
|
+
SearchUserPlaybooksViewResponse: Response containing matching user playbooks
|
|
715
|
+
"""
|
|
716
|
+
req = self._build_request(
|
|
717
|
+
request,
|
|
718
|
+
SearchUserPlaybookRequest,
|
|
719
|
+
query=query,
|
|
720
|
+
user_id=user_id,
|
|
721
|
+
agent_version=agent_version,
|
|
722
|
+
playbook_name=playbook_name,
|
|
723
|
+
start_time=start_time,
|
|
724
|
+
end_time=end_time,
|
|
725
|
+
status_filter=status_filter,
|
|
726
|
+
top_k=top_k,
|
|
727
|
+
threshold=threshold,
|
|
728
|
+
enable_reformulation=enable_reformulation,
|
|
729
|
+
search_mode=search_mode,
|
|
730
|
+
)
|
|
731
|
+
response = self._make_request(
|
|
732
|
+
"POST", "/api/search_user_playbooks", json=req.model_dump()
|
|
733
|
+
)
|
|
734
|
+
return SearchUserPlaybooksViewResponse(**response)
|
|
735
|
+
|
|
736
|
+
def search_agent_playbooks(
|
|
737
|
+
self,
|
|
738
|
+
request: SearchAgentPlaybookRequest | dict | None = None,
|
|
739
|
+
*,
|
|
740
|
+
query: str | None = None,
|
|
741
|
+
agent_version: str | None = None,
|
|
742
|
+
playbook_name: str | None = None,
|
|
743
|
+
start_time: datetime | None = None,
|
|
744
|
+
end_time: datetime | None = None,
|
|
745
|
+
status_filter: list[Status | None] | None = None,
|
|
746
|
+
playbook_status_filter: PlaybookStatus | None = None,
|
|
747
|
+
top_k: int | None = None,
|
|
748
|
+
threshold: float | None = None,
|
|
749
|
+
enable_reformulation: bool | None = None,
|
|
750
|
+
search_mode: SearchMode | None = None,
|
|
751
|
+
) -> SearchAgentPlaybooksViewResponse:
|
|
752
|
+
"""Search for agent playbooks with semantic/text search and filtering.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
request (Optional[SearchAgentPlaybookRequest]): The search request object (alternative to kwargs)
|
|
756
|
+
query (Optional[str]): Query for semantic/text search
|
|
757
|
+
agent_version (Optional[str]): Filter by agent version
|
|
758
|
+
playbook_name (Optional[str]): Filter by playbook name
|
|
759
|
+
start_time (Optional[datetime]): Start time for created_at filter
|
|
760
|
+
end_time (Optional[datetime]): End time for created_at filter
|
|
761
|
+
status_filter (Optional[list[Optional[Status]]]): Filter by status (None for CURRENT, PENDING, ARCHIVED)
|
|
762
|
+
playbook_status_filter (Optional[PlaybookStatus]): Filter by playbook status (PENDING, APPROVED, REJECTED)
|
|
763
|
+
top_k (Optional[int]): Maximum number of results to return (default: 10)
|
|
764
|
+
threshold (Optional[float]): Similarity threshold for vector search (default: 0.4)
|
|
765
|
+
enable_reformulation (Optional[bool]): Enable LLM query reformulation (default: False)
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
SearchAgentPlaybooksViewResponse: Response containing matching agent playbooks
|
|
769
|
+
"""
|
|
770
|
+
req = self._build_request(
|
|
771
|
+
request,
|
|
772
|
+
SearchAgentPlaybookRequest,
|
|
773
|
+
query=query,
|
|
774
|
+
agent_version=agent_version,
|
|
775
|
+
playbook_name=playbook_name,
|
|
776
|
+
start_time=start_time,
|
|
777
|
+
end_time=end_time,
|
|
778
|
+
status_filter=status_filter,
|
|
779
|
+
playbook_status_filter=playbook_status_filter,
|
|
780
|
+
top_k=top_k,
|
|
781
|
+
threshold=threshold,
|
|
782
|
+
enable_reformulation=enable_reformulation,
|
|
783
|
+
search_mode=search_mode,
|
|
784
|
+
)
|
|
785
|
+
response = self._make_request(
|
|
786
|
+
"POST", "/api/search_agent_playbooks", json=req.model_dump()
|
|
787
|
+
)
|
|
788
|
+
return SearchAgentPlaybooksViewResponse(**response)
|
|
789
|
+
|
|
790
|
+
def _delete_profile_sync(
|
|
791
|
+
self, request: DeleteUserProfileRequest
|
|
792
|
+
) -> DeleteUserProfileResponse:
|
|
793
|
+
"""Internal sync method to delete profile."""
|
|
794
|
+
response = self._make_request(
|
|
795
|
+
"DELETE",
|
|
796
|
+
"/api/delete_profile",
|
|
797
|
+
json=request.model_dump(),
|
|
798
|
+
)
|
|
799
|
+
return DeleteUserProfileResponse(**response)
|
|
800
|
+
|
|
801
|
+
async def _delete_profile_async(
|
|
802
|
+
self, request: DeleteUserProfileRequest
|
|
803
|
+
) -> DeleteUserProfileResponse:
|
|
804
|
+
"""Internal async method to delete profile."""
|
|
805
|
+
response = await self._make_async_request(
|
|
806
|
+
"DELETE",
|
|
807
|
+
"/api/delete_profile",
|
|
808
|
+
json=request.model_dump(),
|
|
809
|
+
)
|
|
810
|
+
return DeleteUserProfileResponse(**response)
|
|
811
|
+
|
|
812
|
+
def delete_profile(
|
|
813
|
+
self,
|
|
814
|
+
user_id: str,
|
|
815
|
+
profile_id: str = "",
|
|
816
|
+
search_query: str = "",
|
|
817
|
+
wait_for_response: bool = False,
|
|
818
|
+
) -> DeleteUserProfileResponse | None:
|
|
819
|
+
"""Delete user profiles.
|
|
820
|
+
|
|
821
|
+
This method is optimized for resource efficiency:
|
|
822
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
823
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
user_id (str): The user ID
|
|
827
|
+
profile_id (str, optional): Specific profile ID to delete
|
|
828
|
+
search_query (str, optional): Query to match profiles for deletion
|
|
829
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
Optional[DeleteUserProfileResponse]: Response containing success status and message if wait_for_response=True, None otherwise
|
|
833
|
+
"""
|
|
834
|
+
request = DeleteUserProfileRequest(
|
|
835
|
+
user_id=user_id,
|
|
836
|
+
profile_id=profile_id,
|
|
837
|
+
search_query=search_query,
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
self._cache.invalidate("get_profiles")
|
|
841
|
+
if wait_for_response:
|
|
842
|
+
# Synchronous blocking call
|
|
843
|
+
return self._delete_profile_sync(request)
|
|
844
|
+
# Non-blocking fire-and-forget
|
|
845
|
+
self._fire_and_forget(self._delete_profile_async, request)
|
|
846
|
+
return None
|
|
847
|
+
|
|
848
|
+
def _delete_interaction_sync(
|
|
849
|
+
self, request: DeleteUserInteractionRequest
|
|
850
|
+
) -> DeleteUserInteractionResponse:
|
|
851
|
+
"""Internal sync method to delete interaction."""
|
|
852
|
+
response = self._make_request(
|
|
853
|
+
"DELETE",
|
|
854
|
+
"/api/delete_interaction",
|
|
855
|
+
json=request.model_dump(),
|
|
856
|
+
)
|
|
857
|
+
return DeleteUserInteractionResponse(**response)
|
|
858
|
+
|
|
859
|
+
async def _delete_interaction_async(
|
|
860
|
+
self, request: DeleteUserInteractionRequest
|
|
861
|
+
) -> DeleteUserInteractionResponse:
|
|
862
|
+
"""Internal async method to delete interaction."""
|
|
863
|
+
response = await self._make_async_request(
|
|
864
|
+
"DELETE",
|
|
865
|
+
"/api/delete_interaction",
|
|
866
|
+
json=request.model_dump(),
|
|
867
|
+
)
|
|
868
|
+
return DeleteUserInteractionResponse(**response)
|
|
869
|
+
|
|
870
|
+
def delete_interaction(
|
|
871
|
+
self, user_id: str, interaction_id: int, wait_for_response: bool = False
|
|
872
|
+
) -> DeleteUserInteractionResponse | None:
|
|
873
|
+
"""Delete a user interaction.
|
|
874
|
+
|
|
875
|
+
This method is optimized for resource efficiency:
|
|
876
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
877
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
878
|
+
|
|
879
|
+
Args:
|
|
880
|
+
user_id (str): The user ID
|
|
881
|
+
interaction_id (int): The interaction ID to delete
|
|
882
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
Optional[DeleteUserInteractionResponse]: Response containing success status and message if wait_for_response=True, None otherwise
|
|
886
|
+
"""
|
|
887
|
+
request = DeleteUserInteractionRequest(
|
|
888
|
+
user_id=user_id,
|
|
889
|
+
interaction_id=interaction_id,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
if wait_for_response:
|
|
893
|
+
# Synchronous blocking call
|
|
894
|
+
return self._delete_interaction_sync(request)
|
|
895
|
+
# Non-blocking fire-and-forget
|
|
896
|
+
self._fire_and_forget(self._delete_interaction_async, request)
|
|
897
|
+
return None
|
|
898
|
+
|
|
899
|
+
def _delete_request_sync(
|
|
900
|
+
self, request: DeleteRequestRequest
|
|
901
|
+
) -> DeleteRequestResponse:
|
|
902
|
+
"""Internal sync method to delete request."""
|
|
903
|
+
response = self._make_request(
|
|
904
|
+
"DELETE",
|
|
905
|
+
"/api/delete_request",
|
|
906
|
+
json=request.model_dump(),
|
|
907
|
+
)
|
|
908
|
+
return DeleteRequestResponse(**response)
|
|
909
|
+
|
|
910
|
+
async def _delete_request_async(
|
|
911
|
+
self, request: DeleteRequestRequest
|
|
912
|
+
) -> DeleteRequestResponse:
|
|
913
|
+
"""Internal async method to delete request."""
|
|
914
|
+
response = await self._make_async_request(
|
|
915
|
+
"DELETE",
|
|
916
|
+
"/api/delete_request",
|
|
917
|
+
json=request.model_dump(),
|
|
918
|
+
)
|
|
919
|
+
return DeleteRequestResponse(**response)
|
|
920
|
+
|
|
921
|
+
def delete_request(
|
|
922
|
+
self, request_id: str, wait_for_response: bool = False
|
|
923
|
+
) -> DeleteRequestResponse | None:
|
|
924
|
+
"""Delete a request and all its associated interactions.
|
|
925
|
+
|
|
926
|
+
This method is optimized for resource efficiency:
|
|
927
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
928
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
request_id (str): The request ID to delete
|
|
932
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
Optional[DeleteRequestResponse]: Response containing success status and message if wait_for_response=True, None otherwise
|
|
936
|
+
"""
|
|
937
|
+
request = DeleteRequestRequest(request_id=request_id)
|
|
938
|
+
|
|
939
|
+
if wait_for_response:
|
|
940
|
+
# Synchronous blocking call
|
|
941
|
+
return self._delete_request_sync(request)
|
|
942
|
+
# Non-blocking fire-and-forget
|
|
943
|
+
self._fire_and_forget(self._delete_request_async, request)
|
|
944
|
+
return None
|
|
945
|
+
|
|
946
|
+
def _delete_session_sync(
|
|
947
|
+
self, request: DeleteSessionRequest
|
|
948
|
+
) -> DeleteSessionResponse:
|
|
949
|
+
"""Internal sync method to delete session."""
|
|
950
|
+
response = self._make_request(
|
|
951
|
+
"DELETE",
|
|
952
|
+
"/api/delete_session",
|
|
953
|
+
json=request.model_dump(),
|
|
954
|
+
)
|
|
955
|
+
return DeleteSessionResponse(**response)
|
|
956
|
+
|
|
957
|
+
async def _delete_session_async(
|
|
958
|
+
self, request: DeleteSessionRequest
|
|
959
|
+
) -> DeleteSessionResponse:
|
|
960
|
+
"""Internal async method to delete session."""
|
|
961
|
+
response = await self._make_async_request(
|
|
962
|
+
"DELETE",
|
|
963
|
+
"/api/delete_session",
|
|
964
|
+
json=request.model_dump(),
|
|
965
|
+
)
|
|
966
|
+
return DeleteSessionResponse(**response)
|
|
967
|
+
|
|
968
|
+
def delete_session(
|
|
969
|
+
self, session_id: str, wait_for_response: bool = False
|
|
970
|
+
) -> DeleteSessionResponse | None:
|
|
971
|
+
"""Delete all requests and interactions in a session.
|
|
972
|
+
|
|
973
|
+
This method is optimized for resource efficiency:
|
|
974
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
975
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
session_id (str): The session ID to delete
|
|
979
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
980
|
+
|
|
981
|
+
Returns:
|
|
982
|
+
Optional[DeleteSessionResponse]: Response containing success status, message, and deleted count if wait_for_response=True, None otherwise
|
|
983
|
+
"""
|
|
984
|
+
request = DeleteSessionRequest(session_id=session_id)
|
|
985
|
+
|
|
986
|
+
if wait_for_response:
|
|
987
|
+
# Synchronous blocking call
|
|
988
|
+
return self._delete_session_sync(request)
|
|
989
|
+
# Non-blocking fire-and-forget
|
|
990
|
+
self._fire_and_forget(self._delete_session_async, request)
|
|
991
|
+
return None
|
|
992
|
+
|
|
993
|
+
def _delete_agent_playbook_sync(
|
|
994
|
+
self, request: DeleteAgentPlaybookRequest
|
|
995
|
+
) -> DeleteAgentPlaybookResponse:
|
|
996
|
+
"""Internal sync method to delete agent playbook."""
|
|
997
|
+
response = self._make_request(
|
|
998
|
+
"DELETE",
|
|
999
|
+
"/api/delete_agent_playbook",
|
|
1000
|
+
json=request.model_dump(),
|
|
1001
|
+
)
|
|
1002
|
+
return DeleteAgentPlaybookResponse(**response)
|
|
1003
|
+
|
|
1004
|
+
async def _delete_agent_playbook_async(
|
|
1005
|
+
self, request: DeleteAgentPlaybookRequest
|
|
1006
|
+
) -> DeleteAgentPlaybookResponse:
|
|
1007
|
+
"""Internal async method to delete agent playbook."""
|
|
1008
|
+
response = await self._make_async_request(
|
|
1009
|
+
"DELETE",
|
|
1010
|
+
"/api/delete_agent_playbook",
|
|
1011
|
+
json=request.model_dump(),
|
|
1012
|
+
)
|
|
1013
|
+
return DeleteAgentPlaybookResponse(**response)
|
|
1014
|
+
|
|
1015
|
+
def delete_agent_playbook(
|
|
1016
|
+
self, agent_playbook_id: int, wait_for_response: bool = False
|
|
1017
|
+
) -> DeleteAgentPlaybookResponse | None:
|
|
1018
|
+
"""Delete an agent playbook by ID.
|
|
1019
|
+
|
|
1020
|
+
This method is optimized for resource efficiency:
|
|
1021
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
1022
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
1023
|
+
|
|
1024
|
+
Args:
|
|
1025
|
+
agent_playbook_id (int): The agent playbook ID to delete
|
|
1026
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
1027
|
+
|
|
1028
|
+
Returns:
|
|
1029
|
+
Optional[DeleteAgentPlaybookResponse]: Response containing success status and message if wait_for_response=True, None otherwise
|
|
1030
|
+
"""
|
|
1031
|
+
request = DeleteAgentPlaybookRequest(agent_playbook_id=agent_playbook_id)
|
|
1032
|
+
|
|
1033
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
1034
|
+
if wait_for_response:
|
|
1035
|
+
# Synchronous blocking call
|
|
1036
|
+
return self._delete_agent_playbook_sync(request)
|
|
1037
|
+
# Non-blocking fire-and-forget
|
|
1038
|
+
self._fire_and_forget(self._delete_agent_playbook_async, request)
|
|
1039
|
+
return None
|
|
1040
|
+
|
|
1041
|
+
def _delete_user_playbook_sync(
|
|
1042
|
+
self, request: DeleteUserPlaybookRequest
|
|
1043
|
+
) -> DeleteUserPlaybookResponse:
|
|
1044
|
+
"""Internal sync method to delete user playbook."""
|
|
1045
|
+
response = self._make_request(
|
|
1046
|
+
"DELETE",
|
|
1047
|
+
"/api/delete_user_playbook",
|
|
1048
|
+
json=request.model_dump(),
|
|
1049
|
+
)
|
|
1050
|
+
return DeleteUserPlaybookResponse(**response)
|
|
1051
|
+
|
|
1052
|
+
async def _delete_user_playbook_async(
|
|
1053
|
+
self, request: DeleteUserPlaybookRequest
|
|
1054
|
+
) -> DeleteUserPlaybookResponse:
|
|
1055
|
+
"""Internal async method to delete user playbook."""
|
|
1056
|
+
response = await self._make_async_request(
|
|
1057
|
+
"DELETE",
|
|
1058
|
+
"/api/delete_user_playbook",
|
|
1059
|
+
json=request.model_dump(),
|
|
1060
|
+
)
|
|
1061
|
+
return DeleteUserPlaybookResponse(**response)
|
|
1062
|
+
|
|
1063
|
+
def delete_user_playbook(
|
|
1064
|
+
self, user_playbook_id: int, wait_for_response: bool = False
|
|
1065
|
+
) -> DeleteUserPlaybookResponse | None:
|
|
1066
|
+
"""Delete a user playbook by ID.
|
|
1067
|
+
|
|
1068
|
+
This method is optimized for resource efficiency:
|
|
1069
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
1070
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
1071
|
+
|
|
1072
|
+
Args:
|
|
1073
|
+
user_playbook_id (int): The user playbook ID to delete
|
|
1074
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
1075
|
+
|
|
1076
|
+
Returns:
|
|
1077
|
+
Optional[DeleteUserPlaybookResponse]: Response containing success status and message if wait_for_response=True, None otherwise
|
|
1078
|
+
"""
|
|
1079
|
+
request = DeleteUserPlaybookRequest(user_playbook_id=user_playbook_id)
|
|
1080
|
+
|
|
1081
|
+
if wait_for_response:
|
|
1082
|
+
# Synchronous blocking call
|
|
1083
|
+
return self._delete_user_playbook_sync(request)
|
|
1084
|
+
# Non-blocking fire-and-forget
|
|
1085
|
+
self._fire_and_forget(self._delete_user_playbook_async, request)
|
|
1086
|
+
return None
|
|
1087
|
+
|
|
1088
|
+
def get_profile_change_log(self) -> ProfileChangeLogViewResponse:
|
|
1089
|
+
"""Get profile change log.
|
|
1090
|
+
|
|
1091
|
+
Returns:
|
|
1092
|
+
ProfileChangeLogViewResponse: Response containing profile change log
|
|
1093
|
+
"""
|
|
1094
|
+
response = self._make_request("GET", "/api/profile_change_log")
|
|
1095
|
+
return ProfileChangeLogViewResponse(**response)
|
|
1096
|
+
|
|
1097
|
+
def get_interactions(
|
|
1098
|
+
self,
|
|
1099
|
+
request: GetInteractionsRequest | dict | None = None,
|
|
1100
|
+
*,
|
|
1101
|
+
user_id: str | None = None,
|
|
1102
|
+
start_time: datetime | None = None,
|
|
1103
|
+
end_time: datetime | None = None,
|
|
1104
|
+
top_k: int | None = None,
|
|
1105
|
+
) -> GetInteractionsViewResponse:
|
|
1106
|
+
"""Get user interactions.
|
|
1107
|
+
|
|
1108
|
+
Args:
|
|
1109
|
+
request (Optional[GetInteractionsRequest]): The list request object (alternative to kwargs)
|
|
1110
|
+
user_id (str): The user ID to get interactions for
|
|
1111
|
+
start_time (Optional[datetime]): Filter by start time
|
|
1112
|
+
end_time (Optional[datetime]): Filter by end time
|
|
1113
|
+
top_k (Optional[int]): Maximum number of results to return (default: 30)
|
|
1114
|
+
|
|
1115
|
+
Returns:
|
|
1116
|
+
GetInteractionsViewResponse: Response containing list of interactions
|
|
1117
|
+
"""
|
|
1118
|
+
req = self._build_request(
|
|
1119
|
+
request,
|
|
1120
|
+
GetInteractionsRequest,
|
|
1121
|
+
user_id=user_id,
|
|
1122
|
+
start_time=start_time,
|
|
1123
|
+
end_time=end_time,
|
|
1124
|
+
top_k=top_k,
|
|
1125
|
+
)
|
|
1126
|
+
response = self._make_request(
|
|
1127
|
+
"POST",
|
|
1128
|
+
"/api/get_interactions",
|
|
1129
|
+
json=req.model_dump(),
|
|
1130
|
+
)
|
|
1131
|
+
return GetInteractionsViewResponse(**response)
|
|
1132
|
+
|
|
1133
|
+
def get_profiles(
|
|
1134
|
+
self,
|
|
1135
|
+
request: GetUserProfilesRequest | dict | None = None,
|
|
1136
|
+
force_refresh: bool = False,
|
|
1137
|
+
*,
|
|
1138
|
+
user_id: str | None = None,
|
|
1139
|
+
start_time: datetime | None = None,
|
|
1140
|
+
end_time: datetime | None = None,
|
|
1141
|
+
top_k: int | None = None,
|
|
1142
|
+
status_filter: list[Status | str | None] | None = None,
|
|
1143
|
+
) -> GetProfilesViewResponse:
|
|
1144
|
+
"""Get user profiles.
|
|
1145
|
+
|
|
1146
|
+
Args:
|
|
1147
|
+
request (Optional[GetUserProfilesRequest]): The list request object (alternative to kwargs)
|
|
1148
|
+
force_refresh (bool, optional): If True, bypass cache and fetch fresh data. Defaults to False.
|
|
1149
|
+
user_id (str): The user ID to get profiles for
|
|
1150
|
+
start_time (Optional[datetime]): Filter by start time
|
|
1151
|
+
end_time (Optional[datetime]): Filter by end time
|
|
1152
|
+
top_k (Optional[int]): Maximum number of results to return (default: 30)
|
|
1153
|
+
status_filter (Optional[list[Optional[Union[Status, str]]]]): Filter by profile status. Accepts Status enum or string values (e.g., "archived", "pending").
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
GetProfilesViewResponse: Response containing list of profiles
|
|
1157
|
+
"""
|
|
1158
|
+
# Convert string status values to Status enum
|
|
1159
|
+
converted_status_filter = None
|
|
1160
|
+
if status_filter is not None:
|
|
1161
|
+
converted_status_filter = []
|
|
1162
|
+
for status in status_filter:
|
|
1163
|
+
if status is None:
|
|
1164
|
+
converted_status_filter.append(None)
|
|
1165
|
+
elif isinstance(status, str):
|
|
1166
|
+
converted_status_filter.append(Status(status))
|
|
1167
|
+
else:
|
|
1168
|
+
converted_status_filter.append(status)
|
|
1169
|
+
|
|
1170
|
+
req = self._build_request(
|
|
1171
|
+
request,
|
|
1172
|
+
GetUserProfilesRequest,
|
|
1173
|
+
user_id=user_id,
|
|
1174
|
+
start_time=start_time,
|
|
1175
|
+
end_time=end_time,
|
|
1176
|
+
top_k=top_k,
|
|
1177
|
+
status_filter=converted_status_filter,
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
# Check cache if not forcing refresh
|
|
1181
|
+
if not force_refresh:
|
|
1182
|
+
cached_result = self._cache.get(
|
|
1183
|
+
"get_profiles",
|
|
1184
|
+
user_id=req.user_id,
|
|
1185
|
+
start_time=req.start_time,
|
|
1186
|
+
end_time=req.end_time,
|
|
1187
|
+
top_k=req.top_k,
|
|
1188
|
+
status_filter=req.status_filter,
|
|
1189
|
+
)
|
|
1190
|
+
if cached_result is not None:
|
|
1191
|
+
return cached_result
|
|
1192
|
+
|
|
1193
|
+
# Make API call
|
|
1194
|
+
response = self._make_request(
|
|
1195
|
+
"POST",
|
|
1196
|
+
"/api/get_profiles",
|
|
1197
|
+
json=req.model_dump(),
|
|
1198
|
+
)
|
|
1199
|
+
result = GetProfilesViewResponse(**response)
|
|
1200
|
+
|
|
1201
|
+
# Store in cache
|
|
1202
|
+
self._cache.set(
|
|
1203
|
+
"get_profiles",
|
|
1204
|
+
result,
|
|
1205
|
+
user_id=req.user_id,
|
|
1206
|
+
start_time=req.start_time,
|
|
1207
|
+
end_time=req.end_time,
|
|
1208
|
+
top_k=req.top_k,
|
|
1209
|
+
status_filter=req.status_filter,
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
return result
|
|
1213
|
+
|
|
1214
|
+
def get_all_interactions(
|
|
1215
|
+
self,
|
|
1216
|
+
limit: int = 100,
|
|
1217
|
+
) -> GetInteractionsViewResponse:
|
|
1218
|
+
"""Get all user interactions across all users.
|
|
1219
|
+
|
|
1220
|
+
Args:
|
|
1221
|
+
limit (int, optional): Maximum number of interactions to return. Defaults to 100.
|
|
1222
|
+
|
|
1223
|
+
Returns:
|
|
1224
|
+
GetInteractionsViewResponse: Response containing all user interactions
|
|
1225
|
+
"""
|
|
1226
|
+
response = self._make_request(
|
|
1227
|
+
"GET",
|
|
1228
|
+
f"/api/get_all_interactions?limit={limit}",
|
|
1229
|
+
)
|
|
1230
|
+
return GetInteractionsViewResponse(**response)
|
|
1231
|
+
|
|
1232
|
+
def get_all_profiles(
|
|
1233
|
+
self,
|
|
1234
|
+
limit: int = 100,
|
|
1235
|
+
status_filter: str | None = None,
|
|
1236
|
+
) -> GetProfilesViewResponse:
|
|
1237
|
+
"""Get all user profiles across all users.
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
limit (int, optional): Maximum number of profiles to return. Defaults to 100.
|
|
1241
|
+
status_filter (str, optional): Filter by profile status. Accepts
|
|
1242
|
+
``"current"``, ``"pending"``, or ``"archived"``. If ``None``
|
|
1243
|
+
(the default), profiles with any status are returned.
|
|
1244
|
+
|
|
1245
|
+
Returns:
|
|
1246
|
+
GetProfilesViewResponse: Response containing all user profiles
|
|
1247
|
+
"""
|
|
1248
|
+
from urllib.parse import urlencode
|
|
1249
|
+
|
|
1250
|
+
params: dict[str, str | int] = {"limit": limit}
|
|
1251
|
+
if status_filter:
|
|
1252
|
+
params["status_filter"] = status_filter
|
|
1253
|
+
response = self._make_request(
|
|
1254
|
+
"GET",
|
|
1255
|
+
f"/api/get_all_profiles?{urlencode(params)}",
|
|
1256
|
+
)
|
|
1257
|
+
return GetProfilesViewResponse(**response)
|
|
1258
|
+
|
|
1259
|
+
def set_config(self, config: Config | dict) -> dict:
|
|
1260
|
+
"""Set configuration for the organization.
|
|
1261
|
+
|
|
1262
|
+
Args:
|
|
1263
|
+
config (Union[Config, dict]): The configuration to set
|
|
1264
|
+
|
|
1265
|
+
Returns:
|
|
1266
|
+
dict: Response containing success status and message
|
|
1267
|
+
"""
|
|
1268
|
+
config = self._convert_to_model(config, Config) # type: ignore[reportAssignmentType]
|
|
1269
|
+
return self._make_request(
|
|
1270
|
+
"POST",
|
|
1271
|
+
"/api/set_config",
|
|
1272
|
+
json=config.model_dump(), # type: ignore[reportAttributeAccessIssue]
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
def update_config(self, partial: dict) -> dict:
|
|
1276
|
+
"""Apply a partial (PATCH-style) update to the org config.
|
|
1277
|
+
|
|
1278
|
+
Unlike :meth:`set_config`, this does NOT round-trip the payload
|
|
1279
|
+
through ``Config(**...)`` client-side, so partial updates like
|
|
1280
|
+
``{"shadow_mode_enabled": true}`` succeed without needing
|
|
1281
|
+
the caller to also re-send required fields like ``storage_config``.
|
|
1282
|
+
The server fetches the existing config and shallow-merges
|
|
1283
|
+
atomically — there is no client-side read-modify-write race.
|
|
1284
|
+
|
|
1285
|
+
Nested objects (e.g. ``storage_config``, ``llm_config``) are
|
|
1286
|
+
replaced wholesale; deep merging is intentionally not supported.
|
|
1287
|
+
|
|
1288
|
+
Args:
|
|
1289
|
+
partial: Top-level fields to overlay on the existing config.
|
|
1290
|
+
Unknown keys are dropped server-side by Pydantic.
|
|
1291
|
+
|
|
1292
|
+
Returns:
|
|
1293
|
+
dict: ``{"success": bool, "msg": str}`` from the server.
|
|
1294
|
+
|
|
1295
|
+
Raises:
|
|
1296
|
+
TypeError: If *partial* is not a ``dict``.
|
|
1297
|
+
"""
|
|
1298
|
+
if not isinstance(partial, dict):
|
|
1299
|
+
raise TypeError(
|
|
1300
|
+
f"update_config requires a dict, got {type(partial).__name__}"
|
|
1301
|
+
)
|
|
1302
|
+
return self._make_request("POST", "/api/update_config", json=partial)
|
|
1303
|
+
|
|
1304
|
+
def get_config(self) -> Config:
|
|
1305
|
+
"""Get configuration for the organization.
|
|
1306
|
+
|
|
1307
|
+
Returns:
|
|
1308
|
+
Config: The current configuration
|
|
1309
|
+
"""
|
|
1310
|
+
response = self._make_request(
|
|
1311
|
+
"GET",
|
|
1312
|
+
"/api/get_config",
|
|
1313
|
+
)
|
|
1314
|
+
return Config(**response)
|
|
1315
|
+
|
|
1316
|
+
def invalidate_cache(self, org_id: str | None = None) -> dict:
|
|
1317
|
+
"""Explicitly evict the server-side per-org Reflexio cache entry.
|
|
1318
|
+
|
|
1319
|
+
Useful when the running config has been mutated through a
|
|
1320
|
+
channel the server can't observe (sibling-replica writes,
|
|
1321
|
+
direct DB updates, hand-edited config files on backends that
|
|
1322
|
+
don't support cheap version probing). Phase 1 mtime-based
|
|
1323
|
+
auto-invalidation and Phase 3 DB-version probing cover most
|
|
1324
|
+
cases automatically; this is the manual escape hatch.
|
|
1325
|
+
|
|
1326
|
+
Cross-org invalidation is intentionally not supported: the
|
|
1327
|
+
endpoint only ever invalidates the caller's own org. The
|
|
1328
|
+
``org_id`` argument is a verification token — when provided
|
|
1329
|
+
it must match the token's authenticated org or the server
|
|
1330
|
+
rejects with 403.
|
|
1331
|
+
|
|
1332
|
+
Args:
|
|
1333
|
+
org_id: Optional org_id to verify against the caller's
|
|
1334
|
+
authenticated identity. Omit to let the server
|
|
1335
|
+
resolve it from the auth header.
|
|
1336
|
+
|
|
1337
|
+
Returns:
|
|
1338
|
+
dict: ``{"invalidated": bool, "org_id": str}``. The
|
|
1339
|
+
``invalidated`` flag is False when nothing was cached for
|
|
1340
|
+
the org (still a successful no-op).
|
|
1341
|
+
"""
|
|
1342
|
+
body: dict[str, str] = {}
|
|
1343
|
+
if org_id is not None:
|
|
1344
|
+
body["org_id"] = org_id
|
|
1345
|
+
return self._make_request("POST", "/api/admin/cache/invalidate", json=body)
|
|
1346
|
+
|
|
1347
|
+
def get_user_playbooks(
|
|
1348
|
+
self,
|
|
1349
|
+
request: GetUserPlaybooksRequest | dict | None = None,
|
|
1350
|
+
*,
|
|
1351
|
+
limit: int | None = None,
|
|
1352
|
+
user_id: str | None = None,
|
|
1353
|
+
playbook_name: str | None = None,
|
|
1354
|
+
agent_version: str | None = None,
|
|
1355
|
+
status_filter: list[Status | None] | None = None,
|
|
1356
|
+
) -> GetUserPlaybooksViewResponse:
|
|
1357
|
+
"""Get user playbooks.
|
|
1358
|
+
|
|
1359
|
+
Args:
|
|
1360
|
+
request (Optional[GetUserPlaybooksRequest]): The get request object (alternative to kwargs)
|
|
1361
|
+
limit (Optional[int]): Maximum number of results to return (default: 100)
|
|
1362
|
+
user_id (Optional[str]): Filter by user ID
|
|
1363
|
+
playbook_name (Optional[str]): Filter by playbook name
|
|
1364
|
+
agent_version (Optional[str]): Filter by agent version
|
|
1365
|
+
status_filter (Optional[list[Optional[Status]]]): Filter by status
|
|
1366
|
+
|
|
1367
|
+
Returns:
|
|
1368
|
+
GetUserPlaybooksViewResponse: Response containing user playbooks
|
|
1369
|
+
"""
|
|
1370
|
+
req = self._build_request(
|
|
1371
|
+
request,
|
|
1372
|
+
GetUserPlaybooksRequest,
|
|
1373
|
+
limit=limit,
|
|
1374
|
+
user_id=user_id,
|
|
1375
|
+
playbook_name=playbook_name,
|
|
1376
|
+
agent_version=agent_version,
|
|
1377
|
+
status_filter=status_filter,
|
|
1378
|
+
)
|
|
1379
|
+
response = self._make_request(
|
|
1380
|
+
"POST",
|
|
1381
|
+
"/api/get_user_playbooks",
|
|
1382
|
+
json=req.model_dump(),
|
|
1383
|
+
)
|
|
1384
|
+
return GetUserPlaybooksViewResponse(**response)
|
|
1385
|
+
|
|
1386
|
+
def add_user_playbook(
|
|
1387
|
+
self,
|
|
1388
|
+
user_playbooks: list[UserPlaybook | dict],
|
|
1389
|
+
) -> AddUserPlaybookResponse:
|
|
1390
|
+
"""Add user playbooks directly to storage.
|
|
1391
|
+
|
|
1392
|
+
Args:
|
|
1393
|
+
user_playbooks (list[Union[UserPlaybook, dict]]): List of user playbooks to add.
|
|
1394
|
+
Each user playbook should contain:
|
|
1395
|
+
- agent_version (str): Required. The agent version.
|
|
1396
|
+
- request_id (str): Required. The request ID.
|
|
1397
|
+
- content (str): Required. The playbook content.
|
|
1398
|
+
- playbook_name (str): Optional. The playbook name/category.
|
|
1399
|
+
|
|
1400
|
+
Returns:
|
|
1401
|
+
AddUserPlaybookResponse: Response containing success status, message, and added count.
|
|
1402
|
+
"""
|
|
1403
|
+
# Convert dicts to UserPlaybook objects if needed
|
|
1404
|
+
user_playbook_list = [
|
|
1405
|
+
UserPlaybook(**rf) if isinstance(rf, dict) else rf for rf in user_playbooks
|
|
1406
|
+
]
|
|
1407
|
+
request = AddUserPlaybookRequest(user_playbooks=user_playbook_list)
|
|
1408
|
+
response = self._make_request(
|
|
1409
|
+
"POST",
|
|
1410
|
+
"/api/add_user_playbook",
|
|
1411
|
+
json=request.model_dump(),
|
|
1412
|
+
)
|
|
1413
|
+
return AddUserPlaybookResponse(**response)
|
|
1414
|
+
|
|
1415
|
+
def add_agent_playbooks(
|
|
1416
|
+
self,
|
|
1417
|
+
agent_playbooks: list[AgentPlaybook | dict],
|
|
1418
|
+
) -> AddAgentPlaybookResponse:
|
|
1419
|
+
"""Add agent playbooks directly to storage.
|
|
1420
|
+
|
|
1421
|
+
Args:
|
|
1422
|
+
agent_playbooks (list[Union[AgentPlaybook, dict]]): List of agent playbooks to add.
|
|
1423
|
+
Each agent playbook should contain:
|
|
1424
|
+
- agent_version (str): Required. The agent version.
|
|
1425
|
+
- content (str): Required. The playbook content.
|
|
1426
|
+
- playbook_status (PlaybookStatus): Required. The playbook approval status.
|
|
1427
|
+
- playbook_metadata (str): Required. Metadata about the playbook.
|
|
1428
|
+
- playbook_name (str): Optional. The playbook name/category.
|
|
1429
|
+
|
|
1430
|
+
Returns:
|
|
1431
|
+
AddAgentPlaybookResponse: Response containing success status, message, and added count.
|
|
1432
|
+
"""
|
|
1433
|
+
# Convert dicts to AgentPlaybook objects if needed
|
|
1434
|
+
agent_playbook_list = [
|
|
1435
|
+
AgentPlaybook(**fb) if isinstance(fb, dict) else fb
|
|
1436
|
+
for fb in agent_playbooks
|
|
1437
|
+
]
|
|
1438
|
+
request = AddAgentPlaybookRequest(agent_playbooks=agent_playbook_list)
|
|
1439
|
+
response = self._make_request(
|
|
1440
|
+
"POST",
|
|
1441
|
+
"/api/add_agent_playbook",
|
|
1442
|
+
json=request.model_dump(),
|
|
1443
|
+
)
|
|
1444
|
+
return AddAgentPlaybookResponse(**response)
|
|
1445
|
+
|
|
1446
|
+
@staticmethod
|
|
1447
|
+
def _coerce_user_profile(profile: UserProfile | dict) -> UserProfile:
|
|
1448
|
+
"""Coerce a dict into a ``UserProfile``, filling in missing required fields.
|
|
1449
|
+
|
|
1450
|
+
Required fields on ``UserProfile`` that callers commonly omit
|
|
1451
|
+
(``profile_id``, ``last_modified_timestamp``, ``generated_from_request_id``)
|
|
1452
|
+
are filled in with sensible client-side defaults so that a minimal dict
|
|
1453
|
+
like ``{"user_id": "u", "content": "x"}`` validates successfully.
|
|
1454
|
+
|
|
1455
|
+
Args:
|
|
1456
|
+
profile (Union[UserProfile, dict]): The profile to coerce. If it is
|
|
1457
|
+
already a ``UserProfile`` it is returned unchanged.
|
|
1458
|
+
|
|
1459
|
+
Returns:
|
|
1460
|
+
UserProfile: A fully-populated ``UserProfile`` instance.
|
|
1461
|
+
"""
|
|
1462
|
+
if isinstance(profile, UserProfile):
|
|
1463
|
+
return profile
|
|
1464
|
+
data = dict(profile)
|
|
1465
|
+
data.setdefault("profile_id", f"cli-{uuid.uuid4().hex[:12]}")
|
|
1466
|
+
data.setdefault("last_modified_timestamp", int(datetime.now(UTC).timestamp()))
|
|
1467
|
+
data.setdefault("generated_from_request_id", "cli-manual")
|
|
1468
|
+
data.setdefault("source", "cli-manual")
|
|
1469
|
+
return UserProfile(**data)
|
|
1470
|
+
|
|
1471
|
+
def add_user_profile(
|
|
1472
|
+
self,
|
|
1473
|
+
user_profiles: list[UserProfile | dict],
|
|
1474
|
+
) -> AddUserProfileResponse:
|
|
1475
|
+
"""Add user profiles directly to storage, bypassing inference.
|
|
1476
|
+
|
|
1477
|
+
Mirror of :meth:`add_user_playbook` for the profile resource.
|
|
1478
|
+
Useful for seeding a known fact about the user (testing,
|
|
1479
|
+
migration, manual fact injection) without going through the
|
|
1480
|
+
interaction-based generation pipeline. The server populates
|
|
1481
|
+
the embedding automatically.
|
|
1482
|
+
|
|
1483
|
+
Args:
|
|
1484
|
+
user_profiles (list[Union[UserProfile, dict]]): List of user profiles to add.
|
|
1485
|
+
Each profile must contain at least:
|
|
1486
|
+
- user_id (str): The user the profile belongs to.
|
|
1487
|
+
- content (str): The profile content (used for embedding).
|
|
1488
|
+
When passing dicts, missing required fields are auto-populated
|
|
1489
|
+
client-side with sensible defaults: ``profile_id`` becomes
|
|
1490
|
+
``f"cli-{uuid4.hex[:12]}"``, ``last_modified_timestamp`` is set to
|
|
1491
|
+
``int(datetime.now(UTC).timestamp())``, and
|
|
1492
|
+
``generated_from_request_id`` defaults to ``"cli-manual"``.
|
|
1493
|
+
|
|
1494
|
+
Returns:
|
|
1495
|
+
AddUserProfileResponse: Response containing success status, message, and added count.
|
|
1496
|
+
"""
|
|
1497
|
+
user_profile_list = [self._coerce_user_profile(p) for p in user_profiles]
|
|
1498
|
+
request = AddUserProfileRequest(user_profiles=user_profile_list)
|
|
1499
|
+
response = self._make_request(
|
|
1500
|
+
"POST",
|
|
1501
|
+
"/api/add_user_profile",
|
|
1502
|
+
json=request.model_dump(),
|
|
1503
|
+
)
|
|
1504
|
+
return AddUserProfileResponse(**response)
|
|
1505
|
+
|
|
1506
|
+
def update_user_playbook(
|
|
1507
|
+
self,
|
|
1508
|
+
user_playbook_id: int,
|
|
1509
|
+
*,
|
|
1510
|
+
playbook_name: str | None = None,
|
|
1511
|
+
content: str | None = None,
|
|
1512
|
+
trigger: str | None = None,
|
|
1513
|
+
rationale: str | None = None,
|
|
1514
|
+
) -> UpdateUserPlaybookResponse:
|
|
1515
|
+
"""Update editable fields of a user playbook in place.
|
|
1516
|
+
|
|
1517
|
+
Pass only the fields you want to change. Fields left as
|
|
1518
|
+
``None`` are not touched on the server side.
|
|
1519
|
+
|
|
1520
|
+
Args:
|
|
1521
|
+
user_playbook_id (int): The ID of the user playbook to update.
|
|
1522
|
+
playbook_name (Optional[str]): New playbook category name.
|
|
1523
|
+
content (Optional[str]): New content text.
|
|
1524
|
+
trigger (Optional[str]): New trigger condition.
|
|
1525
|
+
rationale (Optional[str]): New rationale text.
|
|
1526
|
+
|
|
1527
|
+
Returns:
|
|
1528
|
+
UpdateUserPlaybookResponse: Response containing success status and message.
|
|
1529
|
+
"""
|
|
1530
|
+
request = UpdateUserPlaybookRequest(
|
|
1531
|
+
user_playbook_id=user_playbook_id,
|
|
1532
|
+
playbook_name=playbook_name,
|
|
1533
|
+
content=content,
|
|
1534
|
+
trigger=trigger,
|
|
1535
|
+
rationale=rationale,
|
|
1536
|
+
)
|
|
1537
|
+
response = self._make_request(
|
|
1538
|
+
"PUT",
|
|
1539
|
+
"/api/update_user_playbook",
|
|
1540
|
+
json=request.model_dump(),
|
|
1541
|
+
)
|
|
1542
|
+
return UpdateUserPlaybookResponse(**response)
|
|
1543
|
+
|
|
1544
|
+
def update_agent_playbook(
|
|
1545
|
+
self,
|
|
1546
|
+
agent_playbook_id: int,
|
|
1547
|
+
*,
|
|
1548
|
+
playbook_name: str | None = None,
|
|
1549
|
+
content: str | None = None,
|
|
1550
|
+
trigger: str | None = None,
|
|
1551
|
+
rationale: str | None = None,
|
|
1552
|
+
playbook_status: PlaybookStatus | None = None,
|
|
1553
|
+
) -> UpdateAgentPlaybookResponse:
|
|
1554
|
+
"""Update editable fields of an agent playbook in place.
|
|
1555
|
+
|
|
1556
|
+
Pass only the fields you want to change. To change ONLY the
|
|
1557
|
+
approval status, prefer :meth:`update_agent_playbook_status` —
|
|
1558
|
+
it has tighter semantics and a single-purpose endpoint.
|
|
1559
|
+
|
|
1560
|
+
Args:
|
|
1561
|
+
agent_playbook_id (int): The ID of the agent playbook to update.
|
|
1562
|
+
playbook_name (Optional[str]): New playbook category name.
|
|
1563
|
+
content (Optional[str]): New content text.
|
|
1564
|
+
trigger (Optional[str]): New trigger condition.
|
|
1565
|
+
rationale (Optional[str]): New rationale text.
|
|
1566
|
+
playbook_status (Optional[PlaybookStatus]): New approval status.
|
|
1567
|
+
|
|
1568
|
+
Returns:
|
|
1569
|
+
UpdateAgentPlaybookResponse: Response containing success status and message.
|
|
1570
|
+
"""
|
|
1571
|
+
request = UpdateAgentPlaybookRequest(
|
|
1572
|
+
agent_playbook_id=agent_playbook_id,
|
|
1573
|
+
playbook_name=playbook_name,
|
|
1574
|
+
content=content,
|
|
1575
|
+
trigger=trigger,
|
|
1576
|
+
rationale=rationale,
|
|
1577
|
+
playbook_status=playbook_status,
|
|
1578
|
+
)
|
|
1579
|
+
response = self._make_request(
|
|
1580
|
+
"PUT",
|
|
1581
|
+
"/api/update_agent_playbook",
|
|
1582
|
+
json=request.model_dump(),
|
|
1583
|
+
)
|
|
1584
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
1585
|
+
return UpdateAgentPlaybookResponse(**response)
|
|
1586
|
+
|
|
1587
|
+
def update_agent_playbook_status(
|
|
1588
|
+
self,
|
|
1589
|
+
agent_playbook_id: int,
|
|
1590
|
+
*,
|
|
1591
|
+
playbook_status: PlaybookStatus,
|
|
1592
|
+
) -> UpdatePlaybookStatusResponse:
|
|
1593
|
+
"""Update only the approval status of an agent playbook.
|
|
1594
|
+
|
|
1595
|
+
This is the dedicated endpoint for the approval workflow
|
|
1596
|
+
(approve / pending / reject). Use it instead of
|
|
1597
|
+
:meth:`update_agent_playbook` when the only change is the
|
|
1598
|
+
``playbook_status`` — the server enforces tighter validation
|
|
1599
|
+
and writes a smaller change log.
|
|
1600
|
+
|
|
1601
|
+
Args:
|
|
1602
|
+
agent_playbook_id (int): The ID of the agent playbook.
|
|
1603
|
+
playbook_status (PlaybookStatus): New approval status
|
|
1604
|
+
(``PENDING``, ``APPROVED``, or ``REJECTED``).
|
|
1605
|
+
|
|
1606
|
+
Returns:
|
|
1607
|
+
UpdatePlaybookStatusResponse: Response containing success status and message.
|
|
1608
|
+
"""
|
|
1609
|
+
request = UpdatePlaybookStatusRequest(
|
|
1610
|
+
agent_playbook_id=agent_playbook_id,
|
|
1611
|
+
playbook_status=playbook_status,
|
|
1612
|
+
)
|
|
1613
|
+
response = self._make_request(
|
|
1614
|
+
"PUT",
|
|
1615
|
+
"/api/update_agent_playbook_status",
|
|
1616
|
+
json=request.model_dump(),
|
|
1617
|
+
)
|
|
1618
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
1619
|
+
return UpdatePlaybookStatusResponse(**response)
|
|
1620
|
+
|
|
1621
|
+
def get_agent_playbooks(
|
|
1622
|
+
self,
|
|
1623
|
+
request: GetAgentPlaybooksRequest | dict | None = None,
|
|
1624
|
+
force_refresh: bool = False,
|
|
1625
|
+
*,
|
|
1626
|
+
limit: int | None = None,
|
|
1627
|
+
playbook_name: str | None = None,
|
|
1628
|
+
agent_version: str | None = None,
|
|
1629
|
+
status_filter: list[Status | None] | None = None,
|
|
1630
|
+
playbook_status_filter: PlaybookStatus | None = None,
|
|
1631
|
+
) -> GetAgentPlaybooksViewResponse:
|
|
1632
|
+
"""Get agent playbooks.
|
|
1633
|
+
|
|
1634
|
+
Args:
|
|
1635
|
+
request (Optional[GetAgentPlaybooksRequest]): The get request object (alternative to kwargs)
|
|
1636
|
+
force_refresh (bool, optional): If True, bypass cache and fetch fresh data. Defaults to False.
|
|
1637
|
+
limit (Optional[int]): Maximum number of results to return (default: 100)
|
|
1638
|
+
playbook_name (Optional[str]): Filter by playbook name
|
|
1639
|
+
agent_version (Optional[str]): Filter by agent version
|
|
1640
|
+
status_filter (Optional[list[Optional[Status]]]): Filter by status
|
|
1641
|
+
playbook_status_filter (Optional[PlaybookStatus]): Filter by playbook status (default: APPROVED)
|
|
1642
|
+
|
|
1643
|
+
Returns:
|
|
1644
|
+
GetAgentPlaybooksViewResponse: Response containing agent playbooks
|
|
1645
|
+
"""
|
|
1646
|
+
req = self._build_request(
|
|
1647
|
+
request,
|
|
1648
|
+
GetAgentPlaybooksRequest,
|
|
1649
|
+
limit=limit,
|
|
1650
|
+
playbook_name=playbook_name,
|
|
1651
|
+
agent_version=agent_version,
|
|
1652
|
+
status_filter=status_filter,
|
|
1653
|
+
playbook_status_filter=playbook_status_filter,
|
|
1654
|
+
)
|
|
1655
|
+
|
|
1656
|
+
# Check cache if not forcing refresh
|
|
1657
|
+
if not force_refresh:
|
|
1658
|
+
cached_result = self._cache.get(
|
|
1659
|
+
"get_agent_playbooks",
|
|
1660
|
+
limit=req.limit,
|
|
1661
|
+
playbook_name=req.playbook_name,
|
|
1662
|
+
status_filter=req.status_filter,
|
|
1663
|
+
playbook_status_filter=req.playbook_status_filter,
|
|
1664
|
+
)
|
|
1665
|
+
if cached_result is not None:
|
|
1666
|
+
return cached_result
|
|
1667
|
+
|
|
1668
|
+
# Make API call
|
|
1669
|
+
response = self._make_request(
|
|
1670
|
+
"POST",
|
|
1671
|
+
"/api/get_agent_playbooks",
|
|
1672
|
+
json=req.model_dump(),
|
|
1673
|
+
)
|
|
1674
|
+
result = GetAgentPlaybooksViewResponse(**response)
|
|
1675
|
+
|
|
1676
|
+
# Store in cache
|
|
1677
|
+
self._cache.set(
|
|
1678
|
+
"get_agent_playbooks",
|
|
1679
|
+
result,
|
|
1680
|
+
limit=req.limit,
|
|
1681
|
+
playbook_name=req.playbook_name,
|
|
1682
|
+
status_filter=req.status_filter,
|
|
1683
|
+
playbook_status_filter=req.playbook_status_filter,
|
|
1684
|
+
)
|
|
1685
|
+
|
|
1686
|
+
return result
|
|
1687
|
+
|
|
1688
|
+
def get_requests(
|
|
1689
|
+
self,
|
|
1690
|
+
request: GetRequestsRequest | dict | None = None,
|
|
1691
|
+
*,
|
|
1692
|
+
user_id: str | None = None,
|
|
1693
|
+
request_id: str | None = None,
|
|
1694
|
+
start_time: datetime | None = None,
|
|
1695
|
+
end_time: datetime | None = None,
|
|
1696
|
+
top_k: int | None = None,
|
|
1697
|
+
) -> GetRequestsViewResponse:
|
|
1698
|
+
"""Get requests with their associated interactions, grouped by session.
|
|
1699
|
+
|
|
1700
|
+
Args:
|
|
1701
|
+
request (Optional[GetRequestsRequest]): The get request object (alternative to kwargs)
|
|
1702
|
+
user_id (Optional[str]): Filter by user ID
|
|
1703
|
+
request_id (Optional[str]): Filter by request ID
|
|
1704
|
+
start_time (Optional[datetime]): Filter by start time
|
|
1705
|
+
end_time (Optional[datetime]): Filter by end time
|
|
1706
|
+
top_k (Optional[int]): Maximum number of results to return (default: 30)
|
|
1707
|
+
|
|
1708
|
+
Returns:
|
|
1709
|
+
GetRequestsViewResponse: Response containing requests grouped by session with their interactions
|
|
1710
|
+
"""
|
|
1711
|
+
req = self._build_request(
|
|
1712
|
+
request,
|
|
1713
|
+
GetRequestsRequest,
|
|
1714
|
+
user_id=user_id,
|
|
1715
|
+
request_id=request_id,
|
|
1716
|
+
start_time=start_time,
|
|
1717
|
+
end_time=end_time,
|
|
1718
|
+
top_k=top_k,
|
|
1719
|
+
)
|
|
1720
|
+
response = self._make_request(
|
|
1721
|
+
"POST",
|
|
1722
|
+
"/api/get_requests",
|
|
1723
|
+
json=req.model_dump(),
|
|
1724
|
+
)
|
|
1725
|
+
return GetRequestsViewResponse(**response)
|
|
1726
|
+
|
|
1727
|
+
def get_agent_success_evaluation_results(
|
|
1728
|
+
self,
|
|
1729
|
+
request: GetAgentSuccessEvaluationResultsRequest | dict | None = None,
|
|
1730
|
+
*,
|
|
1731
|
+
limit: int | None = None,
|
|
1732
|
+
agent_version: str | None = None,
|
|
1733
|
+
) -> GetEvaluationResultsViewResponse:
|
|
1734
|
+
"""Get agent success evaluation results.
|
|
1735
|
+
|
|
1736
|
+
Args:
|
|
1737
|
+
request (Optional[GetAgentSuccessEvaluationResultsRequest]): The get request object (alternative to kwargs)
|
|
1738
|
+
limit (Optional[int]): Maximum number of results to return (default: 100)
|
|
1739
|
+
agent_version (Optional[str]): Filter by agent version
|
|
1740
|
+
|
|
1741
|
+
Returns:
|
|
1742
|
+
GetEvaluationResultsViewResponse: Response containing agent success evaluation results
|
|
1743
|
+
"""
|
|
1744
|
+
req = self._build_request(
|
|
1745
|
+
request,
|
|
1746
|
+
GetAgentSuccessEvaluationResultsRequest,
|
|
1747
|
+
limit=limit,
|
|
1748
|
+
agent_version=agent_version,
|
|
1749
|
+
)
|
|
1750
|
+
response = self._make_request(
|
|
1751
|
+
"POST",
|
|
1752
|
+
"/api/get_agent_success_evaluation_results",
|
|
1753
|
+
json=req.model_dump(),
|
|
1754
|
+
)
|
|
1755
|
+
return GetEvaluationResultsViewResponse(**response)
|
|
1756
|
+
|
|
1757
|
+
def _poll_operation_status(
|
|
1758
|
+
self,
|
|
1759
|
+
service_name: str,
|
|
1760
|
+
poll_interval: float = 3.0,
|
|
1761
|
+
max_wait: float = 600.0,
|
|
1762
|
+
min_started_at: int | None = None,
|
|
1763
|
+
) -> GetOperationStatusResponse:
|
|
1764
|
+
"""
|
|
1765
|
+
Poll the operation status endpoint until the operation completes, fails, or is cancelled.
|
|
1766
|
+
|
|
1767
|
+
Args:
|
|
1768
|
+
service_name: The service name to poll (e.g. "profile_generation", "playbook_generation")
|
|
1769
|
+
poll_interval: Seconds between polls
|
|
1770
|
+
max_wait: Maximum seconds to wait before raising TimeoutError
|
|
1771
|
+
min_started_at: Optional Unix timestamp. Ignore stale operation
|
|
1772
|
+
states that started before this timestamp.
|
|
1773
|
+
|
|
1774
|
+
Returns:
|
|
1775
|
+
GetOperationStatusResponse: Final operation status
|
|
1776
|
+
"""
|
|
1777
|
+
start = time.monotonic()
|
|
1778
|
+
while True:
|
|
1779
|
+
try:
|
|
1780
|
+
response = self._make_request(
|
|
1781
|
+
"GET",
|
|
1782
|
+
"/api/get_operation_status",
|
|
1783
|
+
params={"service_name": service_name},
|
|
1784
|
+
)
|
|
1785
|
+
except Exception as e:
|
|
1786
|
+
logger.warning("Failed to poll operation status: %s", e)
|
|
1787
|
+
elapsed = time.monotonic() - start
|
|
1788
|
+
if elapsed + poll_interval > max_wait:
|
|
1789
|
+
raise TimeoutError(
|
|
1790
|
+
f"Operation '{service_name}' did not complete within {max_wait}s"
|
|
1791
|
+
) from e
|
|
1792
|
+
time.sleep(poll_interval)
|
|
1793
|
+
continue
|
|
1794
|
+
status_response = GetOperationStatusResponse(**response)
|
|
1795
|
+
op = status_response.operation_status
|
|
1796
|
+
if op and min_started_at is not None and op.started_at < min_started_at:
|
|
1797
|
+
op = None
|
|
1798
|
+
if op and op.status in (
|
|
1799
|
+
OperationStatus.COMPLETED,
|
|
1800
|
+
OperationStatus.FAILED,
|
|
1801
|
+
OperationStatus.CANCELLED,
|
|
1802
|
+
):
|
|
1803
|
+
return status_response
|
|
1804
|
+
elapsed = time.monotonic() - start
|
|
1805
|
+
if elapsed + poll_interval > max_wait:
|
|
1806
|
+
raise TimeoutError(
|
|
1807
|
+
f"Operation '{service_name}' did not complete within {max_wait}s"
|
|
1808
|
+
)
|
|
1809
|
+
time.sleep(poll_interval)
|
|
1810
|
+
|
|
1811
|
+
def _rerun_profile_generation_sync(
|
|
1812
|
+
self, request: RerunProfileGenerationRequest
|
|
1813
|
+
) -> RerunProfileGenerationResponse:
|
|
1814
|
+
"""Internal sync method to rerun profile generation.
|
|
1815
|
+
|
|
1816
|
+
Submits the request, then polls operation status until completion.
|
|
1817
|
+
"""
|
|
1818
|
+
submitted_at = int(time.time())
|
|
1819
|
+
response = self._make_request(
|
|
1820
|
+
"POST",
|
|
1821
|
+
"/api/rerun_profile_generation",
|
|
1822
|
+
json=request.model_dump(),
|
|
1823
|
+
)
|
|
1824
|
+
initial = RerunProfileGenerationResponse(**response)
|
|
1825
|
+
if not initial.success:
|
|
1826
|
+
return initial
|
|
1827
|
+
|
|
1828
|
+
# Poll until the background task completes
|
|
1829
|
+
try:
|
|
1830
|
+
status_response = self._poll_operation_status(
|
|
1831
|
+
"profile_generation", min_started_at=submitted_at
|
|
1832
|
+
)
|
|
1833
|
+
op = status_response.operation_status
|
|
1834
|
+
if op and op.status == OperationStatus.COMPLETED:
|
|
1835
|
+
return RerunProfileGenerationResponse(
|
|
1836
|
+
success=True,
|
|
1837
|
+
msg="Profile generation completed",
|
|
1838
|
+
profiles_generated=op.processed_users,
|
|
1839
|
+
)
|
|
1840
|
+
if op and op.status == OperationStatus.FAILED:
|
|
1841
|
+
return RerunProfileGenerationResponse(
|
|
1842
|
+
success=False,
|
|
1843
|
+
msg=op.error_message or "Profile generation failed",
|
|
1844
|
+
)
|
|
1845
|
+
return RerunProfileGenerationResponse(
|
|
1846
|
+
success=False,
|
|
1847
|
+
msg="Profile generation was cancelled",
|
|
1848
|
+
)
|
|
1849
|
+
except (TimeoutError, Exception) as e:
|
|
1850
|
+
logger.warning("Error while polling profile generation status: %s", e)
|
|
1851
|
+
return initial
|
|
1852
|
+
|
|
1853
|
+
async def _rerun_profile_generation_async(
|
|
1854
|
+
self, request: RerunProfileGenerationRequest
|
|
1855
|
+
) -> RerunProfileGenerationResponse:
|
|
1856
|
+
"""Internal async method to rerun profile generation."""
|
|
1857
|
+
response = await self._make_async_request(
|
|
1858
|
+
"POST",
|
|
1859
|
+
"/api/rerun_profile_generation",
|
|
1860
|
+
json=request.model_dump(),
|
|
1861
|
+
)
|
|
1862
|
+
return RerunProfileGenerationResponse(**response)
|
|
1863
|
+
|
|
1864
|
+
def rerun_profile_generation(
|
|
1865
|
+
self,
|
|
1866
|
+
request: RerunProfileGenerationRequest | dict | None = None,
|
|
1867
|
+
wait_for_response: bool = False,
|
|
1868
|
+
*,
|
|
1869
|
+
user_id: str | None = None,
|
|
1870
|
+
start_time: datetime | None = None,
|
|
1871
|
+
end_time: datetime | None = None,
|
|
1872
|
+
source: str | None = None,
|
|
1873
|
+
extractor_names: list[str] | None = None,
|
|
1874
|
+
) -> RerunProfileGenerationResponse | None:
|
|
1875
|
+
"""Rerun profile generation for users.
|
|
1876
|
+
|
|
1877
|
+
This method is optimized for resource efficiency:
|
|
1878
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
1879
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
1880
|
+
|
|
1881
|
+
Args:
|
|
1882
|
+
request (Optional[RerunProfileGenerationRequest]): The rerun request object (alternative to kwargs)
|
|
1883
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
1884
|
+
user_id (Optional[str]): Specific user ID to rerun for. If None, runs for all users.
|
|
1885
|
+
start_time (Optional[datetime]): Filter interactions by start time.
|
|
1886
|
+
end_time (Optional[datetime]): Filter interactions by end time.
|
|
1887
|
+
source (Optional[str]): Filter interactions by source.
|
|
1888
|
+
extractor_names (Optional[list[str]]): Deprecated compatibility field. Accepted but ignored.
|
|
1889
|
+
|
|
1890
|
+
Returns:
|
|
1891
|
+
Optional[RerunProfileGenerationResponse]: Response containing success status, message, profiles_generated count, and operation_id if wait_for_response=True, None otherwise.
|
|
1892
|
+
"""
|
|
1893
|
+
req = self._build_request(
|
|
1894
|
+
request,
|
|
1895
|
+
RerunProfileGenerationRequest,
|
|
1896
|
+
user_id=user_id,
|
|
1897
|
+
start_time=start_time,
|
|
1898
|
+
end_time=end_time,
|
|
1899
|
+
source=source,
|
|
1900
|
+
extractor_names=extractor_names,
|
|
1901
|
+
)
|
|
1902
|
+
|
|
1903
|
+
if wait_for_response:
|
|
1904
|
+
return self._rerun_profile_generation_sync(req)
|
|
1905
|
+
self._fire_and_forget(self._rerun_profile_generation_async, req)
|
|
1906
|
+
return None
|
|
1907
|
+
|
|
1908
|
+
def upgrade_profiles(
|
|
1909
|
+
self,
|
|
1910
|
+
*,
|
|
1911
|
+
user_id: str | None = None,
|
|
1912
|
+
only_affected_users: bool = True,
|
|
1913
|
+
) -> UpgradeProfilesResponse:
|
|
1914
|
+
"""Promote PENDING profiles to CURRENT, archive old CURRENT, delete old ARCHIVED.
|
|
1915
|
+
|
|
1916
|
+
Args:
|
|
1917
|
+
user_id: Specific user ID to upgrade. If None, upgrades all users.
|
|
1918
|
+
only_affected_users: If True, only upgrade users who have pending profiles.
|
|
1919
|
+
|
|
1920
|
+
Returns:
|
|
1921
|
+
UpgradeProfilesResponse: Counts of archived, promoted, and deleted profiles.
|
|
1922
|
+
"""
|
|
1923
|
+
req = UpgradeProfilesRequest(
|
|
1924
|
+
user_id=user_id,
|
|
1925
|
+
only_affected_users=only_affected_users,
|
|
1926
|
+
)
|
|
1927
|
+
response = self._make_request(
|
|
1928
|
+
"POST",
|
|
1929
|
+
"/api/upgrade_all_profiles",
|
|
1930
|
+
json=req.model_dump(),
|
|
1931
|
+
)
|
|
1932
|
+
return UpgradeProfilesResponse(**response)
|
|
1933
|
+
|
|
1934
|
+
def upgrade_user_playbooks(
|
|
1935
|
+
self,
|
|
1936
|
+
*,
|
|
1937
|
+
agent_version: str | None = None,
|
|
1938
|
+
playbook_name: str | None = None,
|
|
1939
|
+
) -> UpgradeUserPlaybooksResponse:
|
|
1940
|
+
"""Promote PENDING user playbooks to CURRENT, archive old CURRENT, delete old ARCHIVED.
|
|
1941
|
+
|
|
1942
|
+
Args:
|
|
1943
|
+
agent_version: Filter by agent version. If None, upgrades all versions.
|
|
1944
|
+
playbook_name: Filter by playbook name. If None, upgrades all playbooks.
|
|
1945
|
+
|
|
1946
|
+
Returns:
|
|
1947
|
+
UpgradeUserPlaybooksResponse: Counts of archived, promoted, and deleted playbooks.
|
|
1948
|
+
"""
|
|
1949
|
+
req = UpgradeUserPlaybooksRequest(
|
|
1950
|
+
agent_version=agent_version,
|
|
1951
|
+
playbook_name=playbook_name,
|
|
1952
|
+
)
|
|
1953
|
+
response = self._make_request(
|
|
1954
|
+
"POST",
|
|
1955
|
+
"/api/upgrade_all_user_playbooks",
|
|
1956
|
+
json=req.model_dump(),
|
|
1957
|
+
)
|
|
1958
|
+
return UpgradeUserPlaybooksResponse(**response)
|
|
1959
|
+
|
|
1960
|
+
async def _manual_profile_generation_async(
|
|
1961
|
+
self, request: ManualProfileGenerationRequest
|
|
1962
|
+
) -> None:
|
|
1963
|
+
"""Internal async method for manual profile generation."""
|
|
1964
|
+
await self._make_async_request(
|
|
1965
|
+
"POST",
|
|
1966
|
+
"/api/manual_profile_generation",
|
|
1967
|
+
json=request.model_dump(),
|
|
1968
|
+
)
|
|
1969
|
+
|
|
1970
|
+
def manual_profile_generation(
|
|
1971
|
+
self,
|
|
1972
|
+
request: ManualProfileGenerationRequest | dict | None = None,
|
|
1973
|
+
*,
|
|
1974
|
+
user_id: str | None = None,
|
|
1975
|
+
source: str | None = None,
|
|
1976
|
+
extractor_names: list[str] | None = None,
|
|
1977
|
+
) -> None:
|
|
1978
|
+
"""Manually trigger profile generation with window-sized interactions (fire-and-forget).
|
|
1979
|
+
|
|
1980
|
+
Unlike rerun_profile_generation which uses ALL interactions and outputs PENDING status,
|
|
1981
|
+
this method uses window-sized interactions (from window_size config) and
|
|
1982
|
+
outputs profiles with CURRENT status.
|
|
1983
|
+
|
|
1984
|
+
This is a fire-and-forget operation that runs asynchronously in the background.
|
|
1985
|
+
|
|
1986
|
+
Args:
|
|
1987
|
+
request (Optional[ManualProfileGenerationRequest]): The request object (alternative to kwargs)
|
|
1988
|
+
user_id (Optional[str]): Specific user ID to generate for. If None, generates for all users.
|
|
1989
|
+
source (Optional[str]): Filter interactions by source.
|
|
1990
|
+
extractor_names (Optional[list[str]]): Deprecated compatibility field. Accepted but ignored.
|
|
1991
|
+
|
|
1992
|
+
Returns:
|
|
1993
|
+
None: This method always returns None (fire-and-forget).
|
|
1994
|
+
"""
|
|
1995
|
+
req = self._build_request(
|
|
1996
|
+
request,
|
|
1997
|
+
ManualProfileGenerationRequest,
|
|
1998
|
+
user_id=user_id,
|
|
1999
|
+
source=source,
|
|
2000
|
+
extractor_names=extractor_names,
|
|
2001
|
+
)
|
|
2002
|
+
self._fire_and_forget(self._manual_profile_generation_async, req)
|
|
2003
|
+
|
|
2004
|
+
def _rerun_playbook_generation_sync(
|
|
2005
|
+
self, request: RerunPlaybookGenerationRequest
|
|
2006
|
+
) -> RerunPlaybookGenerationResponse:
|
|
2007
|
+
"""Internal sync method to rerun playbook generation.
|
|
2008
|
+
|
|
2009
|
+
Submits the request, then polls operation status until completion.
|
|
2010
|
+
"""
|
|
2011
|
+
submitted_at = int(time.time())
|
|
2012
|
+
response = self._make_request(
|
|
2013
|
+
"POST",
|
|
2014
|
+
"/api/rerun_playbook_generation",
|
|
2015
|
+
json=request.model_dump(),
|
|
2016
|
+
)
|
|
2017
|
+
initial = RerunPlaybookGenerationResponse(**response)
|
|
2018
|
+
if not initial.success:
|
|
2019
|
+
return initial
|
|
2020
|
+
|
|
2021
|
+
# Poll until the background task completes
|
|
2022
|
+
try:
|
|
2023
|
+
status_response = self._poll_operation_status(
|
|
2024
|
+
"playbook_generation", min_started_at=submitted_at
|
|
2025
|
+
)
|
|
2026
|
+
op = status_response.operation_status
|
|
2027
|
+
if op and op.status == OperationStatus.COMPLETED:
|
|
2028
|
+
return RerunPlaybookGenerationResponse(
|
|
2029
|
+
success=True,
|
|
2030
|
+
msg="Playbook generation completed",
|
|
2031
|
+
playbooks_generated=op.processed_users,
|
|
2032
|
+
)
|
|
2033
|
+
if op and op.status == OperationStatus.FAILED:
|
|
2034
|
+
return RerunPlaybookGenerationResponse(
|
|
2035
|
+
success=False,
|
|
2036
|
+
msg=op.error_message or "Playbook generation failed",
|
|
2037
|
+
)
|
|
2038
|
+
return RerunPlaybookGenerationResponse(
|
|
2039
|
+
success=False,
|
|
2040
|
+
msg="Playbook generation was cancelled",
|
|
2041
|
+
)
|
|
2042
|
+
except (TimeoutError, Exception) as e:
|
|
2043
|
+
logger.warning("Error while polling playbook generation status: %s", e)
|
|
2044
|
+
return initial
|
|
2045
|
+
|
|
2046
|
+
async def _rerun_playbook_generation_async(
|
|
2047
|
+
self, request: RerunPlaybookGenerationRequest
|
|
2048
|
+
) -> RerunPlaybookGenerationResponse:
|
|
2049
|
+
"""Internal async method to rerun playbook generation."""
|
|
2050
|
+
response = await self._make_async_request(
|
|
2051
|
+
"POST",
|
|
2052
|
+
"/api/rerun_playbook_generation",
|
|
2053
|
+
json=request.model_dump(),
|
|
2054
|
+
)
|
|
2055
|
+
return RerunPlaybookGenerationResponse(**response)
|
|
2056
|
+
|
|
2057
|
+
def rerun_playbook_generation(
|
|
2058
|
+
self,
|
|
2059
|
+
request: RerunPlaybookGenerationRequest | dict | None = None,
|
|
2060
|
+
wait_for_response: bool = False,
|
|
2061
|
+
*,
|
|
2062
|
+
agent_version: str | None = None,
|
|
2063
|
+
start_time: datetime | None = None,
|
|
2064
|
+
end_time: datetime | None = None,
|
|
2065
|
+
playbook_name: str | None = None,
|
|
2066
|
+
) -> RerunPlaybookGenerationResponse | None:
|
|
2067
|
+
"""Rerun playbook generation for an agent version.
|
|
2068
|
+
|
|
2069
|
+
This method is optimized for resource efficiency:
|
|
2070
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
2071
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
2072
|
+
|
|
2073
|
+
Args:
|
|
2074
|
+
request (Optional[RerunPlaybookGenerationRequest]): The rerun request object (alternative to kwargs)
|
|
2075
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
2076
|
+
agent_version (str): Required. The agent version to evaluate.
|
|
2077
|
+
start_time (Optional[datetime]): Filter by start time.
|
|
2078
|
+
end_time (Optional[datetime]): Filter by end time.
|
|
2079
|
+
playbook_name (Optional[str]): Deprecated compatibility field. Accepted but ignored.
|
|
2080
|
+
|
|
2081
|
+
Returns:
|
|
2082
|
+
Optional[RerunPlaybookGenerationResponse]: Response containing success status, message, playbooks_generated count, and operation_id if wait_for_response=True, None otherwise.
|
|
2083
|
+
"""
|
|
2084
|
+
req = self._build_request(
|
|
2085
|
+
request,
|
|
2086
|
+
RerunPlaybookGenerationRequest,
|
|
2087
|
+
agent_version=agent_version,
|
|
2088
|
+
start_time=start_time,
|
|
2089
|
+
end_time=end_time,
|
|
2090
|
+
playbook_name=playbook_name,
|
|
2091
|
+
)
|
|
2092
|
+
|
|
2093
|
+
if wait_for_response:
|
|
2094
|
+
return self._rerun_playbook_generation_sync(req)
|
|
2095
|
+
self._fire_and_forget(self._rerun_playbook_generation_async, req)
|
|
2096
|
+
return None
|
|
2097
|
+
|
|
2098
|
+
async def _manual_playbook_generation_async(
|
|
2099
|
+
self, request: ManualPlaybookGenerationRequest
|
|
2100
|
+
) -> None:
|
|
2101
|
+
"""Internal async method for manual playbook generation."""
|
|
2102
|
+
await self._make_async_request(
|
|
2103
|
+
"POST",
|
|
2104
|
+
"/api/manual_playbook_generation",
|
|
2105
|
+
json=request.model_dump(),
|
|
2106
|
+
)
|
|
2107
|
+
|
|
2108
|
+
def manual_playbook_generation(
|
|
2109
|
+
self,
|
|
2110
|
+
request: ManualPlaybookGenerationRequest | dict | None = None,
|
|
2111
|
+
*,
|
|
2112
|
+
agent_version: str | None = None,
|
|
2113
|
+
source: str | None = None,
|
|
2114
|
+
playbook_name: str | None = None,
|
|
2115
|
+
) -> None:
|
|
2116
|
+
"""Manually trigger playbook generation with window-sized interactions (fire-and-forget).
|
|
2117
|
+
|
|
2118
|
+
Unlike rerun_playbook_generation which uses ALL interactions and outputs PENDING status,
|
|
2119
|
+
this method uses window-sized interactions (from window_size config) and
|
|
2120
|
+
outputs playbooks with CURRENT status.
|
|
2121
|
+
|
|
2122
|
+
This is a fire-and-forget operation that runs asynchronously in the background.
|
|
2123
|
+
|
|
2124
|
+
Args:
|
|
2125
|
+
request (Optional[ManualPlaybookGenerationRequest]): The request object (alternative to kwargs)
|
|
2126
|
+
agent_version (str): Required. The agent version to evaluate.
|
|
2127
|
+
source (Optional[str]): Filter interactions by source.
|
|
2128
|
+
playbook_name (Optional[str]): Deprecated compatibility field. Accepted but ignored.
|
|
2129
|
+
|
|
2130
|
+
Returns:
|
|
2131
|
+
None: This method always returns None (fire-and-forget).
|
|
2132
|
+
"""
|
|
2133
|
+
req = self._build_request(
|
|
2134
|
+
request,
|
|
2135
|
+
ManualPlaybookGenerationRequest,
|
|
2136
|
+
agent_version=agent_version,
|
|
2137
|
+
source=source,
|
|
2138
|
+
playbook_name=playbook_name,
|
|
2139
|
+
)
|
|
2140
|
+
self._fire_and_forget(self._manual_playbook_generation_async, req)
|
|
2141
|
+
|
|
2142
|
+
def _run_playbook_aggregation_sync(
|
|
2143
|
+
self, request: RunPlaybookAggregationRequest
|
|
2144
|
+
) -> RunPlaybookAggregationResponse:
|
|
2145
|
+
"""Internal sync method to run playbook aggregation."""
|
|
2146
|
+
response = self._make_request(
|
|
2147
|
+
"POST",
|
|
2148
|
+
"/api/run_playbook_aggregation",
|
|
2149
|
+
json=request.model_dump(),
|
|
2150
|
+
)
|
|
2151
|
+
return RunPlaybookAggregationResponse(**response)
|
|
2152
|
+
|
|
2153
|
+
async def _run_playbook_aggregation_async(
|
|
2154
|
+
self, request: RunPlaybookAggregationRequest
|
|
2155
|
+
) -> RunPlaybookAggregationResponse:
|
|
2156
|
+
"""Internal async method to run playbook aggregation."""
|
|
2157
|
+
response = await self._make_async_request(
|
|
2158
|
+
"POST",
|
|
2159
|
+
"/api/run_playbook_aggregation",
|
|
2160
|
+
json=request.model_dump(),
|
|
2161
|
+
)
|
|
2162
|
+
return RunPlaybookAggregationResponse(**response)
|
|
2163
|
+
|
|
2164
|
+
def run_playbook_aggregation(
|
|
2165
|
+
self,
|
|
2166
|
+
request: RunPlaybookAggregationRequest | dict | None = None,
|
|
2167
|
+
wait_for_response: bool = False,
|
|
2168
|
+
*,
|
|
2169
|
+
agent_version: str | None = None,
|
|
2170
|
+
playbook_name: str | None = None,
|
|
2171
|
+
) -> RunPlaybookAggregationResponse | None:
|
|
2172
|
+
"""Run playbook aggregation to cluster similar user playbooks.
|
|
2173
|
+
|
|
2174
|
+
This method is optimized for resource efficiency:
|
|
2175
|
+
- In async contexts (e.g., FastAPI): Uses existing event loop (most efficient)
|
|
2176
|
+
- In sync contexts: Uses shared thread pool (avoids thread creation overhead)
|
|
2177
|
+
|
|
2178
|
+
Args:
|
|
2179
|
+
request (Optional[RunPlaybookAggregationRequest]): The aggregation request object (alternative to kwargs)
|
|
2180
|
+
wait_for_response (bool, optional): If True, wait for response. If False, send request without waiting. Defaults to False.
|
|
2181
|
+
agent_version (str): Required. The agent version.
|
|
2182
|
+
playbook_name (Optional[str]): Deprecated compatibility field. Accepted but ignored.
|
|
2183
|
+
|
|
2184
|
+
Returns:
|
|
2185
|
+
Optional[RunPlaybookAggregationResponse]: Response containing success status and message if wait_for_response=True, None otherwise.
|
|
2186
|
+
"""
|
|
2187
|
+
req = self._build_request(
|
|
2188
|
+
request,
|
|
2189
|
+
RunPlaybookAggregationRequest,
|
|
2190
|
+
agent_version=agent_version,
|
|
2191
|
+
playbook_name=playbook_name,
|
|
2192
|
+
)
|
|
2193
|
+
|
|
2194
|
+
if wait_for_response:
|
|
2195
|
+
return self._run_playbook_aggregation_sync(req)
|
|
2196
|
+
self._fire_and_forget(self._run_playbook_aggregation_async, req)
|
|
2197
|
+
return None
|
|
2198
|
+
|
|
2199
|
+
def search(
|
|
2200
|
+
self,
|
|
2201
|
+
request: UnifiedSearchRequest | dict | None = None,
|
|
2202
|
+
*,
|
|
2203
|
+
query: str | None = None,
|
|
2204
|
+
top_k: int | None = None,
|
|
2205
|
+
threshold: float | None = None,
|
|
2206
|
+
agent_version: str | None = None,
|
|
2207
|
+
playbook_name: str | None = None,
|
|
2208
|
+
user_id: str | None = None,
|
|
2209
|
+
entity_types: list[str] | None = None,
|
|
2210
|
+
agent_playbook_status_filter: list[PlaybookStatus | str] | None = None,
|
|
2211
|
+
enable_reformulation: bool | None = None,
|
|
2212
|
+
enable_agent_answer: bool | None = None,
|
|
2213
|
+
conversation_history: list[ConversationTurn] | list[dict] | None = None,
|
|
2214
|
+
search_mode: SearchMode | str | None = None,
|
|
2215
|
+
) -> UnifiedSearchViewResponse:
|
|
2216
|
+
"""Search across all entity types (profiles, agent playbooks, user playbooks).
|
|
2217
|
+
|
|
2218
|
+
Runs query reformulation and searches all entity types in parallel.
|
|
2219
|
+
Query reformulation is controlled per-request via the enable_reformulation parameter.
|
|
2220
|
+
|
|
2221
|
+
Args:
|
|
2222
|
+
request (Optional[UnifiedSearchRequest]): The search request object (alternative to kwargs)
|
|
2223
|
+
query (str): Search query text
|
|
2224
|
+
top_k (Optional[int]): Maximum results per entity type (default: 5)
|
|
2225
|
+
threshold (Optional[float]): Similarity threshold for vector search (default: 0.3)
|
|
2226
|
+
agent_version (Optional[str]): Filter by agent version (agent_playbooks, user_playbooks)
|
|
2227
|
+
playbook_name (Optional[str]): Filter by playbook name (agent_playbooks, user_playbooks)
|
|
2228
|
+
user_id (Optional[str]): Filter by user ID (profiles, user_playbooks)
|
|
2229
|
+
entity_types (Optional[list[str]]): Entity types to search. Valid values:
|
|
2230
|
+
"profiles", "user_playbooks", "agent_playbooks".
|
|
2231
|
+
agent_playbook_status_filter (Optional[list[Union[PlaybookStatus, str]]]):
|
|
2232
|
+
Agent-playbook approval statuses to include.
|
|
2233
|
+
enable_reformulation (Optional[bool]): Enable LLM query reformulation (default: False)
|
|
2234
|
+
enable_agent_answer (Optional[bool]): Enable agentic answer synthesis when
|
|
2235
|
+
the configured search backend supports it (default: False).
|
|
2236
|
+
conversation_history (Optional[list[ConversationTurn] | list[dict]]): Prior conversation turns for context-aware query reformulation. Accepts ConversationTurn objects or dicts with "role" and "content" keys.
|
|
2237
|
+
search_mode (Optional[SearchMode | str]): Search mode to use. Accepts SearchMode enum or string value ("vector", "fts", "hybrid").
|
|
2238
|
+
|
|
2239
|
+
Returns:
|
|
2240
|
+
UnifiedSearchViewResponse: Combined search results from all entity types
|
|
2241
|
+
"""
|
|
2242
|
+
req = self._build_request(
|
|
2243
|
+
request,
|
|
2244
|
+
UnifiedSearchRequest,
|
|
2245
|
+
query=query,
|
|
2246
|
+
top_k=top_k,
|
|
2247
|
+
threshold=threshold,
|
|
2248
|
+
agent_version=agent_version,
|
|
2249
|
+
playbook_name=playbook_name,
|
|
2250
|
+
user_id=user_id,
|
|
2251
|
+
entity_types=entity_types,
|
|
2252
|
+
agent_playbook_status_filter=agent_playbook_status_filter,
|
|
2253
|
+
enable_reformulation=enable_reformulation,
|
|
2254
|
+
enable_agent_answer=enable_agent_answer,
|
|
2255
|
+
conversation_history=conversation_history,
|
|
2256
|
+
search_mode=search_mode,
|
|
2257
|
+
)
|
|
2258
|
+
response = self._make_request("POST", "/api/search", json=req.model_dump())
|
|
2259
|
+
return UnifiedSearchViewResponse(**response)
|
|
2260
|
+
|
|
2261
|
+
# =========================================================================
|
|
2262
|
+
# Bulk Delete Operations
|
|
2263
|
+
# =========================================================================
|
|
2264
|
+
|
|
2265
|
+
def delete_requests_by_ids(self, request_ids: list[str]) -> BulkDeleteResponse:
|
|
2266
|
+
"""Delete multiple requests by their IDs.
|
|
2267
|
+
|
|
2268
|
+
Args:
|
|
2269
|
+
request_ids (list[str]): List of request IDs to delete
|
|
2270
|
+
|
|
2271
|
+
Returns:
|
|
2272
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2273
|
+
"""
|
|
2274
|
+
req = DeleteRequestsByIdsRequest(request_ids=request_ids)
|
|
2275
|
+
response = self._make_request(
|
|
2276
|
+
"DELETE", "/api/delete_requests_by_ids", json=req.model_dump()
|
|
2277
|
+
)
|
|
2278
|
+
return BulkDeleteResponse(**response)
|
|
2279
|
+
|
|
2280
|
+
def delete_profiles_by_ids(self, profile_ids: list[str]) -> BulkDeleteResponse:
|
|
2281
|
+
"""Delete multiple profiles by their IDs.
|
|
2282
|
+
|
|
2283
|
+
Args:
|
|
2284
|
+
profile_ids (list[str]): List of profile IDs to delete
|
|
2285
|
+
|
|
2286
|
+
Returns:
|
|
2287
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2288
|
+
"""
|
|
2289
|
+
req = DeleteProfilesByIdsRequest(profile_ids=profile_ids)
|
|
2290
|
+
response = self._make_request(
|
|
2291
|
+
"DELETE", "/api/delete_profiles_by_ids", json=req.model_dump()
|
|
2292
|
+
)
|
|
2293
|
+
self._cache.invalidate("get_profiles")
|
|
2294
|
+
return BulkDeleteResponse(**response)
|
|
2295
|
+
|
|
2296
|
+
def delete_agent_playbooks_by_ids(
|
|
2297
|
+
self, agent_playbook_ids: list[int]
|
|
2298
|
+
) -> BulkDeleteResponse:
|
|
2299
|
+
"""Delete multiple agent playbooks by their IDs.
|
|
2300
|
+
|
|
2301
|
+
Args:
|
|
2302
|
+
agent_playbook_ids (list[int]): List of agent playbook IDs to delete
|
|
2303
|
+
|
|
2304
|
+
Returns:
|
|
2305
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2306
|
+
"""
|
|
2307
|
+
req = DeleteAgentPlaybooksByIdsRequest(agent_playbook_ids=agent_playbook_ids)
|
|
2308
|
+
response = self._make_request(
|
|
2309
|
+
"DELETE", "/api/delete_agent_playbooks_by_ids", json=req.model_dump()
|
|
2310
|
+
)
|
|
2311
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
2312
|
+
return BulkDeleteResponse(**response)
|
|
2313
|
+
|
|
2314
|
+
def delete_user_playbooks_by_ids(
|
|
2315
|
+
self, user_playbook_ids: list[int]
|
|
2316
|
+
) -> BulkDeleteResponse:
|
|
2317
|
+
"""Delete multiple user playbooks by their IDs.
|
|
2318
|
+
|
|
2319
|
+
Args:
|
|
2320
|
+
user_playbook_ids (list[int]): List of user playbook IDs to delete
|
|
2321
|
+
|
|
2322
|
+
Returns:
|
|
2323
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2324
|
+
"""
|
|
2325
|
+
req = DeleteUserPlaybooksByIdsRequest(user_playbook_ids=user_playbook_ids)
|
|
2326
|
+
response = self._make_request(
|
|
2327
|
+
"DELETE", "/api/delete_user_playbooks_by_ids", json=req.model_dump()
|
|
2328
|
+
)
|
|
2329
|
+
return BulkDeleteResponse(**response)
|
|
2330
|
+
|
|
2331
|
+
def whoami(self) -> WhoamiResponse:
|
|
2332
|
+
"""Return the server's view of the caller's org and storage routing.
|
|
2333
|
+
|
|
2334
|
+
Returns:
|
|
2335
|
+
WhoamiResponse: Masked summary of org ID and resolved storage.
|
|
2336
|
+
Never contains raw credentials — safe to print.
|
|
2337
|
+
"""
|
|
2338
|
+
response = self._make_request("GET", "/api/whoami")
|
|
2339
|
+
return WhoamiResponse(**response)
|
|
2340
|
+
|
|
2341
|
+
def get_my_config(self) -> MyConfigResponse:
|
|
2342
|
+
"""Return raw storage credentials for the caller's org.
|
|
2343
|
+
|
|
2344
|
+
Used by ``reflexio config storage`` to let users inspect their
|
|
2345
|
+
per-org server-side storage credentials.
|
|
2346
|
+
|
|
2347
|
+
Returns:
|
|
2348
|
+
MyConfigResponse: Unmasked storage config dict, or
|
|
2349
|
+
``success=False`` when no storage is configured
|
|
2350
|
+
server-side or the endpoint is disabled.
|
|
2351
|
+
"""
|
|
2352
|
+
response = self._make_request("GET", "/api/my_config")
|
|
2353
|
+
return MyConfigResponse(**response)
|
|
2354
|
+
|
|
2355
|
+
def delete_all_interactions(self) -> BulkDeleteResponse:
|
|
2356
|
+
"""Delete all requests and their associated interactions.
|
|
2357
|
+
|
|
2358
|
+
Returns:
|
|
2359
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2360
|
+
"""
|
|
2361
|
+
response = self._make_request("DELETE", "/api/delete_all_interactions")
|
|
2362
|
+
self._cache.clear()
|
|
2363
|
+
return BulkDeleteResponse(**response)
|
|
2364
|
+
|
|
2365
|
+
def delete_all_profiles(self) -> BulkDeleteResponse:
|
|
2366
|
+
"""Delete all profiles.
|
|
2367
|
+
|
|
2368
|
+
Returns:
|
|
2369
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2370
|
+
"""
|
|
2371
|
+
response = self._make_request("DELETE", "/api/delete_all_profiles")
|
|
2372
|
+
self._cache.invalidate("get_profiles")
|
|
2373
|
+
return BulkDeleteResponse(**response)
|
|
2374
|
+
|
|
2375
|
+
def delete_all_playbooks(self) -> BulkDeleteResponse:
|
|
2376
|
+
"""Delete all playbooks (both user and agent).
|
|
2377
|
+
|
|
2378
|
+
Cascading variant — wipes both playbook stores. For per-entity
|
|
2379
|
+
semantics use :meth:`delete_all_user_playbooks` (user only) or
|
|
2380
|
+
:meth:`delete_all_agent_playbooks` (agent only).
|
|
2381
|
+
|
|
2382
|
+
Returns:
|
|
2383
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2384
|
+
"""
|
|
2385
|
+
response = self._make_request("DELETE", "/api/delete_all_playbooks")
|
|
2386
|
+
self._cache.clear()
|
|
2387
|
+
return BulkDeleteResponse(**response)
|
|
2388
|
+
|
|
2389
|
+
def delete_all_user_playbooks(self) -> BulkDeleteResponse:
|
|
2390
|
+
"""Delete all user playbooks (user only, not agent).
|
|
2391
|
+
|
|
2392
|
+
Returns:
|
|
2393
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2394
|
+
"""
|
|
2395
|
+
response = self._make_request("DELETE", "/api/delete_all_user_playbooks")
|
|
2396
|
+
return BulkDeleteResponse(**response) # user playbooks not cached
|
|
2397
|
+
|
|
2398
|
+
def delete_all_agent_playbooks(self) -> BulkDeleteResponse:
|
|
2399
|
+
"""Delete all agent playbooks (agent only, not user).
|
|
2400
|
+
|
|
2401
|
+
Returns:
|
|
2402
|
+
BulkDeleteResponse: Response containing success status and deleted count
|
|
2403
|
+
"""
|
|
2404
|
+
response = self._make_request("DELETE", "/api/delete_all_agent_playbooks")
|
|
2405
|
+
self._cache.invalidate("get_agent_playbooks")
|
|
2406
|
+
return BulkDeleteResponse(**response)
|
|
2407
|
+
|
|
2408
|
+
def get_stall_state(self) -> StallStateResponse:
|
|
2409
|
+
"""Read the current learning-stall state from the server.
|
|
2410
|
+
|
|
2411
|
+
Returns:
|
|
2412
|
+
StallStateResponse: ``stalled=False`` when clean.
|
|
2413
|
+
"""
|
|
2414
|
+
response = self._make_request("GET", "/stall_state")
|
|
2415
|
+
return StallStateResponse.model_validate(response)
|
|
2416
|
+
|
|
2417
|
+
def mark_stall_notified(self) -> MarkNotifiedResponse:
|
|
2418
|
+
"""Idempotently flip ``notified_in_cc=1`` on the current stall row.
|
|
2419
|
+
|
|
2420
|
+
Returns:
|
|
2421
|
+
MarkNotifiedResponse: New ``notified_in_cc`` value.
|
|
2422
|
+
"""
|
|
2423
|
+
response = self._make_request("POST", "/stall_state/notified")
|
|
2424
|
+
return MarkNotifiedResponse.model_validate(response)
|
|
2425
|
+
|
|
2426
|
+
# ===============================
|
|
2427
|
+
# Pending tool calls (human questions)
|
|
2428
|
+
# ===============================
|
|
2429
|
+
# The resumable extraction agent can pause and emit an ``ask_human``
|
|
2430
|
+
# pending tool call when it needs a human to answer a question before it
|
|
2431
|
+
# can finish. These methods let a user list those questions, answer them
|
|
2432
|
+
# to unblock the agent, edit a prior answer, mark a question N/A, or
|
|
2433
|
+
# cancel it. See notebooks/08_resumable_agent_human_questions.ipynb.
|
|
2434
|
+
|
|
2435
|
+
def list_pending_tool_calls(
|
|
2436
|
+
self, status: str | None = None, limit: int = 100
|
|
2437
|
+
) -> PendingToolCallListResponse:
|
|
2438
|
+
"""List pending tool calls (e.g. ``ask_human`` questions) for the org.
|
|
2439
|
+
|
|
2440
|
+
Args:
|
|
2441
|
+
status (str | None): Optional status filter. One of ``"pending"``,
|
|
2442
|
+
``"resolved"``, ``"expired"``, ``"superseded"``, or
|
|
2443
|
+
``"cancelled"``. When None, all statuses are returned.
|
|
2444
|
+
limit (int): Maximum number of results to return (1-500, default 100).
|
|
2445
|
+
|
|
2446
|
+
Returns:
|
|
2447
|
+
PendingToolCallListResponse: The matching pending tool calls.
|
|
2448
|
+
"""
|
|
2449
|
+
params: dict[str, Any] = {"limit": limit}
|
|
2450
|
+
if status is not None:
|
|
2451
|
+
params["status"] = status
|
|
2452
|
+
response = self._make_request("GET", "/api/pending_tool_calls", params=params)
|
|
2453
|
+
return PendingToolCallListResponse.model_validate(response)
|
|
2454
|
+
|
|
2455
|
+
def get_pending_tool_call(
|
|
2456
|
+
self, pending_tool_call_id: str
|
|
2457
|
+
) -> PendingToolCallResponse:
|
|
2458
|
+
"""Fetch a single pending tool call by id.
|
|
2459
|
+
|
|
2460
|
+
Args:
|
|
2461
|
+
pending_tool_call_id (str): The id of the pending tool call.
|
|
2462
|
+
|
|
2463
|
+
Returns:
|
|
2464
|
+
PendingToolCallResponse: The pending tool call.
|
|
2465
|
+
"""
|
|
2466
|
+
response = self._make_request(
|
|
2467
|
+
"GET", f"/api/pending_tool_calls/{pending_tool_call_id}"
|
|
2468
|
+
)
|
|
2469
|
+
return PendingToolCallResponse.model_validate(response)
|
|
2470
|
+
|
|
2471
|
+
def resolve_pending_tool_call(
|
|
2472
|
+
self,
|
|
2473
|
+
pending_tool_call_id: str,
|
|
2474
|
+
*,
|
|
2475
|
+
result: dict[str, Any] | None = None,
|
|
2476
|
+
answer: str | None = None,
|
|
2477
|
+
valid_for_seconds: int | None = None,
|
|
2478
|
+
) -> PendingToolCallResponse:
|
|
2479
|
+
"""Resolve a pending tool call, unblocking dependent agent runs.
|
|
2480
|
+
|
|
2481
|
+
Provide exactly one of ``result`` or ``answer``. ``answer`` is a
|
|
2482
|
+
convenience for the common ``ask_human`` case and is wrapped as
|
|
2483
|
+
``{"answer": answer}``; use ``result`` to send an arbitrary tool
|
|
2484
|
+
result payload.
|
|
2485
|
+
|
|
2486
|
+
Args:
|
|
2487
|
+
pending_tool_call_id (str): The id of the pending tool call.
|
|
2488
|
+
result (dict[str, Any] | None): Raw tool result payload.
|
|
2489
|
+
answer (str | None): Human answer text (wrapped as ``{"answer": ...}``).
|
|
2490
|
+
valid_for_seconds (int | None): How long the resolved answer
|
|
2491
|
+
stays valid for answer reuse. Server default when None.
|
|
2492
|
+
|
|
2493
|
+
Returns:
|
|
2494
|
+
PendingToolCallResponse: The resolved pending tool call.
|
|
2495
|
+
|
|
2496
|
+
Raises:
|
|
2497
|
+
ValueError: If both or neither of ``result`` and ``answer`` are set.
|
|
2498
|
+
"""
|
|
2499
|
+
if (result is None) == (answer is None):
|
|
2500
|
+
raise ValueError("Provide exactly one of 'result' or 'answer' to resolve.")
|
|
2501
|
+
payload: dict[str, Any] = {
|
|
2502
|
+
"result": result if result is not None else {"answer": answer}
|
|
2503
|
+
}
|
|
2504
|
+
if valid_for_seconds is not None:
|
|
2505
|
+
payload["valid_for_seconds"] = valid_for_seconds
|
|
2506
|
+
response = self._make_request(
|
|
2507
|
+
"POST",
|
|
2508
|
+
f"/api/pending_tool_calls/{pending_tool_call_id}/resolve",
|
|
2509
|
+
json=payload,
|
|
2510
|
+
)
|
|
2511
|
+
return PendingToolCallResponse.model_validate(response)
|
|
2512
|
+
|
|
2513
|
+
def update_pending_tool_call_answer(
|
|
2514
|
+
self,
|
|
2515
|
+
pending_tool_call_id: str,
|
|
2516
|
+
answer: str,
|
|
2517
|
+
*,
|
|
2518
|
+
valid_for_seconds: int | None = None,
|
|
2519
|
+
) -> PendingToolCallResponse:
|
|
2520
|
+
"""Edit the answer of an already-resolved ``ask_human`` question.
|
|
2521
|
+
|
|
2522
|
+
Editing schedules another resume of dependent agent runs with the new
|
|
2523
|
+
answer.
|
|
2524
|
+
|
|
2525
|
+
Args:
|
|
2526
|
+
pending_tool_call_id (str): The id of the pending tool call.
|
|
2527
|
+
answer (str): The new (non-empty) answer text.
|
|
2528
|
+
valid_for_seconds (int | None): How long the edited answer stays
|
|
2529
|
+
valid for answer reuse. Server default when None.
|
|
2530
|
+
|
|
2531
|
+
Returns:
|
|
2532
|
+
PendingToolCallResponse: The updated pending tool call.
|
|
2533
|
+
"""
|
|
2534
|
+
payload: dict[str, Any] = {"answer": answer}
|
|
2535
|
+
if valid_for_seconds is not None:
|
|
2536
|
+
payload["valid_for_seconds"] = valid_for_seconds
|
|
2537
|
+
response = self._make_request(
|
|
2538
|
+
"PATCH",
|
|
2539
|
+
f"/api/pending_tool_calls/{pending_tool_call_id}/answer",
|
|
2540
|
+
json=payload,
|
|
2541
|
+
)
|
|
2542
|
+
return PendingToolCallResponse.model_validate(response)
|
|
2543
|
+
|
|
2544
|
+
def mark_pending_tool_call_not_applicable(
|
|
2545
|
+
self,
|
|
2546
|
+
pending_tool_call_id: str,
|
|
2547
|
+
*,
|
|
2548
|
+
valid_for_seconds: int | None = None,
|
|
2549
|
+
) -> PendingToolCallResponse:
|
|
2550
|
+
"""Mark an ``ask_human`` question as not applicable.
|
|
2551
|
+
|
|
2552
|
+
Resolves the question with the standard "user does not have
|
|
2553
|
+
information" result so dependent agent runs can proceed.
|
|
2554
|
+
|
|
2555
|
+
Args:
|
|
2556
|
+
pending_tool_call_id (str): The id of the pending tool call.
|
|
2557
|
+
valid_for_seconds (int | None): How long the result stays valid
|
|
2558
|
+
for answer reuse. Server default when None.
|
|
2559
|
+
|
|
2560
|
+
Returns:
|
|
2561
|
+
PendingToolCallResponse: The resolved (N/A) pending tool call.
|
|
2562
|
+
"""
|
|
2563
|
+
payload: dict[str, Any] = {}
|
|
2564
|
+
if valid_for_seconds is not None:
|
|
2565
|
+
payload["valid_for_seconds"] = valid_for_seconds
|
|
2566
|
+
response = self._make_request(
|
|
2567
|
+
"POST",
|
|
2568
|
+
f"/api/pending_tool_calls/{pending_tool_call_id}/not_applicable",
|
|
2569
|
+
json=payload,
|
|
2570
|
+
)
|
|
2571
|
+
return PendingToolCallResponse.model_validate(response)
|
|
2572
|
+
|
|
2573
|
+
def cancel_pending_tool_call(
|
|
2574
|
+
self, pending_tool_call_id: str
|
|
2575
|
+
) -> PendingToolCallResponse:
|
|
2576
|
+
"""Cancel a pending tool call without answering it.
|
|
2577
|
+
|
|
2578
|
+
Only pending (unanswered) tool calls can be cancelled.
|
|
2579
|
+
|
|
2580
|
+
Args:
|
|
2581
|
+
pending_tool_call_id (str): The id of the pending tool call.
|
|
2582
|
+
|
|
2583
|
+
Returns:
|
|
2584
|
+
PendingToolCallResponse: The cancelled pending tool call.
|
|
2585
|
+
"""
|
|
2586
|
+
response = self._make_request(
|
|
2587
|
+
"POST", f"/api/pending_tool_calls/{pending_tool_call_id}/cancel"
|
|
2588
|
+
)
|
|
2589
|
+
return PendingToolCallResponse.model_validate(response)
|
|
2590
|
+
|
|
2591
|
+
def clear_user_data(self, user_id: str) -> ClearUserDataResponse:
|
|
2592
|
+
"""Delete all rows scoped to a single ``user_id``.
|
|
2593
|
+
|
|
2594
|
+
Wipes the user's interactions, user playbooks, profiles, and
|
|
2595
|
+
requests on the server. Does NOT touch agent playbooks — they
|
|
2596
|
+
are intentionally shared cross-project. Used by paired-protocol
|
|
2597
|
+
harnesses (e.g. SWE-bench) to isolate per-task data on a shared
|
|
2598
|
+
backend without one task's clear-all nuking another in-flight
|
|
2599
|
+
task's rows.
|
|
2600
|
+
|
|
2601
|
+
Args:
|
|
2602
|
+
user_id (str): The user id whose data should be cleared.
|
|
2603
|
+
|
|
2604
|
+
Returns:
|
|
2605
|
+
ClearUserDataResponse: Per-entity deletion counts.
|
|
2606
|
+
"""
|
|
2607
|
+
req = ClearUserDataRequest(user_id=user_id)
|
|
2608
|
+
response = self._make_request(
|
|
2609
|
+
"POST", "/api/clear_user_data", json=req.model_dump()
|
|
2610
|
+
)
|
|
2611
|
+
# Nuclear — clear everything that could reference this user.
|
|
2612
|
+
self._cache.clear()
|
|
2613
|
+
return ClearUserDataResponse(**response)
|