diff --git a/src/config.ts b/src/config.ts index 5d31dbb..f7d6b36 100644 --- a/src/config.ts +++ b/src/config.ts @@ -71,7 +71,7 @@ export const getGeminiApiKey = () => loadConfig().MODELS.GEMINI.API_KEY; export const getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY; export const getDeepseekStreamDelay = () => - loadConfig().MODELS.DEEPSEEK.STREAM_DELAY || 20; // Default to 20ms if not specified + loadConfig().MODELS.DEEPSEEK.STREAM_DELAY || 5; // Default to 5ms if not specified export const getSearxngApiEndpoint = () => process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG; diff --git a/src/lib/deepseekChat.ts b/src/lib/deepseekChat.ts deleted file mode 100644 index 83ca6a2..0000000 --- a/src/lib/deepseekChat.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { BaseChatModel, BaseChatModelCallOptions } from '@langchain/core/language_models/chat_models'; -import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; -import { AIMessage, AIMessageChunk, BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages'; -import { ChatResult, ChatGenerationChunk } from '@langchain/core/outputs'; -import axios from 'axios'; - -import { BaseChatModelParams } from '@langchain/core/language_models/chat_models'; - -interface DeepSeekChatParams extends BaseChatModelParams { - apiKey: string; - baseURL: string; - modelName: string; - temperature?: number; - max_tokens?: number; - top_p?: number; - frequency_penalty?: number; - presence_penalty?: number; -} - -export class DeepSeekChat extends BaseChatModel { - private apiKey: string; - private baseURL: string; - private modelName: string; - private temperature: number; - private maxTokens: number; - private topP: number; - private frequencyPenalty: number; - private presencePenalty: number; - - constructor(params: DeepSeekChatParams) { - super(params); - this.apiKey = params.apiKey; - this.baseURL = params.baseURL; - this.modelName = params.modelName; - this.temperature = params.temperature ?? 0.7; - this.maxTokens = params.max_tokens ?? 8192; - this.topP = params.top_p ?? 1; - this.frequencyPenalty = params.frequency_penalty ?? 0; - this.presencePenalty = params.presence_penalty ?? 0; - } - - async _generate( - messages: BaseMessage[], - options: this['ParsedCallOptions'], - runManager?: CallbackManagerForLLMRun - ): Promise { - const formattedMessages = messages.map(msg => ({ - role: this.getRole(msg), - content: msg.content.toString(), - })); - const response = await this.callDeepSeekAPI(formattedMessages, options.stream); - - if (options.stream) { - return this.processStreamingResponse(response, messages, options, runManager); - } else { - const choice = response.data.choices[0]; - let content = choice.message.content || ''; - if (choice.message.reasoning_content) { - content = `\n${choice.message.reasoning_content}\n\n\n${content}`; - } - - // Report usage stats if available - if (response.data.usage && runManager) { - runManager.handleLLMEnd({ - generations: [], - llmOutput: { - tokenUsage: { - completionTokens: response.data.usage.completion_tokens, - promptTokens: response.data.usage.prompt_tokens, - totalTokens: response.data.usage.total_tokens - } - } - }); - } - return { - generations: [ - { - text: content, - message: new AIMessage(content), - }, - ], - }; - } - } - - private getRole(msg: BaseMessage): string { - if (msg instanceof SystemMessage) return 'system'; - if (msg instanceof HumanMessage) return 'user'; - if (msg instanceof AIMessage) return 'assistant'; - return 'user'; // Default to user - } - - private async callDeepSeekAPI(messages: Array<{ role: string; content: string }>, streaming?: boolean) { - return axios.post( - `${this.baseURL}/chat/completions`, - { - messages, - model: this.modelName, - stream: streaming, - temperature: this.temperature, - max_tokens: this.maxTokens, - top_p: this.topP, - frequency_penalty: this.frequencyPenalty, - presence_penalty: this.presencePenalty, - response_format: { type: 'text' }, - ...(streaming && { - stream_options: { - include_usage: true - } - }) - }, - { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.apiKey}`, - }, - responseType: streaming ? 'text' : 'json', - } - ); - } - - public async *_streamResponseChunks(messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun) { - const response = await this.callDeepSeekAPI(messages.map(msg => ({ - role: this.getRole(msg), - content: msg.content.toString(), - })), true); - - let thinkState = -1; // -1: not started, 0: thinking, 1: answered - let currentContent = ''; - - // Split the response into lines - const lines = response.data.split('\n'); - for (const line of lines) { - if (!line.startsWith('data: ')) continue; - const jsonStr = line.slice(6); - if (jsonStr === '[DONE]') break; - - try { - console.log('Received chunk:', jsonStr); - const chunk = JSON.parse(jsonStr); - const delta = chunk.choices[0].delta; - console.log('Parsed delta:', delta); - - // Handle usage stats in final chunk - if (chunk.usage && !chunk.choices?.length) { - runManager?.handleLLMEnd?.({ - generations: [], - llmOutput: { - tokenUsage: { - completionTokens: chunk.usage.completion_tokens, - promptTokens: chunk.usage.prompt_tokens, - totalTokens: chunk.usage.total_tokens - } - } - }); - continue; - } - - // Handle reasoning content - if (delta.reasoning_content) { - if (thinkState === -1) { - thinkState = 0; - const startTag = '\n'; - currentContent += startTag; - console.log('Emitting think start:', startTag); - runManager?.handleLLMNewToken(startTag); - const chunk = new ChatGenerationChunk({ - text: startTag, - message: new AIMessageChunk(startTag), - generationInfo: {} - }); - yield chunk; - } - currentContent += delta.reasoning_content; - console.log('Emitting reasoning:', delta.reasoning_content); - runManager?.handleLLMNewToken(delta.reasoning_content); - const chunk = new ChatGenerationChunk({ - text: delta.reasoning_content, - message: new AIMessageChunk(delta.reasoning_content), - generationInfo: {} - }); - yield chunk; - } - - // Handle regular content - if (delta.content) { - if (thinkState === 0) { - thinkState = 1; - const endTag = '\n\n\n'; - currentContent += endTag; - console.log('Emitting think end:', endTag); - runManager?.handleLLMNewToken(endTag); - const chunk = new ChatGenerationChunk({ - text: endTag, - message: new AIMessageChunk(endTag), - generationInfo: {} - }); - yield chunk; - } - currentContent += delta.content; - console.log('Emitting content:', delta.content); - runManager?.handleLLMNewToken(delta.content); - const chunk = new ChatGenerationChunk({ - text: delta.content, - message: new AIMessageChunk(delta.content), - generationInfo: {} - }); - yield chunk; - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Failed to parse chunk'; - console.error(`Streaming error: ${errorMessage}`); - if (error instanceof Error && error.message.includes('DeepSeek API Error')) { - throw error; - } - } - } - - // Handle any unclosed think block - if (thinkState === 0) { - const endTag = '\n\n\n'; - currentContent += endTag; - runManager?.handleLLMNewToken(endTag); - const chunk = new ChatGenerationChunk({ - text: endTag, - message: new AIMessageChunk(endTag), - generationInfo: {} - }); - yield chunk; - } - } - - private async processStreamingResponse(response: any, messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun): Promise { - let accumulatedContent = ''; - for await (const chunk of this._streamResponseChunks(messages, options, runManager)) { - accumulatedContent += chunk.message.content; - } - return { - generations: [ - { - text: accumulatedContent, - message: new AIMessage(accumulatedContent), - }, - ], - }; - } - - _llmType(): string { - return 'deepseek'; - } -} diff --git a/src/lib/providers/deepseek.ts b/src/lib/providers/deepseek.ts index e03503c..1ce0d1c 100644 --- a/src/lib/providers/deepseek.ts +++ b/src/lib/providers/deepseek.ts @@ -20,7 +20,6 @@ interface ChatModelConfig { model: ReasoningChatModel | ChatOpenAI; } -// Define which models require reasoning capabilities const REASONING_MODELS = ['deepseek-reasoner']; const MODEL_DISPLAY_NAMES: Record = { @@ -35,7 +34,6 @@ export const loadDeepSeekChatModels = async (): Promise>((acc, model) => { - // Only include models we have display names for if (model.id in MODEL_DISPLAY_NAMES) { // Use ReasoningChatModel for models that need reasoning capabilities if (REASONING_MODELS.includes(model.id)) { const streamDelay = getDeepseekStreamDelay(); - logger.debug(`Using stream delay of ${streamDelay}ms for ${model.id}`); acc[model.id] = { displayName: MODEL_DISPLAY_NAMES[model.id], diff --git a/src/lib/reasoningChatModel.ts b/src/lib/reasoningChatModel.ts index 903650d..95444f7 100644 --- a/src/lib/reasoningChatModel.ts +++ b/src/lib/reasoningChatModel.ts @@ -139,10 +139,8 @@ export class ReasoningChatModel extends BaseChatModel { const validators: Record = { 'deepseek-reasoner': { diff --git a/src/utils/messageProcessor.ts b/src/utils/messageProcessor.ts deleted file mode 100644 index b4a975a..0000000 --- a/src/utils/messageProcessor.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Using the import paths that have been working for you -import { BaseMessage, HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages"; -import logger from "./logger"; - -export interface MessageValidationRules { - requireAlternating?: boolean; - firstMessageType?: typeof HumanMessage | typeof AIMessage; - allowSystem?: boolean; -} - -export class MessageProcessor { - private rules: MessageValidationRules; - private modelName: string; - - constructor(modelName: string, rules: MessageValidationRules) { - this.rules = rules; - this.modelName = modelName; - } - - processMessages(messages: BaseMessage[]): BaseMessage[] { - // Always respect requireAlternating for models that need it - if (!this.rules.requireAlternating) { - return messages; - } - - const processedMessages: BaseMessage[] = []; - - for (let i = 0; i < messages.length; i++) { - const currentMsg = messages[i]; - - // Handle system messages - if (currentMsg instanceof SystemMessage) { - if (this.rules.allowSystem) { - processedMessages.push(currentMsg); - } else { - logger.warn(`${this.modelName}: Skipping system message - not allowed`); - } - continue; - } - - // Handle first non-system message - if (processedMessages.length === 0 || - processedMessages[processedMessages.length - 1] instanceof SystemMessage) { - if (this.rules.firstMessageType && - !(currentMsg instanceof this.rules.firstMessageType)) { - logger.warn(`${this.modelName}: Converting first message to required type`); - processedMessages.push(new this.rules.firstMessageType({ - content: currentMsg.content, - additional_kwargs: currentMsg.additional_kwargs - })); - continue; - } - } - - // Handle alternating pattern - const lastMsg = processedMessages[processedMessages.length - 1]; - if (lastMsg instanceof HumanMessage && currentMsg instanceof HumanMessage) { - logger.warn(`${this.modelName}: Skipping consecutive human message`); - continue; - } - if (lastMsg instanceof AIMessage && currentMsg instanceof AIMessage) { - logger.warn(`${this.modelName}: Skipping consecutive AI message`); - continue; - } - - // For deepseek-reasoner, strip out reasoning_content from message history - if (this.modelName === 'deepseek-reasoner' && currentMsg instanceof AIMessage) { - const { reasoning_content, ...cleanedKwargs } = currentMsg.additional_kwargs; - processedMessages.push(new AIMessage({ - content: currentMsg.content, - additional_kwargs: cleanedKwargs - })); - } else { - processedMessages.push(currentMsg); - } - } - - return processedMessages; - } -} - -// Pre-configured processors for specific models -export const getMessageProcessor = (modelName: string): MessageProcessor | null => { - const processors: Record = { - 'deepseek-reasoner': { - requireAlternating: true, - firstMessageType: HumanMessage, - allowSystem: true - }, - // Add more model configurations as needed - }; - - const rules = processors[modelName]; - return rules ? new MessageProcessor(modelName, rules) : null; -}; diff --git a/ui/app/discover/page.tsx b/ui/app/discover/page.tsx index 20d354f..e06c4a7 100644 --- a/ui/app/discover/page.tsx +++ b/ui/app/discover/page.tsx @@ -12,14 +12,12 @@ interface Discover { thumbnail: string; } -// List of available categories const categories = [ 'For You', 'AI', 'Technology', 'Current News', 'Sports', 'Money', 'Gaming', 'Entertainment', 'Art and Culture', 'Science', 'Health', 'Travel' ]; -// Memoized header component that won't re-render when content changes const DiscoverHeader = memo(({ activeCategory, setActiveCategory, @@ -31,7 +29,6 @@ const DiscoverHeader = memo(({ }) => { const categoryContainerRef = useRef(null); - // Function to scroll categories horizontally const scrollCategories = (direction: 'left' | 'right') => { const container = categoryContainerRef.current; if (!container) return; @@ -63,7 +60,6 @@ const DiscoverHeader = memo(({ - {/* Category Navigation with Buttons */}