mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-11 22:50:06 +00:00
fix(core): Support streamed reasoning from custom AI endpoints (no-changelog) (#37875)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { streamText } from 'ai';
|
||||
|
||||
import { createModel } from '../model/model-factory';
|
||||
|
||||
describe('createModel streaming', () => {
|
||||
it('streams array-based reasoning and text from a custom endpoint', async () => {
|
||||
const deltas = [
|
||||
...['Let me ', 'think.'].map((text) => ({
|
||||
content: [{ type: 'thinking', thinking: [{ type: 'text', text }], closed: true }],
|
||||
tool_calls: [],
|
||||
})),
|
||||
{ content: [{ type: 'text', text: 'The answer ' }] },
|
||||
{ content: 'is 391.' },
|
||||
];
|
||||
const responseChunks = [
|
||||
...deltas.map((delta) => ({ choices: [{ index: 0, delta, finish_reason: null }] })),
|
||||
{ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] },
|
||||
{
|
||||
choices: [],
|
||||
usage: { prompt_tokens: 120, completion_tokens: 30, total_tokens: 150 },
|
||||
},
|
||||
];
|
||||
const body = responseChunks
|
||||
.map((chunk) => {
|
||||
const payload = {
|
||||
id: 'completion-1',
|
||||
object: 'chat.completion.chunk',
|
||||
created: 1,
|
||||
model: 'zai-glm-5-2',
|
||||
...chunk,
|
||||
};
|
||||
return `data: ${JSON.stringify(payload)}\n\n`;
|
||||
})
|
||||
.join('');
|
||||
const fetch = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
|
||||
new Response(`${body}data: [DONE]\n\n`, {
|
||||
headers: { 'content-type': 'text/event-stream' },
|
||||
}),
|
||||
);
|
||||
const model = createModel(
|
||||
{
|
||||
id: 'custom/zai-glm-5-2',
|
||||
url: 'https://model.example.com/v1',
|
||||
apiKey: 'test-key',
|
||||
},
|
||||
fetch,
|
||||
);
|
||||
const result = streamText({ model, prompt: 'Hello', maxRetries: 0 });
|
||||
const chunks = [];
|
||||
for await (const chunk of result.fullStream) chunks.push(chunk);
|
||||
|
||||
expect(fetch).toHaveBeenCalledOnce();
|
||||
expect(fetch.mock.calls[0][0]).toBe('https://model.example.com/v1/chat/completions');
|
||||
const requestBody = fetch.mock.calls[0][1]?.body;
|
||||
expect(requestBody).not.toContain('"stream_options"');
|
||||
expect(chunks.filter((chunk) => chunk.type === 'error')).toEqual([]);
|
||||
expect(chunks.filter((chunk) => chunk.type.startsWith('reasoning-'))).toEqual([
|
||||
{ type: 'reasoning-start', id: 'reasoning-0' },
|
||||
{ type: 'reasoning-delta', id: 'reasoning-0', text: 'Let me ' },
|
||||
{ type: 'reasoning-delta', id: 'reasoning-0', text: 'think.' },
|
||||
{ type: 'reasoning-end', id: 'reasoning-0' },
|
||||
]);
|
||||
await expect(result.text).resolves.toBe('The answer is 391.');
|
||||
await expect(result.totalUsage).resolves.toMatchObject({
|
||||
inputTokens: 120,
|
||||
outputTokens: 30,
|
||||
totalTokens: 150,
|
||||
});
|
||||
});
|
||||
});
|
||||
Generated
+39
-4
@@ -207,8 +207,8 @@ catalogs:
|
||||
specifier: ^4.0.20
|
||||
version: 4.0.20
|
||||
'@ai-sdk/openai-compatible':
|
||||
specifier: ^3.0.14
|
||||
version: 3.0.28
|
||||
specifier: ^3.0.44
|
||||
version: 3.0.44
|
||||
'@ai-sdk/otel':
|
||||
specifier: ^1.0.66
|
||||
version: 1.0.74
|
||||
@@ -1209,7 +1209,7 @@ importers:
|
||||
version: 4.0.20([email protected])
|
||||
'@ai-sdk/openai-compatible':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.28([email protected])
|
||||
version: 3.0.44([email protected])
|
||||
'@ai-sdk/otel':
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.74([email protected])
|
||||
@@ -4723,7 +4723,7 @@ importers:
|
||||
version: 4.0.39([email protected])
|
||||
'@ai-sdk/openai-compatible':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.28([email protected])
|
||||
version: 3.0.44([email protected])
|
||||
'@apidevtools/json-schema-ref-parser':
|
||||
specifier: 12.0.2
|
||||
version: 12.0.2
|
||||
@@ -8772,6 +8772,12 @@ packages:
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-pK7mERd/aIJVtyQCe/nV3G3iEJCY1no8GHDOGbgpNC+petjSfsxj+ni0Q8WVVehTUiY9B04F3Xvir+icBgORCg==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-/Hu+btLIO2zuYqCp//vBBAGdM/BeN4gds+NWrlv7Y9WrcTXwnEEwh6P3QZIWh2Tt1I1lswuDIpAJ4t/c/rws3Q==}
|
||||
engines: {node: '>=22'}
|
||||
@@ -8806,6 +8812,16 @@ packages:
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-MFXBn6XDyf37PNQAge/HTatPJE8Vmg/g/w4WPtjSV53jq8FKAzoaN5+43hsdQa9bqgN+/13jxug9ChvsG+godQ==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-fX2ENAc7iDpZ+Wp4+Rk06Usn/Ys7dI9uAkGv0jlF6XVrW13NkRWx5Ou+U6lIM2E1fTLkCs16GGrUAaVvroag7A==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-e0CpNWJUY7OxAFAnCZkw+ri9QOHWwTs1tXP42782KFGCU07qt8NiXCrCVowyCB5dP2r5/Uls+g2oPd8kOJn9dw==}
|
||||
engines: {node: '>=22'}
|
||||
@@ -25311,6 +25327,12 @@ snapshots:
|
||||
'@ai-sdk/provider-utils': 5.0.25([email protected])
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 4.0.10
|
||||
'@ai-sdk/provider-utils': 5.0.36([email protected])
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 4.0.3
|
||||
@@ -25360,6 +25382,19 @@ snapshots:
|
||||
undici: 7.29.1
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 4.0.10
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@workflow/serde': 4.1.0
|
||||
eventsource-parser: 3.0.8
|
||||
undici: 7.29.1
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/[email protected]':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ catalog:
|
||||
'@ai-sdk/mistral': ^4.0.14
|
||||
'@ai-sdk/moonshotai': ^3.0.35
|
||||
'@ai-sdk/openai': ^4.0.20
|
||||
'@ai-sdk/openai-compatible': ^3.0.14
|
||||
'@ai-sdk/openai-compatible': ^3.0.44
|
||||
'@ai-sdk/otel': ^1.0.66
|
||||
'@ai-sdk/provider-utils': ^5.0.27
|
||||
'@ai-sdk/xai': ^4.0.18
|
||||
|
||||
Reference in New Issue
Block a user