Which version of integration_openai are you using?
4.5.1
Which version of Nextcloud are you using?
33.0.6.2, using the nextcloud:33.0.6-apache Docker image.
Which browser are you using? In case you are using the phone App, specify the Android or iOS version and device please.
Not browser-specific. Observed through the Nextcloud Assistant web interface, but the failure occurs in server-side response normalization.
Describe the Bug
When an OpenAI-compatible endpoint returns assistant message.content as a list of typed content chunks, integration_openai 4.5.1 ignores the content and can eventually report:
No result in OpenAI/LocalAI response.
I encountered this repeatedly with Mistral Small 4 (mistral-small-2603) through https://api.eu.mistral.ai/v1. The failures occurred in core:text2text:chatwithtools after an external MCP web-search tool had completed successfully. Both single and parallel tool-use cases were observed, so parallelism was not the cause.
The relevant parser in OpenAiAPIService::createChatCompletion() only accepts content when it is a string:
if (isset($choice['message']['content']) && is_string($choice['message']['content'])) {
$completions['messages'][] = $choice['message']['content'];
}
Mistral's assistant-message schema permits content to be either a string or a list of typed chunks. Observed chunk types included:
Official schema references:
Confirmed response metadata
I reconstructed one previously failed synthetic request and replayed it three times with the same model, messages, tools, n: 1, max_tokens: 5000, no extra parameters, and no explicit reasoning_effort.
All three requests returned HTTP 200, one choice, finish_reason: stop, no tool call, and no audio:
| Replay |
message.content |
Chunk types |
Parser result |
| 1 |
string |
n/a |
accepted |
| 2 |
array |
alternating text and reference, ending in text |
rejected |
| 3 |
array |
alternating text and reference, ending in text |
rejected |
This shows why the problem is intermittent: the same request can return either a string or typed content.
A separate public synthetic request using reasoning_effort: "high" returned a thinking chunk followed by a text chunk. Version 4.5.1 rejected that response as well. This was only a deterministic response-shape probe; I am not proposing reasoning_effort as a production setting or workaround.
No prompts, tool-result bodies, response text, credentials, user identifiers, or raw provider responses have been retained.
Tested local workaround for 4.5.1
I locally patched version 4.5.1 to:
- preserve string content unchanged;
- concatenate top-level
type: "text" chunks in order;
- recognize but not expose
thinking chunks in the 4.5.1 provider;
- ignore
reference, unknown, null, empty, and malformed chunks safely; and
- leave outgoing requests, tool calls, and audio handling unchanged.
This fixed the observed failure. After deployment:
- an explicit web-search request completed two parallel MCP searches and returned an answer; and
- retrying an existing conversation that had previously failed with this parser error returned an answer.
The patch below is the exact tested 4.5.1 workaround. It is not necessarily the preferred implementation for current main, because current main also has a streaming parser and structured reasoning support.
Tested integration_openai 4.5.1 workaround diff
--- a/lib/Service/OpenAiAPIService.php
+++ b/lib/Service/OpenAiAPIService.php
@@ -708,8 +708,9 @@
}
// always try to get a message
- if (isset($choice['message']['content']) && is_string($choice['message']['content'])) {
- $completions['messages'][] = $choice['message']['content'];
+ $content = $this->normalizeChatMessageContent($choice['message']['content'] ?? null);
+ if ($content !== null) {
+ $completions['messages'][] = $content;
}
if (isset($choice['message']['audio'], $choice['message']['audio']['data']) && is_string($choice['message']['audio']['data'])) {
$completions['audio_messages'][] = $choice['message'];
@@ -720,6 +721,36 @@
}
/**
+ * Normalize OpenAI-compatible string content and typed content chunks.
+ *
+ * Thinking chunks are recognized but intentionally not exposed by this
+ * provider version. Unknown and malformed chunks are ignored.
+ */
+ private function normalizeChatMessageContent(mixed $content): ?string {
+ if (is_string($content)) {
+ return $content;
+ }
+ if (!is_array($content) || !array_is_list($content)) {
+ return null;
+ }
+
+ $textChunks = [];
+ foreach ($content as $chunk) {
+ if (!is_array($chunk)) {
+ continue;
+ }
+ if (($chunk['type'] ?? null) === 'thinking') {
+ continue;
+ }
+ if (($chunk['type'] ?? null) === 'text' && isset($chunk['text']) && is_string($chunk['text'])) {
+ $textChunks[] = $chunk['text'];
+ }
+ }
+
+ return $textChunks === [] ? null : implode('', $textChunks);
+ }
+
+ /**
* @param string $configKey
* @return array|null
*/
Local regression fixtures covered:
- existing OpenAI string content;
- Mistral
thinking plus text chunks;
- interleaved
text and reference chunks;
- multiple text and unknown chunks;
- null, empty, and malformed content; and
- unchanged existing tool-call and audio normalization.
The patched file passed PHP 8.3 lint.
Current upstream main also has streaming response parsing. A complete upstream fix may therefore need the same typed-content support in both non-streaming normalization and streaming SSE parsing.
Investigation assistance
I encountered this bug multiple times. OpenAI gpt-5.6-sol was used to help compare the synthetic occurrences, construct a privacy-safe replay, inspect the relevant schemas, and prepare the local workaround and tests.
I supplied the provider credential locally through getpass, approved and performed the production deployment, and manually validated the resulting Assistant and MCP behavior.
Expected Behavior
OpenAI-compatible assistant responses should produce a final text result when message.content is either:
- a normal string; or
- a list containing valid
type: "text" chunks.
For typed content, the text chunks should be concatenated in order. Thinking, reference, unknown, or malformed chunks should not cause otherwise valid final text to be discarded.
Existing behavior for string content, separate reasoning_content, tool calls, audio, and ordinary OpenAI-compatible providers should remain unchanged.
To Reproduce
Intermittent provider reproduction
-
Configure integration_openai 4.5.1 with the Mistral EU OpenAI-compatible endpoint and mistral-small-2603.
-
Submit a sufficiently involved chat request, optionally with tools.
-
Allow the provider to complete the request successfully.
-
Repeat if necessary because Mistral may return either string or typed content for the same request.
-
When message.content is an array of typed chunks, observe that integration_openai returns no message and the task can fail with:
No result in OpenAI/LocalAI response.
Provider-level typed-content reproduction
A near-deterministic way to request the relevant response shape is to send a separate diagnostic request with reasoning_effort: "high":
{
"model": "mistral-small-2603",
"messages": [
{
"role": "user",
"content": "Tell me more about Amsterdam:\n1. What is its history?\n2. What are its demographics?\n3. Is it worth visiting for sightseeing?"
}
],
"n": 1,
"max_tokens": 5000,
"reasoning_effort": "high"
}
In my test, the equivalent synthetic request returned HTTP 200 with:
message.content type: array
chunk types: thinking, text
finish_reason: stop
Version 4.5.1 extracted no message from that response. The parameter is only intended to make the typed response shape reproducible; it is not proposed as a production setting or workaround.
Deterministic parser-level reproduction
Pass a successful synthetic chat-completion response shaped like this through the 4.5.1 response normalizer:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": [
{
"type": "text",
"text": "Synthetic reasoning"
}
]
},
{
"type": "text",
"text": "Synthetic final answer"
}
]
},
"finish_reason": "stop"
}
]
}
Expected result:
Actual 4.5.1 result:
Which version of integration_openai are you using?
4.5.1
Which version of Nextcloud are you using?
33.0.6.2, using the
nextcloud:33.0.6-apacheDocker image.Which browser are you using? In case you are using the phone App, specify the Android or iOS version and device please.
Not browser-specific. Observed through the Nextcloud Assistant web interface, but the failure occurs in server-side response normalization.
Describe the Bug
When an OpenAI-compatible endpoint returns assistant
message.contentas a list of typed content chunks, integration_openai 4.5.1 ignores the content and can eventually report:I encountered this repeatedly with Mistral Small 4 (
mistral-small-2603) throughhttps://api.eu.mistral.ai/v1. The failures occurred incore:text2text:chatwithtoolsafter an external MCP web-search tool had completed successfully. Both single and parallel tool-use cases were observed, so parallelism was not the cause.The relevant parser in
OpenAiAPIService::createChatCompletion()only accepts content when it is a string:Mistral's assistant-message schema permits content to be either a string or a list of typed chunks. Observed chunk types included:
textreferencethinkingOfficial schema references:
Confirmed response metadata
I reconstructed one previously failed synthetic request and replayed it three times with the same model, messages, tools,
n: 1,max_tokens: 5000, no extra parameters, and no explicitreasoning_effort.All three requests returned HTTP 200, one choice,
finish_reason: stop, no tool call, and no audio:message.contenttextandreference, ending intexttextandreference, ending intextThis shows why the problem is intermittent: the same request can return either a string or typed content.
A separate public synthetic request using
reasoning_effort: "high"returned athinkingchunk followed by atextchunk. Version 4.5.1 rejected that response as well. This was only a deterministic response-shape probe; I am not proposingreasoning_effortas a production setting or workaround.No prompts, tool-result bodies, response text, credentials, user identifiers, or raw provider responses have been retained.
Tested local workaround for 4.5.1
I locally patched version 4.5.1 to:
type: "text"chunks in order;thinkingchunks in the 4.5.1 provider;reference, unknown, null, empty, and malformed chunks safely; andThis fixed the observed failure. After deployment:
The patch below is the exact tested 4.5.1 workaround. It is not necessarily the preferred implementation for current
main, because currentmainalso has a streaming parser and structured reasoning support.Tested integration_openai 4.5.1 workaround diff
Local regression fixtures covered:
thinkingplustextchunks;textandreferencechunks;The patched file passed PHP 8.3 lint.
Current upstream
mainalso has streaming response parsing. A complete upstream fix may therefore need the same typed-content support in both non-streaming normalization and streaming SSE parsing.Investigation assistance
I encountered this bug multiple times. OpenAI
gpt-5.6-solwas used to help compare the synthetic occurrences, construct a privacy-safe replay, inspect the relevant schemas, and prepare the local workaround and tests.I supplied the provider credential locally through
getpass, approved and performed the production deployment, and manually validated the resulting Assistant and MCP behavior.Expected Behavior
OpenAI-compatible assistant responses should produce a final text result when
message.contentis either:type: "text"chunks.For typed content, the text chunks should be concatenated in order. Thinking, reference, unknown, or malformed chunks should not cause otherwise valid final text to be discarded.
Existing behavior for string content, separate
reasoning_content, tool calls, audio, and ordinary OpenAI-compatible providers should remain unchanged.To Reproduce
Intermittent provider reproduction
Configure integration_openai 4.5.1 with the Mistral EU OpenAI-compatible endpoint and
mistral-small-2603.Submit a sufficiently involved chat request, optionally with tools.
Allow the provider to complete the request successfully.
Repeat if necessary because Mistral may return either string or typed content for the same request.
When
message.contentis an array of typed chunks, observe that integration_openai returns no message and the task can fail with:Provider-level typed-content reproduction
A near-deterministic way to request the relevant response shape is to send a separate diagnostic request with
reasoning_effort: "high":{ "model": "mistral-small-2603", "messages": [ { "role": "user", "content": "Tell me more about Amsterdam:\n1. What is its history?\n2. What are its demographics?\n3. Is it worth visiting for sightseeing?" } ], "n": 1, "max_tokens": 5000, "reasoning_effort": "high" }In my test, the equivalent synthetic request returned HTTP 200 with:
Version 4.5.1 extracted no message from that response. The parameter is only intended to make the typed response shape reproducible; it is not proposed as a production setting or workaround.
Deterministic parser-level reproduction
Pass a successful synthetic chat-completion response shaped like this through the 4.5.1 response normalizer:
{ "choices": [ { "index": 0, "message": { "role": "assistant", "content": [ { "type": "thinking", "thinking": [ { "type": "text", "text": "Synthetic reasoning" } ] }, { "type": "text", "text": "Synthetic final answer" } ] }, "finish_reason": "stop" } ] }Expected result:
Actual 4.5.1 result: