pbl_kdb_ext —— PBL 知识库KDB只读桩扩展模块M7

1. 模块用途

pbl_kdb_ext 是 PBLProject/Problem-Based LearningAgent OS 的知识库只读扩展层,为 Designer / Critic Agent 与教师端提供「查得到、改不了」的知识条目检索与研究数据聚合能力。

设计定位(对应两个开放问题的当期落地口径):

开放问题 当期口径 本模块实现
Q-OPEN-5 KDB 是否落地向量库 暂不引入向量库/Embedding 不落地向量桩:检索走结构化字段等值 + LIKE 关键词匹配,返回体带 stub 标记,后续可平滑替换为向量检索而不改契约
Q-OPEN-6 research 数据是否可写回 只读、匿名 只读匿名聚合:分组统计评估分数,最小分组 k_min=5,样本不足的分组直接抑制(suppressed_groups 计数),返回 anonymous=true / written=false

G6 范围纪律(硬约束,可 grep 核对)

本模块零写入业务对象,明确不实现以下任何能力:

  • kdb.write / kdb.add_candidate / kdb.propose_pattern(知识条目写入、候选入库、模式提炼)
  • research.write(研究数据写回)
  • sim.*(仿真/模拟相关全部接口)

pbl_kdb_item.is_write_locked 恒为 1,模块内不存在任何 UPDATE / INSERT / DELETE pbl_kdb_item 的代码路径;唯一的写操作是查询留痕日志 pbl_kdb_query_log(审计用途, append-only不属于业务对象写入

2. 数据表2 张,定义在 models/

2.1 pbl_kdb_item —— 知识条目表(只读,写锁定)

字段 类型 说明
id str(32) 主键
tenant_id str(32) 租户隔离,所有查询强制打头
code str(64) 条目业务编码(唯一,配合 tenant_id
item_type str(32) 条目类型pattern / case / rubric_ref 等,取自 appcodes
title str(255) 标题(检索字段)
summary_txt text 摘要正文(LIKE 关键词匹配字段)
payload_json text 结构化载荷JSON读取时反序列化为 dict
source str(64) 来源(可作过滤维度)
version_no int 版本号
status str(16) 状态,检索仅命中 published
is_write_locked short 恒为 1:写锁定标记,本模块只读约束的数据层体现
created_at / updated_at timestamp 审计时间戳

表数据由上游(蓝图编译 / 治理域)产生并置 is_write_locked=1;本模块只消费,不生产。

2.2 pbl_kdb_query_log —— 检索留痕日志表append-only

字段 类型 说明
id str(32) 主键
tenant_id str(32) 租户隔离
query_uid str(64) 查询唯一标识(Q + sha256(参数+时间) 前 20 位)
caller_type str(16) 调用方类型agent / teacher缺省 agent
caller_id str(32) 调用方 ID取自 actor_id()
query_json text 查询参数快照(剔除 caller_id,避免重复存储)
hit_count int 命中条数
latency_ms int 检索耗时(毫秒)
ok short 是否成功(恒 1异常路径直接抛错不落日志
created_at timestamp 记录时间

用途:检索行为可观测(命中率 / 延迟)与审计追溯。只 INSERT不 UPDATE / DELETE。

2.3 跨模块只读依赖

pbl_research_aggregate 只读聚合 pbl_assessment_record(属 pbl_assessment 模块, M6 产出)的 total_score / band / assessor_type / blueprint_id 字段——跨模块只读, 不写不建表,符合交互/扩展层模块的数据所有权边界。

3. 契约接口3 个,全部只读)

三个契约均为 wwwroot/api/*.dspy 薄封装 → 调用 pbl_kdb_ext/api.py 中的同名 async 函数 (函数经 load_pbl_kdb_ext() 注册到 ServerEnvdspy 内直接以全局名调用,无需 import

# 契约名 HTTP 路径(自动路由) 入参 返回要点
1 pbl_kdb_search /pbl_kdb_ext/api/pbl_kdb_search.dspy item_typesourcekeywordlimit(≤50默认 10)、caller_type {ok, data[], hit_count, latency_ms, writable:false, stub};固定过滤 tenant_id + is_write_locked=1 + status='published';写一条 pbl_kdb_query_log
2 pbl_kdb_get /pbl_kdb_ext/api/pbl_kdb_get.dspy idcode {ok, data{...payload_json 已反序列化}, is_write_locked:true, stub};未命中抛 PBL_KDB_NOT_FOUND
3 pbl_research_aggregate /pbl_kdb_ext/api/pbl_research_aggregate.dspy group_by ∈ {band, assessor_type, blueprint_id}(默认 band {ok, dimension, groups[{group,n,avg_score,min_score,max_score}], suppressed_groups, k_min:5, anonymous:true, written:false, note};维度非法抛 PBL_AGG_DIM_INVALID

错误码沿用 pbl_common 统一错误体系(PblErrorPBL_KDB_NOT_FOUNDPBL_AGG_DIM_INVALID。 本模块不另设 errors.py——错误类型与错误码常量由公共内核 pbl_common.api 统一提供, 模块内只做抛出,避免与公共内核重复定义(交付清单与磁盘文件一一对应)。

4. 目录结构

pbl_kdb_ext/
├── pbl_kdb_ext/                 # Python 包(包目录 = 模块名)
│   ├── __init__.py              # ② 导出三个契约函数(三处同步之②)
│   ├── init.py                  # ③ load_pbl_kdb_ext()env.<契约> = <契约>
│   └── api.py                   # ① 契约实现(只读检索 / 详情 / 匿名聚合)
├── wwwroot/
│   ├── index.ui                 # 模块入口页(三张卡片 → urlwidget 到内容区)
│   └── api/
│       ├── pbl_kdb_search.dspy
│       ├── pbl_kdb_get.dspy
│       └── pbl_research_aggregate.dspy
├── models/
│   ├── pbl_kdb_item.json        # 表定义(四段式 summary/fields/indexes/codes
│   └── pbl_kdb_query_log.json
├── json/                        # CRUD 定义(只读浏览用,无 editable 写入 URL
│   ├── kdb_ext_pbl_kdb_item.json
│   └── kdb_ext_pbl_kdb_query_log.json
├── scripts/load_path.py         # RBAC 路径注册(显式枚举,无通配符)
├── sql/pbl_kdb_ext.sql          # DDL建表脚本
├── skill/SKILL.md               # Agent 必读模块技能文档
├── pyproject.toml
└── README.md                    # 本文件

5. 挂载方式(宿主应用集成)

模块不是独立部署单元——无自己的 app.py / 端口 / Dockerfile / 服务脚本,只能由宿主应用 apps/pbls)挂载:

# apps/pbls/app/pbls.py 的 init() 中ServerEnv() 与基础模块加载之后:
from pbl_kdb_ext.init import load_pbl_kdb_ext
load_pbl_kdb_ext()          # 返回 'pbl_kdb_ext'

load_pbl_kdb_ext()唯一集成点,做且只做一件事:把三个契约函数注册到 ServerEnv env.pbl_kdb_search / env.pbl_kdb_get / env.pbl_research_aggregate),使 .dspy / .ui 能以全局名直接调用。宿主决定何时、以何顺序挂载。

5.1 三处同步铁律

新增/删除一个契约函数必须同步改三处,缺一即运行期报错:

  1. pbl_kdb_ext/api.py —— 函数实现;
  2. pbl_kdb_ext/__init__.py —— from .api import <fn> 导出(缺 → ImportError / AttributeError
  3. pbl_kdb_ext/init.py —— env.<fn> = <fn>(缺 → dspy 内 NameError: name '<fn>' is not defined)。

核对命令:grep -rn 'pbl_kdb_search' pbl_kdb_ext/ --include='*.py' 应恰好命中这三处。

5.2 RBAC 注册(scripts/load_path.py

apps/pbls/build.sh 调用 register(),逐条注册(显式枚举,禁止 % / * 通配符

路径 角色
/pbl_kdb_ext logined
/pbl_kdb_ext/index.ui logined
/pbl_kdb_ext/api/pbl_kdb_search.dspy logined
/pbl_kdb_ext/api/pbl_kdb_get.dspy logined
/pbl_kdb_ext/api/pbl_research_aggregate.dspy logined

入口页的目录路径与文件路径都要注册,否则入口页上线即 403。set_role_perm.py 不在位时 脚本打印 PENDING <role> <path> 清单并以退出码 1 返回(不静默跳过、不抛异常),由部署角色 手工补注册。本模块全部接口为登录可读,无 any(匿名)路径,也无 teacher/admin 专属写路径 (因为没有写接口)。

5.3 数据库名

模块内不硬编码库名,统一通过 pbl_common 的 DB 适配层(sql_rows / sql_exec'pbl' 逻辑库标识)由宿主应用的 get_module_dbname() 解析到真实库。

6. 只读 / 匿名聚合约束说明(验收要点)

  1. 零写入:模块内无 kdb.write / research.write / sim.* 任何实现; grep -rn "def .*write\|def .*sim_" pbl_kdb_ext/ 结果为空。
  2. 写锁定pbl_kdb_search 的 WHERE 固定包含 is_write_locked = 1pbl_kdb_get 返回体固定 is_write_locked: true;无任何路径可修改该字段。
  3. 租户隔离:三个契约的 SQL 全部以 tenant_id = ${t}$ 打头(tenant_id() 取自 pbl_common 请求上下文),缺失租户上下文即报错,不降级为全租户查询。
  4. 匿名聚合(隐私线)k_min = 5;分组样本数 < 5 的分组不出现在 groups 仅计入 suppressed_groups;返回体不含任何个体标识(无 student_id / user_id / 明细行)。
  5. 聚合维度白名单group_by 仅允许 band / assessor_type / blueprint_id 其它值抛 PBL_AGG_DIM_INVALID(同时杜绝 SQL 注入——维度名不来自自由文本拼接)。
  6. 参数化 SQL:全部使用 sqlor ${name}$ 占位符,无字符串拼接用户输入。
  7. 桩标记透明:检索/详情返回体带 stub: 'kdb_stub: no vector store (Q-OPEN-5)' 调用方可据此判断结果来自结构化匹配而非语义向量检索。
  8. 可观测:每次检索落一条 pbl_kdb_query_loghit_count / latency_ms / query_json 用于命中率与延迟分析;日志表 append-only。

7. 依赖

  • 基础/公共pbl_common租户上下文、DB 适配、PblError 错误码、actor_id / now_str / json_dump 工具)、ahserverServerEnvsqlor(数据访问)。
  • 数据依赖(只读)pbl_assessmentpbl_assessment_record(聚合数据源)。
  • 被依赖pbl_agent_runtimeDesigner/Critic Agent 检索知识条目)、 pbl_scense_ext / 教师端页面(研究数据聚合展示)。

8. 本地校验

cd modules/pbl_kdb_ext
python3 -m py_compile pbl_kdb_ext/*.py scripts/load_path.py     # .py 编译
python3 -c "import json;[json.load(open('models/'+f)) for f in ('pbl_kdb_item.json','pbl_kdb_query_log.json')]"
python3 -c "import json;json.load(open('wwwroot/index.ui'));print('index.ui JSON OK')"
grep -rn "^import\|^from" wwwroot/ --include='*.dspy'           # dspy 审计:应为空
grep -rn "entire_url(" wwwroot/index.ui | grep -v "/pbl_kdb_ext/"  # 绝对路径核对:应为空

.dspy 不能用 py_compile 校验(框架运行期注入 async 函数体,顶层 return/await 合法), 只做 grep 审计。

Description
No description provided
Readme 25 KiB