ForcePilot/backend/package/yuxi/channels/adapters/telegram/chunking.py
Kris 71fec609bb feat(telegram-adapter): 实现完整的Telegram适配器基础代码
新增了Telegram适配器的全套基础模块,包括:
1.  核心适配器入口与会话工具
2.  账号管理、认证与配置系统
3.  连接相关的轮询、Webhook、更新偏移管理
4.  话题路由、管理与缓存系统
5.  消息反抖动、超时配置与工具类
6.  响应式UI与命令交互系统
7.  反应表情与通知系统
8.  审批与安全审计模块
9.  健康检查与状态监控
10. 贴纸缓存与视觉工具
11. 流式响应与协作功能
12. 群组迁移与目标归一化处理
2026-05-12 00:49:52 +08:00

129 lines
3.8 KiB
Python

from __future__ import annotations
import re
from typing import Any, Literal
ChunkMode = Literal["length", "newline", "paragraph"]
TELEGRAM_MAX_TEXT_LENGTH = 4096
_SENTENCE_BOUNDARY = re.compile(r"[。!?.!?\n]")
def _chunk_by_length(text: str, limit: int) -> list[str]:
chunks: list[str] = []
start = 0
while start < len(text):
end = min(start + limit, len(text))
if end < len(text):
split_at = _find_split_point(text, limit, start, end)
chunks.append(text[start:split_at].strip())
start = split_at
else:
chunks.append(text[start:end].strip())
start = end
return chunks or [text]
def _chunk_by_newline(text: str, limit: int) -> list[str]:
chunks: list[str] = []
lines = text.split("\n")
current = ""
for line in lines:
if len(line) > limit:
if current:
chunks.append(current)
sub_chunks = _chunk_by_length(line, limit)
if sub_chunks:
current = sub_chunks.pop()
chunks.extend(sub_chunks)
else:
current = ""
elif len(current) + len(line) + (1 if current else 0) <= limit:
current = f"{current}\n{line}" if current else line
else:
chunks.append(current)
current = line
if current:
chunks.append(current)
return chunks or [text]
def _chunk_by_paragraph(text: str, limit: int) -> list[str]:
if len(text) <= limit:
return [text]
chunks: list[str] = []
paragraphs = text.split("\n\n")
current = ""
for para in paragraphs:
if len(current) + len(para) + 2 <= limit:
current = f"{current}\n\n{para}" if current else para
else:
if current:
chunks.append(current)
if len(para) > limit:
sub_chunks = _chunk_long_paragraph(para, limit)
if sub_chunks:
if sub_chunks[-1]:
current = sub_chunks.pop()
else:
sub_chunks.pop()
current = ""
chunks.extend(sub_chunks)
else:
current = ""
else:
current = para
if current:
chunks.append(current)
return chunks or [text]
def chunk_text(
text: str,
limit: int = TELEGRAM_MAX_TEXT_LENGTH,
mode: ChunkMode = "paragraph",
) -> list[str]:
modes: dict[ChunkMode, callable] = {
"length": _chunk_by_length,
"newline": _chunk_by_newline,
"paragraph": _chunk_by_paragraph,
}
chunker = modes.get(mode, _chunk_by_paragraph)
return chunker(text, limit)
def chunk_mode_from_config(config: dict[str, Any] | None = None) -> ChunkMode:
cfg = config or {}
streaming_cfg = cfg.get("streaming", {})
if isinstance(streaming_cfg, dict):
return streaming_cfg.get("chunk_mode", "paragraph")
return cfg.get("chunking_mode", "paragraph")
def _chunk_long_paragraph(text: str, limit: int) -> list[str]:
chunks: list[str] = []
while len(text) > limit:
split_at = _find_split_point(text, limit, 0, limit)
chunks.append(text[:split_at].rstrip())
text = text[split_at:].lstrip()
chunks.append(text)
return chunks
def _find_split_point(text: str, limit: int, lo: int = 0, hi: int | None = None) -> int:
look_start = max(lo, lo or limit // 2)
look_end = hi or limit
candidates = [m.start() for m in _SENTENCE_BOUNDARY.finditer(text, look_start, look_end)]
if candidates:
return candidates[-1] + 1
newline = text.rfind("\n", look_start, look_end)
if newline != -1:
return newline + 1
space = text.rfind(" ", look_start, look_end)
if space != -1:
return space + 1
return look_end