Skip to content

Commit ddb739d

Browse files
authored
fix: Wrong reasoning order with OpenRouter-provider with AI SDK 5 (#141)
* 更新项目配置,修改包名为 @gylove1994/openrouter-ai-sdk-provider,添加 .turbo/ 到 .gitignore,优化 VSCode 设置,增加测试用例以确保推理顺序正确。 * 更新 package.json 中的包名为 @openrouter/ai-sdk-provider,并在 VSCode 设置中添加自定义颜色配置。
1 parent ccc5675 commit ddb739d

File tree

3 files changed

+105
-6
lines changed

3 files changed

+105
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ dist
33
./internal
44

55
.env.e2e*
6+
7+
.turbo/

src/chat/index.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,92 @@ describe('doStream', () => {
812812
expect(reasoningDeltas).not.toContain('Also ignored');
813813
});
814814

815+
it('should maintain correct reasoning order when content comes after reasoning (issue #7824)', async () => {
816+
// This test reproduces the issue where reasoning appears first but then gets "pushed down"
817+
// by content that comes later in the stream
818+
server.urls['https://openrouter.ai/api/v1/chat/completions']!.response = {
819+
type: 'stream-chunks',
820+
chunks: [
821+
// First chunk: Start with reasoning
822+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
823+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant",` +
824+
`"reasoning":"I need to think about this step by step..."},` +
825+
`"logprobs":null,"finish_reason":null}]}\n\n`,
826+
// Second chunk: More reasoning
827+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
828+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{` +
829+
`"reasoning":" First, I should analyze the request."},` +
830+
`"logprobs":null,"finish_reason":null}]}\n\n`,
831+
// Third chunk: Even more reasoning
832+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
833+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{` +
834+
`"reasoning":" Then I should provide a helpful response."},` +
835+
`"logprobs":null,"finish_reason":null}]}\n\n`,
836+
// Fourth chunk: Content starts
837+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
838+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"content":"Hello! "},` +
839+
`"logprobs":null,"finish_reason":null}]}\n\n`,
840+
// Fifth chunk: More content
841+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
842+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"content":"How can I help you today?"},` +
843+
`"logprobs":null,"finish_reason":null}]}\n\n`,
844+
// Finish chunk
845+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
846+
`"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{},` +
847+
`"logprobs":null,"finish_reason":"stop"}]}\n\n`,
848+
`data: {"id":"chatcmpl-order-test","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
849+
`"system_fingerprint":"fp_3bc1b5746c","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":30,"total_tokens":47}}\n\n`,
850+
'data: [DONE]\n\n',
851+
],
852+
};
853+
854+
const { stream } = await model.doStream({
855+
prompt: TEST_PROMPT,
856+
});
857+
858+
const elements = await convertReadableStreamToArray(stream);
859+
860+
// The expected order should be:
861+
// 1. reasoning-start
862+
// 2. reasoning-delta (3 times)
863+
// 3. reasoning-end (when text starts)
864+
// 4. text-start
865+
// 5. text-delta (2 times)
866+
// 6. text-end (when stream finishes)
867+
868+
const streamOrder = elements.map(el => el.type);
869+
870+
// Find the positions of key events
871+
const reasoningStartIndex = streamOrder.indexOf('reasoning-start');
872+
const reasoningEndIndex = streamOrder.indexOf('reasoning-end');
873+
const textStartIndex = streamOrder.indexOf('text-start');
874+
875+
// Reasoning should come before text and end before text starts
876+
expect(reasoningStartIndex).toBeLessThan(textStartIndex);
877+
expect(reasoningEndIndex).toBeLessThan(textStartIndex);
878+
879+
// Verify reasoning content
880+
const reasoningDeltas = elements
881+
.filter((el) => el.type === 'reasoning-delta')
882+
.map((el) => (el as { type: 'reasoning-delta'; delta: string }).delta);
883+
884+
expect(reasoningDeltas).toEqual([
885+
'I need to think about this step by step...',
886+
' First, I should analyze the request.',
887+
' Then I should provide a helpful response.',
888+
]);
889+
890+
// Verify text content
891+
const textDeltas = elements
892+
.filter((el) => el.type === 'text-delta')
893+
.map((el) => (el as { type: 'text-delta'; delta: string }).delta);
894+
895+
expect(textDeltas).toEqual([
896+
'Hello! ',
897+
'How can I help you today?',
898+
]);
899+
});
900+
815901
it('should stream tool deltas', async () => {
816902
server.urls['https://openrouter.ai/api/v1/chat/completions']!.response = {
817903
type: 'stream-chunks',

src/chat/index.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,16 @@ export class OpenRouterChatLanguageModel implements LanguageModelV2 {
565565
}
566566

567567
if (delta.content) {
568+
// If reasoning was previously active and now we're starting text content,
569+
// we should end the reasoning first to maintain proper order
570+
if (reasoningStarted && !textStarted) {
571+
controller.enqueue({
572+
type: 'reasoning-end',
573+
id: reasoningId || generateId(),
574+
});
575+
reasoningStarted = false; // Mark as ended so we don't end it again in flush
576+
}
577+
568578
if (!textStarted) {
569579
textId = openrouterResponseId || generateId();
570580
controller.enqueue({
@@ -730,18 +740,19 @@ export class OpenRouterChatLanguageModel implements LanguageModelV2 {
730740
}
731741
}
732742

733-
if (textStarted) {
734-
controller.enqueue({
735-
type: 'text-end',
736-
id: textId || generateId(),
737-
});
738-
}
743+
// End reasoning first if it was started, to maintain proper order
739744
if (reasoningStarted) {
740745
controller.enqueue({
741746
type: 'reasoning-end',
742747
id: reasoningId || generateId(),
743748
});
744749
}
750+
if (textStarted) {
751+
controller.enqueue({
752+
type: 'text-end',
753+
id: textId || generateId(),
754+
});
755+
}
745756

746757
controller.enqueue({
747758
type: 'finish',

0 commit comments

Comments
 (0)