Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions app/dataplane/reverse/protocol/xai_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ class StreamAdapter:
"_summary_mode",
"_last_rollout",
"_content_started",
"_web_search_results",
"_web_search_urls_seen",
"thinking_buf",
"text_buf",
"image_urls",
Expand All @@ -197,14 +199,28 @@ def __init__(self) -> None:
self._last_rollout: str = ""
self._content_started: bool = False
self._reasoning = ReasoningAggregator() if self._summary_mode else None
self._web_search_results: list[dict] = []
self._web_search_urls_seen: set[str] = set()
self.thinking_buf: list[str] = []
self.text_buf: list[str] = []
self.image_urls: list[tuple[str, str]] = [] # [(url, imageUuid), ...]

# 引用已内联为 [[N]](url) 格式,无需末尾附录
# 搜索信源追加:当配置启用且有 webSearchResults 时,格式化为 ## Sources 段落
# 标记行 [grok2api-sources]: # 是 markdown link reference definition,渲染器不显示,
# 用于 _extract_message() 在多轮对话中精确识别并剥离前轮的 Sources 段落
def references_suffix(self) -> str:
"""No-op — citations are now inlined as ``[[N]](url)`` markdown links."""
return ""
"""当有搜索信源且配置启用时,格式化为 ## Sources markdown 段落。"""
if not self._web_search_results:
return ""
if not get_config().get_bool("features.show_search_sources", False):
return ""
lines = ["\n\n## Sources", "[grok2api-sources]: #"]
for item in self._web_search_results:
title = item.get("title") or item.get("url", "")
# 转义 Markdown 链接文本中的特殊字符,防止 []\ 打坏语法
title = title.replace("\\", "\\\\").replace("[", "\\[").replace("]", "\\]")
lines.append(f"- [{title}]({item['url']})")
return "\n".join(lines) + "\n"

# ------------------------------------------------------------------
# Public API
Expand All @@ -231,6 +247,33 @@ def feed(self, data: str) -> list[FrameEvent]:
if card_raw:
events.extend(self._handle_card(card_raw))

# ── 采集 webSearchResults(搜索信源,多帧累积去重)───────
wsr = resp.get("webSearchResults")
if wsr and isinstance(wsr, dict):
for item in wsr.get("results", []):
if isinstance(item, dict) and item.get("url"):
url = item["url"]
if url not in self._web_search_urls_seen:
self._web_search_urls_seen.add(url)
self._web_search_results.append(item)

# ── 采集 xSearchResults(X/Twitter 帖子信源,多帧累积去重)──
xsr = resp.get("xSearchResults")
if xsr and isinstance(xsr, dict):
for item in xsr.get("results", []):
if isinstance(item, dict) and item.get("postId") and item.get("username"):
url = f"https://x.com/{item['username']}/status/{item['postId']}"
if url not in self._web_search_urls_seen:
self._web_search_urls_seen.add(url)
# 构造 title:归一化空白,text 为空退回 @username
# Markdown 转义统一在 references_suffix() 中处理
raw = re.sub(r"\s+", " ", (item.get("text") or "")).strip()
if raw:
title = f"𝕏/@{item['username']}: {raw[:50]}{'...' if len(raw) > 50 else ''}"
else:
title = f"𝕏/@{item['username']}"
self._web_search_results.append({"url": url, "title": title})

token = resp.get("token")
think = resp.get("isThinking")
tag = resp.get("messageTag")
Expand Down
17 changes: 16 additions & 1 deletion app/products/openai/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import base64
import re
from typing import Any, AsyncGenerator

import orjson
Expand Down Expand Up @@ -188,6 +189,12 @@ def _normalize_image_format(value: str | None) -> str:
return fmt


# 精确匹配 grok2api 注入的 Sources 段落(含标记行),用于多轮对话剥离
_SOURCES_STRIP_RE = re.compile(
r"(?:^|\r?\n\r?\n)## Sources\r?\n\[grok2api-sources\]: #\r?\n[\s\S]*$"
)


def _extract_message(messages: list[dict]) -> tuple[str, list[str]]:
"""Flatten OpenAI messages into a single prompt string + file attachments."""
parts: list[str] = []
Expand Down Expand Up @@ -220,6 +227,10 @@ def _extract_message(messages: list[dict]) -> tuple[str, list[str]]:
parts.append(f"[assistant]:\n{xml}")
continue

# ── 剥离前轮 assistant 消息中 grok2api 注入的 Sources 段落 ────────────
if role == "assistant" and isinstance(content, str):
content = _SOURCES_STRIP_RE.sub("", content)

