@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
104 lines (89 loc) • 2.89 kB
text/typescript
import { ChatCompletionContentPartText } from 'ai/prompts';
import OpenAI from 'openai';
import { ChatCompletionContentPart } from 'openai/resources/index.mjs';
import type { Stream } from 'openai/streaming';
import { ChatStreamCallbacks } from '../../types';
import {
StreamProtocolChunk,
StreamProtocolToolCallChunk,
StreamToolCallChunkData,
convertIterableToStream,
createCallbacksTransformer,
createSSEProtocolTransformer,
generateToolCallId,
} from './protocol';
export const transformQwenStream = (chunk: OpenAI.ChatCompletionChunk): StreamProtocolChunk => {
const item = chunk.choices[0];
if (!item) {
return { data: chunk, id: chunk.id, type: 'data' };
}
if (Array.isArray(item.delta?.content)) {
const part = item.delta.content[0];
const process = (part: ChatCompletionContentPart): ChatCompletionContentPartText => {
let [key, value] = Object.entries(part)[0];
if (key === 'image') {
return {
text: ``,
type: 'text',
};
}
return {
text: value,
type: 'text',
};
};
const data = process(part);
return {
data: data.text,
id: chunk.id,
type: 'text',
};
}
if (item.delta?.tool_calls) {
return {
data: item.delta.tool_calls.map(
(value, index): StreamToolCallChunkData => ({
function: value.function,
id: value.id || generateToolCallId(index, value.function?.name),
index: typeof value.index !== 'undefined' ? value.index : index,
type: value.type || 'function',
}),
),
id: chunk.id,
type: 'tool_calls',
} as StreamProtocolToolCallChunk;
}
// DeepSeek reasoner will put thinking in the reasoning_content field
if (
item.delta &&
'reasoning_content' in item.delta &&
typeof item.delta.reasoning_content === 'string' &&
item.delta.reasoning_content !== ''
) {
return { data: item.delta.reasoning_content, id: chunk.id, type: 'reasoning' };
}
if (typeof item.delta?.content === 'string') {
return { data: item.delta.content, id: chunk.id, type: 'text' };
}
if (item.finish_reason) {
return { data: item.finish_reason, id: chunk.id, type: 'stop' };
}
if (item.delta?.content === null) {
return { data: item.delta, id: chunk.id, type: 'data' };
}
return {
data: { delta: item.delta, id: chunk.id, index: item.index },
id: chunk.id,
type: 'data',
};
};
export const QwenAIStream = (
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
callbacks?: ChatStreamCallbacks,
) => {
const readableStream =
stream instanceof ReadableStream ? stream : convertIterableToStream(stream);
return readableStream
.pipeThrough(createSSEProtocolTransformer(transformQwenStream))
.pipeThrough(createCallbacksTransformer(callbacks));
};