apsimo 1.3.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.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
"""Reranker pipeline — scores and re-ranks retrieved documents.
|
|
2
|
+
|
|
3
|
+
Uses the same provider pattern as the embedding pipeline:
|
|
4
|
+
CUDA (sentence-transformers) for GPU, CPU for fallback,
|
|
5
|
+
and an OpenAI-compatible API provider for remote reranking.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import functools
|
|
12
|
+
import logging
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from apsimo.vector.tiers import ModelSpec
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RerankResult:
|
|
24
|
+
"""A single reranked document with score."""
|
|
25
|
+
|
|
26
|
+
index: int # Original index in the input list
|
|
27
|
+
score: float # Relevance score (higher = more relevant)
|
|
28
|
+
text: str # The document text
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RerankerProvider(ABC):
|
|
32
|
+
"""Base class for reranker providers."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, model_id: str) -> None:
|
|
35
|
+
self._model_id = model_id
|
|
36
|
+
self._model = None
|
|
37
|
+
|
|
38
|
+
def calibration_metadata(self) -> dict:
|
|
39
|
+
"""Configuration identity for optional deployment-calibrated cutoffs.
|
|
40
|
+
|
|
41
|
+
A model name is not an immutable weight revision. The caller may add
|
|
42
|
+
an observed revision; otherwise that uncertainty remains explicit.
|
|
43
|
+
"""
|
|
44
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
45
|
+
import hashlib
|
|
46
|
+
endpoint = urlsplit(getattr(self, "_base_url", ""))
|
|
47
|
+
host = endpoint.hostname or ""
|
|
48
|
+
if endpoint.port:
|
|
49
|
+
host += f":{endpoint.port}"
|
|
50
|
+
prompt_style = getattr(self, "_prompt_style", "")
|
|
51
|
+
format_version = "raw-v1"
|
|
52
|
+
if prompt_style == "qwen3":
|
|
53
|
+
format_version = hashlib.sha256((QWEN3_RERANK_PREFIX + QWEN3_RERANK_INSTRUCTION + QWEN3_RERANK_SUFFIX).encode()).hexdigest()
|
|
54
|
+
return {
|
|
55
|
+
"provider": type(self).__name__, "model": self._model_id,
|
|
56
|
+
"endpoint": urlunsplit((endpoint.scheme, host, endpoint.path, "", "")),
|
|
57
|
+
"prompt_style": prompt_style,
|
|
58
|
+
"format_version": format_version,
|
|
59
|
+
"weights_revision": "unverified",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def warmup(self) -> None: ...
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def rerank(
|
|
67
|
+
self,
|
|
68
|
+
query: str,
|
|
69
|
+
documents: list[str],
|
|
70
|
+
top_k: int = 10,
|
|
71
|
+
) -> list[RerankResult]: ...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CUDARerankerProvider(RerankerProvider):
|
|
75
|
+
"""NVIDIA GPU reranker via sentence-transformers CrossEncoder."""
|
|
76
|
+
|
|
77
|
+
async def warmup(self) -> None:
|
|
78
|
+
loop = asyncio.get_running_loop()
|
|
79
|
+
self._model = await loop.run_in_executor(None, self._load_model)
|
|
80
|
+
|
|
81
|
+
def _load_model(self):
|
|
82
|
+
from sentence_transformers import CrossEncoder
|
|
83
|
+
return CrossEncoder(self._model_id, device="cuda")
|
|
84
|
+
|
|
85
|
+
async def rerank(
|
|
86
|
+
self,
|
|
87
|
+
query: str,
|
|
88
|
+
documents: list[str],
|
|
89
|
+
top_k: int = 10,
|
|
90
|
+
) -> list[RerankResult]:
|
|
91
|
+
if not documents:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
pairs = [(query, doc) for doc in documents]
|
|
95
|
+
loop = asyncio.get_running_loop()
|
|
96
|
+
scores = await loop.run_in_executor(
|
|
97
|
+
None,
|
|
98
|
+
functools.partial(self._model.predict, pairs),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
results = [
|
|
102
|
+
RerankResult(index=i, score=float(scores[i]), text=documents[i])
|
|
103
|
+
for i in range(len(documents))
|
|
104
|
+
]
|
|
105
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
106
|
+
return results[:top_k]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class CPURerankerProvider(RerankerProvider):
|
|
110
|
+
"""CPU reranker via sentence-transformers CrossEncoder."""
|
|
111
|
+
|
|
112
|
+
async def warmup(self) -> None:
|
|
113
|
+
loop = asyncio.get_running_loop()
|
|
114
|
+
self._model = await loop.run_in_executor(None, self._load_model)
|
|
115
|
+
|
|
116
|
+
def _load_model(self):
|
|
117
|
+
from sentence_transformers import CrossEncoder
|
|
118
|
+
return CrossEncoder(self._model_id, device="cpu")
|
|
119
|
+
|
|
120
|
+
async def rerank(
|
|
121
|
+
self,
|
|
122
|
+
query: str,
|
|
123
|
+
documents: list[str],
|
|
124
|
+
top_k: int = 10,
|
|
125
|
+
) -> list[RerankResult]:
|
|
126
|
+
if not documents:
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
pairs = [(query, doc) for doc in documents]
|
|
130
|
+
loop = asyncio.get_running_loop()
|
|
131
|
+
scores = await loop.run_in_executor(
|
|
132
|
+
None,
|
|
133
|
+
functools.partial(self._model.predict, pairs),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
results = [
|
|
137
|
+
RerankResult(index=i, score=float(scores[i]), text=documents[i])
|
|
138
|
+
for i in range(len(documents))
|
|
139
|
+
]
|
|
140
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
141
|
+
return results[:top_k]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class MLXRerankerProvider(RerankerProvider):
|
|
145
|
+
"""Apple Silicon reranker via sentence-transformers CrossEncoder with PyTorch MPS.
|
|
146
|
+
|
|
147
|
+
NOTE: This is the legacy provider. For true native MLX performance,
|
|
148
|
+
use ``NativeMLXRerankerProvider`` (provider="native_mlx").
|
|
149
|
+
|
|
150
|
+
NOTE: warmup() runs synchronously in the main thread to avoid
|
|
151
|
+
PyTorch MPS deadlocks in asyncio.run_in_executor (Issue #17).
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
async def warmup(self) -> None:
|
|
155
|
+
self._model = self._load_model()
|
|
156
|
+
|
|
157
|
+
def _load_model(self):
|
|
158
|
+
from sentence_transformers import CrossEncoder
|
|
159
|
+
return CrossEncoder(self._model_id, device="mps")
|
|
160
|
+
|
|
161
|
+
async def rerank(
|
|
162
|
+
self,
|
|
163
|
+
query: str,
|
|
164
|
+
documents: list[str],
|
|
165
|
+
top_k: int = 10,
|
|
166
|
+
) -> list[RerankResult]:
|
|
167
|
+
if not documents:
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
pairs = [(query, doc) for doc in documents]
|
|
171
|
+
loop = asyncio.get_running_loop()
|
|
172
|
+
scores = await loop.run_in_executor(
|
|
173
|
+
None,
|
|
174
|
+
functools.partial(self._model.predict, pairs),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
results = [
|
|
178
|
+
RerankResult(index=i, score=float(scores[i]), text=documents[i])
|
|
179
|
+
for i in range(len(documents))
|
|
180
|
+
]
|
|
181
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
182
|
+
return results[:top_k]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
# Native MLX reranker (Apple Silicon via true MLX framework)
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
class NativeMLXRerankerProvider(RerankerProvider):
|
|
190
|
+
"""Apple Silicon reranker via the native MLX framework (mlx-lm).
|
|
191
|
+
|
|
192
|
+
Loads the original HuggingFace CrossEncoder model with ``mlx_lm`` and
|
|
193
|
+
extracts logits for the true/false classification tokens. This avoids
|
|
194
|
+
the PyTorch MPS overhead entirely.
|
|
195
|
+
|
|
196
|
+
The model must expose a ``config_sentence_transformers.json`` with
|
|
197
|
+
``true_token_id`` and ``false_token_id`` (standard for sentence-transformers
|
|
198
|
+
CrossEncoder checkpoints). If absent, the provider falls back to logits
|
|
199
|
+
for generic "yes"/"no" tokens.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def __init__(self, model_id: str) -> None:
|
|
203
|
+
super().__init__(model_id)
|
|
204
|
+
self._model = None
|
|
205
|
+
self._tokenizer = None
|
|
206
|
+
self._true_token_id: int | None = None
|
|
207
|
+
self._false_token_id: int | None = None
|
|
208
|
+
|
|
209
|
+
async def warmup(self) -> None:
|
|
210
|
+
self._model, self._tokenizer = self._load_model()
|
|
211
|
+
|
|
212
|
+
def _load_model(self):
|
|
213
|
+
from mlx_lm import load
|
|
214
|
+
import json
|
|
215
|
+
from pathlib import Path
|
|
216
|
+
from huggingface_hub import try_to_load_from_cache
|
|
217
|
+
|
|
218
|
+
model, tokenizer = load(self._model_id, lazy=False)
|
|
219
|
+
self._instruction = None
|
|
220
|
+
|
|
221
|
+
# Load instruction / prompt from sentence-transformers config
|
|
222
|
+
try:
|
|
223
|
+
cache_path = try_to_load_from_cache(
|
|
224
|
+
self._model_id, "config_sentence_transformers.json"
|
|
225
|
+
)
|
|
226
|
+
if cache_path and Path(cache_path).exists():
|
|
227
|
+
with open(cache_path) as f:
|
|
228
|
+
st_cfg = json.load(f)
|
|
229
|
+
else:
|
|
230
|
+
from huggingface_hub import snapshot_download
|
|
231
|
+
model_path = Path(snapshot_download(self._model_id, allow_patterns=["config_sentence_transformers.json"]))
|
|
232
|
+
with open(model_path / "config_sentence_transformers.json") as f:
|
|
233
|
+
st_cfg = json.load(f)
|
|
234
|
+
|
|
235
|
+
prompts = st_cfg.get("prompts", {})
|
|
236
|
+
default_name = st_cfg.get("default_prompt_name")
|
|
237
|
+
if default_name and default_name in prompts:
|
|
238
|
+
self._instruction = prompts[default_name]
|
|
239
|
+
elif prompts:
|
|
240
|
+
self._instruction = next(iter(prompts.values()))
|
|
241
|
+
except Exception:
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
# Attempt to load sentence-transformers config for token IDs
|
|
245
|
+
try:
|
|
246
|
+
# Check local cache first, then download
|
|
247
|
+
cache_path = try_to_load_from_cache(
|
|
248
|
+
self._model_id, "config_sentence_transformers.json"
|
|
249
|
+
)
|
|
250
|
+
if cache_path and Path(cache_path).exists():
|
|
251
|
+
with open(cache_path) as f:
|
|
252
|
+
st_cfg = json.load(f)
|
|
253
|
+
else:
|
|
254
|
+
# Try to find in the model directory
|
|
255
|
+
from huggingface_hub import snapshot_download
|
|
256
|
+
model_path = Path(snapshot_download(self._model_id, allow_patterns=["config_sentence_transformers.json"]))
|
|
257
|
+
with open(model_path / "config_sentence_transformers.json") as f:
|
|
258
|
+
st_cfg = json.load(f)
|
|
259
|
+
|
|
260
|
+
# Look for LogitScore module config
|
|
261
|
+
logit_cfg = st_cfg.get("1_LogitScore", {})
|
|
262
|
+
if not logit_cfg:
|
|
263
|
+
# Some models store it directly in the root
|
|
264
|
+
logit_cfg = st_cfg
|
|
265
|
+
self._true_token_id = logit_cfg.get("true_token_id")
|
|
266
|
+
self._false_token_id = logit_cfg.get("false_token_id")
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
# Qwen3 rerankers store token IDs in 1_LogitScore/config.json
|
|
271
|
+
# rather than inside config_sentence_transformers.json.
|
|
272
|
+
if self._true_token_id is None or self._false_token_id is None:
|
|
273
|
+
try:
|
|
274
|
+
cache_path = try_to_load_from_cache(
|
|
275
|
+
self._model_id, "1_LogitScore/config.json"
|
|
276
|
+
)
|
|
277
|
+
if cache_path and Path(cache_path).exists():
|
|
278
|
+
with open(cache_path) as f:
|
|
279
|
+
logit_cfg = json.load(f)
|
|
280
|
+
else:
|
|
281
|
+
from huggingface_hub import snapshot_download
|
|
282
|
+
|
|
283
|
+
model_path = Path(
|
|
284
|
+
snapshot_download(
|
|
285
|
+
self._model_id, allow_patterns=["1_LogitScore/config.json"]
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
with open(model_path / "1_LogitScore" / "config.json") as f:
|
|
289
|
+
logit_cfg = json.load(f)
|
|
290
|
+
self._true_token_id = logit_cfg.get("true_token_id")
|
|
291
|
+
self._false_token_id = logit_cfg.get("false_token_id")
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
return model, tokenizer
|
|
296
|
+
|
|
297
|
+
async def rerank(
|
|
298
|
+
self,
|
|
299
|
+
query: str,
|
|
300
|
+
documents: list[str],
|
|
301
|
+
top_k: int = 10,
|
|
302
|
+
) -> list[RerankResult]:
|
|
303
|
+
if not documents:
|
|
304
|
+
return []
|
|
305
|
+
|
|
306
|
+
loop = asyncio.get_running_loop()
|
|
307
|
+
scores = await loop.run_in_executor(
|
|
308
|
+
None,
|
|
309
|
+
functools.partial(self._score_documents, query, documents),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
results = [
|
|
313
|
+
RerankResult(index=i, score=float(scores[i]), text=documents[i])
|
|
314
|
+
for i in range(len(documents))
|
|
315
|
+
]
|
|
316
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
317
|
+
return results[:top_k]
|
|
318
|
+
|
|
319
|
+
def _score_documents(self, query: str, documents: list[str]) -> list[float]:
|
|
320
|
+
import mlx.core as mx
|
|
321
|
+
|
|
322
|
+
scores: list[float] = []
|
|
323
|
+
for doc in documents:
|
|
324
|
+
# Use chat template if available (Qwen3 rerankers require this)
|
|
325
|
+
if (
|
|
326
|
+
hasattr(self._tokenizer, "apply_chat_template")
|
|
327
|
+
and self._tokenizer.chat_template
|
|
328
|
+
):
|
|
329
|
+
instruction = (
|
|
330
|
+
self._instruction
|
|
331
|
+
or "Given a web search query, retrieve relevant passages that answer the query"
|
|
332
|
+
)
|
|
333
|
+
messages = [
|
|
334
|
+
{"role": "system", "content": instruction},
|
|
335
|
+
{"role": "query", "content": query},
|
|
336
|
+
{"role": "document", "content": doc},
|
|
337
|
+
]
|
|
338
|
+
prompt = self._tokenizer.apply_chat_template(
|
|
339
|
+
messages, tokenize=False, add_generation_prompt=True
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
# Fallback for models without a chat template
|
|
343
|
+
prompt = f"{query}\n{doc}"
|
|
344
|
+
|
|
345
|
+
tokens = mx.array(self._tokenizer.encode(prompt))
|
|
346
|
+
logits = self._model(tokens[None, :])
|
|
347
|
+
last_logits = logits[0, -1, :]
|
|
348
|
+
|
|
349
|
+
if self._true_token_id is not None and self._false_token_id is not None:
|
|
350
|
+
true_score = last_logits[self._true_token_id].item()
|
|
351
|
+
false_score = last_logits[self._false_token_id].item()
|
|
352
|
+
scores.append(true_score - false_score)
|
|
353
|
+
else:
|
|
354
|
+
# Fallback: use max logit as relevance proxy
|
|
355
|
+
scores.append(last_logits.max().item())
|
|
356
|
+
|
|
357
|
+
return scores
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Qwen3-Reranker scores relevance from yes/no token logits, which are only
|
|
361
|
+
# calibrated when the request is wrapped in the model's instruction template
|
|
362
|
+
# (vLLM's /v1/rerank does not apply it server-side — raw strings score noise).
|
|
363
|
+
QWEN3_RERANK_PREFIX = (
|
|
364
|
+
"<|im_start|>system\nJudge whether the Document meets the requirements "
|
|
365
|
+
'based on the Query and the Instruct provided. Note that the answer can '
|
|
366
|
+
'only be "yes" or "no".<|im_end|>\n<|im_start|>user\n'
|
|
367
|
+
)
|
|
368
|
+
QWEN3_RERANK_SUFFIX = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
|
369
|
+
QWEN3_RERANK_INSTRUCTION = (
|
|
370
|
+
"Given a web search query, retrieve relevant passages that answer the query"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def format_qwen3_rerank(query: str, documents: list[str]) -> tuple[str, list[str]]:
|
|
375
|
+
"""Wrap a query and documents in the Qwen3-Reranker instruction template."""
|
|
376
|
+
wrapped_query = (
|
|
377
|
+
f"{QWEN3_RERANK_PREFIX}<Instruct>: {QWEN3_RERANK_INSTRUCTION}"
|
|
378
|
+
f"\n<Query>: {query}\n"
|
|
379
|
+
)
|
|
380
|
+
wrapped_docs = [f"<Document>: {d}{QWEN3_RERANK_SUFFIX}" for d in documents]
|
|
381
|
+
return wrapped_query, wrapped_docs
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class OpenAIAPIRerankerProvider(RerankerProvider):
|
|
385
|
+
"""Reranker via an OpenAI-compatible API endpoint."""
|
|
386
|
+
|
|
387
|
+
def __init__(self, model_id: str) -> None:
|
|
388
|
+
super().__init__(model_id)
|
|
389
|
+
self._base_url: str = ""
|
|
390
|
+
self._api_key: str = ""
|
|
391
|
+
self._prompt_style: str = ""
|
|
392
|
+
|
|
393
|
+
def configure(self, base_url: str, api_key: str, prompt_style: str = "") -> None:
|
|
394
|
+
self._base_url = base_url.rstrip("/")
|
|
395
|
+
self._api_key = api_key
|
|
396
|
+
self._prompt_style = prompt_style
|
|
397
|
+
|
|
398
|
+
async def warmup(self) -> None:
|
|
399
|
+
pass # No local model to warm up
|
|
400
|
+
|
|
401
|
+
async def rerank(
|
|
402
|
+
self,
|
|
403
|
+
query: str,
|
|
404
|
+
documents: list[str],
|
|
405
|
+
top_k: int = 10,
|
|
406
|
+
) -> list[RerankResult]:
|
|
407
|
+
if not documents:
|
|
408
|
+
return []
|
|
409
|
+
|
|
410
|
+
# Use the Jina/Cohere rerank API format (widely supported)
|
|
411
|
+
import httpx
|
|
412
|
+
|
|
413
|
+
send_query, send_docs = query, documents
|
|
414
|
+
if self._prompt_style == "qwen3":
|
|
415
|
+
send_query, send_docs = format_qwen3_rerank(query, documents)
|
|
416
|
+
|
|
417
|
+
url = f"{self._base_url}/v1/rerank"
|
|
418
|
+
headers = {
|
|
419
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
420
|
+
"Content-Type": "application/json",
|
|
421
|
+
}
|
|
422
|
+
payload = {
|
|
423
|
+
"model": self._model_id,
|
|
424
|
+
"query": send_query,
|
|
425
|
+
"documents": send_docs,
|
|
426
|
+
"top_n": top_k,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
430
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
431
|
+
response.raise_for_status()
|
|
432
|
+
data = response.json()
|
|
433
|
+
|
|
434
|
+
results = []
|
|
435
|
+
for item in data.get("results", []):
|
|
436
|
+
idx = item.get("index", 0)
|
|
437
|
+
results.append(RerankResult(
|
|
438
|
+
index=idx,
|
|
439
|
+
score=item.get("relevance_score", 0.0),
|
|
440
|
+
text=documents[idx],
|
|
441
|
+
))
|
|
442
|
+
|
|
443
|
+
return results
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def make_reranker_provider(
|
|
447
|
+
spec: ModelSpec,
|
|
448
|
+
gpu_type: str = "none",
|
|
449
|
+
api_base_url: str | None = None,
|
|
450
|
+
api_key: str | None = None,
|
|
451
|
+
) -> Optional[RerankerProvider]:
|
|
452
|
+
"""Create the appropriate reranker provider based on spec and hardware.
|
|
453
|
+
|
|
454
|
+
Returns None if the spec is None (tier doesn't have a reranker).
|
|
455
|
+
"""
|
|
456
|
+
if spec is None:
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
# Check if this is a multimodal reranker
|
|
460
|
+
if "image" in spec.modalities:
|
|
461
|
+
return _make_multimodal_reranker(spec, gpu_type, api_base_url, api_key)
|
|
462
|
+
|
|
463
|
+
if api_base_url and api_key:
|
|
464
|
+
provider = OpenAIAPIRerankerProvider(spec.model_id)
|
|
465
|
+
provider.configure(api_base_url, api_key)
|
|
466
|
+
return provider
|
|
467
|
+
|
|
468
|
+
if gpu_type == "cuda":
|
|
469
|
+
return CUDARerankerProvider(spec.model_id)
|
|
470
|
+
elif gpu_type == "native_mlx":
|
|
471
|
+
return NativeMLXRerankerProvider(spec.model_id)
|
|
472
|
+
elif gpu_type == "mlx":
|
|
473
|
+
return MLXRerankerProvider(spec.model_id)
|
|
474
|
+
else:
|
|
475
|
+
return CPURerankerProvider(spec.model_id)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _make_multimodal_reranker(
|
|
479
|
+
spec: ModelSpec,
|
|
480
|
+
gpu_type: str = "none",
|
|
481
|
+
api_base_url: str | None = None,
|
|
482
|
+
api_key: str | None = None,
|
|
483
|
+
) -> RerankerProvider:
|
|
484
|
+
"""Create a multimodal reranker provider.
|
|
485
|
+
|
|
486
|
+
For multimodal rerankers (e.g. jina-reranker-m0), uses the CrossEncoder
|
|
487
|
+
interface but supports image+text pairs. Falls back to text-only reranking
|
|
488
|
+
on image captions when a true multimodal cross-encoder isn't available.
|
|
489
|
+
"""
|
|
490
|
+
if api_base_url and api_key:
|
|
491
|
+
provider = OpenAIAPIRerankerProvider(spec.model_id)
|
|
492
|
+
provider.configure(api_base_url, api_key)
|
|
493
|
+
return provider
|
|
494
|
+
|
|
495
|
+
# Multimodal rerankers use the same CrossEncoder interface
|
|
496
|
+
# but with image+text pair support
|
|
497
|
+
if gpu_type == "cuda":
|
|
498
|
+
return CUDAMultimodalRerankerProvider(spec.model_id)
|
|
499
|
+
else:
|
|
500
|
+
return CPUMultimodalRerankerProvider(spec.model_id)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class CUDAMultimodalRerankerProvider(RerankerProvider):
|
|
504
|
+
"""Multimodal reranker on NVIDIA GPU.
|
|
505
|
+
|
|
506
|
+
Uses CrossEncoder with vision-language models that support
|
|
507
|
+
text+image pairs. For image documents, uses caption as text
|
|
508
|
+
representation.
|
|
509
|
+
"""
|
|
510
|
+
|
|
511
|
+
async def warmup(self) -> None:
|
|
512
|
+
loop = asyncio.get_running_loop()
|
|
513
|
+
self._model = await loop.run_in_executor(None, self._load_model)
|
|
514
|
+
|
|
515
|
+
def _load_model(self):
|
|
516
|
+
from sentence_transformers import CrossEncoder
|
|
517
|
+
return CrossEncoder(self._model_id, device="cuda")
|
|
518
|
+
|
|
519
|
+
async def rerank(
|
|
520
|
+
self,
|
|
521
|
+
query: str,
|
|
522
|
+
documents: list[str],
|
|
523
|
+
top_k: int = 10,
|
|
524
|
+
) -> list[RerankResult]:
|
|
525
|
+
if not documents:
|
|
526
|
+
return []
|
|
527
|
+
pairs = [(query, doc) for doc in documents]
|
|
528
|
+
loop = asyncio.get_running_loop()
|
|
529
|
+
scores = await loop.run_in_executor(None, self._model.predict, pairs)
|
|
530
|
+
results = [
|
|
531
|
+
RerankResult(index=i, score=float(s), text=documents[i])
|
|
532
|
+
for i, s in enumerate(scores)
|
|
533
|
+
]
|
|
534
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
535
|
+
return results[:top_k]
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class CPUMultimodalRerankerProvider(RerankerProvider):
|
|
539
|
+
"""Multimodal reranker on CPU."""
|
|
540
|
+
|
|
541
|
+
async def warmup(self) -> None:
|
|
542
|
+
loop = asyncio.get_running_loop()
|
|
543
|
+
self._model = await loop.run_in_executor(None, self._load_model)
|
|
544
|
+
|
|
545
|
+
def _load_model(self):
|
|
546
|
+
from sentence_transformers import CrossEncoder
|
|
547
|
+
return CrossEncoder(self._model_id, device="cpu")
|
|
548
|
+
|
|
549
|
+
async def rerank(
|
|
550
|
+
self,
|
|
551
|
+
query: str,
|
|
552
|
+
documents: list[str],
|
|
553
|
+
top_k: int = 10,
|
|
554
|
+
) -> list[RerankResult]:
|
|
555
|
+
if not documents:
|
|
556
|
+
return []
|
|
557
|
+
pairs = [(query, doc) for doc in documents]
|
|
558
|
+
loop = asyncio.get_running_loop()
|
|
559
|
+
scores = await loop.run_in_executor(None, self._model.predict, pairs)
|
|
560
|
+
results = [
|
|
561
|
+
RerankResult(index=i, score=float(s), text=documents[i])
|
|
562
|
+
for i, s in enumerate(scores)
|
|
563
|
+
]
|
|
564
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
565
|
+
return results[:top_k]
|