@just-every/ensemble 0.2.212 → 0.2.214

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.
Files changed (106) hide show
  1. package/README.md +152 -91
  2. package/dist/cjs/core/ensemble_request.cjs +734 -333
  3. package/dist/cjs/core/ensemble_request.d.ts.map +1 -1
  4. package/dist/cjs/core/ensemble_request.js.map +1 -1
  5. package/dist/cjs/data/model_data.cjs +28 -1
  6. package/dist/cjs/data/model_data.d.ts.map +1 -1
  7. package/dist/cjs/data/model_data.js.map +1 -1
  8. package/dist/cjs/model_providers/base_provider.d.ts.map +1 -1
  9. package/dist/cjs/model_providers/base_provider.js.map +1 -1
  10. package/dist/cjs/model_providers/claude.cjs +72 -72
  11. package/dist/cjs/model_providers/claude.d.ts.map +1 -1
  12. package/dist/cjs/model_providers/claude.js.map +1 -1
  13. package/dist/cjs/model_providers/gemini.cjs +3 -0
  14. package/dist/cjs/model_providers/gemini.d.ts.map +1 -1
  15. package/dist/cjs/model_providers/gemini.js.map +1 -1
  16. package/dist/cjs/model_providers/openai.cjs +72 -168
  17. package/dist/cjs/model_providers/openai.d.ts.map +1 -1
  18. package/dist/cjs/model_providers/openai.js.map +1 -1
  19. package/dist/cjs/model_providers/openai_chat.cjs +55 -24
  20. package/dist/cjs/model_providers/openai_chat.d.ts.map +1 -1
  21. package/dist/cjs/model_providers/openai_chat.js.map +1 -1
  22. package/dist/cjs/model_providers/openai_image_pricing.cjs +184 -0
  23. package/dist/cjs/model_providers/openai_image_pricing.d.ts +19 -0
  24. package/dist/cjs/model_providers/openai_image_pricing.d.ts.map +1 -0
  25. package/dist/cjs/model_providers/openai_image_pricing.js.map +1 -0
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/cjs/types/types.d.ts +22 -4
  28. package/dist/cjs/types/types.d.ts.map +1 -1
  29. package/dist/cjs/utils/agent.cjs +4 -6
  30. package/dist/cjs/utils/agent.d.ts.map +1 -1
  31. package/dist/cjs/utils/agent.js.map +1 -1
  32. package/dist/cjs/utils/cost_tracker.cjs +15 -10
  33. package/dist/cjs/utils/cost_tracker.d.ts.map +1 -1
  34. package/dist/cjs/utils/cost_tracker.js.map +1 -1
  35. package/dist/cjs/utils/ensemble_result.cjs +43 -4
  36. package/dist/cjs/utils/ensemble_result.d.ts +10 -1
  37. package/dist/cjs/utils/ensemble_result.d.ts.map +1 -1
  38. package/dist/cjs/utils/ensemble_result.js.map +1 -1
  39. package/dist/cjs/utils/failure_detection.cjs +292 -0
  40. package/dist/cjs/utils/failure_detection.d.ts +51 -0
  41. package/dist/cjs/utils/failure_detection.d.ts.map +1 -0
  42. package/dist/cjs/utils/failure_detection.js.map +1 -0
  43. package/dist/cjs/utils/json_schema.cjs +490 -0
  44. package/dist/cjs/utils/json_schema.d.ts +10 -0
  45. package/dist/cjs/utils/json_schema.d.ts.map +1 -0
  46. package/dist/cjs/utils/json_schema.js.map +1 -0
  47. package/dist/cjs/utils/tool_execution_manager.cjs +28 -4
  48. package/dist/cjs/utils/tool_execution_manager.d.ts +1 -1
  49. package/dist/cjs/utils/tool_execution_manager.d.ts.map +1 -1
  50. package/dist/cjs/utils/tool_execution_manager.js.map +1 -1
  51. package/dist/cjs/utils/verification.cjs +26 -13
  52. package/dist/cjs/utils/verification.d.ts.map +1 -1
  53. package/dist/cjs/utils/verification.js.map +1 -1
  54. package/dist/core/ensemble_request.d.ts.map +1 -1
  55. package/dist/core/ensemble_request.js +734 -333
  56. package/dist/core/ensemble_request.js.map +1 -1
  57. package/dist/data/model_data.d.ts.map +1 -1
  58. package/dist/data/model_data.js +28 -1
  59. package/dist/data/model_data.js.map +1 -1
  60. package/dist/model_providers/base_provider.d.ts.map +1 -1
  61. package/dist/model_providers/base_provider.js.map +1 -1
  62. package/dist/model_providers/claude.d.ts.map +1 -1
  63. package/dist/model_providers/claude.js +72 -72
  64. package/dist/model_providers/claude.js.map +1 -1
  65. package/dist/model_providers/gemini.d.ts.map +1 -1
  66. package/dist/model_providers/gemini.js +3 -0
  67. package/dist/model_providers/gemini.js.map +1 -1
  68. package/dist/model_providers/openai.d.ts.map +1 -1
  69. package/dist/model_providers/openai.js +72 -168
  70. package/dist/model_providers/openai.js.map +1 -1
  71. package/dist/model_providers/openai_chat.d.ts.map +1 -1
  72. package/dist/model_providers/openai_chat.js +55 -24
  73. package/dist/model_providers/openai_chat.js.map +1 -1
  74. package/dist/model_providers/openai_image_pricing.d.ts +19 -0
  75. package/dist/model_providers/openai_image_pricing.d.ts.map +1 -0
  76. package/dist/model_providers/openai_image_pricing.js +176 -0
  77. package/dist/model_providers/openai_image_pricing.js.map +1 -0
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/dist/types/types.d.ts +22 -4
  80. package/dist/types/types.d.ts.map +1 -1
  81. package/dist/utils/agent.d.ts.map +1 -1
  82. package/dist/utils/agent.js +4 -6
  83. package/dist/utils/agent.js.map +1 -1
  84. package/dist/utils/cost_tracker.d.ts.map +1 -1
  85. package/dist/utils/cost_tracker.js +15 -10
  86. package/dist/utils/cost_tracker.js.map +1 -1
  87. package/dist/utils/ensemble_result.d.ts +10 -1
  88. package/dist/utils/ensemble_result.d.ts.map +1 -1
  89. package/dist/utils/ensemble_result.js +43 -4
  90. package/dist/utils/ensemble_result.js.map +1 -1
  91. package/dist/utils/failure_detection.d.ts +51 -0
  92. package/dist/utils/failure_detection.d.ts.map +1 -0
  93. package/dist/utils/failure_detection.js +280 -0
  94. package/dist/utils/failure_detection.js.map +1 -0
  95. package/dist/utils/json_schema.d.ts +10 -0
  96. package/dist/utils/json_schema.d.ts.map +1 -0
  97. package/dist/utils/json_schema.js +486 -0
  98. package/dist/utils/json_schema.js.map +1 -0
  99. package/dist/utils/tool_execution_manager.d.ts +1 -1
  100. package/dist/utils/tool_execution_manager.d.ts.map +1 -1
  101. package/dist/utils/tool_execution_manager.js +28 -4
  102. package/dist/utils/tool_execution_manager.js.map +1 -1
  103. package/dist/utils/verification.d.ts.map +1 -1
  104. package/dist/utils/verification.js +26 -13
  105. package/dist/utils/verification.js.map +1 -1
  106. package/package.json +1 -1
