"""Reasoning normalization and aggregation for XAI app-chat streams.""" from __future__ import annotations import re from dataclasses import dataclass from typing import Any _GENERIC_HEADERS = { "", "thinking about your request", } _PROGRESSIVE_HINTS = ( "正在", "准备", "计划", "查找", "搜索", "浏览", "确认", "核对", "整合", "挖掘", "比对", "checking", "browsing", "verifying", "integrating", "digging", "cross-checking", "searching", "planning", ) _FINDING_HINTS = ( "尚未", "已经", "已", "确认", "表明", "说明", "显示", "主要", "通常", "支持", "出现", "启动", "持续", "提升", "更新", "灰度", "发布", "上线", "多模态", "视觉", "专家", "context", "token", "参数", "每天", "大潮", "小潮", "半日潮", "引力", "周期", "模式", "confirmed", "launched", "released", "rollout", "testing", "native multimodal", "widely believed", "latest", ) _LOW_VALUE_PREFIXES = ( "用户", "user", "i can", "我可以", "我收集", "建议", "need", "需要", "应该", "since instructions", "proposed", "mermaid", "可以用", "我建议", ) _TRACK_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( ("latest_updates", ("最新", "latest", "today", "recent", "最近", "update", "news", "本周", "4月", "april")), ("release_status", ("release date", "released", "release", "launch", "上线", "发布", "正式发布", "current status")), ("gray_rollout", ("灰度", "grayscale", "gray release", "灰度测试", "内测", "rollout")), ("official_confirmation", ("official", "官网", "official site", "site:", "platform.deepseek.com", "deepseek.ai")), ("ui_modes", ("vision", "视觉", "expert", "专家模式", "fast", "default", "ui", "界面", "mode")), ("v4_lite", ("v4 lite", "sealion", "sealion-lite", "海狮")), ("specs_architecture", ("specs", "parameters", "architecture", "engram", "mhc", "moe", "context", "benchmarks", "规格", "参数", "架构", "万亿")), ("definition_basics", ("定义", "解释", "什么是", "what is", "phenomenon", "现象")), ("causes_mechanism", ("成因", "原因", "cause", "causes", "gravity", "引力", "机制")), ("categories_types", ("春潮", "小潮", "半日潮", "全日潮", "类型", "分类")), ("impacts_applications", ("影响", "应用", "发电", "航运", "生活", "生态")), ) _ZH_LABELS = { "understanding": "理解问题", "scope": "检索范围", "evidence": "核验与证据", "finding": "关键发现", "latest_updates": "最新动态", "release_status": "发布状态与上线节奏", "gray_rollout": "灰度进展", "official_confirmation": "官方渠道确认", "ui_modes": "Expert / Vision 模式关联", "v4_lite": "V4 Lite 与 Sealion 线索", "specs_architecture": "规格、架构与上下文能力", "definition_basics": "定义与基础解释", "causes_mechanism": "成因与机制", "categories_types": "分类与相关类型", "impacts_applications": "影响与应用", } _EN_LABELS = { "understanding": "Understanding", "scope": "Research Scope", "evidence": "Verification", "finding": "Key Findings", "latest_updates": "latest updates", "release_status": "release status and rollout timing", "gray_rollout": "gray rollout progress", "official_confirmation": "official confirmation", "ui_modes": "Expert / Vision mode signals", "v4_lite": "V4 Lite and Sealion clues", "specs_architecture": "specs, architecture, and context capability", "definition_basics": "definition and basic explanation", "causes_mechanism": "causes and mechanism", "categories_types": "categories and related types", "impacts_applications": "impacts and applications", } @dataclass(slots=True) class ReasoningEvent: section: str text: str track: str = "" evidence_level: int = 0 dedupe_key: str = "" class ReasoningAggregator: """Normalize raw stream fragments into enterprise-style reasoning output.""" __slots__ = ( "_language", "_en_votes", "_zh_votes", "_agent_search_started", "_emitted_keys", "_seen_tracks", "_seen_findings", "_pending_events", "_section_started", "_track_best_level", "_track_emit_counts", ) def __init__(self) -> None: self._language: str | None = None self._en_votes = 0 self._zh_votes = 0 self._agent_search_started = False self._emitted_keys: set[str] = set() self._seen_tracks: set[str] = set() self._seen_findings: set[str] = set() self._pending_events: list[ReasoningEvent] = [] self._section_started: set[str] = set() self._track_best_level: dict[tuple[str, str], int] = {} self._track_emit_counts: dict[tuple[str, str], int] = {} def on_thinking( self, token: str, *, tag: str | None, rollout: str | None, step_id: int | None, ) -> list[str]: self._observe_language(token) tag_name = str(tag or "").strip() text = str(token or "").strip() if not text: return [] if tag_name == "header": event = self._normalize_header(text, step_id=step_id) return self._dispatch(event) if event else [] if tag_name == "summary": event = self._normalize_summary(text, step_id=step_id) return self._dispatch(event) if event else [] event = self._normalize_summary(text, step_id=step_id) return self._dispatch(event) if event else [] def on_tool_usage( self, tool_name: str, args: dict[str, Any], *, rollout: str | None, step_id: int | None, ) -> list[str]: lines: list[str] = [] self._observe_language(str(args.get("query") or args.get("message") or args.get("instructions") or "")) if tool_name == "web_search": query = str(args.get("query") or args.get("q") or "").strip() if not query: return [] if str(rollout or "").startswith("Agent") and not self._agent_search_started: self._agent_search_started = True lines.extend(self._dispatch(ReasoningEvent( "scope", self._localized_line("agents_started"), dedupe_key="scope:agents_started", ))) track = self._infer_track(query) if not track: return lines lines.extend(self._dispatch(ReasoningEvent( "scope", self._localized_track_line(track), track=track, evidence_level=1, dedupe_key=f"scope:web:{track}", ))) return lines if tool_name in {"x_search", "x_keyword_search", "x_semantic_search"}: query = str(args.get("query") or "").strip() track = self._infer_track(query) if not track: return [] return self._dispatch(ReasoningEvent( "evidence", self._localized_social_line(track), track=track, evidence_level=2, dedupe_key=f"evidence:social:{track}", )) if tool_name == "browse_page": url = str(args.get("url") or "").strip() source_kind, track = self._classify_page_source(url, args) if not source_kind: return [] return self._dispatch(ReasoningEvent( "evidence", self._localized_browse_line(source_kind, track), track=track or source_kind, evidence_level=4 if source_kind in {"official", "product"} else 3, dedupe_key=f"evidence:browse:{source_kind}:{track or ''}", )) if tool_name in {"search_images", "image_search"}: description = str(args.get("image_description") or args.get("imageDescription") or "").strip() if not description: return [] topic = self._classify_image_topic(description) if not topic: return [] return self._dispatch(ReasoningEvent( "scope", self._localized_image_line(topic), track="visual_assets", evidence_level=1, dedupe_key=f"scope:image:{topic}", )) if tool_name == "chatroom_send": message = str(args.get("message") or "").strip() if not message: return [] lines = [] for section, text, track, level in self._extract_report_events(message): lines.extend(self._dispatch(ReasoningEvent( section, text, track=track, evidence_level=level, dedupe_key=f"{section}:report:{track}:{self._normalize_key(text)}", ))) return lines if tool_name == "code_execution": return self._dispatch(ReasoningEvent( "evidence", self._localized_line("code_execution"), dedupe_key="evidence:code_execution", )) return [] def finalize(self) -> list[str]: if not self._pending_events: return [] if self._language is None: self._language = "en" if self._en_votes > 0 and self._zh_votes == 0 else "zh" return self._flush_pending() def _normalize_header(self, text: str, *, step_id: int | None) -> ReasoningEvent | None: stripped = text.strip() if stripped.lower() in _GENERIC_HEADERS: return None section = "understanding" if not self._looks_like_verification(stripped) and (step_id or 0) <= 1 else "evidence" return ReasoningEvent(section, self._to_bullet_text(stripped), dedupe_key=f"{section}:header:{self._normalize_key(stripped)}") def _normalize_summary(self, text: str, *, step_id: int | None) -> ReasoningEvent | None: summary = text.lstrip("- ").strip() if not summary: return None if summary.startswith(("建议搜索", "正在调用工具搜索")): return None track = self._infer_track(summary) if self._looks_like_progress(summary): section = "evidence" if self._looks_like_verification(summary) else "scope" return ReasoningEvent(section, self._to_bullet_text(summary), track=track, evidence_level=2 if section == "evidence" else 1, dedupe_key=f"{section}:summary:{self._normalize_key(summary)}") if self._looks_like_finding(summary): if self._is_unconfirmed_signal(summary): return ReasoningEvent("evidence", self._to_bullet_text(summary), track=track, evidence_level=2, dedupe_key=f"evidence:summary:{self._normalize_key(summary)}") if not self._agent_search_started and (step_id or 0) <= 1: return ReasoningEvent("understanding", self._to_bullet_text(summary), track=track, evidence_level=2, dedupe_key=f"understanding:summary:{self._normalize_key(summary)}") return ReasoningEvent("finding", self._to_bullet_text(summary), track=track, evidence_level=3, dedupe_key=f"finding:summary:{self._normalize_key(summary)}") section = "understanding" if (step_id or 0) <= 1 else "scope" return ReasoningEvent(section, self._to_bullet_text(summary), track=track, evidence_level=1, dedupe_key=f"{section}:summary:{self._normalize_key(summary)}") def _extract_report_events(self, message: str) -> list[tuple[str, str, str, int]]: parts = re.split(r"(?:\n+|[。!?!?;;]+|\s+-\s+)", message.replace("\\n", "\n")) candidates: list[tuple[int, str]] = [] for raw_part in parts: clause = self._clean_report_clause(raw_part) if not clause: continue if self._language == "zh" and not re.search(r"[\u4e00-\u9fff]", clause): continue if self._language == "en" and re.search(r"[\u4e00-\u9fff]", clause): continue score = self._score_report_clause(clause) if score <= 0: continue candidates.append((score, clause)) candidates.sort(key=lambda item: (-item[0], len(item[1]))) results: list[tuple[str, str, str, int]] = [] seen_local: set[str] = set() seen_track_counts: dict[tuple[str, str], int] = {} for _, clause in candidates: key = self._normalize_key(clause) if key in seen_local: continue seen_local.add(key) track = self._infer_track(clause) section = "finding" if self._looks_like_finding(clause) else "evidence" if self._is_unconfirmed_signal(clause): section = "evidence" track_key = (section, track or "_") current_track_count = seen_track_counts.get(track_key, 0) max_track_count = 2 if section == "finding" else 1 if current_track_count >= max_track_count: continue seen_track_counts[track_key] = current_track_count + 1 level = self._infer_evidence_level(clause, default=3 if section == "finding" else 2) results.append((section, self._to_bullet_text(clause), track, level)) if len(results) >= 6: break results.sort(key=lambda item: (0 if item[0] == "evidence" else 1, item[2], -item[3])) return results def _dispatch(self, event: ReasoningEvent) -> list[str]: if self._language is None: self._pending_events.append(event) if self._zh_votes > 0: self._language = "zh" elif self._en_votes >= 3: self._language = "en" elif len(self._pending_events) < 4: return [] else: self._language = "en" return self._flush_pending() lines: list[str] = [] if self._pending_events: lines.extend(self._flush_pending()) lines.extend(self._emit(event)) return lines def _flush_pending(self) -> list[str]: lines: list[str] = [] pending = self._pending_events self._pending_events = [] for event in pending: lines.extend(self._emit(event)) return lines def _emit(self, event: ReasoningEvent) -> list[str]: text = event.text.strip() if not text: return [] if event.section == "scope" and ("evidence" in self._section_started or "finding" in self._section_started): return [] if event.section == "evidence" and "finding" in self._section_started: if event.evidence_level >= 4 or event.track in { "latest_updates", "release_status", "official_confirmation", "specs_architecture", "v4_lite", }: promoted_key = event.dedupe_key or f"evidence:{self._normalize_key(text)}" event = ReasoningEvent( "finding", text, track=event.track, evidence_level=event.evidence_level, dedupe_key=f"finding:promoted:{promoted_key}", ) else: return [] dedupe_key = event.dedupe_key or f"{event.section}:{self._normalize_key(text)}" if dedupe_key in self._emitted_keys: return [] if event.track: count_key = (event.section, event.track) emitted_count = self._track_emit_counts.get(count_key, 0) max_per_track = 1 if event.section in {"scope", "evidence"} else 2 if emitted_count >= max_per_track and not dedupe_key.endswith("agents_started"): return [] best_key = (event.section, event.track) best_level = self._track_best_level.get(best_key, -1) if best_level > event.evidence_level: return [] self._track_best_level[best_key] = max(best_level, event.evidence_level) self._track_emit_counts[count_key] = emitted_count + 1 self._emitted_keys.add(dedupe_key) lines: list[str] = [] if event.section not in self._section_started: self._section_started.add(event.section) lines.append(self._section_title(event.section) + "\n") lines.append(text + "\n") return lines def _observe_language(self, text: str) -> None: if not text: return cjk_count = len(re.findall(r"[\u4e00-\u9fff]", text)) en_count = len(re.findall(r"[A-Za-z]", text)) if cjk_count >= 4 or cjk_count > max(2, en_count // 2): self._zh_votes += 1 if self._language is None: self._language = "zh" return if en_count >= 4: self._en_votes += 1 def _section_title(self, section: str) -> str: labels = _ZH_LABELS if self._language != "en" else _EN_LABELS return labels.get(section, section) def _localized_line(self, key: str) -> str: zh_map = { "agents_started": "- 已启动并行代理进行交叉检索与核验。", "code_execution": "- 正在执行代码或生成可运行内容。", } en_map = { "agents_started": "- Parallel agents have started cross-checking the topic.", "code_execution": "- Executing code or generating runnable content.", } mapping = zh_map if self._language != "en" else en_map return mapping[key] def _localized_track_line(self, track: str) -> str: label = self._track_label(track) if self._language == "en": return f"- Parallel research: {label}." return f"- 并行检索:{label}。" def _localized_social_line(self, track: str) -> str: label = self._track_label(track) if self._language == "en": return f"- Social cross-check: {label}." return f"- 社媒交叉核验:{label}。" def _localized_browse_line(self, source_kind: str, track: str) -> str: track_label = self._track_label(track) if track else "" if self._language == "en": mapping = { "official": "Page verification: official site and official pages", "product": "Page verification: product page and live UI", "community": "Page verification: public reports and community write-ups", } else: mapping = { "official": "页面核对:官网与官方页面", "product": "页面核对:产品页面与实际界面", "community": "页面核对:公开报道与社区文章", } base = mapping[source_kind] if track_label: connector = ", focusing on " if self._language == "en" else ",重点核对" return f"- {base}{connector}{track_label}{'.' if self._language == 'en' else '。'}" return f"- {base}{'.' if self._language == 'en' else '。'}" def _localized_image_line(self, topic: str) -> str: if self._language == "en": mapping = { "diagram": "- Visual asset search: diagrams and explanatory graphics.", "photo": "- Visual asset search: real-world comparison photos.", "generic": "- Visual asset search: supporting image references.", } else: mapping = { "diagram": "- 视觉素材检索:示意图与结构说明素材。", "photo": "- 视觉素材检索:实景对比图片。", "generic": "- 视觉素材检索:补充说明图片。", } return mapping[topic] def _track_label(self, track: str) -> str: labels = _ZH_LABELS if self._language != "en" else _EN_LABELS return labels.get(track, track) def _infer_track(self, text: str) -> str: lowered = self._compact_query(text).lower() if not lowered: return "" for track, keywords in _TRACK_RULES: if any(keyword in lowered for keyword in keywords): return track return "" def _classify_page_source(self, url: str, args: dict[str, Any]) -> tuple[str, str]: lowered = url.lower() instructions = str(args.get("instructions") or "") track = self._pick_browse_track(f"{url} {instructions}") if any(domain in lowered for domain in ("deepseek.ai", "deepseek.com")): if "chat.deepseek.com" in lowered or "platform.deepseek.com" in lowered: return "product", track or "ui_modes" return "official", track or "official_confirmation" if url: return "community", track return "", track def _pick_browse_track(self, text: str) -> str: lowered = self._compact_query(text).lower() priority = ( ("ui_modes", ("expert", "vision", "mode", "界面", "ui")), ("release_status", ("release", "released", "launch", "发布", "上线", "status")), ("specs_architecture", ("spec", "parameter", "architecture", "context", "engram", "moe", "规格", "参数", "架构", "上下文")), ("v4_lite", ("v4 lite", "sealion", "sealion-lite", "海狮")), ("official_confirmation", ("official", "官网", "current models", "offering")), ) for track, keywords in priority: if any(keyword in lowered for keyword in keywords): return track return self._infer_track(text) def _classify_image_topic(self, text: str) -> str: lowered = text.lower() if any(token in lowered for token in ("diagram", "示意图", "bulge")): return "diagram" if any(token in lowered for token in ("photo", "照片", "real", "high tide", "low tide", "高潮", "低潮")): return "photo" return "generic" def _looks_like_progress(self, text: str) -> bool: lowered = text.lower() return any(hint in lowered for hint in _PROGRESSIVE_HINTS) def _looks_like_verification(self, text: str) -> bool: lowered = text.lower() return any(token in lowered for token in ("确认", "核对", "浏览", "整合", "比对", "check", "verify", "browse", "integrat")) def _looks_like_finding(self, text: str) -> bool: lowered = text.lower() if self._looks_like_progress(text): return False return any(hint in lowered for hint in _FINDING_HINTS) def _clean_report_clause(self, raw_part: str) -> str: clause = re.sub(r"\s+", " ", raw_part).strip(" -•\t") if not clause: return "" delimiter = ":" if ":" in clause else ":" if ":" in clause else "" if delimiter: head, tail = clause.split(delimiter, 1) head_lower = head.strip().lower() if len(head.strip()) <= 18 or any(token in head_lower for token in ("总结", "最新", "关键", "补充", "latest", "summary", "note")): clause = tail.strip() clause = clause.strip(" -•\t") clause = re.sub(r"^(?:我知道|我收集了可靠信息|我收集到的?信息|从搜索结果总结|详细解释要点(?:([^)]+))?|补充)\s*", "", clause) clause = re.sub(r"^(?:that|it shows|it seems)\s+", "", clause, flags=re.IGNORECASE) if len(clause) < 8: return "" lowered = clause.lower() if any(lowered.startswith(prefix) for prefix in _LOW_VALUE_PREFIXES): return "" if "?" in clause or "?" in clause: return "" return self._compact_text(clause, limit=120) def _score_report_clause(self, clause: str) -> int: lowered = clause.lower() score = 0 if any(hint in lowered for hint in _FINDING_HINTS): score += 3 if re.search(r"\b\d+(?:\.\d+)?\b", clause): score += 2 if any(token in clause for token in ("月", "日", "年", "小时", "分钟")): score += 1 if any(token in clause for token in ("重要", "航运", "渔业", "发电", "生态", "模式", "视觉")): score += 1 if any(token in lowered for token in ("可能", "rumor", "传闻", "widely believed", "believed")): score -= 1 if any(token in lowered for token in ("可以", "suggest", "建议", "should", "friendly", "reply")): score -= 2 if len(clause) > 150: score -= 1 return score def _infer_evidence_level(self, clause: str, *, default: int) -> int: lowered = clause.lower() if any(token in lowered for token in ("官网", "official", "chat ui", "界面更新", "页面")): return 4 if any(token in lowered for token in ("x平台", "x posts", "社区", "widely believed", "传闻", "rumor")): return max(2, default - 1) return default def _is_unconfirmed_signal(self, clause: str) -> bool: lowered = clause.lower() return any( token in lowered for token in ( "x平台", "x posts", "社区", "community", "widely believed", "believed", "传闻", "rumor", "曝光", "泄露", ) ) def _to_bullet_text(self, text: str) -> str: stripped = text.strip() if stripped.startswith("- "): stripped = stripped[2:].strip() stripped = self._ensure_terminal_punctuation(stripped) return f"- {stripped}" def _ensure_terminal_punctuation(self, text: str) -> str: stripped = text.strip() if not stripped: return "" if stripped.endswith(("。", "!", "?", ".", "!", "?")): return stripped if re.search(r"[\u4e00-\u9fff]", stripped): return stripped + "。" return stripped + "." def _compact_query(self, text: str) -> str: cleaned = re.sub(r"\b(?:or|and|site:[^\s]+|since:\S+|from:\S+|date:\S+)\b", " ", text, flags=re.IGNORECASE) cleaned = re.sub(r"[()\"']", " ", cleaned) cleaned = re.sub(r"\s+", " ", cleaned).strip() return cleaned def _compact_text(self, text: str, *, limit: int) -> str: compact = re.sub(r"\s+", " ", text).strip() if len(compact) <= limit: return compact return compact[: limit - 3].rstrip() + "..." def _normalize_key(self, text: str) -> str: lowered = text.lower() lowered = re.sub(r"https?://\S+", "", lowered) lowered = re.sub(r"[^\w\u4e00-\u9fff]+", "", lowered) return lowered __all__ = ["ReasoningAggregator", "ReasoningEvent"]