From ac663fca16b58c43ff1753c48c96d732f501bd09 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 11:12:45 +0800 Subject: [PATCH] feat: protocol base lib + rule-based parser MVP + 31 tests --- README.md | 104 ++++-- docs/architecture-overview.md | 225 +++++++------ docs/llm-subnet.md | 76 +++-- docs/logic-trace-subnet.md | 87 ++--- docs/memory-subnet.md | 88 ++++-- docs/routing-compression-subnet.md | 18 +- docs/tensor-parser-network.md | 297 +++++++++++------- requirements.txt | 1 + src/parsers/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 225 bytes .../__pycache__/rule_based.cpython-312.pyc | Bin 0 -> 2825 bytes src/parsers/rule_based.py | 46 +++ src/protocol/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 449 bytes .../__pycache__/codec.cpython-312.pyc | Bin 0 -> 6901 bytes .../__pycache__/registry.cpython-312.pyc | Bin 0 -> 3994 bytes .../__pycache__/tensor_bus.cpython-312.pyc | Bin 0 -> 2319 bytes src/protocol/codec.py | 150 +++++++++ src/protocol/registry.py | 83 +++++ src/protocol/tensor_bus.py | 37 +++ .../__pycache__/test_protocol.cpython-312.pyc | Bin 0 -> 16514 bytes tests/test_protocol.py | 211 +++++++++++++ 22 files changed, 1066 insertions(+), 373 deletions(-) create mode 100644 requirements.txt create mode 100644 src/parsers/__init__.py create mode 100644 src/parsers/__pycache__/__init__.cpython-312.pyc create mode 100644 src/parsers/__pycache__/rule_based.cpython-312.pyc create mode 100644 src/parsers/rule_based.py create mode 100644 src/protocol/__init__.py create mode 100644 src/protocol/__pycache__/__init__.cpython-312.pyc create mode 100644 src/protocol/__pycache__/codec.cpython-312.pyc create mode 100644 src/protocol/__pycache__/registry.cpython-312.pyc create mode 100644 src/protocol/__pycache__/tensor_bus.cpython-312.pyc create mode 100644 src/protocol/codec.py create mode 100644 src/protocol/registry.py create mode 100644 src/protocol/tensor_bus.py create mode 100644 tests/__pycache__/test_protocol.cpython-312.pyc create mode 100644 tests/test_protocol.py diff --git a/README.md b/README.md index 6cdac68..d4a4550 100644 --- a/README.md +++ b/README.md @@ -80,53 +80,93 @@ graph TB 所有子网间不传递自然语言,而是通过**统一张量协议**通信。该协议基于**适配器模式(Adapter Pattern)**和**门面模式(Facade Pattern)**设计,旨在解决模块异构性与通信效率问题。 -### 核心设计模式 +### 统一通信张量协议 (Unified Tensor Protocol) -#### 适配器模式(编解码层) +所有子网间通过**纯数值张量**通信,不传递自然语言、JSON 或任何非张量结构。协议基于**适配器模式**设计。 -每个子网通过一个**编解码层(Codec Layer)**与外部通信,负责将子网内部的私有数据格式(如 PyTorch Tensor、自定义结构等)转换为统一的标准张量格式: +#### 协议定义 +统一通信张量由两部分组成: + +**1. 元数据(独立于张量,由底层总线传递)** +| 字段 | 说明 | +|------|------| +| `source` | 发出模型 ID | +| `target` | 目标模型 ID | +| `reply_req` | 是否需要回复 (true/false) | + +**2. 张量数据(纯数值张量,ndarray)** ``` -┌─────────────┐ ┌───────────────────┐ ┌─────────────┐ -│ 子网内部 │ ──→ │ 编码器 (Encoder) │ ──→ │ 标准张量 │ -│ 私有格式 │ │ 适配器: 内部→标准 │ │ (统一格式) │ -└─────────────┘ └───────────────────┘ └─────────────┘ - │ - ▼ -┌─────────────┐ ┌───────────────────┐ ┌─────────────┐ -│ 子网内部 │ ←── │ 解码器 (Decoder) │ ←── │ 标准张量 │ -│ 私有格式 │ │ 适配器: 标准→内部 │ │ (统一格式) │ -└─────────────┘ └───────────────────┘ └─────────────┘ +┌──────────────────────────────────────────────────┐ +│ Unified Communication Tensor │ +├──────────────────────────────────────────────────┤ +│ [fmt_id] [parser_id] [padding...] [payload...] │ +│ └───────── 自描述前缀 ──────────┘ └──真实数据──┘│ +└──────────────────────────────────────────────────┘ ``` -**作用**:屏蔽不同子网/框架的实现差异。新的子网只要实现编解码适配器,就能接入系统,无需修改调度 Core 或其他已有模块。 - -#### 门面模式(统一张量接口) - -协议对外暴露统一的、简单的接口,隐藏底层复杂的通信过程: +其中 `fmt_id` 和 `parser_id` 为**协议注册表**中预定义的标量标识: +- `fmt_id`:标识数据格式(如 0=ref_tensor, 1=memory_result, 2=reasoning_path) +- `parser_id`:标识应使用哪个解析网络(如 0=ref_parser, 1=memory_parser) ```python -# 统一接口门面 (Facade) -interface TensorBus: - def send(tensor: StandardTensor, target: str) -> None - def recv(source: str) -> StandardTensor +# 协议注册表(系统设计时预定义) +PROTOCOL_REGISTRY = { + "fmt": {0: "ref_tensor", 1: "memory_result", 2: "reasoning_path", ...}, + "parser": {0: "ref_parser", 1: "memory_parser", 2: "reasoning_parser", ...}, +} ``` -调度 Core 和各子网无需关心数据是通过共享内存、NPU 显存直传还是 TCP/IP 传输,只需与统一的张量门面交互。 +#### 编解码层(适配器模式) -### 标准张量格式 +每个子网实现编解码适配器,在子系统内部格式与统一通信张量之间转换: ``` -┌─────────────────────────────────────────────┐ -│ 标准张量 (Standard Tensor) │ -├─────────────────────────────────────────────┤ -│ Header: 源子网 · 目标子网 · 类型 · 时间戳 │ -│ Metadata: 维度 · 精度 · 压缩率 · 置信度 │ -│ Payload: 实际数据(向量/结构化数据) │ -│ Signature: SHA-256 校验 │ -└─────────────────────────────────────────────┘ +┌─────────────┐ ┌─────────────────────┐ ┌────────────────┐ +│ 子网内部 │ ──→ │ 编码适配器 │ ──→ │ 统一通信张量 │ +│ 内部张量 │ │ 拼接 fmt_id+parser_id│ │ [fmt][prs][数据]│ +└─────────────┘ └─────────────────────┘ └────────────────┘ + │ + ▼ +┌─────────────┐ ┌─────────────────────┐ ┌────────────────┐ +│ 子网内部 │ ←── │ 解码适配器 │ ←── │ 统一通信张量 │ +│ 内部张量 │ │ 解析 parser_id → 解码 │ │ [fmt][prs][数据]│ +└─────────────┘ └─────────────────────┘ └────────────────┘ ``` +**编解码器接口**: +```python +class CodecAdapter: + """每个子网实现此接口""" + def encode(self, internal_tensor: Tensor) -> Tensor: + # 1. 查协议注册表获取 fmt_id + # 2. 拼接 [fmt_id, parser_id, ...padding..., payload] + pass + + def decode(self, comm_tensor: Tensor) -> Tensor: + # 1. 读取前缀中的 parser_id + # 2. 路由到对应解码器 + # 3. 剥离前缀,返回内部格式 + pass +``` + +#### 门面模式(通信总线) + +底层通信机制(共享内存/NPU 显存直传/TCP/IP)对子网透明: + +```python +class TensorBus: + def send(tensor: Tensor, source: str, target: str, reply_req: bool) -> None + def recv(source: str) -> tuple[Tensor, str, str, bool] # tensor, source, target, reply_req + def register(subnet_id: str, codec: CodecAdapter) -> None +``` + +#### 核心特性 +- **纯粹张量**:无 JSON,无嵌套结构,全部为数值数据 +- **自描述前缀**:fmt_id + parser_id 编码在张量头部 +- **协议预定义**:所有 fmt_id/parser_id 系统设计时查表确定 +- **硬件亲和**:张量是 NPU/GPU 原生单元,可实现零拷贝 + ### 核心优势 #### 动态并网与即插即用 diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md index 87d1732..9a99849 100644 --- a/docs/architecture-overview.md +++ b/docs/architecture-overview.md @@ -55,109 +55,138 @@ NextAgent 采用**认知解耦架构(Cognitive Decoupled Architecture)**, ## 统一张量协议(Unified Tensor Protocol) -这是 NextAgent 的核心创新——子网间通过标准化的张量格式通信,不传递自然语言。该协议基于**适配器模式(Adapter Pattern)**和**门面模式(Facade Pattern)**设计,旨在解决多子网架构中的模块异构性与通信效率问题。 +NextAgent 的核心——子网间通过**纯数值张量**通信,不传递自然语言、JSON 或任何非张量结构。 -### 核心设计模式 +### 协议定义 -#### 适配器模式(编解码层) +统一通信张量由**元数据**(独立于张量,由底层总线携带)+ **张量数据**(纯数值)两部分组成。 -每个子网通过一个编解码层(Codec Layer)与外部通信。该层充当**适配器**,负责将子网内部的私有数据格式(如 PyTorch Tensor、MindSpore Tensor 或自定义结构)转换为统一的标准张量格式。 +#### 元数据(总线层传递,非张量的一部分) -**作用**:屏蔽不同子网/框架的实现差异。新的子网只要实现对应的编解码器(Adapter),就能接入系统,无需修改调度 Core 或其他已有模块的代码。 +| 字段 | 说明 | +|------|------| +| `source` | 发出模型 ID (如 `routing`, `memory`, `logic_trace`, `llm`, `core`) | +| `target` | 目标模型 ID | +| `reply_req` | 是否需要回复 (true/false) | -#### 门面模式(统一张量接口) +#### 张量数据(纯数值 ndarray) -协议对外暴露统一的、简单的接口,隐藏底层复杂的通信过程: - -```python -class TensorBus: - """统一张量总线 - 门面模式""" - def send(tensor: StandardTensor, target: str) -> None: - # 隐藏了底层实现(共享内存/NPU显存直传/TCP/IP) - pass - - def recv(source: str) -> StandardTensor: - pass - - def register(subnet_id: str, codec: CodecAdapter) -> None: - """动态注册子网 - 运行时热插拔""" - pass +``` +┌────────────────────────────────────────────────────┐ +│ Unified Communication Tensor │ +├────────────────────────────────────────────────────┤ +│ [fmt_id] [parser_id] [padding...] [payload...] │ +│ └───────── 自描述前缀 (前缀张量) ──────┘ └──数据──┘│ +└────────────────────────────────────────────────────┘ ``` -调度 Core 和各子网只需与统一的张量门面交互,无需关心底层通信细节。 +张量本身就是一个纯粹的数值阵列(如 PyTorch Tensor / NumPy ndarray)。其前缀维度编码了协议信息: +- `dim[0]` = **fmt_id**:数据格式标识(查协议注册表) +- `dim[1]` = **parser_id**:应使用的解析网络标识 +- `dim[2..k]`:保留 / padding +- `dim[k+1..]`:实际载荷数据 -### 标准张量格式 +#### 协议注册表(系统设计时预定义) -```json -{ - "header": { - "source": "routing_compression | memory | logic_trace | llm | core", - "target": "routing_compression | memory | logic_trace | llm | core", - "tensor_type": "ref_tensor | memory_query | memory_result | reasoning_path | expression_verify | memory_update | control", - "timestamp": 1717200000, - "trace_id": "uuid-会话追踪ID" - }, - "metadata": { - "dimensions": [768, 128], // 张量维度 - "precision": "float32", // 精度类型 - "compression_ratio": 0.85, // 压缩率 - "confidence": 0.92, // 置信度 - "encoding_model": "transformer_v3" // 编码模型标识 - }, - "payload": { - "data": [0.12, -0.34, ...], // 实际数据(向量/结构化数据) - "indices": [1024, 2048, ...], // 冷区索引(可选) - "metadata_map": {} // 附加元数据 - }, - "signature": "sha256-checksum" // 完整性校验 +```python +PROTOCOL_REGISTRY = { + "fmt": { + 0: "ref_tensor", # 路由引用张量 + 1: "memory_result", # 记忆检索结果 + 2: "memory_query", # 记忆查询 + 3: "memory_update", # 新信息更新 + 4: "reasoning_path", # 推理路径 + 5: "expression_verify", # 表达验证 + 6: "control", # 控制信号 + }, + "parser": { + 0: "ref_parser", + 1: "memory_result_parser", + 2: "memory_query_parser", + 3: "memory_update_parser", + 4: "reasoning_parser", + 5: "expression_parser", + 6: "control_parser", + }, } ``` -### 张量类型 +### 编解码机制(适配器模式) -| 张量类型 | 说明 | 发送方 → 接收方 | -|----------|------|----------------| -| `ref_tensor` | 引用张量,压缩后的多模态特征 | 路由子网 → Core | -| `memory_query` | 记忆查询张量 | Core → 记忆子网 | -| `memory_result` | 记忆检索结果张量 | 记忆子网 → Core | -| `reasoning_path` | 推理路径与验证结果张量 | 逻辑子网 → Core | -| `expression_verify` | 表达验证张量(含语义编码、置信度) | LLM 子网 → Core | -| `memory_update` | 记忆更新参数包张量 | Core → 记忆子网 | -| `control` | 控制信号张量(启停、参数调整) | Core → 任意子网 | - -### 编/解码机制 - -每个子网都配备适配器式的编解码层: +每个子网实现编解码适配器,在其内部张量格式与统一通信张量之间转换: ``` -┌─────────────┐ ┌───────────────────┐ ┌─────────────┐ -│ 子网内部 │ ──→ │ 编码适配器 │ ──→ │ 标准张量 │ -│ 内部格式 │ │ Adapter: 内部→标准 │ │ (统一格式) │ -└─────────────┘ └───────────────────┘ └─────────────┘ - │ - ▼ -┌─────────────┐ ┌───────────────────┐ ┌─────────────┐ -│ 子网内部 │ ←── │ 解码适配器 │ ←── │ 标准张量 │ -│ 内部格式 │ │ Adapter: 标准→内部 │ │ (统一格式) │ -└─────────────┘ └───────────────────┘ └─────────────┘ +┌─────────────┐ ┌─────────────────────┐ ┌──────────────────┐ +│ 子网内部 │ ──→ │ 编码适配器 │ ──→ │ 统一通信张量 │ +│ 内部张量 │ │ 查注册表 → │ │ [fmt][prs][数据] │ +│ │ │ 拼接 fmt+parser 前缀 │ │ │ +└─────────────┘ └─────────────────────┘ └──────────────────┘ + │ + ▼ +┌─────────────┐ ┌─────────────────────┐ ┌──────────────────┐ +│ 子网内部 │ ←── │ 解码适配器 │ ←── │ 统一通信张量 │ +│ 内部张量 │ │ 解析 prefix → │ │ [fmt][prs][数据] │ +│ │ │ 路由对应解码器 → 剥离 │ │ │ +└─────────────┘ └─────────────────────┘ └──────────────────┘ +``` + +#### 编解码接口 + +```python +class CodecAdapter: + """每个子网实现此接口,注册到 TensorBus""" + + def encode(self, internal_tensor: Tensor, + target: str, reply_req: bool) -> Tensor: + """内部格式 → 统一通信张量 + + 1. 查协议注册表,获取内部格式对应的 fmt_id + 2. 查协议注册表,获取本子网的 parser_id + 3. 在数据前拼接 [fmt_id, parser_id, padding..., data...] + """ + pass + + def decode(self, comm_tensor: Tensor) -> Tensor: + """统一通信张量 → 内部格式 + + 1. 读取 comm_tensor[0] 获取 parser_id + 2. 根据 parser_id 路由到对应解码器 + 3. 剥离前缀,返回内部张量格式 + """ + pass + +class TensorBus: + """通信总线(门面模式),屏蔽底层实现""" + def send(self, tensor: Tensor, source: str, target: str, reply_req: bool) -> None: ... + def recv(self, source: str) -> tuple[Tensor, str, str, bool]: ... + def register(self, subnet_id: str, codec: CodecAdapter) -> None: ... ``` ### 核心优势 -#### 动态并网与即插即用 -- 新的子网只需实现编解码适配器,通过 `TensorBus.register()` 向 Core 注册即可接入 -- 支持**运行时热插拔**:不中断系统运行即可添加/移除子网 -- 各子网独立迭代算法,只要编解码接口不变,不影响系统稳定性 -- 支持运行时根据需求训练新的子网并动态接入 +| 优势 | 说明 | +|------|------| +| 纯粹张量 | 无 JSON/嵌套结构,全部为数值数据,NPU/GPU 原生处理 | +| 自描述 | fmt_id + parser_id 编码在张量头部,接收方即知如何解码 | +| 即插即用 | 新子网只需实现编解码适配器,通过 `TensorBus.register()` 注册 | +| 零拷贝 | 张量可直接在设备内存池流转,无需 CPU 搬运 | +| 高保真 | 无损传递高维特征图、注意力权重,不降维 | +| 硬件亲和 | 张量是 NPU/GPU 原生计算单元,无需格式转换 | -#### 零拷贝(Zero-Copy) -支持分布式能力的系统上,张量可通过**内核旁路技术**直接在设备间传递内存地址,避免将数据从 NPU/GPU 搬回 CPU 再打包的昂贵序列化开销。 +### 通信示例 -#### 硬件亲和性 -张量是 NPU/GPU 的**原生计算单元**。协议允许数据在设备内存池中直接流转,无需经过格式转换,大幅降低延迟和 CPU 占用。 +``` +路由子网处理完用户输入后,调用编码适配器: + 内部张量 (形状 [768]) → 编码器 + → 查表: 路由输出格式 → fmt_id=0, parser_id=0 + → 拼接: [0, 0, 0, ..., 768维数据] → 形状 [770] + → TensorBus.send(张量, source="routing", target="core", reply_req=True) -#### 高数据保真度 -相比于文本或 JSON 协议,张量能**无损传递高维特征图、注意力权重矩阵**等复杂数值结构,确保传递的是完整的 +Core 收到后,调用解码适配器: + → 读取 tensor[0]=0 → 格式是 ref_tensor + → 读取 tensor[1]=0 → 用 ref_parser 解码 + → 剥离前 2 维 → 得到 768 维内部张量 +``` ## 调度 Core(前额叶皮层) @@ -282,12 +311,14 @@ function heuristic_process(raw_input): elif next_action.type == "logical_reason": reasoning_tensor = logic_subnet.reason(state.context) state.context["reasoning"] = reasoning_tensor - state.confidence = reasoning_tensor.metadata.confidence + # 从张量前缀解析器解码获取置信度 + state.confidence = decode_confidence_from_tensor(reasoning_tensor) elif next_action.type == "llm_express": verify_tensor = llm_subnet.express(state.context) state.context["verification"] = verify_tensor - if verify_tensor.metadata.confidence > THRESHOLD: + # 解析表达验证张量获取通过判断 + if decode_verified_from_tensor(verify_tensor): state.verified = True # 3. 将本轮新信息 + 结果交给记忆子网(自治更新) @@ -320,10 +351,10 @@ LLM 子网的输出有两个去向,这是核心设计之一: │ → 自然语言文本 │ ──→ 直接输出给用户 │ │ ├── 通道2 ──────────────┤ - │ → 表达验证张量 │ ──→ 返回给 Core - │ (包含:语义编码、 │ Core 分析验证张量 - │ 置信度、情感标记、 │ 判断 LLM 是否正确表达 - │ 表达结构元数据) │ 而非获取自然语言内容 + │ → 表达验证张量(纯张量) │ ──→ 返回给 Core + │ 格式约定见协议注册表 │ Core 的验证器解析此张量 + │ fmt_id=5: 含语义编码/ │ 判断 LLM 是否正确表达 + │ 意图对齐度/实体保留等 │ 而非获取自然语言内容 └──────────────────────┘ ``` @@ -347,8 +378,10 @@ LLM 子网的输出有两个去向,这是核心设计之一: │ (不包含任何记忆处理逻辑) │ │ │ │ Core 只做两件事: │ -│ 1. 封装查询张量 → 传给记忆子网 │ -│ 2. 封装新信息张量 → 传给记忆子网 │ +│ 1. 生成查询张量 → 传给记忆子网 │ +│ (fmt_id=2, 含编码后的查询) │ +│ 2. 生成新信息张量 → 传给记忆子网 │ +│ (fmt_id=3, 含编码后的更新数据)│ └──────────┬───────────────────┘ │ ▼ @@ -370,18 +403,18 @@ LLM 子网的输出有两个去向,这是核心设计之一: ``` Core 需要查询记忆时: │ - ├── 封装查询张量(含意图、种子实体、上下文) - ├── 封装基础张量(含当前标签、路由结果) + ├── 将当前上下文编码为内部查询张量 + ├── 调用编码适配器: 拼接 fmt_id=2 → 统一通信张量 + ├── TensorBus.send(张量, "core", "memory", reply_req=True) │ ▼ -记忆子网接收后自行检索 - │ - ├── 图数据库 Cypher 查询 - ├── 向量数据库语义检索 - ├── 自行关联推理 +记忆子网接收后: + ├── 解码适配器读取 prefix → fmt_id=2 (memory_query) + ├── 自行解析查询意图 → 图DB/向量DB/文本检索 + ├── 将结果编码: 拼接 fmt_id=1 (memory_result) │ ▼ -返回包含记忆的张量给 Core +返回记忆结果张量给 Core ``` ## 数据流完整链路(用户视角) diff --git a/docs/llm-subnet.md b/docs/llm-subnet.md index 613b4a4..df6cd42 100644 --- a/docs/llm-subnet.md +++ b/docs/llm-subnet.md @@ -75,42 +75,52 @@ NextAgent LLM 子网: | **输入** | 结构化推理路径张量(来自 Core,经过张量解码层转换) | | **输出1** | 自然语言文本(直接发送给用户) | | **输出2** | 表达验证张量(发送给 Core) | -| **输出2格式** | 标准张量协议,tensor_type=`expression_verify` | +| **输出2格式** | 统一通信张量,fmt_id=5 (expression_verify) | ### 表达验证张量格式 -```json -{ - "header": { - "source": "llm", - "target": "core", - "tensor_type": "expression_verify", - "trace_id": "trace-uuid", - "timestamp": 1717200000 - }, - "metadata": { - "confidence": 0.94, - "semantic_encoding_dim": 768, - "expression_structure": "sentence+paragraph", - "sentiment": "positive", - "language": "zh-CN", - "token_count": 128, - "verification_status": "verified" - }, - "payload": { - "semantic_encoding": [0.23, -0.56, ...], // 输出文本的语义向量 - "intention_alignment": 0.96, // 与输入推理路径的意图对齐度 - "factual_consistency": 0.92, // 与输入事实的一致性 - "sentence_embeddings": [ // 每句的编码(可选) - {"index": 0, "embedding": [...]}, - {"index": 1, "embedding": [...]} - ], - "emotion_vector": [0.1, 0.8, 0.3, ...], // 情感多维度向量 - "key_entities_preserved": ["天气", "北京", "今天"], // 关键实体保留 - "omission_detected": false // 是否有遗漏信息 - }, - "signature": "sha256-checksum" -} +遵循统一通信张量协议,**无 JSON 结构**,全部为纯数值张量: + +``` +统一通信张量 (纯数值) + prefix: [fmt_id=5, parser_id=5] + payload: + [semantic_encoding (768)] # 输出文本的语义向量 + [intention_alignment (1)] # 与输入推理路径的意图对齐度 (0~1) + [factual_consistency (1)] # 与输入事实的一致性 (0~1) + [emotion_vector (n_dims)] # 情感向量 + [entity_preserved_mask (n_entities)] # 关键实体保留掩码 [0/1] + [overall_score (1)] # 综合验证得分 (0~1) +``` + +**编码示例**: + +```python +def encode_expression_verify(output_text: str, + reasoning_path: Tensor) -> Tensor: + """LLM 自然语言输出 → 表达验证张量 (通道2) + + 通道1: 自然语言文本直接输出给用户 + 通道2: 此函数编码验证特征为纯张量返回给 Core + """ + sem_enc = sentence_encoder(output_text) # [768] + alignment = cosine_sim(sem_enc, path_embedding) # [1] + consistency = compute_factual_consistency( + output_text, reasoning_path) # [1] + emotion = emotion_analyzer(output_text) # [n] + entity_mask = check_entities_preserved(output_text) # [n_entities] + overall = compute_overall_score([alignment, consistency, + entity_mask.mean()]) # [1] + + payload = concat(sem_enc, alignment, consistency, + emotion, entity_mask, overall) + return codec.encode(fmt_id=5, parser_id=5, data=payload) + +# Core 收到后解析 +payload = codec.decode(comm_tensor) # 剥离 fmt_id=5 +overall = payload[-1] +if overall > THRESHOLD: # 验证通过 + user_output = channel_1_text # 允许输出 ``` ## 子网内部架构 diff --git a/docs/logic-trace-subnet.md b/docs/logic-trace-subnet.md index d477e06..a2f19ec 100644 --- a/docs/logic-trace-subnet.md +++ b/docs/logic-trace-subnet.md @@ -44,64 +44,35 @@ ### 推理路径张量格式 -```json -{ - "header": { - "source": "logic_trace", - "target": "core", - "tensor_type": "reasoning_path", - "trace_id": "trace-uuid", - "timestamp": 1717200000 - }, - "payload": { - "reasoning_steps": [ - { - "step_id": 1, - "type": "deduction", - "input": "用户想要查询天气", - "operation": "提取地点和时间信息", - "output": "地点: 北京, 时间: 今天", - "confidence": 0.95, - "source_evidence": ["memory:偏好_北京天气"] - }, - { - "step_id": 2, - "type": "retrieval", - "input": "需要天气数据", - "operation": "查询记忆子网获取北京天气偏好", - "output": "用户关注空气质量>温度", - "confidence": 0.88, - "source_evidence": ["graph:用户_北京_HAS_PERSONA_关注空气质量"] - }, - { - "step_id": 3, - "type": "verification", - "input": "步骤1-2结果", - "operation": "交叉验证", - "output": "结论一致", - "confidence": 0.92, - "source_evidence": ["cross_validation"] - } - ], - "final_confidence": 0.92, - "alternative_paths": [ - { - "path_id": "alt_1", - "conclusion": "用户可能不在北京(上次对话提到出差)", - "confidence": 0.45, - "rejection_reason": "信息过时(3天前的记录)" - } - ], - "verified": true, - "error_log": [] - }, - "metadata": { - "step_count": 3, - "mcts_iterations": 50, - "search_depth": 5, - "verification_status": "passed" - } -} +遵循统一通信张量协议,**无 JSON 结构**,全部为纯数值张量: + +``` +统一通信张量 (纯数值) + prefix: [fmt_id=4, parser_id=4] + payload: + [n_steps] # 推理步数 + [step_embeddings...] # 每步的语义嵌入 [n_steps, 768] + [step_confidences...] # 每步置信度 [n_steps] + [search_depth, mcts_iters, verified] # 元信息 [3] +``` + +**编码示例(内部推理路径 → 统一张量)**: + +```python +def encode_reasoning_path(steps: list[Step], + depth: int, iters: int) -> Tensor: + """内部推理路径 → 统一通信张量""" + n = len(steps) + step_vecs = [encode_step_io(s) for s in steps] # 每步编码 + confs = [s.confidence for s in steps] + + payload = concat( + tensor([n]), # 步数 + stack(step_vecs).flatten(), # 步嵌入 + tensor(confs), # 置信度 + tensor([depth, iters, 1.0 if verified]) # 元信息 + ) + return codec.encode(fmt_id=4, parser_id=4, data=payload) ``` ## 子网内部架构 diff --git a/docs/memory-subnet.md b/docs/memory-subnet.md index 3b54558..fd2241e 100644 --- a/docs/memory-subnet.md +++ b/docs/memory-subnet.md @@ -147,9 +147,9 @@ LIMIT 10 | 项目 | 说明 | |------|------| -| **输入1** | 查询张量(来自 Core,含查询意图、种子实体、上下文) | -| **输入2** | 新信息张量(来自 Core,含本轮标签和子网结果;**不含具体写入指令**) | -| **输出** | `memory_result` 张量(包含检索到的实体/关系/向量结果) | +| **输入1** | 查询张量,fmt_id=2,来自 Core(编码后的查询意图、种子实体) | +| **输入2** | 新信息张量,fmt_id=3,来自 Core(编码后的本轮更新数据;**不含具体写入指令**) | +| **输出** | 记忆结果张量,fmt_id=1,编码检索到的实体/关系向量 | | **自治** | 记忆子网自行决定如何写入、更新、删除、修改记忆 | ## 训练目标 @@ -301,37 +301,63 @@ class Neo4jGraph: ## 新信息张量格式 -Core 传递给记忆子网的**新信息张量**(Core 不发送 +Core 传递给记忆子网的**新信息张量**遵循统一通信张量协议: -```json -{ - "header": { - "source": "core", - "target": "memory", - "tensor_type": "memory_update", - "trace_id": "trace-uuid", - "timestamp": 1717200000 - }, - "payload": { +### 协议定义 + +``` +格式: 统一通信张量 (纯数值张量) + prefix[0] = fmt_id = 3 (memory_update) + prefix[1] = parser_id = 3 (memory_update_parser) + payload: 编码后的记忆更新数据 + 实体编码: [entity_ids..., entity_embeddings...] + 关系编码: [relation_ids..., relation_embeddings...] + 会话标识: [session_tag, turn_tag, ...] +``` + +实体、关系、会话信息通过子网内部的编解码适配器转为张量表示。 + +### 记忆子网内部格式(私有) + +记忆子网内部维护以下私有数据结构,但**对外通信时始终通过统一协议编解码为纯张量**: + +```python +# 内部数据模型(子网私有,不对外暴露) +@Entity +default_graph = { "entities": [ - {"name": "用户_张三", "type": "PersonaNode", "attributes": {"preference": "摇滚音乐"}}, - {"name": "项目_NextAgent", "type": "EntityNode", "attributes": {}} + {"name": "用户_张三", "type": "PersonaNode", "preference": "摇滚音乐"}, + {"name": "项目_NextAgent", "type": "EntityNode"} ], "relations": [ - {"subject": "用户_张三", "relation": "HAS_PERSONA", "object": "喜欢_摇滚", "confidence": 0.95}, - {"subject": "用户_张三", "relation": "RELATES_TO", "object": "项目_NextAgent", "confidence": 0.9} - ], - "session_id": "session-20260601-xxxx", - "turn_id": 42, - "metadata": { - "source_subnets": ["routing", "logic_trace", "core"], - "aggregation_strategy": "merge" - } - }, - "metadata": { - "entity_count": 3, - "relation_count": 2, - "upsert_mode": "merge" - } + {"subject": "用户_张三", "relation": "HAS_PERSONA", "object": "喜欢_摇滚", "confidence": 0.95}, + {"subject": "用户_张三", "relation": "RELATES_TO", "object": "项目_NextAgent", "confidence": 0.9} + ] } + +# 编码适配器将内部数据 → 统一通信张量 +# 解码适配器将统一通信张量 → 内部查询结构 +def encode_memory_update(entities, relations, session_id, turn_id) -> Tensor: + """内部格式 → 统一通信张量 + + 1. 将实体名/关系名编码为 embedding 向量 + 2. 拼接会话标识和时间戳 + 3. 编码适配器: 查表 fmt_id=3, 拼接前缀 + """ + entity_embeds = embed_entity_names(entities) # [n_entities, 768] + relation_embeds = encode_relations(relations) # [n_relations, 768] + meta = tensor([session_hash, turn_id]) # [2] + return codec.encode(fmt_id=3, parser_id=3, + data=concat(entity_embeds, relation_embeds, meta)) + +def decode_memory_query(comm_tensor: Tensor) -> QueryIntent: + """统一通信张量 → 内部查询意图 + + 1. codec.decode(comm_tensor) 剥离前缀 + 2. 将 payload 张量解码为查询条件 + 3. 返回: 图DB检索参数 / 向量检索参数 + """ + payload = codec.decode(comm_tensor) # 剥离 fmt_id=2 + query_embed = payload[:768] + return self.query_embed_to_params(query_embed) ``` diff --git a/docs/routing-compression-subnet.md b/docs/routing-compression-subnet.md index 6110266..fbd5a43 100644 --- a/docs/routing-compression-subnet.md +++ b/docs/routing-compression-subnet.md @@ -71,7 +71,7 @@ |------|------| | **输入** | 原始多模态数据(图像/语音/文本)+ 控制张量(来自Core) | | **输出** | 标准引用张量(发送给Core) | -| **输出格式** | 标准张量协议,tensor_type=`ref_tensor` | +| **输出格式** | 统一通信张量,fmt_id=0 (ref_tensor) | | **压缩目标** | 10:1 ~ 100:1 压缩率 | | **延迟目标** | 单次处理 < 50ms | @@ -103,7 +103,8 @@ │ ▼ │ │ ┌─────────────────────┐ │ │ │ 张量编码层 │ │ -│ │ 内部格式→标准张量格式 │ │ +│ │ 内部格式→统一通信张量 │ │ +│ │ 拼接 fmt_id=0 前缀 │ │ │ └──────────┬──────────┘ │ └─────────────────────────┼─────────────────────┘ │ @@ -195,20 +196,21 @@ 6. 训练 100 epochs,batch size 256 7. 评估:在不同压缩率 (10x, 50x, 100x) 下测量重建质量 -### 阶段 4:张量编码层训练 +### 阶段 4:编码适配器训练 -**目标**:训练编码层将内部张量转换为标准通信张量 +**目标**:训练编码层将内部张量转换为统一通信张量(拼接 fmt_id/parser_id 前缀) **技术路线**: - 可学习的线性投影层 + LayerNorm - 维度对齐网络(Projection Head) **训练步骤**: -1. 定义标准张量协议的目标格式(固定维度、精度等) +1. 定义统一通信张量的目标维度(payload 部分) 2. 收集阶段3的 VQ-VAE 潜在表示作为训练数据 -3. 训练投影网络将内部张量映射到标准格式 -4. 训练配套的解码网络(供其他子网使用) -5. 验证编码/解码循环的一致性误差 +3. 训练投影网络将内部张量映射到 payload 格式 +4. 实现编码适配器: payload + 拼接 [fmt_id=0, parser_id=0, padding..., data] +5. 训练配套的解码网络(供其他子网使用) +6. 验证编码/解码循环的一致性误差 ### 阶段 5:联合端到端微调 diff --git a/docs/tensor-parser-network.md b/docs/tensor-parser-network.md index 0435836..adf2392 100644 --- a/docs/tensor-parser-network.md +++ b/docs/tensor-parser-network.md @@ -42,21 +42,24 @@ NextAgent 的所有子网间通过**标准张量协议**通信。这些张量是 ### 解析目标 -| 张量类型 | 输入 | 人类可读输出 | -|----------|------|-------------| -| `ref_tensor` | 路由子网的压缩特征向量 | "用户输入了一张北京故宫的照片,标签: ['建筑', '故宫', '北京'],置信度: 0.92" | -| `memory_result` | 记忆子网的检索结果 | "找到 3 条相关记忆: (1) 用户上次去北京是 2025年3月, (2) 用户偏好空气质量信息, (3) 用户关注pm2.5指数" | -| `reasoning_path` | 推理路径与验证结果 | "推理链: 步骤1(提取实体)→步骤2(查天气数据)→步骤3(推理结论: 北京今天晴, 22°C), 置信度: 0.95, 已验证: true" | -| `expression_verify` | LLM 表达验证张量 | "表达验证: 语义对齐度 0.96, 情感正向, 关键实体保留: [天气,北京,今天], 遗漏检测: 无" | -| `memory_update` | 记忆更新信息 | "新信息: {实体: [用户_张三], 关系: [HAS_PERSONA 喜欢_摇滚], 置信度: 0.9}" | -| `control` | 控制信号 | "控制信号: Core→路由子网, 类型: [从滑动窗口提取详细信息], 参数: {depth: 2}" | +所有输入为**统一通信张量(纯数值)**,解析为人类可读文本。关键区别:解析器不读取 JSON 字段,而是读取张量前缀中的 parser_id 路由到对应解析器。 + +| 张量 fmt_id | 解析器 | 输入 (纯张量) | 人类可读输出 | +|:----------:|:------:|:------------:|:------------| +| `0` (ref) | ref_parser | payload: 压缩后的多模态特征向量 | "用户输入了一张北京故宫的照片,标签: [建筑, 故宫, 北京]" | +| `1` (memory_result) | memory_parser | payload: 记忆检索结果的实体/关系编码 | "找到 3 条相关记忆: (1) 用户上次去北京是 2025年3月..." | +| `2` (memory_query) | query_parser | payload: 编码后的查询意图 | "Core 查询: 用户上次提到过什么音乐偏好?" | +| `3` (memory_update) | update_parser | payload: 编码后的更新数据 | "新信息: 实体[用户_张三], 关系[HAS_PERSONA 喜欢_摇滚]" | +| `4` (reasoning_path) | reasoning_parser | payload: 推理步嵌入+置信度向量 | "推理链: 步骤1(提取实体: 北京) → 步骤2(查天气...) → 结论" | +| `5` (expression_verify) | expression_parser | payload: 语义编码+对齐度+情感 | "表达验证: 语义对齐度 0.96, 情感正向, 综合得分 0.94" | +| `6` (control) | control_parser | payload: 控制参数编码 | "控制信号: Core→路由子网, 要求从滑动窗口提取详细信息" | ### 输入/输出 | 项目 | 说明 | |------|------| -| **输入** | 标准张量(任意类型) | -| **输出** | 结构化人类可读文本(Markdown / JSON / 自然语言) | +| **输入** | 统一通信张量(纯数值,前缀含 parser_id,其余为载荷数据) | +| **输出** | 结构化人类可读文本(Markdown / 自然语言) | | **不参与运行时** | 纯训练/调试工具,不加入推理链路 | | **延迟要求** | 无(仅离线使用) | @@ -67,88 +70,120 @@ NextAgent 的所有子网间通过**标准张量协议**通信。这些张量是 ### 架构总览 ``` -标准张量 (任意类型) +统一通信张量 (纯数值, [fmt, parser, ...payload]) │ ▼ -┌──────────────────────────────────────────────┐ -│ 张量解析网络 (Tensor Parser) │ -│ │ -│ ┌──────────────────────────────────────────┐│ -│ │ 张量类型分类器 (Tensor Type Classifier) ││ -│ │ 识别张量类型 → 路由到对应解析器 ││ -│ │ 输出: ref / memory_result / reasoning / ││ -│ │ expression / update / control ││ -│ └──────────────────┬───────────────────────┘│ -│ │ │ -│ ┌────────────────┼────────────────────┐ │ -│ │ │ │ │ -│ ▼ ▼ ▼ │ -│ ┌────────┐ ┌────────────┐ ┌────────┐│ -│ │ 引用 │ │ 记忆结果 │ ... │ 控制 ││ -│ │ 解析器 │ │ 解析器 │ │ 解析器 ││ -│ └───┬────┘ └─────┬──────┘ └───┬────┘│ -│ │ │ │ │ -│ └──────────────┼────────────────────┘ │ -│ ▼ │ -│ ┌──────────────────────────────────────────┐│ -│ │ 文本生成器 (Text Generator) ││ -│ │ 将结构化解析结果转换为人类可读文本 ││ -│ │ 支持: 自然语言 / 结构化表格 / JSON ││ -│ └──────────────────────────────────────────┘│ -│ │ -└──────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────┐ +│ 张量解析网络 (Tensor Parser) │ +│ │ +│ 读取 dim[0] = fmt_id +│ 读取 dim[1] = parser_id +│ 剥离前 k 维 → payload 纯数据 +│ │ +│ ┌──────────────────────────────────────────────┐│ +│ │ 解析路由器 (Parser Router) ││ +│ │ parser_id → 直接路由到对应解析器 ││ +│ │ (查表: 0→ref, 1→memory, 4→reasoning, ...) ││ +│ └──────────────────┬───────────────────────────┘│ +│ │ │ +│ ┌────────────────┼────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────┐ ┌────────────┐ ┌──────────┐ │ +│ │ 引用 │ │ 记忆结果 │ │ 推理路径 │ │ +│ │ 解析器 │ │ 解析器 │ │ 解析器 │ │ +│ └───┬────┘ └─────┬──────┘ └────┬─────┘ │ +│ │ │ │ │ +│ └──────────────┼────────────────┘ │ +│ ▼ │ +│ ┌────────────────────────────────────────────┐│ +│ │ 文本生成器 (Text Generator) ││ +│ │ 将解析器输出的结构化数据转为人类可读文本 ││ +│ │ 支持: 自然语言 / 结构化表格 ││ +│ └────────────────────────────────────────────┘│ +│ │ +└────────────────────────────────────────────────┘ │ ▼ 人类可读输出文本 ``` -### 类型分类器 +### 解析路由器 -一个轻量级分类器,从张量 header 中提取 `tensor_type` 字段并路由到对应解析器。 +输入张量中的 `parser_id` 直接索引到对应解析器,无需分类网络: ```python -class TensorTypeClassifier: - def classify(self, tensor: StandardTensor) -> str: - # 直接从 header 读取(运行时信息) + 向量特征校验 - expected_type = tensor.header.tensor_type - payload_hash = hash(tensor.payload.data[:16]) - - # 可选:用小网络验证类型与负载内容一致 - if self.verification_enabled: - type_logits = self.small_classifier(tensor.payload.data) - predicted_type = self.type_names[type_logits.argmax()] - if predicted_type != expected_type: - logger.warning(f"类型不一致: header={expected_type}, payload特征={predicted_type}") - - return expected_type +PARSER_MAP = { + 0: RefParser(), # ref_tensor + 1: MemoryResultParser(), # memory_result + 2: MemoryQueryParser(), # memory_query + 3: MemoryUpdateParser(), # memory_update + 4: ReasoningParser(), # reasoning_path + 5: ExpressionParser(), # expression_verify + 6: ControlParser(), # control +} + +def route_and_parse(comm_tensor: Tensor) -> str: + """ + 1. 读取张量前缀中的 parser_id + 2. 路由到对应解析器 + 3. 解析器处理 payload 部分(剥离前缀后) + """ + parser_id = int(comm_tensor[1]) # dim[1] + parser = PARSER_MAP[parser_id] + + payload = comm_tensor[PREFIX_LEN:] # 剥离前缀 + return parser.parse(payload) ``` +### 类型一致性校验(可选调试功能) + +```python +def verify_type_consistency(comm_tensor: Tensor) -> bool: + """校验 fmt_id 与 payload 内容一致(可选)""" + fmt_id = int(comm_tensor[0]) + parser_id = int(comm_tensor[1]) + payload = comm_tensor[PREFIX_LEN:] + + # 用小网络验证负载特征与 fmt_id 匹配 + if verification_enabled: + predicted = small_classifier(payload[:64]) # 只看前64维 + if predicted != fmt_id: + logger.warning(f"类型不一致: header fmt_id={fmt_id}, payload特征={predicted}") + + return fmt_id == parser_id # 正常情况下 fmt_id == parser_id + ### 专用解析器 +所有解析器接收的是**剥离前缀后的纯载荷张量**,需自己解析其内部结构。 + #### 引用解析器 (Ref Parser) -将路由子网的压缩特征向量解码为标签信息: +路由子网的压缩特征向量解码: ```python class RefParser(nn.Module): - """引用张量解析器""" + """引用张量解析器 + + 输入: 剥离前缀后的 payload 张量 [768] + 输出: 标签文本 + """ def __init__(self, latent_dim=768, label_vocab_size=10000): super().__init__() self.encoder = nn.Linear(latent_dim, 512) - self.label_predictor = nn.Linear(512, label_vocab_size) # 标签分类 - self.confidence_head = nn.Linear(512, 1) # 置信度估计 - self.category_head = nn.Linear(512, 50) # 类别预测 + self.label_predictor = nn.Linear(512, label_vocab_size) + self.confidence_head = nn.Linear(512, 1) - def forward(self, ref_tensor): - h = self.encoder(ref_tensor) - label_logits = self.label_predictor(h) # 预测标签 - confidence = torch.sigmoid(self.confidence_head(h)) # 置信度 - category = self.category_head(h) # 类别 + def forward(self, payload: Tensor): + """payload: 剥离前缀后的纯数据张量""" + h = self.encoder(payload) + label_logits = self.label_predictor(h) + confidence = torch.sigmoid(self.confidence_head(h)) + top_labels = label_logits.topk(5).indices return { - "predicted_labels": label_logits.topk(5).indices, + "predicted_labels": decode_label_ids(top_labels), "confidence": confidence.item(), - "category": category.argmax().item() } ``` @@ -156,25 +191,33 @@ class RefParser(nn.Module): ```python class ReasoningParser(nn.Module): - """推理路径张量→推理链文本""" + """推理路径张量 → 推理链文本 + + 输入 payload 结构: + [n_steps, step_emb_1(768), step_conf_1, + step_emb_2(768), step_conf_2, ..., meta(3)] + 输出: 步骤描述文本 + """ def __init__(self, hidden_dim=768, max_steps=20): super().__init__() - self.step_encoder = nn.Linear(hidden_dim, 256) self.step_decoder = nn.TransformerDecoderLayer( d_model=256, nhead=4 ) - # 将每一步解码为文本 token self.text_head = nn.Linear(256, vocab_size) - def forward(self, reasoning_tensor): - steps = reasoning_tensor["reasoning_steps"] + def forward(self, payload: Tensor): + # 解析 payload 结构: 先读步数,再拆出每步 + n_steps = int(payload[0]) decoded = [] - for step_emb in steps: - h = self.step_encoder(step_emb) - # 自回归解码为文本 + offset = 1 # 跳过步数 + for _ in range(n_steps): + step_emb = payload[offset:offset + 768] + conf = payload[offset + 768] + offset += 769 + h = self.step_decoder(step_emb) step_text = self.decode_step(h) - decoded.append(step_text) + decoded.append(f"{step_text} (conf={conf:.2f})") return "\n".join(decoded) ``` @@ -204,32 +247,36 @@ class ReasoningParser(nn.Module): 对于无法自然获得标注的场景,使用 LLM 生成合成数据: ```python -def synthesize_training_pair(tensor_type: str) -> tuple: - """生成 (标准张量, 人类可读文本) 配对""" +def synthesize_training_pair(fmt_id: int) -> tuple: + """生成 (统一通信张量, 人类可读文本) 配对""" - # 1. 随机生成或从真实数据采样标准张量 - tensor = sample_tensor(tensor_type) + # 1. 随机生成或从真实数据采样统一通信张量 + comm_tensor = sample_comm_tensor(fmt_id) - # 2. 使用 LLM 将张量转为人类可读描述 - metadata_json = json.dumps(tensor.header, ensure_ascii=False) - payload_preview = summarize_payload(tensor.payload.data) + # 2. 解析张量属性,不使用 JSON header(协议中不存在) + parser_id = int(comm_tensor[1]) + payload = comm_tensor[PREFIX_LEN:] + payload_stats = { + "shape": list(payload.shape), + "mean": float(payload.mean()), + "std": float(payload.std()), + } + fmt_name = PROTOCOL_REGISTRY["fmt"][fmt_id] llm_prompt = f""" - 你是一个张量解析专家。以下是一个 {tensor_type} 类型的标准张量: + 你是一个张量解析专家。以下是一个 {fmt_name} 类型的统一通信张量。 + 这是一个纯数值张量,不包含任何 JSON 元数据。 - 头: {metadata_json} - 负载摘要: {payload_preview} - 维度: {tensor.metadata.dimensions} - 置信度: {tensor.metadata.confidence} + fmt_id={fmt_id} ({fmt_name}) + parser_id={parser_id} + 载荷形状: {payload_stats['shape']} + 载荷统计: mean={payload_stats['mean']:.4f}, std={payload_stats['std']:.4f} - 请用自然语言描述这个张量代表什么。 - 例如,如果是 ref_tensor,描述"用户输入了关于XXX的查询,提取到的关键标签是..." - 如果是 reasoning_path,描述推理链的每一步和最终结论。 + 请根据已知的数据格式约定,用自然语言描述此张量的含义。 """ human_readable = llm.generate(llm_prompt) - - return (tensor, human_readable) + return (comm_tensor, human_readable) ``` #### 方法三:端到端采集 @@ -363,34 +410,54 @@ def regression_test(subnet, test_cases): ```python class RuleBasedParser: - """基于规则的张量解析器(MVP)""" + """基于规则的张量解析器(MVP) - def parse(self, tensor: StandardTensor) -> str: - tensor_type = tensor.header.tensor_type + 读取张量前缀中的 parser_id 确定类型, + 然后根据预定格式输出可读文本。 + """ + + PARSER_NAMES = { + 0: "引用张量 (ref_tensor)", + 1: "记忆结果 (memory_result)", + 2: "记忆查询 (memory_query)", + 3: "记忆更新 (memory_update)", + 4: "推理路径 (reasoning_path)", + 5: "表达验证 (expression_verify)", + 6: "控制信号 (control)", + } + + def parse(self, comm_tensor: Tensor) -> str: + fmt_id = int(comm_tensor[0]) + parser_id = int(comm_tensor[1]) + payload = comm_tensor[PREFIX_LEN:] - if tensor_type == "control": - return self.parse_control(tensor) - elif tensor_type == "memory_result": - return self.parse_memory(tensor) + type_name = self.PARSER_NAMES.get(fmt_id, f"未知类型:{fmt_id}") + + if fmt_id == 6: # control + return self.parse_control(payload) + elif fmt_id in (0, 1): # ref / memory_result + return self.parse_vector(payload, type_name) + elif fmt_id == 4: # reasoning + return self.parse_reasoning(payload) + elif fmt_id == 5: # expression + return self.parse_expression(payload) else: - # 其他类型走模板 - return f"[{tensor_type}] 源={tensor.header.source}, 目标={tensor.header.target}, " \ - f"置信度={tensor.metadata.confidence:.2f}, " \ - f"维度={tensor.metadata.dimensions}" + return f"[{type_name}] 维度={list(payload.shape)}, " \ + f"均值={float(payload.mean()):.4f}" - def parse_control(self, tensor): - params = tensor.payload.metadata_map - return f"Core 发往 {tensor.header.target}: " \ - f"控制类型={params.get('control_type', 'unknown')}, " \ - f"参数={params}" + def parse_control(self, payload): + return f"控制信号张量 (维度{list(payload.shape)})" - def parse_memory(self, tensor): - entities = tensor.payload.metadata_map.get('entities', []) - rels = tensor.payload.metadata_map.get('relations', []) - text = f"记忆检索结果: 找到 {len(entities)} 个实体, {len(rels)} 条关系\n" - for r in rels[:3]: - text += f" · {r['subject']} -[{r['type']}]-> {r['object']} (置信度 {r.get('confidence', 1.0)})\n" - return text + def parse_vector(self, payload, type_name): + return f"[{type_name}] {list(payload.shape)}维向量" + + def parse_reasoning(self, payload): + n_steps = int(payload[0]) + return f"推理路径: {n_steps}步, 张量维度{list(payload.shape)}" + + def parse_expression(self, payload): + alignment = float(payload[768]) # 约定的位置 + return f"表达验证: 意图对齐度={alignment:.2f}" ``` 规则版不需要训练,可以立即使用。随着需求积累,逐步加入可学习的解析器替换规则路径。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..87f90de --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +numpy>=1.24.0 diff --git a/src/parsers/__init__.py b/src/parsers/__init__.py new file mode 100644 index 0000000..5ccf741 --- /dev/null +++ b/src/parsers/__init__.py @@ -0,0 +1,3 @@ +from .rule_based import RuleBasedParser + +__all__ = ["RuleBasedParser"] diff --git a/src/parsers/__pycache__/__init__.cpython-312.pyc b/src/parsers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12596d8ce0e885ab425ea2a4e34bdc3969f95289 GIT binary patch literal 225 zcmX@j%ge<81fpMMvfP36V-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##{VBr8%iiiN&cY0f|M$sYON1Kyg1!=387vK#BMykPKMt7JGbrVopwc{7Qz; zAPau!>u2QWrs@|I<);@V=IZ;UR+KoVr{g?xz|6?Vc$Y!_0k_NrF4;!*B6gr40Jf4jY5)KL literal 0 HcmV?d00001 diff --git a/src/parsers/__pycache__/rule_based.cpython-312.pyc b/src/parsers/__pycache__/rule_based.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff5b148accf76bc22a45d0fbc1dcdc5f4534f094 GIT binary patch literal 2825 zcmc&#TWk|o5Z$%c&p6~^LP!PF0n}m|;y@r#f}sL}fE2DA=`U=iZ&0c5Wy4Y~Hf1aqpf3&9a^>)tSl6K^R=ZXbHYa zLE%kGN5KS6NMQbVbTlCbF2K01(tyw4J?eRJ3jW(91cG(z~ zW_&jQICVWB2D?+&zfW8~FLp;vJNE7R?a`U3*x9L({?v~bO*^(fKk=1bZ-SN@ekgWc zOrPkPdNLqB3a2|d#ZzaKk4{X*ZYO&JCXWP4+9^|VU`QP5N#1!VUj7EaB;!Q*e&Xzq zcs?L6Cm&oxDw3Js>9&E4MrzyI40Ps2SSp zV_eOdcuT=SnNq?x^DBr;7>7Hgae3Yj_-{v`u=lWbC5Jh58HiaOr{I*?YHP&Iss;xAl5Y+OIW4It z9my-VYfZrdP&+yJ!P*b^_ z8vb}rCJT8I;zBCFUGt`BavCX`D!7Lllu?LXLA#bJhXm#*(%XLAbx!}dG}#j;td0+$X1CfS5w2^rBTVw!b3&kpP^5^b@zQrqPeu(G_Whfz*79pi2!zQX?bjn`ctP zU)Guuflje^L>#`KetcehaBbajW%;U>G>VK5Ig&VcT@3z&9+=L1z5=w+cQXlYW#1X?uZya;-v&1r9SwZTXr5HgEiWW;=OV~~#wBCX?#Ugxt{ zIkgCMST|4wBDT39{Pg^E zVOzJO&oa6)_z6O)4_hOj$JWMHjQV-wZr-pvgBLfJ{zu4@=Ca8C!38mV++5Su9QrU^ z+P%EX9XlG_g8bP6e7lJ^R`7<3tS5yJt&3L03V7pM-msQ2 zLf{+BdO=H)D1js?=t;88O`$i56bvMJ)Ms;Mb~GeOxtn2G;qpK|P&S6K`2{8GWtc*g zXsOyzwr^k(uAF{U_zV)Lj9Dv)0QS3rP-&+PYE^Y;fJ;xn~G#qXG6RpU=2oAm4E?=9+&%ir_2?g-Tdfm-#XE{T++QBEkRQS@#%txT{ o(j`G_mH%)(Qw|&Gfnres!t03RzhH$TX8Bva47dJ)fsoAp1-B;_O8@`> literal 0 HcmV?d00001 diff --git a/src/parsers/rule_based.py b/src/parsers/rule_based.py new file mode 100644 index 0000000..0e4c008 --- /dev/null +++ b/src/parsers/rule_based.py @@ -0,0 +1,46 @@ +import numpy as np + +from protocol.registry import PROTOCOL_REGISTRY + + +class RuleBasedParser: + """规则解析器 MVP,不依赖 ML。 + + 读取通信张量前缀中的 parser_id 确定类型, + 根据各类型约定的 payload 结构输出可读文本。 + """ + + def parse(self, tensor: np.ndarray) -> str: + if tensor.shape[0] < PROTOCOL_REGISTRY.prefix_len: + return (f"张量过短: {tensor.shape[0]} < " + f"{PROTOCOL_REGISTRY.prefix_len}") + parser_id = int(tensor[1]) + payload = tensor[PROTOCOL_REGISTRY.prefix_len:] + return self._parse_by_type(parser_id, payload) + + def _parse_by_type(self, parser_id: int, payload: np.ndarray) -> str: + if parser_id == 0: + n_labels = int(payload[0]) if len(payload) > 0 else 0 + return f"引用张量: {n_labels}个标签" + elif parser_id == 1: + n_entities = int(payload[0]) if len(payload) > 0 else 0 + n_relations = int(payload[1]) if len(payload) > 1 else 0 + return f"记忆结果: {n_entities}个实体, {n_relations}条关系" + elif parser_id == 2: + return "记忆查询" + elif parser_id == 3: + n_entities = int(payload[0]) if len(payload) > 0 else 0 + n_relations = int(payload[1]) if len(payload) > 1 else 0 + return f"记忆更新: {n_entities}个实体, {n_relations}条关系" + elif parser_id == 4: + n_steps = int(payload[0]) if len(payload) > 0 else 0 + return f"推理路径: {n_steps}步" + elif parser_id == 5: + idx = 768 + intent_alignment = payload[idx] if len(payload) > idx else 0.0 + return f"表达验证: 意图对齐度={intent_alignment:.2f}" + elif parser_id == 6: + return "控制信号" + else: + name = PROTOCOL_REGISTRY.parser_name(parser_id) + return f"未知解析器: {parser_id} ({name})" diff --git a/src/protocol/__init__.py b/src/protocol/__init__.py new file mode 100644 index 0000000..a6334b2 --- /dev/null +++ b/src/protocol/__init__.py @@ -0,0 +1,13 @@ +from .registry import PROTOCOL_REGISTRY, ProtocolRegistry +from .codec import CodecAdapter, IdentityCodec, EncodingError, DecodingError +from .tensor_bus import TensorBus + +__all__ = [ + "PROTOCOL_REGISTRY", + "ProtocolRegistry", + "CodecAdapter", + "IdentityCodec", + "EncodingError", + "DecodingError", + "TensorBus", +] diff --git a/src/protocol/__pycache__/__init__.cpython-312.pyc b/src/protocol/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..460009a86658daab0fcb315b9f88c5f2dce71b69 GIT binary patch literal 449 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 literal 0 HcmV?d00001 diff --git a/src/protocol/__pycache__/registry.cpython-312.pyc b/src/protocol/__pycache__/registry.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da02cd8df5fe217b1a9a5a2d2bc6add8edddf3b2 GIT binary patch literal 3994 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 bVSYg^_fTLFz4$BY`b%hIdLAJF=HY(;^S&{_ literal 0 HcmV?d00001 diff --git a/src/protocol/codec.py b/src/protocol/codec.py new file mode 100644 index 0000000..b314104 --- /dev/null +++ b/src/protocol/codec.py @@ -0,0 +1,150 @@ +""" +CodecAdapter — 编解码适配器基类 + +每个子网继承此类,实现 encode / decode 方法,完成 +内部张量格式 ↔ 统一通信张量(带 fmt_id/parser_id 前缀)的转换。 +""" + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import Any + +import numpy as np + +from .registry import PROTOCOL_REGISTRY + + +class EncodingError(Exception): + """编码失败""" + pass + + +class DecodingError(Exception): + """解码失败""" + pass + + +class CodecAdapter(ABC): + """ + 编解码适配器基类 + + 子类需实现: + - encode(internal_tensor, target, reply_req) -> comm_tensor + - decode(comm_tensor) -> internal_tensor + + 其中 comm_tensor 是形状为 [prefix_len + payload_dim] 的 numpy ndarray + """ + + def __init__(self, subnet_id: str, fmt_id: int, parser_id: int): + PROTOCOL_REGISTRY.validate_subnet_id(subnet_id) + PROTOCOL_REGISTRY.validate_fmt_id(fmt_id) + PROTOCOL_REGISTRY.validate_parser_id(parser_id) + + self.subnet_id = subnet_id + self.fmt_id = fmt_id + self.parser_id = parser_id + self.prefix_len = PROTOCOL_REGISTRY.prefix_len + + # ------------------------------------------------------------------ + # 编码:子网内部格式 → 统一通信张量 + # ------------------------------------------------------------------ + def encode(self, internal_tensor: np.ndarray, target: str, reply_req: bool = True) -> np.ndarray: + """ + 内部张量 → 统一通信张量 + + Parameters + ---------- + internal_tensor : np.ndarray + 子网内部格式的特征向量 (1D 或 2D) + target : str + 目标子网 ID + reply_req : bool + 是否需要回复 + + Returns + ------- + comm_tensor : np.ndarray + 形状为 [prefix_len + ...] 的统一通信张量 + """ + PROTOCOL_REGISTRY.validate_subnet_id(target) + self._validate_internal(internal_tensor) + # 子类可重写此方法实现更复杂的编码逻辑 + return self._build_prefix(target, reply_req, internal_tensor) + + # ------------------------------------------------------------------ + # 解码:统一通信张量 → 内部格式 + # ------------------------------------------------------------------ + def decode(self, comm_tensor: np.ndarray) -> np.ndarray: + """ + 统一通信张量 → 内部张量 + + Parameters + ---------- + comm_tensor : np.ndarray + 统一通信张量,前 prefix_len 维为前缀 + + Returns + ------- + internal_tensor : np.ndarray + 剥离前缀后的 payload 数据 + """ + self._validate_prefix(comm_tensor) + return comm_tensor[self.prefix_len:] # 剥离前缀 + + # ------------------------------------------------------------------ + # 校验内部张量格式(子类可重写) + # ------------------------------------------------------------------ + def _validate_internal(self, tensor: np.ndarray): + if not isinstance(tensor, np.ndarray): + raise EncodingError(f"内部类型必须是 np.ndarray, 得到 {type(tensor)}") + if tensor.ndim not in (1, 2): + raise EncodingError(f"内部张量维度必须是 1 或 2, 得到 {tensor.ndim}") + + # ------------------------------------------------------------------ + # 校验统一通信张量的前缀 + # ------------------------------------------------------------------ + def _validate_prefix(self, tensor: np.ndarray): + if not isinstance(tensor, np.ndarray): + raise DecodingError(f"通信张量必须是 np.ndarray, 得到 {type(tensor)}") + if tensor.shape[0] < self.prefix_len: + raise DecodingError( + f"通信张量长度 ({tensor.shape[0]}) 不足前缀长度 ({self.prefix_len})" + ) + fmt_id = int(tensor[0]) + parser_id = int(tensor[1]) + if fmt_id != self.fmt_id and fmt_id != -1: # -1 表示未知(测试用) + raise DecodingError( + f"fmt_id 不匹配: 预期 {self.fmt_id}, 实际 {fmt_id}" + ) + if parser_id != self.parser_id and parser_id != -1: + raise DecodingError( + f"parser_id 不匹配: 预期 {self.parser_id}, 实际 {parser_id}" + ) + + # ------------------------------------------------------------------ + # 构建前缀 + # ------------------------------------------------------------------ + def _build_prefix(self, target: str, reply_req: bool, + payload: np.ndarray) -> np.ndarray: + padding = self.prefix_len - 2 + prefix = np.array([self.fmt_id, self.parser_id] + [0.0] * padding, + dtype=np.float64) + return np.concatenate([prefix, payload.ravel()]) + + +class IdentityCodec(CodecAdapter): + """ + 恒等编解码器(测试用):fmt_id/parser_id = -1 + 不做格式校验,直接拼接/剥离前缀。 + """ + + def __init__(self): + super().__init__(subnet_id="core", fmt_id=-1, parser_id=-1) + + def _validate_prefix(self, tensor: np.ndarray): + if tensor.shape[0] < self.prefix_len: + raise DecodingError(f"张量过短: {tensor.shape[0]} < {self.prefix_len}") + + def _validate_internal(self, tensor: np.ndarray): + if not isinstance(tensor, np.ndarray): + raise EncodingError(f"内部类型必须是 np.ndarray, 得到 {type(tensor)}") diff --git a/src/protocol/registry.py b/src/protocol/registry.py new file mode 100644 index 0000000..1fcbd4b --- /dev/null +++ b/src/protocol/registry.py @@ -0,0 +1,83 @@ +""" +Protocol Registry — 统一张量通信协议的 fmt_id / parser_id 注册表 + +所有 fmt_id 和 parser_id 均为系统设计时预定义,通信时编码在张量前缀中。 +""" + +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Dict + +# --------------------------------------------------------------------------- +# fmt_id — 数据格式标识 +# --------------------------------------------------------------------------- +FMT_NAMES: Dict[int, str] = { + 0: "ref_tensor", # 路由引用张量 (压缩后的多模态特征) + 1: "memory_result", # 记忆检索结果 (实体/关系编码) + 2: "memory_query", # 记忆查询 (编码后的查询意图) + 3: "memory_update", # 新信息更新 (编码后的更新数据) + 4: "reasoning_path", # 推理路径 (步嵌入 + 置信度) + 5: "expression_verify", # 表达验证 (语义编码 + 对齐度 + 情感) + 6: "control", # 控制信号 (控制参数编码) +} + +# --------------------------------------------------------------------------- +# parser_id — 解析网络标识 +# --------------------------------------------------------------------------- +PARSER_NAMES: Dict[int, str] = { + 0: "ref_parser", # 引用张量解析器 + 1: "memory_result_parser", # 记忆结果解析器 + 2: "memory_query_parser", # 记忆查询解析器 + 3: "memory_update_parser", # 记忆更新解析器 + 4: "reasoning_parser", # 推理路径解析器 + 5: "expression_parser", # 表达验证解析器 + 6: "control_parser", # 控制信号解析器 +} + +# --------------------------------------------------------------------------- +# payload_prefix_len — 张量前缀长度(统一为 2: fmt_id + parser_id) +# 后续扩展可增加 padding,此时修改此值即可 +# --------------------------------------------------------------------------- +PAYLOAD_PREFIX_LEN: int = 2 + +# --------------------------------------------------------------------------- +# 子网 ID 列表 +# --------------------------------------------------------------------------- +SUBNET_IDS = ["routing", "memory", "logic_trace", "llm", "core"] + +# --------------------------------------------------------------------------- +# 校验 +# --------------------------------------------------------------------------- +assert FMT_NAMES.keys() == PARSER_NAMES.keys(), "fmt_id 与 parser_id 必须一一对应" + + +@dataclass +class ProtocolRegistry: + """协议注册表 — 封装查表逻辑""" + + fmt_names: Dict[int, str] = field(default_factory=lambda: dict(FMT_NAMES)) + parser_names: Dict[int, str] = field(default_factory=lambda: dict(PARSER_NAMES)) + prefix_len: int = PAYLOAD_PREFIX_LEN + subnet_ids: list = field(default_factory=lambda: list(SUBNET_IDS)) + + def fmt_name(self, fmt_id: int) -> str: + return self.fmt_names.get(fmt_id, f"unknown_fmt_{fmt_id}") + + def parser_name(self, parser_id: int) -> str: + return self.parser_names.get(parser_id, f"unknown_parser_{parser_id}") + + def validate_fmt_id(self, fmt_id: int): + if fmt_id not in self.fmt_names: + raise ValueError(f"未知的 fmt_id: {fmt_id}, 可选: {list(self.fmt_names.keys())}") + + def validate_parser_id(self, parser_id: int): + if parser_id not in self.parser_names: + raise ValueError(f"未知的 parser_id: {parser_id}, 可选: {list(self.parser_names.keys())}") + + def validate_subnet_id(self, subnet_id: str): + if subnet_id not in self.subnet_ids: + raise ValueError(f"未知的子网 ID: {subnet_id}, 可选: {self.subnet_ids}") + + +# 全局单例 +PROTOCOL_REGISTRY = ProtocolRegistry() diff --git a/src/protocol/tensor_bus.py b/src/protocol/tensor_bus.py new file mode 100644 index 0000000..ad4d468 --- /dev/null +++ b/src/protocol/tensor_bus.py @@ -0,0 +1,37 @@ +from typing import Dict + +import numpy as np + +from .registry import PROTOCOL_REGISTRY +from .codec import CodecAdapter, DecodingError + + +class TensorBus: + """TensorBus 通信总线(门面模式) + + 维护子网注册表 {subnet_id: CodecAdapter}, + 提供统一的通信入口。 + """ + + def __init__(self): + self._subnets: Dict[str, CodecAdapter] = {} + + def register(self, subnet_id: str, codec: CodecAdapter): + PROTOCOL_REGISTRY.validate_subnet_id(subnet_id) + self._subnets[subnet_id] = codec + + def send(self, tensor: np.ndarray, source: str, target: str, + reply_req: bool = True) -> np.ndarray: + if source not in self._subnets: + raise KeyError(f"源子网未注册: {source}") + if target not in self._subnets: + raise KeyError(f"目标子网未注册: {target}") + source_codec = self._subnets[source] + target_codec = self._subnets[target] + comm_tensor = source_codec.encode(tensor, target, reply_req) + return target_codec.decode(comm_tensor) + + def recv(self, source: str, tensor: np.ndarray) -> np.ndarray: + if source not in self._subnets: + raise KeyError(f"源子网未注册: {source}") + return self._subnets[source].decode(tensor) diff --git a/tests/__pycache__/test_protocol.cpython-312.pyc b/tests/__pycache__/test_protocol.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07979afbe5e4fe65f2074cff16f63b7be90c868e GIT binary patch literal 16514 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