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
apsimo/vector/store.py
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"""Colony Vector Store — LanceDB wrapper.
|
|
2
|
+
|
|
3
|
+
Provides CRUD + ANN search across typed collections. Each
|
|
4
|
+
``Collection`` maps to a separate LanceDB table. All vector
|
|
5
|
+
operations degrade gracefully when the store is not initialized.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import math
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import re
|
|
16
|
+
import time
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
import pyarrow as pa
|
|
20
|
+
|
|
21
|
+
from apsimo.vector.collections import Collection
|
|
22
|
+
from apsimo.vector.query import VectorItem, VectorResult
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# LanceDB's json_extract() requires LargeBinary but our metadata column is Utf8.
|
|
27
|
+
# Detect filters that use json_extract(metadata, ...) so we can apply them in Python.
|
|
28
|
+
_METADATA_JSON_FILTER_RE = re.compile(
|
|
29
|
+
r"\bjson_extract\s*\(\s*metadata\b", re.IGNORECASE
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Parse individual json_extract(metadata, '$.key') <op> <value> conditions.
|
|
33
|
+
_JSON_COND_RE = re.compile(
|
|
34
|
+
r"json_extract\s*\(\s*metadata\s*,\s*['\"]?\$\.(\w+)['\"]?\s*\)"
|
|
35
|
+
r"\s*(>=|<=|!=|<>|>|<|=|LIKE)"
|
|
36
|
+
r"\s*(%?'[^']*'%?|%?\"[^\"]*\"%?|[-\d.]+)",
|
|
37
|
+
re.IGNORECASE,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _eval_metadata_filter(meta_str: str, filter_expr: str) -> bool:
|
|
42
|
+
"""Evaluate a filter expression containing json_extract(metadata,...) in Python.
|
|
43
|
+
|
|
44
|
+
Handles the patterns actually used in the codebase:
|
|
45
|
+
- ``json_extract(metadata, '$.key') >= <number>``
|
|
46
|
+
- ``json_extract(metadata, '$.key') LIKE '%value%'``
|
|
47
|
+
Multiple conditions are treated as AND (all must pass).
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
meta = json.loads(meta_str) if meta_str else {}
|
|
51
|
+
except (json.JSONDecodeError, TypeError):
|
|
52
|
+
meta = {}
|
|
53
|
+
|
|
54
|
+
for match in _JSON_COND_RE.finditer(filter_expr):
|
|
55
|
+
key = match.group(1)
|
|
56
|
+
op = match.group(2).upper()
|
|
57
|
+
raw_val = match.group(3).strip()
|
|
58
|
+
|
|
59
|
+
meta_val = meta.get(key)
|
|
60
|
+
|
|
61
|
+
if op == "LIKE":
|
|
62
|
+
# Strip surrounding % and quotes for a substring match
|
|
63
|
+
pattern = raw_val.strip("'\"").replace("%", "")
|
|
64
|
+
if pattern not in str(meta_val if meta_val is not None else ""):
|
|
65
|
+
return False
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Numeric or string comparison — strip quotes first
|
|
69
|
+
val_str = raw_val.strip("'\"")
|
|
70
|
+
try:
|
|
71
|
+
num_val = float(val_str)
|
|
72
|
+
num_meta = float(meta_val) if meta_val is not None else 0.0
|
|
73
|
+
if op == ">=" and not (num_meta >= num_val):
|
|
74
|
+
return False
|
|
75
|
+
elif op == "<=" and not (num_meta <= num_val):
|
|
76
|
+
return False
|
|
77
|
+
elif op == ">" and not (num_meta > num_val):
|
|
78
|
+
return False
|
|
79
|
+
elif op == "<" and not (num_meta < num_val):
|
|
80
|
+
return False
|
|
81
|
+
elif op == "=" and not (num_meta == num_val):
|
|
82
|
+
return False
|
|
83
|
+
elif op in ("!=", "<>") and not (num_meta != num_val):
|
|
84
|
+
return False
|
|
85
|
+
except (ValueError, TypeError):
|
|
86
|
+
str_meta = str(meta_val if meta_val is not None else "")
|
|
87
|
+
if op == "=" and str_meta != val_str:
|
|
88
|
+
return False
|
|
89
|
+
elif op in ("!=", "<>") and str_meta == val_str:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _base_schema(dims: int) -> pa.Schema:
|
|
96
|
+
"""Build the Arrow schema shared by all collection tables."""
|
|
97
|
+
return pa.schema([
|
|
98
|
+
pa.field("id", pa.utf8()),
|
|
99
|
+
pa.field("text", pa.utf8()),
|
|
100
|
+
pa.field("vector", pa.list_(pa.float32(), dims)),
|
|
101
|
+
pa.field("metadata", pa.utf8()),
|
|
102
|
+
pa.field("scope_key", pa.utf8()),
|
|
103
|
+
pa.field("modality", pa.utf8()),
|
|
104
|
+
pa.field("image_hash", pa.utf8()),
|
|
105
|
+
pa.field("image_ref", pa.utf8()),
|
|
106
|
+
pa.field("thumbnail_ref", pa.utf8()),
|
|
107
|
+
pa.field("caption", pa.utf8()),
|
|
108
|
+
pa.field("created_at", pa.float64()),
|
|
109
|
+
pa.field("updated_at", pa.float64()),
|
|
110
|
+
])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class VectorStore:
|
|
114
|
+
"""LanceDB-backed vector store. One table per Collection."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, data_dir: str, *, identity=None, catalog=None) -> None:
|
|
117
|
+
self._data_dir = data_dir
|
|
118
|
+
self._db = None
|
|
119
|
+
self._dims: int | None = None
|
|
120
|
+
self.identity = identity
|
|
121
|
+
self.catalog = catalog
|
|
122
|
+
if (identity is None) != (catalog is None):
|
|
123
|
+
raise ValueError('Managed indexes require both embedding identity and source-ledger catalog')
|
|
124
|
+
self._generation_dbs = {}
|
|
125
|
+
|
|
126
|
+
async def connect(self, dimensions: int) -> None:
|
|
127
|
+
"""Open (or create) the LanceDB database directory."""
|
|
128
|
+
import lancedb
|
|
129
|
+
|
|
130
|
+
os.makedirs(self._data_dir, exist_ok=True)
|
|
131
|
+
self._db = await lancedb.connect_async(self._data_dir)
|
|
132
|
+
self._dims = dimensions
|
|
133
|
+
if self.identity is not None and self.identity.dimensions != dimensions:
|
|
134
|
+
raise ValueError('Embedding identity dimension disagrees with selected pipeline')
|
|
135
|
+
if self.catalog is not None:
|
|
136
|
+
self.catalog.initialize(self.identity, legacy_exists=bool(await self._db.table_names()))
|
|
137
|
+
self._generation_dbs['legacy'] = self._db
|
|
138
|
+
logger.info("VectorStore connected (path=%s, dims=%d)", self._data_dir, dimensions)
|
|
139
|
+
|
|
140
|
+
async def ensure_collections(self, dimensions: int) -> None:
|
|
141
|
+
"""Create any missing collection tables."""
|
|
142
|
+
if self._db is None:
|
|
143
|
+
await self.connect(dimensions)
|
|
144
|
+
|
|
145
|
+
db = self._db
|
|
146
|
+
if self.catalog is not None:
|
|
147
|
+
active = self.catalog.active()
|
|
148
|
+
if active['fingerprint'] != self.identity.fingerprint:
|
|
149
|
+
return # Never relabel or overwrite an existing unknown index.
|
|
150
|
+
db = await self._generation_db(active)
|
|
151
|
+
existing = set(await db.table_names())
|
|
152
|
+
schema = _base_schema(dimensions)
|
|
153
|
+
for col in Collection:
|
|
154
|
+
if col.value not in existing:
|
|
155
|
+
await db.create_table(col.value, schema=schema)
|
|
156
|
+
logger.info("Created vector collection: %s", col.value)
|
|
157
|
+
|
|
158
|
+
async def _generation_db(self, generation):
|
|
159
|
+
key = generation['id']
|
|
160
|
+
if key not in self._generation_dbs:
|
|
161
|
+
import lancedb
|
|
162
|
+
if not re.fullmatch(r'[0-9a-f]{32}', key):
|
|
163
|
+
raise ValueError('Invalid vector generation identifier')
|
|
164
|
+
path = Path(self._data_dir) / 'generations' / key
|
|
165
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
self._generation_dbs[key] = await lancedb.connect_async(str(path))
|
|
167
|
+
return self._generation_dbs[key]
|
|
168
|
+
|
|
169
|
+
async def _table(self, collection, *, write=False, semantic=False, generation=None):
|
|
170
|
+
if self.catalog is None:
|
|
171
|
+
return await self._db.open_table(collection.value)
|
|
172
|
+
if generation is None:
|
|
173
|
+
generation = (self.catalog.write_generation(self.identity) if write else
|
|
174
|
+
self.catalog.read_generation(self.identity) if semantic else self.catalog.active())
|
|
175
|
+
db = await self._generation_db(generation)
|
|
176
|
+
if write and collection.value not in await db.table_names():
|
|
177
|
+
await db.create_table(collection.value, schema=_base_schema(generation['identity']['dimensions']), exist_ok=True)
|
|
178
|
+
return await db.open_table(collection.value)
|
|
179
|
+
|
|
180
|
+
def _eligible(self, collection, entry_id, metadata):
|
|
181
|
+
return self.catalog is None or not (
|
|
182
|
+
self.catalog.deleted(collection.value, str(entry_id)) or self.catalog.source_erased(metadata))
|
|
183
|
+
|
|
184
|
+
def _validate_vector(self, vector):
|
|
185
|
+
if self._dims and len(vector) != self._dims:
|
|
186
|
+
raise ValueError(f'Vector dimension mismatch: expected {self._dims}, got {len(vector)}; rebuild a separate index generation')
|
|
187
|
+
if not vector or not all(math.isfinite(value) for value in vector) or not any(vector):
|
|
188
|
+
raise ValueError('Vector must contain finite nonzero values')
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _quoted(value):
|
|
192
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------------------------
|
|
195
|
+
# CRUD
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
async def add(
|
|
199
|
+
self,
|
|
200
|
+
collection: Collection,
|
|
201
|
+
id: str,
|
|
202
|
+
text: str,
|
|
203
|
+
vector: list[float],
|
|
204
|
+
metadata: dict[str, Any] | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Add a single vector entry."""
|
|
207
|
+
now = time.time()
|
|
208
|
+
meta = metadata or {}
|
|
209
|
+
meta.setdefault("embedded_at", now)
|
|
210
|
+
meta.setdefault("modality", "text")
|
|
211
|
+
|
|
212
|
+
# Dimension validation
|
|
213
|
+
self._validate_vector(vector)
|
|
214
|
+
if not self._eligible(collection, id, meta):
|
|
215
|
+
return
|
|
216
|
+
if self.identity is not None:
|
|
217
|
+
meta = {**meta, 'embedding_fingerprint': self.identity.fingerprint,
|
|
218
|
+
'requested_model': self.identity.requested_model,
|
|
219
|
+
'served_model': self.identity.served_model,
|
|
220
|
+
'declared_revision': self.identity.declared_revision}
|
|
221
|
+
|
|
222
|
+
table = await self._table(collection, write=True)
|
|
223
|
+
await table.add([{
|
|
224
|
+
"id": id,
|
|
225
|
+
"text": text,
|
|
226
|
+
"vector": vector,
|
|
227
|
+
"metadata": json.dumps(meta),
|
|
228
|
+
"modality": meta.get("modality", "text"),
|
|
229
|
+
"image_hash": meta.get("image_hash", ""),
|
|
230
|
+
"image_ref": meta.get("image_ref", ""),
|
|
231
|
+
"thumbnail_ref": meta.get("thumbnail_ref", ""),
|
|
232
|
+
"caption": meta.get("caption", ""),
|
|
233
|
+
"created_at": now,
|
|
234
|
+
"updated_at": now,
|
|
235
|
+
}])
|
|
236
|
+
if not self._eligible(collection, id, meta):
|
|
237
|
+
await table.delete('id = ' + self._quoted(id))
|
|
238
|
+
|
|
239
|
+
async def add_batch(
|
|
240
|
+
self,
|
|
241
|
+
collection: Collection,
|
|
242
|
+
items: list[VectorItem],
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Add a batch of vector entries."""
|
|
245
|
+
if not items:
|
|
246
|
+
return
|
|
247
|
+
now = time.time()
|
|
248
|
+
|
|
249
|
+
# Dimension validation on first item
|
|
250
|
+
for item in items:
|
|
251
|
+
self._validate_vector(item.vector)
|
|
252
|
+
|
|
253
|
+
table = await self._table(collection, write=True)
|
|
254
|
+
rows = []
|
|
255
|
+
for item in items:
|
|
256
|
+
meta = item.metadata or {}
|
|
257
|
+
if not self._eligible(collection, item.id, meta):
|
|
258
|
+
continue
|
|
259
|
+
if self.identity is not None:
|
|
260
|
+
meta = {**meta, 'embedding_fingerprint': self.identity.fingerprint}
|
|
261
|
+
meta.setdefault("embedded_at", now)
|
|
262
|
+
meta.setdefault("modality", "text")
|
|
263
|
+
rows.append({
|
|
264
|
+
"id": item.id,
|
|
265
|
+
"text": item.text,
|
|
266
|
+
"vector": item.vector,
|
|
267
|
+
"metadata": json.dumps(meta),
|
|
268
|
+
"modality": meta.get("modality", "text"),
|
|
269
|
+
"image_hash": meta.get("image_hash", ""),
|
|
270
|
+
"image_ref": meta.get("image_ref", ""),
|
|
271
|
+
"thumbnail_ref": meta.get("thumbnail_ref", ""),
|
|
272
|
+
"caption": meta.get("caption", ""),
|
|
273
|
+
"created_at": now,
|
|
274
|
+
"updated_at": now,
|
|
275
|
+
})
|
|
276
|
+
if rows:
|
|
277
|
+
await table.add(rows)
|
|
278
|
+
for item in items:
|
|
279
|
+
if not self._eligible(collection, item.id, item.metadata):
|
|
280
|
+
await table.delete('id = ' + self._quoted(item.id))
|
|
281
|
+
|
|
282
|
+
async def search(
|
|
283
|
+
self,
|
|
284
|
+
collection: Collection,
|
|
285
|
+
query_vector: list[float],
|
|
286
|
+
limit: int = 10,
|
|
287
|
+
filter: Optional[str] = None,
|
|
288
|
+
min_score: float = 0.0,
|
|
289
|
+
) -> list[VectorResult]:
|
|
290
|
+
"""ANN search on a collection. Returns results sorted by score descending."""
|
|
291
|
+
self._validate_vector(query_vector)
|
|
292
|
+
table = await self._table(collection, semantic=True)
|
|
293
|
+
query = table.vector_search(query_vector).distance_type("cosine")
|
|
294
|
+
|
|
295
|
+
if filter and _METADATA_JSON_FILTER_RE.search(filter):
|
|
296
|
+
# json_extract(metadata, ...) requires LargeBinary but the column is
|
|
297
|
+
# Utf8 — the SQL planner rejects it. Fetch a larger candidate set
|
|
298
|
+
# without the metadata filter and apply it in Python instead.
|
|
299
|
+
query = query.limit(max(limit * 20, 200))
|
|
300
|
+
raw = await query.to_pandas()
|
|
301
|
+
mask = raw["metadata"].apply(
|
|
302
|
+
lambda m: _eval_metadata_filter(str(m) if m is not None else "{}", filter)
|
|
303
|
+
)
|
|
304
|
+
results = raw[mask].head(limit)
|
|
305
|
+
else:
|
|
306
|
+
query = query.limit(limit)
|
|
307
|
+
if filter:
|
|
308
|
+
query = query.where(filter)
|
|
309
|
+
results = await query.to_pandas()
|
|
310
|
+
|
|
311
|
+
out: list[VectorResult] = []
|
|
312
|
+
for _, row in results.iterrows():
|
|
313
|
+
# LanceDB returns _distance (cosine distance); convert to similarity
|
|
314
|
+
score = 1.0 - float(row.get("_distance", 0.0))
|
|
315
|
+
if score < min_score:
|
|
316
|
+
continue
|
|
317
|
+
meta_str = row.get("metadata", "{}")
|
|
318
|
+
try:
|
|
319
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else {}
|
|
320
|
+
except (json.JSONDecodeError, TypeError):
|
|
321
|
+
meta = {}
|
|
322
|
+
if not self._eligible(collection, row['id'], meta):
|
|
323
|
+
continue
|
|
324
|
+
out.append(VectorResult(
|
|
325
|
+
id=str(row["id"]),
|
|
326
|
+
score=score,
|
|
327
|
+
text=str(row.get("text", "")),
|
|
328
|
+
metadata=meta,
|
|
329
|
+
))
|
|
330
|
+
|
|
331
|
+
return out
|
|
332
|
+
|
|
333
|
+
async def search_cross_modal(
|
|
334
|
+
self,
|
|
335
|
+
collection: Collection,
|
|
336
|
+
query_vector: list[float],
|
|
337
|
+
limit: int = 10,
|
|
338
|
+
filter_modality: Optional[str] = None,
|
|
339
|
+
min_score: float = 0.0,
|
|
340
|
+
) -> list[dict[str, Any]]:
|
|
341
|
+
"""Cross-modal search — text query finds images, image query finds text.
|
|
342
|
+
|
|
343
|
+
Works because multimodal models produce vectors in the same
|
|
344
|
+
embedding space regardless of input type.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
filter_modality : str, optional
|
|
349
|
+
Only return results of this modality ("text" or "image").
|
|
350
|
+
None = return all modalities.
|
|
351
|
+
"""
|
|
352
|
+
filter_clause = None
|
|
353
|
+
if filter_modality:
|
|
354
|
+
filter_clause = f"modality = '{filter_modality}'"
|
|
355
|
+
|
|
356
|
+
results = await self.search(
|
|
357
|
+
collection, query_vector, limit=limit,
|
|
358
|
+
filter=filter_clause, min_score=min_score,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Enrich with modality-specific fields
|
|
362
|
+
enriched = []
|
|
363
|
+
for r in results:
|
|
364
|
+
meta = r.metadata or {}
|
|
365
|
+
entry = {
|
|
366
|
+
"id": r.id,
|
|
367
|
+
"score": r.score,
|
|
368
|
+
"text": r.text,
|
|
369
|
+
"modality": meta.get("modality", "text"),
|
|
370
|
+
"image_ref": meta.get("image_ref", ""),
|
|
371
|
+
"image_hash": meta.get("image_hash", ""),
|
|
372
|
+
"thumbnail_ref": meta.get("thumbnail_ref", ""),
|
|
373
|
+
"caption": meta.get("caption", ""),
|
|
374
|
+
"metadata": meta,
|
|
375
|
+
}
|
|
376
|
+
enriched.append(entry)
|
|
377
|
+
return enriched
|
|
378
|
+
|
|
379
|
+
async def search_by_image_hash(self, collection: Collection, image_hash: str) -> Optional[VectorResult]:
|
|
380
|
+
"""Find an existing vector by image hash (for dedup)."""
|
|
381
|
+
try:
|
|
382
|
+
table = await self._table(collection, semantic=True)
|
|
383
|
+
results = await table.query().where('image_hash = ' + self._quoted(image_hash)).limit(1).to_pandas()
|
|
384
|
+
if results.empty:
|
|
385
|
+
return None
|
|
386
|
+
row = results.iloc[0]
|
|
387
|
+
meta_str = row.get("metadata", "{}")
|
|
388
|
+
try:
|
|
389
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else {}
|
|
390
|
+
except (json.JSONDecodeError, TypeError):
|
|
391
|
+
meta = {}
|
|
392
|
+
if not self._eligible(collection, row['id'], meta):
|
|
393
|
+
return None
|
|
394
|
+
return VectorResult(
|
|
395
|
+
id=str(row["id"]),
|
|
396
|
+
score=1.0,
|
|
397
|
+
text=str(row.get("text", "")),
|
|
398
|
+
metadata=meta,
|
|
399
|
+
)
|
|
400
|
+
except Exception:
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
async def delete(self, collection: Collection, id: str) -> None:
|
|
404
|
+
"""Delete a single entry by ID."""
|
|
405
|
+
if self.catalog is None:
|
|
406
|
+
table = await self._table(collection)
|
|
407
|
+
await table.delete('id = ' + self._quoted(id))
|
|
408
|
+
return
|
|
409
|
+
self.catalog.delete(collection.value, id)
|
|
410
|
+
# Exact ID removal covers active, staged, unknown legacy and retained generations.
|
|
411
|
+
for generation in self.catalog.generations():
|
|
412
|
+
db = await self._generation_db(generation)
|
|
413
|
+
if collection.value in await db.table_names():
|
|
414
|
+
await (await db.open_table(collection.value)).delete('id = ' + self._quoted(id))
|
|
415
|
+
|
|
416
|
+
async def update(
|
|
417
|
+
self,
|
|
418
|
+
collection: Collection,
|
|
419
|
+
id: str,
|
|
420
|
+
text: str,
|
|
421
|
+
vector: list[float],
|
|
422
|
+
metadata: dict[str, Any] | None = None,
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Validate first, then atomically replace an entry in one generation."""
|
|
425
|
+
self._validate_vector(vector)
|
|
426
|
+
meta = dict(metadata or {})
|
|
427
|
+
if not self._eligible(collection, id, meta):
|
|
428
|
+
return
|
|
429
|
+
now = time.time()
|
|
430
|
+
meta.setdefault('embedded_at', now)
|
|
431
|
+
if self.identity is not None:
|
|
432
|
+
meta['embedding_fingerprint'] = self.identity.fingerprint
|
|
433
|
+
row = {'id': id, 'text': text, 'vector': vector, 'metadata': json.dumps(meta),
|
|
434
|
+
'modality': meta.get('modality', 'text'), 'image_hash': meta.get('image_hash', ''),
|
|
435
|
+
'image_ref': meta.get('image_ref', ''), 'thumbnail_ref': meta.get('thumbnail_ref', ''),
|
|
436
|
+
'caption': meta.get('caption', ''), 'created_at': now, 'updated_at': now}
|
|
437
|
+
table = await self._table(collection, write=True)
|
|
438
|
+
await table.merge_insert('id').when_matched_update_all().when_not_matched_insert_all().execute([row])
|
|
439
|
+
if not self._eligible(collection, id, meta):
|
|
440
|
+
await table.delete('id = ' + self._quoted(id))
|
|
441
|
+
|
|
442
|
+
async def get(self, collection: Collection, id: str) -> Optional[VectorResult]:
|
|
443
|
+
"""Fetch a single entry by ID."""
|
|
444
|
+
table = await self._table(collection)
|
|
445
|
+
results = await table.query().where('id = ' + self._quoted(id)).limit(1).to_pandas()
|
|
446
|
+
if results.empty:
|
|
447
|
+
return None
|
|
448
|
+
row = results.iloc[0]
|
|
449
|
+
meta_str = row.get("metadata", "{}")
|
|
450
|
+
try:
|
|
451
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else {}
|
|
452
|
+
except (json.JSONDecodeError, TypeError):
|
|
453
|
+
meta = {}
|
|
454
|
+
if not self._eligible(collection, row['id'], meta):
|
|
455
|
+
return None
|
|
456
|
+
return VectorResult(
|
|
457
|
+
id=str(row["id"]),
|
|
458
|
+
score=1.0,
|
|
459
|
+
text=str(row.get("text", "")),
|
|
460
|
+
metadata=meta,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
async def count(self, collection: Collection) -> int:
|
|
464
|
+
"""Return the number of entries in a collection."""
|
|
465
|
+
table = await self._table(collection)
|
|
466
|
+
return await table.count_rows()
|
|
467
|
+
|
|
468
|
+
async def list_ids(self, collection: Collection) -> list[str]:
|
|
469
|
+
"""Return all row ids in a collection via a projected query.
|
|
470
|
+
|
|
471
|
+
Unlike ``scan_all`` this never materializes the vector column — an
|
|
472
|
+
id-only projection, cheap enough to run against a large store (used
|
|
473
|
+
by the orphan-vector vacuum to diff against graph node ids).
|
|
474
|
+
"""
|
|
475
|
+
table = await self._table(collection)
|
|
476
|
+
df = await table.query().select(["id"]).to_pandas()
|
|
477
|
+
if df.empty:
|
|
478
|
+
return []
|
|
479
|
+
return [str(x) for x in df["id"].tolist()]
|
|
480
|
+
|
|
481
|
+
async def scan_all(self, collection: Collection) -> list[dict[str, Any]]:
|
|
482
|
+
"""Return all rows from a collection as raw dicts."""
|
|
483
|
+
table = await self._table(collection)
|
|
484
|
+
df = await table.to_pandas()
|
|
485
|
+
if df.empty:
|
|
486
|
+
return []
|
|
487
|
+
return df.to_dict(orient="records")
|
|
488
|
+
|
|
489
|
+
async def get_stored_models(self) -> list[str]:
|
|
490
|
+
"""Return unique model_id values across all collections."""
|
|
491
|
+
models: set[str] = set()
|
|
492
|
+
for col in Collection:
|
|
493
|
+
try:
|
|
494
|
+
rows = await self.scan_all(col)
|
|
495
|
+
for row in rows:
|
|
496
|
+
meta_str = row.get("metadata", "{}")
|
|
497
|
+
try:
|
|
498
|
+
meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
|
|
499
|
+
except (json.JSONDecodeError, TypeError):
|
|
500
|
+
meta = {}
|
|
501
|
+
model_id = meta.get("model_id", "")
|
|
502
|
+
if model_id:
|
|
503
|
+
models.add(model_id)
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
return sorted(models)
|
|
507
|
+
|
|
508
|
+
async def close(self) -> None:
|
|
509
|
+
"""Release database resources."""
|
|
510
|
+
self._db = None
|
|
511
|
+
self._generation_dbs.clear()
|
|
512
|
+
|
|
513
|
+
async def erase_source_projections(self, turn_ids):
|
|
514
|
+
"""Remove exact linked rows even when the old graph row is already gone."""
|
|
515
|
+
if self.catalog is None:
|
|
516
|
+
return 0
|
|
517
|
+
selected = {'turn:' + value for value in turn_ids}
|
|
518
|
+
deleted = set()
|
|
519
|
+
for generation in self.catalog.generations():
|
|
520
|
+
db = await self._generation_db(generation)
|
|
521
|
+
names = await db.table_names()
|
|
522
|
+
for collection in Collection:
|
|
523
|
+
if collection.value not in names:
|
|
524
|
+
continue
|
|
525
|
+
table = await db.open_table(collection.value)
|
|
526
|
+
rows = await table.query().select(['id', 'metadata']).to_list()
|
|
527
|
+
for row in rows:
|
|
528
|
+
meta = json.loads(row['metadata'] or '{}')
|
|
529
|
+
if meta.get('source_uri') in selected and self.catalog.source_erased(meta):
|
|
530
|
+
self.catalog.delete(collection.value, row['id'])
|
|
531
|
+
await table.delete('id = ' + self._quoted(row['id']))
|
|
532
|
+
deleted.add((collection.value, row['id']))
|
|
533
|
+
return len(deleted)
|