将常用工具函数移动到utils目录 重构提供商策略模式实现 新增docker-compose构建配置文件 优化UI配置选择器的样式和交互 重构代理工具和API管理模块 更新脚本路径和依赖引用
690 lines
28 KiB
JavaScript
690 lines
28 KiB
JavaScript
/**
|
||
* Ollama转换器
|
||
* 处理Ollama协议与其他协议之间的转换
|
||
*/
|
||
|
||
import { v4 as uuidv4 } from 'uuid';
|
||
import { createHash } from 'crypto';
|
||
import { BaseConverter } from '../BaseConverter.js';
|
||
import { MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
|
||
import {
|
||
OLLAMA_DEFAULT_CONTEXT_LENGTH,
|
||
OLLAMA_DEFAULT_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_DEFAULT_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_SONNET_45_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_SONNET_45_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_HAIKU_45_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_HAIKU_45_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_OPUS_41_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_OPUS_41_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_SONNET_40_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_SONNET_40_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_SONNET_37_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_SONNET_37_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_OPUS_40_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_OPUS_40_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_HAIKU_35_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_HAIKU_35_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_HAIKU_30_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_HAIKU_30_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_SONNET_35_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_SONNET_35_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_CLAUDE_OPUS_30_CONTEXT_LENGTH,
|
||
OLLAMA_CLAUDE_OPUS_30_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_25_PRO_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_25_PRO_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_25_FLASH_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_25_FLASH_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_25_IMAGE_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_25_IMAGE_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_25_LIVE_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_25_LIVE_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_25_TTS_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_25_TTS_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_20_FLASH_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_20_FLASH_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_20_IMAGE_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_20_IMAGE_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_15_PRO_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_15_PRO_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_15_FLASH_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_15_FLASH_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GEMINI_DEFAULT_CONTEXT_LENGTH,
|
||
OLLAMA_GEMINI_DEFAULT_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GPT4_TURBO_CONTEXT_LENGTH,
|
||
OLLAMA_GPT4_TURBO_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GPT4_32K_CONTEXT_LENGTH,
|
||
OLLAMA_GPT4_32K_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GPT4_BASE_CONTEXT_LENGTH,
|
||
OLLAMA_GPT4_BASE_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GPT35_16K_CONTEXT_LENGTH,
|
||
OLLAMA_GPT35_16K_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_GPT35_BASE_CONTEXT_LENGTH,
|
||
OLLAMA_GPT35_BASE_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_QWEN_CODER_PLUS_CONTEXT_LENGTH,
|
||
OLLAMA_QWEN_CODER_PLUS_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_QWEN_VL_PLUS_CONTEXT_LENGTH,
|
||
OLLAMA_QWEN_VL_PLUS_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_QWEN_CODER_FLASH_CONTEXT_LENGTH,
|
||
OLLAMA_QWEN_CODER_FLASH_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_QWEN_DEFAULT_CONTEXT_LENGTH,
|
||
OLLAMA_QWEN_DEFAULT_MAX_OUTPUT_TOKENS,
|
||
OLLAMA_DEFAULT_FILE_TYPE,
|
||
OLLAMA_DEFAULT_QUANTIZATION_VERSION,
|
||
OLLAMA_DEFAULT_ROPE_FREQ_BASE,
|
||
OLLAMA_DEFAULT_TEMPERATURE,
|
||
OLLAMA_DEFAULT_TOP_P,
|
||
OLLAMA_DEFAULT_QUANTIZATION_LEVEL,
|
||
OLLAMA_SHOW_QUANTIZATION_LEVEL
|
||
} from '../utils.js';
|
||
|
||
|
||
|
||
/**
|
||
* Ollama转换器类
|
||
* 实现Ollama协议到其他协议的转换
|
||
*/
|
||
export class OllamaConverter extends BaseConverter {
|
||
constructor() {
|
||
super('ollama');
|
||
}
|
||
|
||
/**
|
||
* 转换请求 - Ollama -> 其他协议
|
||
*/
|
||
convertRequest(data, targetProtocol) {
|
||
switch (targetProtocol) {
|
||
case MODEL_PROTOCOL_PREFIX.OPENAI:
|
||
case MODEL_PROTOCOL_PREFIX.CLAUDE:
|
||
case MODEL_PROTOCOL_PREFIX.GEMINI:
|
||
return this.toOpenAIRequest(data);
|
||
default:
|
||
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 转换响应 - 其他协议 -> Ollama
|
||
*/
|
||
convertResponse(data, sourceProtocol, model) {
|
||
return this.toOllamaChatResponse(data, model);
|
||
}
|
||
|
||
/**
|
||
* 转换流式响应块 - 其他协议 -> Ollama
|
||
*/
|
||
convertStreamChunk(chunk, sourceProtocol, model, isDone = false) {
|
||
return this.toOllamaStreamChunk(chunk, model, isDone);
|
||
}
|
||
|
||
/**
|
||
* 转换模型列表 - 其他协议 -> Ollama
|
||
*/
|
||
convertModelList(data, sourceProtocol) {
|
||
return this.toOllamaTags(data, sourceProtocol);
|
||
}
|
||
|
||
// =========================================================================
|
||
// Ollama -> OpenAI 转换
|
||
// =========================================================================
|
||
|
||
/**
|
||
* Ollama请求 -> OpenAI请求
|
||
*/
|
||
toOpenAIRequest(ollamaRequest) {
|
||
const openaiRequest = {
|
||
model: ollamaRequest.model || 'default',
|
||
messages: [],
|
||
stream: ollamaRequest.stream !== undefined ? ollamaRequest.stream : false
|
||
};
|
||
|
||
// Map Ollama messages to OpenAI format
|
||
if (ollamaRequest.messages && Array.isArray(ollamaRequest.messages)) {
|
||
openaiRequest.messages = ollamaRequest.messages.map(msg => ({
|
||
role: msg.role || 'user',
|
||
content: msg.content || ''
|
||
}));
|
||
}
|
||
|
||
// Map Ollama options to OpenAI parameters
|
||
if (ollamaRequest.options) {
|
||
const opts = ollamaRequest.options;
|
||
if (opts.temperature !== undefined) openaiRequest.temperature = opts.temperature;
|
||
if (opts.top_p !== undefined) openaiRequest.top_p = opts.top_p;
|
||
if (opts.top_k !== undefined) openaiRequest.top_k = opts.top_k;
|
||
if (opts.num_predict !== undefined) openaiRequest.max_tokens = opts.num_predict;
|
||
if (opts.stop !== undefined) openaiRequest.stop = opts.stop;
|
||
}
|
||
|
||
// Handle system prompt
|
||
if (ollamaRequest.system) {
|
||
openaiRequest.messages.unshift({
|
||
role: 'system',
|
||
content: ollamaRequest.system
|
||
});
|
||
}
|
||
|
||
// Handle template/prompt for generate endpoint
|
||
if (ollamaRequest.prompt) {
|
||
openaiRequest.messages = [{
|
||
role: 'user',
|
||
content: ollamaRequest.prompt
|
||
}];
|
||
|
||
// Add system prompt if provided
|
||
if (ollamaRequest.system) {
|
||
openaiRequest.messages.unshift({
|
||
role: 'system',
|
||
content: ollamaRequest.system
|
||
});
|
||
}
|
||
}
|
||
|
||
return openaiRequest;
|
||
}
|
||
|
||
// =========================================================================
|
||
// OpenAI/Claude/Gemini -> Ollama 转换
|
||
// =========================================================================
|
||
|
||
/**
|
||
* OpenAI/Claude/Gemini响应 -> Ollama chat响应
|
||
*/
|
||
toOllamaChatResponse(response, model) {
|
||
const ollamaResponse = {
|
||
model: model || response.model || 'unknown',
|
||
created_at: new Date().toISOString(),
|
||
done: true
|
||
};
|
||
|
||
// Handle OpenAI format (choices array)
|
||
if (response.choices && response.choices.length > 0) {
|
||
const choice = response.choices[0];
|
||
ollamaResponse.message = {
|
||
role: choice.message?.role || 'assistant',
|
||
content: choice.message?.content || ''
|
||
};
|
||
|
||
// Map finish reason
|
||
if (choice.finish_reason) {
|
||
ollamaResponse.done_reason = choice.finish_reason === 'stop' ? 'stop' : choice.finish_reason;
|
||
}
|
||
}
|
||
// Handle Claude format (content array)
|
||
else if (response.content && Array.isArray(response.content)) {
|
||
let textContent = '';
|
||
response.content.forEach(block => {
|
||
if (block.type === 'text' && block.text) {
|
||
textContent += block.text;
|
||
}
|
||
});
|
||
|
||
ollamaResponse.message = {
|
||
role: response.role || 'assistant',
|
||
content: textContent
|
||
};
|
||
|
||
if (response.stop_reason) {
|
||
ollamaResponse.done_reason = response.stop_reason === 'end_turn' ? 'stop' : response.stop_reason;
|
||
}
|
||
}
|
||
// Handle Gemini format (candidates array)
|
||
else if (response.candidates && response.candidates.length > 0) {
|
||
const candidate = response.candidates[0];
|
||
let textContent = '';
|
||
if (candidate.content && candidate.content.parts) {
|
||
textContent = candidate.content.parts
|
||
.filter(part => part.text)
|
||
.map(part => part.text)
|
||
.join('');
|
||
}
|
||
|
||
ollamaResponse.message = {
|
||
role: candidate.content?.role || 'assistant',
|
||
content: textContent
|
||
};
|
||
|
||
if (candidate.finishReason) {
|
||
ollamaResponse.done_reason = candidate.finishReason.toLowerCase();
|
||
}
|
||
}
|
||
|
||
// Add usage statistics if available
|
||
const usage = response.usage || response.usageMetadata;
|
||
if (usage) {
|
||
ollamaResponse.prompt_eval_count = usage.prompt_tokens || usage.input_tokens || usage.promptTokenCount || 0;
|
||
ollamaResponse.eval_count = usage.completion_tokens || usage.output_tokens || usage.candidatesTokenCount || 0;
|
||
ollamaResponse.total_duration = 0;
|
||
ollamaResponse.load_duration = 0;
|
||
ollamaResponse.prompt_eval_duration = 0;
|
||
ollamaResponse.eval_duration = 0;
|
||
}
|
||
|
||
return ollamaResponse;
|
||
}
|
||
|
||
/**
|
||
* OpenAI/Claude/Gemini generate响应 -> Ollama generate响应
|
||
*/
|
||
toOllamaGenerateResponse(response, model) {
|
||
const ollamaResponse = {
|
||
model: model || response.model || 'unknown',
|
||
created_at: new Date().toISOString(),
|
||
done: true
|
||
};
|
||
|
||
// Handle OpenAI format
|
||
if (response.choices && response.choices.length > 0) {
|
||
const choice = response.choices[0];
|
||
ollamaResponse.response = choice.message?.content || choice.text || '';
|
||
|
||
if (choice.finish_reason) {
|
||
ollamaResponse.done_reason = choice.finish_reason === 'stop' ? 'stop' : choice.finish_reason;
|
||
}
|
||
}
|
||
// Handle Claude format
|
||
else if (response.content && Array.isArray(response.content)) {
|
||
let textContent = '';
|
||
response.content.forEach(block => {
|
||
if (block.type === 'text' && block.text) {
|
||
textContent += block.text;
|
||
}
|
||
});
|
||
ollamaResponse.response = textContent;
|
||
|
||
if (response.stop_reason) {
|
||
ollamaResponse.done_reason = response.stop_reason === 'end_turn' ? 'stop' : response.stop_reason;
|
||
}
|
||
}
|
||
// Handle Gemini format
|
||
else if (response.candidates && response.candidates.length > 0) {
|
||
const candidate = response.candidates[0];
|
||
let textContent = '';
|
||
if (candidate.content && candidate.content.parts) {
|
||
textContent = candidate.content.parts
|
||
.filter(part => part.text)
|
||
.map(part => part.text)
|
||
.join('');
|
||
}
|
||
ollamaResponse.response = textContent;
|
||
|
||
if (candidate.finishReason) {
|
||
ollamaResponse.done_reason = candidate.finishReason.toLowerCase();
|
||
}
|
||
}
|
||
|
||
// Add usage statistics
|
||
const genUsage = response.usage || response.usageMetadata;
|
||
if (genUsage) {
|
||
ollamaResponse.prompt_eval_count = genUsage.prompt_tokens || genUsage.input_tokens || genUsage.promptTokenCount || 0;
|
||
ollamaResponse.eval_count = genUsage.completion_tokens || genUsage.output_tokens || genUsage.candidatesTokenCount || 0;
|
||
ollamaResponse.total_duration = 0;
|
||
ollamaResponse.load_duration = 0;
|
||
ollamaResponse.prompt_eval_duration = 0;
|
||
ollamaResponse.eval_duration = 0;
|
||
}
|
||
|
||
return ollamaResponse;
|
||
}
|
||
|
||
/**
|
||
* OpenAI/Claude/Gemini流式块 -> Ollama流式块
|
||
*/
|
||
toOllamaStreamChunk(chunk, model, isDone = false) {
|
||
const ollamaChunk = {
|
||
model: model || 'unknown',
|
||
created_at: new Date().toISOString(),
|
||
done: isDone
|
||
};
|
||
|
||
// Handle Claude SSE format
|
||
if (chunk.type) {
|
||
if (chunk.type === 'content_block_delta' && chunk.delta) {
|
||
ollamaChunk.message = {
|
||
role: 'assistant',
|
||
content: chunk.delta.text || ''
|
||
};
|
||
} else if (chunk.type === 'message_delta' && chunk.usage) {
|
||
ollamaChunk.message = {
|
||
role: 'assistant',
|
||
content: ''
|
||
};
|
||
ollamaChunk.prompt_eval_count = 0;
|
||
ollamaChunk.eval_count = chunk.usage.output_tokens || 0;
|
||
} else {
|
||
ollamaChunk.message = {
|
||
role: 'assistant',
|
||
content: ''
|
||
};
|
||
}
|
||
}
|
||
// Handle Gemini format
|
||
else if (!isDone && chunk.candidates && chunk.candidates.length > 0) {
|
||
const candidate = chunk.candidates[0];
|
||
let content = '';
|
||
if (candidate.content && candidate.content.parts) {
|
||
content = candidate.content.parts
|
||
.filter(part => part.text)
|
||
.map(part => part.text)
|
||
.join('');
|
||
}
|
||
ollamaChunk.message = {
|
||
role: 'assistant',
|
||
content: content
|
||
};
|
||
}
|
||
// Handle OpenAI format
|
||
else if (!isDone && chunk.choices && chunk.choices.length > 0) {
|
||
const delta = chunk.choices[0].delta;
|
||
ollamaChunk.message = {
|
||
role: delta.role || 'assistant',
|
||
content: delta.content || ''
|
||
};
|
||
}
|
||
// Handle final chunk
|
||
else if (isDone) {
|
||
ollamaChunk.message = {
|
||
role: 'assistant',
|
||
content: ''
|
||
};
|
||
ollamaChunk.done_reason = 'stop';
|
||
}
|
||
|
||
return ollamaChunk;
|
||
}
|
||
|
||
/**
|
||
* OpenAI/Claude/Gemini流式块 -> Ollama generate流式块
|
||
*/
|
||
toOllamaGenerateStreamChunk(chunk, model, isDone = false) {
|
||
const ollamaChunk = {
|
||
model: model || 'unknown',
|
||
created_at: new Date().toISOString(),
|
||
done: isDone
|
||
};
|
||
|
||
// Handle Claude SSE format
|
||
if (chunk.type) {
|
||
if (chunk.type === 'content_block_delta' && chunk.delta) {
|
||
ollamaChunk.response = chunk.delta.text || '';
|
||
} else if (chunk.type === 'message_delta' && chunk.usage) {
|
||
ollamaChunk.response = '';
|
||
ollamaChunk.prompt_eval_count = 0;
|
||
ollamaChunk.eval_count = chunk.usage.output_tokens || 0;
|
||
} else {
|
||
ollamaChunk.response = '';
|
||
}
|
||
}
|
||
// Handle OpenAI format
|
||
else if (!isDone && chunk.choices && chunk.choices.length > 0) {
|
||
const delta = chunk.choices[0].delta;
|
||
ollamaChunk.response = delta.content || '';
|
||
}
|
||
// Handle final chunk
|
||
else if (isDone) {
|
||
ollamaChunk.response = '';
|
||
ollamaChunk.done_reason = 'stop';
|
||
}
|
||
|
||
return ollamaChunk;
|
||
}
|
||
|
||
/**
|
||
* OpenAI/Claude/Gemini模型列表 -> Ollama tags
|
||
*/
|
||
toOllamaTags(modelList, sourceProtocol = null) {
|
||
const models = [];
|
||
|
||
// Handle both OpenAI format (data array) and Gemini format (models array)
|
||
const sourceModels = modelList.data || modelList.models || [];
|
||
|
||
if (Array.isArray(sourceModels)) {
|
||
sourceModels.forEach(model => {
|
||
// Get model name
|
||
let modelName = model.id || model.name || model.displayName || 'unknown';
|
||
|
||
// Remove "models/" prefix if present (for Gemini)
|
||
if (modelName.startsWith('models/')) {
|
||
modelName = modelName.substring(7); // Remove "models/"
|
||
}
|
||
|
||
// Skip models with invalid names
|
||
if (modelName === 'unknown' || !modelName) {
|
||
return;
|
||
}
|
||
|
||
// IMPORTANT: Copilot expects family: "Ollama" with capital O!
|
||
const modelOwner = 'Ollama';
|
||
|
||
models.push({
|
||
name: modelName,
|
||
model: modelName,
|
||
modified_at: new Date().toISOString(),
|
||
size: 0, // As in the old patch
|
||
digest: '', // Empty string, as in the old patch
|
||
details: {
|
||
parent_model: '',
|
||
format: 'gguf',
|
||
family: modelOwner, // "Ollama" with capital O
|
||
families: [modelOwner],
|
||
parameter_size: '0B', // As in the old patch
|
||
quantization_level: OLLAMA_DEFAULT_QUANTIZATION_LEVEL
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
return { models };
|
||
}
|
||
|
||
/**
|
||
* Generate Ollama show response
|
||
*/
|
||
toOllamaShowResponse(modelName) {
|
||
// Minimal implementation, as in the old patch
|
||
let contextLength = OLLAMA_DEFAULT_CONTEXT_LENGTH;
|
||
let maxOutputTokens = OLLAMA_DEFAULT_MAX_OUTPUT_TOKENS;
|
||
let family = 'Ollama'; // ВАЖНО: С большой буквы, как ожидает Copilot!
|
||
let architecture = 'transformer';
|
||
|
||
const lowerName = modelName.toLowerCase();
|
||
|
||
// Determine contextLength by model name
|
||
// Claude models
|
||
if (lowerName.includes('claude')) {
|
||
architecture = 'claude';
|
||
contextLength = OLLAMA_CLAUDE_DEFAULT_CONTEXT_LENGTH; // Default 200K
|
||
|
||
// Claude Sonnet 4.5
|
||
if (lowerName.includes('sonnet-4-5') || lowerName.includes('sonnet-4.5')) {
|
||
contextLength = OLLAMA_CLAUDE_SONNET_45_CONTEXT_LENGTH; // 200K (1M beta available)
|
||
maxOutputTokens = OLLAMA_CLAUDE_SONNET_45_MAX_OUTPUT_TOKENS; // 64K output
|
||
}
|
||
// Claude Haiku 4.5
|
||
else if (lowerName.includes('haiku-4-5') || lowerName.includes('haiku-4.5')) {
|
||
contextLength = OLLAMA_CLAUDE_HAIKU_45_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_HAIKU_45_MAX_OUTPUT_TOKENS; // 64K output
|
||
}
|
||
// Claude Opus 4.1
|
||
else if (lowerName.includes('opus-4-1') || lowerName.includes('opus-4.1')) {
|
||
contextLength = OLLAMA_CLAUDE_OPUS_41_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_OPUS_41_MAX_OUTPUT_TOKENS; // 32K output
|
||
}
|
||
// Claude Sonnet 4.0 (legacy)
|
||
else if (lowerName.includes('sonnet-4-0') || lowerName.includes('sonnet-4.0') || lowerName.includes('sonnet-4-20')) {
|
||
contextLength = OLLAMA_CLAUDE_SONNET_40_CONTEXT_LENGTH; // 200K (1M beta available)
|
||
maxOutputTokens = OLLAMA_CLAUDE_SONNET_40_MAX_OUTPUT_TOKENS; // 64K output
|
||
}
|
||
// Claude Sonnet 3.7 (legacy)
|
||
else if (lowerName.includes('3-7') || lowerName.includes('3.7')) {
|
||
contextLength = OLLAMA_CLAUDE_SONNET_37_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_SONNET_37_MAX_OUTPUT_TOKENS; // 64K output (128K beta available)
|
||
}
|
||
// Claude Opus 4.0 (legacy)
|
||
else if (lowerName.includes('opus-4-0') || lowerName.includes('opus-4.0') || lowerName.includes('opus-4-20')) {
|
||
contextLength = OLLAMA_CLAUDE_OPUS_40_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_OPUS_40_MAX_OUTPUT_TOKENS; // 32K output
|
||
}
|
||
// Claude Haiku 3.5 (legacy)
|
||
else if (lowerName.includes('haiku-3-5') || lowerName.includes('haiku-3.5')) {
|
||
contextLength = OLLAMA_CLAUDE_HAIKU_35_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_HAIKU_35_MAX_OUTPUT_TOKENS; // 8K output
|
||
}
|
||
// Claude Haiku 3.0 (legacy)
|
||
else if (lowerName.includes('haiku-3-0') || lowerName.includes('haiku-3.0') || lowerName.includes('haiku-20240307')) {
|
||
contextLength = OLLAMA_CLAUDE_HAIKU_30_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_HAIKU_30_MAX_OUTPUT_TOKENS; // 4K output
|
||
}
|
||
// Claude Sonnet 3.5 (legacy)
|
||
else if (lowerName.includes('sonnet-3-5') || lowerName.includes('sonnet-3.5')) {
|
||
contextLength = OLLAMA_CLAUDE_SONNET_35_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_SONNET_35_MAX_OUTPUT_TOKENS; // 8K output
|
||
}
|
||
// Claude Opus 3.0 (legacy)
|
||
else if (lowerName.includes('opus-3-0') || lowerName.includes('opus-3.0') || lowerName.includes('opus') && lowerName.includes('20240229')) {
|
||
contextLength = OLLAMA_CLAUDE_OPUS_30_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_OPUS_30_MAX_OUTPUT_TOKENS; // 4K output
|
||
}
|
||
// Default for Claude
|
||
else {
|
||
contextLength = OLLAMA_CLAUDE_DEFAULT_CONTEXT_LENGTH; // 200K
|
||
maxOutputTokens = OLLAMA_CLAUDE_HAIKU_35_MAX_OUTPUT_TOKENS; // 8K output
|
||
}
|
||
}
|
||
// Gemini models
|
||
else if (lowerName.includes('gemini')) {
|
||
architecture = 'gemini';
|
||
|
||
// Gemini 2.5 Pro
|
||
if (lowerName.includes('2.5') && lowerName.includes('pro')) {
|
||
contextLength = OLLAMA_GEMINI_25_PRO_CONTEXT_LENGTH; // 1M input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_25_PRO_MAX_OUTPUT_TOKENS; // 65K output tokens
|
||
}
|
||
// Gemini 2.5 Flash / Flash-Lite
|
||
else if (lowerName.includes('2.5') && (lowerName.includes('flash') || lowerName.includes('lite'))) {
|
||
contextLength = OLLAMA_GEMINI_25_FLASH_CONTEXT_LENGTH; // 1M input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_25_FLASH_MAX_OUTPUT_TOKENS; // 65K output tokens
|
||
}
|
||
// Gemini 2.5 Flash Image
|
||
else if (lowerName.includes('2.5') && lowerName.includes('image')) {
|
||
contextLength = OLLAMA_GEMINI_25_IMAGE_CONTEXT_LENGTH; // 65K input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_25_IMAGE_MAX_OUTPUT_TOKENS; // 32K output tokens
|
||
}
|
||
// Gemini 2.5 Flash Live / Native Audio
|
||
else if (lowerName.includes('2.5') && (lowerName.includes('live') || lowerName.includes('native-audio'))) {
|
||
contextLength = OLLAMA_GEMINI_25_LIVE_CONTEXT_LENGTH; // 131K input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_25_LIVE_MAX_OUTPUT_TOKENS; // 8K output tokens
|
||
}
|
||
// Gemini 2.5 TTS
|
||
else if (lowerName.includes('2.5') && lowerName.includes('tts')) {
|
||
contextLength = OLLAMA_GEMINI_25_TTS_CONTEXT_LENGTH; // 8K input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_25_TTS_MAX_OUTPUT_TOKENS; // 16K output tokens
|
||
}
|
||
// Gemini 2.0 Flash
|
||
else if (lowerName.includes('2.0') && lowerName.includes('flash')) {
|
||
contextLength = OLLAMA_GEMINI_20_FLASH_CONTEXT_LENGTH; // 1M input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_20_FLASH_MAX_OUTPUT_TOKENS; // 8K output tokens
|
||
}
|
||
// Gemini 2.0 Flash Image
|
||
else if (lowerName.includes('2.0') && lowerName.includes('image')) {
|
||
contextLength = OLLAMA_GEMINI_20_IMAGE_CONTEXT_LENGTH; // 32K input tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_20_IMAGE_MAX_OUTPUT_TOKENS; // 8K output tokens
|
||
}
|
||
// Gemini 1.5 Pro (legacy)
|
||
else if (lowerName.includes('1.5') && lowerName.includes('pro')) {
|
||
contextLength = OLLAMA_GEMINI_15_PRO_CONTEXT_LENGTH; // 2M tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_15_PRO_MAX_OUTPUT_TOKENS;
|
||
}
|
||
// Gemini 1.5 Flash (legacy)
|
||
else if (lowerName.includes('1.5') && lowerName.includes('flash')) {
|
||
contextLength = OLLAMA_GEMINI_15_FLASH_CONTEXT_LENGTH; // 1M tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_15_FLASH_MAX_OUTPUT_TOKENS;
|
||
}
|
||
// Default for Gemini
|
||
else {
|
||
contextLength = OLLAMA_GEMINI_DEFAULT_CONTEXT_LENGTH; // 1M tokens
|
||
maxOutputTokens = OLLAMA_GEMINI_DEFAULT_MAX_OUTPUT_TOKENS;
|
||
}
|
||
}
|
||
// GPT-4 models
|
||
else if (lowerName.includes('gpt-4')) {
|
||
architecture = 'gpt';
|
||
|
||
if (lowerName.includes('turbo') || lowerName.includes('preview')) {
|
||
contextLength = OLLAMA_GPT4_TURBO_CONTEXT_LENGTH; // GPT-4 Turbo
|
||
maxOutputTokens = OLLAMA_GPT4_TURBO_MAX_OUTPUT_TOKENS;
|
||
} else if (lowerName.includes('32k')) {
|
||
contextLength = OLLAMA_GPT4_32K_CONTEXT_LENGTH;
|
||
maxOutputTokens = OLLAMA_GPT4_32K_MAX_OUTPUT_TOKENS;
|
||
} else {
|
||
contextLength = OLLAMA_GPT4_BASE_CONTEXT_LENGTH; // GPT-4 base
|
||
maxOutputTokens = OLLAMA_GPT4_BASE_MAX_OUTPUT_TOKENS;
|
||
}
|
||
}
|
||
// GPT-3.5 models
|
||
else if (lowerName.includes('gpt-3.5')) {
|
||
architecture = 'gpt';
|
||
|
||
if (lowerName.includes('16k')) {
|
||
contextLength = OLLAMA_GPT35_16K_CONTEXT_LENGTH;
|
||
maxOutputTokens = OLLAMA_GPT35_16K_MAX_OUTPUT_TOKENS;
|
||
} else {
|
||
contextLength = OLLAMA_GPT35_BASE_CONTEXT_LENGTH;
|
||
maxOutputTokens = OLLAMA_GPT35_BASE_MAX_OUTPUT_TOKENS;
|
||
}
|
||
}
|
||
// Qwen models
|
||
else if (lowerName.includes('qwen')) {
|
||
architecture = 'qwen';
|
||
|
||
// Qwen3 Coder Plus (coder-model)
|
||
if (lowerName.includes('coder-plus') || lowerName.includes('coder_plus') || lowerName.includes('coder-model')) {
|
||
contextLength = OLLAMA_QWEN_CODER_PLUS_CONTEXT_LENGTH; // 128K tokens
|
||
maxOutputTokens = OLLAMA_QWEN_CODER_PLUS_MAX_OUTPUT_TOKENS; // 65K output
|
||
}
|
||
// Qwen3 VL Plus (vision-model)
|
||
else if (lowerName.includes('vl-plus') || lowerName.includes('vl_plus') || lowerName.includes('vision-model')) {
|
||
contextLength = OLLAMA_QWEN_VL_PLUS_CONTEXT_LENGTH; // 256K tokens
|
||
maxOutputTokens = OLLAMA_QWEN_VL_PLUS_MAX_OUTPUT_TOKENS; // 32K output
|
||
}
|
||
// Qwen3 Coder Flash
|
||
else if (lowerName.includes('coder-flash') || lowerName.includes('coder_flash')) {
|
||
contextLength = OLLAMA_QWEN_CODER_FLASH_CONTEXT_LENGTH; // 128K tokens
|
||
maxOutputTokens = OLLAMA_QWEN_CODER_FLASH_MAX_OUTPUT_TOKENS; // 65K output
|
||
}
|
||
// Default for Qwen
|
||
else {
|
||
contextLength = OLLAMA_QWEN_DEFAULT_CONTEXT_LENGTH; // 32K tokens
|
||
maxOutputTokens = OLLAMA_QWEN_DEFAULT_MAX_OUTPUT_TOKENS;
|
||
}
|
||
}
|
||
|
||
// Minimal parameter_size, as in the old patch
|
||
let parameterSize = '0B';
|
||
|
||
return {
|
||
license: '',
|
||
modelfile: `# Modelfile for ${modelName}\nFROM ${modelName}`,
|
||
parameters: `num_ctx ${contextLength}\nnum_predict ${maxOutputTokens}\ntemperature ${OLLAMA_DEFAULT_TEMPERATURE}\ntop_p ${OLLAMA_DEFAULT_TOP_P}`,
|
||
template: '{{ if .System }}{{ .System }}\n{{ end }}{{ .Prompt }}',
|
||
details: {
|
||
parent_model: '',
|
||
format: 'gguf',
|
||
family: family,
|
||
families: [family],
|
||
parameter_size: parameterSize,
|
||
quantization_level: OLLAMA_SHOW_QUANTIZATION_LEVEL
|
||
},
|
||
model_info: {
|
||
'general.architecture': architecture,
|
||
'general.file_type': OLLAMA_DEFAULT_FILE_TYPE,
|
||
'general.parameter_count': 0,
|
||
'general.quantization_version': OLLAMA_DEFAULT_QUANTIZATION_VERSION,
|
||
'general.context_length': contextLength,
|
||
'llama.context_length': contextLength,
|
||
'llama.rope.freq_base': OLLAMA_DEFAULT_ROPE_FREQ_BASE
|
||
},
|
||
capabilities: ['tools', 'vision', 'completion'] // Indicate that the model supports tool calling
|
||
};
|
||
}
|
||
}
|