mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 02:52:27 +08:00
use this new providerMetadata approach
This commit is contained in:
parent
1dfbabc1d1
commit
59a358a3ee
@ -1,4 +1,5 @@
|
||||
import type { ToolCall } from './tools/tool_interfaces.js';
|
||||
import type { ModelMetadata } from './providers/provider_options.js';
|
||||
|
||||
export interface Message {
|
||||
role: 'user' | 'assistant' | 'system' | 'tool';
|
||||
@ -36,6 +37,7 @@ export interface ChatCompletionOptions {
|
||||
tools?: any[]; // Tools to provide to the LLM
|
||||
useAdvancedContext?: boolean; // Whether to use advanced context enrichment
|
||||
toolExecutionStatus?: any[]; // Status information about executed tools for feedback
|
||||
providerMetadata?: ModelMetadata; // Metadata about the provider and model capabilities
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
|
@ -18,15 +18,15 @@ export class LLMCompletionStage extends BasePipelineStage<LLMCompletionInput, {
|
||||
*/
|
||||
protected async process(input: LLMCompletionInput): Promise<{ response: ChatResponse }> {
|
||||
const { messages, options, provider } = input;
|
||||
|
||||
|
||||
// Create a copy of options to avoid modifying the original
|
||||
const updatedOptions = { ...options };
|
||||
|
||||
|
||||
// Check if tools should be enabled
|
||||
if (updatedOptions.enableTools !== false) {
|
||||
// Get all available tools from the registry
|
||||
const toolDefinitions = toolRegistry.getAllToolDefinitions();
|
||||
|
||||
|
||||
if (toolDefinitions.length > 0) {
|
||||
// Enable tools and add them to the options
|
||||
updatedOptions.enableTools = true;
|
||||
@ -35,11 +35,23 @@ export class LLMCompletionStage extends BasePipelineStage<LLMCompletionInput, {
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Generating LLM completion, provider: ${provider || 'auto'}, model: ${updatedOptions?.model || 'default'}`);
|
||||
// Determine which provider to use - prioritize in this order:
|
||||
// 1. Explicit provider parameter (legacy approach)
|
||||
// 2. Provider from metadata
|
||||
// 3. Auto-selection
|
||||
let selectedProvider = provider;
|
||||
|
||||
// If provider is specified, use that specific provider
|
||||
if (provider && aiServiceManager.isProviderAvailable(provider)) {
|
||||
const service = aiServiceManager.getService(provider);
|
||||
// If no explicit provider is specified, check for provider metadata
|
||||
if (!selectedProvider && updatedOptions.providerMetadata?.provider) {
|
||||
selectedProvider = updatedOptions.providerMetadata.provider;
|
||||
log.info(`Using provider ${selectedProvider} from metadata for model ${updatedOptions.model}`);
|
||||
}
|
||||
|
||||
log.info(`Generating LLM completion, provider: ${selectedProvider || 'auto'}, model: ${updatedOptions?.model || 'default'}`);
|
||||
|
||||
// If provider is specified (either explicit or from metadata), use that specific provider
|
||||
if (selectedProvider && aiServiceManager.isProviderAvailable(selectedProvider)) {
|
||||
const service = aiServiceManager.getService(selectedProvider);
|
||||
const response = await service.generateChatCompletion(messages, updatedOptions);
|
||||
return { response };
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { BasePipelineStage } from '../pipeline_stage.js';
|
||||
import type { ModelSelectionInput } from '../interfaces.js';
|
||||
import type { ChatCompletionOptions } from '../../ai_interface.js';
|
||||
import type { ModelMetadata } from '../../providers/provider_options.js';
|
||||
import log from '../../../log.js';
|
||||
import options from '../../../options.js';
|
||||
import aiServiceManager from '../../ai_service_manager.js';
|
||||
/**
|
||||
* Pipeline stage for selecting the appropriate LLM model
|
||||
*/
|
||||
@ -21,13 +23,22 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
|
||||
// If model already specified, don't override it
|
||||
if (updatedOptions.model) {
|
||||
log.info(`Using explicitly specified model: ${updatedOptions.model}`);
|
||||
// Check if the model has a provider prefix, which indicates legacy format
|
||||
const modelParts = this.parseModelIdentifier(updatedOptions.model);
|
||||
|
||||
if (modelParts.provider) {
|
||||
// Add provider metadata for backward compatibility
|
||||
this.addProviderMetadata(updatedOptions, modelParts.provider, modelParts.model);
|
||||
// Update the model to be just the model name without provider prefix
|
||||
updatedOptions.model = modelParts.model;
|
||||
log.info(`Using explicitly specified model: ${modelParts.model} from provider: ${modelParts.provider}`);
|
||||
} else {
|
||||
log.info(`Using explicitly specified model: ${updatedOptions.model}`);
|
||||
}
|
||||
|
||||
return { options: updatedOptions };
|
||||
}
|
||||
|
||||
// Get default model based on provider precedence
|
||||
let defaultModel = 'openai:gpt-3.5-turbo'; // Fallback default
|
||||
|
||||
// Enable tools by default unless explicitly disabled
|
||||
updatedOptions.enableTools = updatedOptions.enableTools !== false;
|
||||
|
||||
@ -61,6 +72,10 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
}
|
||||
}
|
||||
|
||||
// Get default provider and model based on precedence
|
||||
let defaultProvider = 'openai';
|
||||
let defaultModelName = 'gpt-3.5-turbo';
|
||||
|
||||
try {
|
||||
// Get provider precedence list
|
||||
const providerPrecedence = await options.getOption('aiProviderPrecedence');
|
||||
@ -78,18 +93,19 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
// Check for first available provider
|
||||
if (providers.length > 0) {
|
||||
const firstProvider = providers[0];
|
||||
defaultProvider = firstProvider;
|
||||
|
||||
// Get provider-specific default model
|
||||
if (firstProvider === 'openai') {
|
||||
const model = await options.getOption('openaiDefaultModel');
|
||||
if (model) defaultModel = `openai:${model}`;
|
||||
if (model) defaultModelName = model;
|
||||
} else if (firstProvider === 'anthropic') {
|
||||
const model = await options.getOption('anthropicDefaultModel');
|
||||
if (model) defaultModel = `anthropic:${model}`;
|
||||
if (model) defaultModelName = model;
|
||||
} else if (firstProvider === 'ollama') {
|
||||
const model = await options.getOption('ollamaDefaultModel');
|
||||
if (model) {
|
||||
defaultModel = `ollama:${model}`;
|
||||
defaultModelName = model;
|
||||
|
||||
// Enable tools for all Ollama models
|
||||
// The Ollama API will handle models that don't support tool calling
|
||||
@ -130,9 +146,125 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
queryComplexity = contentLength > 10000 ? 'high' : 'medium';
|
||||
}
|
||||
|
||||
updatedOptions.model = defaultModel;
|
||||
// Set the model and add provider metadata
|
||||
updatedOptions.model = defaultModelName;
|
||||
this.addProviderMetadata(updatedOptions, defaultProvider, defaultModelName);
|
||||
|
||||
log.info(`Selected model: ${updatedOptions.model} for query complexity: ${queryComplexity}`);
|
||||
log.info(`Selected model: ${defaultModelName} from provider: ${defaultProvider} for query complexity: ${queryComplexity}`);
|
||||
return { options: updatedOptions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to parse model identifier with provider prefix
|
||||
* Handles legacy format "provider:model"
|
||||
*/
|
||||
private parseModelIdentifier(modelId: string): { provider?: string, model: string } {
|
||||
if (!modelId) return { model: '' };
|
||||
|
||||
const parts = modelId.split(':');
|
||||
if (parts.length === 1) {
|
||||
// No provider prefix
|
||||
return { model: modelId };
|
||||
} else {
|
||||
// Extract provider and model
|
||||
const provider = parts[0];
|
||||
const model = parts.slice(1).join(':'); // Handle model names that might include :
|
||||
return { provider, model };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add provider metadata to the options based on model name
|
||||
*/
|
||||
private addProviderMetadata(options: ChatCompletionOptions, provider: string, modelName: string): void {
|
||||
// Check if we already have providerMetadata
|
||||
if (options.providerMetadata) {
|
||||
// If providerMetadata exists but not modelId, add the model name
|
||||
if (!options.providerMetadata.modelId && modelName) {
|
||||
options.providerMetadata.modelId = modelName;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If no provider could be determined, try to use precedence
|
||||
let selectedProvider = provider;
|
||||
if (!selectedProvider) {
|
||||
// List of providers in precedence order
|
||||
const providerPrecedence = ['anthropic', 'openai', 'ollama'];
|
||||
|
||||
// Find the first available provider
|
||||
for (const p of providerPrecedence) {
|
||||
if (aiServiceManager.isProviderAvailable(p)) {
|
||||
selectedProvider = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the provider metadata in the options
|
||||
if (selectedProvider) {
|
||||
// Ensure the provider is one of the valid types
|
||||
const validProvider = selectedProvider as 'openai' | 'anthropic' | 'ollama' | 'local';
|
||||
|
||||
options.providerMetadata = {
|
||||
provider: validProvider,
|
||||
modelId: modelName
|
||||
};
|
||||
|
||||
// For backward compatibility, ensure model name is set without prefix
|
||||
if (options.model && options.model.includes(':')) {
|
||||
options.model = modelName || options.model.split(':')[1];
|
||||
}
|
||||
|
||||
log.info(`Set provider metadata: provider=${selectedProvider}, model=${modelName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine model based on provider precedence
|
||||
*/
|
||||
private determineDefaultModel(input: ModelSelectionInput): string {
|
||||
const providerPrecedence = ['anthropic', 'openai', 'ollama'];
|
||||
|
||||
// Use only providers that are available
|
||||
const availableProviders = providerPrecedence.filter(provider =>
|
||||
aiServiceManager.isProviderAvailable(provider));
|
||||
|
||||
if (availableProviders.length === 0) {
|
||||
throw new Error('No AI providers are available');
|
||||
}
|
||||
|
||||
// Get the first available provider and its default model
|
||||
const defaultProvider = availableProviders[0] as 'openai' | 'anthropic' | 'ollama' | 'local';
|
||||
let defaultModel = 'gpt-3.5-turbo'; // Default fallback
|
||||
|
||||
// Set provider metadata
|
||||
if (!input.options.providerMetadata) {
|
||||
input.options.providerMetadata = {
|
||||
provider: defaultProvider,
|
||||
modelId: defaultModel
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Selected default model ${defaultModel} from provider ${defaultProvider}`);
|
||||
return defaultModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get estimated context window for Ollama models
|
||||
*/
|
||||
private getOllamaContextWindow(model: string): number {
|
||||
// Estimate based on model family
|
||||
if (model.includes('llama3')) {
|
||||
return 8192;
|
||||
} else if (model.includes('llama2')) {
|
||||
return 4096;
|
||||
} else if (model.includes('mistral') || model.includes('mixtral')) {
|
||||
return 8192;
|
||||
} else if (model.includes('gemma')) {
|
||||
return 8192;
|
||||
} else {
|
||||
return 4096; // Default fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -457,7 +457,8 @@ class RestChatService {
|
||||
systemPrompt: session.messages.find(m => m.role === 'system')?.content,
|
||||
temperature: session.metadata.temperature,
|
||||
maxTokens: session.metadata.maxTokens,
|
||||
model: session.metadata.model
|
||||
model: session.metadata.model,
|
||||
stream: req.method === 'GET' ? true : undefined // Explicitly set stream: true for GET requests
|
||||
},
|
||||
streamCallback: req.method === 'GET' ? (data, done) => {
|
||||
res.write(`data: ${JSON.stringify({ content: data, done })}\n\n`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user