ai-sdk-ollama 3.7.1 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - aeafdd6: - Update `generateText` to detect and use the stable `output` option instead of the deprecated `experimental_output` when `enableToolsWithStructuredOutput` is enabled, including a two-phase tools + structured output path and an integration test to guard this behavior.
8
+
9
+ ## 3.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ed617f1: - **Fix:** Make `OllamaProviderOptions`, `OllamaChatProviderOptions`, and `OllamaEmbeddingProviderOptions` compatible with the AI SDK's `providerOptions` (`Record<string, JSONObject>`). They are now defined as type aliases from Zod schemas (`z.infer`), so passing e.g. `providerOptions: { ollama: { structuredOutputs: true } }` into `streamText`, `generateText`, or `ToolLoopAgent` type-checks correctly (fixes #548).
14
+ - **New:** Export provider option Zod schemas: `ollamaProviderOptionsSchema`, `ollamaChatProviderOptionsSchema`, `ollamaEmbeddingProviderOptionsSchema`, and `ollamaRerankingProviderOptionsSchema` for validation or parsing.
15
+ - **New:** Image generation support via `ollama.imageModel(modelId)` and `OllamaImageModel`, using the AI SDK's `generateImage()` and experimental Ollama image models (e.g. `x/z-image-turbo`, `x/flux2-klein`). Supports `providerOptions.ollama` (e.g. `steps`, `negative_prompt`).
16
+ - **Change:** Reranking model now uses `parseProviderOptions` from `@ai-sdk/provider-utils` for `providerOptions.ollama`; `OllamaRerankingProviderOptions` is now a type inferred from the exported schema.
17
+
3
18
  ## 3.7.1
4
19
 
5
20
  ### Patch Changes
