Refactor SQL streaming behavior in API server to default to LLM native delta transmission without secondary splitting. Update NLChatRequest to clarify optional parameters for SQL stream granularity and streaming throttle. Adjust _sse_stream_text_chunks and _chat_stream_events methods to streamline content handling and improve performance. Revise impact analysis documentation to reflect these changes.
This commit is contained in:
+5
-48
@@ -150,15 +150,6 @@ class NLChatRequest(BaseModel):
|
||||
session_id: Optional[str] = Field(None, description="会话ID")
|
||||
visitor_biz_id: Optional[str] = Field(None, description="访客业务ID")
|
||||
user_id: Optional[str] = Field(None, description="用户ID")
|
||||
streaming_throttle: Optional[int] = Field(
|
||||
None,
|
||||
description="流式节流:相邻 content 分片之间的间隔毫秒数(0/None 表示不延迟)",
|
||||
)
|
||||
sql_stream_granularity: Optional[str] = Field(
|
||||
None,
|
||||
validation_alias=AliasChoices("sql_stream_granularity", "sqlStreamGranularity"),
|
||||
description="SQL 生成流式分片:delta | char;不传则使用环境变量 SSE_SQL_GEN_SPLIT(默认 char)",
|
||||
)
|
||||
|
||||
|
||||
class IntentPayload(BaseModel):
|
||||
@@ -254,23 +245,6 @@ def _sse_data(obj: Dict[str, Any]) -> bytes:
|
||||
return f"data: {json.dumps(obj, ensure_ascii=False)}\n\n".encode("utf-8")
|
||||
|
||||
|
||||
def _iter_sql_gen_content_pieces(text: str, mode: Optional[str] = None) -> List[str]:
|
||||
"""
|
||||
将 LLM 流式片段再拆成前端期望的多条 SSE(与 chatStore onDelta 累加一致)。
|
||||
每条均为:{"stage": "sql_gen", "stream_kind": "content", "content": "..."}
|
||||
|
||||
mode / 环境变量 SSE_SQL_GEN_SPLIT:
|
||||
- char(请求默认):按 Unicode 标量逐字符发送(与「用户」「问题」逐条 data 一致)
|
||||
- delta:与上游 LLM 每次 delta 一致(块更大、事件更少)
|
||||
"""
|
||||
if not text:
|
||||
return []
|
||||
raw = (mode or os.getenv("SSE_SQL_GEN_SPLIT", "char") or "char").strip().lower()
|
||||
if raw in ("delta", "none", "0", "false"):
|
||||
return [text]
|
||||
return list(text)
|
||||
|
||||
|
||||
# 与前端 chatStore onDelta 一致:多次 { stage, stream_kind: content, content } 累加
|
||||
_DEFAULT_SSE_CHUNK_CHARS = int(os.getenv("SSE_STREAM_CHUNK_CHARS", "64"))
|
||||
|
||||
@@ -280,21 +254,16 @@ async def _sse_stream_text_chunks(
|
||||
content: str,
|
||||
*,
|
||||
chunk_size: Optional[int] = None,
|
||||
throttle_ms: Optional[int] = None,
|
||||
) -> AsyncIterator[bytes]:
|
||||
"""将长文本拆成多段 SSE,便于浏览器逐段渲染(流式)。"""
|
||||
if not content:
|
||||
return
|
||||
size = max(8, chunk_size or _DEFAULT_SSE_CHUNK_CHARS)
|
||||
delay = (throttle_ms or 0) / 1000.0 if throttle_ms and throttle_ms > 0 else 0.0
|
||||
for i in range(0, len(content), size):
|
||||
yield _sse_data(
|
||||
{"stage": stage, "stream_kind": "content", "content": content[i : i + size]}
|
||||
)
|
||||
if delay:
|
||||
await asyncio.sleep(delay)
|
||||
else:
|
||||
await asyncio.sleep(0)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
|
||||
def _conversation_nl_dict(reply: str) -> Dict[str, Any]:
|
||||
@@ -698,9 +667,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
|
||||
reply[:500] + ("…" if len(reply) > 500 else ""),
|
||||
)
|
||||
yield _sse_data({"stage": "orchestrator", "stream_kind": "content", "content": "CHAT"})
|
||||
async for pkt in _sse_stream_text_chunks(
|
||||
"chat", reply, throttle_ms=request.streaming_throttle
|
||||
):
|
||||
async for pkt in _sse_stream_text_chunks("chat", reply):
|
||||
yield pkt
|
||||
data_dict = _conversation_nl_dict(reply)
|
||||
yield _sse_data({"code": 200, "msg": "success", "data": data_dict})
|
||||
@@ -742,27 +709,19 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
|
||||
loop.call_soon_threadsafe(chunk_queue.put_nowait, None)
|
||||
|
||||
gen_task = asyncio.create_task(asyncio.to_thread(_run_generate_sync))
|
||||
throttle_ms = request.streaming_throttle or 0
|
||||
sql_chunk_delay = throttle_ms / 1000.0 if throttle_ms > 0 else 0.0
|
||||
gran = (
|
||||
(request.sql_stream_granularity or "").strip()
|
||||
or os.getenv("SSE_SQL_GEN_SPLIT", "char")
|
||||
or "char"
|
||||
).lower()
|
||||
while True:
|
||||
piece = await chunk_queue.get()
|
||||
if piece is None:
|
||||
break
|
||||
for frag in _iter_sql_gen_content_pieces(piece, mode=gran):
|
||||
# 不做二次切分:直接按 LLM 原生 delta 逐条透传给前端
|
||||
if piece:
|
||||
yield _sse_data(
|
||||
{
|
||||
"stage": "sql_gen",
|
||||
"stream_kind": "content",
|
||||
"content": frag,
|
||||
"content": piece,
|
||||
}
|
||||
)
|
||||
if sql_chunk_delay:
|
||||
await asyncio.sleep(sql_chunk_delay)
|
||||
await gen_task
|
||||
if holder.get("error"):
|
||||
raise holder["error"]
|
||||
@@ -928,8 +887,6 @@ async def nl_chat_stream(request: NLChatRequest):
|
||||
自然语言对话流式接口(SSE)。
|
||||
事件体为 JSON:分片 `data: {"stage","stream_kind","content"}`(例:sql_gen 时
|
||||
`stream_kind` 为 `content`)或结束包 `{code, msg, data}`。
|
||||
可选:`sql_stream_granularity` / `sqlStreamGranularity`(delta|char,未传则 `SSE_SQL_GEN_SPLIT`,默认 char)、
|
||||
`streaming_throttle`(相邻 content 分片间隔毫秒)。
|
||||
"""
|
||||
return StreamingResponse(
|
||||
_chat_stream_events(request),
|
||||
|
||||
Reference in New Issue
Block a user