v2.0.1:修复推理型模型(Qwen3/DeepSeek reasoning_content)误报未收到输出+连接测试按连通判定+采样失败不中断+流式超时放宽60s/300s

This commit is contained in:
2026-08-23 18:49:09 +08:00
parent 87b836b52a
commit 5de118a7b5
6 changed files with 55 additions and 22 deletions
+23 -11
View File
@@ -56,6 +56,8 @@ def _parse_sse_line(line):
def _metrics(start, first_token_at, end, prompt_tokens, output_tokens,
cached_tokens, output_chars, prompt_chars):
if first_token_at is None:
first_token_at = end # 未收到正文但请求完成(如纯 usage 响应)
ttft_ms = (first_token_at - start) * 1000
decode_ms = (end - first_token_at) * 1000
total_ms = (end - start) * 1000
@@ -103,6 +105,7 @@ def stream_openai(cfg, prompt, gen, log, should_stop=None):
first_token_at = None
output_chars = 0
prompt_tokens = output_tokens = cached_tokens = 0
event_count = 0
resp = None
try:
while True:
@@ -126,13 +129,15 @@ def stream_openai(cfg, prompt, gen, log, should_stop=None):
for obj in _iter_json(resp):
if should_stop and should_stop():
raise StopRequested()
event_count += 1
if obj.get("choices"):
delta = obj["choices"][0].get("delta") or {}
text = delta.get("content") or ""
if text:
# 兼容推理型模型:Qwen3/DeepSeek 思维链在 reasoning_content
piece = delta.get("content") or delta.get("reasoning_content") or ""
if piece:
if first_token_at is None:
first_token_at = time.time()
output_chars += len(text)
output_chars += len(piece)
usage = obj.get("usage")
if usage:
prompt_tokens = usage.get("prompt_tokens") or 0
@@ -148,8 +153,8 @@ def stream_openai(cfg, prompt, gen, log, should_stop=None):
if resp is not None:
resp.close()
if first_token_at is None:
raise ProviderError("未收到任何输出内容")
if event_count == 0:
raise ProviderError("未收到任何输出内容HTTP 200 但响应流为空)")
end = time.time()
return _metrics(start, first_token_at, end, prompt_tokens, output_tokens,
cached_tokens, output_chars, len(prompt))
@@ -190,6 +195,7 @@ def stream_anthropic(cfg, prompt, gen, log, should_stop=None):
first_token_at = None
output_chars = 0
prompt_tokens = output_tokens = 0
event_count = 0
resp = None
try:
if should_stop and should_stop():
@@ -204,12 +210,15 @@ def stream_anthropic(cfg, prompt, gen, log, should_stop=None):
for obj in _iter_json(resp):
if should_stop and should_stop():
raise StopRequested()
event_count += 1
etype = obj.get("type")
if etype == "message_start":
usage = (obj.get("message") or {}).get("usage") or {}
prompt_tokens = usage.get("input_tokens") or 0
elif etype == "content_block_delta":
text = (obj.get("delta") or {}).get("text") or ""
delta = obj.get("delta") or {}
# 兼容 extended thinkingthinking 文本也算输出
text = delta.get("text") or delta.get("thinking") or ""
if text:
if first_token_at is None:
first_token_at = time.time()
@@ -225,8 +234,8 @@ def stream_anthropic(cfg, prompt, gen, log, should_stop=None):
if resp is not None:
resp.close()
if first_token_at is None:
raise ProviderError("未收到任何输出内容")
if event_count == 0:
raise ProviderError("未收到任何输出内容HTTP 200 但响应流为空)")
end = time.time()
return _metrics(start, first_token_at, end, prompt_tokens, output_tokens,
0, output_chars, len(prompt))
@@ -252,6 +261,7 @@ def stream_google(cfg, prompt, gen, log, should_stop=None):
first_token_at = None
output_chars = 0
prompt_tokens = output_tokens = cached_tokens = 0
event_count = 0
resp = None
try:
if should_stop and should_stop():
@@ -266,11 +276,13 @@ def stream_google(cfg, prompt, gen, log, should_stop=None):
for obj in _iter_json(resp):
if should_stop and should_stop():
raise StopRequested()
event_count += 1
cands = obj.get("candidates") or []
if cands:
parts = (cands[0].get("content") or {}).get("parts") or []
for part in parts:
text = part.get("text") or ""
# 兼容 thinking 模型:thought 文本也算输出
text = part.get("text") or part.get("thought") or ""
if text:
if first_token_at is None:
first_token_at = time.time()
@@ -288,8 +300,8 @@ def stream_google(cfg, prompt, gen, log, should_stop=None):
if resp is not None:
resp.close()
if first_token_at is None:
raise ProviderError("未收到任何输出内容")
if event_count == 0:
raise ProviderError("未收到任何输出内容HTTP 200 但响应流为空)")
end = time.time()
return _metrics(start, first_token_at, end, prompt_tokens, output_tokens,
cached_tokens, output_chars, len(prompt))