"""kb_block 注入契约:零库冷启动 vs 有库全量。 背景(2026-07-24):零库时 kb_block 返回空串,agent 不知道 KB 机制存在,用户说 「放进我的知识库」被就近写进 .memory/。修复 = 零库注极简冷启动契约(建库步骤 + INDEX 行格式 + 知识库/记忆分工),与 memory 空契约常驻同一课。本测试锁住两个分支。 """ from __future__ import annotations import sys import tempfile import unittest from pathlib import Path from uuid import UUID sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from core.kb import ( # noqa: E402 INDEX_LINE_FORMAT, create_kb, format_index_line, kb_block, ) _UID = UUID("6b14d2ab-7a6e-4d0b-8562-ea62a801e32c") class TestKbBlock(unittest.TestCase): def setUp(self): self.ws = Path(tempfile.mkdtemp()) / "workspace" def test_cold_start_injects_minimal_contract(self): block = kb_block(self.ws, _UID, "/workspace/.kb") # 机制存在 + 建库/入库指引 + 行格式契约都在 self.assertIn("个人知识库", block) self.assertIn("还没有任何知识库", block) self.assertIn("/workspace/.kb/<库名>/docs/", block) self.assertIn(INDEX_LINE_FORMAT, block) # 与记忆的分工写明(误写 .memory 正是要防的失败模式) self.assertIn("短事实才写记忆", block) # 冷启动块不含任何库清单 self.assertNotIn("### 库「", block) def test_cold_start_host_path_display(self): block = kb_block(self.ws, _UID, None) root = self.ws / "users" / str(_UID) / ".kb" self.assertIn(str(root), block) def test_with_kb_injects_full_index(self): d = create_kb(self.ws, _UID, "标准库") assert d is not None line = format_index_line( title="水泥胶砂强度检验方法", doc="docs/gbt17671.md", source="sources/gbt17671.pdf", summary="ISO 法测定水泥胶砂抗压抗折强度。", keywords="水泥,强度,GB/T 17671", ) (d / "INDEX.md").write_text(f"# 标准库\n\n{line}\n", encoding="utf-8") block = kb_block(self.ws, _UID, "/workspace/.kb") # 全量分支:库清单 + 条目 + 完整契约(对话内入库段) self.assertIn("### 库「标准库」", block) self.assertIn("水泥胶砂强度检验方法", block) self.assertIn("对话内入库", block) # 冷启动文案不应出现 self.assertNotIn("还没有任何知识库", block) def test_empty_kb_counts_as_having_kb(self): # 建了库但零文档:走全量分支(注库名 + 空库提示),不再是冷启动 create_kb(self.ws, _UID, "新库") block = kb_block(self.ws, _UID, "/workspace/.kb") self.assertIn("### 库「新库」", block) self.assertIn("空库", block) self.assertNotIn("还没有任何知识库", block) if __name__ == "__main__": unittest.main()