prepare_data.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from __future__ import annotations
  2. import re
  3. from pathlib import Path
  4. from app.config import PROJECT_ROOT, Settings, get_settings
  5. from app.embeddings import create_embedding_provider
  6. from app.milvus_store import IndexedDocument, MilvusVectorStore
  7. from app.sql_store import initialize_database
  8. INCIDENTS_SOURCE_PATH = PROJECT_ROOT / "data" / "source" / "incidents.csv"
  9. def split_document(path: Path) -> list[IndexedDocument]:
  10. text = path.read_text(encoding="utf-8").strip()
  11. if not text:
  12. raise ValueError(f"Milvus 原始文档为空:{path}")
  13. metadata_lines = {
  14. line.split(":", 1)[0]: line.split(":", 1)[1].strip()
  15. for line in text.splitlines()
  16. if ":" in line
  17. }
  18. service = metadata_lines.get("服务", "")
  19. fault_type = metadata_lines.get("故障类型", "")
  20. client_os = metadata_lines.get("适用系统", "")
  21. client_version = metadata_lines.get("客户端版本", "")
  22. document_type = metadata_lines.get("文档类别", "")
  23. required_metadata = {
  24. "服务": service,
  25. "故障类型": fault_type,
  26. "适用系统": client_os,
  27. "客户端版本": client_version,
  28. "文档类别": document_type,
  29. }
  30. missing_metadata = [
  31. field_name
  32. for field_name, value in required_metadata.items()
  33. if not value
  34. ]
  35. if missing_metadata:
  36. raise ValueError(
  37. f"文档缺少元数据 {', '.join(missing_metadata)}:{path.name}"
  38. )
  39. # 课程文档以二级标题作为稳定 Chunk 边界,保留章节语义和来源定位。
  40. sections = [
  41. section.strip()
  42. for section in re.split(r"(?=^## )", text, flags=re.MULTILINE)
  43. if section.strip()
  44. ]
  45. title = text.splitlines()[0].lstrip("# ")
  46. return [
  47. IndexedDocument(
  48. content=section,
  49. source=path.name,
  50. doc_type=document_type,
  51. chunk_index=index,
  52. service=service,
  53. fault_type=fault_type,
  54. client_os=client_os,
  55. client_version=client_version,
  56. metadata={
  57. "title": title,
  58. "document_type": document_type,
  59. },
  60. )
  61. for index, section in enumerate(sections)
  62. ]
  63. def prepare_sqlite(settings: Settings) -> int:
  64. return initialize_database(
  65. path=settings.sqlite_path,
  66. source_path=INCIDENTS_SOURCE_PATH,
  67. reset=True,
  68. )
  69. def prepare_milvus(settings: Settings) -> tuple[int, int, int]:
  70. document_paths = sorted(settings.documents_path.glob("*.md"))
  71. if not document_paths:
  72. raise FileNotFoundError(f"Milvus 原始文档不存在:{settings.documents_path}")
  73. # 先完成所有文档校验和切分,再连接 Milvus,避免半批脏数据。
  74. documents = [
  75. document
  76. for path in document_paths
  77. for document in split_document(path)
  78. ]
  79. embeddings = create_embedding_provider(settings)
  80. store = MilvusVectorStore(
  81. uri=settings.milvus_uri,
  82. token=settings.milvus_token.get_secret_value(),
  83. collection_name=settings.milvus_collection,
  84. embeddings=embeddings,
  85. )
  86. # 演示数据采用全量重建,确保 Schema、索引和向量维度保持一致。
  87. store.create_collection(recreate=True)
  88. inserted = store.insert_documents(documents)
  89. return len(document_paths), inserted, embeddings.dimension
  90. def main() -> None:
  91. settings = get_settings()
  92. print("数据准备开始")
  93. print(f"- SQLite 原始工单:{INCIDENTS_SOURCE_PATH}")
  94. print(f"- Milvus 原始文档:{settings.documents_path}")
  95. sqlite_rows = prepare_sqlite(settings)
  96. document_count, chunk_count, dimension = prepare_milvus(settings)
  97. print(f"SQLite 完成:database={settings.sqlite_path},rows={sqlite_rows}")
  98. print(
  99. "Milvus 完成:"
  100. f"collection={settings.milvus_collection},"
  101. f"documents={document_count},chunks={chunk_count},dimension={dimension}"
  102. )
  103. if __name__ == "__main__":
  104. main()