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.

Solutions

  1. If the user cancelled, treat this as normal termination and discard partial state
  2. Ensure the consumer thread updates observe_window[1] = time.time() each poll iteration
  3. Increase watch_dog_patience for slow providers
  4. 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

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.

Appendix: 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.py

View on GitHub (pinned to d6bde0fa54)