ltcai 11.5.2 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +54 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""Honest context-quality and multimodal signals for a result set.
|
|
2
|
-
|
|
3
|
-
Plain functions over an already-computed match list — no store, no I/O — so
|
|
4
|
-
every mixin in the package (and the chat surfaces outside it) can build the
|
|
5
|
-
same signal the same way.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
# ruff: noqa: F403,F405
|
|
11
|
-
from .._kg_common import * # noqa: F403,F401
|
|
12
|
-
|
|
13
|
-
#: Node types that are a *thing you can look at or listen to*, not prose. A
|
|
14
|
-
#: match of one of these means the answer rests on more than text.
|
|
15
|
-
MULTIMODAL_NODE_TYPES = ("Image", "ImageText")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def multimodal_signal(matches: Iterable[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
19
|
-
"""``{"images": n, "types": [...]}`` when a result set includes pictures.
|
|
20
|
-
|
|
21
|
-
``None`` when it does not: the context-quality contract stays four keys
|
|
22
|
-
wide for the ordinary all-text case, and a caller that sees the key knows
|
|
23
|
-
it means something rather than having to compare a zero.
|
|
24
|
-
"""
|
|
25
|
-
images = 0
|
|
26
|
-
seen: List[str] = []
|
|
27
|
-
for match in matches:
|
|
28
|
-
node_type = str(match.get("type") or "")
|
|
29
|
-
if node_type in MULTIMODAL_NODE_TYPES:
|
|
30
|
-
images += 1
|
|
31
|
-
if node_type not in seen:
|
|
32
|
-
seen.append(node_type)
|
|
33
|
-
if not images:
|
|
34
|
-
return None
|
|
35
|
-
return {"images": images, "types": seen}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def context_quality_signal(
|
|
39
|
-
mode: str,
|
|
40
|
-
nodes: int,
|
|
41
|
-
*,
|
|
42
|
-
reason: Optional[str] = None,
|
|
43
|
-
vector: Optional[Dict[str, Any]] = None,
|
|
44
|
-
multimodal: Optional[Dict[str, Any]] = None,
|
|
45
|
-
) -> Dict[str, Any]:
|
|
46
|
-
"""Honest RAG context-quality signal (v9.8.0, additive contract).
|
|
47
|
-
|
|
48
|
-
Shape consumed by the chat metadata channel:
|
|
49
|
-
``{"mode": "hybrid"|"lexical_only"|"none", "nodes": int, "limited": bool,
|
|
50
|
-
"reason": str|None}``. ``nodes == 0`` always collapses ``mode`` to
|
|
51
|
-
``"none"``; ``limited`` is true whenever the context is thin (0–1 nodes)
|
|
52
|
-
or the vector side fell back to lexical-only retrieval. ``reason`` is a
|
|
53
|
-
short human-readable Korean phrase, only present when limited.
|
|
54
|
-
|
|
55
|
-
``vector`` (v11.1.0) carries the vector channel's own honesty block —
|
|
56
|
-
which backend scored, whether it was approximate, whether the candidate
|
|
57
|
-
scan was truncated. "hybrid, 6 nodes" describes two different answers
|
|
58
|
-
depending on those bits, and the caller that has to say "I did not find
|
|
59
|
-
it" deserves to know which one it got. The key is present **only when
|
|
60
|
-
there is a caveat to report**: an exact, complete vector scan is the
|
|
61
|
-
contract's baseline assumption, so annotating it would be noise, and the
|
|
62
|
-
four-key shape stays exactly what existing consumers pin.
|
|
63
|
-
|
|
64
|
-
``multimodal`` (v11.1.0) follows the same present-only-when-true rule and
|
|
65
|
-
says that part of this context is a picture. "6 nodes" reads differently
|
|
66
|
-
when two of them are screenshots whose text came out of OCR, and the
|
|
67
|
-
surface that has to explain the answer deserves to know.
|
|
68
|
-
"""
|
|
69
|
-
nodes = max(0, int(nodes or 0))
|
|
70
|
-
mode = str(mode or "none")
|
|
71
|
-
if nodes == 0:
|
|
72
|
-
mode = "none"
|
|
73
|
-
if mode not in ("hybrid", "lexical_only", "none"):
|
|
74
|
-
mode = "lexical_only"
|
|
75
|
-
limited = nodes <= 1 or mode != "hybrid"
|
|
76
|
-
if reason is None and limited:
|
|
77
|
-
if nodes == 0:
|
|
78
|
-
reason = "그래프에서 관련 지식을 찾지 못했습니다"
|
|
79
|
-
elif mode == "lexical_only":
|
|
80
|
-
reason = "벡터 검색을 사용할 수 없어 키워드 검색 결과만 사용했습니다"
|
|
81
|
-
else:
|
|
82
|
-
reason = "그래프 기반 컨텍스트가 제한적입니다"
|
|
83
|
-
if not limited:
|
|
84
|
-
reason = None
|
|
85
|
-
signal: Dict[str, Any] = {
|
|
86
|
-
"mode": mode,
|
|
87
|
-
"nodes": nodes,
|
|
88
|
-
"limited": limited,
|
|
89
|
-
"reason": reason,
|
|
90
|
-
}
|
|
91
|
-
if vector is not None:
|
|
92
|
-
signal["vector"] = dict(vector)
|
|
93
|
-
if multimodal is not None:
|
|
94
|
-
signal["multimodal"] = dict(multimodal)
|
|
95
|
-
return signal
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
# ruff: noqa: F403,F405
|
|
6
|
-
from ._kg_common import * # noqa: F403,F401
|
|
7
|
-
|
|
8
|
-
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
9
|
-
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
10
|
-
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
13
|
-
else:
|
|
14
|
-
_Core = object
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class KnowledgeGraphDocGenMixin(_Core):
|
|
19
|
-
"""Multi-hop retrieval specialised for document generation, split out
|
|
20
|
-
of retrieval. Composed into KnowledgeGraphStore alongside the other
|
|
21
|
-
retrieval mixins; shared instance means sibling helpers resolve via MRO.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def search_for_document_generation(
|
|
25
|
-
self,
|
|
26
|
-
query: str,
|
|
27
|
-
limit: int = 10,
|
|
28
|
-
*,
|
|
29
|
-
allowed_workspaces=None,
|
|
30
|
-
include_legacy_global: bool = False,
|
|
31
|
-
) -> List[Dict[str, Any]]:
|
|
32
|
-
"""Hybrid retrieval optimized for document generation.
|
|
33
|
-
|
|
34
|
-
Scoring: 0.5*text_relevance + 0.3*graph_relationship + 0.2*recency
|
|
35
|
-
Returns nodes with rich context for document generation prompts.
|
|
36
|
-
"""
|
|
37
|
-
query = str(query or "").strip()
|
|
38
|
-
if not query:
|
|
39
|
-
return []
|
|
40
|
-
limit = max(1, min(int(limit or 10), 50))
|
|
41
|
-
terms = _topic_candidates(query, limit=12)
|
|
42
|
-
now = datetime.now()
|
|
43
|
-
nt, et = self._read_tables()
|
|
44
|
-
|
|
45
|
-
with self._connect() as conn:
|
|
46
|
-
candidate_rows = []
|
|
47
|
-
seen_ids = set()
|
|
48
|
-
|
|
49
|
-
# `query` is non-empty here — the early return above took the blank case.
|
|
50
|
-
q = f"%{query}%"
|
|
51
|
-
rows = conn.execute(
|
|
52
|
-
f"""
|
|
53
|
-
SELECT id, type, title, summary, metadata_json, updated_at
|
|
54
|
-
FROM {nt}
|
|
55
|
-
WHERE (title LIKE ? OR summary LIKE ? OR metadata_json LIKE ?)
|
|
56
|
-
AND type IN ('Document', 'File', 'CodeFile', 'SlideDeck',
|
|
57
|
-
'Spreadsheet', 'Image', 'ImageText', 'Audio',
|
|
58
|
-
'Chat', 'Decision', 'Task', 'Concept',
|
|
59
|
-
'Feature', 'Page', 'Slide')
|
|
60
|
-
ORDER BY updated_at DESC, id ASC
|
|
61
|
-
LIMIT ?
|
|
62
|
-
""",
|
|
63
|
-
(q, q, q, limit * 5),
|
|
64
|
-
).fetchall()
|
|
65
|
-
for row in rows:
|
|
66
|
-
if row["id"] not in seen_ids:
|
|
67
|
-
seen_ids.add(row["id"])
|
|
68
|
-
candidate_rows.append(row)
|
|
69
|
-
|
|
70
|
-
for term in terms:
|
|
71
|
-
t = f"%{term}%"
|
|
72
|
-
rows = conn.execute(
|
|
73
|
-
f"""
|
|
74
|
-
SELECT id, type, title, summary, metadata_json, updated_at
|
|
75
|
-
FROM {nt}
|
|
76
|
-
WHERE (title LIKE ? OR summary LIKE ? OR metadata_json LIKE ?)
|
|
77
|
-
AND type IN ('Document', 'File', 'CodeFile', 'SlideDeck',
|
|
78
|
-
'Spreadsheet', 'Image', 'ImageText', 'Audio',
|
|
79
|
-
'Chat', 'Decision', 'Task', 'Concept',
|
|
80
|
-
'Feature', 'Page', 'Slide')
|
|
81
|
-
ORDER BY updated_at DESC, id ASC
|
|
82
|
-
LIMIT ?
|
|
83
|
-
""",
|
|
84
|
-
(t, t, t, limit * 3),
|
|
85
|
-
).fetchall()
|
|
86
|
-
for row in rows:
|
|
87
|
-
if row["id"] not in seen_ids:
|
|
88
|
-
seen_ids.add(row["id"])
|
|
89
|
-
candidate_rows.append(row)
|
|
90
|
-
|
|
91
|
-
scored_results = []
|
|
92
|
-
for row in candidate_rows:
|
|
93
|
-
haystack = (
|
|
94
|
-
f"{row['title']} {row['summary']} {row['metadata_json']}".lower()
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
text_hits = sum(1 for term in terms if term.lower() in haystack)
|
|
98
|
-
text_score = min(1.0, text_hits / max(len(terms), 1))
|
|
99
|
-
|
|
100
|
-
edge_count = conn.execute(
|
|
101
|
-
f"SELECT COUNT(*) AS c FROM {et} WHERE from_node=? OR to_node=?",
|
|
102
|
-
(row["id"], row["id"]),
|
|
103
|
-
).fetchone()["c"]
|
|
104
|
-
graph_score = min(1.0, math.log1p(edge_count) / 4.0)
|
|
105
|
-
|
|
106
|
-
recency = _recency_score(
|
|
107
|
-
row["updated_at"], now=now, half_life_days=14.0
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
doc_type_boost = (
|
|
111
|
-
1.2
|
|
112
|
-
if row["type"]
|
|
113
|
-
in (
|
|
114
|
-
"Document",
|
|
115
|
-
"File",
|
|
116
|
-
"SlideDeck",
|
|
117
|
-
"Decision",
|
|
118
|
-
)
|
|
119
|
-
else 1.0
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
hybrid_score = (
|
|
123
|
-
0.5 * text_score + 0.3 * graph_score + 0.2 * recency
|
|
124
|
-
) * doc_type_boost
|
|
125
|
-
|
|
126
|
-
meta = _safe_loads(row["metadata_json"])
|
|
127
|
-
neighbor_concepts = []
|
|
128
|
-
neighbor_rows = conn.execute(
|
|
129
|
-
f"""
|
|
130
|
-
SELECT n.id, n.title, n.type FROM {et} e
|
|
131
|
-
JOIN {nt} n ON n.id = CASE WHEN e.from_node = ? THEN e.to_node ELSE e.from_node END
|
|
132
|
-
WHERE (e.from_node = ? OR e.to_node = ?)
|
|
133
|
-
AND n.type IN ('Concept', 'Feature', 'Decision', 'Task')
|
|
134
|
-
LIMIT 8
|
|
135
|
-
""",
|
|
136
|
-
(row["id"], row["id"], row["id"]),
|
|
137
|
-
).fetchall()
|
|
138
|
-
for nr in neighbor_rows:
|
|
139
|
-
neighbor_concepts.append({"id": nr["id"], "title": nr["title"], "type": nr["type"]})
|
|
140
|
-
|
|
141
|
-
scored_results.append(
|
|
142
|
-
{
|
|
143
|
-
"id": row["id"],
|
|
144
|
-
"type": row["type"],
|
|
145
|
-
"title": row["title"],
|
|
146
|
-
"summary": row["summary"],
|
|
147
|
-
"metadata": meta,
|
|
148
|
-
"updated_at": row["updated_at"],
|
|
149
|
-
"hybrid_score": round(hybrid_score, 4),
|
|
150
|
-
"scores": {
|
|
151
|
-
"text": round(text_score, 4),
|
|
152
|
-
"graph": round(graph_score, 4),
|
|
153
|
-
"recency": round(recency, 4),
|
|
154
|
-
},
|
|
155
|
-
"related_concepts": neighbor_concepts,
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
if allowed_workspaces is not None:
|
|
160
|
-
scored_results = self.filter_scoped_nodes(
|
|
161
|
-
scored_results,
|
|
162
|
-
allowed_workspaces,
|
|
163
|
-
include_legacy_global=include_legacy_global,
|
|
164
|
-
)
|
|
165
|
-
for item in scored_results:
|
|
166
|
-
item["related_concepts"] = self.filter_scoped_nodes(
|
|
167
|
-
item.get("related_concepts", []),
|
|
168
|
-
allowed_workspaces,
|
|
169
|
-
include_legacy_global=include_legacy_global,
|
|
170
|
-
)
|
|
171
|
-
scored_results.sort(key=lambda x: x["hybrid_score"], reverse=True)
|
|
172
|
-
return scored_results[:limit]
|
|
173
|
-
|
|
174
|
-
def multi_hop_context(
|
|
175
|
-
self,
|
|
176
|
-
node_ids: List[str],
|
|
177
|
-
max_hops: int = 2,
|
|
178
|
-
*,
|
|
179
|
-
allowed_workspaces=None,
|
|
180
|
-
include_legacy_global: bool = False,
|
|
181
|
-
) -> Dict[str, Any]:
|
|
182
|
-
"""Multi-hop graph traversal from seed nodes for richer context."""
|
|
183
|
-
visited_nodes = set()
|
|
184
|
-
visited_edges = set()
|
|
185
|
-
all_nodes = []
|
|
186
|
-
all_edges = []
|
|
187
|
-
frontier = set(node_ids)
|
|
188
|
-
nt, et = self._read_tables()
|
|
189
|
-
|
|
190
|
-
with self._connect() as conn:
|
|
191
|
-
for hop in range(max_hops):
|
|
192
|
-
if not frontier:
|
|
193
|
-
break
|
|
194
|
-
next_frontier = set()
|
|
195
|
-
for nid in frontier:
|
|
196
|
-
if nid in visited_nodes:
|
|
197
|
-
continue
|
|
198
|
-
visited_nodes.add(nid)
|
|
199
|
-
row = conn.execute(
|
|
200
|
-
f"SELECT id, type, title, summary, metadata_json, updated_at FROM {nt} WHERE id=?",
|
|
201
|
-
(nid,),
|
|
202
|
-
).fetchone()
|
|
203
|
-
if row:
|
|
204
|
-
all_nodes.append(
|
|
205
|
-
{
|
|
206
|
-
"id": row["id"],
|
|
207
|
-
"type": row["type"],
|
|
208
|
-
"title": row["title"],
|
|
209
|
-
"summary": row["summary"],
|
|
210
|
-
"metadata": _safe_loads(row["metadata_json"]),
|
|
211
|
-
"hop": hop,
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
edge_rows = conn.execute(
|
|
215
|
-
f"""
|
|
216
|
-
SELECT id, from_node, to_node, type, weight
|
|
217
|
-
FROM {et} WHERE from_node=? OR to_node=?
|
|
218
|
-
ORDER BY id ASC
|
|
219
|
-
""",
|
|
220
|
-
(nid, nid),
|
|
221
|
-
).fetchall()
|
|
222
|
-
for er in edge_rows:
|
|
223
|
-
if er["id"] not in visited_edges:
|
|
224
|
-
visited_edges.add(er["id"])
|
|
225
|
-
all_edges.append(
|
|
226
|
-
{
|
|
227
|
-
"from": er["from_node"],
|
|
228
|
-
"to": er["to_node"],
|
|
229
|
-
"type": er["type"],
|
|
230
|
-
"weight": er["weight"],
|
|
231
|
-
}
|
|
232
|
-
)
|
|
233
|
-
other = (
|
|
234
|
-
er["to_node"]
|
|
235
|
-
if er["from_node"] == nid
|
|
236
|
-
else er["from_node"]
|
|
237
|
-
)
|
|
238
|
-
if other not in visited_nodes:
|
|
239
|
-
next_frontier.add(other)
|
|
240
|
-
frontier = next_frontier
|
|
241
|
-
|
|
242
|
-
if allowed_workspaces is not None:
|
|
243
|
-
all_nodes = self.filter_scoped_nodes(
|
|
244
|
-
all_nodes,
|
|
245
|
-
allowed_workspaces,
|
|
246
|
-
include_legacy_global=include_legacy_global,
|
|
247
|
-
)
|
|
248
|
-
visible_ids = {node.get("id") for node in all_nodes}
|
|
249
|
-
all_edges = [
|
|
250
|
-
edge for edge in all_edges
|
|
251
|
-
if edge.get("from") in visible_ids and edge.get("to") in visible_ids
|
|
252
|
-
]
|
|
253
|
-
return {"nodes": all_nodes, "edges": all_edges}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"""Single retrieval policy consulted by both hybrid fusion layers.
|
|
2
|
-
|
|
3
|
-
Review 2026-07-25 (UX/harness/loop/KG-RAG), Wave 0.2 — "RetrievalPolicy
|
|
4
|
-
단일화" + "query rewrite" + "recency age decay". Before this module the two
|
|
5
|
-
hybrid entry points (``KnowledgeGraphStore.hybrid_search`` two-channel
|
|
6
|
-
alpha fusion and ``SearchService.hybrid_search`` three-channel weight
|
|
7
|
-
fusion) each resolved :func:`lattice_brain.graph.fusion.fusion_profile`
|
|
8
|
-
independently and had no shared place to hang retrieval-wide behavior.
|
|
9
|
-
This module is that single place:
|
|
10
|
-
|
|
11
|
-
* :func:`rewrite_query` — deterministic, rule-based ko/en normalization
|
|
12
|
-
(NO LLM). Strips a conservative fixed list of leading/trailing politeness
|
|
13
|
-
fillers ("~좀 알려줘", "궁금해", "뭐였지", "what is", "please", …) and
|
|
14
|
-
collapses repeated whitespace. Code-class queries pass through untouched
|
|
15
|
-
(except whitespace collapse) because exact identifiers/filenames ARE the
|
|
16
|
-
retrieval signal. ``LATTICEAI_QUERY_REWRITE=0`` disables rewriting.
|
|
17
|
-
* :func:`resolve_policy` — composes the rewrite with
|
|
18
|
-
:func:`~lattice_brain.graph.fusion.fusion_profile` (the weight table is
|
|
19
|
-
NOT duplicated here) and adds ``recency_half_life_days``, which is
|
|
20
|
-
non-``None`` ONLY for the ``recency`` query class — the honest signal
|
|
21
|
-
that age decay applies exactly where the fusion layers wire it.
|
|
22
|
-
|
|
23
|
-
Both functions are pure, deterministic, and never raise; the ``fact``
|
|
24
|
-
query class resolves to the historical default weights, so fact-class
|
|
25
|
-
behavior stays byte-compatible. Scores are never combined with ``or``
|
|
26
|
-
(score ``0.0`` is falsy but valid).
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
|
-
import os
|
|
32
|
-
import re
|
|
33
|
-
from typing import Any, Dict, List, Mapping, Optional
|
|
34
|
-
|
|
35
|
-
from .fusion import classify_query, fusion_profile
|
|
36
|
-
|
|
37
|
-
QUERY_REWRITE_ENV = "LATTICEAI_QUERY_REWRITE"
|
|
38
|
-
|
|
39
|
-
# Half-life (days) for the recency-class age decay. Matches the default of
|
|
40
|
-
# lattice_brain.graph._kg_fsutil._recency_score, which the fusion layers use
|
|
41
|
-
# to compute the actual multiplier — this module only declares the policy.
|
|
42
|
-
RECENCY_HALF_LIFE_DAYS = 14.0
|
|
43
|
-
|
|
44
|
-
# A filler strip is applied only when the remaining query keeps at least this
|
|
45
|
-
# many characters — never rewrite a query down to (near-)nothing.
|
|
46
|
-
_MIN_REMAINDER_CHARS = 4
|
|
47
|
-
|
|
48
|
-
_WS_RE = re.compile(r"\s+")
|
|
49
|
-
|
|
50
|
-
# Conservative trailing Korean politeness/filler phrases ("… 좀 알려줘",
|
|
51
|
-
# "… 궁금해", "… 뭐였지"). Anchored at end-of-string, must start the query or
|
|
52
|
-
# follow whitespace so word interiors are never clipped.
|
|
53
|
-
_KO_FILLER_TAIL_RE = re.compile(
|
|
54
|
-
r"(?:^|(?<=\s))(?:좀\s+)?"
|
|
55
|
-
r"(?:알려\s*줘요?|알려\s*주세요|알려\s*줄래요?"
|
|
56
|
-
r"|말해\s*줘요?|말해\s*주세요"
|
|
57
|
-
r"|설명해\s*줘요?|설명해\s*주세요"
|
|
58
|
-
r"|궁금해요?|궁금합니다"
|
|
59
|
-
r"|뭐였지|뭐였더라|뭐더라|뭐지|뭐야"
|
|
60
|
-
r")\s*[?!.…~]*\s*$"
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# Conservative leading English filler ("what is …", "tell me about …",
|
|
64
|
-
# optionally prefixed with "please").
|
|
65
|
-
_EN_FILLER_LEAD_RE = re.compile(
|
|
66
|
-
r"^(?:please\s+)?(?:tell\s+me\s+about|what\s+is|what\s+was|what\s+are|what's)\s+",
|
|
67
|
-
re.IGNORECASE,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Conservative trailing English politeness ("…, please?").
|
|
71
|
-
_EN_FILLER_TAIL_RE = re.compile(r"[,\s]*\bplease\b\s*[?!.]*\s*$", re.IGNORECASE)
|
|
72
|
-
|
|
73
|
-
_FILLER_RULES = (
|
|
74
|
-
("strip_filler_ko", _KO_FILLER_TAIL_RE),
|
|
75
|
-
("strip_filler_en_leading", _EN_FILLER_LEAD_RE),
|
|
76
|
-
("strip_filler_en_trailing", _EN_FILLER_TAIL_RE),
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _rewrite_enabled() -> bool:
|
|
81
|
-
raw = os.getenv(QUERY_REWRITE_ENV, "").strip().lower()
|
|
82
|
-
return raw not in {"0", "false", "no", "off"}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def rewrite_query(query: Any) -> Dict[str, Any]:
|
|
86
|
-
"""Deterministic rule-based ko/en query normalization (never raises).
|
|
87
|
-
|
|
88
|
-
Returns ``{"original": str, "rewritten": str, "rules": [rule names]}``.
|
|
89
|
-
Only three conservative rules exist: whitespace collapse, trailing
|
|
90
|
-
Korean filler strip, and leading/trailing English filler strip — each
|
|
91
|
-
filler strip applies only when the remainder keeps >= 4 characters.
|
|
92
|
-
Code-class queries (exact identifiers/filenames are the signal) are
|
|
93
|
-
returned unchanged apart from whitespace collapse. The
|
|
94
|
-
``LATTICEAI_QUERY_REWRITE=0`` kill-switch disables rewriting entirely
|
|
95
|
-
(``rewritten == original``). Empty input yields empty output.
|
|
96
|
-
"""
|
|
97
|
-
try:
|
|
98
|
-
text = str(query or "").strip()
|
|
99
|
-
except Exception: # noqa: BLE001 — rewrite must never raise
|
|
100
|
-
return {"original": "", "rewritten": "", "rules": []}
|
|
101
|
-
if not text:
|
|
102
|
-
return {"original": "", "rewritten": "", "rules": []}
|
|
103
|
-
original = text
|
|
104
|
-
if not _rewrite_enabled():
|
|
105
|
-
return {"original": original, "rewritten": original, "rules": []}
|
|
106
|
-
|
|
107
|
-
rules: List[str] = []
|
|
108
|
-
rewritten = _WS_RE.sub(" ", text).strip()
|
|
109
|
-
if rewritten != original:
|
|
110
|
-
rules.append("collapse_whitespace")
|
|
111
|
-
|
|
112
|
-
if classify_query(original) == "code":
|
|
113
|
-
# Exact identifiers/filenames are the retrieval signal — never touch
|
|
114
|
-
# them (whitespace collapse is the only permitted normalization).
|
|
115
|
-
return {"original": original, "rewritten": rewritten, "rules": rules}
|
|
116
|
-
|
|
117
|
-
for name, pattern in _FILLER_RULES:
|
|
118
|
-
candidate = pattern.sub("", rewritten).strip()
|
|
119
|
-
if candidate != rewritten and len(candidate) >= _MIN_REMAINDER_CHARS:
|
|
120
|
-
rewritten = candidate
|
|
121
|
-
rules.append(name)
|
|
122
|
-
return {"original": original, "rewritten": rewritten, "rules": rules}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def resolve_policy(
|
|
126
|
-
query: Any,
|
|
127
|
-
*,
|
|
128
|
-
overrides: Optional[Mapping[str, Mapping[str, float]]] = None,
|
|
129
|
-
) -> Dict[str, Any]:
|
|
130
|
-
"""Resolve the full retrieval policy for ``query`` (never raises).
|
|
131
|
-
|
|
132
|
-
Composes :func:`rewrite_query` with
|
|
133
|
-
:func:`~lattice_brain.graph.fusion.fusion_profile` (classification runs
|
|
134
|
-
on the ORIGINAL query, so ``query_class`` / ``weights`` / ``alpha`` are
|
|
135
|
-
byte-identical to calling ``fusion_profile`` directly, as both hybrid
|
|
136
|
-
layers did before this module). Returns::
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
"query_class": "fact" | "code" | "person" | "recency",
|
|
140
|
-
"weights": {"keyword", "vector", "graph"}, # service fusion
|
|
141
|
-
"alpha": float, # graph-layer fusion
|
|
142
|
-
"fusion_strategy": "alpha" | "rrf", # how they combine
|
|
143
|
-
"original_query": str,
|
|
144
|
-
"search_query": str, # the rewritten form to search with
|
|
145
|
-
"rewrite_rules": [str],
|
|
146
|
-
"recency_half_life_days": float | None, # 14.0 only for recency
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
``fusion_strategy`` is ``"alpha"`` for every class unless
|
|
150
|
-
``LATTICEAI_FUSION_STRATEGY`` says otherwise, so the default policy is
|
|
151
|
-
byte-identical to the pre-11.1.0 one.
|
|
152
|
-
|
|
153
|
-
``recency_half_life_days`` is non-``None`` only for the ``recency``
|
|
154
|
-
class — the honest contract that age decay applies exactly where the
|
|
155
|
-
fusion layers wire it, and nowhere else.
|
|
156
|
-
"""
|
|
157
|
-
rewrite = rewrite_query(query)
|
|
158
|
-
profile = fusion_profile(rewrite["original"], overrides=overrides)
|
|
159
|
-
query_class = profile["query_class"]
|
|
160
|
-
search_query = rewrite["rewritten"] if rewrite["rewritten"] else rewrite["original"]
|
|
161
|
-
return {
|
|
162
|
-
"query_class": query_class,
|
|
163
|
-
"weights": dict(profile["weights"]),
|
|
164
|
-
"alpha": float(profile["alpha"]),
|
|
165
|
-
"fusion_strategy": str(profile["strategy"]),
|
|
166
|
-
"original_query": rewrite["original"],
|
|
167
|
-
"search_query": search_query,
|
|
168
|
-
"rewrite_rules": list(rewrite["rules"]),
|
|
169
|
-
"recency_half_life_days": (
|
|
170
|
-
RECENCY_HALF_LIFE_DAYS if query_class == "recency" else None
|
|
171
|
-
),
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
__all__ = [
|
|
176
|
-
"QUERY_REWRITE_ENV",
|
|
177
|
-
"RECENCY_HALF_LIFE_DAYS",
|
|
178
|
-
"resolve_policy",
|
|
179
|
-
"rewrite_query",
|
|
180
|
-
]
|