{"record":{"id":"5ba436821800e540","repo":"binary-husky/gpt_academic","slug":"error-5ba436","errorCode":null,"errorMessage":"用户取消了程序。","messagePattern":"用户取消了程序。","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"request_llms/bridge_ollama.py","lineNumber":110,"sourceCode":"        except requests.exceptions.ConnectionError:\n            chunk = next(stream_response) # 失败了，重试一次？再失败就没办法了。\n        chunk_decoded, chunkjson, is_last_chunk = decode_chunk(chunk)\n        if chunk:\n            try:\n                if is_last_chunk:\n                    # 判定为数据流的结束，gpt_replying_buffer也写完了\n                    logger.info(f'[response] {result}')\n                    break\n                result += chunkjson['message'][\"content\"]\n                if not console_silence: print(chunkjson['message'][\"content\"], end='')\n                if observe_window is not None:\n                    # 观测窗，把已经获取的数据显示出去\n                    if len(observe_window) >= 1:\n                        observe_window[0] += chunkjson['message'][\"content\"]\n                    # 看门狗，如果超过期限没有喂狗，则终止\n                    if len(observe_window) >= 2:\n                        if (time.time()-observe_window[1]) > watch_dog_patience:\n                            raise RuntimeError(\"用户取消了程序。\")\n            except Exception as e:\n                chunk = get_full_error(chunk, stream_response)\n                chunk_decoded = chunk.decode()\n                error_msg = chunk_decoded\n                logger.error(error_msg)\n                raise RuntimeError(\"Json解析不合常规\")\n    return result\n\n\ndef predict(inputs, llm_kwargs, plugin_kwargs, chatbot, history=[], system_prompt='', stream = True, additional_fn=None):\n    \"\"\"\n    发送至chatGPT，流式获取输出。\n    用于基础的对话功能。\n    inputs 是本次问询的输入\n    top_p, temperature是chatGPT的内部调优参数\n    history 是之前的对话列表（注意无论是inputs还是history，内容太长了都会触发token数量溢出的错误）\n    chatbot 为WebUI中显示的对话列表，修改它，然后yield出去，可以直接修改对话界面内容\n    additional_fn代表点击的哪个按钮，按钮见functional.py","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/binary-husky/gpt_academic/blob/d6bde0fa54373309bd05823a49bda8da019d2c77/request_llms/bridge_ollama.py#L92-L128","documentation":"RuntimeError raised by the watchdog in bridge_ollama.predict_no_ui_long_connection. The caller passes an observe_window list whose second element holds the last 'feed' timestamp; if the consuming thread does not refresh observe_window[1] within watch_dog_patience seconds, the generation loop assumes the user pressed stop and aborts. Despite the message text ('user cancelled the program'), it fires on any watchdog timeout, not only an explicit cancel.","triggerScenarios":"Calling predict_no_ui_long_connection with observe_window=[buffer, timestamp] and never updating observe_window[1] while streaming; user clicking Stop in the WebUI which stops feeding the dog; a slow Ollama server whose chunks exceed the patience window.","commonSituations":"Plugin code that reuses the observe_window pattern but forgets to refresh the timestamp each loop; long generations on a heavily loaded local Ollama instance; genuine user cancellation via the Gradio UI.","solutions":["If the user truly cancelled, no fix is needed - the error is the intended stop signal","Otherwise, make the consumer thread update observe_window[1] = time.time() every iteration while it still wants output","Increase watch_dog_patience if the local Ollama server legitimately stalls longer than the limit","Catch RuntimeError around the call and check the message to distinguish cancellation from other failures"],"exampleFix":"// before\nfor resp in predict_no_ui_long_connection(..., observe_window=ow):\n    pass  # never feed the dog\n\n// after\nwhile True:\n    ow[1] = time.time()\n    try:\n        resp = next(it)\n    except StopIteration:\n        break","handlingStrategy":"try-catch","validationCode":"import time\n# contract check before calling\nassert len(observe_window) >= 2 and isinstance(observe_window[1], float), \\\n    'observe_window must be [buffer, heartbeat_timestamp]'","typeGuard":null,"tryCatchPattern":"try:\n    result = predict_no_ui_long_connection(inputs, llm_kwargs, history, observe_window=ow)\nexcept RuntimeError as e:\n    if str(e) == '用户取消了程序。':\n        return ow[0]  # partial output, user cancelled\n    raise","preventionTips":["Always spawn a feeder thread that refreshes observe_window[1] every 1-2 seconds while output is still wanted","Treat this exact message as the cancellation signal, not as a provider failure","Never retry automatically on cancellation - check user intent first"],"tags":["ollama","streaming","watchdog","cancellation","timeout"],"backgroundTag":null,"analyzedSha":"d6bde0fa54373309bd05823a49bda8da019d2c77","analyzedAt":"2026-08-14T22:48:35.038Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}