binary-husky/gpt_academic · error · RuntimeError
用户取消了程序。
Error message
用户取消了程序。
What it means
Watchdog RuntimeError inside bridge_openrouter.predict_no_ui_long_connection: observe_window[1] holds the caller's last heartbeat timestamp, and if it is older than watch_dog_patience seconds the loop aborts with '用户取消了程序。' (user cancelled). It is a cooperative-cancellation mechanism - any caller that stops feeding the dog causes it, intentional or not.
Source
Thrown at request_llms/bridge_openrouter.py:199
raise RuntimeError("OpenAI拒绝了请求:" + error_msg)
if ('data: [DONE]' in chunk_decoded): break # api2d 正常完成
# 提前读取一些信息 (用于判断异常)
json_data = chunkjson['choices'][0]
delta = json_data["delta"]
if len(delta) == 0: break
if (not has_content) and has_role: continue
if (not has_content) and (not has_role): continue # raise RuntimeError("发现不标准的第三方接口:"+delta)
if has_content: # has_role = True/False
result += delta["content"]
if not console_silence: print(delta["content"], end='')
if observe_window is not None:
# 观测窗,把已经获取的数据显示出去
if len(observe_window) >= 1:
observe_window[0] += delta["content"]
# 看门狗,如果超过期限没有喂狗,则终止
if len(observe_window) >= 2:
if (time.time()-observe_window[1]) > watch_dog_patience:
raise RuntimeError("用户取消了程序。")
else: raise RuntimeError("意外Json结构:"+delta)
if json_data and json_data['finish_reason'] == 'content_filter':
raise RuntimeError("由于提问含不合规内容被Azure过滤。")
if json_data and json_data['finish_reason'] == 'length':
raise ConnectionAbortedError("正常结束,但显示Token不足,导致输出不完整,请削减单次输入的文本量。")
return result
def predict(inputs:str, llm_kwargs:dict, plugin_kwargs:dict, chatbot:ChatBotWithCookies,
history:list=[], system_prompt:str='', stream:bool=True, additional_fn:str=None):
"""
发送至chatGPT,流式获取输出。
用于基础的对话功能。
inputs 是本次问询的输入
top_p, temperature是chatGPT的内部调优参数
history 是之前的对话列表(注意无论是inputs还是history,内容太长了都会触发token数量溢出的错误)
chatbot 为WebUI中显示的对话列表,修改它,然后yield出去,可以直接修改对话界面内容
additional_fn代表点击的哪个按钮,按钮见functional.pyView on GitHub (pinned to d6bde0fa54)
Solutions
- If the user cancelled, treat this as normal termination and discard partial state
- Ensure the consumer thread updates observe_window[1] = time.time() each poll iteration
- Increase watch_dog_patience for slow providers
- Wrap the call in try/except RuntimeError and distinguish cancellation from provider errors before retrying
Example fix
# consumer contract
import threading, time
def feed(ow):
while streaming[0]:
ow[1] = time.time()
time.sleep(1) Defensive patterns
Strategy: try-catch
Validate before calling
import time assert len(observe_window) >= 2, 'watchdog requires observe_window[1] heartbeat' ow[1] = time.time() # prime the dog before first chunk
Try / catch
try:
result = predict_no_ui_long_connection(...)
except RuntimeError as e:
if str(e) == '用户取消了程序。':
cancelled.set(); return ow[0]
raise Prevention
- Implement the feeder thread as a reusable helper so no call site forgets it
- Refresh the heartbeat in the same loop that reads observe_window[0]
- Document that this message means cancellation by contract across all bridges
When it happens
Trigger: Caller stops refreshing observe_window[1] (user pressed Stop, or the monitoring thread died); network stall so no delta arrives while the watchdog compares against an unfed timestamp; plugin using predict_no_ui_long_connection without a live feeder thread.
Common situations: Gradio Stop button during a long OpenRouter generation; plugin author forgetting the feeder-thread contract; slow upstream where chunks pause longer than the patience value.
Related errors
AI-assisted analysis of binary-husky/gpt_academic@d6bde0fa54 (2026-08-14).
Data as JSON: /api/errors/0fd2fd2940a61efd.
Report an issue: GitHub.