Coverage for agentos/rag/loader.py: 20%
80 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 23:17 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 23:17 +0800
1"""
2文档加载器 — PDF / DOCX / TXT / Markdown 解析与分块。
4零外部 HTTP 依赖,纯本地解析。
5"""
7from __future__ import annotations
9import os
10from pathlib import Path
13class Document:
14 """文档片段。"""
16 def __init__(self, content: str, source: str = "", page: int = 0, metadata: dict | None = None):
17 self.content = content
18 self.source = source
19 self.page = page
20 self.metadata = metadata or {}
22 def __repr__(self):
23 return f"Document(source={self.source!r}, chars={len(self.content)})"
26class DocumentLoader:
27 """文档加载器 — 支持多种格式的文档解析与智能分块。
29 Args:
30 chunk_size: 分块大小(字符数)
31 chunk_overlap: 块间重叠字符数
32 """
34 SUPPORTED_SUFFIXES = {
35 ".pdf",
36 ".docx",
37 ".txt",
38 ".md",
39 ".markdown",
40 ".py",
41 ".json",
42 ".yaml",
43 ".yml",
44 }
46 def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):
47 self.chunk_size = chunk_size
48 self.chunk_overlap = chunk_overlap
50 def load_file(self, path: str) -> list[Document]:
51 """加载单个文件,自动识别格式。"""
52 path = os.path.abspath(path)
53 suffix = Path(path).suffix.lower()
54 if suffix not in self.SUPPORTED_SUFFIXES:
55 raise ValueError(f"不支持的文件格式: {suffix}。支持: {self.SUPPORTED_SUFFIXES}")
57 if suffix == ".pdf":
58 text = self._read_pdf(path)
59 elif suffix == ".docx":
60 text = self._read_docx(path)
61 else:
62 with open(path, encoding="utf-8", errors="replace") as f:
63 text = f.read()
65 return self._chunk(text, source=path)
67 def load_directory(self, dir_path: str, recursive: bool = True) -> list[Document]:
68 """加载目录下所有支持的文件。"""
69 docs = []
70 for root, _, files in os.walk(dir_path):
71 for fn in sorted(files):
72 fp = os.path.join(root, fn)
73 suffix = Path(fp).suffix.lower()
74 if suffix in self.SUPPORTED_SUFFIXES:
75 try:
76 docs.extend(self.load_file(fp))
77 except Exception:
78 pass
79 if not recursive:
80 break
81 return docs
83 def _read_pdf(self, path: str) -> str:
84 """读取 PDF 文本。"""
85 try:
86 import pypdf
88 reader = pypdf.PdfReader(path)
89 pages = []
90 for page in reader.pages:
91 text = page.extract_text()
92 if text:
93 pages.append(text)
94 return "\n\n".join(pages)
95 except ImportError:
96 raise ImportError("pypdf 未安装。运行: pip install pypdf")
98 def _read_docx(self, path: str) -> str:
99 """读取 DOCX 文本。"""
100 try:
101 from docx import Document as DocxDocument
103 doc = DocxDocument(path)
104 paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
105 return "\n".join(paragraphs)
106 except ImportError:
107 raise ImportError("python-docx 未安装。运行: pip install python-docx")
109 def _chunk(self, text: str, source: str = "") -> list[Document]:
110 """固定大小+重叠分块。"""
111 if len(text) <= self.chunk_size:
112 return [Document(content=text.strip(), source=source)]
114 chunks = []
115 start = 0
116 while start < len(text):
117 end = min(start + self.chunk_size, len(text))
118 chunk = text[start:end].strip()
119 if chunk:
120 chunks.append(Document(content=chunk, source=source))
121 start += self.chunk_size - self.chunk_overlap
122 return chunks
125# ── 便捷函数 ──────────────────────────────────────────────────
128def load_file(path: str, chunk_size: int = 1000, chunk_overlap: int = 200) -> list[Document]:
129 """便捷函数:加载单个文件。"""
130 loader = DocumentLoader(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
131 return loader.load_file(path)
134def load_directory(
135 dir_path: str, recursive: bool = True, chunk_size: int = 1000, chunk_overlap: int = 200
136) -> list[Document]:
137 """便捷函数:加载目录。"""
138 loader = DocumentLoader(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
139 return loader.load_directory(dir_path, recursive=recursive)