@push.rocks/smartai 0.13.2 → 2.0.0
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/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/index.d.ts +6 -11
- package/dist_ts/index.js +6 -12
- package/dist_ts/plugins.d.ts +10 -15
- package/dist_ts/plugins.js +13 -19
- package/dist_ts/smartai.classes.smartai.d.ts +7 -0
- package/dist_ts/smartai.classes.smartai.js +51 -0
- package/dist_ts/smartai.interfaces.d.ts +41 -0
- package/dist_ts/smartai.interfaces.js +2 -0
- package/dist_ts/smartai.middleware.anthropic.d.ts +7 -0
- package/dist_ts/smartai.middleware.anthropic.js +36 -0
- package/dist_ts/smartai.provider.ollama.d.ts +8 -0
- package/dist_ts/smartai.provider.ollama.js +378 -0
- package/dist_ts_audio/index.d.ts +9 -0
- package/dist_ts_audio/index.js +15 -0
- package/dist_ts_audio/plugins.d.ts +2 -0
- package/dist_ts_audio/plugins.js +3 -0
- package/dist_ts_document/index.d.ts +11 -0
- package/dist_ts_document/index.js +45 -0
- package/dist_ts_document/plugins.d.ts +3 -0
- package/dist_ts_document/plugins.js +4 -0
- package/dist_ts_image/index.d.ts +46 -0
- package/dist_ts_image/index.js +110 -0
- package/dist_ts_image/plugins.d.ts +3 -0
- package/dist_ts_image/plugins.js +4 -0
- package/dist_ts_research/index.d.ts +19 -0
- package/dist_ts_research/index.js +98 -0
- package/dist_ts_research/plugins.d.ts +2 -0
- package/dist_ts_research/plugins.js +3 -0
- package/dist_ts_vision/index.d.ts +8 -0
- package/dist_ts_vision/index.js +21 -0
- package/dist_ts_vision/plugins.d.ts +2 -0
- package/dist_ts_vision/plugins.js +3 -0
- package/package.json +50 -22
- package/readme.hints.md +34 -88
- package/readme.md +284 -547
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/index.ts +8 -11
- package/ts/plugins.ts +19 -35
- package/ts/smartai.classes.smartai.ts +51 -0
- package/ts/smartai.interfaces.ts +53 -0
- package/ts/smartai.middleware.anthropic.ts +38 -0
- package/ts/smartai.provider.ollama.ts +426 -0
- package/ts_audio/index.ts +24 -0
- package/ts_audio/plugins.ts +2 -0
- package/ts_document/index.ts +61 -0
- package/ts_document/plugins.ts +3 -0
- package/ts_image/index.ts +147 -0
- package/ts_image/plugins.ts +3 -0
- package/ts_research/index.ts +120 -0
- package/ts_research/plugins.ts +2 -0
- package/ts_vision/index.ts +29 -0
- package/ts_vision/plugins.ts +2 -0
- package/dist_ts/abstract.classes.multimodal.d.ts +0 -212
- package/dist_ts/abstract.classes.multimodal.js +0 -43
- package/dist_ts/classes.conversation.d.ts +0 -31
- package/dist_ts/classes.conversation.js +0 -150
- package/dist_ts/classes.smartai.d.ts +0 -59
- package/dist_ts/classes.smartai.js +0 -139
- package/dist_ts/classes.tts.d.ts +0 -6
- package/dist_ts/classes.tts.js +0 -10
- package/dist_ts/interfaces.d.ts +0 -1
- package/dist_ts/interfaces.js +0 -2
- package/dist_ts/paths.d.ts +0 -2
- package/dist_ts/paths.js +0 -4
- package/dist_ts/provider.anthropic.d.ts +0 -48
- package/dist_ts/provider.anthropic.js +0 -369
- package/dist_ts/provider.elevenlabs.d.ts +0 -43
- package/dist_ts/provider.elevenlabs.js +0 -64
- package/dist_ts/provider.exo.d.ts +0 -40
- package/dist_ts/provider.exo.js +0 -116
- package/dist_ts/provider.groq.d.ts +0 -39
- package/dist_ts/provider.groq.js +0 -178
- package/dist_ts/provider.mistral.d.ts +0 -61
- package/dist_ts/provider.mistral.js +0 -288
- package/dist_ts/provider.ollama.d.ts +0 -141
- package/dist_ts/provider.ollama.js +0 -519
- package/dist_ts/provider.openai.d.ts +0 -62
- package/dist_ts/provider.openai.js +0 -403
- package/dist_ts/provider.perplexity.d.ts +0 -37
- package/dist_ts/provider.perplexity.js +0 -215
- package/dist_ts/provider.xai.d.ts +0 -52
- package/dist_ts/provider.xai.js +0 -160
- package/ts/abstract.classes.multimodal.ts +0 -240
- package/ts/classes.conversation.ts +0 -176
- package/ts/classes.smartai.ts +0 -187
- package/ts/classes.tts.ts +0 -15
- package/ts/interfaces.ts +0 -0
- package/ts/paths.ts +0 -4
- package/ts/provider.anthropic.ts +0 -446
- package/ts/provider.elevenlabs.ts +0 -116
- package/ts/provider.exo.ts +0 -155
- package/ts/provider.groq.ts +0 -219
- package/ts/provider.mistral.ts +0 -352
- package/ts/provider.ollama.ts +0 -695
- package/ts/provider.openai.ts +0 -462
- package/ts/provider.perplexity.ts +0 -259
- package/ts/provider.xai.ts +0 -214
package/ts/provider.ollama.ts
DELETED
|
@@ -1,695 +0,0 @@
|
|
|
1
|
-
import * as plugins from './plugins.js';
|
|
2
|
-
import * as paths from './paths.js';
|
|
3
|
-
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
|
4
|
-
import type {
|
|
5
|
-
ChatOptions,
|
|
6
|
-
ChatResponse,
|
|
7
|
-
ChatMessage,
|
|
8
|
-
ResearchOptions,
|
|
9
|
-
ResearchResponse,
|
|
10
|
-
ImageGenerateOptions,
|
|
11
|
-
ImageEditOptions,
|
|
12
|
-
ImageResponse,
|
|
13
|
-
StreamingChatOptions
|
|
14
|
-
} from './abstract.classes.multimodal.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Ollama model runtime options
|
|
18
|
-
* @see https://github.com/ollama/ollama/blob/main/docs/modelfile.md
|
|
19
|
-
*/
|
|
20
|
-
export interface IOllamaModelOptions {
|
|
21
|
-
num_ctx?: number; // Context window (default: 2048)
|
|
22
|
-
temperature?: number; // 0 = deterministic (default: 0.8)
|
|
23
|
-
top_k?: number; // Top-k sampling (default: 40)
|
|
24
|
-
top_p?: number; // Nucleus sampling (default: 0.9)
|
|
25
|
-
repeat_penalty?: number;// Repeat penalty (default: 1.1)
|
|
26
|
-
num_predict?: number; // Max tokens to predict
|
|
27
|
-
stop?: string[]; // Stop sequences
|
|
28
|
-
seed?: number; // Random seed for reproducibility
|
|
29
|
-
think?: boolean; // Enable thinking/reasoning mode (for GPT-OSS, QwQ, etc.)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* JSON Schema tool definition for Ollama native tool calling
|
|
34
|
-
* @see https://docs.ollama.com/capabilities/tool-calling
|
|
35
|
-
*/
|
|
36
|
-
export interface IOllamaTool {
|
|
37
|
-
type: 'function';
|
|
38
|
-
function: {
|
|
39
|
-
name: string;
|
|
40
|
-
description: string;
|
|
41
|
-
parameters: {
|
|
42
|
-
type: 'object';
|
|
43
|
-
properties: Record<string, {
|
|
44
|
-
type: string;
|
|
45
|
-
description?: string;
|
|
46
|
-
enum?: string[];
|
|
47
|
-
}>;
|
|
48
|
-
required?: string[];
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Tool call returned by model in native tool calling mode
|
|
55
|
-
*/
|
|
56
|
-
export interface IOllamaToolCall {
|
|
57
|
-
function: {
|
|
58
|
-
name: string;
|
|
59
|
-
arguments: Record<string, unknown>;
|
|
60
|
-
index?: number;
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface IOllamaProviderOptions {
|
|
65
|
-
baseUrl?: string;
|
|
66
|
-
model?: string;
|
|
67
|
-
visionModel?: string; // Model to use for vision tasks (e.g. 'llava')
|
|
68
|
-
defaultOptions?: IOllamaModelOptions; // Default model options
|
|
69
|
-
defaultTimeout?: number; // Default timeout in ms (default: 120000)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Extended chat options with Ollama-specific settings
|
|
74
|
-
*/
|
|
75
|
-
export interface IOllamaChatOptions extends ChatOptions {
|
|
76
|
-
options?: IOllamaModelOptions; // Per-request model options
|
|
77
|
-
timeout?: number; // Per-request timeout in ms
|
|
78
|
-
model?: string; // Per-request model override
|
|
79
|
-
tools?: IOllamaTool[]; // Available tools for native function calling
|
|
80
|
-
// images is inherited from ChatOptions
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Chunk emitted during streaming
|
|
85
|
-
*/
|
|
86
|
-
export interface IOllamaStreamChunk {
|
|
87
|
-
content: string;
|
|
88
|
-
thinking?: string; // For models with extended thinking
|
|
89
|
-
toolCalls?: IOllamaToolCall[]; // Tool calls in streaming mode
|
|
90
|
-
done: boolean;
|
|
91
|
-
stats?: {
|
|
92
|
-
totalDuration?: number;
|
|
93
|
-
evalCount?: number;
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Extended chat response with Ollama-specific fields
|
|
99
|
-
*/
|
|
100
|
-
export interface IOllamaChatResponse extends ChatResponse {
|
|
101
|
-
thinking?: string;
|
|
102
|
-
toolCalls?: IOllamaToolCall[]; // Tool calls from model (native tool calling)
|
|
103
|
-
stats?: {
|
|
104
|
-
totalDuration?: number;
|
|
105
|
-
evalCount?: number;
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export class OllamaProvider extends MultiModalModel {
|
|
110
|
-
private options: IOllamaProviderOptions;
|
|
111
|
-
private baseUrl: string;
|
|
112
|
-
private model: string;
|
|
113
|
-
private visionModel: string;
|
|
114
|
-
private defaultOptions: IOllamaModelOptions;
|
|
115
|
-
private defaultTimeout: number;
|
|
116
|
-
|
|
117
|
-
constructor(optionsArg: IOllamaProviderOptions = {}) {
|
|
118
|
-
super();
|
|
119
|
-
this.options = optionsArg;
|
|
120
|
-
this.baseUrl = optionsArg.baseUrl || 'http://localhost:11434';
|
|
121
|
-
this.model = optionsArg.model || 'llama2';
|
|
122
|
-
this.visionModel = optionsArg.visionModel || 'llava';
|
|
123
|
-
this.defaultOptions = optionsArg.defaultOptions || {};
|
|
124
|
-
this.defaultTimeout = optionsArg.defaultTimeout || 120000;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async start() {
|
|
128
|
-
await super.start();
|
|
129
|
-
// Verify Ollama is running
|
|
130
|
-
try {
|
|
131
|
-
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error('Failed to connect to Ollama server');
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
throw new Error(`Failed to connect to Ollama server at ${this.baseUrl}: ${error.message}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async stop() {
|
|
141
|
-
await super.stop();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public async chatStream(input: ReadableStream<Uint8Array>): Promise<ReadableStream<string>> {
|
|
145
|
-
// Create a TextDecoder to handle incoming chunks
|
|
146
|
-
const decoder = new TextDecoder();
|
|
147
|
-
let buffer = '';
|
|
148
|
-
let currentMessage: { role: string; content: string; } | null = null;
|
|
149
|
-
|
|
150
|
-
// Create a TransformStream to process the input
|
|
151
|
-
const transform = new TransformStream<Uint8Array, string>({
|
|
152
|
-
transform: async (chunk, controller) => {
|
|
153
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
154
|
-
|
|
155
|
-
// Try to parse complete JSON messages from the buffer
|
|
156
|
-
while (true) {
|
|
157
|
-
const newlineIndex = buffer.indexOf('\n');
|
|
158
|
-
if (newlineIndex === -1) break;
|
|
159
|
-
|
|
160
|
-
const line = buffer.slice(0, newlineIndex);
|
|
161
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
162
|
-
|
|
163
|
-
if (line.trim()) {
|
|
164
|
-
try {
|
|
165
|
-
const message = JSON.parse(line);
|
|
166
|
-
currentMessage = {
|
|
167
|
-
role: message.role || 'user',
|
|
168
|
-
content: message.content || '',
|
|
169
|
-
};
|
|
170
|
-
} catch (e) {
|
|
171
|
-
console.error('Failed to parse message:', e);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// If we have a complete message, send it to Ollama
|
|
177
|
-
if (currentMessage) {
|
|
178
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
179
|
-
method: 'POST',
|
|
180
|
-
headers: {
|
|
181
|
-
'Content-Type': 'application/json',
|
|
182
|
-
},
|
|
183
|
-
body: JSON.stringify({
|
|
184
|
-
model: this.model,
|
|
185
|
-
messages: [{ role: currentMessage.role, content: currentMessage.content }],
|
|
186
|
-
stream: true,
|
|
187
|
-
}),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Process each chunk from Ollama
|
|
191
|
-
const reader = response.body?.getReader();
|
|
192
|
-
if (reader) {
|
|
193
|
-
try {
|
|
194
|
-
while (true) {
|
|
195
|
-
const { done, value } = await reader.read();
|
|
196
|
-
if (done) break;
|
|
197
|
-
|
|
198
|
-
const chunk = new TextDecoder().decode(value);
|
|
199
|
-
const lines = chunk.split('\n');
|
|
200
|
-
|
|
201
|
-
for (const line of lines) {
|
|
202
|
-
if (line.trim()) {
|
|
203
|
-
try {
|
|
204
|
-
const parsed = JSON.parse(line);
|
|
205
|
-
const content = parsed.message?.content;
|
|
206
|
-
if (content) {
|
|
207
|
-
controller.enqueue(content);
|
|
208
|
-
}
|
|
209
|
-
} catch (e) {
|
|
210
|
-
console.error('Failed to parse Ollama response:', e);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} finally {
|
|
216
|
-
reader.releaseLock();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
currentMessage = null;
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
flush(controller) {
|
|
225
|
-
if (buffer) {
|
|
226
|
-
try {
|
|
227
|
-
const message = JSON.parse(buffer);
|
|
228
|
-
controller.enqueue(message.content || '');
|
|
229
|
-
} catch (e) {
|
|
230
|
-
console.error('Failed to parse remaining buffer:', e);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Connect the input to our transform stream
|
|
237
|
-
return input.pipeThrough(transform);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Implementing the synchronous chat interaction
|
|
241
|
-
public async chat(optionsArg: ChatOptions): Promise<ChatResponse> {
|
|
242
|
-
// Format messages for Ollama
|
|
243
|
-
const historyMessages = optionsArg.messageHistory.map((msg) => {
|
|
244
|
-
const formatted: { role: string; content: string; images?: string[]; reasoning?: string } = {
|
|
245
|
-
role: msg.role,
|
|
246
|
-
content: msg.content,
|
|
247
|
-
};
|
|
248
|
-
if (msg.images && msg.images.length > 0) {
|
|
249
|
-
formatted.images = msg.images;
|
|
250
|
-
}
|
|
251
|
-
if (msg.reasoning) {
|
|
252
|
-
formatted.reasoning = msg.reasoning;
|
|
253
|
-
}
|
|
254
|
-
return formatted;
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Build user message with optional images
|
|
258
|
-
const userMessage: { role: string; content: string; images?: string[] } = {
|
|
259
|
-
role: 'user',
|
|
260
|
-
content: optionsArg.userMessage,
|
|
261
|
-
};
|
|
262
|
-
if (optionsArg.images && optionsArg.images.length > 0) {
|
|
263
|
-
userMessage.images = optionsArg.images;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const messages = [
|
|
267
|
-
{ role: 'system', content: optionsArg.systemMessage },
|
|
268
|
-
...historyMessages,
|
|
269
|
-
userMessage,
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
// Build request body - include think parameter if set
|
|
273
|
-
const requestBody: Record<string, unknown> = {
|
|
274
|
-
model: this.model,
|
|
275
|
-
messages: messages,
|
|
276
|
-
stream: false,
|
|
277
|
-
options: this.defaultOptions,
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
// Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
|
|
281
|
-
if (this.defaultOptions.think !== undefined) {
|
|
282
|
-
requestBody.think = this.defaultOptions.think;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Make API call to Ollama with defaultOptions and timeout
|
|
286
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
287
|
-
method: 'POST',
|
|
288
|
-
headers: {
|
|
289
|
-
'Content-Type': 'application/json',
|
|
290
|
-
},
|
|
291
|
-
body: JSON.stringify(requestBody),
|
|
292
|
-
signal: AbortSignal.timeout(this.defaultTimeout),
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
if (!response.ok) {
|
|
296
|
-
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const result = await response.json();
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
role: 'assistant' as const,
|
|
303
|
-
message: result.message.content,
|
|
304
|
-
reasoning: result.message.thinking || result.message.reasoning,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Streaming chat with token callback (implements MultiModalModel interface)
|
|
310
|
-
* Calls onToken for each token generated during the response
|
|
311
|
-
*/
|
|
312
|
-
public async chatStreaming(optionsArg: StreamingChatOptions): Promise<ChatResponse> {
|
|
313
|
-
const onToken = optionsArg.onToken;
|
|
314
|
-
|
|
315
|
-
// Use existing collectStreamResponse with callback, including images
|
|
316
|
-
const response = await this.collectStreamResponse(
|
|
317
|
-
{
|
|
318
|
-
systemMessage: optionsArg.systemMessage,
|
|
319
|
-
userMessage: optionsArg.userMessage,
|
|
320
|
-
messageHistory: optionsArg.messageHistory,
|
|
321
|
-
images: optionsArg.images,
|
|
322
|
-
},
|
|
323
|
-
(chunk) => {
|
|
324
|
-
if (onToken) {
|
|
325
|
-
if (chunk.thinking) onToken(chunk.thinking);
|
|
326
|
-
if (chunk.content) onToken(chunk.content);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
role: 'assistant' as const,
|
|
333
|
-
message: response.message,
|
|
334
|
-
reasoning: response.thinking,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Streaming chat with async iteration and options support
|
|
340
|
-
*/
|
|
341
|
-
public async chatStreamResponse(
|
|
342
|
-
optionsArg: IOllamaChatOptions
|
|
343
|
-
): Promise<AsyncIterable<IOllamaStreamChunk>> {
|
|
344
|
-
const model = optionsArg.model || this.model;
|
|
345
|
-
const timeout = optionsArg.timeout || this.defaultTimeout;
|
|
346
|
-
const modelOptions = { ...this.defaultOptions, ...optionsArg.options };
|
|
347
|
-
|
|
348
|
-
// Format history messages with optional images and reasoning
|
|
349
|
-
const historyMessages = optionsArg.messageHistory.map((msg) => {
|
|
350
|
-
const formatted: { role: string; content: string; images?: string[]; reasoning?: string } = {
|
|
351
|
-
role: msg.role,
|
|
352
|
-
content: msg.content,
|
|
353
|
-
};
|
|
354
|
-
if (msg.images && msg.images.length > 0) {
|
|
355
|
-
formatted.images = msg.images;
|
|
356
|
-
}
|
|
357
|
-
if (msg.reasoning) {
|
|
358
|
-
formatted.reasoning = msg.reasoning;
|
|
359
|
-
}
|
|
360
|
-
return formatted;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Build user message with optional images
|
|
364
|
-
const userMessage: { role: string; content: string; images?: string[] } = {
|
|
365
|
-
role: 'user',
|
|
366
|
-
content: optionsArg.userMessage,
|
|
367
|
-
};
|
|
368
|
-
if (optionsArg.images && optionsArg.images.length > 0) {
|
|
369
|
-
userMessage.images = optionsArg.images;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const messages = [
|
|
373
|
-
{ role: 'system', content: optionsArg.systemMessage },
|
|
374
|
-
...historyMessages,
|
|
375
|
-
userMessage,
|
|
376
|
-
];
|
|
377
|
-
|
|
378
|
-
// Build request body with optional tools and think parameters
|
|
379
|
-
const requestBody: Record<string, unknown> = {
|
|
380
|
-
model,
|
|
381
|
-
messages,
|
|
382
|
-
stream: true,
|
|
383
|
-
options: modelOptions,
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
// Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
|
|
387
|
-
if (modelOptions.think !== undefined) {
|
|
388
|
-
requestBody.think = modelOptions.think;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Add tools for native function calling
|
|
392
|
-
if (optionsArg.tools && optionsArg.tools.length > 0) {
|
|
393
|
-
requestBody.tools = optionsArg.tools;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
397
|
-
method: 'POST',
|
|
398
|
-
headers: { 'Content-Type': 'application/json' },
|
|
399
|
-
body: JSON.stringify(requestBody),
|
|
400
|
-
signal: AbortSignal.timeout(timeout),
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
if (!response.ok) {
|
|
404
|
-
throw new Error(`Ollama API error: ${response.status}`);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const reader = response.body!.getReader();
|
|
408
|
-
const decoder = new TextDecoder();
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
[Symbol.asyncIterator]: async function* () {
|
|
412
|
-
let buffer = '';
|
|
413
|
-
try {
|
|
414
|
-
while (true) {
|
|
415
|
-
const { done, value } = await reader.read();
|
|
416
|
-
if (done) break;
|
|
417
|
-
buffer += decoder.decode(value, { stream: true });
|
|
418
|
-
const lines = buffer.split('\n');
|
|
419
|
-
buffer = lines.pop() || '';
|
|
420
|
-
for (const line of lines) {
|
|
421
|
-
if (!line.trim()) continue;
|
|
422
|
-
try {
|
|
423
|
-
const json = JSON.parse(line);
|
|
424
|
-
|
|
425
|
-
// Parse tool_calls from response
|
|
426
|
-
let toolCalls: IOllamaToolCall[] | undefined;
|
|
427
|
-
if (json.message?.tool_calls && Array.isArray(json.message.tool_calls)) {
|
|
428
|
-
toolCalls = json.message.tool_calls.map((tc: any) => ({
|
|
429
|
-
function: {
|
|
430
|
-
name: tc.function?.name || '',
|
|
431
|
-
arguments: typeof tc.function?.arguments === 'string'
|
|
432
|
-
? JSON.parse(tc.function.arguments)
|
|
433
|
-
: tc.function?.arguments || {},
|
|
434
|
-
index: tc.index,
|
|
435
|
-
},
|
|
436
|
-
}));
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
yield {
|
|
440
|
-
content: json.message?.content || '',
|
|
441
|
-
thinking: json.message?.thinking,
|
|
442
|
-
toolCalls,
|
|
443
|
-
done: json.done || false,
|
|
444
|
-
stats: json.done ? {
|
|
445
|
-
totalDuration: json.total_duration,
|
|
446
|
-
evalCount: json.eval_count,
|
|
447
|
-
} : undefined,
|
|
448
|
-
} as IOllamaStreamChunk;
|
|
449
|
-
} catch { /* skip malformed */ }
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
} finally {
|
|
453
|
-
reader.releaseLock();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Stream and collect full response with optional progress callback
|
|
461
|
-
*/
|
|
462
|
-
public async collectStreamResponse(
|
|
463
|
-
optionsArg: IOllamaChatOptions,
|
|
464
|
-
onChunk?: (chunk: IOllamaStreamChunk) => void
|
|
465
|
-
): Promise<IOllamaChatResponse> {
|
|
466
|
-
const stream = await this.chatStreamResponse(optionsArg);
|
|
467
|
-
let content = '';
|
|
468
|
-
let thinking = '';
|
|
469
|
-
let toolCalls: IOllamaToolCall[] = [];
|
|
470
|
-
let stats: IOllamaChatResponse['stats'];
|
|
471
|
-
|
|
472
|
-
for await (const chunk of stream) {
|
|
473
|
-
if (chunk.content) content += chunk.content;
|
|
474
|
-
if (chunk.thinking) thinking += chunk.thinking;
|
|
475
|
-
if (chunk.toolCalls) toolCalls = toolCalls.concat(chunk.toolCalls);
|
|
476
|
-
if (chunk.stats) stats = chunk.stats;
|
|
477
|
-
if (onChunk) onChunk(chunk);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
role: 'assistant' as const,
|
|
482
|
-
message: content,
|
|
483
|
-
thinking: thinking || undefined,
|
|
484
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
485
|
-
stats,
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Non-streaming chat with full options support
|
|
491
|
-
*/
|
|
492
|
-
public async chatWithOptions(optionsArg: IOllamaChatOptions): Promise<IOllamaChatResponse> {
|
|
493
|
-
const model = optionsArg.model || this.model;
|
|
494
|
-
const timeout = optionsArg.timeout || this.defaultTimeout;
|
|
495
|
-
const modelOptions = { ...this.defaultOptions, ...optionsArg.options };
|
|
496
|
-
|
|
497
|
-
// Format history messages with optional images, reasoning, and tool role
|
|
498
|
-
const historyMessages = optionsArg.messageHistory.map((msg) => {
|
|
499
|
-
// Handle tool result messages
|
|
500
|
-
if ((msg as any).role === 'tool') {
|
|
501
|
-
return {
|
|
502
|
-
role: 'tool',
|
|
503
|
-
content: msg.content,
|
|
504
|
-
tool_name: (msg as any).toolName,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const formatted: { role: string; content: string; images?: string[]; reasoning?: string } = {
|
|
509
|
-
role: msg.role,
|
|
510
|
-
content: msg.content,
|
|
511
|
-
};
|
|
512
|
-
if (msg.images && msg.images.length > 0) {
|
|
513
|
-
formatted.images = msg.images;
|
|
514
|
-
}
|
|
515
|
-
if (msg.reasoning) {
|
|
516
|
-
formatted.reasoning = msg.reasoning;
|
|
517
|
-
}
|
|
518
|
-
return formatted;
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
// Build user message with optional images
|
|
522
|
-
const userMessage: { role: string; content: string; images?: string[] } = {
|
|
523
|
-
role: 'user',
|
|
524
|
-
content: optionsArg.userMessage,
|
|
525
|
-
};
|
|
526
|
-
if (optionsArg.images && optionsArg.images.length > 0) {
|
|
527
|
-
userMessage.images = optionsArg.images;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const messages = [
|
|
531
|
-
{ role: 'system', content: optionsArg.systemMessage },
|
|
532
|
-
...historyMessages,
|
|
533
|
-
userMessage,
|
|
534
|
-
];
|
|
535
|
-
|
|
536
|
-
// Build request body with optional tools and think parameters
|
|
537
|
-
const requestBody: Record<string, unknown> = {
|
|
538
|
-
model,
|
|
539
|
-
messages,
|
|
540
|
-
stream: false,
|
|
541
|
-
options: modelOptions,
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
// Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
|
|
545
|
-
if (modelOptions.think !== undefined) {
|
|
546
|
-
requestBody.think = modelOptions.think;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Add tools for native function calling
|
|
550
|
-
if (optionsArg.tools && optionsArg.tools.length > 0) {
|
|
551
|
-
requestBody.tools = optionsArg.tools;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
555
|
-
method: 'POST',
|
|
556
|
-
headers: { 'Content-Type': 'application/json' },
|
|
557
|
-
body: JSON.stringify(requestBody),
|
|
558
|
-
signal: AbortSignal.timeout(timeout),
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
if (!response.ok) {
|
|
562
|
-
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const result = await response.json();
|
|
566
|
-
|
|
567
|
-
// Parse tool_calls from response
|
|
568
|
-
let toolCalls: IOllamaToolCall[] | undefined;
|
|
569
|
-
if (result.message?.tool_calls && Array.isArray(result.message.tool_calls)) {
|
|
570
|
-
toolCalls = result.message.tool_calls.map((tc: any) => ({
|
|
571
|
-
function: {
|
|
572
|
-
name: tc.function?.name || '',
|
|
573
|
-
arguments: typeof tc.function?.arguments === 'string'
|
|
574
|
-
? JSON.parse(tc.function.arguments)
|
|
575
|
-
: tc.function?.arguments || {},
|
|
576
|
-
index: tc.index,
|
|
577
|
-
},
|
|
578
|
-
}));
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return {
|
|
582
|
-
role: 'assistant' as const,
|
|
583
|
-
message: result.message.content || '',
|
|
584
|
-
thinking: result.message.thinking,
|
|
585
|
-
toolCalls,
|
|
586
|
-
stats: {
|
|
587
|
-
totalDuration: result.total_duration,
|
|
588
|
-
evalCount: result.eval_count,
|
|
589
|
-
},
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
public async audio(optionsArg: { message: string }): Promise<NodeJS.ReadableStream> {
|
|
594
|
-
throw new Error('Audio generation is not supported by Ollama.');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
public async vision(optionsArg: { image: Buffer; prompt: string }): Promise<string> {
|
|
598
|
-
const base64Image = optionsArg.image.toString('base64');
|
|
599
|
-
|
|
600
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
601
|
-
method: 'POST',
|
|
602
|
-
headers: {
|
|
603
|
-
'Content-Type': 'application/json',
|
|
604
|
-
},
|
|
605
|
-
body: JSON.stringify({
|
|
606
|
-
model: this.visionModel,
|
|
607
|
-
messages: [{
|
|
608
|
-
role: 'user',
|
|
609
|
-
content: optionsArg.prompt,
|
|
610
|
-
images: [base64Image]
|
|
611
|
-
}],
|
|
612
|
-
stream: false
|
|
613
|
-
}),
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
if (!response.ok) {
|
|
617
|
-
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const result = await response.json();
|
|
621
|
-
return result.message.content;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
public async document(optionsArg: {
|
|
625
|
-
systemMessage: string;
|
|
626
|
-
userMessage: string;
|
|
627
|
-
pdfDocuments: Uint8Array[];
|
|
628
|
-
messageHistory: ChatMessage[];
|
|
629
|
-
}): Promise<{ message: any }> {
|
|
630
|
-
// Ensure SmartPdf is initialized before processing documents
|
|
631
|
-
await this.ensureSmartpdfReady();
|
|
632
|
-
|
|
633
|
-
// Convert PDF documents to images using SmartPDF
|
|
634
|
-
let documentImageBytesArray: Uint8Array[] = [];
|
|
635
|
-
|
|
636
|
-
for (const pdfDocument of optionsArg.pdfDocuments) {
|
|
637
|
-
const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
|
|
638
|
-
documentImageBytesArray = documentImageBytesArray.concat(documentImageArray);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Convert images to base64
|
|
642
|
-
const base64Images = documentImageBytesArray.map(bytes => Buffer.from(bytes).toString('base64'));
|
|
643
|
-
|
|
644
|
-
// Send request to Ollama with images
|
|
645
|
-
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
646
|
-
method: 'POST',
|
|
647
|
-
headers: {
|
|
648
|
-
'Content-Type': 'application/json',
|
|
649
|
-
},
|
|
650
|
-
body: JSON.stringify({
|
|
651
|
-
model: this.visionModel,
|
|
652
|
-
messages: [
|
|
653
|
-
{ role: 'system', content: optionsArg.systemMessage },
|
|
654
|
-
...optionsArg.messageHistory,
|
|
655
|
-
{
|
|
656
|
-
role: 'user',
|
|
657
|
-
content: optionsArg.userMessage,
|
|
658
|
-
images: base64Images
|
|
659
|
-
}
|
|
660
|
-
],
|
|
661
|
-
stream: false
|
|
662
|
-
}),
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
if (!response.ok) {
|
|
666
|
-
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const result = await response.json();
|
|
670
|
-
return {
|
|
671
|
-
message: {
|
|
672
|
-
role: 'assistant',
|
|
673
|
-
content: result.message.content
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
|
679
|
-
throw new Error('Research capabilities are not yet supported by Ollama provider.');
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* Image generation is not supported by Ollama
|
|
684
|
-
*/
|
|
685
|
-
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
|
686
|
-
throw new Error('Image generation is not supported by Ollama. Please use OpenAI provider for image generation.');
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Image editing is not supported by Ollama
|
|
691
|
-
*/
|
|
692
|
-
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
|
693
|
-
throw new Error('Image editing is not supported by Ollama. Please use OpenAI provider for image editing.');
|
|
694
|
-
}
|
|
695
|
-
}
|