UNPKG

mnemos-coder

Version:

CLI-based coding agent with graph-based execution loop and terminal UI

217 lines 9.78 kB
/** * LLM Client for communicating with Qwen models * Based on seahorse llm_client.py */ import fetch from 'node-fetch'; import { getLogger } from './utils/Logger.js'; export class LLMClient { config; logger; constructor(config) { this.config = config; this.logger = getLogger('LLMClient'); } async chat(messages, options = {}) { try { // Log the full request being sent to LLM const requestBody = { model: this.config.model, messages, temperature: options.temperature ?? this.config.temperature, max_tokens: options.max_tokens ?? this.config.max_tokens, stream: false }; this.logger.debug('Sending request to LLM', { url: `${this.config.base_url}/chat/completions`, model: requestBody.model, temperature: requestBody.temperature, max_tokens: requestBody.max_tokens, messageCount: messages.length }); // Log full messages array with system prompts this.logger.trace('Full LLM request messages:', { messages: messages.map(m => ({ role: m.role, contentLength: m.content.length, contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : '') })) }); // Log complete system prompt if present const systemMessage = messages.find(m => m.role === 'system'); if (systemMessage) { this.logger.trace('System prompt sent to LLM:', { systemPrompt: systemMessage.content }); } const response = await fetch(`${this.config.base_url}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.api_key}` }, body: JSON.stringify(requestBody) }); if (!response.ok) { const errorText = await response.text(); this.logger.error('LLM request failed', { status: response.status, statusText: response.statusText, errorBody: errorText }); throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); } const data = await response.json(); const responseContent = data.choices?.[0]?.message?.content || ''; // Log the full response from LLM this.logger.debug('Received response from LLM', { responseLength: responseContent.length, hasContent: !!responseContent, usage: data.usage }); // Log response content safely (it could be very large) if (responseContent.length > 5000) { this.logger.trace('Full LLM response content (truncated):', { contentLength: responseContent.length, contentPreview: responseContent.substring(0, 2000) + '...[truncated]' }); } else { this.logger.trace('Full LLM response content:', { content: responseContent }); } // Check if response contains tool calls const hasToolCalls = responseContent.includes('<tool_call>'); this.logger.debug('Response analysis', { hasToolCalls, toolCallCount: hasToolCalls ? (responseContent.match(/<tool_call>/g) || []).length : 0 }); return responseContent; } catch (error) { this.logger.error('LLM request failed with exception', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); console.error('LLM request failed:', error); throw error; } } async *streamChat(messages, options = {}) { try { // Log the full request being sent to LLM const requestBody = { model: this.config.model, messages, temperature: options.temperature ?? this.config.temperature, max_tokens: options.max_tokens ?? this.config.max_tokens, stream: true }; this.logger.debug('Sending streaming request to LLM', { url: `${this.config.base_url}/chat/completions`, model: requestBody.model, temperature: requestBody.temperature, max_tokens: requestBody.max_tokens, messageCount: messages.length }); // Log full messages array with system prompts this.logger.trace('Full LLM streaming request messages:', { messages: messages.map(m => ({ role: m.role, contentLength: m.content.length, contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : '') })) }); // Log complete system prompt if present const systemMessage = messages.find(m => m.role === 'system'); if (systemMessage) { this.logger.debug('System prompt sent to LLM (streaming):', { systemPrompt: systemMessage.content }); } const response = await fetch(`${this.config.base_url}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.api_key}` }, body: JSON.stringify(requestBody) }); if (!response.ok) { const errorText = await response.text(); this.logger.error('LLM streaming request failed', { status: response.status, statusText: response.statusText, errorBody: errorText }); throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); } if (!response.body) { throw new Error('No response body'); } // For Node.js environments, response.body is a Readable stream let buffer = ''; let fullResponse = ''; for await (const chunk of response.body) { buffer += chunk.toString(); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim() === '') continue; if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { // Log the complete streamed response this.logger.info('Streaming response completed', { totalLength: fullResponse.length, hasToolCalls: fullResponse.includes('<tool_call>'), toolCallCount: (fullResponse.match(/<tool_call>/g) || []).length }); // Log response content safely (it could be very large) if (fullResponse.length > 5000) { this.logger.debug('Full streamed response (truncated):', { contentLength: fullResponse.length, contentPreview: fullResponse.substring(0, 2000) + '...[truncated]' }); } else { this.logger.debug('Full streamed response:', { content: fullResponse }); } continue; } try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { fullResponse += content; yield { type: 'content', content }; } } catch (parseError) { this.logger.warn('Failed to parse SSE data', { error: parseError instanceof Error ? parseError.message : String(parseError), data: data }); console.warn('Failed to parse SSE data:', parseError); } } } } } catch (error) { this.logger.error('Stream chat failed with exception', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); console.error('Stream chat failed:', error); throw error; } } } //# sourceMappingURL=llm-client.js.map