@@ -35,7 +35,6 @@ __export(index_browser_exports, {
35
35
  module.exports = __toCommonJS(index_browser_exports);
36
36
 
37
37
  // src/provider.browser.ts
38
- var import_provider = require("@ai-sdk/provider");
39
38
  var import_browser = require("ollama/browser");
40
39
 
41
40
  // src/utils/tool-calling-reliability.ts
@@ -16423,6 +16422,7 @@ var ollamaRerankingResponseSchema = external_exports.object({
16423
16422
  )
16424
16423
  });
16425
16424
  var ollamaRerankingProviderOptionsSchema = external_exports.object({
16425
+ /** Custom instruction for this specific reranking call. Overrides the instruction set in model settings. */
16426
16426
  instruction: external_exports.string().optional()
16427
16427
  });
16428
16428
  var ollamaErrorSchema = external_exports.object({
@@ -16455,15 +16455,11 @@ var OllamaRerankingModel = class {
16455
16455
  providerOptions
16456
16456
  }) {
16457
16457
  const warnings = [];
16458
- let rerankingOptions;
16459
- if (providerOptions?.ollama) {
16460
- const parsed = ollamaRerankingProviderOptionsSchema.safeParse(
16461
- providerOptions.ollama
16462
- );
16463
- if (parsed.success) {
16464
- rerankingOptions = parsed.data;
16465
- }
16466
- }
16458
+ const rerankingOptions = await (0, import_provider_utils2.parseProviderOptions)({
16459
+ provider: "ollama",
16460
+ providerOptions,
16461
+ schema: ollamaRerankingProviderOptionsSchema
16462
+ });
16467
16463
  let documentValues;
16468
16464
  if (documents.type === "object") {
16469
16465
  warnings.push({
@@ -16657,6 +16653,115 @@ var OllamaEmbeddingRerankingModel = class {
16657
16653
  }
16658
16654
  };
16659
16655
 
16656
+ // src/models/image-model.ts
16657
+ var import_provider_utils3 = require("@ai-sdk/provider-utils");
16658
+ var ollamaImageProviderOptionsSchema = external_exports.object({
16659
+ steps: external_exports.number().optional(),
16660
+ negative_prompt: external_exports.string().optional()
16661
+ });
16662
+ var OllamaImageModel = class {
16663
+ constructor(modelId, config2) {
16664
+ this.config = config2;
16665
+ __publicField(this, "specificationVersion", "v3");
16666
+ __publicField(this, "provider");
16667
+ __publicField(this, "modelId");
16668
+ __publicField(this, "maxImagesPerCall", 1);
16669
+ this.modelId = modelId;
16670
+ this.provider = config2.provider;
16671
+ }
16672
+ async doGenerate(options) {
16673
+ const {
16674
+ prompt,
16675
+ size,
16676
+ aspectRatio,
16677
+ seed,
16678
+ providerOptions,
16679
+ abortSignal,
16680
+ headers: optHeaders
16681
+ } = options;
16682
+ if (prompt == null || prompt === "") {
16683
+ throw new OllamaError({ message: "Image generation requires a prompt" });
16684
+ }
16685
+ const url2 = `${this.config.baseURL.replace(/\/$/, "")}/api/generate`;
16686
+ const body = {
16687
+ model: this.modelId,
16688
+ prompt,
16689
+ stream: false
16690
+ };
16691
+ if (size) {
16692
+ const [w, h] = size.split("x").map(Number);
16693
+ if (!Number.isNaN(w)) body.width = w;
16694
+ if (!Number.isNaN(h)) body.height = h;
16695
+ }
16696
+ if (aspectRatio && body.width == null && body.height == null) {
16697
+ const [ratioW, ratioH] = aspectRatio.split(":").map(Number);
16698
+ if (ratioW != null && ratioH != null && !Number.isNaN(ratioW) && !Number.isNaN(ratioH) && ratioW > 0 && ratioH > 0) {
16699
+ const target = 1024;
16700
+ body.width = Math.round(target * Math.sqrt(ratioW / ratioH));
16701
+ body.height = Math.round(target * Math.sqrt(ratioH / ratioW));
16702
+ }
16703
+ }
16704
+ if (seed != null) {
16705
+ const opts = typeof body.options === "object" && body.options ? body.options : {};
16706
+ body.options = { ...opts, seed };
16707
+ }
16708
+ const po = await (0, import_provider_utils3.parseProviderOptions)({
16709
+ provider: "ollama",
16710
+ providerOptions,
16711
+ schema: ollamaImageProviderOptionsSchema
16712
+ });
16713
+ if (po?.steps != null) body.steps = po.steps;
16714
+ if (po?.negative_prompt != null) body.negative_prompt = po.negative_prompt;
16715
+ const headers = {
16716
+ "Content-Type": "application/json",
16717
+ ...this.config.headers(),
16718
+ ...optHeaders
16719
+ };
16720
+ const res = await (this.config.fetch ?? fetch)(url2, {
16721
+ method: "POST",
16722
+ headers,
16723
+ body: JSON.stringify(body),
16724
+ signal: abortSignal
16725
+ });
16726
+ if (!res.ok) {
16727
+ const errBody = await res.json().catch(() => ({}));
16728
+ throw new OllamaError({
16729
+ message: errBody.error ?? `Ollama API error: ${res.status} ${res.statusText}`
16730
+ });
16731
+ }
16732
+ const data = await res.json();
16733
+ const images = [];
16734
+ if (Array.isArray(data.images) && data.images.length > 0) {
16735
+ images.push(...data.images);
16736
+ } else if (typeof data.image === "string" && data.image.trim().length > 0) {
16737
+ images.push(data.image.trim());
16738
+ } else if (typeof data.response === "string" && data.response.trim().length > 0) {
16739
+ const trimmed = data.response.trim();
16740
+ if (/^[A-Za-z0-9+/=]+$/.test(trimmed) && trimmed.length > 100) {
16741
+ images.push(trimmed);
16742
+ }
16743
+ }
16744
+ const responseHeaders = {};
16745
+ for (const [key, value] of res.headers.entries()) {
16746
+ responseHeaders[key] = value;
16747
+ }
16748
+ return {
16749
+ images,
16750
+ warnings: [],
16751
+ response: {
16752
+ timestamp: /* @__PURE__ */ new Date(),
16753
+ modelId: data.model ?? this.modelId,
16754
+ headers: Object.keys(responseHeaders).length > 0 ? responseHeaders : void 0
16755
+ },
16756
+ usage: data.eval_count == null ? void 0 : {
16757
+ inputTokens: void 0,
16758
+ outputTokens: data.eval_count,
16759
+ totalTokens: data.eval_count
16760
+ }
16761
+ };
16762
+ }
16763
+ };
16764
+
16660
16765
  // src/tool/web-search.ts
16661
16766
  var import_ai = require("ai");
16662
16767
  var webSearchInputSchema = external_exports.object({
@@ -16909,10 +17014,10 @@ function createOllama(options = {}) {
16909
17014
  });
16910
17015
  };
16911
17016
  const createRerankingModel = (modelId, settings = {}) => {
16912
- const baseURL = options.baseURL ?? "http://127.0.0.1:11434";
17017
+ const baseURL2 = options.baseURL ?? "http://127.0.0.1:11434";
16913
17018
  return new OllamaRerankingModel(modelId, settings, {
16914
17019
  provider: "ollama.reranking",
16915
- baseURL,
17020
+ baseURL: baseURL2,
16916
17021
  headers: () => normalizedHeaders,
16917
17022
  fetch: options.fetch
16918
17023
  });
@@ -16939,13 +17044,15 @@ function createOllama(options = {}) {
16939
17044
  provider.reranking = createRerankingModel;
16940
17045
  provider.rerankingModel = createRerankingModel;
16941
17046
  provider.embeddingReranking = createEmbeddingRerankingModel;
16942
- provider.imageModel = (modelId) => {
16943
- throw new import_provider.NoSuchModelError({
16944
- modelId,
16945
- modelType: "imageModel",
16946
- message: "Image generation is not supported by Ollama"
16947
- });
16948
- };
17047
+ const baseURL = options.baseURL ?? "http://127.0.0.1:11434";
17048
+ const createImageModel = (modelId) => new OllamaImageModel(modelId, {
17049
+ provider: "ollama",
17050
+ modelId,
17051
+ baseURL,
17052
+ headers: () => normalizedHeaders,
17053
+ fetch: options.fetch
17054
+ });
17055
+ provider.imageModel = createImageModel;
16949
17056
  const toolsWithClient = {
16950
17057
  webSearch: (options2 = {}) => ollamaTools.webSearch({ ...options2, client }),
16951
17058
  webFetch: (options2 = {}) => ollamaTools.webFetch({ ...options2, client })
@@ -16959,6 +17066,17 @@ var ollama = createOllama();
16959
17066
 
16960
17067
  // src/functions/generate-text.ts
16961
17068
  var import_ai3 = require("ai");
17069
+ function resultWithOverrides(base, overrides) {
17070
+ const descriptors = {};
17071
+ for (const key of Object.keys(overrides)) {
17072
+ descriptors[key] = {
17073
+ value: overrides[key],
17074
+ enumerable: true,
17075
+ configurable: true
17076
+ };
17077
+ }
17078
+ return Object.create(base, descriptors);
17079
+ }
16962
17080
  async function generateText(options) {
16963
17081
  const { enhancedOptions = {}, ...generateTextOptions } = options;
16964
17082
  const {
@@ -16969,33 +17087,42 @@ async function generateText(options) {
16969
17087
  enableToolsWithStructuredOutput = false
16970
17088
  } = enhancedOptions;
16971
17089
  const hasTools = generateTextOptions.tools && Object.keys(generateTextOptions.tools).length > 0;
16972
- const hasExperimentalOutput = "experimental_output" in generateTextOptions;
17090
+ const hasOutput = "output" in generateTextOptions;
16973
17091
  const requiresTools = generateTextOptions.toolChoice === "required" || typeof generateTextOptions.toolChoice === "object" && "type" in generateTextOptions.toolChoice && generateTextOptions.toolChoice.type === "tool";
16974
- if (enableToolsWithStructuredOutput && hasExperimentalOutput && requiresTools && hasTools) {
16975
- const toolResult = await (0, import_ai3.generateText)({
16976
- ...generateTextOptions,
16977
- experimental_output: void 0
16978
- });
17092
+ if (enableToolsWithStructuredOutput && hasOutput && requiresTools && hasTools) {
17093
+ const phase1Options = Object.fromEntries(
17094
+ Object.entries(generateTextOptions).filter(([key]) => key !== "output")
17095
+ );
17096
+ const toolResult = await (0, import_ai3.generateText)(
17097
+ phase1Options
17098
+ );
16979
17099
  if (toolResult.toolCalls && toolResult.toolCalls.length > 0) {
16980
17100
  const toolContext = toolResult.toolResults?.map(
16981
- (tr, i) => `${toolResult.toolCalls?.[i]?.toolName}: ${JSON.stringify(tr.output || tr)}`
17101
+ (tr, i) => `${toolResult.toolCalls?.[i]?.toolName}: ${JSON.stringify(tr.output ?? tr)}`
16982
17102
  ).join("\n");
16983
- const contextualPrompt = typeof generateTextOptions.prompt === "string" ? `${generateTextOptions.prompt}
17103
+ const toolResultsSuffix = `
16984
17104
 
16985
17105
  Tool Results:
16986
17106
  ${toolContext}
16987
17107
 
16988
- Please provide a structured response based on these tool results.` : generateTextOptions.prompt;
16989
- const _generateTextOptions = {
16990
- ...generateTextOptions,
16991
- prompt: contextualPrompt,
16992
- tools: void 0,
16993
- toolChoice: void 0
16994
- };
17108
+ Please provide a structured response based on these tool results.`;
17109
+ const phase2Base = Object.fromEntries(
17110
+ Object.entries(generateTextOptions).filter(
17111
+ ([key]) => key !== "tools" && key !== "toolChoice"
17112
+ )
17113
+ );
17114
+ const phase2OptionsWithPrompt = typeof phase2Base.prompt === "string" ? { ...phase2Base, prompt: phase2Base.prompt + toolResultsSuffix } : phase2Base.messages ? {
17115
+ ...phase2Base,
17116
+ prompt: void 0,
17117
+ messages: [
17118
+ ...phase2Base.messages,
17119
+ { role: "user", content: toolResultsSuffix.trim() }
17120
+ ]
17121
+ } : phase2Base;
16995
17122
  const structuredResult = await (0, import_ai3.generateText)(
16996
- _generateTextOptions
17123
+ phase2OptionsWithPrompt
16997
17124
  );
16998
- const enhancedResult = Object.assign(structuredResult, {
17125
+ const enhancedResult = resultWithOverrides(structuredResult, {
16999
17126
  toolCalls: toolResult.toolCalls,
17000
17127
  toolResults: toolResult.toolResults,
17001
17128
  staticToolCalls: toolResult.staticToolCalls,
@@ -17003,9 +17130,9 @@ Please provide a structured response based on these tool results.` : generateTex
17003
17130
  staticToolResults: toolResult.staticToolResults,
17004
17131
  dynamicToolResults: toolResult.dynamicToolResults,
17005
17132
  usage: {
17006
- inputTokens: (toolResult.usage.inputTokens || 0) + (structuredResult.usage.inputTokens || 0),
17007
- outputTokens: (toolResult.usage.outputTokens || 0) + (structuredResult.usage.outputTokens || 0),
17008
- totalTokens: (toolResult.usage.totalTokens || 0) + (structuredResult.usage.totalTokens || 0)
17133
+ inputTokens: (toolResult.usage.inputTokens ?? 0) + (structuredResult.usage.inputTokens ?? 0),
17134
+ outputTokens: (toolResult.usage.outputTokens ?? 0) + (structuredResult.usage.outputTokens ?? 0),
17135
+ totalTokens: (toolResult.usage.totalTokens ?? 0) + (structuredResult.usage.totalTokens ?? 0)
17009
17136
  }
17010
17137
  });
17011
17138
  return enhancedResult;
@@ -17016,7 +17143,9 @@ Please provide a structured response based on these tool results.` : generateTex
17016
17143
  // Only set stopWhen default if user didn't provide one and tools are enabled
17017
17144
  stopWhen: generateTextOptions.stopWhen ?? (hasTools ? (0, import_ai3.stepCountIs)(5) : void 0)
17018
17145
  });
17019
- const toolsWereCalled = result.steps?.some((step) => step.toolCalls && step.toolCalls.length > 0) ?? false;
17146
+ const toolsWereCalled = result.steps?.some(
17147
+ (step) => step.toolCalls && step.toolCalls.length > 0
17148
+ ) ?? false;
17020
17149
  const hasMinimalText = !result.text || result.text.trim().length < minResponseLength;
17021
17150
  if (!hasTools || !toolsWereCalled || !hasMinimalText || !enableSynthesis) {
17022
17151
  return result;
@@ -17048,11 +17177,16 @@ Tool results:
17048
17177
  ${toolContext}
17049
17178
 
17050
17179
  ${synthesisPrompt}`;
17051
- const { tools, prompt, messages, ...baseOptions } = generateTextOptions;
17052
- const synthesisOptions = messages ? {
17180
+ const generateOptions = generateTextOptions;
17181
+ const baseOptions = Object.fromEntries(
17182
+ Object.entries(generateOptions).filter(
17183
+ ([key]) => key !== "tools" && key !== "prompt" && key !== "messages"
17184
+ )
17185
+ );
17186
+ const synthesisOptions = generateOptions.messages ? {
17053
17187
  ...baseOptions,
17054
17188
  messages: [
17055
- ...messages || [],
17189
+ ...generateOptions.messages || [],
17056
17190
  { role: "user", content: fullSynthesisPrompt }
17057
17191
  ]
17058
17192
  } : {
@@ -17063,12 +17197,12 @@ ${synthesisPrompt}`;
17063
17197
  synthesisOptions
17064
17198
  );
17065
17199
  if (synthesisResult.text && synthesisResult.text.trim().length >= minResponseLength) {
17066
- const enhancedResult = Object.assign(result, {
17200
+ const enhancedResult = resultWithOverrides(result, {
17067
17201
  text: synthesisResult.text,
17068
17202
  usage: {
17069
- inputTokens: (result.usage.inputTokens || 0) + (synthesisResult.usage.inputTokens || 0),
17070
- outputTokens: (result.usage.outputTokens || 0) + (synthesisResult.usage.outputTokens || 0),
17071
- totalTokens: (result.usage.totalTokens || 0) + (synthesisResult.usage.totalTokens || 0)
17203
+ inputTokens: (result.usage.inputTokens ?? 0) + (synthesisResult.usage.inputTokens ?? 0),
17204
+ outputTokens: (result.usage.outputTokens ?? 0) + (synthesisResult.usage.outputTokens ?? 0),
17205
+ totalTokens: (result.usage.totalTokens ?? 0) + (synthesisResult.usage.totalTokens ?? 0)
17072
17206
  }
17073
17207
  });
17074
17208
  return enhancedResult;