"""RAG 管道:标题感知的连续窗口分块。""" import re TARGET = 500 MIN_CHUNK = 120 OVERLAP_RATIO = 0.15 def chunk_texts(units: list[dict], target: int = TARGET) -> list[dict]: """连续合并相同元数据的相邻单元,避免字幕类文件被切成大量细碎向量。""" target = max(MIN_CHUNK, int(target or TARGET)) chunks = [] buf: list[str] = [] buf_len = 0 active_meta: dict | None = None def flush() -> None: nonlocal buf, buf_len if buf: text = "".join(buf) if len(text) >= 30: chunks.append({"text": text, "meta": dict(active_meta or {})}) buf, buf_len = [], 0 for u in units: text = str(u.get("text") or "") meta = dict(u.get("meta") or {}) if not text: continue if active_meta is not None and meta != active_meta: flush() if active_meta is None or meta != active_meta: active_meta = meta # 按句子/标点切候选段 parts = _split_sentences(text) for p in parts: if buf_len + len(p) > target * 1.15 and buf: chunks.append({"text": "".join(buf), "meta": dict(active_meta)}) # 重叠:保留最后 15% 字符 overlap = "".join(buf) cut = int(len(overlap) * OVERLAP_RATIO) keep = overlap[-cut:] if cut > 30 else "" buf = [keep] if keep else [] buf_len = len(keep) buf.append(p) buf_len += len(p) flush() return chunks def _split_sentences(text: str) -> list[str]: # 在句号/问号/感叹号/换行后切分,保持小段不小于 30 字 parts = re.split(r"(?<=[。!?!?;;])\s*|\n+", text) merged, cur = [], "" for p in parts: if not p: continue cur += p if len(cur) >= 30: merged.append(cur) cur = "" if cur: merged.append(cur) return merged or [text]