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,68 @@
|
|
|
1
|
+
"""CSV entity extractor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import io
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from apsimo.world_model.extraction.base import FormatExtractor, ExtractedEntity
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CSVExtractor(FormatExtractor):
|
|
16
|
+
"""Extract entities from CSV data.
|
|
17
|
+
|
|
18
|
+
Each row becomes an entity. Column headers are used as attribute keys.
|
|
19
|
+
A 'name' or 'title' column (if present) is used as the entity name.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def supported_formats(self) -> List[str]:
|
|
23
|
+
return ["text/csv"]
|
|
24
|
+
|
|
25
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
26
|
+
try:
|
|
27
|
+
text = content.decode("utf-8")
|
|
28
|
+
except UnicodeDecodeError:
|
|
29
|
+
try:
|
|
30
|
+
text = content.decode("latin-1")
|
|
31
|
+
except Exception:
|
|
32
|
+
return ""
|
|
33
|
+
return text
|
|
34
|
+
|
|
35
|
+
async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
36
|
+
try:
|
|
37
|
+
text = content.decode("utf-8")
|
|
38
|
+
except UnicodeDecodeError:
|
|
39
|
+
try:
|
|
40
|
+
text = content.decode("latin-1")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.warning("Failed to decode CSV: %s", e)
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
reader = csv.DictReader(io.StringIO(text))
|
|
46
|
+
if not reader.fieldnames:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
entities = []
|
|
50
|
+
for i, row in enumerate(reader):
|
|
51
|
+
# Find the name column
|
|
52
|
+
name = row.get("name") or row.get("title") or row.get("Name") or row.get("Title") or f"row-{i}"
|
|
53
|
+
etype = row.get("type") or row.get("Type") or "entity"
|
|
54
|
+
|
|
55
|
+
# All other columns become attributes
|
|
56
|
+
attrs = {}
|
|
57
|
+
for k, v in row.items():
|
|
58
|
+
if k and k.lower() not in ("name", "title", "type") and v:
|
|
59
|
+
attrs[k] = v
|
|
60
|
+
|
|
61
|
+
entities.append(ExtractedEntity(
|
|
62
|
+
name=str(name).strip(),
|
|
63
|
+
entity_type=str(etype).strip(),
|
|
64
|
+
attributes=attrs,
|
|
65
|
+
confidence=0.85,
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
return entities
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""HTML entity extractor (requires BeautifulSoup)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
from apsimo.world_model.extraction.base import FormatExtractor, ExtractedEntity
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from bs4 import BeautifulSoup
|
|
14
|
+
HAS_BS4 = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_BS4 = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HTMLExtractor(FormatExtractor):
|
|
20
|
+
"""Extract text and entities from HTML via BeautifulSoup."""
|
|
21
|
+
|
|
22
|
+
def supported_formats(self) -> List[str]:
|
|
23
|
+
return ["text/html"]
|
|
24
|
+
|
|
25
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
26
|
+
if not HAS_BS4:
|
|
27
|
+
logger.warning("BeautifulSoup not installed — cannot extract HTML text")
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
soup = BeautifulSoup(content, "html.parser")
|
|
32
|
+
# Remove script and style elements
|
|
33
|
+
for tag in soup(["script", "style", "nav", "footer", "header"]):
|
|
34
|
+
tag.decompose()
|
|
35
|
+
return soup.get_text(separator="\n", strip=True)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.warning("Failed to extract HTML text: %s", e)
|
|
38
|
+
return ""
|
|
39
|
+
|
|
40
|
+
async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
41
|
+
if not HAS_BS4:
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
soup = BeautifulSoup(content, "html.parser")
|
|
46
|
+
entities = []
|
|
47
|
+
|
|
48
|
+
# Extract metadata entities
|
|
49
|
+
title = soup.find("title")
|
|
50
|
+
if title and title.string:
|
|
51
|
+
entities.append(ExtractedEntity(
|
|
52
|
+
name=title.string.strip(),
|
|
53
|
+
entity_type="webpage",
|
|
54
|
+
attributes={"type": "title"},
|
|
55
|
+
confidence=0.95,
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
# Extract links as relationship hints
|
|
59
|
+
for a in soup.find_all("a", href=True)[:20]:
|
|
60
|
+
link_text = a.get_text(strip=True)
|
|
61
|
+
if link_text:
|
|
62
|
+
entities.append(ExtractedEntity(
|
|
63
|
+
name=link_text,
|
|
64
|
+
entity_type="link",
|
|
65
|
+
attributes={"href": a["href"]},
|
|
66
|
+
confidence=0.7,
|
|
67
|
+
))
|
|
68
|
+
|
|
69
|
+
return entities
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning("Failed to extract HTML entities: %s", e)
|
|
72
|
+
return []
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""JSON entity extractor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
|
|
9
|
+
from apsimo.world_model.extraction.base import FormatExtractor, ExtractedEntity
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JSONExtractor(FormatExtractor):
|
|
15
|
+
"""Extract entities from structured JSON data.
|
|
16
|
+
|
|
17
|
+
Handles two patterns:
|
|
18
|
+
1. Array of objects: each object becomes an entity
|
|
19
|
+
2. Nested objects: top-level keys become entity types
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def supported_formats(self) -> List[str]:
|
|
23
|
+
return ["application/json"]
|
|
24
|
+
|
|
25
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
26
|
+
try:
|
|
27
|
+
data = json.loads(content)
|
|
28
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
29
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
33
|
+
try:
|
|
34
|
+
data = json.loads(content)
|
|
35
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
36
|
+
logger.warning("Failed to parse JSON: %s", e)
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
entities = []
|
|
40
|
+
|
|
41
|
+
# Array of objects — each becomes an entity
|
|
42
|
+
if isinstance(data, list):
|
|
43
|
+
for i, item in enumerate(data):
|
|
44
|
+
if isinstance(item, dict):
|
|
45
|
+
name = item.get("name") or item.get("title") or item.get("id") or f"item-{i}"
|
|
46
|
+
etype = item.get("type", "entity")
|
|
47
|
+
attrs = {k: v for k, v in item.items() if k not in ("name", "title", "id", "type")}
|
|
48
|
+
entities.append(ExtractedEntity(
|
|
49
|
+
name=str(name),
|
|
50
|
+
entity_type=str(etype),
|
|
51
|
+
attributes=attrs,
|
|
52
|
+
confidence=0.9,
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
# Single object — top-level keys become attributes
|
|
56
|
+
elif isinstance(data, dict):
|
|
57
|
+
# If it has a 'name' or 'title', treat as one entity
|
|
58
|
+
name = data.get("name") or data.get("title") or metadata.get("filename", "json-object")
|
|
59
|
+
etype = data.get("type", "entity")
|
|
60
|
+
attrs = {k: v for k, v in data.items() if k not in ("name", "title", "type") and isinstance(v, (str, int, float, bool))}
|
|
61
|
+
entities.append(ExtractedEntity(
|
|
62
|
+
name=str(name),
|
|
63
|
+
entity_type=str(etype),
|
|
64
|
+
attributes=attrs,
|
|
65
|
+
confidence=0.9,
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
return entities
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""PDF entity extractor (requires PyMuPDF)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
from apsimo.world_model.extraction.base import FormatExtractor, ExtractedEntity
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import fitz # PyMuPDF
|
|
14
|
+
HAS_PYMUPDF = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
HAS_PYMUPDF = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PDFExtractor(FormatExtractor):
|
|
20
|
+
"""Extract text and entities from PDF files via PyMuPDF."""
|
|
21
|
+
|
|
22
|
+
def supported_formats(self) -> List[str]:
|
|
23
|
+
return ["application/pdf"]
|
|
24
|
+
|
|
25
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
26
|
+
if not HAS_PYMUPDF:
|
|
27
|
+
logger.warning("PyMuPDF not installed — cannot extract PDF text")
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
doc = fitz.open(stream=content, filetype="pdf")
|
|
32
|
+
text_parts = []
|
|
33
|
+
for page in doc:
|
|
34
|
+
text_parts.append(page.get_text())
|
|
35
|
+
doc.close()
|
|
36
|
+
return "\n\n".join(text_parts)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.warning("Failed to extract PDF text: %s", e)
|
|
39
|
+
return ""
|
|
40
|
+
|
|
41
|
+
async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
42
|
+
# PDFs don't have structured entities — rely on text extraction + LLM
|
|
43
|
+
return []
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Plain text extractor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
from apsimo.world_model.extraction.base import FormatExtractor, ExtractedEntity
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TextExtractor(FormatExtractor):
|
|
14
|
+
"""Extract entities from plain text (via LLM)."""
|
|
15
|
+
|
|
16
|
+
def supported_formats(self) -> List[str]:
|
|
17
|
+
return ["text/plain", "text/markdown"]
|
|
18
|
+
|
|
19
|
+
async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
|
|
20
|
+
try:
|
|
21
|
+
return content.decode("utf-8")
|
|
22
|
+
except UnicodeDecodeError:
|
|
23
|
+
try:
|
|
24
|
+
return content.decode("latin-1")
|
|
25
|
+
except Exception:
|
|
26
|
+
logger.warning("Failed to decode text content")
|
|
27
|
+
return ""
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""LLM-backed entity extractor — the fallback path for ExtractionPipeline.
|
|
2
|
+
|
|
3
|
+
``ExtractionPipeline`` first tries each ``FormatExtractor``'s structured
|
|
4
|
+
``extract_entities`` pass. When that returns an empty list (common for
|
|
5
|
+
unstructured PDFs, plain HTML, free-form text) the pipeline falls back to
|
|
6
|
+
``extract_text`` + this LLM extractor.
|
|
7
|
+
|
|
8
|
+
The LLM is prompted to return a strict JSON array; anything else (prose,
|
|
9
|
+
partial JSON, network errors) degrades to an empty list so the pipeline
|
|
10
|
+
stays non-fatal.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
from apsimo.world_model.extraction.base import ExtractedEntity
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
LlmExtractFn = Callable[[str, Dict[str, Any]], Awaitable[List[ExtractedEntity]]]
|
|
25
|
+
|
|
26
|
+
# Plenty of room for a useful document but bounded so we don't blow tokens.
|
|
27
|
+
MAX_INPUT_CHARS = 12_000
|
|
28
|
+
MAX_RESPONSE_TOKENS = 1_024
|
|
29
|
+
|
|
30
|
+
_SYSTEM_PROMPT = (
|
|
31
|
+
"You are an entity extraction engine. Read the document and return a "
|
|
32
|
+
"JSON array of entities. Each entity must be an object with keys: "
|
|
33
|
+
'"name" (string, required), "type" (one of: person, company, project, '
|
|
34
|
+
'product, location, event, concept), "attributes" (object, optional), '
|
|
35
|
+
'"confidence" (number 0-1, optional). Return ONLY the JSON array: '
|
|
36
|
+
"no prose, no code fences, no explanation. Return [] if no entities "
|
|
37
|
+
"are present. Do not invent entities not grounded in the text."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_llm_extract_fn(
|
|
42
|
+
llm_router: Any,
|
|
43
|
+
*,
|
|
44
|
+
max_input_chars: int = MAX_INPUT_CHARS,
|
|
45
|
+
max_response_tokens: int = MAX_RESPONSE_TOKENS,
|
|
46
|
+
) -> LlmExtractFn:
|
|
47
|
+
"""Build an ``llm_extract_fn`` bound to the given ``LLMRouter``.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
llm_router :
|
|
52
|
+
An object with an ``async complete(messages, ...)`` method that
|
|
53
|
+
returns an object with a ``.content`` attribute (the ``LLMRouter``
|
|
54
|
+
from ``apsimo.router.router``).
|
|
55
|
+
max_input_chars :
|
|
56
|
+
Truncate the text to this many characters before sending to the LLM.
|
|
57
|
+
max_response_tokens :
|
|
58
|
+
Cap on LLM output tokens.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
async def extract(text: str, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
|
|
62
|
+
snippet = (text or "").strip()
|
|
63
|
+
if not snippet:
|
|
64
|
+
return []
|
|
65
|
+
if len(snippet) > max_input_chars:
|
|
66
|
+
snippet = snippet[:max_input_chars]
|
|
67
|
+
user_prompt = _build_user_prompt(snippet, metadata)
|
|
68
|
+
try:
|
|
69
|
+
resp = await llm_router.complete(
|
|
70
|
+
messages=[
|
|
71
|
+
{"role": "system", "content": _SYSTEM_PROMPT},
|
|
72
|
+
{"role": "user", "content": user_prompt},
|
|
73
|
+
],
|
|
74
|
+
context={"task": "entity_extraction", "max_tokens": max_response_tokens},
|
|
75
|
+
)
|
|
76
|
+
except Exception as exc:
|
|
77
|
+
logger.warning("LLM entity extraction call failed: %s", exc)
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
content = getattr(resp, "content", "") or ""
|
|
81
|
+
return _parse_entity_array(content)
|
|
82
|
+
|
|
83
|
+
return extract
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _build_user_prompt(text: str, metadata: Dict[str, Any]) -> str:
|
|
87
|
+
hints: List[str] = []
|
|
88
|
+
filename = metadata.get("filename") or metadata.get("source")
|
|
89
|
+
if filename:
|
|
90
|
+
hints.append(f"Source: {filename}")
|
|
91
|
+
if metadata.get("mime_type"):
|
|
92
|
+
hints.append(f"MIME: {metadata['mime_type']}")
|
|
93
|
+
prefix = ("\n".join(hints) + "\n\n") if hints else ""
|
|
94
|
+
return f"{prefix}Document:\n---\n{text}\n---\n\nReturn the JSON array."
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_TYPE_SYNONYMS = {
|
|
98
|
+
"organization": "company", "org": "company", "business": "company",
|
|
99
|
+
"place": "location",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _parse_entity_array(raw: str) -> List[ExtractedEntity]:
|
|
104
|
+
"""Best-effort parse of an LLM response into ``ExtractedEntity`` objects.
|
|
105
|
+
|
|
106
|
+
Accepts either a bare JSON array or an object with an ``entities`` key
|
|
107
|
+
(LLMs occasionally wrap the array). Tolerates code fences.
|
|
108
|
+
"""
|
|
109
|
+
if not raw:
|
|
110
|
+
return []
|
|
111
|
+
candidate = _strip_code_fence(raw).strip()
|
|
112
|
+
try:
|
|
113
|
+
parsed = json.loads(candidate)
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
# Fall back to the first [...] block in the response.
|
|
116
|
+
match = re.search(r"\[.*\]", candidate, re.DOTALL)
|
|
117
|
+
if not match:
|
|
118
|
+
logger.debug("LLM extractor: no JSON array in response")
|
|
119
|
+
return []
|
|
120
|
+
try:
|
|
121
|
+
parsed = json.loads(match.group(0))
|
|
122
|
+
except json.JSONDecodeError as exc:
|
|
123
|
+
logger.debug("LLM extractor: JSON parse failed (%s)", exc)
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
if isinstance(parsed, dict):
|
|
127
|
+
parsed = parsed.get("entities") or parsed.get("items") or []
|
|
128
|
+
if not isinstance(parsed, list):
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
entities: List[ExtractedEntity] = []
|
|
132
|
+
for item in parsed:
|
|
133
|
+
if not isinstance(item, dict):
|
|
134
|
+
continue
|
|
135
|
+
name = item.get("name")
|
|
136
|
+
entity_type = item.get("type") or item.get("entity_type")
|
|
137
|
+
if not isinstance(name, str) or not isinstance(entity_type, str):
|
|
138
|
+
continue
|
|
139
|
+
entity_type = _TYPE_SYNONYMS.get(
|
|
140
|
+
entity_type.strip().lower(), entity_type.strip().lower())
|
|
141
|
+
attributes = item.get("attributes")
|
|
142
|
+
if not isinstance(attributes, dict):
|
|
143
|
+
attributes = None
|
|
144
|
+
confidence = item.get("confidence", 0.7)
|
|
145
|
+
try:
|
|
146
|
+
confidence = float(confidence)
|
|
147
|
+
except (TypeError, ValueError):
|
|
148
|
+
confidence = 0.7
|
|
149
|
+
entities.append(
|
|
150
|
+
ExtractedEntity(
|
|
151
|
+
name=name.strip(),
|
|
152
|
+
entity_type=entity_type.strip(),
|
|
153
|
+
attributes=attributes,
|
|
154
|
+
confidence=max(0.0, min(1.0, confidence)),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
return entities
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
_CODE_FENCE = re.compile(r"^```(?:json)?\s*|\s*```$", re.MULTILINE)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _strip_code_fence(text: str) -> str:
|
|
164
|
+
return _CODE_FENCE.sub("", text)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Extraction pipeline — orchestrates format detection and entity extraction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from apsimo.world_model.extraction.base import ExtractedEntity, FormatExtractor
|
|
9
|
+
from apsimo.world_model.extraction.detector import FormatDetector
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExtractionPipeline:
|
|
15
|
+
"""Orchestrate format detection and entity extraction.
|
|
16
|
+
|
|
17
|
+
Flow:
|
|
18
|
+
1. Detect format
|
|
19
|
+
2. Try structured extraction (fast, no LLM)
|
|
20
|
+
3. Fall back to text extraction + LLM
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
extractors: List[FormatExtractor],
|
|
26
|
+
llm_extract_fn: Optional[Callable] = None,
|
|
27
|
+
):
|
|
28
|
+
self._extractors: Dict[str, FormatExtractor] = {}
|
|
29
|
+
for ext in extractors:
|
|
30
|
+
for fmt in ext.supported_formats():
|
|
31
|
+
self._extractors[fmt] = ext
|
|
32
|
+
self._detector = FormatDetector()
|
|
33
|
+
self._llm_extract_fn = llm_extract_fn
|
|
34
|
+
|
|
35
|
+
async def extract(
|
|
36
|
+
self,
|
|
37
|
+
content: bytes,
|
|
38
|
+
filename: str = "",
|
|
39
|
+
mime_type: str = "",
|
|
40
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
41
|
+
) -> List[ExtractedEntity]:
|
|
42
|
+
"""Extract entities from content.
|
|
43
|
+
|
|
44
|
+
1. Detect format
|
|
45
|
+
2. Try structured extraction (fast, no LLM)
|
|
46
|
+
3. Fall back to text extraction + LLM
|
|
47
|
+
"""
|
|
48
|
+
metadata = metadata or {}
|
|
49
|
+
|
|
50
|
+
# Detect format
|
|
51
|
+
fmt = self._detector.detect(content, filename=filename, mime_type=mime_type)
|
|
52
|
+
logger.debug("Detected format: %s", fmt)
|
|
53
|
+
|
|
54
|
+
# Find extractor
|
|
55
|
+
extractor = self._extractors.get(fmt)
|
|
56
|
+
if extractor is None:
|
|
57
|
+
logger.warning("No extractor for format '%s' — skipping", fmt)
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
# 1. Try structured extraction first
|
|
61
|
+
entities = await extractor.extract_entities(content, metadata)
|
|
62
|
+
|
|
63
|
+
# 2. Fall back to text + LLM
|
|
64
|
+
if not entities:
|
|
65
|
+
text = await extractor.extract_text(content, metadata)
|
|
66
|
+
if text and self._llm_extract_fn:
|
|
67
|
+
try:
|
|
68
|
+
entities = await self._llm_extract_fn(text, metadata)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.warning("LLM extraction failed: %s", e)
|
|
71
|
+
|
|
72
|
+
logger.info("Extracted %d entities from %s format", len(entities), fmt)
|
|
73
|
+
return entities
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Mind Model bridge: provides world model context to the mind model pipeline."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..store import WorldModelStore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MindModelBridge:
|
|
10
|
+
"""Provides world model context to the mind model pipeline."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, store: "WorldModelStore") -> None:
|
|
13
|
+
self._store = store
|
|
14
|
+
|
|
15
|
+
async def get_context_for_person(
|
|
16
|
+
self,
|
|
17
|
+
person_id: str,
|
|
18
|
+
as_of: Optional[str] = None,
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""Fetch world model context relevant to a person's mental state.
|
|
21
|
+
|
|
22
|
+
Returns employer, active projects, upcoming events, and shared
|
|
23
|
+
concepts that may inform state estimation.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
person_id: The world model entity ID of the person.
|
|
27
|
+
as_of: Optional ISO8601 datetime for temporal queries.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict with 'employer', 'projects', 'events', 'concepts' keys.
|
|
31
|
+
"""
|
|
32
|
+
context: Dict[str, Any] = {
|
|
33
|
+
"employer": None,
|
|
34
|
+
"projects": [],
|
|
35
|
+
"events": [],
|
|
36
|
+
"concepts": [],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Current employer
|
|
40
|
+
employer_rels = await self._store.query_relationships(
|
|
41
|
+
source_id=person_id,
|
|
42
|
+
relationship_type="WM_WORKS_AT",
|
|
43
|
+
active_only=(as_of is None),
|
|
44
|
+
min_confidence=0.30,
|
|
45
|
+
limit=1,
|
|
46
|
+
)
|
|
47
|
+
if employer_rels:
|
|
48
|
+
employer = await self._store.get_entity(employer_rels[0].target_id)
|
|
49
|
+
context["employer"] = employer
|
|
50
|
+
|
|
51
|
+
# Active project memberships
|
|
52
|
+
project_rels = await self._store.query_relationships(
|
|
53
|
+
source_id=person_id,
|
|
54
|
+
relationship_type="WM_MEMBER_OF",
|
|
55
|
+
target_types=["project"],
|
|
56
|
+
active_only=(as_of is None),
|
|
57
|
+
min_confidence=0.30,
|
|
58
|
+
limit=10,
|
|
59
|
+
)
|
|
60
|
+
for rel in project_rels:
|
|
61
|
+
project = await self._store.get_entity(rel.target_id)
|
|
62
|
+
if project:
|
|
63
|
+
context["projects"].append(project)
|
|
64
|
+
|
|
65
|
+
# Upcoming events (attended)
|
|
66
|
+
event_rels = await self._store.query_relationships(
|
|
67
|
+
source_id=person_id,
|
|
68
|
+
relationship_type="WM_ATTENDED",
|
|
69
|
+
target_types=["event"],
|
|
70
|
+
min_confidence=0.30,
|
|
71
|
+
limit=10,
|
|
72
|
+
)
|
|
73
|
+
for rel in event_rels:
|
|
74
|
+
event = await self._store.get_entity(rel.target_id)
|
|
75
|
+
if event:
|
|
76
|
+
context["events"].append(event)
|
|
77
|
+
|
|
78
|
+
# Tagged concepts
|
|
79
|
+
concept_rels = await self._store.query_relationships(
|
|
80
|
+
source_id=person_id,
|
|
81
|
+
relationship_type="WM_TAGGED_WITH",
|
|
82
|
+
target_types=["concept"],
|
|
83
|
+
min_confidence=0.30,
|
|
84
|
+
limit=10,
|
|
85
|
+
)
|
|
86
|
+
for rel in concept_rels:
|
|
87
|
+
concept = await self._store.get_entity(rel.target_id)
|
|
88
|
+
if concept:
|
|
89
|
+
context["concepts"].append(concept)
|
|
90
|
+
|
|
91
|
+
return context
|
|
92
|
+
|
|
93
|
+
async def enrich_mind_signal(
|
|
94
|
+
self,
|
|
95
|
+
person_id: str,
|
|
96
|
+
signal_text: str,
|
|
97
|
+
) -> Optional[str]:
|
|
98
|
+
"""Cross-reference a text signal with world model entities.
|
|
99
|
+
|
|
100
|
+
Returns the entity ID if a matching EventEntity or ConceptEntity
|
|
101
|
+
is found, otherwise None.
|
|
102
|
+
"""
|
|
103
|
+
if not signal_text:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# Search events and concepts
|
|
107
|
+
candidates = await self._store.find_entities(
|
|
108
|
+
query=signal_text,
|
|
109
|
+
min_confidence=0.30,
|
|
110
|
+
limit=5,
|
|
111
|
+
)
|
|
112
|
+
for candidate in candidates:
|
|
113
|
+
if candidate.entity_type in ("event", "concept"):
|
|
114
|
+
return candidate.id
|
|
115
|
+
return None
|