dsh-data-domain-knowledge 0.1.0-alpha.12 → 0.1.0-alpha.13

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.
Files changed (62) hide show
  1. package/README.md +15 -7
  2. package/lib/client.js +64 -7
  3. package/lib/types/environment/install.d.ts +2 -1
  4. package/lib/types/environment/install.d.ts.map +1 -1
  5. package/lib/types/environment/install.js +20 -11
  6. package/lib/types/environment/install.js.map +1 -1
  7. package/lib/types/environment/manifest.d.ts +203 -12
  8. package/lib/types/environment/manifest.d.ts.map +1 -1
  9. package/lib/types/environment/manifest.js +23 -21
  10. package/lib/types/environment/manifest.js.map +1 -1
  11. package/lib/types/environment/model-catalog.d.ts +191 -0
  12. package/lib/types/environment/model-catalog.d.ts.map +1 -0
  13. package/lib/types/environment/model-catalog.js +91 -0
  14. package/lib/types/environment/model-catalog.js.map +1 -0
  15. package/lib/types/environment/selection.d.ts +9 -0
  16. package/lib/types/environment/selection.d.ts.map +1 -0
  17. package/lib/types/environment/selection.js +29 -0
  18. package/lib/types/environment/selection.js.map +1 -0
  19. package/lib/types/environment/service.d.ts +1 -1
  20. package/lib/types/environment/service.d.ts.map +1 -1
  21. package/lib/types/environment/service.js +54 -33
  22. package/lib/types/environment/service.js.map +1 -1
  23. package/lib/types/environment/types.d.ts +7 -1
  24. package/lib/types/environment/types.d.ts.map +1 -1
  25. package/lib/types/images.d.ts +26 -0
  26. package/lib/types/images.d.ts.map +1 -0
  27. package/lib/types/images.js +196 -0
  28. package/lib/types/images.js.map +1 -0
  29. package/lib/types/index.d.ts.map +1 -1
  30. package/lib/types/index.js +25 -3
  31. package/lib/types/index.js.map +1 -1
  32. package/lib/types/layout.d.ts +2 -0
  33. package/lib/types/layout.d.ts.map +1 -1
  34. package/lib/types/layout.js +2 -0
  35. package/lib/types/layout.js.map +1 -1
  36. package/lib/types/runtime.d.ts +9 -3
  37. package/lib/types/runtime.d.ts.map +1 -1
  38. package/lib/types/runtime.js +73 -16
  39. package/lib/types/runtime.js.map +1 -1
  40. package/lib/types/store/migrate.d.ts.map +1 -1
  41. package/lib/types/store/migrate.js +5 -4
  42. package/lib/types/store/migrate.js.map +1 -1
  43. package/lib/types/types.d.ts +1 -1
  44. package/lib/types/types.d.ts.map +1 -1
  45. package/lib/types/types.js +3 -4
  46. package/lib/types/types.js.map +1 -1
  47. package/package.json +1 -1
  48. package/python/prepare-models.py +1 -1
  49. package/scripts/setup-embedding.sh +11 -2
  50. package/src/client/EnvironmentSetup.tsx +54 -8
  51. package/src/environment/install.ts +22 -10
  52. package/src/environment/manifest.ts +25 -22
  53. package/src/environment/model-catalog.ts +105 -0
  54. package/src/environment/selection.ts +29 -0
  55. package/src/environment/service.ts +46 -26
  56. package/src/environment/types.ts +5 -1
  57. package/src/images.ts +203 -0
  58. package/src/index.ts +30 -4
  59. package/src/layout.ts +2 -0
  60. package/src/runtime.ts +68 -16
  61. package/src/store/migrate.ts +4 -4
  62. package/src/types.ts +4 -5
package/src/runtime.ts CHANGED
@@ -44,7 +44,7 @@ import {
44
44
  RELATED_METADATA_KEY, type DeadLink, type RelatedEntry,
45
45
  } from './wiki-finalize.js'
46
46
  import { applyRerank, applyThreshold, evidenceExcerpts, graphBoost, matchOf, noteIndexText, reviewWeight, rrfFuse, tokenSet, type RouteRanking } from './recall-rank.js'
47
- import { ASSETS_DIR, KNOWLEDGE_DB_FILE, SOURCE_RAW_DIR, defaultAgentRoot, knowledgeBaseRoot } from './layout.js'
47
+ import { ASSETS_DIR, KNOWLEDGE_DB_FILE, PUBLISHED_IMAGE_MARKER, SOURCE_RAW_DIR, defaultAgentRoot, knowledgeBaseRoot } from './layout.js'
48
48
  import { KnowledgeDb, type CardKnowledgeRow, type Journal as StoreJournal, type SourceOriginal } from './store/knowledge-db.js'
