Skip to content

Commit 6d835a7

Browse files
authored
fix (provider/grok): filter duplicated reasoning chunks (#7330)
## background XAI's Grok reasoning API was sending repetitive "Thinking... " placeholder text in each reasoning delta, causing reasoning output to show "Thinking... Thinking... Thinking..." instead of meaningful reasoning content. ## summary - add reasoning delta deduplication to XAI provider ## tasks - [x] reasoning deduplication logic in XAI streaming transform - [x] tested reasoning via examples + e2e ## future work * monitor XAI API for native reasoning deduplication support related issue #7311
1 parent 4a1e0c8 commit 6d835a7

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

‎.changeset/nasty-queens-play.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/xai': patch
3+
---
4+
5+
fix (provider/grok): filter duplicated reasoning chunks

‎packages/xai/src/xai-chat-language-model.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,96 @@ describe('XaiChatLanguageModel', () => {
12851285
]
12861286
`);
12871287
});
1288+
1289+
it('should deduplicate repetitive reasoning deltas', async () => {
1290+
server.urls['https://api.x.ai/v1/chat/completions'].response = {
1291+
type: 'stream-chunks',
1292+
chunks: [
1293+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1294+
`"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1295+
// Multiple identical "Thinking..." deltas (simulating Grok 4 issue)
1296+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1297+
`"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1298+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1299+
`"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1300+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1301+
`"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1302+
// Different reasoning content should still come through
1303+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1304+
`"choices":[{"index":0,"delta":{"reasoning_content":"Actually calculating now..."},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1305+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1306+
`"choices":[{"index":0,"delta":{"content":"The answer is 42."},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1307+
`data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1308+
`"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],` +
1309+
`"usage":{"prompt_tokens":15,"total_tokens":35,"completion_tokens":20,"completion_tokens_details":{"reasoning_tokens":10}},"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1310+
`data: [DONE]\n\n`,
1311+
],
1312+
};
1313+
1314+
const { stream } = await reasoningModel.doStream({
1315+
prompt: TEST_PROMPT,
1316+
includeRawChunks: false,
1317+
providerOptions: {
1318+
xai: { reasoningEffort: 'low' },
1319+
},
1320+
});
1321+
1322+
expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1323+
[
1324+
{
1325+
"type": "stream-start",
1326+
"warnings": [],
1327+
},
1328+
{
1329+
"id": "grok-4-test",
1330+
"modelId": "grok-4-0709",
1331+
"timestamp": 2025-06-21T20:35:20.000Z,
1332+
"type": "response-metadata",
1333+
},
1334+
{
1335+
"id": "reasoning-grok-4-test",
1336+
"type": "reasoning-start",
1337+
},
1338+
{
1339+
"delta": "Thinking... ",
1340+
"id": "reasoning-grok-4-test",
1341+
"type": "reasoning-delta",
1342+
},
1343+
{
1344+
"delta": "Actually calculating now...",
1345+
"id": "reasoning-grok-4-test",
1346+
"type": "reasoning-delta",
1347+
},
1348+
{
1349+
"id": "text-grok-4-test",
1350+
"type": "text-start",
1351+
},
1352+
{
1353+
"delta": "The answer is 42.",
1354+
"id": "text-grok-4-test",
1355+
"type": "text-delta",
1356+
},
1357+
{
1358+
"id": "reasoning-grok-4-test",
1359+
"type": "reasoning-end",
1360+
},
1361+
{
1362+
"id": "text-grok-4-test",
1363+
"type": "text-end",
1364+
},
1365+
{
1366+
"finishReason": "stop",
1367+
"type": "finish",
1368+
"usage": {
1369+
"inputTokens": 15,
1370+
"outputTokens": 20,
1371+
"reasoningTokens": 10,
1372+
"totalTokens": 35,
1373+
},
1374+
},
1375+
]
1376+
`);
1377+
});
12881378
});
12891379
});
12901380

‎packages/xai/src/xai-chat-language-model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ export class XaiChatLanguageModel implements LanguageModelV2 {
330330
};
331331
let isFirstChunk = true;
332332
const contentBlocks: Record<string, { type: 'text' | 'reasoning' }> = {};
333+
const lastReasoningDeltas: Record<string, string> = {};
333334

334335
const self = this;
335336

@@ -439,6 +440,12 @@ export class XaiChatLanguageModel implements LanguageModelV2 {
439440
) {
440441
const blockId = `reasoning-${value.id || choiceIndex}`;
441442

443+
// skip if this reasoning content duplicates the last delta
444+
if (lastReasoningDeltas[blockId] === delta.reasoning_content) {
445+
return;
446+
}
447+
lastReasoningDeltas[blockId] = delta.reasoning_content;
448+
442449
if (contentBlocks[blockId] == null) {
443450
contentBlocks[blockId] = { type: 'reasoning' };
444451
controller.enqueue({

0 commit comments

Comments
 (0)