mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
fix(api): handle array message content on OpenAI chat completions (#15359)
### Related issues Closes #15358 <!-- After filing upstream, replace XXXX with your issue number. --> --- ### What problem does this PR solve? `POST /api/v1/openai/<chat_id>/chat/completions` forwards `messages` to `async_chat` without normalizing `content`. Downstream, `dialog_service` assumes string content: ```python re.sub(r"##\d+\$\$", "", m["content"]) ``` OpenAI-compatible clients may send `content` as an **array** of parts (text, `image_url`, etc.), including text-only arrays. That causes `TypeError` and HTTP **500** instead of a valid response or a clear **400**. `openai_api.py` also reads `messages[-1]["content"]` directly for `prompt` without handling list-shaped content. This PR normalizes array `content` to a string (concatenating `type: text` parts) before calling `async_chat`, matching a minimal OpenAI-compat path. Image parts can be documented as unsupported or handled in a follow-up if vision integration is required.
This commit is contained in:
committed by
GitHub
parent
67a3ed7558
commit
09d0a17453
@@ -1045,7 +1045,112 @@ def test_openai_nonstream_branch_unit(monkeypatch):
|
||||
|
||||
res = _run(inspect.unwrap(module.openai_chat_completions)("chat-1"))
|
||||
assert res["choices"][0]["message"]["content"] == "world"
|
||||
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_openai_defaults_to_nonstream_when_stream_omitted_unit(monkeypatch):
|
||||
"""Omitted stream must default to false (OpenAI API compat), not SSE."""
|
||||
module = _load_openai_api_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module, "num_tokens_from_string", lambda text: len(text or ""))
|
||||
monkeypatch.setattr(
|
||||
module.DialogService,
|
||||
"query",
|
||||
lambda **_kwargs: [SimpleNamespace(kb_ids=[], llm_id="chat-model", tenant_id="tenant-1")],
|
||||
)
|
||||
|
||||
stream_flags = []
|
||||
|
||||
async def fake_async_chat(_dia, _msg, stream, **_kwargs):
|
||||
stream_flags.append(stream)
|
||||
yield {"answer": "hello", "reference": {}}
|
||||
|
||||
monkeypatch.setattr(module, "async_chat", fake_async_chat)
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"model": "model",
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
res = _run(inspect.unwrap(module.openai_chat_completions)("chat-1"))
|
||||
assert stream_flags == [False]
|
||||
assert isinstance(res, dict)
|
||||
assert res["object"] == "chat.completion"
|
||||
assert res["choices"][0]["message"]["content"] == "hello"
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_openai_array_text_content_normalized_unit(monkeypatch):
|
||||
"""OpenAI-style array content with text parts must not crash async_chat."""
|
||||
module = _load_openai_api_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module, "num_tokens_from_string", lambda text: len(text or ""))
|
||||
monkeypatch.setattr(
|
||||
module.DialogService,
|
||||
"query",
|
||||
lambda **_kwargs: [SimpleNamespace(kb_ids=[], llm_id="chat-model", tenant_id="tenant-1")],
|
||||
)
|
||||
|
||||
captured_msg = []
|
||||
|
||||
async def fake_async_chat(_dia, msg, _stream, **_kwargs):
|
||||
captured_msg.append(msg)
|
||||
yield {"answer": "ok", "reference": {}}
|
||||
|
||||
monkeypatch.setattr(module, "async_chat", fake_async_chat)
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"model": "model",
|
||||
"stream": False,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Hello"},
|
||||
{"type": "text", "text": "World"},
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
res = _run(inspect.unwrap(module.openai_chat_completions)("chat-1"))
|
||||
assert captured_msg[0][0]["content"] == "Hello\nWorld"
|
||||
assert res["choices"][0]["message"]["content"] == "ok"
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_openai_invalid_message_content_type_unit(monkeypatch):
|
||||
module = _load_openai_api_module(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(
|
||||
module.DialogService,
|
||||
"query",
|
||||
lambda **_kwargs: [SimpleNamespace(kb_ids=[], llm_id="chat-model", tenant_id="tenant-1")],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
module,
|
||||
"get_request_json",
|
||||
lambda: _AwaitableValue(
|
||||
{
|
||||
"model": "model",
|
||||
"messages": [{"role": "user", "content": 12345}],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
res = _run(inspect.unwrap(module.openai_chat_completions)("chat-1"))
|
||||
assert "messages[].content must be a string or an array of content parts." in res["message"]
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_agents_openai_compatibility_unit(monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user