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,394 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Context Builder — Knowledge Graph 기반 문서 생성용 컨텍스트 조합 모듈.
|
|
3
|
-
|
|
4
|
-
retrieve_context_for_generation() 파이프라인:
|
|
5
|
-
Step 1: Query → Hybrid Search (text + graph + recency)
|
|
6
|
-
Step 2: Seed nodes → Multi-hop traversal (Document → Project → Concept)
|
|
7
|
-
Step 3: Top-K 결과를 구조화된 Markdown Context로 변환
|
|
8
|
-
|
|
9
|
-
**Shared context contract (v9.9.6).** Chat assembles context through
|
|
10
|
-
:class:`lattice_brain.context.ContextAssembler` — budgeted, provenance-carrying,
|
|
11
|
-
with an honest ``context_quality`` signal. Document generation used to build
|
|
12
|
-
its own markdown with no budget and no quality signal, so the same Brain
|
|
13
|
-
answered a chat question and wrote a document under different guarantees.
|
|
14
|
-
|
|
15
|
-
The rendering stays different on purpose (a document prompt wants structured
|
|
16
|
-
sections, a chat prompt wants terse lines), but the *contract* is now one:
|
|
17
|
-
|
|
18
|
-
* the same ``approx_tokens`` accounting and an explicit budget,
|
|
19
|
-
* the same ``context_quality_signal`` shape chat reports,
|
|
20
|
-
* a ``trace`` in the assembler's shape (budget / used / sections).
|
|
21
|
-
|
|
22
|
-
Review 2026-07-27 P1 #5: "같은 Brain이면 같은 품질 계약".
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
import re
|
|
26
|
-
from typing import Any, Dict, List
|
|
27
|
-
|
|
28
|
-
from lattice_brain.context import approx_tokens
|
|
29
|
-
from lattice_brain.graph.retrieval import context_quality_signal, multimodal_signal
|
|
30
|
-
from lattice_brain.self_model import DEFAULT_SUMMARY_TOKENS, summary_for_prompt
|
|
31
|
-
|
|
32
|
-
_CLEAN_RE = re.compile(r"\s+")
|
|
33
|
-
|
|
34
|
-
# Approximate-token ceiling for the document-generation context block. Mirrors
|
|
35
|
-
# the chat assembler's default budget so neither path can silently outgrow the
|
|
36
|
-
# other on the same Brain.
|
|
37
|
-
DEFAULT_DOCUMENT_CONTEXT_BUDGET = 2000
|
|
38
|
-
|
|
39
|
-
# ── Self-Model injection (v11.1.0) ──────────────────────────────────────────
|
|
40
|
-
# What the Brain knows about its owner rides along with the knowledge, so a
|
|
41
|
-
# generated document sounds like the person who asked for it. Three rules keep
|
|
42
|
-
# it from becoming another thing that can go wrong:
|
|
43
|
-
# 1. an empty Self-Model injects *nothing* — no header, no blank section;
|
|
44
|
-
# 2. the block never takes more than half the context budget, so the profile
|
|
45
|
-
# can never crowd out the knowledge the request is actually about;
|
|
46
|
-
# 3. the returned contract is unchanged — same keys, same
|
|
47
|
-
# ``context_quality`` shape, one extra trace section only when a block
|
|
48
|
-
# was really injected.
|
|
49
|
-
SELF_MODEL_SECTION_TITLE = "사용자 프로필"
|
|
50
|
-
SELF_MODEL_TRACE_SOURCE = "self_model"
|
|
51
|
-
SELF_MODEL_SECTION_HEADER = f"### 🙋 {SELF_MODEL_SECTION_TITLE}\n\n"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _clean(text: str, max_len: int = 700) -> str:
|
|
55
|
-
return _CLEAN_RE.sub(" ", str(text or "")).strip()[:max_len]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _empty_result(query: str, reason: str) -> Dict[str, Any]:
|
|
59
|
-
quality = context_quality_signal("none", 0, reason=reason)
|
|
60
|
-
return {
|
|
61
|
-
"query": query,
|
|
62
|
-
"context_markdown": "",
|
|
63
|
-
"sources": [],
|
|
64
|
-
"stats": {"method": "none", "matches": 0},
|
|
65
|
-
"context_quality": quality,
|
|
66
|
-
"trace": {
|
|
67
|
-
"budget_approx_tokens": DEFAULT_DOCUMENT_CONTEXT_BUDGET,
|
|
68
|
-
"used_approx_tokens": 0,
|
|
69
|
-
"sections": [],
|
|
70
|
-
},
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _fit_to_budget(context_md: str, budget: int) -> tuple:
|
|
75
|
-
"""Trim rendered markdown to ``budget`` approximate tokens.
|
|
76
|
-
|
|
77
|
-
Cuts at a section boundary when possible so the prompt never ends inside
|
|
78
|
-
half an entry, and reports the trim so the trace stays honest.
|
|
79
|
-
"""
|
|
80
|
-
if budget <= 0 or approx_tokens(context_md) <= budget:
|
|
81
|
-
return context_md, False
|
|
82
|
-
limit = budget * 4
|
|
83
|
-
head = context_md[:limit]
|
|
84
|
-
boundary = head.rfind("\n### ")
|
|
85
|
-
if boundary > limit // 3:
|
|
86
|
-
head = head[:boundary]
|
|
87
|
-
return head.rstrip(), True
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _self_model_budget(budget: int, limit_tokens: int) -> int:
|
|
91
|
-
"""Tokens the profile block may use — never more than half the budget."""
|
|
92
|
-
if budget <= 0:
|
|
93
|
-
return limit_tokens
|
|
94
|
-
return min(limit_tokens, budget // 2)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _self_model_block(
|
|
98
|
-
kg_store,
|
|
99
|
-
*,
|
|
100
|
-
enabled: bool,
|
|
101
|
-
budget: int,
|
|
102
|
-
limit_tokens: int,
|
|
103
|
-
allowed_workspaces,
|
|
104
|
-
) -> str:
|
|
105
|
-
"""Rendered profile section, or ``""`` when there is nothing to inject.
|
|
106
|
-
|
|
107
|
-
The section header is charged to the same allowance as the summary, so the
|
|
108
|
-
*rendered block* — not just its text — honours the ceiling.
|
|
109
|
-
"""
|
|
110
|
-
allowance = _self_model_budget(budget, limit_tokens) - approx_tokens(
|
|
111
|
-
SELF_MODEL_SECTION_HEADER
|
|
112
|
-
)
|
|
113
|
-
if not enabled or allowance <= 0:
|
|
114
|
-
return ""
|
|
115
|
-
summary = summary_for_prompt(
|
|
116
|
-
kg_store, limit_tokens=allowance, allowed_workspaces=allowed_workspaces
|
|
117
|
-
)
|
|
118
|
-
if not summary:
|
|
119
|
-
return ""
|
|
120
|
-
return f"{SELF_MODEL_SECTION_HEADER}{summary}"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _with_self_model(block: str, context_md: str) -> str:
|
|
124
|
-
"""Prepend the profile block to rendered knowledge (either may be empty)."""
|
|
125
|
-
return "\n\n".join(part for part in (block, context_md) if part)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _self_model_trace(block: str) -> List[Dict[str, Any]]:
|
|
129
|
-
if not block:
|
|
130
|
-
return []
|
|
131
|
-
return [{
|
|
132
|
-
"name": SELF_MODEL_SECTION_TITLE,
|
|
133
|
-
"source": SELF_MODEL_TRACE_SOURCE,
|
|
134
|
-
"approx_tokens": approx_tokens(block),
|
|
135
|
-
"provenance": [],
|
|
136
|
-
}]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def retrieve_context_for_generation(
|
|
140
|
-
kg_store,
|
|
141
|
-
query: str,
|
|
142
|
-
*,
|
|
143
|
-
max_results: int = 10,
|
|
144
|
-
max_hops: int = 2,
|
|
145
|
-
allowed_workspaces=None,
|
|
146
|
-
include_legacy_global: bool = False,
|
|
147
|
-
budget: int = DEFAULT_DOCUMENT_CONTEXT_BUDGET,
|
|
148
|
-
include_self_model: bool = True,
|
|
149
|
-
self_model_tokens: int = DEFAULT_SUMMARY_TOKENS,
|
|
150
|
-
) -> Dict[str, Any]:
|
|
151
|
-
"""Knowledge Graph에서 문서 생성에 필요한 컨텍스트를 검색·조합한다.
|
|
152
|
-
|
|
153
|
-
Returns:
|
|
154
|
-
{
|
|
155
|
-
"query": str,
|
|
156
|
-
"context_markdown": str, # LLM 프롬프트에 직접 주입할 Markdown
|
|
157
|
-
"sources": [...], # 참조된 소스 목록
|
|
158
|
-
"stats": {...}, # 검색 통계
|
|
159
|
-
"context_quality": {...}, # chat과 동일한 정직 신호 (v9.9.6)
|
|
160
|
-
"trace": {...}, # assembler와 동일한 예산/섹션 추적
|
|
161
|
-
}
|
|
162
|
-
"""
|
|
163
|
-
query = str(query or "").strip()
|
|
164
|
-
if not query or not kg_store:
|
|
165
|
-
return _empty_result(query, "문서 생성 컨텍스트를 조회할 수 없습니다")
|
|
166
|
-
|
|
167
|
-
scope_kwargs = (
|
|
168
|
-
{
|
|
169
|
-
"allowed_workspaces": allowed_workspaces,
|
|
170
|
-
"include_legacy_global": include_legacy_global,
|
|
171
|
-
}
|
|
172
|
-
if allowed_workspaces is not None
|
|
173
|
-
else {}
|
|
174
|
-
)
|
|
175
|
-
self_model_md = _self_model_block(
|
|
176
|
-
kg_store,
|
|
177
|
-
enabled=include_self_model,
|
|
178
|
-
budget=budget,
|
|
179
|
-
limit_tokens=self_model_tokens,
|
|
180
|
-
allowed_workspaces=allowed_workspaces,
|
|
181
|
-
)
|
|
182
|
-
# The profile's tokens come out of the same budget, so injecting it can
|
|
183
|
-
# never push the assembled context over the ceiling the caller asked for.
|
|
184
|
-
# The blank line that joins the two blocks is counted too — otherwise the
|
|
185
|
-
# assembled total lands one token over a tight budget.
|
|
186
|
-
knowledge_budget = budget
|
|
187
|
-
if self_model_md:
|
|
188
|
-
knowledge_budget -= approx_tokens(f"{self_model_md}\n\n")
|
|
189
|
-
|
|
190
|
-
results = kg_store.search_for_document_generation(query, limit=max_results, **scope_kwargs)
|
|
191
|
-
if not results:
|
|
192
|
-
fallback_ctx = kg_store.context_for_query(
|
|
193
|
-
query,
|
|
194
|
-
limit=max_results,
|
|
195
|
-
**scope_kwargs,
|
|
196
|
-
)
|
|
197
|
-
fallback_ctx, trimmed = _fit_to_budget(fallback_ctx or "", knowledge_budget)
|
|
198
|
-
# Lexical fallback: the hybrid document search found nothing, so the
|
|
199
|
-
# signal says lexical_only — exactly what chat reports in the same
|
|
200
|
-
# situation, never a quiet downgrade.
|
|
201
|
-
quality = context_quality_signal(
|
|
202
|
-
"lexical_only" if fallback_ctx else "none",
|
|
203
|
-
1 if fallback_ctx else 0,
|
|
204
|
-
)
|
|
205
|
-
assembled = _with_self_model(self_model_md, fallback_ctx)
|
|
206
|
-
return {
|
|
207
|
-
"query": query,
|
|
208
|
-
"context_markdown": assembled,
|
|
209
|
-
"sources": [],
|
|
210
|
-
"stats": {"method": "fallback", "matches": 0, "budget_trimmed": trimmed},
|
|
211
|
-
"context_quality": quality,
|
|
212
|
-
"trace": {
|
|
213
|
-
"budget_approx_tokens": budget,
|
|
214
|
-
"used_approx_tokens": approx_tokens(assembled),
|
|
215
|
-
"sections": _self_model_trace(self_model_md) + [{
|
|
216
|
-
"name": "Knowledge (fallback)",
|
|
217
|
-
"source": "knowledge",
|
|
218
|
-
"approx_tokens": approx_tokens(fallback_ctx),
|
|
219
|
-
"provenance": [],
|
|
220
|
-
}],
|
|
221
|
-
},
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
seed_ids = [r["id"] for r in results]
|
|
225
|
-
hop_data = kg_store.multi_hop_context(
|
|
226
|
-
seed_ids,
|
|
227
|
-
max_hops=max_hops,
|
|
228
|
-
**scope_kwargs,
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
extra_nodes_by_id = {}
|
|
232
|
-
for node in hop_data.get("nodes", []):
|
|
233
|
-
if node["id"] not in {r["id"] for r in results}:
|
|
234
|
-
extra_nodes_by_id[node["id"]] = node
|
|
235
|
-
|
|
236
|
-
sections = _build_context_sections(results, extra_nodes_by_id, hop_data.get("edges", []))
|
|
237
|
-
context_md = _render_markdown(query, sections)
|
|
238
|
-
sources = _extract_sources(results)
|
|
239
|
-
context_md, trimmed = _fit_to_budget(context_md, knowledge_budget)
|
|
240
|
-
assembled = _with_self_model(self_model_md, context_md)
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
"query": query,
|
|
244
|
-
"context_markdown": assembled,
|
|
245
|
-
"sources": sources,
|
|
246
|
-
"stats": {
|
|
247
|
-
"method": "hybrid",
|
|
248
|
-
"primary_matches": len(results),
|
|
249
|
-
"graph_nodes": len(hop_data.get("nodes", [])),
|
|
250
|
-
"graph_edges": len(hop_data.get("edges", [])),
|
|
251
|
-
"budget_trimmed": trimmed,
|
|
252
|
-
},
|
|
253
|
-
# Same honest signal chat reports for the same Brain — including the
|
|
254
|
-
# multimodal key when the document's context really rests on pictures.
|
|
255
|
-
"context_quality": context_quality_signal(
|
|
256
|
-
"hybrid", len(results), multimodal=multimodal_signal(results)
|
|
257
|
-
),
|
|
258
|
-
"trace": {
|
|
259
|
-
"budget_approx_tokens": budget,
|
|
260
|
-
"used_approx_tokens": approx_tokens(assembled),
|
|
261
|
-
"sections": _self_model_trace(self_model_md) + [
|
|
262
|
-
{
|
|
263
|
-
"name": section["title"],
|
|
264
|
-
"source": "knowledge",
|
|
265
|
-
"approx_tokens": approx_tokens(
|
|
266
|
-
" ".join(str(item.get("summary") or "") for item in section["items"])
|
|
267
|
-
),
|
|
268
|
-
"provenance": [
|
|
269
|
-
{"id": item.get("id"), "type": item.get("type")}
|
|
270
|
-
for item in section["items"][:8]
|
|
271
|
-
],
|
|
272
|
-
}
|
|
273
|
-
for section in sections
|
|
274
|
-
],
|
|
275
|
-
},
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def _build_context_sections(
|
|
280
|
-
primary_results: List[Dict[str, Any]],
|
|
281
|
-
extra_nodes: Dict[str, Dict[str, Any]],
|
|
282
|
-
edges: List[Dict[str, Any]],
|
|
283
|
-
) -> List[Dict[str, Any]]:
|
|
284
|
-
sections = []
|
|
285
|
-
|
|
286
|
-
docs = [r for r in primary_results if r["type"] in (
|
|
287
|
-
"Document", "File", "SlideDeck", "Spreadsheet", "CodeFile", "Image", "ImageText",
|
|
288
|
-
"Audio",
|
|
289
|
-
)]
|
|
290
|
-
if docs:
|
|
291
|
-
sections.append({
|
|
292
|
-
"title": "관련 문서/파일",
|
|
293
|
-
"items": docs,
|
|
294
|
-
"icon": "📄",
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
decisions = [r for r in primary_results if r["type"] in ("Decision", "Task")]
|
|
298
|
-
if decisions:
|
|
299
|
-
sections.append({
|
|
300
|
-
"title": "관련 결정사항/작업",
|
|
301
|
-
"items": decisions,
|
|
302
|
-
"icon": "✅",
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
conversations = [r for r in primary_results if r["type"] == "Chat"]
|
|
306
|
-
if conversations:
|
|
307
|
-
sections.append({
|
|
308
|
-
"title": "관련 대화",
|
|
309
|
-
"items": conversations,
|
|
310
|
-
"icon": "💬",
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
concepts = [r for r in primary_results if r["type"] in ("Concept", "Feature")]
|
|
314
|
-
extra_concepts = [n for n in extra_nodes.values() if n["type"] in ("Concept", "Feature")]
|
|
315
|
-
all_concepts = concepts + extra_concepts[:8]
|
|
316
|
-
if all_concepts:
|
|
317
|
-
sections.append({
|
|
318
|
-
"title": "관련 개념/기술",
|
|
319
|
-
"items": all_concepts,
|
|
320
|
-
"icon": "🔗",
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
return sections
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
def _render_markdown(query: str, sections: List[Dict[str, Any]]) -> str:
|
|
327
|
-
lines = []
|
|
328
|
-
for section in sections:
|
|
329
|
-
if not section["items"]:
|
|
330
|
-
continue
|
|
331
|
-
lines.append(f"### {section['icon']} {section['title']}")
|
|
332
|
-
lines.append("")
|
|
333
|
-
for item in section["items"][:8]:
|
|
334
|
-
title = item.get("title", "")
|
|
335
|
-
summary = _clean(item.get("summary", ""))
|
|
336
|
-
item_type = item.get("type", "")
|
|
337
|
-
score_info = ""
|
|
338
|
-
if "hybrid_score" in item:
|
|
339
|
-
score_info = f" (relevance: {item['hybrid_score']:.2f})"
|
|
340
|
-
|
|
341
|
-
meta = item.get("metadata") or {}
|
|
342
|
-
source = (
|
|
343
|
-
meta.get("relative_path")
|
|
344
|
-
or meta.get("filename")
|
|
345
|
-
or meta.get("conversation_id")
|
|
346
|
-
or meta.get("source")
|
|
347
|
-
or item.get("id", "")
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
lines.append(f"- **[{item_type}] {title}**{score_info}")
|
|
351
|
-
if source and source != item.get("id", ""):
|
|
352
|
-
lines.append(f" - 출처: {source}")
|
|
353
|
-
if summary:
|
|
354
|
-
lines.append(f" - {summary}")
|
|
355
|
-
|
|
356
|
-
related = item.get("related_concepts", [])
|
|
357
|
-
if related:
|
|
358
|
-
tags = ", ".join(c["title"] for c in related[:5])
|
|
359
|
-
lines.append(f" - 관련: {tags}")
|
|
360
|
-
|
|
361
|
-
lines.append("")
|
|
362
|
-
|
|
363
|
-
return "\n".join(lines).strip()
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
def _extract_sources(results: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
|
367
|
-
sources = []
|
|
368
|
-
seen = set()
|
|
369
|
-
for r in results:
|
|
370
|
-
meta = r.get("metadata") or {}
|
|
371
|
-
source_key = (
|
|
372
|
-
meta.get("relative_path")
|
|
373
|
-
or meta.get("filename")
|
|
374
|
-
or meta.get("conversation_id")
|
|
375
|
-
or r.get("id", "")
|
|
376
|
-
)
|
|
377
|
-
if source_key and source_key not in seen:
|
|
378
|
-
seen.add(source_key)
|
|
379
|
-
sources.append({
|
|
380
|
-
"id": r.get("id", ""),
|
|
381
|
-
"type": r.get("type", ""),
|
|
382
|
-
"title": r.get("title", ""),
|
|
383
|
-
"source": source_key,
|
|
384
|
-
})
|
|
385
|
-
return sources
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def format_sources_footnote(sources: List[Dict[str, str]]) -> str:
|
|
389
|
-
if not sources:
|
|
390
|
-
return ""
|
|
391
|
-
lines = ["\n---\n**참조된 지식 그래프 노드:**"]
|
|
392
|
-
for i, src in enumerate(sources[:10], 1):
|
|
393
|
-
lines.append(f"{i}. [{src['type']}] {src['title']} ({src['source']})")
|
|
394
|
-
return "\n".join(lines)
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Document Generator — 지식 그래프 기반 고품질 문서 자동 생성 모듈.
|
|
3
|
-
|
|
4
|
-
사용자가 "Q3 마케팅 전략 보고서 작성해줘" 같은 일반 채팅을 하면,
|
|
5
|
-
Knowledge Graph에서 관련 과거 문서/개념/관계를 자동으로 찾아
|
|
6
|
-
LLM이 자연스럽고 일관성 있게 새로운 문서를 생성한다.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import re
|
|
10
|
-
from typing import Optional
|
|
11
|
-
|
|
12
|
-
_DOCUMENT_INTENT_PATTERNS = [
|
|
13
|
-
re.compile(r"(보고서|계획서|기획서|제안서|문서|리포트|요약서|분석서|전략서|매뉴얼|가이드)", re.IGNORECASE),
|
|
14
|
-
re.compile(r"(작성|만들어|생성|써|줘|write|create|generate|draft|compose|prepare)", re.IGNORECASE),
|
|
15
|
-
re.compile(r"(report|proposal|plan|document|summary|analysis|strategy|guide|manual|brief)", re.IGNORECASE),
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
_STRONG_INTENT_PATTERNS = [
|
|
19
|
-
re.compile(r"(작성해|만들어\s*줘|써\s*줘|생성해|write\s+(?:a|me|the)|create\s+(?:a|me|the)|draft\s+(?:a|me|the))", re.IGNORECASE),
|
|
20
|
-
re.compile(r"(보고서|계획서|기획서|제안서|전략서|매뉴얼).*(작성|만들|생성|써)", re.IGNORECASE),
|
|
21
|
-
re.compile(r"(작성|만들|생성|써).*(보고서|계획서|기획서|제안서|전략서|매뉴얼)", re.IGNORECASE),
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def detect_document_intent(message: str) -> bool:
|
|
26
|
-
"""Detect whether the user's message is requesting document generation."""
|
|
27
|
-
if not message or len(message) < 5:
|
|
28
|
-
return False
|
|
29
|
-
for pattern in _STRONG_INTENT_PATTERNS:
|
|
30
|
-
if pattern.search(message):
|
|
31
|
-
return True
|
|
32
|
-
hit_count = sum(1 for p in _DOCUMENT_INTENT_PATTERNS if p.search(message))
|
|
33
|
-
return hit_count >= 2
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
DOCUMENT_GENERATION_SYSTEM_PROMPT = """당신은 사용자의 개인 AI 지식 어시스턴트 Lattice AI입니다.
|
|
37
|
-
사용자의 기존 지식 기반을 활용하여 고품질 문서를 생성합니다.
|
|
38
|
-
|
|
39
|
-
## 지침
|
|
40
|
-
1. 아래 제공된 지식 그래프 컨텍스트를 최대한 활용하세요.
|
|
41
|
-
2. 이전 문서의 스타일과 톤을 유지하면서 최신적이고 전문적인 문서를 작성하세요.
|
|
42
|
-
3. 출처는 자연스럽게 본문이나 각주에 포함하세요.
|
|
43
|
-
4. 사용자의 언어(한국어/영어)에 맞춰 작성하세요.
|
|
44
|
-
5. 구조화된 포맷(제목, 소제목, 목록 등)을 사용하세요.
|
|
45
|
-
|
|
46
|
-
## 사용자의 지식 기반
|
|
47
|
-
|
|
48
|
-
{graph_context}"""
|
|
49
|
-
|
|
50
|
-
DOCUMENT_GENERATION_FOLLOWUP_PROMPT = """당신은 사용자의 개인 AI 지식 어시스턴트 Lattice AI입니다.
|
|
51
|
-
이전에 생성한 문서를 사용자의 요청에 따라 수정/보완합니다.
|
|
52
|
-
|
|
53
|
-
## 이전 생성 컨텍스트
|
|
54
|
-
|
|
55
|
-
{graph_context}
|
|
56
|
-
|
|
57
|
-
## 이전 문서
|
|
58
|
-
{previous_document}
|
|
59
|
-
|
|
60
|
-
위 문서를 사용자의 요청에 따라 수정하세요. 기존 스타일과 톤을 유지하세요."""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def build_document_system_prompt(graph_context: str) -> str:
|
|
64
|
-
if not graph_context:
|
|
65
|
-
return DOCUMENT_GENERATION_SYSTEM_PROMPT.replace("{graph_context}", "(사용 가능한 지식 기반이 없습니다. 일반 지식을 활용하여 작성합니다.)")
|
|
66
|
-
return DOCUMENT_GENERATION_SYSTEM_PROMPT.replace("{graph_context}", graph_context)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def build_followup_system_prompt(graph_context: str, previous_document: str) -> str:
|
|
70
|
-
prompt = DOCUMENT_GENERATION_FOLLOWUP_PROMPT.replace("{graph_context}", graph_context or "(없음)")
|
|
71
|
-
return prompt.replace("{previous_document}", previous_document or "(없음)")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class DocumentGenerationSession:
|
|
75
|
-
"""Maintains state across iterative document generation requests."""
|
|
76
|
-
|
|
77
|
-
def __init__(self):
|
|
78
|
-
self._last_context: Optional[str] = None
|
|
79
|
-
self._last_document: Optional[str] = None
|
|
80
|
-
self._conversation_id: Optional[str] = None
|
|
81
|
-
|
|
82
|
-
@property
|
|
83
|
-
def has_previous(self) -> bool:
|
|
84
|
-
return self._last_document is not None
|
|
85
|
-
|
|
86
|
-
def update(self, context: str, document: str, conversation_id: Optional[str] = None) -> None:
|
|
87
|
-
self._last_context = context
|
|
88
|
-
self._last_document = document
|
|
89
|
-
if conversation_id:
|
|
90
|
-
self._conversation_id = conversation_id
|
|
91
|
-
|
|
92
|
-
def get_system_prompt(self, graph_context: str) -> str:
|
|
93
|
-
if self.has_previous:
|
|
94
|
-
return build_followup_system_prompt(
|
|
95
|
-
graph_context or self._last_context or "",
|
|
96
|
-
self._last_document or "",
|
|
97
|
-
)
|
|
98
|
-
return build_document_system_prompt(graph_context)
|
|
99
|
-
|
|
100
|
-
def clear(self) -> None:
|
|
101
|
-
self._last_context = None
|
|
102
|
-
self._last_document = None
|
|
103
|
-
self._conversation_id = None
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
"""Enterprise extension seam (open-core boundary).
|
|
2
|
-
|
|
3
|
-
LatticeAI Community is fully functional on its own. This module defines the
|
|
4
|
-
*seam* where a future, separately-distributed Enterprise plugin can attach
|
|
5
|
-
advanced capabilities (SSO provisioning, SCIM, tenant isolation, compliance
|
|
6
|
-
retention, SIEM export, and so on) **without** any of that code living in the
|
|
7
|
-
public Community repository.
|
|
8
|
-
|
|
9
|
-
Design rules enforced here:
|
|
10
|
-
|
|
11
|
-
* Community is the default :class:`Edition`. Every :class:`EnterpriseCapability`
|
|
12
|
-
is **disabled** unless an Enterprise provider is explicitly registered.
|
|
13
|
-
* Nothing in this module restricts or gates a Community feature. It only answers
|
|
14
|
-
"is this *Enterprise* capability available?" — which is ``False`` in the open
|
|
15
|
-
build.
|
|
16
|
-
* Enterprise behaviour is supplied at runtime through
|
|
17
|
-
:class:`CapabilityProvider` implementations registered on the shared
|
|
18
|
-
:data:`capability_registry`. The public code never imports Enterprise code.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import os
|
|
24
|
-
from enum import Enum
|
|
25
|
-
from typing import Dict, Protocol, runtime_checkable
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class Edition(str, Enum):
|
|
29
|
-
"""Distribution edition. Community is the open-source default."""
|
|
30
|
-
|
|
31
|
-
COMMUNITY = "community"
|
|
32
|
-
ENTERPRISE = "enterprise"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class EnterpriseCapability(str, Enum):
|
|
36
|
-
"""Capabilities reserved for the Enterprise edition.
|
|
37
|
-
|
|
38
|
-
These are documented in ``docs/ENTERPRISE.md``. They are intentionally
|
|
39
|
-
declared (so the seam is stable and discoverable) but never implemented in
|
|
40
|
-
the Community build.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
SSO_ADVANCED = "sso_advanced"
|
|
44
|
-
IDP_PROVISIONING = "idp_provisioning" # Entra ID / Okta provisioning
|
|
45
|
-
SCIM = "scim"
|
|
46
|
-
RBAC_ABAC_ADVANCED = "rbac_abac_advanced"
|
|
47
|
-
TENANT_ISOLATION = "tenant_isolation"
|
|
48
|
-
COMPLIANCE_RETENTION = "compliance_retention"
|
|
49
|
-
SIEM_EXPORT = "siem_export"
|
|
50
|
-
PRIVATE_VPC = "private_vpc"
|
|
51
|
-
AIR_GAPPED_DEPLOYMENT = "air_gapped_deployment"
|
|
52
|
-
DLP_POLICY = "dlp_policy"
|
|
53
|
-
EDISCOVERY = "ediscovery"
|
|
54
|
-
ADMIN_POLICY_PACKS = "admin_policy_packs"
|
|
55
|
-
# Human review-before-promote governance for knowledge-graph concept
|
|
56
|
-
# promotions (review 2026-07-25 Wave 4). Community users can opt in per
|
|
57
|
-
# deployment via LATTICEAI_GRAPH_PROMOTION_REVIEW; this capability is the
|
|
58
|
-
# seam for Enterprise policy packs that mandate reviewed promotions.
|
|
59
|
-
GRAPH_PROMOTION_REVIEW = "graph_promotion_review"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@runtime_checkable
|
|
63
|
-
class CapabilityProvider(Protocol):
|
|
64
|
-
"""Contract an Enterprise plugin implements to light up capabilities.
|
|
65
|
-
|
|
66
|
-
A provider is registered at runtime via
|
|
67
|
-
:meth:`CapabilityRegistry.register_provider`. The Community build ships no
|
|
68
|
-
provider, so :meth:`is_enabled` is effectively ``False`` everywhere.
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
def edition(self) -> Edition:
|
|
72
|
-
...
|
|
73
|
-
|
|
74
|
-
def is_enabled(self, capability: EnterpriseCapability) -> bool:
|
|
75
|
-
...
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class CommunityCapabilityProvider:
|
|
79
|
-
"""Default provider: Community edition, no Enterprise capabilities."""
|
|
80
|
-
|
|
81
|
-
def edition(self) -> Edition:
|
|
82
|
-
return Edition.COMMUNITY
|
|
83
|
-
|
|
84
|
-
def is_enabled(self, capability: EnterpriseCapability) -> bool: # noqa: ARG002
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class CapabilityRegistry:
|
|
89
|
-
"""Runtime seam that an Enterprise plugin can attach a provider to.
|
|
90
|
-
|
|
91
|
-
The registry holds at most one active provider. Until an Enterprise build
|
|
92
|
-
registers one, the :class:`CommunityCapabilityProvider` answers every query
|
|
93
|
-
with "Community / disabled".
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
def __init__(self) -> None:
|
|
97
|
-
self._provider: CapabilityProvider = CommunityCapabilityProvider()
|
|
98
|
-
|
|
99
|
-
def register_provider(self, provider: CapabilityProvider) -> None:
|
|
100
|
-
if not isinstance(provider, CapabilityProvider):
|
|
101
|
-
raise TypeError("provider must implement the CapabilityProvider protocol")
|
|
102
|
-
self._provider = provider
|
|
103
|
-
|
|
104
|
-
def reset(self) -> None:
|
|
105
|
-
"""Restore the Community default provider (used by tests)."""
|
|
106
|
-
self._provider = CommunityCapabilityProvider()
|
|
107
|
-
|
|
108
|
-
def edition(self) -> Edition:
|
|
109
|
-
return self._provider.edition()
|
|
110
|
-
|
|
111
|
-
def is_capability_enabled(self, capability: EnterpriseCapability) -> bool:
|
|
112
|
-
try:
|
|
113
|
-
return bool(self._provider.is_enabled(capability))
|
|
114
|
-
except Exception:
|
|
115
|
-
# A misbehaving plugin must never break a Community request.
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
def describe(self) -> Dict[str, object]:
|
|
119
|
-
"""Edition + capability matrix for ``/workspace/editions`` and admin UI."""
|
|
120
|
-
return {
|
|
121
|
-
"edition": self.edition().value,
|
|
122
|
-
"is_enterprise": self.edition() is Edition.ENTERPRISE,
|
|
123
|
-
"capabilities": {
|
|
124
|
-
cap.value: self.is_capability_enabled(cap) for cap in EnterpriseCapability
|
|
125
|
-
},
|
|
126
|
-
"community_notice": (
|
|
127
|
-
"All listed capabilities are Enterprise-only extension points. "
|
|
128
|
-
"The open-source Community edition ships none of them enabled; "
|
|
129
|
-
"see docs/ENTERPRISE.md and docs/EDITION_STRATEGY.md."
|
|
130
|
-
),
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# Shared singleton seam. Enterprise plugins import this and call
|
|
135
|
-
# ``capability_registry.register_provider(...)`` during their own startup.
|
|
136
|
-
capability_registry = CapabilityRegistry()
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def detect_edition() -> Edition:
|
|
140
|
-
"""Best-effort edition detection.
|
|
141
|
-
|
|
142
|
-
Honours an explicit ``LATTICE_EDITION=enterprise`` opt-in for environments
|
|
143
|
-
that have separately installed an Enterprise provider, but otherwise the
|
|
144
|
-
registry's active provider is the source of truth.
|
|
145
|
-
"""
|
|
146
|
-
env = os.environ.get("LATTICE_EDITION", "").strip().lower()
|
|
147
|
-
if env == Edition.ENTERPRISE.value and capability_registry.edition() is Edition.ENTERPRISE:
|
|
148
|
-
return Edition.ENTERPRISE
|
|
149
|
-
return capability_registry.edition()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def is_capability_enabled(capability: EnterpriseCapability) -> bool:
|
|
153
|
-
"""Module-level convenience used by Community code at extension points."""
|
|
154
|
-
return capability_registry.is_capability_enabled(capability)
|