63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
"""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]
|