package/README.md CHANGED
@@ -16,6 +16,7 @@ npm run demo
16
16
  This opens a unified demo interface at http://localhost:3000 with all demos:
17
17
 
18
18
  ### Demo Interface
19
+
19
20
  ![Ensemble Demos Interface](demo/screenshots/demo-overview.png)
20
21
 
21
22
  Navigate to http://localhost:3000 to access all demos through a unified interface.
@@ -35,12 +36,13 @@ See the [demo README](demo/README.md) for detailed information about each demo.
35
36
  - 🎯 **Smart Result Processing** - Automatic summarization and truncation for long outputs
36
37
 
37
38
  ## Model Updates (Dec 2025)
39
+
38
40
  - OpenAI: Added GPT-5.2 (base + chat-latest + pro) and refreshed GPT-5.1/GPT-5/Codex pricing
39
41
  - Anthropic: Claude 4.5 (Sonnet/Haiku, incl. 1M context) and Claude Opus 4.1
40
42
  - Google: Gemini 3 (Pro/Flash/Ultra) and refreshed Gemini 2.5 pricing incl. image/TTS/native-audio
41
43
  - xAI: Grok 4.1 Fast and Grok 4 Fast with tiered pricing; updated Grok 4/3/mini variants plus Grok Imagine image generation/editing support (`grok-imagine-image`, `grok-imagine-image-pro`)
