-
-
Notifications
You must be signed in to change notification settings - Fork 2k
fix: certain APIs return SSE-style string responses by parsing them as JSON and reconstructing a ChatCompletion object #7280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -462,7 +462,29 @@ async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stream=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extra_body=extra_body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --- 新增:兼容某些 API 强制返回 SSE 格式的 Bug --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(completion, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning(f"检测到 API 返回了字符串而非对象,尝试自动修复: {completion[:100]}...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 如果是 data:{...} 格式,去掉 "data:" 并解析 JSON | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json_str = completion.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if json_str.startswith("data:"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json_str = json_str[5:].strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 尝试解析 JSON | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completion_dict = json.loads(json_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+467
to
+476
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): SSE-like Some gateways send multi-line SSE chunks (e.g. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 重新构造 ChatCompletion 对象 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completion = ChatCompletion.construct(**completion_dict) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info("成功将字符串响应转换为 ChatCompletion 对象。") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+475
to
+481
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Using Because
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error(f"自动修复失败: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 如果修复失败,继续抛出原始错误 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise Exception(f"API 返回格式错误且无法修复:{type(completion)}: {completion}。") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+482
to
+485
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Re-raising a new generic The comment promises to rethrow the original error, but the code creates a new
Comment on lines
+467
to
+485
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里存在两个主要问题:
此外,建议优化异常处理逻辑以提高鲁棒性。 if isinstance(completion, str):
logger.warning(f"检测到 API 返回了字符串而非对象,尝试自动修复: {completion[:100]}...")
try:
# 兼容多行 SSE 格式,提取第一个包含有效 JSON 的 data 行
json_str = None
for line in completion.splitlines():
line = line.strip()
if line.startswith("data:"):
content = line[5:].strip()
if content and content != "[DONE]":
json_str = content
break
if not json_str:
json_str = completion.strip()
completion_dict = json.loads(json_str)
# 使用 model_validate 以确保嵌套对象(如 choices, message)被正确解析为 Pydantic 模型
# construct 方法不是递归的,会导致后续访问属性时抛出 AttributeError
completion = ChatCompletion.model_validate(completion_dict)
logger.info("成功将字符串响应转换为 ChatCompletion 对象。")
except Exception as e:
logger.error(f"自动修复失败: {e}")
raise Exception(f"API 返回格式错误且无法修复:{type(completion)}: {completion}。") |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # --------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+465
to
+486
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(completion, ChatCompletion): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise Exception( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"API 返回的 completion 类型错误:{type(completion)}: {completion}。", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 suggestion (security): Logging the raw response snippet may expose sensitive content and could be toned down or guarded.
This substring can still include user prompts or other private data, which may be sensitive depending on where logs are stored or shipped. Please consider masking/redacting the payload, logging only metadata (e.g., length or content type), or gating this detailed snippet behind a debug-only flag.
Suggested implementation:
If not already present at the top of
astrbot/core/provider/sources/openai_source.py, addimport loggingand ensureloggeris configured appropriately for your project’s logging setup.