49
49
  import { exportKnowledgeTree as exportTree, migrateKnowledgeTree } from './store/migrate.js'
50
50
  import { COMPILE_SNAPSHOT, type CompileSnapshot } from './compiler/snapshot.js'
@@ -59,6 +59,8 @@ import { unlinkIfExists, writeBufferAtomic, writeTextAtomic } from './fs-atomic.
59
59
  import { envelopeFailure, envelopeSuccess } from 'dsh-data-contracts/envelope'
60
60
  import type { LineageContext, LineageRef } from 'dsh-data-contracts/refs'
61
61
  import { analyzeQuery, relationCandidates, type QueryAnalysis } from './query-analysis.js'
62
+ import { readModelSelection, selectedEmbedding } from './environment/selection.js'
63
+ import { rerankModel as rerankModelOption } from './environment/model-catalog.js'
62
64
 
63
65
  // **知识树只有三段**(v1.1 起):`01_source_raw` / `02_wiki` / `03_compiled`。
64
66
  //
@@ -1719,8 +1721,8 @@ export interface WikiSchemaSnapshot {
1719
1721
  /**
1720
1722
  * 解析三个「家当」路径(0029)。
1721
1723
  *
1722
- * 优先级:**配置里显式写的值**(由构造函数处理)> `<agentRoot>/resource/…`(存在时)
1723
- * > 兜底(`python3` / HF 模型名)。
1724
+ * 资源目录内的隔离解释器优先于 DSH_PYTHON;自定义配置路径仍由构造函数处理。
1725
+ * 未准备资源时回退到 DSH_PYTHON、`python3` 或 HF 模型名。
1724
1726
  *
1725
1727
  * `modelId` 把本地模型的**身份**与它的位置解耦,避免同一 Agent 根目录下的
1726
1728
  * resource 目录变化导致向量清单误判为配置不匹配。
@@ -1729,6 +1731,7 @@ function resolveResources(resourceRoot: string | undefined): {
1729
1731
  python: string
1730
1732
  embeddingModel: string
1731
1733
  rerankModel: string
1734
+ embeddingDimension?: number
1732
1735
  modelId: (path: string) => string
1733
1736
  } {
1734
1737
  const root = resourceRoot ? resolve(resourceRoot) : undefined
@@ -1739,14 +1742,20 @@ function resolveResources(resourceRoot: string | undefined): {
1739
1742
  const python = firstExisting([
1740
1743
  root && process.platform === 'win32' ? join(root, 'python', 'Scripts', 'python.exe') : undefined,
1741
1744
  root ? join(root, 'python', 'bin', 'python') : undefined,
1742
- ], 'python3')
1745
+ ], process.env.DSH_PYTHON || 'python3')
1746
+ const selected = selectedEmbedding(root)
1747
+ const selection = root ? readModelSelection(root) : undefined
1748
+ const selectedRerank = root && selection ? join(root, 'models', rerankModelOption(selection.rerank).directory) : undefined
1743
1749
  const embeddingModel = firstExisting([
1750
+ selected?.path,
1744
1751
  root ? join(root, 'models', PREFERRED_LOCAL_EMBEDDING_MODEL_DIR) : undefined,
1745
1752
  root ? join(root, 'models', LOCAL_EMBEDDING_MODEL_DIR) : undefined,
1746
1753
  ], FALLBACK_EMBEDDING_MODEL)
1747
1754
  const rerankModel = firstExisting([
1755
+ selectedRerank,
1748
1756
  root ? join(root, 'models', LOCAL_RERANK_MODEL_DIR) : undefined,
1749
1757
  ], FALLBACK_RERANK_MODEL)
1758
+ const embeddingDimension = selected?.path && embeddingModel === selected.path ? selected.dimension : undefined
1750
1759
  const modelId = (path: string): string => {
1751
1760
  for (const base of [root]) {
1752
1761
  if (!base || !path.startsWith(`${base}${sep}`)) continue
@@ -1755,7 +1764,14 @@ function resolveResources(resourceRoot: string | undefined): {
1755
1764
  }
1756
1765
  return path
1757
1766
  }
1758
- return { python, embeddingModel, rerankModel, modelId }
1767
+ return { python, embeddingModel, rerankModel, embeddingDimension, modelId }
1768
+ }
1769
+
1770
+ /** An inherited DSH_PYTHON value selects the base interpreter, not the dependency environment. */
1771
+ function resolveConfiguredPython(configured: string | undefined, resourcePython: string): string {
1772
+ const selected = configured?.trim()
1773
+ const hostPython = process.env.DSH_PYTHON?.trim()
1774
+ return selected && selected !== hostPython ? selected : resourcePython
1759
1775
  }
1760
1776
 
1761
1777
  export interface KnowledgeRuntimeOptions {
@@ -1773,7 +1789,7 @@ export interface KnowledgeRuntimeOptions {
1773
1789
  * 0029:家当目录(`<agentRoot>/resource`)。
1774
1790
  *
1775
1791
  * 配置里那三个路径字段(`python` / `embeddingModel` / `rerankModel`)为空时,
1776
- * 它们从这里解析。省略时使用系统命令或模型名作为兜底 —— 独立运行时与单元
1792
+ * 它们从这里解析。省略时使用 DSH Python、系统命令或模型名作为兜底 —— 独立运行时与单元
1777
1793
  * 测试不必先造一个根路径。
1778
1794
  */
1779
1795
  resourceRoot?: string
@@ -2161,7 +2177,8 @@ export class KnowledgeRuntime {
2161
2177
  this.modelSeamProvider = options.modelSeam
2162
2178
  this.resourceRoot = options.resourceRoot
2163
2179
  const resources = resolveResources(options.resourceRoot)
2164
- this.pythonPath = config.python?.trim() || resources.python
2180
+ if (!this.config.embeddingModel?.trim() && resources.embeddingDimension) this.config.embeddingDimension = resources.embeddingDimension
2181
+ this.pythonPath = resolveConfiguredPython(config.python, resources.python)
2165
2182
  this.embeddingModelPath = config.embeddingModel?.trim() || resources.embeddingModel
2166
2183
  this.rerankModelPath = config.rerankModel?.trim() || resources.rerankModel
2167
2184
  this.embeddingModelId = resources.modelId(this.embeddingModelPath)
@@ -2201,6 +2218,10 @@ export class KnowledgeRuntime {
2201
2218
  updateConfig(patch: Partial<RuntimeKnowledgeConfig>): void {
2202
2219
  const previous = this.config
2203
2220
  this.config = { ...previous, ...patch }
2221
+ const resources = resolveResources(this.resourceRoot)
2222
+ // A managed model preset owns its output dimension. Custom model paths keep
2223
+ // the advanced dimension setting from the plugin config.
2224
+ if (!this.config.embeddingModel?.trim() && resources.embeddingDimension) this.config.embeddingDimension = resources.embeddingDimension
2204
2225
 
2205
2226
  const modelChanged =
2206
2227
  previous.embeddingProvider !== this.config.embeddingProvider ||
@@ -2227,8 +2248,7 @@ export class KnowledgeRuntime {
2227
2248
  this.rerankWorker = null
2228
2249
  this.rerankUnavailable = undefined
2229
2250
  }
2230
- const resources = resolveResources(this.resourceRoot)
2231
- if (patch.python !== undefined) this.pythonPath = patch.python.trim() || resources.python
2251
+ this.pythonPath = resolveConfiguredPython(this.config.python, resources.python)
2232
2252
  if (patch.embeddingModel !== undefined) this.embeddingModelPath = patch.embeddingModel.trim() || resources.embeddingModel
2233
2253
  if (patch.rerankModel !== undefined) this.rerankModelPath = patch.rerankModel.trim() || resources.rerankModel
2234
2254
  this.embeddingModelId = resources.modelId(this.embeddingModelPath)
@@ -7454,7 +7474,7 @@ export class KnowledgeRuntime {
7454
7474
  const cardOrphans = cards.filter(card => card.sourceRefs.every(ref => !live.has(ref))).length
7455
7475
  const events = (await this.loadEvents()).filter(event => event.records.some(record => !live.has(record.sourceId))).length
7456
7476
  /**
7457
- * 图片也进这张账(2026-10-05)。
7477
+ * 未被引用的来源图片也进这张账(2026-10-05)。`images.publish` 写入的独立图片会被识别并保留。
7458
7478
  *
7459
7479
  * 少了它,「来源与卡片都清完了、只剩一堆没人引用的图」这种状态在界面上**一个字都没有**
7460
7480
  * —— 清理孤儿的入口因此不出现,而用户唯一能看到的读数(「收录 N 个文件」)又把它算成
@@ -7462,7 +7482,7 @@ export class KnowledgeRuntime {
7462
7482
  * 「按一下能清掉几个」是同几个。
7463
7483
  */
7464
7484
  const names = await this.assetBucketNames()
7465
- const assets = names.length === 0 ? 0 : this.unreferencedBuckets(names, sources, cards).length
7485
+ const assets = names.length === 0 ? 0 : (await this.unreferencedBuckets(names, sources, cards)).length
7466
7486
  return { knowledge, cards: cardOrphans, events, assets }
7467
7487
  }
7468
7488
 
@@ -7490,8 +7510,9 @@ export class KnowledgeRuntime {
7490
7510
  }
7491
7511
 
7492
7512
  /**
7493
- * 还有没有人在引用这个桶 —— 判据是**三处文本里出现过 `_assets/<bucket>/`**:
7494
- * 来源正文、来源 front matter 的 `metadata.assets`、卡片正文。
7513
+ * 还有没有人在引用这个桶 —— 判据是来源正文、来源 front matter 的 `metadata.assets`、
7514
+ * 卡片正文出现过 `_assets/<bucket>/`,或者它是 `images.publish` 写入的独立图床图片。
7515
+ * 独立图片没有来源/卡片引用是正常状态,必须保留。
7495
7516
  *
7496
7517
  * 为什么三处都要看:桶名是**内容哈希的前 12 位**(`convert/ingest.ts`),不是来源 id。
7497
7518
  * 同一份内容以两个分类导入会共用同一个桶;而卡片正文会把图片引用**复制进去**
@@ -7499,7 +7520,7 @@ export class KnowledgeRuntime {
7499
7520
  *
7500
7521
  * 不看 `03_compiled/`:那是派生物,下一次 `rebuild` 会把指向已删图片的引用一起扔掉。
7501
7522
  */
7502
- private unreferencedBuckets(names: string[], sources: SourceRaw[], cards: KnowledgeCard[]): string[] {
7523
+ private async unreferencedBuckets(names: string[], sources: SourceRaw[], cards: KnowledgeCard[]): Promise<string[]> {
7503
7524
  const referenced = new Set<string>()
7504
7525
  /**
7505
7526
  * **两种引用形式都算**:`assets/<桶>/`(新写的)与 `_assets/<桶>/`(存量)。
@@ -7518,7 +7539,38 @@ export class KnowledgeRuntime {
7518
7539
  if (Array.isArray(source.metadata.assets)) for (const item of source.metadata.assets) if (typeof item === 'string') collect(item)
7519
7540
  }
7520
7541
  for (const card of cards) collect(card.content)
7521
- return names.filter(name => !referenced.has(name))
7542
+ const unreferenced: string[] = []
7543
+ for (const name of names) {
7544
+ if (referenced.has(name)) continue
7545
+ if (await this.isPublishedImageBucket(name)) continue
7546
+ unreferenced.push(name)
7547
+ }
7548
+ return unreferenced
7549
+ }
7550
+
7551
+ /**
7552
+ * `images.publish` 的图片不需要被来源或卡片引用。新写入的桶有明确标记;同时兼容
7553
+ * 标记加入前已发布的图片:它们以图片自身的 SHA-256 前 12 位作为桶名。
7554
+ */
7555
+ private async isPublishedImageBucket(name: string): Promise<boolean> {
7556
+ for (const root of this.assetRoots()) {
7557
+ if (existsSync(join(root, name, PUBLISHED_IMAGE_MARKER))) return true
7558
+ }
7559
+
7560
+ // 旧版本 images.publish 只会写入知识库根目录的 assets/,因此只在这里做哈希识别。
7561
+ if (!/^[a-f0-9]{12}$/i.test(name)) return false
7562
+ const dir = join(this.rootDir, ASSETS_DIR, name)
7563
+ try {
7564
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
7565
+ if (!entry.isFile() || !/\.(?:png|jpe?g|gif|webp|avif|bmp|svg)$/i.test(entry.name)) continue
7566
+ const path = join(dir, entry.name)
7567
+ const details = await stat(path)
7568
+ if (details.size > 20 * 1024 * 1024) continue
7569
+ const bytes = await readFile(path)
7570
+ if (createHash('sha256').update(bytes).digest('hex').slice(0, 12) === name) return true
7571
+ }
7572
+ } catch { /* 桶目录可能在扫描期间被删除 */ }
7573
+ return false
7522
7574
  }
7523
7575
 
7524
7576
  /**
@@ -7588,7 +7640,7 @@ export class KnowledgeRuntime {
7588
7640
  if (names.length === 0) return { buckets: 0, files: 0, problems: [] }
7589
7641
  const problems: string[] = []
7590
7642
  const cards = this.cards.length > 0 ? this.cards : await this.loadCards()
7591
- const doomed = this.unreferencedBuckets(names, await this.loadSources(), cards)
7643
+ const doomed = await this.unreferencedBuckets(names, await this.loadSources(), cards)
7592
7644
  let buckets = 0
7593
7645
  let files = 0
7594
7646
  for (const name of doomed) {
@@ -35,7 +35,7 @@
35
35
  import { existsSync } from 'node:fs'
36
36
  import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
37
37
  import { basename, dirname, join, relative } from 'node:path'
38
- import { ASSETS_DIR, SOURCE_RAW_DIR } from '../layout.js'
38
+ import { ASSETS_DIR, PUBLISHED_IMAGE_MARKER, SOURCE_RAW_DIR } from '../layout.js'
39
39
  import type { KnowledgeCard, SourceRaw } from '../types.js'
40
40
  import type { KnowledgeDb } from './knowledge-db.js'
41
41
 
@@ -300,7 +300,7 @@ export async function migrateKnowledgeTree(input: MigrationInput): Promise<Migra
300
300
  const to = join(kbRoot, ASSETS_DIR, bucket)
301
301
  report.assetBuckets += 1
302
302
  if (dryRun) {
303
- report.assetFiles += (await listFiles(from, '')).length
303
+ report.assetFiles += (await listFiles(from, '')).filter(path => basename(path) !== PUBLISHED_IMAGE_MARKER).length
304
304
  continue
305
305
  }
306
306
  try {
@@ -316,7 +316,7 @@ export async function migrateKnowledgeTree(input: MigrationInput): Promise<Migra
316
316
  */
317
317
  if (!existsSync(to)) await rename(from, to)
318
318
  else await mergeAssetBucket(from, to)
319
- report.assetFiles += (await listFiles(to, '')).length
319
+ report.assetFiles += (await listFiles(to, '')).filter(path => basename(path) !== PUBLISHED_IMAGE_MARKER).length
320
320
  } catch (cause) {
321
321
  problems.push(`搬不动图片桶 ${bucket}:${cause instanceof Error ? cause.message : String(cause)}`)
322
322
  }
@@ -565,7 +565,7 @@ async function copyContentTree(from: string, to: string, overwrite: boolean): Pr
565
565
  if (!overwrite && existsSync(target)) continue
566
566
  await mkdir(dirname(target), { recursive: true })
567
567
  await cp(source, target, { force: true })
568
- copied += 1
568
+ if (basename(source) !== PUBLISHED_IMAGE_MARKER) copied += 1
569
569
  }
570
570
  return copied
571
571
  }
package/src/types.ts CHANGED
@@ -130,7 +130,7 @@ export interface KnowledgeConfig {
130
130
  * 组织(表达层)。两步都开着才是完整的「Knowledge Compiler + Wiki Materializer」。
131
131
  */
132
132
  cardOrganize: boolean
133
- /** Python 解释器。空串 = 自动(`<agentRoot>/resource/python/bin/python`,再退回 `python3`)。 */
133
+ /** Python 解释器。空串 = 自动(隔离依赖环境,再退回 `DSH_PYTHON` / `python3`)。 */
134
134
  python: string
135
135
  embeddingProvider: EmbeddingProvider
136
136
  embeddingBaseUrl: string
@@ -231,10 +231,9 @@ export const DEFAULT_KNOWLEDGE_CONFIG: RuntimeKnowledgeConfig = {
231
231
  // 卡片组织(模型)默认**开**:Knowledge 只是事实层,Wiki 卡片正文必须经过这一步
232
232
  // 物化成可读的表达。只有显式写 `false` / `0` 才关闭兼容路径。
233
233
  cardOrganize: !['false', '0'].includes((process.env.DSH_KNOWLEDGE_CARD_ORGANIZE ?? '').trim().toLowerCase()),
234
- // 0029:三个「家当」路径的默认值是**空串 = 自动**。它们跟着 `<agentRoot>/resource/`
235
- // 走,而 agentRoot 由设置决定、模块加载时还不知道 —— 所以在运行时解析
236
- // (`runtime.ts` 的 `resolveResources`)。环境变量仍然优先。
237
- python: process.env.DSH_PYTHON || '',
234
+ // 0029:空串 = 自动。优先用 `<agentRoot>/resource/python/` 的隔离依赖环境;
235
+ // DSH_PYTHON 只作为创建该环境的基础解释器和未准备时的运行时回退。
236
+ python: '',
238
237
  embeddingProvider: 'wemm-python',
239
238
  embeddingBaseUrl: process.env.DSH_EMBEDDING_BASE_URL || 'http://127.0.0.1:11434',
240
239
  embeddingModel: process.env.DSH_EMBEDDING_MODEL || process.env.DSH_EMBEDDING_MODEL_PATH || '',