1
1
/**
2
- * Ollama provider implementation
2
+ * Ollama provider implementation using the official Ollama npm package
3
3
*/
4
4
5
+ import ollama , { Ollama , ChatResponse , Tool } from 'ollama' ;
5
6
import { TokenUsage } from '../../tokens.js' ;
6
7
import { LLMProvider } from '../provider.js' ;
7
8
import {
9
+ FunctionDefinition ,
8
10
GenerateOptions ,
9
11
LLMResponse ,
10
12
Message ,
@@ -19,29 +21,26 @@ export interface OllamaOptions extends ProviderOptions {
19
21
}
20
22
21
23
/**
22
- * Ollama provider implementation
24
+ * Ollama provider implementation using the official Ollama npm package
23
25
*/
24
26
export class OllamaProvider implements LLMProvider {
25
27
name : string = 'ollama' ;
26
28
provider : string = 'ollama.chat' ;
27
29
model : string ;
28
- private baseUrl : string ;
30
+ private client : Ollama ;
29
31
30
32
constructor ( model : string , options : OllamaOptions = { } ) {
31
33
this . model = model ;
32
- this . baseUrl =
33
- options . baseUrl ||
34
- process . env . OLLAMA_BASE_URL ||
34
+ const baseUrl =
35
+ options . baseUrl ||
36
+ process . env . OLLAMA_BASE_URL ||
35
37
'http://localhost:11434' ;
36
38
37
- // Ensure baseUrl doesn't end with a slash
38
- if ( this . baseUrl . endsWith ( '/' ) ) {
39
- this . baseUrl = this . baseUrl . slice ( 0 , - 1 ) ;
40
- }
39
+ this . client = new Ollama ( { host : baseUrl } ) ;
41
40
}
42
41
43
42
/**
44
- * Generate text using Ollama API
43
+ * Generate text using Ollama API via the official npm package
45
44
*/
46
45
async generateText ( options : GenerateOptions ) : Promise < LLMResponse > {
47
46
const {
@@ -52,75 +51,55 @@ export class OllamaProvider implements LLMProvider {
52
51
topP,
53
52
frequencyPenalty,
54
53
presencePenalty,
54
+ stopSequences,
55
55
} = options ;
56
56
57
57
// Format messages for Ollama API
58
58
const formattedMessages = this . formatMessages ( messages ) ;
59
59
60
60
try {
61
- // Prepare request options
62
- const requestOptions : any = {
61
+ // Prepare chat options
62
+ const ollamaOptions : Record < string , any > = {
63
+ temperature,
64
+ } ;
65
+
66
+ // Add optional parameters if provided
67
+ if ( topP !== undefined ) ollamaOptions . top_p = topP ;
68
+ if ( frequencyPenalty !== undefined ) ollamaOptions . frequency_penalty = frequencyPenalty ;
69
+ if ( presencePenalty !== undefined ) ollamaOptions . presence_penalty = presencePenalty ;
70
+ if ( maxTokens !== undefined ) ollamaOptions . num_predict = maxTokens ;
71
+ if ( stopSequences && stopSequences . length > 0 ) ollamaOptions . stop = stopSequences ;
72
+
73
+ // Prepare request parameters
74
+ const requestParams : any = {
63
75
model : this . model ,
64
76
messages : formattedMessages ,
65
77
stream : false ,
66
- options : {
67
- temperature : temperature ,
68
- // Ollama uses top_k instead of top_p, but we'll include top_p if provided
69
- ...( topP !== undefined && { top_p : topP } ) ,
70
- ...( frequencyPenalty !== undefined && {
71
- frequency_penalty : frequencyPenalty ,
72
- } ) ,
73
- ...( presencePenalty !== undefined && {
74
- presence_penalty : presencePenalty ,
75
- } ) ,
76
- } ,
78
+ options : ollamaOptions ,
77
79
} ;
78
80
79
- // Add max_tokens if provided
80
- if ( maxTokens !== undefined ) {
81
- requestOptions . options . num_predict = maxTokens ;
82
- }
83
-
84
81
// Add functions/tools if provided
85
82
if ( functions && functions . length > 0 ) {
86
- requestOptions . tools = functions . map ( ( fn ) => ( {
87
- name : fn . name ,
88
- description : fn . description ,
89
- parameters : fn . parameters ,
90
- } ) ) ;
91
- }
92
-
93
- // Make the API request
94
- const response = await fetch ( `${ this . baseUrl } /api/chat` , {
95
- method : 'POST' ,
96
- headers : {
97
- 'Content-Type' : 'application/json' ,
98
- } ,
99
- body : JSON . stringify ( requestOptions ) ,
100
- } ) ;
101
-
102
- if ( ! response . ok ) {
103
- const errorText = await response . text ( ) ;
104
- throw new Error ( `Ollama API error: ${ response . status } ${ errorText } ` ) ;
83
+ requestParams . tools = this . convertFunctionsToTools ( functions ) ;
105
84
}
106
85
107
- const data = await response . json ( ) ;
86
+ // Make the API request using the Ollama client
87
+ const response = await this . client . chat ( requestParams ) ;
108
88
109
- // Extract content and tool calls
110
- const content = data . message ?. content || '' ;
111
- const toolCalls =
112
- data . message ?. tool_calls ?. map ( ( toolCall : any ) => ( {
113
- id :
114
- toolCall . id ||
115
- `tool-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) } ` ,
116
- name : toolCall . name ,
117
- content : JSON . stringify ( toolCall . args || toolCall . arguments || { } ) ,
118
- } ) ) || [ ] ;
89
+ // Extract content from response
90
+ const content = response . message ?. content || '' ;
91
+
92
+ // Process tool calls if present
93
+ const toolCalls = this . processToolCalls ( response ) ;
119
94
120
95
// Create token usage from response data
121
96
const tokenUsage = new TokenUsage ( ) ;
122
- tokenUsage . input = data . prompt_eval_count || 0 ;
123
- tokenUsage . output = data . eval_count || 0 ;
97
+ if ( response . prompt_eval_count ) {
98
+ tokenUsage . input = response . prompt_eval_count ;
99
+ }
100
+ if ( response . eval_count ) {
101
+ tokenUsage . output = response . eval_count ;
102
+ }
124
103
125
104
return {
126
105
text : content ,
@@ -132,6 +111,37 @@ export class OllamaProvider implements LLMProvider {
132
111
}
133
112
}
134
113
114
+ /**
115
+ * Convert our FunctionDefinition format to Ollama's Tool format
116
+ */
117
+ private convertFunctionsToTools ( functions : FunctionDefinition [ ] ) : Tool [ ] {
118
+ return functions . map ( ( fn ) => ( {
119
+ type : 'function' ,
120
+ function : {
121
+ name : fn . name ,
122
+ description : fn . description ,
123
+ parameters : fn . parameters ,
124
+ }
125
+ } ) ) ;
126
+ }
127
+
128
+ /**
129
+ * Process tool calls from the Ollama response
130
+ */
131
+ private processToolCalls ( response : ChatResponse ) : any [ ] {
132
+ if ( ! response . message ?. tool_calls || response . message . tool_calls . length === 0 ) {
133
+ return [ ] ;
134
+ }
135
+
136
+ return response . message . tool_calls . map ( ( toolCall ) => ( {
137
+ id : toolCall . function ?. name
138
+ ? `tool-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) } `
139
+ : toolCall . id ,
140
+ name : toolCall . function ?. name ,
141
+ content : JSON . stringify ( toolCall . function ?. arguments || { } ) ,
142
+ } ) ) ;
143
+ }
144
+
135
145
/**
136
146
* Format messages for Ollama API
137
147
*/
@@ -161,8 +171,10 @@ export class OllamaProvider implements LLMProvider {
161
171
tool_calls : [
162
172
{
163
173
id : msg . id ,
164
- name : msg . name ,
165
- arguments : msg . content ,
174
+ function : {
175
+ name : msg . name ,
176
+ arguments : msg . content ,
177
+ }
166
178
} ,
167
179
] ,
168
180
} ;
@@ -174,4 +186,4 @@ export class OllamaProvider implements LLMProvider {
174
186
} ;
175
187
} ) ;
176
188
}
177
- }
189
+ }
0 commit comments