42
44
 
43
- *Codex-Max pricing reflects current published rates and may change if OpenAI updates pricing.
45
+ \*Codex-Max pricing reflects current published rates and may change if OpenAI updates pricing.
44
46
 
45
47
  ## Installation
46
48
 
@@ -61,13 +63,13 @@ Available API keys (add only the ones you need):
61
63
  ```bash
62
64
  # LLM Providers
63
65
  OPENAI_API_KEY=your-openai-key
64
- ANTHROPIC_API_KEY=your-anthropic-key
66
+ ANTHROPIC_API_KEY=your-anthropic-key
65
67
  GOOGLE_API_KEY=your-google-key
66
68
  XAI_API_KEY=your-xai-key
67
69
  DEEPSEEK_API_KEY=your-deepseek-key
68
70
  OPENROUTER_API_KEY=your-openrouter-key
69
71
 
70
- # Voice & Audio Providers
72
+ # Voice & Audio Providers
71
73
  ELEVENLABS_API_KEY=your-elevenlabs-key
72
74
 
73
75
  # Search Providers
@@ -81,9 +83,7 @@ BRAVE_API_KEY=your-brave-key
81
83
  ```typescript
82
84
  import { ensembleRequest, ensembleResult } from '@just-every/ensemble';
83
85
 
84
- const messages = [
85
- { type: 'message', role: 'user', content: 'How many of the letter "e" is there in "Ensemble"?' }
86
- ];
86
+ const messages = [{ type: 'message', role: 'user', content: 'How many of the letter "e" is there in "Ensemble"?' }];
87
87
 
88
88
  // Perform initial request
