@measey/mycoder-agent
Version:
Agent module for mycoder - an AI-powered software development assistant
202 lines • 7.09 kB
JavaScript
/**
* Ollama provider implementation using the official Ollama npm package
*/
import { Ollama, } from 'ollama';
import { TokenUsage } from '../../tokens.js';
// Define model context window sizes for Ollama models
// These are approximate and may vary based on specific model configurations
const OLLAMA_CONTEXT_WINDOWS = {
llama2: 4096,
'llama2-uncensored': 4096,
'llama2:13b': 4096,
'llama2:70b': 4096,
mistral: 8192,
'mistral:7b': 8192,
mixtral: 32768,
codellama: 16384,
phi: 2048,
phi2: 2048,
openchat: 8192,
};
/**
* Ollama provider implementation using the official Ollama npm package
*/
export class OllamaProvider {
name = 'ollama';
provider = 'ollama.chat';
model;
options;
client;
constructor(model, options = {}) {
this.model = model;
this.options = options;
const baseUrl = options.baseUrl ||
process.env.OLLAMA_BASE_URL ||
'http://localhost:11434';
this.client = new Ollama({ host: baseUrl });
}
/**
* Generate text using Ollama API via the official npm package
*/
async generateText(options) {
const { messages, functions, temperature = 0.7, maxTokens: requestMaxTokens, topP, frequencyPenalty, presencePenalty, stopSequences, } = options;
// Format messages for Ollama API
const formattedMessages = this.formatMessages(messages);
// Prepare request options
const requestOptions = {
model: this.model,
messages: formattedMessages,
stream: false,
options: {
temperature: temperature,
...(topP !== undefined && { top_p: topP }),
...(frequencyPenalty !== undefined && {
frequency_penalty: frequencyPenalty,
}),
...(presencePenalty !== undefined && {
presence_penalty: presencePenalty,
}),
...(stopSequences &&
stopSequences.length > 0 && { stop: stopSequences }),
},
};
// Add max_tokens if provided
if (requestMaxTokens !== undefined) {
requestOptions.options = {
...requestOptions.options,
num_predict: requestMaxTokens,
};
}
// Add functions/tools if provided
if (functions && functions.length > 0) {
requestOptions.tools = this.convertFunctionsToTools(functions);
}
// Make the API request using the Ollama client
const response = await this.client.chat({
...requestOptions,
stream: false,
});
// Extract content and tool calls
const content = response.message?.content || '';
// Handle tool calls if present
const toolCalls = this.extractToolCalls(response);
// Create token usage from response data
const tokenUsage = new TokenUsage();
tokenUsage.output = response.eval_count || 0;
tokenUsage.input = response.prompt_eval_count || 0;
// Calculate total tokens and get max tokens for the model
const totalTokens = tokenUsage.input + tokenUsage.output;
// Extract the base model name without specific parameters
// Check if model exists in limits, otherwise use base model or default
let contextWindow = OLLAMA_CONTEXT_WINDOWS[this.model];
if (!contextWindow) {
const baseModelName = this.model.split(':')[0];
if (baseModelName) {
contextWindow = OLLAMA_CONTEXT_WINDOWS[baseModelName];
}
// If still no context window, use the one from configuration if available
if (!contextWindow && this.options.contextWindow) {
contextWindow = this.options.contextWindow;
}
}
return {
text: content,
toolCalls: toolCalls,
tokenUsage: tokenUsage,
totalTokens,
contextWindow,
};
}
/*
interface Tool {
type: string;
function: {
name: string;
description: string;
parameters: {
type: string;
required: string[];
properties: {
[key: string]: {
type: string;
description: string;
enum?: string[];
};
};
};
};
}*/
/**
* Convert our function definitions to Ollama tool format
*/
convertFunctionsToTools(functions) {
return functions.map((fn) => ({
type: 'function',
function: {
name: fn.name,
description: fn.description,
parameters: fn.parameters,
},
}));
}
/**
* Extract tool calls from Ollama response
*/
extractToolCalls(response) {
if (!response.message?.tool_calls) {
return [];
}
return response.message.tool_calls.map((toolCall) => {
return {
id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
name: toolCall.function?.name,
content: typeof toolCall.function?.arguments === 'string'
? toolCall.function.arguments
: JSON.stringify(toolCall.function?.arguments || {}),
};
});
}
/**
* Format messages for Ollama API
*/
formatMessages(messages) {
const output = [];
messages.forEach((msg) => {
switch (msg.role) {
case 'user':
case 'assistant':
case 'system':
output.push({
role: msg.role,
content: msg.content,
});
break;
case 'tool_result':
// Ollama expects tool results as a 'tool' role
output.push({
role: 'tool',
content: typeof msg.content === 'string'
? msg.content
: JSON.stringify(msg.content),
});
break;
case 'tool_use': {
// So there is an issue here is that ollama expects tool calls to be part of the assistant message
// get last message and add tool call to it
const lastMessage = output[output.length - 1];
lastMessage.tool_calls = [
{
function: {
name: msg.name,
arguments: JSON.parse(msg.content),
},
},
];
break;
}
}
});
return output;
}
}
//# sourceMappingURL=ollama.js.map