fix: OpenAI Compatible Provider 空响应自动重试机制#191
Conversation
- OpenAI Provider 增加空响应自动重试机制(最多 2 次重试,指数退避) - 增强空响应诊断日志,记录原始响应摘要便于排查 - structured_json_pipeline 将空响应标记为可重试错误 - 更新并新增单元测试覆盖重试逻辑 Closes shenminglinyi#185
📝 WalkthroughWalkthroughThis PR implements automatic retry logic with exponential backoff for transient empty-content responses from the OpenAI provider. It expands error detection markers in the pipeline, reworks the provider's generate method to retry empty responses, adds diagnostic logging of raw response summaries, and adds comprehensive test coverage for retry and recovery paths. ChangesEmpty Content Retry Mechanism
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@infrastructure/ai/providers/openai_provider.py`:
- Around line 77-93: The computed flag use_responses should be recalculated on
each retry so changes to the class-level _fallback_to_chat_cache are respected;
move or re-evaluate use_responses (which depends on base_url, self._use_legacy
and self.__class__._fallback_to_chat_cache) inside the for loop in generate()
before deciding to call _generate_via_responses, so that after you add base_url
to _fallback_to_chat_cache subsequent attempts will skip the Responses path and
fall back to chat completions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ceb01747-95b5-4c7d-a1ed-0257cd115370
📒 Files selected for processing (3)
application/ai/structured_json_pipeline.pyinfrastructure/ai/providers/openai_provider.pytests/unit/infrastructure/ai/providers/test_openai_provider.py
| base_url = self.settings.base_url or "https://api.openai.com/v1" | ||
| use_responses = not self._use_legacy and base_url not in self.__class__._fallback_to_chat_cache | ||
|
|
||
| if use_responses: | ||
| last_empty_exc: Exception | None = None | ||
| for attempt in range(1 + _EMPTY_CONTENT_MAX_RETRIES): | ||
| try: | ||
| return await self._generate_via_responses(prompt, config) | ||
| except (openai.NotFoundError, openai.BadRequestError) as e: | ||
| logger.info(f"Responses API unsupported for {base_url}, falling back to chat completions: {str(e)}") | ||
| self.__class__._fallback_to_chat_cache.add(base_url) | ||
| except Exception as e: | ||
| # 某些网关在路径错误时可能不抛严格的 404 而是抛出其他错误,如果消息含有明确路径错误也尝试降级 | ||
| if "404" in str(e) or "Not Found" in str(e) or "400" in str(e) or "Account invalid" in str(e) or "INVALID_ARGUMENT" in str(e): | ||
| logger.info(f"Gateway returned error for Responses API ({base_url}), falling back: {str(e)}") | ||
| self.__class__._fallback_to_chat_cache.add(base_url) | ||
| else: | ||
| raise | ||
| if use_responses: | ||
| try: | ||
| return await self._generate_via_responses(prompt, config) | ||
| except (openai.NotFoundError, openai.BadRequestError) as e: | ||
| logger.info(f"Responses API unsupported for {base_url}, falling back to chat completions: {str(e)}") | ||
| self.__class__._fallback_to_chat_cache.add(base_url) | ||
| except Exception as e: | ||
| # 某些网关在路径错误时可能不抛严格的 404 而是抛出其他错误,如果消息含有明确路径错误也尝试降级 | ||
| if "404" in str(e) or "Not Found" in str(e) or "400" in str(e) or "Account invalid" in str(e) or "INVALID_ARGUMENT" in str(e): | ||
| logger.info(f"Gateway returned error for Responses API ({base_url}), falling back: {str(e)}") | ||
| self.__class__._fallback_to_chat_cache.add(base_url) |
There was a problem hiding this comment.
Recompute use_responses inside the retry loop.
Line 78 computes use_responses once, but Lines 88/93 mutate _fallback_to_chat_cache later. In the same generate() call, subsequent retries still use the stale True and keep hitting Responses before falling back again.
💡 Suggested fix
- use_responses = not self._use_legacy and base_url not in self.__class__._fallback_to_chat_cache
-
last_empty_exc: Exception | None = None
for attempt in range(1 + _EMPTY_CONTENT_MAX_RETRIES):
+ use_responses = (
+ not self._use_legacy
+ and base_url not in self.__class__._fallback_to_chat_cache
+ )
try:
if use_responses:
try:
return await self._generate_via_responses(prompt, config)
except (openai.NotFoundError, openai.BadRequestError) as e:
logger.info(f"Responses API unsupported for {base_url}, falling back to chat completions: {str(e)}")
self.__class__._fallback_to_chat_cache.add(base_url)
+ use_responses = False
except Exception as e:
# 某些网关在路径错误时可能不抛严格的 404 而是抛出其他错误,如果消息含有明确路径错误也尝试降级
if "404" in str(e) or "Not Found" in str(e) or "400" in str(e) or "Account invalid" in str(e) or "INVALID_ARGUMENT" in str(e):
logger.info(f"Gateway returned error for Responses API ({base_url}), falling back: {str(e)}")
self.__class__._fallback_to_chat_cache.add(base_url)
+ use_responses = False
else:
raise🧰 Tools
🪛 Ruff (0.15.15)
[warning] 87-87: Use explicit conversion flag
Replace with conversion flag
(RUF010)
[warning] 90-90: Comment contains ambiguous , (FULLWIDTH COMMA). Did you mean , (COMMA)?
(RUF003)
[warning] 92-92: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@infrastructure/ai/providers/openai_provider.py` around lines 77 - 93, The
computed flag use_responses should be recalculated on each retry so changes to
the class-level _fallback_to_chat_cache are respected; move or re-evaluate
use_responses (which depends on base_url, self._use_legacy and
self.__class__._fallback_to_chat_cache) inside the for loop in generate() before
deciding to call _generate_via_responses, so that after you add base_url to
_fallback_to_chat_cache subsequent attempts will skip the Responses path and
fall back to chat completions.
Summary
Test plan
Closes #185
Summary by CodeRabbit
Release Notes