11 KiB
pbl_kdb_ext —— PBL 知识库(KDB)只读桩扩展模块(M7)
1. 模块用途
pbl_kdb_ext 是 PBL(Project/Problem-Based Learning)Agent 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() 注册到 ServerEnv,dspy 内直接以全局名调用,无需 import)。
| # | 契约名 | HTTP 路径(自动路由) | 入参 | 返回要点 |
|---|---|---|---|---|
| 1 | pbl_kdb_search |
/pbl_kdb_ext/api/pbl_kdb_search.dspy |
item_type、source、keyword、limit(≤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 |
id 或 code |
{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 统一错误体系(PblError):PBL_KDB_NOT_FOUND、PBL_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 三处同步铁律
新增/删除一个契约函数必须同步改三处,缺一即运行期报错:
pbl_kdb_ext/api.py—— 函数实现;pbl_kdb_ext/__init__.py——from .api import <fn>导出(缺 → ImportError / AttributeError);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. 只读 / 匿名聚合约束说明(验收要点)
- 零写入:模块内无
kdb.write/research.write/sim.*任何实现;grep -rn "def .*write\|def .*sim_" pbl_kdb_ext/结果为空。 - 写锁定:
pbl_kdb_search的 WHERE 固定包含is_write_locked = 1;pbl_kdb_get返回体固定is_write_locked: true;无任何路径可修改该字段。 - 租户隔离:三个契约的 SQL 全部以
tenant_id = ${t}$打头(tenant_id()取自pbl_common请求上下文),缺失租户上下文即报错,不降级为全租户查询。 - 匿名聚合(隐私线):
k_min = 5;分组样本数< 5的分组不出现在groups中, 仅计入suppressed_groups;返回体不含任何个体标识(无 student_id / user_id / 明细行)。 - 聚合维度白名单:
group_by仅允许band/assessor_type/blueprint_id, 其它值抛PBL_AGG_DIM_INVALID(同时杜绝 SQL 注入——维度名不来自自由文本拼接)。 - 参数化 SQL:全部使用 sqlor
${name}$占位符,无字符串拼接用户输入。 - 桩标记透明:检索/详情返回体带
stub: 'kdb_stub: no vector store (Q-OPEN-5)', 调用方可据此判断结果来自结构化匹配而非语义向量检索。 - 可观测:每次检索落一条
pbl_kdb_query_log(hit_count / latency_ms / query_json), 用于命中率与延迟分析;日志表 append-only。
7. 依赖
- 基础/公共:
pbl_common(租户上下文、DB 适配、PblError错误码、actor_id/now_str/json_dump工具)、ahserver(ServerEnv)、sqlor(数据访问)。 - 数据依赖(只读):
pbl_assessment的pbl_assessment_record(聚合数据源)。 - 被依赖:
pbl_agent_runtime(Designer/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 审计。