{"record":{"id":"828a898d7af15d34","repo":"binary-husky/gpt_academic","slug":"gemini","errorCode":null,"errorMessage":"解析GEMINI消息出错。","messagePattern":"解析GEMINI消息出错。","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"request_llms/bridge_google_gemini.py","lineNumber":36,"sourceCode":"def predict_no_ui_long_connection(inputs:str, llm_kwargs:dict, history:list=[], sys_prompt:str=\"\", observe_window:list=[],\n                                  console_silence:bool=False):\n    # 检查API_KEY\n    if get_conf(\"GEMINI_API_KEY\") == \"\":\n        raise ValueError(f\"请配置 GEMINI_API_KEY。\")\n\n    genai = GoogleChatInit(llm_kwargs)\n    watch_dog_patience = 5  # 看门狗的耐心, 设置5秒即可\n    gpt_replying_buffer = ''\n    stream_response = genai.generate_chat(inputs, llm_kwargs, history, sys_prompt)\n    for response in stream_response:\n        results = response.decode()\n        match = re.search(r'\"text\":\\s*\"((?:[^\"\\\\]|\\\\.)*)\"', results, flags=re.DOTALL)\n        error_match = re.search(r'\\\"message\\\":\\s*\\\"(.*?)\\\"', results, flags=re.DOTALL)\n        if match:\n            try:\n                paraphrase = json.loads('{\"text\": \"%s\"}' % match.group(1))\n            except:\n                raise ValueError(f\"解析GEMINI消息出错。\")\n            buffer = paraphrase['text']\n            gpt_replying_buffer += buffer\n            if len(observe_window) >= 1:\n                observe_window[0] = gpt_replying_buffer\n            if len(observe_window) >= 2:\n                if (time.time() - observe_window[1]) > watch_dog_patience: raise RuntimeError(\"程序终止。\")\n        if error_match:\n            raise RuntimeError(f'{gpt_replying_buffer} 对话错误')\n    return gpt_replying_buffer\n\ndef make_media_input(inputs, image_paths):\n    image_base64_array = []\n    for image_path in image_paths:\n        path = os.path.abspath(image_path)\n        inputs = inputs + f'<br/><br/><div align=\"center\"><img src=\"file={path}\"></div>'\n        base64 = encode_image(path)\n        image_base64_array.append(base64)\n    return inputs, image_base64_array","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/binary-husky/gpt_academic/blob/d6bde0fa54373309bd05823a49bda8da019d2c77/request_llms/bridge_google_gemini.py#L18-L54","documentation":"ValueError ('解析GEMINI消息出错。') raised when the regex-extracted \"text\" field from a Gemini stream frame cannot be re-assembled into valid JSON: the code fishes the captured group out of the raw SSE bytes with a regex, wraps it as '{\"text\": \"%s\"}', and calls json.loads. If the captured string contains unescaped quotes, control characters, or unicode the naive wrapping can't represent, json.loads throws and this error replaces it. It signals the hand-rolled parser lost against Gemini's escaping, not that Gemini itself errored (error frames take the separate error_match branch).","triggerScenarios":"Gemini streams text containing characters that break the regex/wrapping contract: raw double quotes with unusual escaping, newlines/backslashes in the captured span, non-ASCII output under odd encodings; also fires on chunk boundaries that split an escaped sequence so the regex capture is malformed.","commonSituations":"Asking Gemini for code with heavy quote/backslash content; Chinese/multilingual replies with unicode escapes; proxy re-chunking SSE frames so decode() yields partial JSON; older bridge versions' regex parser versus newer Gemini response formats.","solutions":["Retry the query — transient chunk-boundary splits often parse fine on a second attempt.","Update gpt_academic: newer bridge_google_gemini.py replaced regex fishing with proper full-frame JSON parsing.","If persistent, reduce prompt-induced heavy quoting (ask for plain text instead of JSON/code blocks) to confirm parser escaping is the cause.","Check the proxy chain: intermediary re-chunking of the SSE stream is a common amplifier of partial-frame parse failures."],"exampleFix":"# before (fragile regex reassembly)\nparaphrase = json.loads('{\"text\": \"%s\"}' % match.group(1))\n\n# after (parse the whole frame)\nframe = json.loads(response.decode())\nbuffer = frame[\"candidates\"][0][\"content\"][\"parts\"][0][\"text\"]","handlingStrategy":"retry","validationCode":null,"typeGuard":"def is_parseable_gemini_text_frame(raw: bytes) -> bool:\n    \"\"\"A frame is safely parseable only if the regex capture survives JSON re-wrapping.\"\"\"\n    import re, json\n    text = raw.decode(errors='replace')\n    m = re.search(r'\"text\":\\s*\"((?:[^\"\\\\]|\\\\.)*)\"', text, flags=re.DOTALL)\n    if not m:\n        return False\n    try:\n        json.loads('{\"text\": \"%s\"}' % m.group(1))\n        return True\n    except Exception:\n        return False","tryCatchPattern":"try:\n    result = predict_no_ui_long_connection(inputs, llm_kwargs, history, sys_prompt, observe_window)\nexcept ValueError as e:\n    if '解析GEMINI消息出错' in str(e):\n        time.sleep(1)\n        result = predict_no_ui_long_connection(inputs, llm_kwargs, history, sys_prompt, observe_window)  # chunk-boundary splits usually clear on retry\n    else:\n        raise","preventionTips":["Retry once — transient SSE chunk-boundary splits are the most common cause.","Update gpt_academic to a version parsing full Gemini frames with json.loads instead of regex reassembly.","Avoid proxies that re-chunk SSE frames.","When prompts induce heavy quoting/code output, prefer models/endpoints whose parsing path is frame-based."],"tags":["gemini","google","json","parsing","streaming"],"backgroundTag":null,"analyzedSha":"d6bde0fa54373309bd05823a49bda8da019d2c77","analyzedAt":"2026-08-14T22:48:35.038Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}