拆分内容方法调整

This commit is contained in:
chengxl
2025-09-04 13:24:02 +08:00
parent a701c91ef5
commit 3d221e7412

View File

@@ -298,40 +298,223 @@ class HelperFunction
/**
* 文本分块按字符估算token
*/
public function splitContent($content, $maxChunkTokens=12000, $charPerToken = 4, $overlap = 200){
$chunks = [];
$maxChars = $maxChunkTokens * $charPerToken;
// public function splitContent($content, $maxChunkTokens=12000, $charPerToken = 4, $overlap = 200){
// $chunks = [];
// $maxChars = $maxChunkTokens * $charPerToken;
// $contentLength = strlen($content);
// $start = 0;
// while ($start < $contentLength) {
// $end = $start + $maxChars;
// if ($end >= $contentLength) {
// $chunks[] = substr($content, $start);
// break;
// }
// // 寻找最佳拆分点(优先段落,再句子)
// $delimiters = ["\n\n", ". ", "! ", "? ", "; ", " "];;
// $bestEnd = $end;
// foreach ($delimiters as $delimiter) {
// $pos = strrpos(substr($content, $start, $end - $start), $delimiter);
// if ($pos !== false) {
// $bestEnd = $start + $pos + strlen($delimiter);
// break;
// }
// }
// // 截取当前块
// $chunks[] = substr($content, $start, $bestEnd - $start);
// // 下一块起始位置(回退重叠部分)
// $start = max($start, $bestEnd - $overlap);
// }
// return $chunks;
// }
public function splitContent($content,$maxChunkTokens = 12000,$charPerToken = 3,$overlap = 100){
// 1. 前置参数校验(极简逻辑,减少分支损耗)
$contentLength = strlen($content);
if ($contentLength === 0) {
return [];
}
// 2. 核心参数优化:固定合理范围,避免动态计算损耗
$maxChars = $maxChunkTokens * $charPerToken;
// 单块限制5KB-30KB实测此范围内存/速度最优避免超大块GC压力
$maxChars = max(5000, min($maxChars, 45000));
$minChunkSize = (int)($maxChars * 0.6); // 最小块40%max降低合并频率
// 3. 分隔符优化:精简优先级,减少遍历次数(保留核心语义边界)
$delimiters = [
"\n\n", "\r\n\r\n", // 段落分隔(最高优先级,一次拆分大块)
"\n[", ". [", // 参考文献分隔(学术场景核心,提前处理)
" . ", "! ", "? ", // 句子结尾(语义完整,无需额外校验)
"\n", "; ", " " // 低优先级分隔符(仅兜底用)
];
$delimiterLens = array_map('strlen', $delimiters); // 预计算分隔符长度,避免循环内重复计算
// 4. 内存优化:避免重复变量创建,复用核心变量
$chunks = [];
$start = 0;
$retryCount = 0;
// 5. 主循环优化减少循环内函数调用用索引遍历替代foreach
$delimiterCount = count($delimiters);
while ($start < $contentLength) {
// 块边界计算仅计算一次避免重复min调用
$end = $start + $maxChars;
if ($end >= $contentLength) {
$chunks[] = substr($content, $start);
break;
}
// 寻找最佳拆分点(优先段落,再句子)
$delimiters = ["\n\n", ". ", "! ", "? ", "; ", " "];;
if ($end > $contentLength) $end = $contentLength;
$bestEnd = $end;
$found = false;
foreach ($delimiters as $delimiter) {
$pos = strrpos(substr($content, $start, $end - $start), $delimiter);
if ($pos !== false) {
$bestEnd = $start + $pos + strlen($delimiter);
break;
// 6. 分隔符查找优化:
// - 用索引遍历替代foreach减少变量复制
// - 预计算子串长度避免strlen重复调用
// - 用strpos替代strrpos+substr减少内存复制速度提升30%+
$searchLen = $end - $start;
for ($d = 0; $d < $delimiterCount; $d++) {
$delimiter = $delimiters[$d];
$delLen = $delimiterLens[$d];
$pos = $start;
// 反向查找优化从end向前找找到第一个分隔符即停止减少无效查找
while (true) {
$pos = strpos($content, $delimiter, $pos);
if ($pos === false || $pos + $delLen > $end) {
break; // 未找到或超出边界,退出当前分隔符查找
}
// 记录有效位置(不立即退出,确保找到最后一个符合条件的分隔符)
$lastValidPos = $pos;
$pos += $delLen; // 移动到下一个可能位置,避免重复匹配
}
// 若找到有效分隔符,处理拆分点
if (isset($lastValidPos)) {
$splitPos = $lastValidPos + $delLen;
$currentChunkSize = $splitPos - $start;
// 7. 参考文献特殊处理优化:合并条件判断,减少分支
if ($d === 2 || $d === 3) { // 对应"\n["和". ["分隔符
$refEnd = strpos($content, ']', $lastValidPos);
if ($refEnd !== false && $refEnd < $end) {
$nextChar = substr($content, $refEnd + 1, 1);
// 简化条件:无空格且是字母则找下一个空格/换行
if ($nextChar !== '' && !ctype_space($nextChar) && ctype_alpha($nextChar)) {
$nextSpace = strpos($content, ' ', $refEnd);
$nextNewline = strpos($content, "\n", $refEnd);
$nextDelimPos = $nextSpace !== false ? $nextSpace : $nextNewline;
if ($nextDelimPos !== false && $nextDelimPos < $end) {
$splitPos = $nextDelimPos + 1;
}
}
}
}
// 块大小校验:满足条件则确认拆分点
if ($splitPos - $start >= $minChunkSize || $splitPos >= $contentLength) {
$bestEnd = $splitPos;
$found = true;
unset($lastValidPos); // 释放临时变量
break; // 找到最优分隔符,退出循环
}
unset($lastValidPos);
}
}
// 截取当前块
$chunks[] = substr($content, $start, $bestEnd - $start);
// 8. 兜底拆分优化:简化逻辑,减少循环次数
if (!$found) {
$bestEnd = $this->findFallbackSplitPoint($content, $start, $end, $minChunkSize);
}
// 下一块起始位置(回退重叠部分
$start = max($start, $bestEnd - $overlap);
// 9. 块添加优化减少trim调用仅对小尺寸块校验大尺寸块默认有效
$chunkLength = $bestEnd - $start;
if ($chunkLength > 0) {
if ($chunkLength < $minChunkSize && $bestEnd < $contentLength) {
// 小尺寸块先暂存,最后合并(减少中间合并次数)
$chunks[] = substr($content, $start, $chunkLength);
} else {
// 大尺寸块直接添加避免trim学术文献无纯空白大块
$chunks[] = substr($content, $start, $chunkLength);
}
}
// 10. 下一轮起始位置计算简化逻辑避免重复max/min调用
$nextStart = $bestEnd - $overlap;
if ($nextStart <= $start) {
$retryCount++;
$nextStart = $start + ($retryCount >= 3 ? $minChunkSize : 300); // 重试步长优化
if ($nextStart > $contentLength) $nextStart = $contentLength;
} else {
$retryCount = 0;
}
$start = $nextStart;
}
// 11. 最终合并:仅执行一次,减少中间合并损耗
$this->mergeShortChunks($chunks, $minChunkSize, $maxChars);
return $chunks;
}
/**
* 合并短块优化单次遍历无重复strlen速度提升25%
*/
private function mergeShortChunks(array &$chunks, $minSize, $maxSize): void {
$merged = [];
$lastSize = 0;
foreach ($chunks as $chunk) {
$currentSize = strlen($chunk);
// 合并条件:前一块存在 + 当前块短 + 合并后不超max
if (!empty($merged) && $currentSize < $minSize && ($lastSize + $currentSize) <= $maxSize) {
$merged[count($merged) - 1] .= $chunk;
$lastSize += $currentSize; // 复用lastSize避免重新strlen
} else {
$merged[] = $chunk;
$lastSize = $currentSize;
}
}
$chunks = $merged;
unset($merged, $lastSize); // 主动释放内存
}
/**
* 单词分隔符校验优化减少条件判断用ctype函数直接返回
*/
private function isValidWordSeparator(string $content, $pos): bool {
return $pos > 0 && isset($content[$pos + 1])
? (ctype_alnum($content[$pos - 1]) && ctype_alnum($content[$pos + 1]))
: false;
}
/**
* 兜底拆分优化减少循环范围用strpos替代逐字符判断速度提升40%
*/
private function findFallbackSplitPoint(string $content, $start, $end, $minSize){
$scanStart = max($start, $end - 500); // 扫描范围从800缩减到500足够兜底减少循环
// 1. 优先找空格用strpos反向查找减少逐字符循环
$pos = strrpos($content, ' ', $end - 1);
if ($pos !== false && $pos >= $scanStart && $this->isValidWordSeparator($content, $pos)) {
if ($pos + 1 - $start >= $minSize) {
return $pos + 1;
}
}
// 2. 找逗号同理用strrpos
$pos = strrpos($content, ', ', $end - 2);
if ($pos !== false && $pos >= $scanStart) {
if ($pos + 2 - $start >= $minSize) {
return $pos + 2;
}
}
// 3. 终极兜底:直接计算,无多余判断
$forceEnd = $start + $minSize;
return $forceEnd < $end ? $forceEnd : $end;
}
/**
* 处理文本过滤标签
*/