Gemini 结构化输出重新请求总是失败: 请求在模型转换时结束
**受影响的:** `instructor/v2/providers/genai/handlers.py::reask_genai_structured_outputs`, 在 `Mode.GENAI_STRUCTURED_OUTPUTS` 上 (对于 `Provider.GENAI`,此模式是 `Mode.JSON` 的别名)。**版本:** 1.16.0 (重现)、1.17.0 (在标签处,函数是字节相同的)。**SDK:** google-genai 2.20.0。 **模型:** Gemini-3.7-flash (任何 Gemini 模型的行为都相同)。## 发生的情况在验证失败后,`reask_genai_structured_outputs` 将更正信息添加到 `kwargs["contents"]` 中,作为一个单个 `types.ModelContent`:Python kwargs["contents"].append( types.ModelContent( parts=[types.Part.from_text(text=f"Validation Error found:\n{exception}\n...")] ) ) 因此,重建的请求在模型转换后结束,Gemini 拒绝了每个重试请求: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Requests ending with a model turn are not supported.', 'status': 'INVALID_ARGUMENT' }} 其结果是,在 Gemini 结构化输出上,第一次重试后的每次尝试都是一个失败的提供者调用,并且调用者最终看到的异常是 400,而不是导致重试请求的验证错误。同一个函数中的第二个较小问题:`kwargs = kwargs.copy()` 是一个浅拷贝,因此 `append` 会修改调用者原始的 `contents` 列表。## 最小重现Python from pydantic import BaseModel, field_validator from instructor import Mode, from_provider class Color(BaseModel): name: str @field_validator("name") @classmethod def _never(cls, v: str) -> str: raise ValueError("every name is rejected on purpose") client = from_provider("google/Gemini-3.7-flash", api_key=KEY, async_client=False, mode=Mode.GENAI_STRUCTURED_OUTPUTS) client.on("completion:error", lambda err, **_: print("provider error:", str(err)[:120])) client.chat.completions.create_with_completion( response_model=Color, messages=[{"role": "user", "content": "Name one color."}], ) 输出(1.16.0): provider error: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Requests ending with a model turn are not supported.', ... InstructorRetryException: <failed_attempts> ... 1 validation error for Color ... } 发送了两个提供者请求;第二个始终返回 400。## 预期的结果重试请求应在用户转换后结束,与 `reask_genai_tools` 一致:将模型的上一个尝试添加为模型内容,然后将验证错误添加为 `types.Content(role="user", ...)`。## 解决方法在 Gemini 调用上传传 `max_retries=0`,以便不尝试重试(该参数计算第一次尝试后的重试次数);验证失败则立即在 `InstructorRetryException` 上显示失败尝试,并附带失败尝试。
内容来源: 567-labs/instructor