marvisx-cli 0.1.0__py3-none-any.whl
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.
- core/api/__init__.py +0 -0
- core/api/agents/__init__.py +0 -0
- core/api/agents/session_health.py +59 -0
- core/api/agents/session_manager.py +206 -0
- core/api/bin/marvisx-state-hook.py +182 -0
- core/api/config.py +533 -0
- core/api/db.py +1516 -0
- core/api/dependencies/__init__.py +0 -0
- core/api/dependencies/tenant.py +34 -0
- core/api/main.py +1641 -0
- core/api/mcp/__init__.py +8 -0
- core/api/mcp/_adapter.py +184 -0
- core/api/mcp/server.py +58 -0
- core/api/mcp/tools/__init__.py +59 -0
- core/api/mcp/tools/brain.py +599 -0
- core/api/mcp/tools/graph.py +380 -0
- core/api/mcp/tools/handoffs.py +112 -0
- core/api/mcp/tools/ingest.py +326 -0
- core/api/mcp/tools/learnings.py +144 -0
- core/api/mcp/tools/projects.py +99 -0
- core/api/mcp/tools/pull_requests.py +173 -0
- core/api/mcp/tools/safety.py +111 -0
- core/api/mcp/tools/search.py +79 -0
- core/api/mcp/tools/tasks.py +258 -0
- core/api/middleware/__init__.py +0 -0
- core/api/middleware/tool_call_audit.py +111 -0
- core/api/models/__init__.py +346 -0
- core/api/models/auth.py +48 -0
- core/api/models/brain.py +1006 -0
- core/api/models/common.py +76 -0
- core/api/models/costs.py +91 -0
- core/api/models/graph.py +66 -0
- core/api/models/graph_cosmo.py +125 -0
- core/api/models/graph_pr_impact.py +257 -0
- core/api/models/graph_ux.py +141 -0
- core/api/models/inbox.py +230 -0
- core/api/models/ingest_keys.py +108 -0
- core/api/models/kg.py +41 -0
- core/api/models/llm_config.py +56 -0
- core/api/models/monitoring.py +234 -0
- core/api/models/projects.py +161 -0
- core/api/models/search.py +42 -0
- core/api/models/sessions.py +322 -0
- core/api/models/tasks.py +184 -0
- core/api/models/teams.py +63 -0
- core/api/models/users.py +184 -0
- core/api/observability/__init__.py +0 -0
- core/api/observability/tracing.py +92 -0
- core/api/paths.py +26 -0
- core/api/rate_limit.py +24 -0
- core/api/rbac.py +112 -0
- core/api/routers/__init__.py +0 -0
- core/api/routers/_adapter.py +24 -0
- core/api/routers/admin_pr_impact.py +230 -0
- core/api/routers/admin_settings.py +147 -0
- core/api/routers/agent.py +1079 -0
- core/api/routers/agent_tokens.py +276 -0
- core/api/routers/app_settings.py +112 -0
- core/api/routers/audit.py +89 -0
- core/api/routers/auth.py +586 -0
- core/api/routers/bench.py +161 -0
- core/api/routers/brain.py +881 -0
- core/api/routers/brain_directions.py +527 -0
- core/api/routers/ci_checks.py +140 -0
- core/api/routers/comments.py +273 -0
- core/api/routers/costs.py +148 -0
- core/api/routers/docs_coverage.py +217 -0
- core/api/routers/docs_governance.py +63 -0
- core/api/routers/documents.py +318 -0
- core/api/routers/files.py +163 -0
- core/api/routers/finder.py +987 -0
- core/api/routers/graph.py +836 -0
- core/api/routers/handoffs.py +156 -0
- core/api/routers/inbox.py +496 -0
- core/api/routers/ingest_api_keys.py +205 -0
- core/api/routers/ingest_triage.py +1227 -0
- core/api/routers/judge.py +306 -0
- core/api/routers/kg.py +336 -0
- core/api/routers/learnings.py +253 -0
- core/api/routers/llm_config.py +130 -0
- core/api/routers/monitoring.py +347 -0
- core/api/routers/notifications.py +125 -0
- core/api/routers/pr_impact.py +315 -0
- core/api/routers/projects.py +1061 -0
- core/api/routers/pull_requests.py +312 -0
- core/api/routers/push.py +67 -0
- core/api/routers/raci.py +228 -0
- core/api/routers/search.py +125 -0
- core/api/routers/sessions.py +3100 -0
- core/api/routers/settings.py +90 -0
- core/api/routers/share_repo.py +68 -0
- core/api/routers/status_updates.py +96 -0
- core/api/routers/tags.py +45 -0
- core/api/routers/tasks.py +526 -0
- core/api/routers/teams.py +425 -0
- core/api/routers/terminal.py +105 -0
- core/api/routers/users.py +331 -0
- core/api/routers/webhooks.py +330 -0
- core/api/runtime_settings.py +84 -0
- core/api/security.py +652 -0
- core/api/services/__init__.py +0 -0
- core/api/services/audit.py +58 -0
- core/api/services/auto_approval.py +82 -0
- core/api/services/brain/__init__.py +52 -0
- core/api/services/brain/baseline.py +230 -0
- core/api/services/brain/capabilities.py +75 -0
- core/api/services/brain/cascade_rollup.py +388 -0
- core/api/services/brain/compound_bridge.py +215 -0
- core/api/services/brain/cycle.py +1242 -0
- core/api/services/brain/cycle_snapshot.py +371 -0
- core/api/services/brain/digest_collector.py +147 -0
- core/api/services/brain/direction.py +421 -0
- core/api/services/brain/drift.py +356 -0
- core/api/services/brain/drift_router.py +409 -0
- core/api/services/brain/edge_metrics.py +79 -0
- core/api/services/brain/events_reader.py +222 -0
- core/api/services/brain/findings.py +1379 -0
- core/api/services/brain/findings_reader.py +1006 -0
- core/api/services/brain/jobs.py +733 -0
- core/api/services/brain/journal.py +206 -0
- core/api/services/brain/knowledge_forms.py +92 -0
- core/api/services/brain/llm/__init__.py +37 -0
- core/api/services/brain/llm/_runner.py +62 -0
- core/api/services/brain/llm/base.py +70 -0
- core/api/services/brain/llm/cache.py +99 -0
- core/api/services/brain/llm/constants.py +46 -0
- core/api/services/brain/llm/direction_alignment.py +289 -0
- core/api/services/brain/llm/factory.py +132 -0
- core/api/services/brain/llm/finding_reasoning.py +98 -0
- core/api/services/brain/llm/finding_summary.py +92 -0
- core/api/services/brain/llm/grounding.py +46 -0
- core/api/services/brain/llm/journal_polish.py +96 -0
- core/api/services/brain/llm/local_gateway.py +426 -0
- core/api/services/brain/llm/parsers.py +71 -0
- core/api/services/brain/llm/router_glue.py +422 -0
- core/api/services/brain/memory_ops.py +1677 -0
- core/api/services/brain/models.py +140 -0
- core/api/services/brain/owner_hint.py +211 -0
- core/api/services/brain/recap.py +307 -0
- core/api/services/brain/rules/__init__.py +65 -0
- core/api/services/brain/rules/_signals.py +205 -0
- core/api/services/brain/rules/dr1_activity_without_status.py +108 -0
- core/api/services/brain/rules/dr2_decision_without_adr.py +110 -0
- core/api/services/brain/rules/dr3_stale_open_loop.py +127 -0
- core/api/services/brain/rules/dr4_docs_governance_drift.py +79 -0
- core/api/services/brain/rules/dr5_playbook_changed.py +99 -0
- core/api/services/brain/rules/dr6_external_update_unpropagated.py +100 -0
- core/api/services/brain/rules/dr7_claimed_decision_gap.py +123 -0
- core/api/services/brain/rules/dr8_direction_misalignment.py +230 -0
- core/api/services/brain/runs_reader.py +485 -0
- core/api/services/brain/scope.py +79 -0
- core/api/services/brain/sources/__init__.py +46 -0
- core/api/services/brain/sources/base.py +86 -0
- core/api/services/brain/sources/git_kg.py +393 -0
- core/api/services/brain/sources/handoffs.py +157 -0
- core/api/services/brain/sources/ingestor.py +130 -0
- core/api/services/brain/sources/learnings.py +121 -0
- core/api/services/brain/sources/pir_tasks.py +245 -0
- core/api/services/brain/watermarks.py +147 -0
- core/api/services/brain/ws_emitter.py +170 -0
- core/api/services/cc_tasks_reader.py +76 -0
- core/api/services/ci_service.py +263 -0
- core/api/services/claude_metrics.py +796 -0
- core/api/services/codex_metrics.py +364 -0
- core/api/services/conversation_reader.py +102 -0
- core/api/services/cost_service.py +243 -0
- core/api/services/crypto.py +147 -0
- core/api/services/docs_governance/__init__.py +1 -0
- core/api/services/docs_governance/confidence.py +230 -0
- core/api/services/docs_governance/config.py +87 -0
- core/api/services/docs_governance/enrichment.py +65 -0
- core/api/services/docs_governance/frontmatter_validator.py +83 -0
- core/api/services/docs_governance/hard_gates.py +221 -0
- core/api/services/docs_governance/triage_orchestrator.py +98 -0
- core/api/services/embedding_internal.py +395 -0
- core/api/services/embedding_service.py +832 -0
- core/api/services/event_dispatcher.py +167 -0
- core/api/services/events.py +70 -0
- core/api/services/git_ops.py +621 -0
- core/api/services/graph_cosmo_service.py +440 -0
- core/api/services/graph_ranker.py +306 -0
- core/api/services/graph_service.py +1589 -0
- core/api/services/inbox.py +800 -0
- core/api/services/inbox_digest.py +221 -0
- core/api/services/inbox_digest_deep_research.py +80 -0
- core/api/services/inbox_digest_jobs.py +595 -0
- core/api/services/inbox_gmail_sync.py +167 -0
- core/api/services/inbox_llm_classifier.py +906 -0
- core/api/services/inbox_source_identity.py +116 -0
- core/api/services/inbox_sources.py +456 -0
- core/api/services/inbox_taxonomy.py +195 -0
- core/api/services/inbox_tldr.py +1079 -0
- core/api/services/inbox_triage.py +899 -0
- core/api/services/ingest/__init__.py +13 -0
- core/api/services/ingest/api_key_auth.py +136 -0
- core/api/services/ingest/auto_approve.py +120 -0
- core/api/services/ingest/classifier.py +138 -0
- core/api/services/ingest/confidence.py +173 -0
- core/api/services/ingest/dispatch.py +88 -0
- core/api/services/ingest/embedding_router.py +272 -0
- core/api/services/ingest/events.py +33 -0
- core/api/services/ingest/ignore_patterns.py +79 -0
- core/api/services/ingest/image_probe.py +218 -0
- core/api/services/ingest/ingress.py +263 -0
- core/api/services/ingest/insert_saga.py +793 -0
- core/api/services/ingest/llm/__init__.py +13 -0
- core/api/services/ingest/llm/anthropic_haiku.py +23 -0
- core/api/services/ingest/llm/base.py +59 -0
- core/api/services/ingest/llm/byok_provider.py +130 -0
- core/api/services/ingest/llm/classification_context.py +301 -0
- core/api/services/ingest/llm/config_store.py +246 -0
- core/api/services/ingest/llm/factory.py +24 -0
- core/api/services/ingest/llm/kg_enricher.py +306 -0
- core/api/services/ingest/llm/local_gateway.py +821 -0
- core/api/services/ingest/llm/local_vllm.py +23 -0
- core/api/services/ingest/llm/openai_nano.py +349 -0
- core/api/services/ingest/lock_advisory.py +57 -0
- core/api/services/ingest/parser_router.py +1756 -0
- core/api/services/ingest/parsers/__init__.py +1 -0
- core/api/services/ingest/parsers/docling_parser.py +142 -0
- core/api/services/ingest/parsers/docparse_gateway.py +178 -0
- core/api/services/ingest/parsers/docx_parser.py +127 -0
- core/api/services/ingest/parsers/folder_unpacker.py +85 -0
- core/api/services/ingest/parsers/gateway_aux.py +147 -0
- core/api/services/ingest/parsers/image_parser.py +251 -0
- core/api/services/ingest/parsers/internal_markdown.py +89 -0
- core/api/services/ingest/parsers/ocr_gateway.py +117 -0
- core/api/services/ingest/parsers/ocr_pdf_parser.py +112 -0
- core/api/services/ingest/parsers/pdf_types.py +13 -0
- core/api/services/ingest/parsers/transcript_parser.py +445 -0
- core/api/services/ingest/parsers/vision_gateway.py +186 -0
- core/api/services/ingest/parsers/xlsx_parser.py +91 -0
- core/api/services/ingest/parsers/zip_unpacker.py +126 -0
- core/api/services/ingest/preflight.py +393 -0
- core/api/services/ingest/retry_voyage.py +88 -0
- core/api/services/ingest/routing_policy.py +307 -0
- core/api/services/ingest/serializers/__init__.py +1 -0
- core/api/services/ingest/serializers/xlsx_to_markdown.py +80 -0
- core/api/services/ingest/skip_log.py +74 -0
- core/api/services/ingest/watcher.py +637 -0
- core/api/services/kg/__init__.py +0 -0
- core/api/services/kg/audit.py +49 -0
- core/api/services/kg/hybrid_search.py +691 -0
- core/api/services/kg/lens.py +339 -0
- core/api/services/kg/pr_impact.py +770 -0
- core/api/services/kg/queries.py +152 -0
- core/api/services/kg/ranking.py +89 -0
- core/api/services/kg/rrf.py +143 -0
- core/api/services/kg_watcher_control.py +161 -0
- core/api/services/local_llm/__init__.py +19 -0
- core/api/services/local_llm/async_client.py +385 -0
- core/api/services/local_llm/client.py +173 -0
- core/api/services/local_llm/url_validator.py +44 -0
- core/api/services/metrics_collector.py +646 -0
- core/api/services/metrics_providers.py +65 -0
- core/api/services/model_registry.py +266 -0
- core/api/services/model_router.py +137 -0
- core/api/services/n8n_client.py +77 -0
- core/api/services/newsletter_llm_gateway.py +66 -0
- core/api/services/notification_service.py +134 -0
- core/api/services/openai_responses.py +55 -0
- core/api/services/opencode_metrics.py +375 -0
- core/api/services/opencode_sessions.py +173 -0
- core/api/services/pii_redactor.py +138 -0
- core/api/services/pr_impact_pipeline/__init__.py +21 -0
- core/api/services/pr_impact_pipeline/differ.py +421 -0
- core/api/services/pr_impact_pipeline/dispatcher.py +415 -0
- core/api/services/pr_impact_pipeline/gc.py +93 -0
- core/api/services/pr_impact_pipeline/languages.py +192 -0
- core/api/services/pr_impact_pipeline/parser.py +178 -0
- core/api/services/pr_impact_pipeline/writer.py +394 -0
- core/api/services/pr_service.py +1393 -0
- core/api/services/project_paths.py +70 -0
- core/api/services/project_status_updates.py +265 -0
- core/api/services/providers.py +276 -0
- core/api/services/push_service.py +170 -0
- core/api/services/reminder_service.py +89 -0
- core/api/services/runas.py +41 -0
- core/api/services/salience_service.py +69 -0
- core/api/services/security_collector.py +281 -0
- core/api/services/session_catalog.py +385 -0
- core/api/services/session_metrics_service.py +301 -0
- core/api/services/session_ops.py +272 -0
- core/api/services/session_state.py +173 -0
- core/api/services/share_links.py +222 -0
- core/api/services/task_transitions.py +146 -0
- core/api/services/terminal_metrics.py +462 -0
- core/api/services/terminal_metrics_dump.py +203 -0
- core/api/services/tmux.py +1205 -0
- core/api/services/webhook_service.py +422 -0
- core/api/services/workspace_sync.py +164 -0
- core/api/templates/__init__.py +1 -0
- core/api/templates/markdown_share.py +164 -0
- core/api/terminal.py +1031 -0
- core/api/tests/__init__.py +0 -0
- core/api/tests/test_agent_facing_auth_dependencies.py +132 -0
- core/api/tests/test_audit_permissions.py +133 -0
- core/api/tests/test_backfill_session_conversations.py +90 -0
- core/api/tests/test_backfill_working_seconds_msg.py +129 -0
- core/api/tests/test_claude_metrics.py +326 -0
- core/api/tests/test_codex_metrics.py +189 -0
- core/api/tests/test_finder_paths.py +74 -0
- core/api/tests/test_git_ops_merge.py +155 -0
- core/api/tests/test_learnings_check_search.py +81 -0
- core/api/tests/test_metrics_providers.py +133 -0
- core/api/tests/test_migration_087.py +164 -0
- core/api/tests/test_migration_088.py +94 -0
- core/api/tests/test_migration_089.py +116 -0
- core/api/tests/test_openai_responses.py +24 -0
- core/api/tests/test_opencode_metrics.py +740 -0
- core/api/tests/test_opencode_sessions.py +321 -0
- core/api/tests/test_pr_workflow_e2e.py +457 -0
- core/api/tests/test_projects_handoffs.py +31 -0
- core/api/tests/test_providers.py +138 -0
- core/api/tests/test_safety_bridge.py +347 -0
- core/api/tests/test_session_catalog.py +142 -0
- core/api/tests/test_session_conversations.py +512 -0
- core/api/tests/test_session_metrics_service.py +270 -0
- core/api/tests/test_session_resume_paths.py +548 -0
- core/api/tests/test_session_theme_mode_migration.py +56 -0
- core/api/tests/test_sessions_rbac.py +131 -0
- core/api/tests/test_share_edit.py +398 -0
- core/api/tests/test_share_repo.py +200 -0
- core/api/tests/test_terminal_session_manager.py +98 -0
- core/api/tests/test_terminal_upload.py +34 -0
- core/api/tests/test_tmux.py +272 -0
- core/api/tests/test_workspace_sync.py +186 -0
- core/api/tests/test_ws_ticket_in_memory.py +73 -0
- core/api/use_cases/__init__.py +11 -0
- core/api/use_cases/_context.py +89 -0
- core/api/use_cases/_errors.py +62 -0
- core/api/use_cases/_roles.py +16 -0
- core/api/use_cases/audit.py +171 -0
- core/api/use_cases/brain.py +1232 -0
- core/api/use_cases/costs.py +249 -0
- core/api/use_cases/graph.py +1153 -0
- core/api/use_cases/handoffs.py +506 -0
- core/api/use_cases/ingest_triage.py +1229 -0
- core/api/use_cases/learnings.py +538 -0
- core/api/use_cases/projects.py +705 -0
- core/api/use_cases/pull_requests.py +415 -0
- core/api/use_cases/search.py +926 -0
- core/api/use_cases/tasks.py +1495 -0
- core/api/visibility.py +141 -0
- core/cli/__init__.py +5 -0
- core/cli/_index_source.py +632 -0
- core/cli/_runtime_ctx.py +160 -0
- core/cli/_transmute.py +241 -0
- core/cli/marvis_doctor.py +704 -0
- core/cli/marvis_feedback.py +396 -0
- core/cli/marvis_governance.py +315 -0
- core/cli/marvis_hooks.py +515 -0
- core/cli/marvis_init.py +757 -0
- core/cli/marvis_mcp.py +401 -0
- core/cli/marvis_runtime.py +855 -0
- core/cli/marvis_telemetry.py +228 -0
- core/scripts/_drift_check.py +716 -0
- core/scripts/_frontmatter.py +66 -0
- core/scripts/_graph_writer.py +189 -0
- core/scripts/ast_parser.py +1553 -0
- core/scripts/install_hooks/__init__.py +1 -0
- core/scripts/install_hooks/_config.sh +109 -0
- core/scripts/install_hooks/block-dangerous-bash.sh +23 -0
- core/scripts/install_hooks/block-db-direct-write.sh +23 -0
- core/scripts/install_hooks/block-push-no-task.sh +23 -0
- core/scripts/install_hooks/block-staging-to-prod.sh +23 -0
- core/scripts/install_hooks/block-subtree-push.sh +23 -0
- core/scripts/install_hooks/config.json +53 -0
- core/scripts/install_hooks/enforce-no-merge-main.sh +23 -0
- core/scripts/install_hooks/enforce-worktree.sh +23 -0
- core/scripts/install_hooks/quality-gate.sh +170 -0
- core/scripts/install_hooks/safety_bridge.py +968 -0
- core/scripts/install_hooks/secret-scan.sh +23 -0
- core/scripts/migrate_spike_node_ids.py +122 -0
- core/scripts/populate_artifacts.py +2198 -0
- core/scripts/populate_cross_project.py +2457 -0
- core/scripts/populate_inbox_nodes.py +357 -0
- core/scripts/populate_pr_impact.py +267 -0
- core/scripts/populate_project_nodes.py +603 -0
- core/scripts/populate_touch_counter.py +337 -0
- core/scripts/reparse_failed.py +57 -0
- core/scripts/safety_bridge.py +968 -0
- core/telemetry/__init__.py +9 -0
- core/telemetry/client.py +405 -0
- core/telemetry/schema.py +122 -0
- core/wizard/__init__.py +65 -0
- core/wizard/byok_vault.py +147 -0
- core/wizard/defaults.py +58 -0
- core/wizard/state.py +117 -0
- core/wizard/steps.py +70 -0
- core/wizard/validation.py +136 -0
- marvisx_cli-0.1.0.dist-info/METADATA +201 -0
- marvisx_cli-0.1.0.dist-info/RECORD +587 -0
- marvisx_cli-0.1.0.dist-info/WHEEL +5 -0
- marvisx_cli-0.1.0.dist-info/entry_points.txt +3 -0
- marvisx_cli-0.1.0.dist-info/licenses/LICENSE +98 -0
- marvisx_cli-0.1.0.dist-info/top_level.txt +3 -0
- migrations/001_initial.sql +33 -0
- migrations/002_tasks.sql +30 -0
- migrations/003_session_management.sql +7 -0
- migrations/004_projects_comments.sql +65 -0
- migrations/005_session_intelligence.sql +15 -0
- migrations/006_settings.sql +12 -0
- migrations/007_task_scoring.sql +12 -0
- migrations/008_cost_tracking.sql +31 -0
- migrations/009_session_card_metrics.sql +3 -0
- migrations/010_monitoring.sql +55 -0
- migrations/012_agent_api.sql +21 -0
- migrations/013_session_complete.sql +8 -0
- migrations/015_pull_requests.sql +43 -0
- migrations/015_pull_requests_down.sql +5 -0
- migrations/016_users_raci.sql +116 -0
- migrations/017_task_cost_entries.sql +87 -0
- migrations/018_agents.sql +73 -0
- migrations/018_agents_down.sql +13 -0
- migrations/019_review_feedback.sql +18 -0
- migrations/020_pr_commit_sha.sql +4 -0
- migrations/021_webhook_events.sql +18 -0
- migrations/022_devx_agent_managed.sql +11 -0
- migrations/022_devx_agent_managed_down.sql +6 -0
- migrations/023_devx_p1_gate.sql +7 -0
- migrations/023_devx_p1_gate_down.sql +3 -0
- migrations/024_chat_messages.sql +16 -0
- migrations/024_pr_conversation_id.sql +8 -0
- migrations/024_task_indexes.sql +21 -0
- migrations/024_task_indexes_down.sql +7 -0
- migrations/025_audit_log.sql +17 -0
- migrations/026_agent_tokens.sql +20 -0
- migrations/027_teams_auth_phase_b.sql +35 -0
- migrations/028_learnings.sql +23 -0
- migrations/029_team_roles.sql +14 -0
- migrations/030_finder_pins.sql +10 -0
- migrations/031_pr_deploy_status.sql +9 -0
- migrations/032_task_reminders.sql +7 -0
- migrations/033_events_retry_count.sql +6 -0
- migrations/033_session_owner.sql +9 -0
- migrations/034_notifications.sql +38 -0
- migrations/035_shared_links.sql +15 -0
- migrations/036_session_index_upgrade.sql +29 -0
- migrations/037_pr_approval.sql +15 -0
- migrations/038_pr_submitted_by.sql +6 -0
- migrations/039_push_subscriptions.sql +17 -0
- migrations/040_semantic_search.sql +16 -0
- migrations/041_workspaces.sql +63 -0
- migrations/042_oidc_providers.sql +24 -0
- migrations/043_ci_checks.sql +31 -0
- migrations/044_agent_metrics.sql +30 -0
- migrations/045_documents_doc_type.sql +5 -0
- migrations/046_salience.sql +13 -0
- migrations/047_seed_missing_agents.sql +6 -0
- migrations/048_fix_agent_paths_roles.sql +5 -0
- migrations/049_agent_role_and_learnings_schema.sql +3 -0
- migrations/050_session_provider.sql +2 -0
- migrations/051_session_launch_profile.sql +4 -0
- migrations/052_session_theme_mode.sql +2 -0
- migrations/052_task_kind.sql +4 -0
- migrations/053_inbox_items.sql +31 -0
- migrations/054_inbox_triage_contract.sql +30 -0
- migrations/055_inbox_topic_treatment.sql +12 -0
- migrations/056_inbox_treatment_read_save.sql +57 -0
- migrations/057_session_theme_mode_backfill.sql +4 -0
- migrations/058_inbox_item_status_lifecycle.sql +13 -0
- migrations/059_inbox_tldr_and_source_scores.sql +18 -0
- migrations/060_newsletter.sql +16 -0
- migrations/061_inbox_redesign.sql +69 -0
- migrations/062_fix_inbox_sources_backfill.sql +37 -0
- migrations/063_task_completion_mode.sql +23 -0
- migrations/064_judge_mode_setting.sql +4 -0
- migrations/065_knowledge_graph_spike.sql +40 -0
- migrations/066_digest_ranking_inputs.sql +10 -0
- migrations/066_kg_artifact_nodes.sql +129 -0
- migrations/067_inbox_digest_selections.sql +28 -0
- migrations/067_kg_temporal.sql +53 -0
- migrations/068_inbox_digest_app_settings.sql +9 -0
- migrations/068_kg_touch_counter.sql +52 -0
- migrations/069_kg_doc_types.sql +117 -0
- migrations/070_digest_ranking_inputs_recovery.sql +3 -0
- migrations/071_inbox_digest_selections_recovery.sql +3 -0
- migrations/072_inbox_digest_app_settings_recovery.sql +3 -0
- migrations/073_kg_cross_project.sql +216 -0
- migrations/073_kg_cross_project_down.sql +77 -0
- migrations/074_kg_infra_types.sql +208 -0
- migrations/074_kg_infra_types_down.sql +80 -0
- migrations/075_kg_file_state_recovery.sql +35 -0
- migrations/075_kg_file_state_recovery_down.sql +5 -0
- migrations/076_kg_watcher_state.sql +33 -0
- migrations/076_kg_watcher_state_down.sql +3 -0
- migrations/077_kg_doc_types_extend.sql +226 -0
- migrations/077_kg_doc_types_extend_down.sql +80 -0
- migrations/078_kg_fts5.sql +102 -0
- migrations/078_kg_fts5_down.sql +14 -0
- migrations/079_kg_missing_indexes.sql +31 -0
- migrations/079_kg_missing_indexes_down.sql +10 -0
- migrations/080_kg_fts5_extended.sql +232 -0
- migrations/080_kg_fts5_extended_down.sql +25 -0
- migrations/081_kg_lens_indexes.sql +9 -0
- migrations/081_kg_lens_indexes_down.sql +3 -0
- migrations/082_kg_pins.sql +26 -0
- migrations/082_kg_pins_down.sql +14 -0
- migrations/083_kg_graph_nodes_degree.sql +20 -0
- migrations/083_kg_graph_nodes_degree_down.sql +15 -0
- migrations/084_drop_legacy_scheduler_tables.sql +58 -0
- migrations/084_drop_legacy_scheduler_tables_down.sql +112 -0
- migrations/085_kg_edge_resolves_to.sql +142 -0
- migrations/085_kg_edge_resolves_to_down.sql +66 -0
- migrations/086_project_status_updates_feed.sql +20 -0
- migrations/086_project_status_updates_feed_down.sql +36 -0
- migrations/087_session_metrics_dual.sql +50 -0
- migrations/087_session_metrics_dual_down.sql +21 -0
- migrations/088_rename_context_pct_legacy.sql +23 -0
- migrations/088_rename_context_pct_legacy_down.sql +8 -0
- migrations/089_session_metrics_equivalent_cost.sql +26 -0
- migrations/089_session_metrics_equivalent_cost_down.sql +11 -0
- migrations/090_kg_inbox_node_type.sql +26 -0
- migrations/090_kg_inbox_node_type_down.sql +20 -0
- migrations/091_kg_inbox_node_type_check.sql +265 -0
- migrations/091_kg_inbox_node_type_check_down.sql +129 -0
- migrations/092_sessions_activity_state_ts.sql +29 -0
- migrations/092_sessions_activity_state_ts_down.sql +14 -0
- migrations/093_sessions_activity_state_column.sql +29 -0
- migrations/093_sessions_activity_state_column_down.sql +10 -0
- migrations/094_ingest_pending.sql +55 -0
- migrations/094_ingest_pending_down.sql +15 -0
- migrations/095_kg_intent_first.sql +77 -0
- migrations/095_kg_intent_first_down.sql +25 -0
- migrations/096_kg_xlsx_artifact_prefix.sql +17 -0
- migrations/096_kg_xlsx_artifact_prefix_down.sql +11 -0
- migrations/097_ingest_change_history.sql +37 -0
- migrations/097_ingest_change_history_down.sql +13 -0
- migrations/098_kg_node_type_business.sql +254 -0
- migrations/098_kg_node_type_business_down.sql +195 -0
- migrations/099_kg_edges_restore_weight.sql +58 -0
- migrations/099_kg_edges_restore_weight_down.sql +12 -0
- migrations/100_kg_enriched_at.sql +25 -0
- migrations/100_kg_enriched_at_down.sql +12 -0
- migrations/101_local_llm_shadow_comparisons.sql +66 -0
- migrations/101_local_llm_shadow_comparisons_down.sql +15 -0
- migrations/102_promote_llm_costs.sql +69 -0
- migrations/102_promote_llm_costs_down.sql +19 -0
- migrations/103_ingest_skipped_log.sql +46 -0
- migrations/103_ingest_skipped_log_down.sql +15 -0
- migrations/120_docs_governance.sql +50 -0
- migrations/120_docs_governance_down.sql +11 -0
- migrations/121_notification_event_fk_cleanup.sql +21 -0
- migrations/121_notification_event_fk_cleanup_down.sql +10 -0
- migrations/122_docs_drift_history.sql +34 -0
- migrations/122_docs_drift_history_down.sql +15 -0
- migrations/123_ingest_parser_waiting_status.sql +69 -0
- migrations/123_ingest_parser_waiting_status_down.sql +69 -0
- migrations/124_heypocket_recordings.sql +63 -0
- migrations/124_heypocket_recordings_down.sql +13 -0
- migrations/125_kg_node_type_record.sql +219 -0
- migrations/125_kg_node_type_record_down.sql +205 -0
- migrations/126_ingest_terminal_upload_source_kind.sql +69 -0
- migrations/126_ingest_terminal_upload_source_kind_down.sql +69 -0
- migrations/127_brain_v1_substrate.sql +200 -0
- migrations/127_brain_v1_substrate_down.sql +32 -0
- migrations/128_brain_drift_signals.sql +157 -0
- migrations/128_brain_drift_signals_down.sql +23 -0
- migrations/129_brain_memory_operations.sql +232 -0
- migrations/129_brain_memory_operations_down.sql +27 -0
- migrations/130_brain_findings.sql +258 -0
- migrations/130_brain_findings_down.sql +29 -0
- migrations/132_kg_pr_modifies.sql +242 -0
- migrations/132_kg_pr_modifies_down.sql +99 -0
- migrations/133_brain_v1_2_direction_schema.sql +476 -0
- migrations/133_brain_v1_2_direction_schema_down.sql +273 -0
- migrations/134_brain_journal_narrative_polished.sql +8 -0
- migrations/134_brain_journal_narrative_polished_down.sql +6 -0
- migrations/135_kg_edges_provider.sql +21 -0
- migrations/136_documents_fts.sql +56 -0
- migrations/137_promote_llm_costs.sql +59 -0
- migrations/137_promote_llm_costs_down.sql +19 -0
- migrations/138_ingest_api_keys.sql +39 -0
- migrations/138_ingest_api_keys_down.sql +8 -0
- migrations/139_ingest_pending_ingress.sql +91 -0
- migrations/139_ingest_pending_ingress_down.sql +73 -0
- migrations/140_ingest_idempotency_quota.sql +45 -0
- migrations/140_ingest_idempotency_quota_down.sql +9 -0
- migrations/141_ingest_pending_metadata.sql +16 -0
- migrations/141_ingest_pending_metadata_down.sql +7 -0
- migrations/142_llm_function_config.sql +36 -0
- migrations/142_llm_function_config_down.sql +8 -0
- migrations/143_kg_code_embeddings.sql +25 -0
- migrations/143_kg_code_embeddings_down.sql +5 -0
- migrations/__init__.py +4 -0
- projects/_template/project.yaml +46 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Self-hosted embedding client for Granite 97M multilingual models.
|
|
2
|
+
|
|
3
|
+
Torch-free engine: runs ``ibm-granite/granite-embedding-97m-multilingual-r2``
|
|
4
|
+
(384-dim, ModernBert) directly via ``onnxruntime`` + the Rust ``tokenizers``
|
|
5
|
+
package + ``huggingface_hub``. No ``sentence-transformers`` / ``optimum`` /
|
|
6
|
+
``torch`` — those drag in ~1GB of wheels even when inference runs on ONNX.
|
|
7
|
+
|
|
8
|
+
The public shape mirrors the previous client so ``embedding_service.py`` and the
|
|
9
|
+
MCP/search layers are untouched (no-fork): ``embed_texts(texts, input_type,
|
|
10
|
+
batch_size) -> list[list[float]]`` plus ``is_available()`` / ``can_attempt_load``
|
|
11
|
+
/ ``active_model_name`` / ``dimensions`` / ``cache_folder``. Model loading is
|
|
12
|
+
lazy: constructing the client only does inexpensive env + RAM checks; the ONNX
|
|
13
|
+
session and tokenizer are created on the first embed (or warmed in
|
|
14
|
+
``is_available()``).
|
|
15
|
+
|
|
16
|
+
Pooling is **CLS** (``last_hidden_state[:, 0]``) then L2-normalize — NOT mean.
|
|
17
|
+
This matches the model's ``1_Pooling/config.json`` (``pooling_mode_cls_token=true``).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import importlib
|
|
23
|
+
import logging
|
|
24
|
+
import math
|
|
25
|
+
import os
|
|
26
|
+
import threading
|
|
27
|
+
import time
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("marvisx.embedding_internal")
|
|
32
|
+
|
|
33
|
+
DEFAULT_MODEL = "ibm-granite/granite-embedding-97m-multilingual-r2"
|
|
34
|
+
DEFAULT_RAM_THRESHOLD_MB = 4096
|
|
35
|
+
EXPECTED_DIMENSIONS = 384
|
|
36
|
+
_MB = 1024 * 1024
|
|
37
|
+
|
|
38
|
+
# --- Pinned model revision (review delta #2) --------------------------------
|
|
39
|
+
# Pin the exact commit so the model graph + tokenizer never drift silently under
|
|
40
|
+
# us (a future re-export would break the cosine>=0.999 invariant the search
|
|
41
|
+
# pipeline depends on). The equivalence fixture is generated from THIS revision.
|
|
42
|
+
# https://huggingface.co/ibm-granite/granite-embedding-97m-multilingual-r2
|
|
43
|
+
MODEL_REVISION = "835ad14087e140460703cf0fae09f97d469d65c2"
|
|
44
|
+
|
|
45
|
+
# --- ONNX graph selection ---------------------------------------------------
|
|
46
|
+
# The HF repo ships two ONNX graphs: onnx/model.onnx (fp32, ~390MB) and
|
|
47
|
+
# onnx/model_quint8_avx2.onnx (int8, ~98MB). There is no fp16 export in the repo.
|
|
48
|
+
# Precision was measured empirically via tests/test_embedding_onnx_equivalence.py
|
|
49
|
+
# (review delta #7): fp32 holds cosine>=0.999 vs sentence-transformers on the
|
|
50
|
+
# IT+EN code+prose corpus; int8 drops below it (1-3 recall points at risk), so
|
|
51
|
+
# fp32 is the default. Override with EMBEDDING_ONNX_FILE for benchmarking only.
|
|
52
|
+
_ONNX_FILE_DEFAULT = "onnx/model.onnx"
|
|
53
|
+
|
|
54
|
+
# --- Tokenizer constants (review delta #3) ----------------------------------
|
|
55
|
+
# Hardcoded with a source comment rather than parsing config at runtime (a
|
|
56
|
+
# parser + fallback is the worst of both worlds for a single fixed model). The
|
|
57
|
+
# equivalence test catches any drift from these values.
|
|
58
|
+
# Source: config.json (pad_token_id=179935, cls_token_id=179934),
|
|
59
|
+
# tokenizer_config.json (pad_token="<|endoftext|>", model_max_length=32768),
|
|
60
|
+
# special_tokens_map.json (pad_token "<|endoftext|>").
|
|
61
|
+
PAD_ID = 179935
|
|
62
|
+
PAD_TOKEN = "<|endoftext|>"
|
|
63
|
+
CLS_TOKEN_ID = 179934 # added by the tokenizer template, never injected by hand
|
|
64
|
+
|
|
65
|
+
# Truncation length: MUST match what sentence-transformers used, or long inputs
|
|
66
|
+
# get a different CLS vector => cosine<0.999 (verified: truncating at 512 dropped
|
|
67
|
+
# two >512-token corpus strings to cos~0.994). sentence-transformers reads this
|
|
68
|
+
# model's truncation from sentence_bert_config.json => max_seq_length=32768 (NOT
|
|
69
|
+
# the 512 the plan assumed; this ModernBert R2 model has a 32768 context window,
|
|
70
|
+
# matching config.json max_position_embeddings). Hardcode 32768 to mirror ST.
|
|
71
|
+
# Source: sentence_bert_config.json (max_seq_length=32768), config.json
|
|
72
|
+
# (max_position_embeddings=32768). Padding is to the per-batch max, not MAX_LEN,
|
|
73
|
+
# so short inputs stay cheap; only genuinely long docs pay the longer sequence.
|
|
74
|
+
MAX_LEN = 32768
|
|
75
|
+
|
|
76
|
+
# Only fetch the ONNX graph + tokenizer/config — never *.safetensors / *.bin
|
|
77
|
+
# (those are the torch weights we are eliminating).
|
|
78
|
+
_ALLOW_PATTERNS = [
|
|
79
|
+
"onnx/model.onnx",
|
|
80
|
+
"onnx/model.onnx_data", # external-data sidecar if the graph is split (safe no-op if absent)
|
|
81
|
+
"onnx/model_quint8_avx2.onnx",
|
|
82
|
+
"tokenizer.json",
|
|
83
|
+
"tokenizer_config.json",
|
|
84
|
+
"special_tokens_map.json",
|
|
85
|
+
"config.json",
|
|
86
|
+
"1_Pooling/config.json",
|
|
87
|
+
"config_sentence_transformers.json",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class GraniteEmbeddingClient:
|
|
92
|
+
"""Lazy torch-free onnxruntime client for local Granite embeddings.
|
|
93
|
+
|
|
94
|
+
Granite is the only local model. Machines below the RAM floor are NOT
|
|
95
|
+
silently downgraded to a weaker model: the client emits an actionable
|
|
96
|
+
warning pointing at the hardware requirement and proceeds with Granite.
|
|
97
|
+
|
|
98
|
+
Thread-safety (review delta #4): the API is async and offloads embeds via
|
|
99
|
+
``run_in_executor``, so multiple threads may call ``embed_texts`` / trigger
|
|
100
|
+
the lazy load concurrently. The lazy load is guarded by ``_load_lock`` (two
|
|
101
|
+
threads must not download/instantiate twice). ``InferenceSession.run`` is
|
|
102
|
+
thread-safe, but a ``tokenizers.Tokenizer`` configured with
|
|
103
|
+
``enable_padding``/``enable_truncation`` mutates internal state and is NOT
|
|
104
|
+
thread-safe, so each embed call tokenizes under ``_encode_lock``.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
model_name: str = DEFAULT_MODEL,
|
|
110
|
+
device: str = "cpu",
|
|
111
|
+
) -> None:
|
|
112
|
+
self.model_name = model_name
|
|
113
|
+
self.device = device
|
|
114
|
+
self._active_model_name = model_name
|
|
115
|
+
# _model holds the loaded engine tuple (session, tokenizer); None until
|
|
116
|
+
# the lazy load succeeds. This is what is_available() gates on, mirroring
|
|
117
|
+
# the previous contract (review delta #5).
|
|
118
|
+
self._model: tuple[Any, Any] | None = None
|
|
119
|
+
self._backend: str | None = None
|
|
120
|
+
self._dimensions: int | None = None
|
|
121
|
+
self._load_error: BaseException | None = None
|
|
122
|
+
self._load_lock = threading.Lock()
|
|
123
|
+
self._encode_lock = threading.Lock()
|
|
124
|
+
self._cache_folder = Path(
|
|
125
|
+
os.environ.get("HF_HOME", "~/.cache/huggingface")
|
|
126
|
+
).expanduser()
|
|
127
|
+
self._ram_threshold_mb = _int_from_env(
|
|
128
|
+
"EMBEDDING_RAM_THRESHOLD_MB", DEFAULT_RAM_THRESHOLD_MB
|
|
129
|
+
)
|
|
130
|
+
self._onnx_file = os.environ.get("EMBEDDING_ONNX_FILE", _ONNX_FILE_DEFAULT)
|
|
131
|
+
|
|
132
|
+
available_mb = _available_memory_mb()
|
|
133
|
+
if available_mb is not None and available_mb < self._ram_threshold_mb:
|
|
134
|
+
logger.warning(
|
|
135
|
+
"Granite embedding may be RAM-constrained: available RAM %.0fMB is "
|
|
136
|
+
"below the %dMB floor. The local engine requires more memory; see the "
|
|
137
|
+
"hardware requirement (run `marvis doctor`). Proceeding with %s — no "
|
|
138
|
+
"silent downgrade to a weaker model.",
|
|
139
|
+
available_mb,
|
|
140
|
+
self._ram_threshold_mb,
|
|
141
|
+
model_name,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def active_model_name(self) -> str:
|
|
146
|
+
"""Return the active Granite model name."""
|
|
147
|
+
|
|
148
|
+
return self._active_model_name
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def cache_folder(self) -> Path:
|
|
152
|
+
"""Return the Hugging Face cache folder used for the model snapshot."""
|
|
153
|
+
|
|
154
|
+
return self._cache_folder
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def can_attempt_load(self) -> bool:
|
|
158
|
+
"""Return false only after a model load crash has been recorded."""
|
|
159
|
+
|
|
160
|
+
return self._load_error is None
|
|
161
|
+
|
|
162
|
+
def is_available(self) -> bool:
|
|
163
|
+
"""Return true once the model has loaded successfully.
|
|
164
|
+
|
|
165
|
+
Eagerly warms the model (review delta #6): the first
|
|
166
|
+
``InferenceSession.__init__`` + a dummy run cost 1-3s; paying it here
|
|
167
|
+
moves the cold-start out of the first real request. A load failure is
|
|
168
|
+
swallowed (recorded in ``_load_error``) so callers can degrade to the
|
|
169
|
+
keyword lane instead of crashing — matching the previous contract.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
if self._model is not None and self._load_error is None:
|
|
173
|
+
return True
|
|
174
|
+
if self._load_error is not None:
|
|
175
|
+
return False
|
|
176
|
+
try:
|
|
177
|
+
self._get_model()
|
|
178
|
+
except Exception: # noqa: BLE001 — recorded in _load_error, degrade gracefully
|
|
179
|
+
return False
|
|
180
|
+
return self._model is not None and self._load_error is None
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def dimensions(self) -> int:
|
|
184
|
+
"""Native output dimensions for Granite."""
|
|
185
|
+
|
|
186
|
+
return self._dimensions or EXPECTED_DIMENSIONS
|
|
187
|
+
|
|
188
|
+
def embed_texts(
|
|
189
|
+
self,
|
|
190
|
+
texts: list[str],
|
|
191
|
+
input_type: str = "document",
|
|
192
|
+
batch_size: int = 16,
|
|
193
|
+
) -> list[list[float]]:
|
|
194
|
+
"""Embed texts and return L2-normalized native 384-dimensional vectors.
|
|
195
|
+
|
|
196
|
+
``input_type`` is a no-op (kept for interface compatibility): the model's
|
|
197
|
+
``config_sentence_transformers.json`` declares empty query/document
|
|
198
|
+
prompts, so sentence-transformers adds no prefix either.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
if not texts:
|
|
202
|
+
return []
|
|
203
|
+
|
|
204
|
+
self._get_model()
|
|
205
|
+
try:
|
|
206
|
+
raw_vectors = self._encode(texts, batch_size)
|
|
207
|
+
vectors = [_normalize_l2(row) for row in raw_vectors]
|
|
208
|
+
except Exception:
|
|
209
|
+
logger.exception(
|
|
210
|
+
"Granite embedding failed model=%s backend=%s input_type=%s",
|
|
211
|
+
self._active_model_name,
|
|
212
|
+
self._backend or "unknown",
|
|
213
|
+
input_type,
|
|
214
|
+
)
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
if vectors:
|
|
218
|
+
self._dimensions = len(vectors[0])
|
|
219
|
+
return vectors
|
|
220
|
+
|
|
221
|
+
def _get_model(self) -> tuple[Any, Any]:
|
|
222
|
+
if self._model is not None:
|
|
223
|
+
return self._model
|
|
224
|
+
with self._load_lock:
|
|
225
|
+
# Re-check under the lock: a concurrent caller may have loaded it.
|
|
226
|
+
if self._model is not None:
|
|
227
|
+
return self._model
|
|
228
|
+
if self._load_error is not None:
|
|
229
|
+
raise RuntimeError(
|
|
230
|
+
f"Embedding model load previously failed: {self._active_model_name}"
|
|
231
|
+
) from self._load_error
|
|
232
|
+
|
|
233
|
+
start = time.perf_counter()
|
|
234
|
+
try:
|
|
235
|
+
session, tokenizer = self._load_onnx_session()
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
self._load_error = exc
|
|
238
|
+
logger.exception(
|
|
239
|
+
"Failed to load embedding model model=%s device=%s cache=%s",
|
|
240
|
+
self._active_model_name,
|
|
241
|
+
self.device,
|
|
242
|
+
self._cache_folder,
|
|
243
|
+
)
|
|
244
|
+
raise RuntimeError(
|
|
245
|
+
f"Embedding model unavailable: {self._active_model_name}"
|
|
246
|
+
) from exc
|
|
247
|
+
|
|
248
|
+
self._model = (session, tokenizer)
|
|
249
|
+
self._backend = "onnxruntime"
|
|
250
|
+
self._dimensions = _session_output_dim(session) or EXPECTED_DIMENSIONS
|
|
251
|
+
if self._dimensions != EXPECTED_DIMENSIONS:
|
|
252
|
+
logger.warning(
|
|
253
|
+
"Granite ONNX output dim=%d != expected %d",
|
|
254
|
+
self._dimensions,
|
|
255
|
+
EXPECTED_DIMENSIONS,
|
|
256
|
+
)
|
|
257
|
+
logger.info(
|
|
258
|
+
"Embedding model loaded model=%s backend=%s device=%s dims=%d "
|
|
259
|
+
"onnx=%s cache=%s load_ms=%d",
|
|
260
|
+
self._active_model_name,
|
|
261
|
+
self._backend,
|
|
262
|
+
self.device,
|
|
263
|
+
self._dimensions,
|
|
264
|
+
self._onnx_file,
|
|
265
|
+
self._cache_folder,
|
|
266
|
+
int((time.perf_counter() - start) * 1000),
|
|
267
|
+
)
|
|
268
|
+
return self._model
|
|
269
|
+
|
|
270
|
+
def _load_onnx_session(self) -> tuple[Any, Any]:
|
|
271
|
+
ort = importlib.import_module("onnxruntime")
|
|
272
|
+
tokenizers_mod = importlib.import_module("tokenizers")
|
|
273
|
+
hub = importlib.import_module("huggingface_hub")
|
|
274
|
+
|
|
275
|
+
local = hub.snapshot_download(
|
|
276
|
+
self._active_model_name,
|
|
277
|
+
revision=MODEL_REVISION, # pinned (delta #2)
|
|
278
|
+
cache_dir=str(self._cache_folder),
|
|
279
|
+
allow_patterns=_ALLOW_PATTERNS,
|
|
280
|
+
)
|
|
281
|
+
local_path = Path(local)
|
|
282
|
+
|
|
283
|
+
tokenizer = tokenizers_mod.Tokenizer.from_file(str(local_path / "tokenizer.json"))
|
|
284
|
+
tokenizer.enable_truncation(max_length=MAX_LEN)
|
|
285
|
+
tokenizer.enable_padding(pad_id=PAD_ID, pad_token=PAD_TOKEN)
|
|
286
|
+
|
|
287
|
+
sess_options = ort.SessionOptions()
|
|
288
|
+
# intra_op = physical cores by default; inter_op=1 (single graph, batched).
|
|
289
|
+
threads = _int_from_env("EMBEDDING_ORT_THREADS", _physical_cores())
|
|
290
|
+
if threads > 0:
|
|
291
|
+
sess_options.intra_op_num_threads = threads
|
|
292
|
+
sess_options.inter_op_num_threads = 1
|
|
293
|
+
sess_options.graph_optimization_level = (
|
|
294
|
+
ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
|
295
|
+
)
|
|
296
|
+
# Persist the optimized graph next to the model so subsequent cold-starts
|
|
297
|
+
# skip re-optimization (review delta #6). Best-effort: ignore if unwritable.
|
|
298
|
+
try:
|
|
299
|
+
opt_path = local_path / "onnx" / (
|
|
300
|
+
Path(self._onnx_file).name + ".ort_optimized.onnx"
|
|
301
|
+
)
|
|
302
|
+
sess_options.optimized_model_filepath = str(opt_path)
|
|
303
|
+
except Exception: # noqa: BLE001
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
session = ort.InferenceSession(
|
|
307
|
+
str(local_path / self._onnx_file),
|
|
308
|
+
sess_options=sess_options,
|
|
309
|
+
providers=["CPUExecutionProvider"],
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Warm-up dummy run so the first real request does not pay graph init.
|
|
313
|
+
try:
|
|
314
|
+
self._run_session(session, tokenizer, ["warmup"])
|
|
315
|
+
except Exception: # noqa: BLE001 — warmup is best-effort
|
|
316
|
+
logger.debug("Granite ONNX warm-up run failed (non-fatal)", exc_info=True)
|
|
317
|
+
|
|
318
|
+
return session, tokenizer
|
|
319
|
+
|
|
320
|
+
def _encode(self, texts: list[str], batch_size: int) -> list[list[float]]:
|
|
321
|
+
session, tokenizer = self._model # type: ignore[misc]
|
|
322
|
+
out: list[list[float]] = []
|
|
323
|
+
for start in range(0, len(texts), max(1, batch_size)):
|
|
324
|
+
chunk = texts[start : start + max(1, batch_size)]
|
|
325
|
+
out.extend(self._run_session(session, tokenizer, chunk))
|
|
326
|
+
return out
|
|
327
|
+
|
|
328
|
+
def _run_session(
|
|
329
|
+
self, session: Any, tokenizer: Any, chunk: list[str]
|
|
330
|
+
) -> list[list[float]]:
|
|
331
|
+
import numpy as np # local import keeps module import cheap
|
|
332
|
+
|
|
333
|
+
# Tokenizer state (padding/truncation) is mutable + not thread-safe:
|
|
334
|
+
# encode_batch under a lock; padding is to the max of THIS batch.
|
|
335
|
+
with self._encode_lock:
|
|
336
|
+
encodings = tokenizer.encode_batch(chunk)
|
|
337
|
+
ids = np.array([e.ids for e in encodings], dtype=np.int64)
|
|
338
|
+
mask = np.array([e.attention_mask for e in encodings], dtype=np.int64)
|
|
339
|
+
hidden = session.run(
|
|
340
|
+
["last_hidden_state"],
|
|
341
|
+
{"input_ids": ids, "attention_mask": mask},
|
|
342
|
+
)[0]
|
|
343
|
+
# CLS pooling — first token. NOT mean pooling (delta from the old client).
|
|
344
|
+
cls = hidden[:, 0]
|
|
345
|
+
return [[float(v) for v in row] for row in cls]
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _physical_cores() -> int:
|
|
349
|
+
try:
|
|
350
|
+
psutil = importlib.import_module("psutil")
|
|
351
|
+
cores = psutil.cpu_count(logical=False)
|
|
352
|
+
if cores:
|
|
353
|
+
return int(cores)
|
|
354
|
+
except Exception: # noqa: BLE001
|
|
355
|
+
pass
|
|
356
|
+
return os.cpu_count() or 1
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _session_output_dim(session: Any) -> int | None:
|
|
360
|
+
try:
|
|
361
|
+
for output in session.get_outputs():
|
|
362
|
+
if output.name == "last_hidden_state":
|
|
363
|
+
shape = output.shape
|
|
364
|
+
if shape and isinstance(shape[-1], int):
|
|
365
|
+
return int(shape[-1])
|
|
366
|
+
except Exception: # noqa: BLE001
|
|
367
|
+
return None
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _available_memory_mb() -> float | None:
|
|
372
|
+
try:
|
|
373
|
+
psutil = importlib.import_module("psutil")
|
|
374
|
+
except ImportError:
|
|
375
|
+
logger.warning("psutil is not installed; skipping embedding RAM gate")
|
|
376
|
+
return None
|
|
377
|
+
return float(psutil.virtual_memory().available) / _MB
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _int_from_env(name: str, default: int) -> int:
|
|
381
|
+
raw = os.environ.get(name)
|
|
382
|
+
if not raw:
|
|
383
|
+
return default
|
|
384
|
+
try:
|
|
385
|
+
return int(raw)
|
|
386
|
+
except ValueError:
|
|
387
|
+
logger.warning("Invalid %s=%r; using %d", name, raw, default)
|
|
388
|
+
return default
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _normalize_l2(vector: list[float]) -> list[float]:
|
|
392
|
+
norm = math.sqrt(sum(value * value for value in vector))
|
|
393
|
+
if norm == 0.0:
|
|
394
|
+
return [0.0 for _ in vector]
|
|
395
|
+
return [value / norm for value in vector]
|