union_kb_ingest 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -13
- package/config/config.yaml +2 -2
- package/ingest.py +28 -33
- package/normalizer.py +491 -42
- package/package.json +2 -3
- package/parser.py +116 -1
- package/prompts/generate_kb_items.md +2 -2
- package/prompts//350/201/224/345/220/210/350/277/220/347/273/264/347/237/245/350/257/206/345/272/223/345/273/272/347/253/213/350/247/204/350/214/203.md +105 -213
- package/requirements.txt +1 -1
- package/schemas.py +14 -2
- package/splitter.py +6 -0
- package/validator.py +63 -6
- package/writer.py +4 -1
- package/ArkKickidcService.java +0 -578
- package/drafts/.gitkeep +0 -1
- /package/{approved → result}/.gitkeep +0 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
2. 通过 Docling slim 的离线文本解析能力生成统一 Markdown 中间格式。
|
|
9
9
|
3. 按章节、场景、规则、指标等粒度切割。
|
|
10
10
|
4. 可选调用大模型,把内容整理为项目知识库规范要求的 Markdown 文件。
|
|
11
|
-
5.
|
|
11
|
+
5. 默认生成可直接交给知识库项目使用的 `status: active` Markdown 文件。
|
|
12
12
|
|
|
13
13
|
启用大模型时,工具会把 `prompts/联合运维知识库建立规范.md` 作为格式和质量约束放入提示词,要求模型依据原文语义判断业务场景、模块、角色、标签和风险等级。代码中的启发式生成只作为未启用大模型或调用失败时的兜底,不使用预设业务关键词去指导大模型输出。
|
|
14
14
|
|
|
@@ -37,13 +37,13 @@ python -m pip install -r requirements.txt
|
|
|
37
37
|
input/
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
生成知识库文件:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
python ingest.py draft
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
如果 `
|
|
46
|
+
如果 `result/` 中已有生成文件,命令会先询问是否覆盖。选择 `y` 后会清空 `result/` 中已有生成文件,再重新生成;选择其他内容会直接退出,避免多次生成结果相互影响。
|
|
47
47
|
|
|
48
48
|
只解析为中间 Markdown:
|
|
49
49
|
|
|
@@ -51,25 +51,23 @@ python ingest.py draft
|
|
|
51
51
|
python ingest.py parse
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
校验生成结果:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
python ingest.py validate
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
默认目录为 `input/`、`parsed/` 和 `result/`。只有需要处理其他目录时,才使用 `--input` 或 `--output` 覆盖。
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
python ingest.py promote
|
|
64
|
-
```
|
|
62
|
+
`draft` 默认按 `config/config.yaml` 的 `draft.max_chars` 控制单次送入模型的原文长度,并额外提供文档目录和相邻片段摘要作为辅助上下文。这样可以降低私有模型单轮负载,同时尽量保留前后章节关系。命令行仍可用 `--max-chars` 临时覆盖。
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
每条知识库文件会写入分类画像元数据:`category`、`category_description` 和 `category_keywords`。这些字段优先来自源文件一级标题、首页标题、章节目录和文件名,用于标识一个批次/业务场景的大类。后续 RAG 入库和检索时,应把这些字段写入向量库 metadata,并用于分类过滤、查询路由或重排加权,降低不同场景之间因为相似词命中而串场的概率。
|
|
67
65
|
|
|
68
|
-
`
|
|
66
|
+
生成结果会按原始输入遍历顺序写入 `source_order`,并用 `000001-...md` 这样的文件名前缀保持目录排序与原文从上到下的顺序一致。页码只写入 Front Matter 的 `source_pages`/`source_trace` 和正文 `## 5. 来源依据`,不会进入正文 `## 1. 核心内容` 到 `## 4. 关联能力`。
|
|
69
67
|
|
|
70
68
|
## 大模型配置
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
默认不强制调用大模型,会使用启发式模板生成知识库文件。
|
|
73
71
|
|
|
74
72
|
如果要启用大模型整理,修改 `config/config.yaml`:
|
|
75
73
|
|
|
@@ -98,10 +96,10 @@ export KB_LLM_API_KEY="your-zhipu-api-key"
|
|
|
98
96
|
export KB_LLM_MODEL="glm-4.7"
|
|
99
97
|
```
|
|
100
98
|
|
|
101
|
-
工具通过
|
|
99
|
+
工具通过 Z.AI 新版 Python SDK 调用中文智谱开放平台 GLM,依赖固定为 `zai-sdk==0.2.2`,客户端固定使用官方中文写法 `from zai import ZhipuAiClient`,`base_url` 使用 `https://open.bigmodel.cn/api/paas/v4/`。工具不再包含旧 `zhipuai` SDK、国际版 `ZaiClient` 或 OpenAI 调用路径,也不 import 项目 `src` 代码。
|
|
102
100
|
|
|
103
101
|
## 与线上项目的关系
|
|
104
102
|
|
|
105
|
-
这个工具只产出符合规范的 `*.md`
|
|
103
|
+
这个工具只产出符合规范的 `*.md` 文件到 `result/`,后续由线上知识库加载流程处理。
|
|
106
104
|
|
|
107
105
|
建议线上打包时排除整个 `tools/kb_ingest` 目录。
|
package/config/config.yaml
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
llm:
|
|
2
|
-
enabled:
|
|
2
|
+
enabled: false
|
|
3
3
|
timeout_seconds: 120
|
|
4
4
|
max_tokens: 8192
|
|
5
5
|
temperature: 0.1
|
|
6
|
-
api_key: "
|
|
6
|
+
api_key: "your-zhipu-api-key"
|
|
7
7
|
model: "GLM-4.7-Flash"
|
|
8
8
|
base_url: "https://open.bigmodel.cn/api/paas/v4/"
|
|
9
9
|
|
package/ingest.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
|
-
import shutil
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import List
|
|
@@ -41,20 +40,19 @@ def cmd_parse(args) -> int:
|
|
|
41
40
|
def cmd_draft(args) -> int:
|
|
42
41
|
input_path = Path(args.input)
|
|
43
42
|
output_dir = Path(args.output)
|
|
44
|
-
approved_dir = Path(args.approved_dir)
|
|
45
|
-
result_dir = Path(args.result_dir)
|
|
46
43
|
|
|
47
44
|
existing = _list_effective_files(output_dir)
|
|
48
|
-
if existing and not _confirm_overwrite(output_dir,
|
|
45
|
+
if existing and not _confirm_overwrite(output_dir, existing):
|
|
49
46
|
print("aborted. existing files were kept.")
|
|
50
47
|
return 0
|
|
51
48
|
|
|
52
49
|
if existing:
|
|
53
|
-
_clear_generated_files(output_dir
|
|
50
|
+
_clear_generated_files(output_dir)
|
|
54
51
|
|
|
55
52
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
56
53
|
|
|
57
54
|
total_items = 0
|
|
55
|
+
source_order = 0
|
|
58
56
|
draft_config = get_draft_config()
|
|
59
57
|
max_chars = args.max_chars or draft_config.max_chars
|
|
60
58
|
files = iter_input_files(input_path)
|
|
@@ -67,7 +65,11 @@ def cmd_draft(args) -> int:
|
|
|
67
65
|
outline_max_sections=draft_config.outline_max_sections,
|
|
68
66
|
)
|
|
69
67
|
for block in blocks:
|
|
70
|
-
for item in normalize_block(block, status=
|
|
68
|
+
for item in normalize_block(block, status=args.status):
|
|
69
|
+
source_order += 1
|
|
70
|
+
item.source_order = source_order
|
|
71
|
+
item.source_pages = sorted(set(block.pages))
|
|
72
|
+
item.source_trace = _source_trace(block)
|
|
71
73
|
write_item(item, output_dir)
|
|
72
74
|
total_items += 1
|
|
73
75
|
print(f"drafted: {path} blocks={len(blocks)}")
|
|
@@ -86,15 +88,11 @@ def _list_effective_files(path: Path) -> list[Path]:
|
|
|
86
88
|
|
|
87
89
|
def _confirm_overwrite(
|
|
88
90
|
output_dir: Path,
|
|
89
|
-
approved_dir: Path,
|
|
90
|
-
result_dir: Path,
|
|
91
91
|
existing: list[Path],
|
|
92
92
|
) -> bool:
|
|
93
93
|
print(f"found {len(existing)} existing file(s) in {output_dir}.")
|
|
94
94
|
print("Continuing will delete existing generated files under:")
|
|
95
95
|
print(f"- {output_dir}")
|
|
96
|
-
print(f"- {approved_dir}")
|
|
97
|
-
print(f"- {result_dir}")
|
|
98
96
|
answer = input("Overwrite and continue? [y/N]: ").strip().lower()
|
|
99
97
|
return answer in {"y", "yes"}
|
|
100
98
|
|
|
@@ -120,6 +118,13 @@ def _attach_block_context(
|
|
|
120
118
|
parts = []
|
|
121
119
|
if outline:
|
|
122
120
|
parts.append(f"文档章节目录:\n{outline}")
|
|
121
|
+
if block.category_description:
|
|
122
|
+
parts.append(
|
|
123
|
+
"知识大类说明:\n"
|
|
124
|
+
f"大类:{block.category}\n"
|
|
125
|
+
f"说明:{block.category_description}\n"
|
|
126
|
+
f"关键词:{', '.join(block.category_keywords)}"
|
|
127
|
+
)
|
|
123
128
|
if idx > 0:
|
|
124
129
|
parts.append(
|
|
125
130
|
"上一片段摘要:\n"
|
|
@@ -139,6 +144,9 @@ def _attach_block_context(
|
|
|
139
144
|
pages=block.pages,
|
|
140
145
|
order=block.order,
|
|
141
146
|
context="\n\n".join(parts),
|
|
147
|
+
category=block.category,
|
|
148
|
+
category_description=block.category_description,
|
|
149
|
+
category_keywords=block.category_keywords,
|
|
142
150
|
))
|
|
143
151
|
return output
|
|
144
152
|
|
|
@@ -167,6 +175,13 @@ def _compact_context_text(text: str, limit: int) -> str:
|
|
|
167
175
|
return compact[:limit].rstrip() + "..."
|
|
168
176
|
|
|
169
177
|
|
|
178
|
+
def _source_trace(block: ParsedBlock) -> str:
|
|
179
|
+
parts = [f"section={block.source_section}"]
|
|
180
|
+
if block.pages:
|
|
181
|
+
parts.append(f"pages={','.join(map(str, sorted(set(block.pages))))}")
|
|
182
|
+
return "; ".join(parts)
|
|
183
|
+
|
|
184
|
+
|
|
170
185
|
def cmd_validate(args) -> int:
|
|
171
186
|
issues = validate_dir(Path(args.input))
|
|
172
187
|
for issue in issues:
|
|
@@ -175,20 +190,6 @@ def cmd_validate(args) -> int:
|
|
|
175
190
|
return 1 if any(i.level == "error" for i in issues) else 0
|
|
176
191
|
|
|
177
192
|
|
|
178
|
-
def cmd_promote(args) -> int:
|
|
179
|
-
input_dir = Path(args.input)
|
|
180
|
-
result_dir = Path(args.result_dir)
|
|
181
|
-
result_dir.mkdir(parents=True, exist_ok=True)
|
|
182
|
-
count = 0
|
|
183
|
-
for path in sorted(input_dir.rglob("*.md")):
|
|
184
|
-
target = result_dir / path.name
|
|
185
|
-
shutil.copy2(path, target)
|
|
186
|
-
count += 1
|
|
187
|
-
print(f"promoted: {path} -> {target}")
|
|
188
|
-
print(f"done. promoted={count}")
|
|
189
|
-
return 0
|
|
190
|
-
|
|
191
|
-
|
|
192
193
|
def build_parser() -> argparse.ArgumentParser:
|
|
193
194
|
parser = argparse.ArgumentParser(description="Offline document-to-knowledge Markdown generator.")
|
|
194
195
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -200,21 +201,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
200
201
|
|
|
201
202
|
draft_cmd = sub.add_parser("draft", help="Generate draft knowledge files.")
|
|
202
203
|
draft_cmd.add_argument("--input", default=str(CURRENT_DIR / "input"))
|
|
203
|
-
draft_cmd.add_argument("--output", default=str(CURRENT_DIR / "
|
|
204
|
-
draft_cmd.add_argument("--
|
|
205
|
-
draft_cmd.add_argument("--result-dir", default=str(CURRENT_DIR / "result"))
|
|
204
|
+
draft_cmd.add_argument("--output", default=str(CURRENT_DIR / "result"))
|
|
205
|
+
draft_cmd.add_argument("--status", default="active", choices=["draft", "active"])
|
|
206
206
|
draft_cmd.add_argument("--max-chars", type=int, default=None)
|
|
207
207
|
draft_cmd.set_defaults(func=cmd_draft)
|
|
208
208
|
|
|
209
209
|
validate_cmd = sub.add_parser("validate", help="Validate generated Markdown files.")
|
|
210
|
-
validate_cmd.add_argument("--input", default=str(CURRENT_DIR / "
|
|
210
|
+
validate_cmd.add_argument("--input", default=str(CURRENT_DIR / "result"))
|
|
211
211
|
validate_cmd.set_defaults(func=cmd_validate)
|
|
212
212
|
|
|
213
|
-
promote_cmd = sub.add_parser("promote", help="Copy reviewed files to result.")
|
|
214
|
-
promote_cmd.add_argument("--input", default=str(CURRENT_DIR / "approved"))
|
|
215
|
-
promote_cmd.add_argument("--result-dir", default=str(CURRENT_DIR / "result"))
|
|
216
|
-
promote_cmd.set_defaults(func=cmd_promote)
|
|
217
|
-
|
|
218
213
|
return parser
|
|
219
214
|
|
|
220
215
|
|