89
89
  for await (const event of ensembleRequest(messages)) {
@@ -104,21 +104,22 @@ const stream = ensembleRequest(messages, validatorAgent);
104
104
  const result = await ensembleResult(stream);
105
105
  console.log('Validation Result:', {
106
106
  message: result.message,
107
+ requestStatus: result.requestStatus,
108
+ failure: result.failure,
107
109
  cost: result.cost,
108
110
  completed: result.completed,
109
- duration: result.endTime
110
- ? result.endTime.getTime() - result.startTime.getTime()
111
- : 0,
111
+ duration: result.endTime ? result.endTime.getTime() - result.startTime.getTime() : 0,
112
112
  messageIds: Array.from(result.messageIds),
113
113
  });
114
114
  ```
115
115
 
116
116
  ## Documentation
117
117
 
118
+ - [Request Lifecycle & Failures](docs/retry-behavior.md) - Outer request status, retries, timeouts, verification, and failure handling
118
119
  - [Tool Execution Guide](docs/tool-execution.md) - Advanced tool calling features
119
120
  - [Interactive Demos](demo/) - Web-based demos for core features
120
121
  - Generated [API Reference](docs/api) with `npm run docs`
121
-
122
+
122
123
  Run `npm run docs` to regenerate the HTML documentation.
123
124
 
124
125
  ## Core Concepts
@@ -130,25 +131,27 @@ Define tools that LLMs can call:
130
131
  ```typescript
131
132
  const agent = {
132
133
  model: 'o3',
133
- tools: [{
134
- definition: {
135
- type: 'function',
136
- function: {
137
- name: 'get_weather',
138
- description: 'Get weather for a location',
139
- parameters: {
140
- type: 'object',
141
- properties: {
142
- location: { type: 'string' }
134
+ tools: [
135
+ {
136
+ definition: {
137
+ type: 'function',
138
+ function: {
139
+ name: 'get_weather',
140
+ description: 'Get weather for a location',
141
+ parameters: {
142
+ type: 'object',
143
+ properties: {
144
+ location: { type: 'string' },
145
+ },
146
+ required: ['location'],
143
147
  },
144
- required: ['location']
145
- }
146
- }
148
+ },
149
+ },
150
+ function: async (location: string) => {
151
+ return `Weather in ${location}: Sunny, 72°F`;
152
+ },
147
153
  },
148
- function: async (location: string) => {
149
- return `Weather in ${location}: Sunny, 72°F`;
150
- }
151
- }]
154
+ ],
152
155
  };
153
156
  ```
154
157
 
@@ -159,7 +162,8 @@ All providers emit standardized events:
159
162
  - `message_start` / `message_delta` / `message_complete` - Message streaming
160
163
  - `tool_start` / `tool_delta` / `tool_done` - Tool execution
161
164
  - `cost_update` - Token usage and cost tracking
162
- - `error` - Error handling
165
+ - `operation_status` - Authoritative outer request lifecycle (`started`, `retrying`, `completed`, `failed`)
166
+ - `error` - Failure details for the current request round (`error`, `code`, `details`, `recoverable`)
163
167
 
164
168
  ### Agent Configuration
165
169
 
@@ -179,15 +183,19 @@ const agent = {
179
183
  ```
180
184
 
181
185
  Key configuration options:
186
+
182
187
  - `maxToolCalls` - Limits the total number of tool calls across all rounds
183
188
  - `maxToolCallRoundsPerTurn` - Limits sequential rounds where each round can have multiple parallel tool calls
184
189
  - `modelSettings` - Provider-specific parameters like temperature, max_tokens, etc.
190
+ - `modelSettings.timeout_ms` - Whole outer-request timeout budget shared across retries and tool completion
191
+ - `retryOptions` - Outer retry/backoff policy for recoverable request failures
185
192
 
186
193
  ### Multimodal Input (Images)
187
194
 
188
195
  For multimodal models, pass content as an array of typed parts. In addition to `input_text` and `input_image`, Ensemble now accepts a simpler `image` part that can take base64 data or a URL.
189
196
 
190
197
  Supported image fields:
198
+
191
199
  - `type: 'image'`
192
200
  - `data`: base64 string **or** full `data:<mime>;base64,...` URL
193
201
  - `url`: http(s) URL
@@ -199,27 +207,29 @@ Supported image fields:
199
207
  import { ensembleRequest } from '@just-every/ensemble';
200
208
 
201
209
  const messages = [
202
- {
203
- type: 'message',
204
- role: 'user',
205
- content: [
206
- { type: 'input_text', text: 'Describe this image.' },
207
- { type: 'image', data: myPngBase64, mime_type: 'image/png' }
208
- // or: { type: 'image', url: 'https://example.com/cat.png' }
209
- ],
210
- },
210
+ {
211
+ type: 'message',
212
+ role: 'user',
213
+ content: [
214
+ { type: 'input_text', text: 'Describe this image.' },
215
+ { type: 'image', data: myPngBase64, mime_type: 'image/png' },
216
+ // or: { type: 'image', url: 'https://example.com/cat.png' }
217
+ ],
218
+ },
211
219
  ];
212
220
 
213
221
  for await (const event of ensembleRequest(messages, { model: 'gemini-3-flash-preview' })) {
214
- if (event.type === 'message_complete' && 'content' in event) {
215
- console.log(event.content);
216
- }
222
+ if (event.type === 'message_complete' && 'content' in event) {
223
+ console.log(event.content);
224
+ }
217
225
  }
218
226
  ```
219
227
 
220
228
  ### Structured JSON Output
221
229
 
222
- Use `modelSettings.json_schema` to request a JSON-only response. The schema is validated by providers that support it.
230
+ Use `modelSettings.json_schema` to request a JSON-only response. Ensemble treats this as the authoritative structured-output contract. When `strict: true` is enabled, the final response is validated by the outer request lifecycle and invalid JSON/schema violations fail the request instead of silently completing.
231
+
232
+ Prefer `modelSettings.json_schema`. The older `jsonSchema` agent property is still accepted as a compatibility alias and is mapped onto `modelSettings.json_schema`.
223
233
 
224
234
  The example below combines **image input** with **JSON output**:
225
235
 
@@ -227,40 +237,74 @@ The example below combines **image input** with **JSON output**:
227
237
  import { ensembleRequest, ensembleResult } from '@just-every/ensemble';
228
238
 
229
239
  const agent = {
230
- model: 'gemini-3-flash-preview',
231
- modelSettings: {
232
- temperature: 0.2,
233
- json_schema: {
234
- name: 'image_analysis',
235
- type: 'json_schema',
236
- schema: {
237
- type: 'object',
238
- properties: {
239
- dominant_color: { type: 'string' },
240
- confidence: { type: 'number' },
240
+ model: 'gemini-3-flash-preview',
241
+ modelSettings: {
242
+ temperature: 0.2,
243
+ json_schema: {
244
+ name: 'image_analysis',
245
+ type: 'json_schema',
246
+ schema: {
247
+ type: 'object',
248
+ properties: {
249
+ dominant_color: { type: 'string' },
250
+ confidence: { type: 'number' },
251
+ },
252
+ required: ['dominant_color', 'confidence'],
253
+ },
241
254
  },
242
- required: ['dominant_color', 'confidence'],
243
- },
244
255
  },
245
- },
246
256
  };
247
257
 
248
258
  const messages = [
249
- {
250
- type: 'message',
251
- role: 'user',
252
- content: [
253
- { type: 'input_text', text: 'Analyze this image and return JSON.' },
254
- { type: 'image', data: myPngBase64, mime_type: 'image/png' },
255
- ],
256
- },
259
+ {
260
+ type: 'message',
261
+ role: 'user',
262
+ content: [
263
+ { type: 'input_text', text: 'Analyze this image and return JSON.' },
264
+ { type: 'image', data: myPngBase64, mime_type: 'image/png' },
265
+ ],
266
+ },
257
267
  ];
258
268
 
259
269
  const result = await ensembleResult(ensembleRequest(messages, agent));
270
+ if (!result.completed) {
271
+ throw new Error(result.failure?.error || 'Request failed');
272
+ }
260
273
  const parsed = JSON.parse(result.message);
261
274
  console.log(parsed.dominant_color, parsed.confidence);
262
275
  ```
263
276
 
277
+ `ensembleResult(...)` also exposes `requestStatus` and `failure`, so callers can distinguish the final outer request outcome from earlier recoverable errors in the stream.
278
+
279
+ ### Request Lifecycle
280
+
281
+ `operation_status` is the authoritative outer lifecycle for one `ensembleRequest(...)` call:
282
+
283
+ - `started` is emitted once when the outer request begins
284
+ - `retrying` is emitted after a recoverable failure and before the next attempt
285
+ - `completed` is the only authoritative success outcome
286
+ - `failed` is the only authoritative terminal failure outcome
287
+
288
+ Recoverable `error` events can appear before a successful `completed` status. Callers that want the final outcome should prefer `operation_status` or `ensembleResult(...)` over treating the first `error` event as terminal.
289
+
290
+ ```ts
291
+ const stream = ensembleRequest(messages, {
292
+ model: 'claude-4-sonnet',
293
+ modelSettings: { timeout_ms: 15_000 },
294
+ retryOptions: { maxRetries: 1 },
295
+ });
296
+
297
+ for await (const event of stream) {
298
+ if (event.type === 'operation_status') {
299
+ console.log(event.status, event.reason, event.attempt, event.max_attempts);
300
+ }
301
+
302
+ if (event.type === 'error') {
303
+ console.log(event.recoverable ? 'recoverable' : 'terminal', event.code, event.error);
304
+ }
305
+ }
306
+ ```
307
+
264
308
  ### Advanced Features
265
309
 
266
310
  - **Parallel Tool Execution** - Tools run concurrently by default within each round
@@ -278,21 +322,25 @@ import { ensembleVoice, ensembleVoice } from '@just-every/ensemble';
278
322
 
279
323
  // Simple voice generation
280
324
  const audioData = await ensembleVoice('Hello, world!', {
281
- model: 'tts-1' // or 'gemini-2.5-flash-preview-tts'
325
+ model: 'tts-1', // or 'gemini-2.5-flash-preview-tts'
282
326
  });
283
327
 
284
328
  // Voice generation with options
285
- const audioData = await ensembleVoice('Welcome to our service', {
286
- model: 'tts-1-hd'
287
- }, {
288
- voice: 'nova', // Voice selection
289
- speed: 1.2, // Speech speed (0.25-4.0)
290
- response_format: 'mp3' // Audio format
291
- });
329
+ const audioData = await ensembleVoice(
330
+ 'Welcome to our service',
331
+ {
332
+ model: 'tts-1-hd',
333
+ },
334
+ {
335
+ voice: 'nova', // Voice selection
336
+ speed: 1.2, // Speech speed (0.25-4.0)
337
+ response_format: 'mp3', // Audio format
338
+ }
339
+ );
292
340
 
293
341
  // Streaming voice generation
294
342
  for await (const event of ensembleVoice('Long text...', {
295
- model: 'gemini-2.5-pro-preview-tts'
343
+ model: 'gemini-2.5-pro-preview-tts',
296
344
  })) {
297
345
  if (event.type === 'audio_stream') {
298
346
  // Process audio chunk
@@ -302,6 +350,7 @@ for await (const event of ensembleVoice('Long text...', {
302
350
  ```
303
351
 
304
352
  **Supported Voice Models:**
353
+
305
354
  - OpenAI: `tts-1`, `tts-1-hd`
306
355
  - Google Gemini: `gemini-2.5-flash-preview-tts`, `gemini-2.5-pro-preview-tts`
307
356
 
@@ -312,30 +361,35 @@ Use OpenAI GPT-Image-1 (or the new cost-efficient GPT-Image-1 Mini) or Google Ge
312
361
  ```ts
313
362
  import { ensembleImage } from '@just-every/ensemble';
314
363
 
315
- const images = await ensembleImage('A serene lake at dawn', { model: 'gemini-2.5-flash-image-preview' }, { size: 'portrait' });
364
+ const images = await ensembleImage(
365
+ 'A serene lake at dawn',
366
+ { model: 'gemini-2.5-flash-image-preview' },
367
+ { size: 'portrait' }
368
+ );
316
369
 
317
370
  // Gemini 3.1 Flash Image: grounded generation + thinking controls + metadata callback
318
371
  const grounded = await ensembleImage(
319
- 'A detailed painting of a Timareta butterfly resting on a flower',
320
- { model: 'gemini-3.1-flash-image-preview' },
321
- {
322
- size: '16:9',
323
- quality: 'high', // 4K
324
- grounding: {
325
- web_search: true,
326
- image_search: true,
327
- },
328
- thinking: {
329
- level: 'high',
330
- include_thoughts: true,
331
- },
332
- on_metadata: metadata => {
333
- // metadata.citations includes containing-page URLs for attribution compliance
334
- console.log(metadata.citations);
335
- },
336
- }
372
+ 'A detailed painting of a Timareta butterfly resting on a flower',
373
+ { model: 'gemini-3.1-flash-image-preview' },
374
+ {
375
+ size: '16:9',
376
+ quality: 'high', // 4K
377
+ grounding: {
378
+ web_search: true,
379
+ image_search: true,
380
+ },
381
+ thinking: {
382
+ level: 'high',
383
+ include_thoughts: true,
384
+ },
385
+ on_metadata: metadata => {
386
+ // metadata.citations includes containing-page URLs for attribution compliance
387
+ console.log(metadata.citations);
388
+ },
389
+ }
337
390
  );
338
391
  ```
392
+
339
393
  - ElevenLabs: `eleven_multilingual_v2`, `eleven_turbo_v2_5`
340
394
 
341
395
  ## Development
@@ -360,12 +414,14 @@ npm run lint
360
414
  Additional image providers
361
415
 
362
416
  New providers added
417
+
363
418
  - Fireworks AI (FLUX family: Kontext/Pro/Schnell) – async APIs with result polling. Docs: Fireworks Image API.
364
419
  - Stability AI (Stable Image Ultra/SDXL) – REST v2beta endpoints supporting text-to-image and image-to-image.
365
420
  - Runway Gen-4 Image – via FAL.ai.
366
421
  - Recraft v3 – via FAL.ai (supports text-to-vector and vector-style outputs).
367
422
 
368
423
  Environment
424
+
369
425
  ```
370
426
  FIREWORKS_API_KEY=your_key
371
427
  STABILITY_API_KEY=your_key
@@ -373,6 +429,7 @@ FAL_KEY=your_key
373
429
  ```
374
430
 
375
431
  Fallbacks
432
+
376
433
  - If Fireworks returns 401/403 or is not configured, requests for Flux-family models automatically fall back to FAL.ai equivalents when `FAL_KEY` is set.
377
434
 
378
435
  - Luma Photon (official): set `LUMA_API_KEY` and use `luma-photon-1` or `luma-photon-flash-1`.
@@ -380,6 +437,7 @@ Fallbacks
380
437
  - Midjourney v7 (3rd-party): set `MIDJOURNEY_API_KEY` (or `KIE_API_KEY`) and optional `MJ_API_BASE`; use `midjourney-v7`.
381
438
 
382
439
  Notes
440
+
383
441
  - Gemini 3.1 Flash Image supports 0.5K/1K/2K/4K tiers, explicit aspect ratios, Google Image Search grounding, and thinking controls.
384
442
  - Gemini 3 Pro Image supports explicit 1K/2K/4K resolution presets mapped to official aspect-ratio tables.
385
443
  - Luma Photon and Ideogram return URLs; we pass them through without altering pixels.
@@ -406,16 +464,19 @@ Contributions are welcome! Please:
406
464
  ## Troubleshooting
407
465
 
408
466
  ### Provider Issues
467
+
409
468
  - Ensure API keys are set correctly
410
469
  - Check rate limits for your provider
411
470
  - Verify model names match provider expectations
412
471
 
413
472
  ### Tool Calling
473
+
414
474
  - Tools must follow the OpenAI function schema
415
475
  - Ensure tool functions are async
416
476
  - Check timeout settings for long-running tools
417
477
 
418
478
  ### Streaming Issues
479
+
419
480
  - Verify network connectivity
420
481
  - Check for provider-specific errors in events
421
482
  - Enable debug logging with `DEBUG=ensemble:*`