feat(ollama): Enhance Ollama support by adding new models, updating API format handling, and integrating translation functionality.
This commit is contained in:
159
open-sse/translator/request/openai-to-ollama.js
Normal file
159
open-sse/translator/request/openai-to-ollama.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
|
||||
/**
|
||||
* Convert OpenAI request to Ollama format
|
||||
*
|
||||
* Ollama expects:
|
||||
* - model: string
|
||||
* - messages: Array<{role: string, content: string}>
|
||||
* - stream: boolean
|
||||
* - options?: {temperature?: number, num_predict?: number}
|
||||
*
|
||||
* Key differences from OpenAI:
|
||||
* - Content must be string, not array
|
||||
* - No support for tool_calls in request (tools are handled differently)
|
||||
* - tool role maps to user
|
||||
*/
|
||||
export function openaiToOllamaRequest(model, body, stream) {
|
||||
const result = {
|
||||
model: model,
|
||||
messages: normalizeMessages(body.messages),
|
||||
stream: stream
|
||||
};
|
||||
|
||||
// Temperature
|
||||
if (body.temperature !== undefined) {
|
||||
result.options = result.options || {};
|
||||
result.options.temperature = body.temperature;
|
||||
}
|
||||
|
||||
// Max tokens (Ollama uses num_predict)
|
||||
if (body.max_tokens !== undefined) {
|
||||
result.options = result.options || {};
|
||||
result.options.num_predict = body.max_tokens;
|
||||
}
|
||||
|
||||
// Top_p
|
||||
if (body.top_p !== undefined) {
|
||||
result.options = result.options || {};
|
||||
result.options.top_p = body.top_p;
|
||||
}
|
||||
|
||||
// Tools (Ollama supports tools in OpenAI format)
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
result.tools = body.tools;
|
||||
}
|
||||
|
||||
// Tool choice
|
||||
if (body.tool_choice) {
|
||||
result.tool_choice = body.tool_choice;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize messages to Ollama format
|
||||
* - Content must be string
|
||||
* - tool messages: convert tool_call_id to tool_name
|
||||
* - assistant messages: keep tool_calls as-is
|
||||
*/
|
||||
function normalizeMessages(messages) {
|
||||
if (!Array.isArray(messages)) return messages;
|
||||
|
||||
const result = [];
|
||||
const toolCallMap = new Map(); // Map tool_call_id -> tool_name
|
||||
|
||||
// First pass: build tool_call_id -> tool_name map from assistant messages
|
||||
for (const msg of messages) {
|
||||
if (msg.role === "assistant" && msg.tool_calls) {
|
||||
for (const tc of msg.tool_calls) {
|
||||
if (tc.id && tc.function?.name) {
|
||||
toolCallMap.set(tc.id, tc.function.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: convert messages
|
||||
for (const msg of messages) {
|
||||
// Handle tool result messages (OpenAI format -> Ollama format)
|
||||
if (msg.role === "tool") {
|
||||
const toolResult = normalizeContent(msg.content);
|
||||
if (!toolResult) continue;
|
||||
|
||||
// Get tool_name from map or use msg.name as fallback
|
||||
const toolName = toolCallMap.get(msg.tool_call_id) || msg.name || "unknown_tool";
|
||||
|
||||
result.push({
|
||||
role: "tool",
|
||||
tool_name: toolName,
|
||||
content: toolResult
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle assistant messages with tool_calls
|
||||
if (msg.role === "assistant" && msg.tool_calls) {
|
||||
const content = normalizeContent(msg.content) || "";
|
||||
|
||||
// Convert OpenAI tool_calls format to Ollama format
|
||||
const ollamaToolCalls = msg.tool_calls.map(tc => ({
|
||||
type: "function",
|
||||
function: {
|
||||
index: tc.index || 0,
|
||||
name: tc.function?.name || "",
|
||||
arguments: typeof tc.function?.arguments === "string"
|
||||
? JSON.parse(tc.function.arguments || "{}")
|
||||
: tc.function?.arguments || {}
|
||||
}
|
||||
}));
|
||||
|
||||
result.push({
|
||||
role: "assistant",
|
||||
content: content,
|
||||
tool_calls: ollamaToolCalls
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normal messages
|
||||
const role = msg.role;
|
||||
const content = normalizeContent(msg.content);
|
||||
|
||||
// Skip empty messages (except assistant)
|
||||
if (!content && role !== "assistant") continue;
|
||||
|
||||
result.push({
|
||||
role: role,
|
||||
content: content
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize content to string
|
||||
* Ollama only accepts string content
|
||||
*/
|
||||
function normalizeContent(content) {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
// Extract text from content array
|
||||
const textParts = content
|
||||
.filter(block => block && block.type === "text" && block.text)
|
||||
.map(block => block.text);
|
||||
|
||||
return textParts.join("\n") || "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// Register translator
|
||||
register(FORMATS.OPENAI, FORMATS.OLLAMA, openaiToOllamaRequest, null);
|
||||
Reference in New Issue
Block a user