@holokai/holo-provider-ollama 0.1.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/LICENSE +21 -0
- package/README.md +750 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +136 -0
- package/dist/manifest.js.map +1 -0
- package/dist/ollama.auditor.d.ts +22 -0
- package/dist/ollama.auditor.d.ts.map +1 -0
- package/dist/ollama.auditor.js +151 -0
- package/dist/ollama.auditor.js.map +1 -0
- package/dist/ollama.provider.d.ts +14 -0
- package/dist/ollama.provider.d.ts.map +1 -0
- package/dist/ollama.provider.js +88 -0
- package/dist/ollama.provider.js.map +1 -0
- package/dist/ollama.response.factory.d.ts +7 -0
- package/dist/ollama.response.factory.d.ts.map +1 -0
- package/dist/ollama.response.factory.js +9 -0
- package/dist/ollama.response.factory.js.map +1 -0
- package/dist/ollama.translator.d.ts +22 -0
- package/dist/ollama.translator.d.ts.map +1 -0
- package/dist/ollama.translator.js +90 -0
- package/dist/ollama.translator.js.map +1 -0
- package/dist/ollama.wire.adapter.d.ts +6 -0
- package/dist/ollama.wire.adapter.d.ts.map +1 -0
- package/dist/ollama.wire.adapter.js +11 -0
- package/dist/ollama.wire.adapter.js.map +1 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +57 -0
- package/dist/plugin.js.map +1 -0
- package/dist/translators/index.d.ts +9 -0
- package/dist/translators/index.d.ts.map +1 -0
- package/dist/translators/index.js +9 -0
- package/dist/translators/index.js.map +1 -0
- package/dist/translators/ollama.chat.request.translator.d.ts +20 -0
- package/dist/translators/ollama.chat.request.translator.d.ts.map +1 -0
- package/dist/translators/ollama.chat.request.translator.js +100 -0
- package/dist/translators/ollama.chat.request.translator.js.map +1 -0
- package/dist/translators/ollama.chat.response.translator.d.ts +22 -0
- package/dist/translators/ollama.chat.response.translator.d.ts.map +1 -0
- package/dist/translators/ollama.chat.response.translator.js +127 -0
- package/dist/translators/ollama.chat.response.translator.js.map +1 -0
- package/dist/translators/ollama.generate.request.translator.d.ts +13 -0
- package/dist/translators/ollama.generate.request.translator.d.ts.map +1 -0
- package/dist/translators/ollama.generate.request.translator.js +90 -0
- package/dist/translators/ollama.generate.request.translator.js.map +1 -0
- package/dist/translators/ollama.generate.response.translator.d.ts +20 -0
- package/dist/translators/ollama.generate.response.translator.d.ts.map +1 -0
- package/dist/translators/ollama.generate.response.translator.js +104 -0
- package/dist/translators/ollama.generate.response.translator.js.map +1 -0
- package/dist/translators/ollama.message.translators.d.ts +14 -0
- package/dist/translators/ollama.message.translators.d.ts.map +1 -0
- package/dist/translators/ollama.message.translators.js +117 -0
- package/dist/translators/ollama.message.translators.js.map +1 -0
- package/dist/translators/ollama.options.translators.d.ts +11 -0
- package/dist/translators/ollama.options.translators.d.ts.map +1 -0
- package/dist/translators/ollama.options.translators.js +37 -0
- package/dist/translators/ollama.options.translators.js.map +1 -0
- package/dist/translators/ollama.tool.translators.d.ts +13 -0
- package/dist/translators/ollama.tool.translators.d.ts.map +1 -0
- package/dist/translators/ollama.tool.translators.js +62 -0
- package/dist/translators/ollama.tool.translators.js.map +1 -0
- package/dist/translators/streaming/index.d.ts +5 -0
- package/dist/translators/streaming/index.d.ts.map +1 -0
- package/dist/translators/streaming/index.js +5 -0
- package/dist/translators/streaming/index.js.map +1 -0
- package/dist/translators/streaming/ollama.content.delta.translator.d.ts +14 -0
- package/dist/translators/streaming/ollama.content.delta.translator.d.ts.map +1 -0
- package/dist/translators/streaming/ollama.content.delta.translator.js +76 -0
- package/dist/translators/streaming/ollama.content.delta.translator.js.map +1 -0
- package/dist/translators/streaming/ollama.message.delta.translator.d.ts +15 -0
- package/dist/translators/streaming/ollama.message.delta.translator.d.ts.map +1 -0
- package/dist/translators/streaming/ollama.message.delta.translator.js +116 -0
- package/dist/translators/streaming/ollama.message.delta.translator.js.map +1 -0
- package/dist/translators/streaming/ollama.message.stop.translator.d.ts +16 -0
- package/dist/translators/streaming/ollama.message.stop.translator.d.ts.map +1 -0
- package/dist/translators/streaming/ollama.message.stop.translator.js +87 -0
- package/dist/translators/streaming/ollama.message.stop.translator.js.map +1 -0
- package/dist/translators/streaming/ollama.stream.translator.d.ts +20 -0
- package/dist/translators/streaming/ollama.stream.translator.d.ts.map +1 -0
- package/dist/translators/streaming/ollama.stream.translator.js +82 -0
- package/dist/translators/streaming/ollama.stream.translator.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/request.types.d.ts +13 -0
- package/dist/types/request.types.d.ts.map +1 -0
- package/dist/types/request.types.js +8 -0
- package/dist/types/request.types.js.map +1 -0
- package/dist/types/response.types.d.ts +40 -0
- package/dist/types/response.types.d.ts.map +1 -0
- package/dist/types/response.types.js +9 -0
- package/dist/types/response.types.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
# @holokai/holo-provider-ollama
|
|
2
|
+
|
|
3
|
+
> **Official Ollama provider plugin for Holo LLM Gateway**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@holokai/holo-provider-ollama)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The Ollama provider plugin enables Holo to communicate with locally-hosted Ollama models through the universal Holo format. This plugin is part of the migration from the monolithic provider architecture to a plugin-based system, providing complete bidirectional translation between Ollama's native API and the portable Holo format.
|
|
13
|
+
|
|
14
|
+
### Key Features
|
|
15
|
+
|
|
16
|
+
- ✅ **Full Holo SDK Integration** - Uses `@holokai/sdk` types for strict type safety
|
|
17
|
+
- ✅ **Bidirectional Translation** - Ollama ↔ Holo format with lossless core fields
|
|
18
|
+
- ✅ **Dual Mode Support** - Both Chat and Generate endpoints
|
|
19
|
+
- ✅ **Streaming Support** - Frame-based streaming with proper orchestration
|
|
20
|
+
- ✅ **Tool Calling** - Function calling support (Chat mode only)
|
|
21
|
+
- ✅ **Vision/Multimodal** - Image support via URLs and base64
|
|
22
|
+
- ✅ **Local Deployment** - No external API dependencies
|
|
23
|
+
- ✅ **Plugin Architecture** - Auto-discovered, hot-reloadable, independently versioned
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @holokai/holo-provider-ollama
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Peer Dependencies
|
|
34
|
+
|
|
35
|
+
This plugin requires:
|
|
36
|
+
- `@holokai/sdk` ^0.1.0 - Holo universal format types and plugin contracts
|
|
37
|
+
- `ollama` ^0.6.3 - Official Ollama JavaScript SDK
|
|
38
|
+
|
|
39
|
+
### Prerequisites
|
|
40
|
+
|
|
41
|
+
- Ollama must be installed and running locally: https://ollama.com/download
|
|
42
|
+
- Default endpoint: `http://localhost:11434`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Automatic Discovery
|
|
49
|
+
|
|
50
|
+
When installed in a Holo worker environment, this plugin is automatically discovered and loaded by the plugin system. No manual registration required.
|
|
51
|
+
|
|
52
|
+
### Configuration
|
|
53
|
+
|
|
54
|
+
Add a provider configuration to your Holo deployment:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"id": "ollama-local",
|
|
59
|
+
"provider_type": "ollama",
|
|
60
|
+
"plugin_id": "@holokai/holo-provider-ollama",
|
|
61
|
+
"base_url": "http://localhost:11434",
|
|
62
|
+
"model": "llama2",
|
|
63
|
+
"config": {
|
|
64
|
+
"defaultModel": "llama2",
|
|
65
|
+
"timeoutMs": 60000
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Usage in Code
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { HoloRequest, HoloResponse } from '@holokai/sdk';
|
|
74
|
+
|
|
75
|
+
const request: HoloRequest = {
|
|
76
|
+
model: 'llama2',
|
|
77
|
+
messages: [
|
|
78
|
+
{ role: 'user', content: 'Explain quantum entanglement briefly.' }
|
|
79
|
+
],
|
|
80
|
+
max_tokens: 500,
|
|
81
|
+
temperature: 0.7
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Plugin handles translation automatically
|
|
85
|
+
const response: HoloResponse = await holoClient.chat(request);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Migration from Monolithic Architecture
|
|
91
|
+
|
|
92
|
+
### What Changed
|
|
93
|
+
|
|
94
|
+
This plugin represents the extraction of Ollama provider logic from the monolithic `src/providers/ollama/` codebase into a standalone, independently versioned package.
|
|
95
|
+
|
|
96
|
+
**Before** (Monolithic):
|
|
97
|
+
```
|
|
98
|
+
src/providers/ollama/
|
|
99
|
+
├── ollama.translator.ts
|
|
100
|
+
├── translators/
|
|
101
|
+
│ ├── chat/
|
|
102
|
+
│ └── generate/
|
|
103
|
+
├── streaming/
|
|
104
|
+
└── types/
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**After** (Plugin):
|
|
108
|
+
```
|
|
109
|
+
@holokai/holo-provider-ollama
|
|
110
|
+
├── src/
|
|
111
|
+
│ ├── plugin.ts # Plugin entrypoint
|
|
112
|
+
│ ├── manifest.ts # Plugin metadata
|
|
113
|
+
│ ├── ollama.provider.ts # Provider implementation
|
|
114
|
+
│ └── translators/ # Translation logic (preserved)
|
|
115
|
+
└── package.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Migration Benefits
|
|
119
|
+
|
|
120
|
+
1. **Independent Versioning** - Update Ollama support without core releases
|
|
121
|
+
2. **Hot Reload** - Deploy new Ollama versions without downtime
|
|
122
|
+
3. **Type Safety** - Strict SDK types eliminate `Record<string, unknown>`
|
|
123
|
+
4. **Reduced Coupling** - Plugin contracts enforce clean boundaries
|
|
124
|
+
5. **Local First** - No external API keys or dependencies
|
|
125
|
+
|
|
126
|
+
### Breaking Changes
|
|
127
|
+
|
|
128
|
+
- **Import paths changed**: Use `@holokai/sdk` for types instead of `../../types`
|
|
129
|
+
- **Configuration schema**: Now validated via plugin manifest
|
|
130
|
+
- **Dependency injection**: Uses plugin container instead of core DI
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Architecture
|
|
135
|
+
|
|
136
|
+
### Plugin Structure
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
@holokai/holo-provider-ollama/
|
|
140
|
+
├── src/
|
|
141
|
+
│ ├── plugin.ts # ProviderPlugin implementation
|
|
142
|
+
│ ├── manifest.ts # Plugin metadata & config schema
|
|
143
|
+
│ ├── ollama.provider.ts # Core provider logic
|
|
144
|
+
│ ├── ollama.translator.ts # Main translator facade
|
|
145
|
+
│ ├── translators/
|
|
146
|
+
│ │ ├── ollama.chat.request.translator.ts
|
|
147
|
+
│ │ ├── ollama.chat.response.translator.ts
|
|
148
|
+
│ │ ├── ollama.generate.request.translator.ts
|
|
149
|
+
│ │ ├── ollama.generate.response.translator.ts
|
|
150
|
+
│ │ ├── ollama.message.translator.ts
|
|
151
|
+
│ │ └── streaming/
|
|
152
|
+
│ │ ├── ollama.stream.translator.ts # Orchestrator
|
|
153
|
+
│ │ ├── ollama.content.delta.translator.ts
|
|
154
|
+
│ │ ├── ollama.message.delta.translator.ts
|
|
155
|
+
│ │ └── ollama.message.stop.translator.ts
|
|
156
|
+
│ ├── types/
|
|
157
|
+
│ │ └── (Re-exports from ollama SDK)
|
|
158
|
+
│ └── utils/
|
|
159
|
+
│ └── (Helper functions)
|
|
160
|
+
└── package.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Translation Flow
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
┌─────────────────┐
|
|
167
|
+
│ Holo Request │
|
|
168
|
+
│ (SDK types) │
|
|
169
|
+
└────────┬────────┘
|
|
170
|
+
│
|
|
171
|
+
↓
|
|
172
|
+
┌─────────────────────────┐
|
|
173
|
+
│ OllamaRequestTranslator │
|
|
174
|
+
│ - Detects mode │
|
|
175
|
+
│ - Maps to Chat/Gen │
|
|
176
|
+
│ - Nests in options │
|
|
177
|
+
└────────┬────────────────┘
|
|
178
|
+
│
|
|
179
|
+
↓
|
|
180
|
+
┌─────────────────┐
|
|
181
|
+
│ Ollama API │
|
|
182
|
+
│ (local/remote) │
|
|
183
|
+
└────────┬────────┘
|
|
184
|
+
│
|
|
185
|
+
↓
|
|
186
|
+
┌──────────────────────────┐
|
|
187
|
+
│ OllamaResponseTranslator │
|
|
188
|
+
│ - Detects mode │
|
|
189
|
+
│ - Synthesizes ID │
|
|
190
|
+
│ - Converts timestamp │
|
|
191
|
+
└────────┬─────────────────┘
|
|
192
|
+
│
|
|
193
|
+
↓
|
|
194
|
+
┌─────────────────┐
|
|
195
|
+
│ Holo Response │
|
|
196
|
+
│ (SDK types) │
|
|
197
|
+
└─────────────────┘
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Holo Format Mapping
|
|
203
|
+
|
|
204
|
+
This plugin implements the official Holo format mappings as documented in the SDK.
|
|
205
|
+
|
|
206
|
+
### Request Mapping: Holo → Ollama
|
|
207
|
+
|
|
208
|
+
| Holo Field | Ollama Field | Transformation | Notes |
|
|
209
|
+
|------------|-------------|----------------|-------|
|
|
210
|
+
| **Direct 1:1** ||||
|
|
211
|
+
| `model` | `model` | Direct | Required |
|
|
212
|
+
| `stream` | `stream` | Direct | Optional |
|
|
213
|
+
| **Structure Transforms** ||||
|
|
214
|
+
| `system` (Chat) | First message with `role:'system'` | Inject as message | Optional |
|
|
215
|
+
| `system` (Generate) | `system` | Top-level field | Optional |
|
|
216
|
+
| `messages` (Chat) | `messages` | Flatten to text + extract images | Required |
|
|
217
|
+
| `messages` (Generate) | `prompt` | Extract from single user message | Converted to string |
|
|
218
|
+
| `temperature` | `options.temperature` | Nest in options | Optional |
|
|
219
|
+
| `top_p` | `options.top_p` | Nest in options | Optional |
|
|
220
|
+
| `top_k` | `options.top_k` | Nest in options | Optional |
|
|
221
|
+
| `max_tokens` | `options.num_predict` | Nest + rename | Optional |
|
|
222
|
+
| `stop_sequences` | `options.stop` | Nest in options | Array format |
|
|
223
|
+
| `frequency_penalty` | `options.frequency_penalty` | Nest in options | Optional |
|
|
224
|
+
| `presence_penalty` | `options.presence_penalty` | Nest in options | Optional |
|
|
225
|
+
| `seed` | `options.seed` | Nest in options | Optional |
|
|
226
|
+
| `response_format.type: 'json_object'` | `format: "json"` | Map to string | Optional |
|
|
227
|
+
| `response_format.schema` | `format: {...}` | Pass schema object | Optional |
|
|
228
|
+
| `tools` (Chat) | `tools` | Direct | Chat mode only |
|
|
229
|
+
|
|
230
|
+
**Dropped Fields** (Holo → Ollama):
|
|
231
|
+
- `tool_choice` - Ollama doesn't support explicit tool selection (log warning)
|
|
232
|
+
- `service_tier` - Not applicable to local models
|
|
233
|
+
- `metadata` - Provider-specific field
|
|
234
|
+
|
|
235
|
+
**Ollama-Specific Fields** (not in Holo):
|
|
236
|
+
- `keep_alive` - Model memory duration (handled via config)
|
|
237
|
+
- `options.*` - Runtime-specific hardware options
|
|
238
|
+
- `raw` - Skip prompt formatting (Generate mode only)
|
|
239
|
+
|
|
240
|
+
### Response Mapping: Ollama → Holo
|
|
241
|
+
|
|
242
|
+
| Ollama Field | Holo Field | Transformation | Notes |
|
|
243
|
+
|-------------|------------|----------------|-------|
|
|
244
|
+
| **Direct 1:1** ||||
|
|
245
|
+
| `model` | `model` | Direct | Always present |
|
|
246
|
+
| `message.role` (Chat) | `messages[0].role` | Wrap in array | Always 'assistant' |
|
|
247
|
+
| `message.content` (Chat) | `messages[0].content` | Direct | Text content |
|
|
248
|
+
| `response` (Generate) | `messages[0].content` | Wrap in message | Text content |
|
|
249
|
+
| `message.tool_calls` | `messages[0].tool_calls` | Direct | If present |
|
|
250
|
+
| **Structure Transforms** ||||
|
|
251
|
+
| N/A | `id` | Synthesize UUID | Ollama lacks ID |
|
|
252
|
+
| `created_at` | `created` | Parse ISO8601 to ms | `Date.parse(created_at)` |
|
|
253
|
+
| `done_reason: 'stop'` | `finish_reason: 'stop'` | Direct | Optional |
|
|
254
|
+
| `done_reason: 'length'` | `finish_reason: 'length'` | Direct | Optional |
|
|
255
|
+
| `done: true` with no `done_reason` | `finish_reason: 'stop'` | Default | Fallback behavior |
|
|
256
|
+
| `prompt_eval_count` | `usage.input_tokens` | Rename | Optional |
|
|
257
|
+
| `eval_count` | `usage.output_tokens` | Rename | Optional |
|
|
258
|
+
| Computed | `usage.total_tokens` | `input + output` | Derived |
|
|
259
|
+
| `total_duration` (ns) | `usage.timings.total` | Direct | Optional; nanoseconds |
|
|
260
|
+
| `load_duration` (ns) | `usage.timings.load` | Direct | Optional; nanoseconds |
|
|
261
|
+
| `prompt_eval_duration` (ns) | `usage.timings.prompt_eval` | Direct | Optional; nanoseconds |
|
|
262
|
+
| `eval_duration` (ns) | `usage.timings.eval` | Direct | Optional; nanoseconds |
|
|
263
|
+
|
|
264
|
+
**ID Synthesis**:
|
|
265
|
+
- Ollama responses do NOT include `id` fields
|
|
266
|
+
- Translators MUST synthesize UUIDs for all responses and streaming chunks
|
|
267
|
+
- For streaming: Generate once at start, reuse across all chunks
|
|
268
|
+
|
|
269
|
+
**Timestamp Conversion**:
|
|
270
|
+
- Ollama: ISO8601 string (e.g., `"2024-01-01T12:00:00Z"`)
|
|
271
|
+
- Holo: Milliseconds since epoch (`number`)
|
|
272
|
+
- Conversion: `Date.parse(created_at)`
|
|
273
|
+
|
|
274
|
+
**Finish Reason Mapping**:
|
|
275
|
+
|
|
276
|
+
| Ollama `done_reason` | Holo `finish_reason` | Notes |
|
|
277
|
+
|---------------------|---------------------|-------|
|
|
278
|
+
| `'stop'` | `'stop'` | Natural completion |
|
|
279
|
+
| `'length'` | `'length'` | Hit token limit |
|
|
280
|
+
| `null` or missing + `done: true` | `'stop'` | Default fallback |
|
|
281
|
+
|
|
282
|
+
### Content Mapping
|
|
283
|
+
|
|
284
|
+
#### Text Content
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// Holo
|
|
288
|
+
{ type: 'text', text: 'Hello' }
|
|
289
|
+
|
|
290
|
+
// Ollama Chat (flattened)
|
|
291
|
+
{ role: 'user', content: 'Hello' }
|
|
292
|
+
|
|
293
|
+
// Ollama Generate (string)
|
|
294
|
+
{ prompt: 'Hello' }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Image Content
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Holo
|
|
301
|
+
{
|
|
302
|
+
role: 'user',
|
|
303
|
+
content: [
|
|
304
|
+
{ type: 'text', text: 'What is in this image?' },
|
|
305
|
+
{ type: 'image', url: 'https://example.com/image.png' }
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Ollama Chat (extracted to images array)
|
|
310
|
+
{
|
|
311
|
+
role: 'user',
|
|
312
|
+
content: 'What is in this image?',
|
|
313
|
+
images: ['https://example.com/image.png']
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Ollama Generate (NOT SUPPORTED)
|
|
317
|
+
// Images cannot be used in Generate mode
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Tool Calls
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Ollama Response (OpenAI-style)
|
|
324
|
+
{
|
|
325
|
+
message: {
|
|
326
|
+
role: 'assistant',
|
|
327
|
+
content: '',
|
|
328
|
+
tool_calls: [{
|
|
329
|
+
id: 'call_abc',
|
|
330
|
+
type: 'function',
|
|
331
|
+
function: { name: 'get_weather', arguments: { location: 'SF' } }
|
|
332
|
+
}]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Holo Response (direct mapping)
|
|
337
|
+
{
|
|
338
|
+
messages: [{
|
|
339
|
+
role: 'assistant',
|
|
340
|
+
content: '',
|
|
341
|
+
tool_calls: [{
|
|
342
|
+
id: 'call_abc',
|
|
343
|
+
type: 'function',
|
|
344
|
+
function: { name: 'get_weather', arguments: { location: 'SF' } }
|
|
345
|
+
}]
|
|
346
|
+
}]
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Note**: Ollama uses OpenAI-style tool call format, so mapping is direct (no extraction needed).
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Dual Mode Support
|
|
355
|
+
|
|
356
|
+
### Chat Mode vs Generate Mode
|
|
357
|
+
|
|
358
|
+
Ollama provides two distinct endpoints with different capabilities:
|
|
359
|
+
|
|
360
|
+
| Feature | Chat Mode (`/api/chat`) | Generate Mode (`/api/generate`) |
|
|
361
|
+
|---------|------------------------|--------------------------------|
|
|
362
|
+
| **Endpoint** | `/api/chat` | `/api/generate` |
|
|
363
|
+
| **Input** | `messages[]` array | `prompt` string |
|
|
364
|
+
| **Conversation** | ✅ Multi-turn history | ❌ Single prompt only |
|
|
365
|
+
| **System Prompt** | ✅ As first message | ✅ Top-level field |
|
|
366
|
+
| **Tools** | ✅ Function calling | ❌ Not supported |
|
|
367
|
+
| **Images** | ✅ Via `images[]` | ❌ Not supported |
|
|
368
|
+
| **Context Continuation** | ❌ Use message history | ✅ Via `context` array |
|
|
369
|
+
| **Use Case** | Interactive chat | Single completion |
|
|
370
|
+
|
|
371
|
+
### Mode Selection
|
|
372
|
+
|
|
373
|
+
The plugin automatically selects the appropriate mode based on the request:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Auto-detected as Chat Mode (has tools)
|
|
377
|
+
const chatRequest: HoloRequest = {
|
|
378
|
+
model: 'llama2',
|
|
379
|
+
messages: [{ role: 'user', content: 'What is the weather?' }],
|
|
380
|
+
tools: [{ name: 'get_weather', parameters: {...} }]
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Can be either mode (no tools, single message)
|
|
384
|
+
const simpleRequest: HoloRequest = {
|
|
385
|
+
model: 'llama2',
|
|
386
|
+
messages: [{ role: 'user', content: 'Complete this sentence...' }]
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Force Generate Mode via provider config
|
|
390
|
+
const generateRequest: HoloRequest = {
|
|
391
|
+
model: 'llama2',
|
|
392
|
+
messages: [{ role: 'user', content: 'Complete: Once upon a time' }],
|
|
393
|
+
provider_config: {
|
|
394
|
+
mode: 'generate' // Force Generate endpoint
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Default**: Chat mode is preferred unless explicitly configured otherwise.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Streaming
|
|
404
|
+
|
|
405
|
+
### Frame-Based Streaming
|
|
406
|
+
|
|
407
|
+
Ollama uses a simpler frame-based streaming model compared to Claude's event-based approach:
|
|
408
|
+
|
|
409
|
+
#### Streaming Frames
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// Frame 1: First content
|
|
413
|
+
{
|
|
414
|
+
model: 'llama2',
|
|
415
|
+
created_at: '2024-01-01T12:00:00Z',
|
|
416
|
+
message: { role: 'assistant', content: 'Hello' }, // Chat mode
|
|
417
|
+
// response: 'Hello', // Generate mode
|
|
418
|
+
done: false
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Frame 2: More content
|
|
422
|
+
{
|
|
423
|
+
model: 'llama2',
|
|
424
|
+
created_at: '2024-01-01T12:00:01Z',
|
|
425
|
+
message: { role: 'assistant', content: ' there' },
|
|
426
|
+
done: false
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Frame 3: Final frame with usage
|
|
430
|
+
{
|
|
431
|
+
model: 'llama2',
|
|
432
|
+
created_at: '2024-01-01T12:00:02Z',
|
|
433
|
+
message: { role: 'assistant', content: '!' },
|
|
434
|
+
done: true,
|
|
435
|
+
done_reason: 'stop',
|
|
436
|
+
prompt_eval_count: 10,
|
|
437
|
+
eval_count: 3,
|
|
438
|
+
total_duration: 1500000000 // nanoseconds
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
#### Holo Mapping
|
|
443
|
+
|
|
444
|
+
The plugin translates Ollama frames to Holo streaming events:
|
|
445
|
+
|
|
446
|
+
| Ollama Frame | Holo Event | Notes |
|
|
447
|
+
|--------------|-----------|-------|
|
|
448
|
+
| First frame (`done: false`) | `message_start` + `content_delta` | Synthesized by orchestrator |
|
|
449
|
+
| Content frames (`done: false`) | `content_delta` | Incremental text |
|
|
450
|
+
| Final frame (`done: true`) | `message_delta` + `message_stop` | Usage + completion |
|
|
451
|
+
|
|
452
|
+
### Streaming Example
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { HoloStreamChunk } from '@holokai/sdk';
|
|
456
|
+
|
|
457
|
+
const stream = await ollamaProvider.streamChat(request);
|
|
458
|
+
|
|
459
|
+
for await (const chunk: HoloStreamChunk of stream) {
|
|
460
|
+
switch (chunk.delta?.type) {
|
|
461
|
+
case 'message_start':
|
|
462
|
+
console.log('Message started:', chunk.id);
|
|
463
|
+
break;
|
|
464
|
+
case 'content_delta':
|
|
465
|
+
process.stdout.write(chunk.delta.delta.content ?? '');
|
|
466
|
+
break;
|
|
467
|
+
case 'message_delta':
|
|
468
|
+
console.log('Usage:', chunk.usage);
|
|
469
|
+
break;
|
|
470
|
+
case 'message_stop':
|
|
471
|
+
console.log('Complete. Reason:', chunk.finish_reason);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Ollama-Specific Features
|
|
480
|
+
|
|
481
|
+
### Keep Alive
|
|
482
|
+
|
|
483
|
+
Control how long models stay in memory:
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
const request: HoloRequest = {
|
|
487
|
+
model: 'llama2',
|
|
488
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
489
|
+
provider_config: {
|
|
490
|
+
keep_alive: '5m' // Keep model loaded for 5 minutes
|
|
491
|
+
// or: keep_alive: 300 // 300 seconds
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Hardware Control
|
|
497
|
+
|
|
498
|
+
Configure GPU and NUMA settings:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
const request: HoloRequest = {
|
|
502
|
+
model: 'llama2',
|
|
503
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
504
|
+
provider_config: {
|
|
505
|
+
options: {
|
|
506
|
+
num_gpu: 1, // Number of GPUs to use
|
|
507
|
+
main_gpu: 0, // Primary GPU index
|
|
508
|
+
numa: true // Enable NUMA optimization
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Context Window Override
|
|
515
|
+
|
|
516
|
+
Override model's default context size:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
const request: HoloRequest = {
|
|
520
|
+
model: 'llama2',
|
|
521
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
522
|
+
provider_config: {
|
|
523
|
+
options: {
|
|
524
|
+
num_ctx: 4096 // Override context window
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Raw Mode (Generate Only)
|
|
531
|
+
|
|
532
|
+
Skip prompt formatting in Generate mode:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
const request: HoloRequest = {
|
|
536
|
+
model: 'llama2',
|
|
537
|
+
messages: [{ role: 'user', content: 'Raw prompt text' }],
|
|
538
|
+
provider_config: {
|
|
539
|
+
mode: 'generate',
|
|
540
|
+
raw: true // Skip Ollama's prompt template
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Type Safety
|
|
548
|
+
|
|
549
|
+
### SDK Integration
|
|
550
|
+
|
|
551
|
+
This plugin uses strict SDK types exclusively:
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import type {
|
|
555
|
+
HoloRequest,
|
|
556
|
+
HoloResponse,
|
|
557
|
+
HoloMessage,
|
|
558
|
+
HoloTool,
|
|
559
|
+
HoloJsonSchema
|
|
560
|
+
} from '@holokai/sdk';
|
|
561
|
+
|
|
562
|
+
// ❌ NO: Record<string, unknown>
|
|
563
|
+
// ✅ YES: Proper SDK types
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Migration from Legacy Types
|
|
567
|
+
|
|
568
|
+
**Before** (Legacy provider):
|
|
569
|
+
```typescript
|
|
570
|
+
import { HoloTool } from '../../types/holo/requests';
|
|
571
|
+
|
|
572
|
+
interface HoloTool {
|
|
573
|
+
parameters?: Record<string, unknown>; // ❌ Loose typing
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**After** (Plugin SDK):
|
|
578
|
+
```typescript
|
|
579
|
+
import type { HoloTool, HoloJsonSchema } from '@holokai/sdk';
|
|
580
|
+
|
|
581
|
+
interface HoloTool {
|
|
582
|
+
parameters?: HoloJsonSchema; // ✅ Strict JSON Schema Draft 7
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Type Safety
|
|
587
|
+
|
|
588
|
+
All interfaces use strict TypeScript types from `@holokai/sdk` for compile-time validation.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Configuration Schema
|
|
593
|
+
|
|
594
|
+
The plugin exposes a JSON Schema for configuration validation:
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
{
|
|
598
|
+
baseUrl?: string; // Ollama endpoint (default: http://localhost:11434)
|
|
599
|
+
defaultModel?: string; // Fallback model (e.g., "llama2")
|
|
600
|
+
allowedModels?: string[]; // Model allowlist
|
|
601
|
+
timeoutMs?: number; // Request timeout (default: 60000)
|
|
602
|
+
maxRetries?: number; // Retry attempts (default: 2)
|
|
603
|
+
defaultKeepAlive?: string; // Default keep_alive ("5m", 300)
|
|
604
|
+
logRequests?: boolean; // Observability (default: false)
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
See [manifest.ts](./src/manifest.ts) for the complete schema.
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Development
|
|
613
|
+
|
|
614
|
+
### Setup
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
# Install dependencies
|
|
618
|
+
npm install
|
|
619
|
+
|
|
620
|
+
# Build
|
|
621
|
+
npm run build
|
|
622
|
+
|
|
623
|
+
# Type checking
|
|
624
|
+
npm run type-check
|
|
625
|
+
|
|
626
|
+
# Run tests
|
|
627
|
+
npm test
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Testing
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
# Unit tests
|
|
634
|
+
npm test
|
|
635
|
+
|
|
636
|
+
# Integration tests (requires Ollama running)
|
|
637
|
+
npm run test:integration
|
|
638
|
+
|
|
639
|
+
# Watch mode
|
|
640
|
+
npm run test:watch
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Building
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
# Production build
|
|
647
|
+
npm run build
|
|
648
|
+
|
|
649
|
+
# Watch mode
|
|
650
|
+
npm run build:watch
|
|
651
|
+
|
|
652
|
+
# Clean
|
|
653
|
+
npm run clean
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## Known Issues & Workarounds
|
|
659
|
+
|
|
660
|
+
### Missing Response IDs
|
|
661
|
+
|
|
662
|
+
**Issue**: Ollama responses don't include `id` fields.
|
|
663
|
+
|
|
664
|
+
**Workaround**: Plugin automatically synthesizes UUIDs for all responses. For streaming, the same ID is used across all chunks in a session.
|
|
665
|
+
|
|
666
|
+
### Timestamp Format
|
|
667
|
+
|
|
668
|
+
**Issue**: Ollama returns timestamps as ISO8601 strings, not milliseconds.
|
|
669
|
+
|
|
670
|
+
**Workaround**: Plugin automatically converts to milliseconds: `Date.parse(created_at)`.
|
|
671
|
+
|
|
672
|
+
### Missing Finish Reasons
|
|
673
|
+
|
|
674
|
+
**Issue**: Some models return `done: true` without `done_reason`.
|
|
675
|
+
|
|
676
|
+
**Workaround**: Plugin defaults to `finish_reason: 'stop'` when missing.
|
|
677
|
+
|
|
678
|
+
### Tool Choice Not Supported
|
|
679
|
+
|
|
680
|
+
**Issue**: Ollama doesn't support explicit `tool_choice` like Claude/OpenAI.
|
|
681
|
+
|
|
682
|
+
**Behavior**: Plugin logs a warning and ignores `tool_choice` field. Model auto-selects tools when provided.
|
|
683
|
+
|
|
684
|
+
### Empty Streaming Frames
|
|
685
|
+
|
|
686
|
+
**Issue**: Ollama may emit empty frames (`content: ""`) during slow tokenization.
|
|
687
|
+
|
|
688
|
+
**Workaround**: Plugin skips empty frames to avoid emitting no-op events.
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## Related Documentation
|
|
693
|
+
|
|
694
|
+
### SDK Documentation
|
|
695
|
+
- [SDK README](../sdk/README.md) - Plugin development guide and templates
|
|
696
|
+
|
|
697
|
+
### Ollama Documentation
|
|
698
|
+
- [Official API Docs](https://github.com/ollama/ollama/blob/main/docs/api.md)
|
|
699
|
+
- [Model Library](https://ollama.com/library)
|
|
700
|
+
- [Model Files](https://github.com/ollama/ollama/blob/main/docs/modelfile.md)
|
|
701
|
+
- [FAQ](https://github.com/ollama/ollama/blob/main/docs/faq.md)
|
|
702
|
+
|
|
703
|
+
### Migration Notes
|
|
704
|
+
- This plugin was extracted from the monolithic `src/providers/ollama/` codebase
|
|
705
|
+
- Migration to plugin architecture is complete
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Contributing
|
|
710
|
+
|
|
711
|
+
### Adding Features
|
|
712
|
+
|
|
713
|
+
1. Update types in `@holokai/sdk` first (if needed)
|
|
714
|
+
2. Implement translator logic
|
|
715
|
+
3. Write tests (unit + integration)
|
|
716
|
+
4. Update this README
|
|
717
|
+
|
|
718
|
+
### Reporting Issues
|
|
719
|
+
|
|
720
|
+
Found a bug or have a feature request?
|
|
721
|
+
- GitHub Issues: https://github.com/holokai/holo-provider-ollama/issues
|
|
722
|
+
- Include: Holo version, Ollama version, model name, request/response samples
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## License
|
|
727
|
+
|
|
728
|
+
MIT © Holokai
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## Changelog
|
|
733
|
+
|
|
734
|
+
### v0.1.0 (Current)
|
|
735
|
+
- ✅ Initial plugin release
|
|
736
|
+
- ✅ Extracted from monolithic architecture
|
|
737
|
+
- ✅ Migrated to SDK types
|
|
738
|
+
- ✅ Validated against Holo format spec
|
|
739
|
+
- ✅ Dual mode support (Chat + Generate)
|
|
740
|
+
- ✅ Complete streaming orchestration
|
|
741
|
+
- ✅ Tool calling support
|
|
742
|
+
- ✅ Vision/multimodal support
|
|
743
|
+
- ✅ Local model deployment
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
**Last Updated**: 2025-12-18
|
|
748
|
+
**Plugin Version**: 0.1.0
|
|
749
|
+
**SDK Version**: ^0.1.0
|
|
750
|
+
**Ollama SDK**: ^0.6.3
|