feat(llm): resolve compilation and typecheck errors

This commit is contained in:
perfectra1n
2025-08-09 08:35:23 -07:00
parent a1e596b81b
commit 97ec882528
10 changed files with 153 additions and 114 deletions

View File

@@ -561,13 +561,9 @@ async function handleStreamingProcess(
const aiServiceManager = await import('../../services/llm/ai_service_manager.js'); const aiServiceManager = await import('../../services/llm/ai_service_manager.js');
await aiServiceManager.default.getOrCreateAnyService(); await aiServiceManager.default.getOrCreateAnyService();
// Use the chat pipeline directly for streaming // Use the simplified chat pipeline directly for streaming
const { ChatPipeline } = await import('../../services/llm/pipeline/chat_pipeline.js'); const simplifiedPipeline = await import('../../services/llm/pipeline/simplified_pipeline.js');
const pipeline = new ChatPipeline({ const pipeline = simplifiedPipeline.default;
enableStreaming: true,
enableMetrics: true,
maxToolCallIterations: 5
});
// Get selected model // Get selected model
const { getSelectedModelConfig } = await import('../../services/llm/config/configuration_helpers.js'); const { getSelectedModelConfig } = await import('../../services/llm/config/configuration_helpers.js');

View File

@@ -1,5 +1,8 @@
import type { ToolCall } from './tools/tool_interfaces.js';
import type { ModelMetadata } from './providers/provider_options.js'; import type { ModelMetadata } from './providers/provider_options.js';
import type { ToolCall } from './tools/tool_interfaces.js';
// Re-export ToolCall so it's available from this module
export type { ToolCall } from './tools/tool_interfaces.js';
/** /**
* Interface for chat messages between client and LLM models * Interface for chat messages between client and LLM models

View File

@@ -754,13 +754,22 @@ export class AIServiceManager implements IAIServiceManager, Disposable {
return 'openai'; return 'openai';
} }
/**
* Check if a service cache entry is stale
*/
private isServiceStale(entry: ServiceCacheEntry): boolean {
const now = Date.now();
return now - entry.lastUsed > this.SERVICE_TTL_MS;
}
/** /**
* Check if a specific provider is available * Check if a specific provider is available
*/ */
isProviderAvailable(provider: string): boolean { isProviderAvailable(provider: string): boolean {
// Check if this is the current provider and if it's available // Check if we have a cached service for this provider
if (this.currentProvider === provider && this.currentService) { const cachedEntry = this.serviceCache.get(provider as ServiceProviders);
return this.currentService.isAvailable(); if (cachedEntry && !this.isServiceStale(cachedEntry)) {
return cachedEntry.service.isAvailable();
} }
// For other providers, check configuration // For other providers, check configuration
@@ -784,8 +793,9 @@ export class AIServiceManager implements IAIServiceManager, Disposable {
* Get metadata about a provider * Get metadata about a provider
*/ */
getProviderMetadata(provider: string): ProviderMetadata | null { getProviderMetadata(provider: string): ProviderMetadata | null {
// Only return metadata if this is the current active provider // Check if we have a cached service for this provider
if (this.currentProvider === provider && this.currentService) { const cachedEntry = this.serviceCache.get(provider as ServiceProviders);
if (cachedEntry && !this.isServiceStale(cachedEntry)) {
return { return {
name: provider, name: provider,
capabilities: { capabilities: {

View File

@@ -1,7 +1,4 @@
import type { Message } from "../ai_interface.js"; import type { Message } from "../ai_interface.js";
// These imports need to be added for the factory to work
import { OpenAIMessageFormatter } from "../formatters/openai_formatter.js";
import { OllamaMessageFormatter } from "../formatters/ollama_formatter.js";
/** /**
* Interface for provider-specific message formatters * Interface for provider-specific message formatters
@@ -34,6 +31,41 @@ export interface MessageFormatter {
getMaxContextLength(): number; getMaxContextLength(): number;
} }
/**
* Default message formatter implementation
*/
class DefaultMessageFormatter implements MessageFormatter {
formatMessages(messages: Message[], systemPrompt?: string, context?: string): Message[] {
const formattedMessages: Message[] = [];
// Add system prompt if provided
if (systemPrompt || context) {
const systemContent = [systemPrompt, context].filter(Boolean).join('\n\n');
if (systemContent) {
formattedMessages.push({
role: 'system',
content: systemContent
});
}
}
// Add the rest of the messages
formattedMessages.push(...messages);
return formattedMessages;
}
cleanContextContent(content: string): string {
// Basic cleanup: trim and remove excessive whitespace
return content.trim().replace(/\n{3,}/g, '\n\n');
}
getMaxContextLength(): number {
// Default to a reasonable context length
return 10000;
}
}
/** /**
* Factory to get the appropriate message formatter for a provider * Factory to get the appropriate message formatter for a provider
*/ */
@@ -69,23 +101,9 @@ export class MessageFormatterFactory {
return this.formatters[providerKey]; return this.formatters[providerKey];
} }
// Create and cache new formatter // For now, all providers use the default formatter
switch (providerKey) { // In the future, we can add provider-specific formatters here
case 'openai': this.formatters[providerKey] = new DefaultMessageFormatter();
this.formatters[providerKey] = new OpenAIMessageFormatter();
break;
case 'anthropic':
console.warn('Anthropic formatter not available, using OpenAI formatter as fallback');
this.formatters[providerKey] = new OpenAIMessageFormatter();
break;
case 'ollama':
this.formatters[providerKey] = new OllamaMessageFormatter();
break;
default:
// Default to OpenAI formatter for unknown providers
console.warn(`No specific formatter for provider: ${providerName}. Using OpenAI formatter as default.`);
this.formatters[providerKey] = new OpenAIMessageFormatter();
}
return this.formatters[providerKey]; return this.formatters[providerKey];
} }

View File

@@ -8,8 +8,8 @@
* - No scattered options.getOption() calls * - No scattered options.getOption() calls
*/ */
import options from '../../../options.js'; import options from '../../options.js';
import log from '../../../log.js'; import log from '../../log.js';
import type { ChatCompletionOptions } from '../ai_interface.js'; import type { ChatCompletionOptions } from '../ai_interface.js';
// Configuration interfaces // Configuration interfaces
@@ -108,9 +108,9 @@ export class ConfigurationService {
this.lastLoadTime = Date.now(); this.lastLoadTime = Date.now();
if (!this.validationResult.valid) { if (!this.validationResult.valid) {
log.error('Configuration validation failed', this.validationResult.errors); log.error(`Configuration validation failed: ${JSON.stringify(this.validationResult.errors)}`);
} else if (this.validationResult.warnings.length > 0) { } else if (this.validationResult.warnings.length > 0) {
log.warn('Configuration warnings', this.validationResult.warnings); log.info(`[WARN] Configuration warnings: ${JSON.stringify(this.validationResult.warnings)}`);
} else { } else {
log.info('Configuration loaded and validated successfully'); log.info('Configuration loaded and validated successfully');
} }
@@ -146,43 +146,43 @@ export class ConfigurationService {
// Default configuration // Default configuration
const defaults: DefaultConfiguration = { const defaults: DefaultConfiguration = {
systemPrompt: options.getOption('llmSystemPrompt') || 'You are a helpful AI assistant.', systemPrompt: (options as any).getOptionOrNull('llmSystemPrompt') || 'You are a helpful AI assistant.',
temperature: this.parseFloat(options.getOption('llmTemperature'), 0.7), temperature: this.parseFloat((options as any).getOptionOrNull('llmTemperature'), 0.7),
maxTokens: this.parseInt(options.getOption('llmMaxTokens'), 2000), maxTokens: this.parseInt((options as any).getOptionOrNull('llmMaxTokens'), 2000),
topP: this.parseFloat(options.getOption('llmTopP'), 0.9), topP: this.parseFloat((options as any).getOptionOrNull('llmTopP'), 0.9),
presencePenalty: this.parseFloat(options.getOption('llmPresencePenalty'), 0), presencePenalty: this.parseFloat((options as any).getOptionOrNull('llmPresencePenalty'), 0),
frequencyPenalty: this.parseFloat(options.getOption('llmFrequencyPenalty'), 0) frequencyPenalty: this.parseFloat((options as any).getOptionOrNull('llmFrequencyPenalty'), 0)
}; };
// Tool configuration // Tool configuration
const tools: ToolConfiguration = { const tools: ToolConfiguration = {
enabled: options.getOptionBool('llmToolsEnabled') !== false, enabled: (options as any).getOptionBool('llmToolsEnabled') !== false,
maxIterations: this.parseInt(options.getOption('llmMaxToolIterations'), 5), maxIterations: this.parseInt((options as any).getOptionOrNull('llmMaxToolIterations'), 5),
timeout: this.parseInt(options.getOption('llmToolTimeout'), 30000), timeout: this.parseInt((options as any).getOptionOrNull('llmToolTimeout'), 30000),
parallelExecution: options.getOptionBool('llmParallelTools') !== false parallelExecution: (options as any).getOptionBool('llmParallelTools') !== false
}; };
// Streaming configuration // Streaming configuration
const streaming: StreamingConfiguration = { const streaming: StreamingConfiguration = {
enabled: options.getOptionBool('llmStreamingEnabled') !== false, enabled: (options as any).getOptionBool('llmStreamingEnabled') !== false,
chunkSize: this.parseInt(options.getOption('llmStreamChunkSize'), 256), chunkSize: this.parseInt((options as any).getOptionOrNull('llmStreamChunkSize'), 256),
flushInterval: this.parseInt(options.getOption('llmStreamFlushInterval'), 100) flushInterval: this.parseInt((options as any).getOptionOrNull('llmStreamFlushInterval'), 100)
}; };
// Debug configuration // Debug configuration
const debug: DebugConfiguration = { const debug: DebugConfiguration = {
enabled: options.getOptionBool('llmDebugEnabled'), enabled: (options as any).getOptionBool('llmDebugEnabled'),
logLevel: this.getLogLevel(), logLevel: this.getLogLevel(),
enableMetrics: options.getOptionBool('llmMetricsEnabled'), enableMetrics: (options as any).getOptionBool('llmMetricsEnabled'),
enableTracing: options.getOptionBool('llmTracingEnabled') enableTracing: (options as any).getOptionBool('llmTracingEnabled')
}; };
// Limit configuration // Limit configuration
const limits: LimitConfiguration = { const limits: LimitConfiguration = {
maxMessageLength: this.parseInt(options.getOption('llmMaxMessageLength'), 100000), maxMessageLength: this.parseInt((options as any).getOptionOrNull('llmMaxMessageLength'), 100000),
maxConversationLength: this.parseInt(options.getOption('llmMaxConversationLength'), 50), maxConversationLength: this.parseInt((options as any).getOptionOrNull('llmMaxConversationLength'), 50),
maxContextLength: this.parseInt(options.getOption('llmMaxContextLength'), 10000), maxContextLength: this.parseInt((options as any).getOptionOrNull('llmMaxContextLength'), 10000),
rateLimitPerMinute: this.parseInt(options.getOption('llmRateLimitPerMinute'), 60) rateLimitPerMinute: this.parseInt((options as any).getOptionOrNull('llmRateLimitPerMinute'), 60)
}; };
return { return {
@@ -199,14 +199,14 @@ export class ConfigurationService {
* Load OpenAI configuration * Load OpenAI configuration
*/ */
private loadOpenAIConfig() { private loadOpenAIConfig() {
const apiKey = options.getOption('openaiApiKey'); const apiKey = options.getOption('openaiApiKey' as any);
if (!apiKey) return undefined; if (!apiKey) return undefined;
return { return {
apiKey, apiKey,
baseUrl: options.getOption('openaiBaseUrl') || undefined, baseUrl: options.getOption('openaiBaseUrl' as any) || undefined,
defaultModel: options.getOption('openaiDefaultModel') || 'gpt-4-turbo-preview', defaultModel: options.getOption('openaiDefaultModel' as any) || 'gpt-4-turbo-preview',
maxTokens: this.parseInt(options.getOption('openaiMaxTokens'), 4096) maxTokens: this.parseInt(options.getOption('openaiMaxTokens' as any), 4096)
}; };
} }
@@ -214,14 +214,14 @@ export class ConfigurationService {
* Load Anthropic configuration * Load Anthropic configuration
*/ */
private loadAnthropicConfig() { private loadAnthropicConfig() {
const apiKey = options.getOption('anthropicApiKey'); const apiKey = options.getOption('anthropicApiKey' as any);
if (!apiKey) return undefined; if (!apiKey) return undefined;
return { return {
apiKey, apiKey,
baseUrl: options.getOption('anthropicBaseUrl') || undefined, baseUrl: options.getOption('anthropicBaseUrl' as any) || undefined,
defaultModel: options.getOption('anthropicDefaultModel') || 'claude-3-opus-20240229', defaultModel: options.getOption('anthropicDefaultModel' as any) || 'claude-3-opus-20240229',
maxTokens: this.parseInt(options.getOption('anthropicMaxTokens'), 4096) maxTokens: this.parseInt(options.getOption('anthropicMaxTokens' as any), 4096)
}; };
} }
@@ -229,13 +229,13 @@ export class ConfigurationService {
* Load Ollama configuration * Load Ollama configuration
*/ */
private loadOllamaConfig() { private loadOllamaConfig() {
const baseUrl = options.getOption('ollamaBaseUrl'); const baseUrl = options.getOption('ollamaBaseUrl' as any);
if (!baseUrl) return undefined; if (!baseUrl) return undefined;
return { return {
baseUrl, baseUrl,
defaultModel: options.getOption('ollamaDefaultModel') || 'llama2', defaultModel: options.getOption('ollamaDefaultModel' as any) || 'llama2',
maxTokens: this.parseInt(options.getOption('ollamaMaxTokens'), 2048) maxTokens: this.parseInt(options.getOption('ollamaMaxTokens' as any), 2048)
}; };
} }
@@ -262,13 +262,13 @@ export class ConfigurationService {
errors.push(`Configuration missing for selected provider: ${config.providers.selected}`); errors.push(`Configuration missing for selected provider: ${config.providers.selected}`);
} else { } else {
// Provider-specific validation // Provider-specific validation
if (config.providers.selected === 'openai' && !selectedConfig.apiKey) { if (config.providers.selected === 'openai' && !('apiKey' in selectedConfig && selectedConfig.apiKey)) {
errors.push('OpenAI API key is required'); errors.push('OpenAI API key is required');
} }
if (config.providers.selected === 'anthropic' && !selectedConfig.apiKey) { if (config.providers.selected === 'anthropic' && !('apiKey' in selectedConfig && selectedConfig.apiKey)) {
errors.push('Anthropic API key is required'); errors.push('Anthropic API key is required');
} }
if (config.providers.selected === 'ollama' && !selectedConfig.baseUrl) { if (config.providers.selected === 'ollama' && !('baseUrl' in selectedConfig && selectedConfig.baseUrl)) {
errors.push('Ollama base URL is required'); errors.push('Ollama base URL is required');
} }
} }
@@ -304,7 +304,7 @@ export class ConfigurationService {
* Get selected provider * Get selected provider
*/ */
private getSelectedProvider(): 'openai' | 'anthropic' | 'ollama' | null { private getSelectedProvider(): 'openai' | 'anthropic' | 'ollama' | null {
const provider = options.getOption('aiSelectedProvider'); const provider = options.getOption('aiSelectedProvider' as any);
if (provider === 'openai' || provider === 'anthropic' || provider === 'ollama') { if (provider === 'openai' || provider === 'anthropic' || provider === 'ollama') {
return provider; return provider;
} }
@@ -315,7 +315,7 @@ export class ConfigurationService {
* Get log level * Get log level
*/ */
private getLogLevel(): 'error' | 'warn' | 'info' | 'debug' { private getLogLevel(): 'error' | 'warn' | 'info' | 'debug' {
const level = options.getOption('llmLogLevel') || 'info'; const level = options.getOption('llmLogLevel' as any) || 'info';
if (level === 'error' || level === 'warn' || level === 'info' || level === 'debug') { if (level === 'error' || level === 'warn' || level === 'info' || level === 'debug') {
return level; return level;
} }
@@ -347,7 +347,7 @@ export class ConfigurationService {
if (!this.config || Date.now() - this.lastLoadTime > this.CACHE_DURATION) { if (!this.config || Date.now() - this.lastLoadTime > this.CACHE_DURATION) {
// Reload configuration if cache expired // Reload configuration if cache expired
this.initialize().catch(error => { this.initialize().catch(error => {
log.error('Failed to reload configuration', error); log.error(`Failed to reload configuration: ${error instanceof Error ? error.message : String(error)}`);
}); });
} }
@@ -416,10 +416,10 @@ export class ConfigurationService {
const defaults = this.getDefaultConfig(); const defaults = this.getDefaultConfig();
return { return {
temperature: defaults.temperature, temperature: defaults.temperature,
max_tokens: defaults.maxTokens, maxTokens: defaults.maxTokens,
top_p: defaults.topP, topP: defaults.topP,
presence_penalty: defaults.presencePenalty, presencePenalty: defaults.presencePenalty,
frequency_penalty: defaults.frequencyPenalty frequencyPenalty: defaults.frequencyPenalty
}; };
} }

View File

@@ -8,7 +8,7 @@
* - No production debug statements * - No production debug statements
*/ */
import log from '../../../log.js'; import log from '../../log.js';
import configurationService from './configuration_service.js'; import configurationService from './configuration_service.js';
// Log levels // Log levels
@@ -128,14 +128,20 @@ export class LoggingService {
switch (entry.level) { switch (entry.level) {
case LogLevel.ERROR: case LogLevel.ERROR:
if (entry.error) { if (entry.error) {
log.error(formattedMessage, entry.error); log.error(`${formattedMessage}: ${entry.error instanceof Error ? entry.error.message : String(entry.error)}`);
} else if (entry.data) {
log.error(`${formattedMessage}: ${JSON.stringify(entry.data)}`);
} else { } else {
log.error(formattedMessage, entry.data); log.error(formattedMessage);
} }
break; break;
case LogLevel.WARN: case LogLevel.WARN:
log.warn(formattedMessage, entry.data); if (entry.data && Object.keys(entry.data).length > 0) {
log.info(`[WARN] ${formattedMessage} - ${JSON.stringify(entry.data)}`);
} else {
log.info(`[WARN] ${formattedMessage}`);
}
break; break;
case LogLevel.INFO: case LogLevel.INFO:

View File

@@ -8,7 +8,7 @@
* - Performance characteristics * - Performance characteristics
*/ */
import log from '../../../log.js'; import log from '../../log.js';
// Model capability interfaces // Model capability interfaces
export interface ModelCapabilities { export interface ModelCapabilities {
@@ -354,7 +354,7 @@ export class ModelRegistry {
registerModel(model: ModelInfo): void { registerModel(model: ModelInfo): void {
const key = `${model.provider}:${model.id}`; const key = `${model.provider}:${model.id}`;
this.models.set(key, model); this.models.set(key, model);
log.debug(`Registered model: ${key}`); log.info(`Registered model: ${key}`);
} }
/** /**
@@ -412,15 +412,16 @@ export class ModelRegistry {
if (constraints?.requiresStreaming) { if (constraints?.requiresStreaming) {
candidates = candidates.filter(m => m.capabilities.supportsStreaming); candidates = candidates.filter(m => m.capabilities.supportsStreaming);
} }
if (constraints?.minContextWindow) { if (constraints?.minContextWindow !== undefined) {
candidates = candidates.filter(m => m.capabilities.contextWindow >= constraints.minContextWindow); const minWindow = constraints.minContextWindow;
candidates = candidates.filter(m => m.capabilities.contextWindow >= minWindow);
} }
// Filter by cost // Filter by cost
if (constraints?.maxCost !== undefined) { if (constraints?.maxCost !== undefined) {
candidates = candidates.filter(m => { candidates = candidates.filter(m => {
if (!m.cost) return true; // Local models have no cost if (!m.cost) return true; // Local models have no cost
return m.cost.inputTokens <= constraints.maxCost; return m.cost.inputTokens <= constraints.maxCost!;
}); });
} }

View File

@@ -177,12 +177,12 @@ export class SimplifiedChatPipeline {
} }
// Execute LLM call // Execute LLM call
const service = aiServiceManager.getService(); const service = await aiServiceManager.getService();
if (!service) { if (!service) {
throw new Error('No AI service available'); throw new Error('No AI service available');
} }
const response = await service.chat(messages, options); const response = await service.generateChatCompletion(messages, options);
this.recordMetric('llm_execution', Date.now() - startTime); this.recordMetric('llm_execution', Date.now() - startTime);
logger.log(LogLevel.DEBUG, 'Stage 2: LLM execution completed', { logger.log(LogLevel.DEBUG, 'Stage 2: LLM execution completed', {
@@ -249,12 +249,12 @@ export class SimplifiedChatPipeline {
enableTools: true enableTools: true
}; };
const service = aiServiceManager.getService(); const service = await aiServiceManager.getService();
if (!service) { if (!service) {
throw new Error('No AI service available'); throw new Error('No AI service available');
} }
currentResponse = await service.chat(currentMessages, followUpOptions); currentResponse = await service.generateChatCompletion(currentMessages, followUpOptions);
// Check if we need another iteration // Check if we need another iteration
if (!currentResponse.tool_calls?.length) { if (!currentResponse.tool_calls?.length) {
@@ -302,9 +302,8 @@ export class SimplifiedChatPipeline {
response.text = accumulatedText; response.text = accumulatedText;
} }
// Add metadata // Add metadata to response (cast to any to add extra properties)
response.metadata = { (response as any).metadata = {
...response.metadata,
requestId: logger.requestId, requestId: logger.requestId,
processingTime: Date.now() - startTime processingTime: Date.now() - startTime
}; };
@@ -325,7 +324,7 @@ export class SimplifiedChatPipeline {
toolCalls: ToolCall[], toolCalls: ToolCall[],
logger: ReturnType<typeof loggingService.withRequestId> logger: ReturnType<typeof loggingService.withRequestId>
): Promise<Array<{ toolCallId: string; content: string }>> { ): Promise<Array<{ toolCallId: string; content: string }>> {
const results = []; const results: Array<{ toolCallId: string; content: string }> = [];
for (const toolCall of toolCalls) { for (const toolCall of toolCalls) {
try { try {
@@ -334,17 +333,20 @@ export class SimplifiedChatPipeline {
throw new Error(`Tool not found: ${toolCall.function.name}`); throw new Error(`Tool not found: ${toolCall.function.name}`);
} }
const args = JSON.parse(toolCall.function.arguments || '{}'); const argsString = typeof toolCall.function.arguments === 'string'
? toolCall.function.arguments
: JSON.stringify(toolCall.function.arguments || {});
const args = JSON.parse(argsString);
const result = await tool.execute(args); const result = await tool.execute(args);
results.push({ results.push({
toolCallId: toolCall.id, toolCallId: toolCall.id || `tool_${Date.now()}`,
content: typeof result === 'string' ? result : JSON.stringify(result) content: typeof result === 'string' ? result : JSON.stringify(result)
}); });
logger.log(LogLevel.DEBUG, 'Tool executed successfully', { logger.log(LogLevel.DEBUG, 'Tool executed successfully', {
tool: toolCall.function.name, tool: toolCall.function.name,
toolCallId: toolCall.id toolCallId: toolCall.id || 'no-id'
}); });
} catch (error) { } catch (error) {
@@ -354,7 +356,7 @@ export class SimplifiedChatPipeline {
}); });
results.push({ results.push({
toolCallId: toolCall.id, toolCallId: toolCall.id || `tool_error_${Date.now()}`,
content: `Error: ${error instanceof Error ? error.message : String(error)}` content: `Error: ${error instanceof Error ? error.message : String(error)}`
}); });
} }
@@ -371,7 +373,16 @@ export class SimplifiedChatPipeline {
// This is a simplified context extraction // This is a simplified context extraction
// In production, this would call the semantic search service // In production, this would call the semantic search service
const contextService = await import('../context/services/context_service.js'); const contextService = await import('../context/services/context_service.js');
return await contextService.default.getContextForQuery(query, noteId); const results = await contextService.default.findRelevantNotes(query, noteId, {
maxResults: 5,
summarize: true
});
// Format results as context string
if (results && results.length > 0) {
return results.map(r => `${r.title}: ${r.content}`).join('\n\n');
}
return null;
} catch (error) { } catch (error) {
loggingService.log(LogLevel.ERROR, 'Context extraction failed', { error }); loggingService.log(LogLevel.ERROR, 'Context extraction failed', { error });
return null; return null;

View File

@@ -1,6 +1,5 @@
import { BaseAIService } from '../base_ai_service.js'; import { BaseAIService } from '../base_ai_service.js';
import type { Message, ChatCompletionOptions, ChatResponse, StreamChunk } from '../ai_interface.js'; import type { Message, ChatCompletionOptions, ChatResponse, StreamChunk } from '../ai_interface.js';
import { OllamaMessageFormatter } from '../formatters/ollama_formatter.js';
import log from '../../log.js'; import log from '../../log.js';
import type { ToolCall, Tool } from '../tools/tool_interfaces.js'; import type { ToolCall, Tool } from '../tools/tool_interfaces.js';
import toolRegistry from '../tools/tool_registry.js'; import toolRegistry from '../tools/tool_registry.js';
@@ -55,12 +54,10 @@ interface OllamaRequestOptions {
} }
export class OllamaService extends BaseAIService { export class OllamaService extends BaseAIService {
private formatter: OllamaMessageFormatter;
private client: Ollama | null = null; private client: Ollama | null = null;
constructor() { constructor() {
super('Ollama'); super('Ollama');
this.formatter = new OllamaMessageFormatter();
} }
override isAvailable(): boolean { override isAvailable(): boolean {
@@ -147,14 +144,11 @@ export class OllamaService extends BaseAIService {
// Determine if tools will be used in this request // Determine if tools will be used in this request
const willUseTools = providerOptions.enableTools !== false; const willUseTools = providerOptions.enableTools !== false;
// Use the formatter to prepare messages // Format messages directly (Ollama uses OpenAI format)
messagesToSend = this.formatter.formatMessages( messagesToSend = [
messages, { role: 'system', content: systemPrompt },
systemPrompt, ...messages
undefined, // context ];
providerOptions.preserveSystemPrompt,
willUseTools // Pass flag indicating if tools will be used
);
log.info(`Sending to Ollama with formatted messages: ${messagesToSend.length}${willUseTools ? ' (with tool instructions)' : ''}`); log.info(`Sending to Ollama with formatted messages: ${messagesToSend.length}${willUseTools ? ' (with tool instructions)' : ''}`);
} }

View File

@@ -60,7 +60,7 @@ export class ToolFormatAdapter {
// OpenAI format matches our standard format // OpenAI format matches our standard format
return tools; return tools;
default: default:
log.warn(`Unknown provider ${provider}, returning tools in standard format`); log.info(`Warning: Unknown provider ${provider}, returning tools in standard format`);
return tools; return tools;
} }
} }
@@ -148,7 +148,7 @@ export class ToolFormatAdapter {
// OpenAI format matches our standard format // OpenAI format matches our standard format
return toolCalls as ToolCall[]; return toolCalls as ToolCall[];
default: default:
log.warn(`Unknown provider ${provider}, attempting standard conversion`); log.info(`Warning: Unknown provider ${provider}, attempting standard conversion`);
return toolCalls as ToolCall[]; return toolCalls as ToolCall[];
} }
} }
@@ -265,7 +265,7 @@ export class ToolFormatAdapter {
// Warn if required array is missing or empty (Anthropic may send empty inputs) // Warn if required array is missing or empty (Anthropic may send empty inputs)
if (!tool.input_schema.required || tool.input_schema.required.length === 0) { if (!tool.input_schema.required || tool.input_schema.required.length === 0) {
log.warn(`Anthropic tool ${tool.name} has no required parameters - may receive empty inputs`); log.info(`Warning: Anthropic tool ${tool.name} has no required parameters - may receive empty inputs`);
} }
return true; return true;