Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
195 lines
6.0 KiB
Python
195 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
RAG 检索结果 → OpenMAIC Requirement 转换器
|
||
|
||
将 WeKnora RAG 检索结果转换为结构化的 OpenMAIC 课程生成需求描述。
|
||
此脚本仅做数据转换,不涉及网络调用。
|
||
|
||
用法:
|
||
echo '{"chunks": [...], "audience": "初学者"}' | python scripts/rag-to-requirement.py
|
||
python scripts/rag-to-requirement.py --file results.json
|
||
|
||
输入格式 (JSON):
|
||
{
|
||
"chunks": [
|
||
{
|
||
"document_name": "文档A.pdf",
|
||
"content": "文档内容片段...",
|
||
"metadata": {"page": 5, "section": "第三章"}
|
||
}
|
||
],
|
||
"query": "用户原始查询(可选)",
|
||
"audience": "目标受众描述(可选,默认'相关领域的学习者')",
|
||
"depth": "教学深度: beginner|intermediate|advanced(可选,默认'intermediate')",
|
||
"language": "zh-CN|en-US(可选,默认'zh-CN')",
|
||
"focus_areas": ["重点领域1", "重点领域2"] # 可选
|
||
}
|
||
|
||
输出格式 (JSON):
|
||
{
|
||
"requirement": "结构化的教学需求描述",
|
||
"metadata": {
|
||
"source_documents": ["文档A.pdf", "文档B.pdf"],
|
||
"total_chunks": 5,
|
||
"audience": "初学者",
|
||
"depth": "intermediate",
|
||
"language": "zh-CN"
|
||
}
|
||
}
|
||
"""
|
||
|
||
import json
|
||
import sys
|
||
from typing import Any
|
||
|
||
|
||
def extract_key_topics(chunks: list[dict], max_topics: int = 5) -> list[str]:
|
||
"""从文档块中提取关键主题(基于内容摘要和 section 信息)。"""
|
||
topics = []
|
||
seen = set()
|
||
|
||
for chunk in chunks:
|
||
metadata = chunk.get("metadata", {})
|
||
# 优先使用 section/chapter 信息
|
||
for key in ("section", "chapter", "heading", "title"):
|
||
if key in metadata and metadata[key] not in seen:
|
||
topics.append(metadata[key])
|
||
seen.add(metadata[key])
|
||
if len(topics) >= max_topics:
|
||
return topics
|
||
|
||
# 如果 section 信息不足,从内容前 50 字提取
|
||
for chunk in chunks:
|
||
content = chunk.get("content", "")[:50].strip()
|
||
if content and content not in seen:
|
||
topics.append(content + "...")
|
||
seen.add(content)
|
||
if len(topics) >= max_topics:
|
||
break
|
||
|
||
return topics
|
||
|
||
|
||
def build_requirement(data: dict[str, Any]) -> str:
|
||
"""将 RAG 结果构建为 OpenMAIC requirement 字符串。"""
|
||
chunks = data.get("chunks", [])
|
||
query = data.get("query", "")
|
||
audience = data.get("audience", "相关领域的学习者")
|
||
depth = data.get("depth", "intermediate")
|
||
language = data.get("language", "zh-CN")
|
||
focus_areas = data.get("focus_areas", [])
|
||
|
||
depth_map = {
|
||
"beginner": "入门",
|
||
"intermediate": "中级",
|
||
"advanced": "高级",
|
||
}
|
||
depth_cn = depth_map.get(depth, "中级")
|
||
|
||
# 提取文档名称
|
||
source_docs = list({c.get("document_name", "未知文档") for c in chunks})
|
||
|
||
# 提取关键主题
|
||
key_topics = extract_key_topics(chunks)
|
||
|
||
# 构建 requirement
|
||
parts = []
|
||
|
||
# 开头:基于什么内容,为谁创建什么课程
|
||
if query:
|
||
parts.append(f"基于以下知识库内容,为{audience}创建一个{depth_cn}课程。")
|
||
parts.append(f"用户原始需求:{query}")
|
||
else:
|
||
parts.append(f"基于以下知识库内容,为{audience}创建一个{depth_cn}课程。")
|
||
|
||
# 内容来源
|
||
if source_docs:
|
||
docs_str = "、".join(source_docs[:5])
|
||
if len(source_docs) > 5:
|
||
docs_str += f" 等{len(source_docs)}个文档"
|
||
parts.append(f"\n内容来源:{docs_str}")
|
||
|
||
# 关键主题
|
||
if key_topics:
|
||
parts.append("\n核心主题:")
|
||
for i, topic in enumerate(key_topics, 1):
|
||
parts.append(f" {i}. {topic}")
|
||
|
||
# 重点领域
|
||
if focus_areas:
|
||
parts.append("\n重点覆盖:")
|
||
for area in focus_areas:
|
||
parts.append(f" - {area}")
|
||
|
||
# 语言要求
|
||
if language == "zh-CN":
|
||
parts.append("\n请使用中文生成课程内容。")
|
||
|
||
return "\n".join(parts)
|
||
|
||
|
||
def process(input_data: dict[str, Any]) -> dict[str, Any]:
|
||
"""主处理函数。"""
|
||
chunks = input_data.get("chunks", [])
|
||
if not chunks:
|
||
return {
|
||
"requirement": input_data.get("query", ""),
|
||
"metadata": {
|
||
"source_documents": [],
|
||
"total_chunks": 0,
|
||
"error": "未提供检索结果,使用原始查询作为 requirement",
|
||
},
|
||
}
|
||
|
||
requirement = build_requirement(input_data)
|
||
|
||
source_docs = list({c.get("document_name", "未知文档") for c in chunks})
|
||
|
||
return {
|
||
"requirement": requirement,
|
||
"metadata": {
|
||
"source_documents": source_docs,
|
||
"total_chunks": len(chunks),
|
||
"audience": input_data.get("audience", "相关领域的学习者"),
|
||
"depth": input_data.get("depth", "intermediate"),
|
||
"language": input_data.get("language", "zh-CN"),
|
||
},
|
||
}
|
||
|
||
|
||
def main() -> None:
|
||
"""入口:从 stdin 或文件读取输入,输出 JSON 结果。"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description="RAG 结果 → OpenMAIC Requirement 转换器")
|
||
parser.add_argument("--file", "-f", help="输入 JSON 文件路径")
|
||
args = parser.parse_args()
|
||
|
||
# 读取输入
|
||
if args.file:
|
||
with open(args.file, "r", encoding="utf-8") as f:
|
||
input_data = json.load(f)
|
||
else:
|
||
input_text = sys.stdin.read()
|
||
if not input_text.strip():
|
||
print(
|
||
"错误: 未提供输入数据。用法:\n"
|
||
" echo '{\"chunks\": [...]}' | python rag-to-requirement.py\n"
|
||
" python rag-to-requirement.py --file input.json",
|
||
file=sys.stderr,
|
||
)
|
||
sys.exit(1)
|
||
try:
|
||
input_data = json.loads(input_text)
|
||
except json.JSONDecodeError as e:
|
||
print(f"错误: 输入 JSON 解析失败: {e}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# 处理并输出
|
||
result = process(input_data)
|
||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|