This commit is contained in:
2026-04-08 11:00:31 +08:00
parent fb21a127b5
commit 7019cf180d
17 changed files with 960 additions and 1 deletions

View File

@@ -0,0 +1,84 @@
"""
回复文本解析模块
负责处理 Agent 返回的原始文本,提取最终正式回复。
模型思考标记格式(按优先级检测):
1. <think...</think 标签对MiniMax M2.7 等模型)
2. 💭 emoji 前缀(旧版格式,保留兼容)
ADK 最后一个 event 会重复发送完整内容,需要去重。
"""
import logging
import re
logger = logging.getLogger(__name__)
def extract_final_reply(text: str) -> str:
"""
从 SSE 收集的完整文本中提取最终正式回复。
策略:
1. 去除 <think...</think 标签及其内容
2. 兼容 💭 emoji 分割(旧版格式)
3. 去重
"""
if not text:
return text
# 步骤1去除 <think...</think 标签块
if "<think" in text:
text = re.sub(r"<think[\s\S]*?</think\s*>?", "", text)
# 步骤2兼容 💭 emoji 分割(旧版格式)
if "💭" in text:
text = text.split("💭")[-1]
# 清理前导空白
text = text.strip()
# 步骤3去重
text = _deduplicate(text)
return text
def _deduplicate(text: str) -> str:
"""
对回复文本去重。
ADK 最后一个 SSE event 会重复发送完整内容(含思考+回复),
去除 <think...> 后可能出现连续两份相同的正式回复。
支持行级和字符级去重。
"""
if not text:
return text
# 行级去重
lines = text.split("\n")
non_empty = [l for l in lines if l.strip()]
if len(non_empty) > 1:
mid = len(non_empty) // 2
if non_empty[:mid] == non_empty[mid:]:
return "\n".join(non_empty[:mid])
for overlap_len in range(min(len(non_empty) // 2, 20), 0, -1):
if non_empty[-overlap_len:] == non_empty[:overlap_len]:
return "\n".join(non_empty[overlap_len:])
# 字符级去重:检查文本是否由两个相同的子串拼接而成
length = len(text)
for sub_len in range(length // 2, 0, -1):
if length % sub_len == 0 and text[:sub_len] * (length // sub_len) == text:
return text[:sub_len]
# 部分重叠:前缀和后缀相同,中间有重复
for overlap in range(min(length // 2, 500), 0, -1):
if text[:overlap] == text[-overlap:]:
trimmed = text[overlap:]
trimmed_len = len(trimmed)
for sub_len in range(trimmed_len // 2, 0, -1):
if trimmed_len % sub_len == 0 and trimmed[:sub_len] * (trimmed_len // sub_len) == trimmed:
return trimmed[:sub_len]
return text