# ── normal content handling ───────────────────────────────────────────
if isinstance(content, str):
if content.strip():
Expand All @@ -230,7 +241,11 @@ def _extract_message(messages: list[dict]) -> tuple[str, list[str]]:
continue
btype = block.get("type")
if btype == "text":
text = (block.get("text") or "").strip()
text = (block.get("text") or "")
# 块列表中的 assistant text 也需剥离 Sources(先 regex 再 strip,与 str 路径对齐)
if role == "assistant":
text = _SOURCES_STRIP_RE.sub("", text)
text = text.strip()
if text:
parts.append(f"[{role}]: {text}")
elif btype == "image_url":
Expand Down
1 change: 1 addition & 0 deletions app/statics/admin/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@
{ key: 'thinking_summary', label: '思考精简输出', labelKey: 'config.schema.fields.thinkingSummary.label', type: 'bool', desc: '启用后将思考过程提炼为结构化摘要。关闭时输出完整的原始推理过程,支持多 Agent 模型的协作详情与工具调用展示。', descKey: 'config.schema.fields.thinkingSummary.desc' },
{ key: 'dynamic_statsig', label: '动态 Statsig', labelKey: 'config.schema.fields.dynamicStatsig.label', type: 'bool', desc: '为每次请求动态生成 Statsig 设备指纹,以降低风控拦截概率。', descKey: 'config.schema.fields.dynamicStatsig.desc' },
{ key: 'enable_nsfw', label: '允许 NSFW 生成', labelKey: 'config.schema.fields.enableNsfw.label', type: 'bool', desc: '允许图像生成接口绕过 NSFW 内容过滤。', descKey: 'config.schema.fields.enableNsfw.desc' },
{ key: 'show_search_sources', label: '搜索信源', labelKey: 'config.schema.fields.showSearchSources.label', type: 'bool', desc: '当 Grok 执行网络搜索时,在响应末尾追加信源链接(## Sources 段落)。', descKey: 'config.schema.fields.showSearchSources.desc' },
{ key: 'custom_instruction', label: '全局附加指令', labelKey: 'config.schema.fields.customInstruction.label', type: 'textarea', desc: '为每次请求注入统一的 system 消息,用于约束模型行为或固定角色设定。', descKey: 'config.schema.fields.customInstruction.desc' },
]
},
Expand Down
1 change: 1 addition & 0 deletions app/statics/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"thinkingSummary": { "label": "Kompakte Reasoning-Ausgabe" },
"dynamicStatsig": { "label": "Dynamisches Statsig" },
"enableNsfw": { "label": "NSFW-Erzeugung zulassen" },
"showSearchSources": { "label": "Suchquellen" },
"customInstruction": { "label": "Globale Zusatzanweisung" },
"imageFormat": { "label": "Bildausgabeformat" },
"videoFormat": { "label": "Videoausgabeformat" },
Expand Down
4 changes: 4 additions & 0 deletions app/statics/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@
"label": "Allow NSFW Generation",
"desc": "Permits the image generation endpoint to bypass NSFW content filtering."
},
"showSearchSources": {
"label": "Search Sources",
"desc": "Append search source links (## Sources section) at the end of responses when Grok performs web search."
},
"customInstruction": {
"label": "Global Supplemental Instruction",
"desc": "Injects a consistent system message into every request to enforce model behavior or establish a fixed role."
Expand Down
1 change: 1 addition & 0 deletions app/statics/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"thinkingSummary": { "label": "Razonamiento condensado" },
"dynamicStatsig": { "label": "Statsig dinámico" },
"enableNsfw": { "label": "Permitir generación NSFW" },
"showSearchSources": { "label": "Fuentes de búsqueda" },
"customInstruction": { "label": "Instrucción suplementaria global" },
"imageFormat": { "label": "Formato de salida de imagen" },
"videoFormat": { "label": "Formato de salida de video" },
Expand Down
1 change: 1 addition & 0 deletions app/statics/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"thinkingSummary": { "label": "Raisonnement condensé" },
"dynamicStatsig": { "label": "Statsig dynamique" },
"enableNsfw": { "label": "Autoriser la génération NSFW" },
"showSearchSources": { "label": "Sources de recherche" },
"customInstruction": { "label": "Instruction globale supplémentaire" },
"imageFormat": { "label": "Format de sortie image" },
"videoFormat": { "label": "Format de sortie vidéo" },
Expand Down
1 change: 1 addition & 0 deletions app/statics/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
"thinkingSummary": { "label": "思考要約出力" },
"dynamicStatsig": { "label": "動的 Statsig" },
"enableNsfw": { "label": "NSFW 生成を許可" },
"showSearchSources": { "label": "検索ソース" },
"customInstruction": { "label": "グローバル補助指示" },
"imageFormat": { "label": "画像出力形式" },
"videoFormat": { "label": "動画出力形式" },
Expand Down
4 changes: 4 additions & 0 deletions app/statics/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@
"label": "允许 NSFW 生成",
"desc": "允许图像生成接口绕过 NSFW 内容过滤。"
},
"showSearchSources": {
"label": "搜索信源",
"desc": "当 Grok 执行网络搜索时,在响应末尾追加搜索信源链接(## Sources 段落)。"
},
"customInstruction": {
"label": "全局附加指令",
"desc": "为每次请求注入统一的 system 消息,用于约束模型行为或固定角色设定。"
Expand Down
2 changes: 2 additions & 0 deletions config.defaults.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ thinking_summary = false
dynamic_statsig = true
# 是否允许生成 NSFW 图片
enable_nsfw = true
# 当 Grok 执行网络搜索时,在响应末尾追加搜索信源链接(## Sources 段落)
show_search_sources = false
# 全局附加指令
custom_instruction = ""

Expand Down
Loading