ollama-ai-provider
Version:
Vercel AI Provider for running LLMs locally using Ollama
805 lines (790 loc) • 24.9 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
createOllama: () => createOllama,
ollama: () => ollama
});
module.exports = __toCommonJS(src_exports);
// src/ollama-provider.ts
var import_provider_utils7 = require("@ai-sdk/provider-utils");
// src/ollama-chat-language-model.ts
var import_provider_utils5 = require("@ai-sdk/provider-utils");
var import_zod2 = require("zod");
// src/convert-to-ollama-chat-messages.ts
var import_provider = require("@ai-sdk/provider");
var import_provider_utils = require("@ai-sdk/provider-utils");
function convertToOllamaChatMessages(prompt) {
const messages = [];
for (const { content, role } of prompt) {
switch (role) {
case "system": {
messages.push({ content, role: "system" });
break;
}
case "user": {
messages.push({
...content.reduce(
(previous, current) => {
if (current.type === "text") {
previous.content += current.text;
} else if (current.type === "image" && current.image instanceof URL) {
throw new import_provider.UnsupportedFunctionalityError({
functionality: "Image URLs in user messages"
});
} else if (current.type === "image" && current.image instanceof Uint8Array) {
previous.images = previous.images || [];
previous.images.push((0, import_provider_utils.convertUint8ArrayToBase64)(current.image));
}
return previous;
},
{ content: "" }
),
role: "user"
});
break;
}
case "assistant": {
const text = [];
const toolCalls = [];
for (const part of content) {
switch (part.type) {
case "text": {
text.push(part.text);
break;
}
case "tool-call": {
toolCalls.push({
function: {
arguments: part.args,
name: part.toolName
},
id: part.toolCallId,
type: "function"
});
break;
}
default: {
const _exhaustiveCheck = part;
throw new Error(`Unsupported part: ${_exhaustiveCheck}`);
}
}
}
messages.push({
content: text.join(","),
role: "assistant",
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
});
break;
}
case "tool": {
messages.push(
...content.map((part) => ({
// Non serialized contents are not accepted by ollama, triggering the following error:
// "json: cannot unmarshal array into Go struct field ChatRequest.messages of type string"
content: typeof part.result === "object" ? JSON.stringify(part.result) : `${part.result}`,
role: "tool",
tool_call_id: part.toolCallId
}))
);
break;
}
default: {
const _exhaustiveCheck = role;
throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
}
}
}
return messages;
}
// src/generate-tool/infer-tool-calls-from-stream.ts
var import_provider_utils2 = require("@ai-sdk/provider-utils");
var import_partial_json = require("partial-json");
var InferToolCallsFromStream = class {
constructor({
tools,
type
}) {
this._firstMessage = true;
this._tools = tools;
this._toolPartial = "";
this._toolCalls = [];
this._type = type;
this._detectedToolCall = false;
}
get toolCalls() {
return this._toolCalls;
}
get detectedToolCall() {
return this._detectedToolCall;
}
parse({
controller,
delta
}) {
var _a;
this.detectToolCall(delta);
if (!this._detectedToolCall) {
return false;
}
this._toolPartial += delta;
let parsedFunctions = (0, import_partial_json.parse)(this._toolPartial);
if (!Array.isArray(parsedFunctions)) {
parsedFunctions = [parsedFunctions];
}
for (const [index, parsedFunction] of parsedFunctions.entries()) {
const parsedArguments = (_a = JSON.stringify(parsedFunction == null ? void 0 : parsedFunction.parameters)) != null ? _a : "";
if (parsedArguments === "") {
continue;
}
if (!this._toolCalls[index]) {
this._toolCalls[index] = {
function: {
arguments: "",
name: parsedFunction.name
},
id: (0, import_provider_utils2.generateId)(),
type: "function"
};
}
const toolCall = this._toolCalls[index];
toolCall.function.arguments = parsedArguments;
controller.enqueue({
argsTextDelta: delta,
toolCallId: toolCall.id,
toolCallType: "function",
toolName: toolCall.function.name,
type: "tool-call-delta"
});
}
return true;
}
finish({
controller
}) {
for (const toolCall of this.toolCalls) {
controller.enqueue({
args: toolCall.function.arguments,
toolCallId: toolCall.id,
toolCallType: "function",
toolName: toolCall.function.name,
type: "tool-call"
});
}
return this.finishReason();
}
detectToolCall(delta) {
if (!this._tools || this._tools.length === 0) {
return;
}
if (this._firstMessage) {
if (this._type === "object-tool") {
this._detectedToolCall = true;
} else if (this._type === "regular" && (delta.trim().startsWith("{") || delta.trim().startsWith("["))) {
this._detectedToolCall = true;
}
this._firstMessage = false;
}
}
finishReason() {
if (!this.detectedToolCall) {
return "stop";
}
return this._type === "object-tool" ? "stop" : "tool-calls";
}
};
// src/map-ollama-finish-reason.ts
function mapOllamaFinishReason({
finishReason,
hasToolCalls
}) {
switch (finishReason) {
case "stop": {
return hasToolCalls ? "tool-calls" : "stop";
}
default: {
return "other";
}
}
}
// src/ollama-error.ts
var import_provider_utils3 = require("@ai-sdk/provider-utils");
var import_zod = require("zod");
var ollamaErrorDataSchema = import_zod.z.object({
error: import_zod.z.object({
code: import_zod.z.string().nullable(),
message: import_zod.z.string(),
param: import_zod.z.any().nullable(),
type: import_zod.z.string()
})
});
var ollamaFailedResponseHandler = (0, import_provider_utils3.createJsonErrorResponseHandler)({
errorSchema: ollamaErrorDataSchema,
errorToMessage: (data) => data.error.message
});
// src/prepare-tools.ts
var import_provider2 = require("@ai-sdk/provider");
function prepareTools({
mode
}) {
var _a;
const tools = ((_a = mode.tools) == null ? void 0 : _a.length) ? mode.tools : void 0;
const toolWarnings = [];
const toolChoice = mode.toolChoice;
if (tools === void 0) {
return {
tools: void 0,
toolWarnings
};
}
const ollamaTools = [];
for (const tool of tools) {
if (tool.type === "provider-defined") {
toolWarnings.push({ tool, type: "unsupported-tool" });
} else {
ollamaTools.push({
function: {
description: tool.description,
name: tool.name,
parameters: tool.parameters
},
type: "function"
});
}
}
if (toolChoice === void 0) {
return {
tools: ollamaTools,
toolWarnings
};
}
const type = toolChoice.type;
switch (type) {
case "auto": {
return {
tools: ollamaTools,
toolWarnings
};
}
case "none": {
return {
tools: void 0,
toolWarnings
};
}
default: {
const _exhaustiveCheck = type;
throw new import_provider2.UnsupportedFunctionalityError({
functionality: `Unsupported tool choice type: ${_exhaustiveCheck}`
});
}
}
}
// src/utils/remove-undefined.ts
function removeUndefined(object) {
return Object.fromEntries(
Object.entries(object).filter(([, v]) => v !== void 0)
);
}
// src/utils/response-handler.ts
var import_provider3 = require("@ai-sdk/provider");
var import_provider_utils4 = require("@ai-sdk/provider-utils");
// src/utils/text-line-stream.ts
var TextLineStream = class extends TransformStream {
constructor() {
super({
flush: (controller) => {
if (this.buffer.length === 0) return;
controller.enqueue(this.buffer);
},
transform: (chunkText, controller) => {
chunkText = this.buffer + chunkText;
while (true) {
const EOL = chunkText.indexOf("\n");
if (EOL === -1) break;
controller.enqueue(chunkText.slice(0, EOL));
chunkText = chunkText.slice(EOL + 1);
}
this.buffer = chunkText;
}
});
this.buffer = "";
}
};
// src/utils/response-handler.ts
var createJsonStreamResponseHandler = (chunkSchema) => async ({ response }) => {
const responseHeaders = (0, import_provider_utils4.extractResponseHeaders)(response);
if (response.body === null) {
throw new import_provider3.EmptyResponseBodyError({});
}
return {
responseHeaders,
value: response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream()).pipeThrough(
new TransformStream({
transform(chunkText, controller) {
controller.enqueue(
(0, import_provider_utils4.safeParseJSON)({
schema: chunkSchema,
text: chunkText
})
);
}
})
)
};
};
// src/ollama-chat-language-model.ts
var OllamaChatLanguageModel = class {
constructor(modelId, settings, config) {
this.modelId = modelId;
this.settings = settings;
this.config = config;
this.specificationVersion = "v1";
this.defaultObjectGenerationMode = "json";
this.supportsImageUrls = false;
}
get supportsStructuredOutputs() {
var _a;
return (_a = this.settings.structuredOutputs) != null ? _a : false;
}
get provider() {
return this.config.provider;
}
getArguments({
frequencyPenalty,
maxTokens,
mode,
presencePenalty,
prompt,
responseFormat,
seed,
stopSequences,
temperature,
topK,
topP
}) {
const type = mode.type;
const warnings = [];
if (responseFormat !== void 0 && responseFormat.type === "json" && responseFormat.schema !== void 0 && !this.supportsStructuredOutputs) {
warnings.push({
details: "JSON response format schema is only supported with structuredOutputs",
setting: "responseFormat",
type: "unsupported-setting"
});
}
const baseArguments = {
format: responseFormat == null ? void 0 : responseFormat.type,
model: this.modelId,
options: removeUndefined({
f16_kv: this.settings.f16Kv,
frequency_penalty: frequencyPenalty,
low_vram: this.settings.lowVram,
main_gpu: this.settings.mainGpu,
min_p: this.settings.minP,
mirostat: this.settings.mirostat,
mirostat_eta: this.settings.mirostatEta,
mirostat_tau: this.settings.mirostatTau,
num_batch: this.settings.numBatch,
num_ctx: this.settings.numCtx,
num_gpu: this.settings.numGpu,
num_keep: this.settings.numKeep,
num_predict: maxTokens,
num_thread: this.settings.numThread,
numa: this.settings.numa,
penalize_newline: this.settings.penalizeNewline,
presence_penalty: presencePenalty,
repeat_last_n: this.settings.repeatLastN,
repeat_penalty: this.settings.repeatPenalty,
seed,
stop: stopSequences,
temperature,
tfs_z: this.settings.tfsZ,
top_k: topK,
top_p: topP,
typical_p: this.settings.typicalP,
use_mlock: this.settings.useMlock,
use_mmap: this.settings.useMmap,
vocab_only: this.settings.vocabOnly
})
};
switch (type) {
case "regular": {
const { tools, toolWarnings } = prepareTools({
mode
});
return {
args: {
...baseArguments,
messages: convertToOllamaChatMessages(prompt),
tools
},
type,
warnings: [...warnings, ...toolWarnings]
};
}
case "object-json": {
return {
args: {
...baseArguments,
format: this.supportsStructuredOutputs && mode.schema !== void 0 ? mode.schema : "json",
messages: convertToOllamaChatMessages(prompt)
},
type,
warnings
};
}
case "object-tool": {
return {
args: {
...baseArguments,
messages: convertToOllamaChatMessages(prompt),
tool_choice: {
function: { name: mode.tool.name },
type: "function"
},
tools: [
{
function: {
description: mode.tool.description,
name: mode.tool.name,
parameters: mode.tool.parameters
},
type: "function"
}
]
},
type,
warnings
};
}
default: {
const _exhaustiveCheck = type;
throw new Error(`Unsupported type: ${_exhaustiveCheck}`);
}
}
}
async doGenerate(options) {
var _a, _b;
const { args, warnings } = this.getArguments(options);
const body = {
...args,
stream: false
};
const { responseHeaders, value: response } = await (0, import_provider_utils5.postJsonToApi)({
abortSignal: options.abortSignal,
body,
failedResponseHandler: ollamaFailedResponseHandler,
fetch: this.config.fetch,
headers: (0, import_provider_utils5.combineHeaders)(this.config.headers(), options.headers),
successfulResponseHandler: (0, import_provider_utils5.createJsonResponseHandler)(
ollamaChatResponseSchema
),
url: `${this.config.baseURL}/chat`
});
const { messages: rawPrompt, ...rawSettings } = body;
const toolCalls = (_a = response.message.tool_calls) == null ? void 0 : _a.map((toolCall) => {
var _a2;
return {
args: JSON.stringify(toolCall.function.arguments),
toolCallId: (_a2 = toolCall.id) != null ? _a2 : (0, import_provider_utils5.generateId)(),
toolCallType: "function",
toolName: toolCall.function.name
};
});
return {
finishReason: mapOllamaFinishReason({
finishReason: response.done_reason,
hasToolCalls: toolCalls !== void 0 && toolCalls.length > 0
}),
rawCall: { rawPrompt, rawSettings },
rawResponse: { headers: responseHeaders },
request: { body: JSON.stringify(body) },
text: (_b = response.message.content) != null ? _b : void 0,
toolCalls,
usage: {
completionTokens: response.eval_count || 0,
promptTokens: response.prompt_eval_count || 0
},
warnings
};
}
async doStream(options) {
if (this.settings.simulateStreaming) {
const result = await this.doGenerate(options);
const simulatedStream = new ReadableStream({
start(controller) {
controller.enqueue({ type: "response-metadata", ...result.response });
if (result.text) {
controller.enqueue({
textDelta: result.text,
type: "text-delta"
});
}
if (result.toolCalls) {
for (const toolCall of result.toolCalls) {
controller.enqueue({
argsTextDelta: toolCall.args,
toolCallId: toolCall.toolCallId,
toolCallType: "function",
toolName: toolCall.toolName,
type: "tool-call-delta"
});
controller.enqueue({
type: "tool-call",
...toolCall
});
}
}
controller.enqueue({
finishReason: result.finishReason,
logprobs: result.logprobs,
providerMetadata: result.providerMetadata,
type: "finish",
usage: result.usage
});
controller.close();
}
});
return {
rawCall: result.rawCall,
rawResponse: result.rawResponse,
stream: simulatedStream,
warnings: result.warnings
};
}
const { args: body, type, warnings } = this.getArguments(options);
const { responseHeaders, value: response } = await (0, import_provider_utils5.postJsonToApi)({
abortSignal: options.abortSignal,
body,
failedResponseHandler: ollamaFailedResponseHandler,
fetch: this.config.fetch,
headers: (0, import_provider_utils5.combineHeaders)(this.config.headers(), options.headers),
successfulResponseHandler: createJsonStreamResponseHandler(
ollamaChatStreamChunkSchema
),
url: `${this.config.baseURL}/chat`
});
const { messages: rawPrompt, ...rawSettings } = body;
const tools = options.mode.type === "regular" ? options.mode.tools : options.mode.type === "object-tool" ? [options.mode.tool] : void 0;
const inferToolCallsFromStream = new InferToolCallsFromStream({
tools,
type
});
let finishReason = "other";
let usage = {
completionTokens: Number.NaN,
promptTokens: Number.NaN
};
const { experimentalStreamTools = true } = this.settings;
return {
rawCall: { rawPrompt, rawSettings },
rawResponse: { headers: responseHeaders },
request: { body: JSON.stringify(body) },
stream: response.pipeThrough(
new TransformStream({
async flush(controller) {
controller.enqueue({
finishReason,
type: "finish",
usage
});
},
async transform(chunk, controller) {
if (!chunk.success) {
controller.enqueue({ error: chunk.error, type: "error" });
return;
}
const value = chunk.value;
if (value.done) {
finishReason = inferToolCallsFromStream.finish({ controller });
usage = {
completionTokens: value.eval_count,
promptTokens: value.prompt_eval_count || 0
};
return;
}
if (experimentalStreamTools) {
const isToolCallStream = inferToolCallsFromStream.parse({
controller,
delta: value.message.content
});
if (isToolCallStream) {
return;
}
}
if (value.message.content !== null) {
controller.enqueue({
textDelta: value.message.content,
type: "text-delta"
});
}
}
})
),
warnings
};
}
};
var ollamaChatResponseSchema = import_zod2.z.object({
created_at: import_zod2.z.string(),
done: import_zod2.z.literal(true),
done_reason: import_zod2.z.string().optional().nullable(),
eval_count: import_zod2.z.number(),
eval_duration: import_zod2.z.number(),
load_duration: import_zod2.z.number().optional(),
message: import_zod2.z.object({
content: import_zod2.z.string(),
role: import_zod2.z.string(),
tool_calls: import_zod2.z.array(
import_zod2.z.object({
function: import_zod2.z.object({
arguments: import_zod2.z.record(import_zod2.z.any()),
name: import_zod2.z.string()
}),
id: import_zod2.z.string().optional()
})
).optional().nullable()
}),
model: import_zod2.z.string(),
prompt_eval_count: import_zod2.z.number().optional(),
prompt_eval_duration: import_zod2.z.number().optional(),
total_duration: import_zod2.z.number()
});
var ollamaChatStreamChunkSchema = import_zod2.z.discriminatedUnion("done", [
import_zod2.z.object({
created_at: import_zod2.z.string(),
done: import_zod2.z.literal(false),
message: import_zod2.z.object({
content: import_zod2.z.string(),
role: import_zod2.z.string()
}),
model: import_zod2.z.string()
}),
import_zod2.z.object({
created_at: import_zod2.z.string(),
done: import_zod2.z.literal(true),
eval_count: import_zod2.z.number(),
eval_duration: import_zod2.z.number(),
load_duration: import_zod2.z.number().optional(),
model: import_zod2.z.string(),
prompt_eval_count: import_zod2.z.number().optional(),
prompt_eval_duration: import_zod2.z.number().optional(),
total_duration: import_zod2.z.number()
})
]);
// src/ollama-embedding-model.ts
var import_provider4 = require("@ai-sdk/provider");
var import_provider_utils6 = require("@ai-sdk/provider-utils");
var import_zod3 = require("zod");
var OllamaEmbeddingModel = class {
constructor(modelId, settings, config) {
this.specificationVersion = "v1";
this.modelId = modelId;
this.settings = settings;
this.config = config;
}
get provider() {
return this.config.provider;
}
get maxEmbeddingsPerCall() {
var _a;
return (_a = this.settings.maxEmbeddingsPerCall) != null ? _a : 2048;
}
get supportsParallelCalls() {
return false;
}
async doEmbed({
abortSignal,
values
}) {
if (values.length > this.maxEmbeddingsPerCall) {
throw new import_provider4.TooManyEmbeddingValuesForCallError({
maxEmbeddingsPerCall: this.maxEmbeddingsPerCall,
modelId: this.modelId,
provider: this.provider,
values
});
}
const { responseHeaders, value: response } = await (0, import_provider_utils6.postJsonToApi)({
abortSignal,
body: {
input: values,
model: this.modelId
},
failedResponseHandler: ollamaFailedResponseHandler,
fetch: this.config.fetch,
headers: this.config.headers(),
successfulResponseHandler: (0, import_provider_utils6.createJsonResponseHandler)(
ollamaTextEmbeddingResponseSchema
),
url: `${this.config.baseURL}/embed`
});
return {
embeddings: response.embeddings,
rawResponse: { headers: responseHeaders },
usage: response.prompt_eval_count ? { tokens: response.prompt_eval_count } : void 0
};
}
};
var ollamaTextEmbeddingResponseSchema = import_zod3.z.object({
embeddings: import_zod3.z.array(import_zod3.z.array(import_zod3.z.number())),
prompt_eval_count: import_zod3.z.number().nullable()
});
// src/ollama-provider.ts
function createOllama(options = {}) {
var _a;
const baseURL = (_a = (0, import_provider_utils7.withoutTrailingSlash)(options.baseURL)) != null ? _a : "http://127.0.0.1:11434/api";
const getHeaders = () => ({
...options.headers
});
const createChatModel = (modelId, settings = {}) => new OllamaChatLanguageModel(modelId, settings, {
baseURL,
fetch: options.fetch,
headers: getHeaders,
provider: "ollama.chat"
});
const createEmbeddingModel = (modelId, settings = {}) => new OllamaEmbeddingModel(modelId, settings, {
baseURL,
fetch: options.fetch,
headers: getHeaders,
provider: "ollama.embedding"
});
const provider = function(modelId, settings) {
if (new.target) {
throw new Error(
"The Ollama model function cannot be called with the new keyword."
);
}
return createChatModel(modelId, settings);
};
provider.chat = createChatModel;
provider.embedding = createEmbeddingModel;
provider.languageModel = createChatModel;
provider.textEmbedding = createEmbeddingModel;
provider.textEmbeddingModel = createEmbeddingModel;
return provider;
}
var ollama = createOllama();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createOllama,
ollama
});
//# sourceMappingURL=index.js.map