diff --git a/docs/algorithms/retrieval.md b/docs/algorithms/retrieval.md index e5df9f3..e7fd72e 100644 --- a/docs/algorithms/retrieval.md +++ b/docs/algorithms/retrieval.md @@ -57,7 +57,7 @@ ## 9. 可选增强 -默认全部关闭,按需启用: +默认全部按需启用(注意:`enableCrossRecall` 在应用设置里默认**开**,其余增强项默认关,以代码为准): - **交叉召回**(`enableCrossRecall`):精确实体锚点存在时,把相连的 `event` 邻居作为扩散种子加入(能量 `1.5 × edge.strength`)。范围比"双记忆交叉检索"窄,只走"精确锚点 → 相连事件邻居"。 - **共现增强**(`enableCooccurrenceBoost`):用精确锚点和补充向量锚点构建共现索引,给图分加 bonus。 @@ -96,15 +96,20 @@ ## 关键默认参数 -| 参数 | 默认 | 含义 | -| --- | --- | --- | -| `topK` | 20 | 排序候选数 | -| `maxRecallNodes` | 8 | 最终注入节点上限 | -| `diffusionTopK` | 100 | 扩散保留节点数 | -| `llmCandidatePool` | 30 | LLM 精排候选池 | -| `enableLLMRecall` | true | LLM 精排 | -| `enableVectorPrefilter` | true | 向量预筛 | -| `enableGraphDiffusion` | true | 图扩散 | -| `enableCrossRecall` | false | 交叉召回 | -| `enableProbRecall` | false | 概率召回 | -| `enableDiversitySampling` | true | DPP 多样性 | +> 注意两套默认值的区别:下表"retrieve() 回退"是当调用方省略选项时 `retriever.js` 内部的兜底值;"应用设置"是 `runtime/settings-defaults.js` 里正常召回实际使用的默认,由 `index.js` 映射进 `retrieve()`。两者不同时,**正常召回看应用设置那一列**。 + +| 参数 | retrieve() 回退 | 应用设置(正常召回) | 含义 | +| --- | --- | --- | --- | +| topK | 20 | `recallTopK` 20 | 排序候选数 | +| maxRecallNodes | 8 | `recallMaxNodes` **12** | 最终注入节点上限 | +| diffusionTopK | 100 | 100 | 扩散保留节点数 | +| llmCandidatePool | 30 | 30 | LLM 精排候选池 | +| enableLLMRecall | true | true | LLM 精排 | +| enableVectorPrefilter | true | true | 向量预筛 | +| enableGraphDiffusion | true | true | 图扩散 | +| enableVisibility | (未设即关) | **true** | 可见性过滤 | +| enableCrossRecall | false | **true** | 交叉召回 | +| enableProbRecall | false | false | 概率召回 | +| enableDiversitySampling | true | true | DPP 多样性 | + +> 所以正常召回的实际最终注入上限是 **12**(不是 8),且交叉召回和可见性过滤默认**开启**——除非用户改设置。 diff --git a/docs/algorithms/vector-and-embedding.md b/docs/algorithms/vector-and-embedding.md index 91ce6a2..e777e8f 100644 --- a/docs/algorithms/vector-and-embedding.md +++ b/docs/algorithms/vector-and-embedding.md @@ -6,7 +6,7 @@ ## Embedding 执行位置 -> `embeddingExecution` 默认 `client`——embedding 默认在客户端(浏览器)执行。 +> embedding 默认在客户端(浏览器)执行:`embeddingTransportMode` 默认 `"direct"`(直连第三方 embedding URL)。另一个值是 `"backend"`(走宿主后端代理)。 第三方自定义 URL 是一等公民:OpenAI 兼容 `/v1/embeddings`、one-api、new-api、litellm、vLLM、llama.cpp、Ollama 桥接等。Authority 不生成 embedding,只存/搜向量。 @@ -74,6 +74,6 @@ HTTP 错误(400/401/403/429/502 等)会带状态码和响应体抛出,而 | 参数 | 默认 | 含义 | | --- | --- | --- | -| `embeddingExecution` | client | embedding 执行位置 | +| `embeddingTransportMode` | direct | embedding 传输模式(direct 直连 / backend 后端代理) | | `embeddingBatchSize` | 10(上限 100) | 批量分块大小 | | `encoding_format` | float | 直连 OpenAI 兼容请求 | diff --git a/docs/architecture/control-plane.md b/docs/architecture/control-plane.md index 5cacf0c..427b215 100644 --- a/docs/architecture/control-plane.md +++ b/docs/architecture/control-plane.md @@ -14,20 +14,28 @@ 聊天身份是一切持久化的主键。问题从来不是"chatId 这个键选错了",而是"有好几个来源都自称是当前身份,还互相偷偷顶替"。 -身份核心在 `runtime/identity-resolver.js`,把身份明确拆成**四条独立通道**,绝不互相顶替: +身份核心在 `runtime/identity-resolver.js`,把身份明确按**四类语义**区分对待,绝不互相顶替: -| 通道 | 含义 | 唯一来源 | +| 类别 | 含义 | 来源 | | --- | --- | --- | -| **active identity** | 当前宿主活动聊天 | 只来自宿主上下文(context integrity / hostChatId 的已知别名 / hostChatId) | +| **active / current identity** | 当前宿主活动聊天 | 只来自宿主上下文(context integrity / hostChatId 的已知别名 / hostChatId) | | **graph-owner identity** | 图谱自带的所属身份 | 图谱 meta,只用于校验/恢复 | -| **queued identity** | 排队持久化的身份 | 持久化队列状态,只用于校验 | +| **queued identity** | 排队持久化的身份 | 持久化状态,只用于校验/恢复 | | **marker identity** | commit marker 的身份 | commit marker,只用于校验/恢复 | +实现上: + +- **active/current** 由独立入口 `resolveCurrentChatIdentityCore()` 解析,只认宿主上下文来源。 +- **graph-owner** 由 `resolveGraphOwnerIdentityCore()` 解析。 +- **queued / marker** 没有各自独立的解析入口——它们和 graph-owner 一起,在 `resolveRuntimeGraphFallbackIdentityCore()` / `resolvePersistenceChatIdCore()` 这类**恢复/兜底**聚合里被读取(含 `persistenceState.queuedPersistChatId` 和 `persistenceState.commitMarker.chatId`),并配合调用方的身份等值校验使用。 + +关键不在于"每类都有独立函数",而在于**只有 active/current 这条通道能产出"当前聊天",其余通道一律只进校验/恢复,不能升格为活动身份**。 + **核心不变量:** > active identity 只能来自宿主上下文。graph-owner / queued / marker 身份只能用于校验和恢复,**绝不能"偷偷"变成当前聊天**。 -这正是"未进入聊天"那类 bug 的根:旧代码用一个"优先级抽奖"函数接受十几个竞争来源,结果某个非活动身份被当成了活动聊天。现在 `resolveActiveIdentity` / `resolveGraphOwnerIdentity` / `resolveQueuedIdentity` / `resolveMarkerIdentity` 是分开的入口,不给聚合入口诱导污染。 +这正是"未进入聊天"那类 bug 的根:旧代码用一个"优先级抽奖"函数接受十几个竞争来源,结果某个非活动身份被当成了活动聊天。现在 active/current 有专门入口,恢复/兜底身份走单独的 fallback 聚合,不给"非活动身份污染活动身份"留口子。 > 身份是每次操作解析一次、显式传递的,不是从全局随用随取。 @@ -35,7 +43,7 @@ 持久化确认逻辑收敛在 `sync/persistence-reducer.js`,是**纯函数**:无 IO、无图谱变更、无 UI 副作用。 -它把"这批记忆到底存好了没"变成 `(身份, 存储层 tier, 版本 revision, 证据)` 的纯函数。核心不变量写死进 reducer,而不是到处打补丁: +它把"这批记忆到底存好了没"变成关于 `(身份, 存储层 tier, 版本 revision, 证据)` 的纯计算。核心不变量: ``` 已确认版本 >= 排队版本 @@ -44,6 +52,8 @@ ⟹ pendingPersist 必须为 false ``` +> 实现说明:这条不变量**不是**某个单一 reducer 事件全包的。`reducePersistenceStatePatch()` 处理通用 `ACCEPTED` / `QUEUED` 事件;`buildAcceptedPersistenceStatePatch()` 在规范 tier 被接受时清 `pendingPersist`(但它本身不查排队版本/身份);"陈旧 pending 自动清除"的规划逻辑在纯函数 `planAcceptedPendingClear()`(`sync/legacy-persistence-repair.js`,经 reducer re-export);**身份等值校验在调用方**(`index.js` 接受路径)完成后才调用应用函数。所以这条不变量 = 纯规划器 + 调用方身份门禁的合成,而非单个事件转换。 + **派生不变量:** > recovery-only tier(`shadow` / `metadata-full` / `runtime-recovery` 等)永远不能推进确认状态。它们只用于灾难恢复,不能被当作"数据已安全落地"的证据。 diff --git a/docs/architecture/server-integration.md b/docs/architecture/server-integration.md index 958afb0..c065354 100644 --- a/docs/architecture/server-integration.md +++ b/docs/architecture/server-integration.md @@ -5,7 +5,7 @@ ST-BME 可以独立运行(纯前端),也可以在检测到 st-doa/Authorit ## 核心原则 1. **BME 独立运行是第一公民。** 没有 st-doa 时,BME 必须完整可用,优雅降级,不报错。纯前端模式显示"纯前端模式",不是错误状态。 -2. **第三方自定义 URL embedding 是主流路径。** OpenAI 兼容 `/v1/embeddings`、one-api、new-api、litellm、vLLM、llama.cpp、Ollama 桥接等,都是一等公民。embedding 默认在客户端执行(`embeddingExecution` 默认 `client`)。 +2. **第三方自定义 URL embedding 是主流路径。** OpenAI 兼容 `/v1/embeddings`、one-api、new-api、litellm、vLLM、llama.cpp、Ollama 桥接等,都是一等公民。embedding 默认在客户端执行(`embeddingTransportMode` 默认 `"direct"`,直连第三方 URL)。 3. **集成是自动的,不需要用户配置。** 能力探测驱动的升级/降级,没有面向用户的"服务器模式"开关。既要优雅降级(无 st-doa),也要优雅升级(有 st-doa),全程零用户干预。 > Authority 是**增强层**,不是硬依赖。它不生成 embedding——只通过 Trivium 存储/搜索向量,并要求调用方提供向量。 diff --git a/docs/architecture/storage-and-formats.md b/docs/architecture/storage-and-formats.md index ac48b5b..39a4882 100644 --- a/docs/architecture/storage-and-formats.md +++ b/docs/architecture/storage-and-formats.md @@ -39,16 +39,18 @@ ST-BME 的图谱数据可能存在多种位置,取决于宿主环境和服务 这是保证"ST-BME 以后不需要再做 v4/v5 大迁移"的核心机制。它**不是**一个信封框架或预留字段,而是一条解析纪律: -### 1. 宽容解析(保留未知字段) +### 1. 宽容解析(保留未知嵌套字段) -> 读取方遇到不认识的字段,必须保留、不报错、不丢弃。 +> 读取方遇到不认识的**嵌套**字段(在 meta / state / 各记录里),必须保留、不报错、不丢弃。 具体现状: - 节点 / 边 / tombstone 记录:整对象克隆,未知字段天然保留。 - `meta`:展开保留,已有 `meta.schemaVersion`。 -- 顶层:冻结为六键 + 保留未知顶层字段。 +- **顶层:冻结为六键,未知顶层键会被丢弃**——顶层是契约层,新增顶层键是契约违规(`normalizeGraphSnapshotShape` 只返回这六个键)。 -原理:如果读取代码遇到不认识的字段就崩或就丢,那么**任何**格式改动都会逼出一次迁移;如果遇到不认识的字段就忽略并原样保留,那么以后所有改动都是**加法**,老版本读新数据照样不崩,永远不需要换命名空间、不需要大搬家。这是 protobuf 这类协议几十年验证过的做法。 +> **所以演进规则是:永远不要新增顶层键;新字段一律放进 `meta` / `state` / 记录对象里,那里才保证 round-trip。** + +原理:如果读取代码遇到不认识的嵌套字段就崩或就丢,那么**任何**字段改动都会逼出一次迁移;如果遇到不认识的嵌套字段就忽略并原样保留,那么以后所有改动都是**加法**,老版本读新数据照样不崩,永远不需要换命名空间、不需要大搬家。这是 protobuf 这类协议几十年验证过的做法。冻结顶层 + 演进只走嵌套,是这套纪律的边界。 ### 2. 只加不减 diff --git a/docs/contributing/conventions.md b/docs/contributing/conventions.md index 7504d99..e8dae70 100644 --- a/docs/contributing/conventions.md +++ b/docs/contributing/conventions.md @@ -51,7 +51,7 @@ const graphPersistenceState = new Proxy({}, { 这些在 [`../architecture/control-plane.md`](../architecture/control-plane.md) 有完整说明,这里列出最易被无意破坏的: 1. **active identity 只来自宿主上下文**;graph-owner/queued/marker 身份只用于校验/恢复,绝不变成当前聊天。 -2. **`已确认版本 >= 排队版本 && 同身份 && 规范 tier ⟹ pendingPersist === false`**——写在 reducer 里,不要在外面打补丁绕过。 +2. **`已确认版本 >= 排队版本 && 同身份 && 规范 tier ⟹ pendingPersist === false`**——由纯规划器(`planAcceptedPendingClear` / `buildAcceptedPersistenceStatePatch`)加调用方身份门禁合成,不要在外面另起一套绕过它。 3. **recovery-only tier(shadow/metadata)永远不能推进确认状态。** 4. **reroll 复用召回时,被 reroll 助手楼层的图谱回滚必须保留**——召回复用和图谱回滚是两件事。 5. **向量空间不兼容时返回空向量结果回退图/词法召回**,绝不静默复用旧向量。