ForcePilot/backend/package/yuxi/channels/sdk/chunking.py
Kris 7a972055b7 feat: 新增渠道服务基础框架与核心工具类
新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括:
1.  多协议定义:认证、消息、配置、网关等核心接口
2.  策略模块:上下文、群聊、去重、防抖等业务策略
3.  工具集:重试、去重、文本分块、消息格式化等SDK工具
4.  基础设施:外部进程管理、事件广播、熔断机制等
5.  账户与管道系统:账户管理、消息处理管道实现
6.  运行时服务:状态收集、维护任务、日志等后台服务
2026-05-12 00:53:57 +08:00

107 lines
2.9 KiB
Python

from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Literal
_SENTENCE_BOUNDARY = re.compile(r"[。!?.!?\n]")
@dataclass
class ChunkConfig:
limit: int = 4096
mode: Literal["text", "markdown"] = "text"
def chunk_message(content: str, config: ChunkConfig | None = None) -> list[str]:
cfg = config or ChunkConfig()
if len(content) <= cfg.limit:
return [content]
if cfg.mode == "markdown":
return _chunk_markdown(content, cfg.limit)
return _chunk_text(content, cfg.limit)
def _chunk_text(text: str, limit: int) -> list[str]:
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 = _chunk_long_text(para, limit)
if sub:
if sub[-1]:
current = sub.pop()
else:
sub.pop()
current = ""
chunks.extend(sub)
else:
current = ""
else:
current = para
if current:
chunks.append(current)
return chunks or [text]
def _chunk_long_text(text: str, limit: int) -> list[str]:
chunks: list[str] = []
while len(text) > limit:
split_at = _find_split_point(text, 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) -> int:
candidates = [m.start() for m in _SENTENCE_BOUNDARY.finditer(text, limit // 2, limit)]
if candidates:
return candidates[-1] + 1
newline = text.rfind("\n", limit // 2, limit)
if newline != -1:
return newline + 1
space = text.rfind(" ", limit // 2, limit)
if space != -1:
return space + 1
return limit
def _chunk_markdown(text: str, limit: int) -> list[str]:
lines = text.split("\n")
chunks: list[str] = []
current = ""
in_code_block = False
for line in lines:
if line.strip().startswith("```"):
if in_code_block:
in_code_block = False
else:
if current and len(current) + len(line) + 1 > limit:
chunks.append(current)
current = line
in_code_block = True
continue
in_code_block = True
if current and len(current) + len(line) + 1 > limit and not in_code_block:
chunks.append(current)
current = line
else:
current = f"{current}\n{line}" if current else line
if current:
chunks.append(current)
return chunks or [text]