feat: 完成 issue #46 RAG 知识库模板化接入(工艺规范/SOP/国标)

- core/rag-kb:领域 RAG 知识库按模板配置(PRD 5.4/7.3,EPIC #6 子任务)
- templating.py:知识源三类分类 + 模板命名推导 + 零依赖轻量 YAML 配置加载
- documents.py:文档段落抽取分块,chunk 携带 文档/章节/段落 溯源信息
- store.py:from_template_config 按模板自动构建 + 中英混合词频检索 + 类别过滤
- config/kb.template.yaml:ti-cl4 模板 RAG 库资产示例(工艺规范/SOP/国标)
- tests:29 用例全绿(模板化/分块溯源/检索排序/类别过滤/配置校验)
This commit is contained in:
2026-08-04 16:31:31 +08:00
parent 9c3e59695a
commit 1fb1d278d5
11 changed files with 1135 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""模板化(templating)单元测试:知识源分类 / 命名推导 / YAML 配置加载。"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import _bootstrap # noqa: F401
from rag_kb.templating import ( # noqa: E402
KbTemplateConfig,
KbTemplateNaming,
KnowledgeSourceKind,
SOURCE_KINDS,
load_kb_config,
sanitize,
)
CONFIG_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"config", "kb.template.yaml",
)
class SanitizeTest(unittest.TestCase):
def test_lower_and_replace_unsafe(self):
# 小写 + 空格等非安全字符替换为 `_`;RAG 命名**保留中文**(文档对象键)
self.assertEqual(sanitize("Ti-Cl4 模板"), "ti-cl4_模板")
self.assertEqual(sanitize("A.B-c_d"), "a.b-c_d")
def test_chinese_kept(self):
self.assertEqual(sanitize("沸腾氯化工艺规范"), "沸腾氯化工艺规范")
# `/` 属非安全字符,替换为 `_`
self.assertEqual(sanitize("GB/T 氯气安全使用标准"), "gb_t_氯气安全使用标准")
def test_empty_fallback(self):
self.assertEqual(sanitize(""), "kb")
self.assertEqual(sanitize("..."), "kb")
class SourceKindTest(unittest.TestCase):
def test_kinds_registered(self):
# 三类知识源固定注册(PRD 7.3:工艺规范 / SOP / 国标)
self.assertEqual(
set(SOURCE_KINDS),
{"process", "sop", "standard"},
)
def test_labels(self):
self.assertEqual(KnowledgeSourceKind.PROCESS.label, "工艺规范")
self.assertEqual(KnowledgeSourceKind.SOP.label, "SOP/操作手册")
self.assertEqual(KnowledgeSourceKind.STANDARD.label, "国标/标准")
class NamingTest(unittest.TestCase):
def setUp(self):
self.naming = KbTemplateNaming(template="ti-cl4")
def test_collection(self):
# 向量库 collection:下划线形态(对齐 data-bus `tpl_{tpl}` schema)
self.assertEqual(self.naming.collection(), "ti_cl4_kb")
def test_index_and_namespace(self):
self.assertEqual(self.naming.index_name(), "ti_cl4_kb_idx")
self.assertEqual(self.naming.namespace(), "tpl-ti-cl4-kb")
def test_doc_object_key(self):
self.assertEqual(
self.naming.doc_object_key("沸腾氯化工艺规范"),
"kb/沸腾氯化工艺规范.md",
)
def test_default_template(self):
self.assertEqual(KbTemplateNaming("").collection(), "kb_kb")
class YamlLoadTest(unittest.TestCase):
def test_load_example_config(self):
config = load_kb_config(CONFIG_PATH)
self.assertEqual(config.template, "ti-cl4")
self.assertEqual(config.version, "1.0.0")
# 三类知识源各至少声明一份文档
self.assertIn(KnowledgeSourceKind.PROCESS, {s.kind for s in config.sources})
self.assertIn(KnowledgeSourceKind.SOP, {s.kind for s in config.sources})
self.assertIn(KnowledgeSourceKind.STANDARD, {s.kind for s in config.sources})
self.assertGreaterEqual(len(config.all_documents()), 3)
def test_documents_for_kind(self):
config = load_kb_config(CONFIG_PATH)
docs = config.documents_for(KnowledgeSourceKind.PROCESS)
self.assertIn("沸腾氯化工艺规范", docs)
self.assertEqual(
config.documents_for(KnowledgeSourceKind.STANDARD),
["GB/T 氯气安全使用标准", "GB/T 钛及钛合金加工标准"],
)
def test_unknown_kind_rejected(self):
# 未知知识源类别必须报错(杜绝配置拼写漂移)
import tempfile
with tempfile.NamedTemporaryFile("w", suffix=".yaml", delete=False,
encoding="utf-8") as fh:
fh.write("template: ti-cl4\nsources:\n - kind: hmm\n documents: [a]\n")
path = fh.name
try:
with self.assertRaises(ValueError):
load_kb_config(path)
finally:
os.unlink(path)
def test_missing_template_rejected(self):
import tempfile
with tempfile.NamedTemporaryFile("w", suffix=".yaml", delete=False,
encoding="utf-8") as fh:
fh.write("version: 1.0.0\nsources: []\n")
path = fh.name
try:
with self.assertRaises(ValueError):
load_kb_config(path)
finally:
os.unlink(path)
if __name__ == "__main__":
unittest.main()