@startsimpli/llm 0.4.27 → 0.4.28

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/README.md CHANGED
@@ -196,7 +196,6 @@ const { text, words } = await speech.transcribe({
196
196
 
197
197
  | Env | Provider | List price |
198
198
  | --- | --- | --- |
199
- | `GROQ_API_KEY` | Groq `whisper-large-v3-turbo` | $0.04 / audio hour |
200
199
  | `OPENAI_API_KEY` | OpenAI `gpt-4o-mini-transcribe` (default; `gpt-4o-transcribe` / `whisper-1` cost 2x) | $0.18 / hour |
201
200
  | `ELEVENLABS_API_KEY` | ElevenLabs Scribe (keyterm hints are a billed add-on, so off unless `sendKeyterms`) | $0.22 / hour |
202
201
  | `SPEECH_PROVIDER=mock` | deterministic, for tests | — |
@@ -206,8 +205,8 @@ Retryable errors back off and retry; a failure that survives falls through to th
206
205
  vendor (`fallback: false` to pin one). Bad audio (`INVALID_REQUEST`) never falls through.
207
206
 
208
207
  **Add a vendor** — implement `ISpeechProvider` (state `costPerMinuteUsd`) and register it, no
209
- service edit. An OpenAI-compatible host is a ten-line subclass of `OpenAISpeechProvider`
210
- (see `providers/groq.ts`):
208
+ service edit. An OpenAI-compatible host is just `OpenAISpeechProvider` with a different
209
+ `name` / `baseUrl` / `defaultModel` / `prices`:
211
210
 
212
211
  ```ts
213
212
  registerSpeechProvider('mine', () => (process.env.MINE_KEY ? new MyProvider(process.env.MINE_KEY) : null));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/llm",
3
- "version": "0.4.27",
3
+ "version": "0.4.28",
4
4
  "description": "Generic LLM service infrastructure — provider abstraction, retry logic, JSON extraction",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import {
4
- ElevenLabsSpeechProvider, GroqSpeechProvider, OpenAISpeechProvider, SpeechService, createGroqSpeechProvider,
4
+ ElevenLabsSpeechProvider, OpenAISpeechProvider, SpeechService,
5
5
  } from '../speech';
6
6
  import type { ISpeechProvider, TranscribeRequest } from '../speech';
7
7
 
@@ -19,23 +19,21 @@ describe('what each vendor costs per audio minute (USD, list price)', () => {
19
19
  it('elevenlabs Scribe is $0.22/hour', () => {
20
20
  expect(new ElevenLabsSpeechProvider({ apiKey: 'k' }).costPerMinuteUsd()).toBeCloseTo(0.22 / 60);
21
21
  });
22
- it('groq whisper-large-v3-turbo is $0.04/hour', () => {
23
- expect(new GroqSpeechProvider({ apiKey: 'k' }).costPerMinuteUsd()).toBeCloseTo(0.04 / 60);
24
- });
25
22
  });
26
23
 
27
- describe('groq provider (OpenAI-compatible endpoint)', () => {
28
- it('is only built when GROQ_API_KEY is set', () => {
29
- expect(createGroqSpeechProvider({})).toBeNull();
30
- expect(createGroqSpeechProvider({ GROQ_API_KEY: 'g' })?.name).toBe('groq');
31
- });
32
- it('posts to Groq with its own default model and reports itself as groq', async () => {
24
+ describe('an OpenAI-compatible host is the OpenAI provider with different options', () => {
25
+ it('reports its own name, model and price and posts to its own base URL', async () => {
33
26
  const f = okJson({ text: ' hi ' });
34
- const out = await new GroqSpeechProvider({ apiKey: 'g', fetchImpl: f as never }).transcribe(req);
27
+ const p = new OpenAISpeechProvider({
28
+ apiKey: 'k', name: 'selfhosted', baseUrl: 'https://stt.example.com/openai', defaultModel: 'w',
29
+ prices: { w: 0.001 }, fetchImpl: f as never,
30
+ });
31
+ const out = await p.transcribe(req);
35
32
  const [url, init] = f.mock.calls[0] as unknown as [string, RequestInit];
36
- expect(url).toBe('https://api.groq.com/openai/v1/audio/transcriptions');
37
- expect((init.body as FormData).get('model')).toBe('whisper-large-v3-turbo');
38
- expect(out).toMatchObject({ text: 'hi', provider: 'groq' });
33
+ expect(url).toBe('https://stt.example.com/openai/v1/audio/transcriptions');
34
+ expect((init.body as FormData).get('model')).toBe('w');
35
+ expect(out).toMatchObject({ text: 'hi', provider: 'selfhosted' });
36
+ expect(p.costPerMinuteUsd()).toBeCloseTo(0.001);
39
37
  });
40
38
  });
41
39
 
@@ -153,7 +153,7 @@ describe('SpeechService', () => {
153
153
  });
154
154
  it('says what to set when nothing is configured', async () => {
155
155
  const s = withProviders(new SpeechService());
156
- await expect(s.transcribe(req)).rejects.toThrow(/GROQ_API_KEY, OPENAI_API_KEY or ELEVENLABS_API_KEY/);
156
+ await expect(s.transcribe(req)).rejects.toThrow(/OPENAI_API_KEY or ELEVENLABS_API_KEY/);
157
157
  expect(s.isAvailable()).toBe(false);
158
158
  });
159
159
  it('SPEECH_LANGUAGE is a default, and a per-call language wins', async () => {
@@ -14,6 +14,5 @@ export type {
14
14
  export { ElevenLabsSpeechProvider, createElevenLabsSpeechProvider } from './providers/elevenlabs';
15
15
  export { OpenAISpeechProvider, createOpenAISpeechProvider } from './providers/openai';
16
16
  export type { OpenAISpeechOptions } from './providers/openai';
17
- export { GroqSpeechProvider, createGroqSpeechProvider } from './providers/groq';
18
17
  export { MockSpeechProvider, setMockSpeechBehavior, resetMockSpeechBehavior } from './providers/mock';
19
18
  export { audioExtension, speechHttpError } from './http';
@@ -5,8 +5,8 @@
5
5
  * and equally accurate on our benchmark. Per-word timings are only offered by
6
6
  * `whisper-1` (verbose_json), so asking for `timestamps: 'word'` switches model.
7
7
  *
8
- * ANY OpenAI-COMPATIBLE ENDPOINT IS THIS CLASS WITH DIFFERENT OPTIONS (see
9
- * groq.ts): name, base URL, default model, word-timing model and price table.
8
+ * ANY OpenAI-COMPATIBLE ENDPOINT IS THIS CLASS WITH DIFFERENT OPTIONS: name, base
9
+ * URL, default model, word-timing model and price table.
10
10
  */
11
11
  import type { ISpeechProvider } from '../interface';
12
12
  import { assertAudio, audioBlob, speechFetch } from '../http';
@@ -2,7 +2,6 @@ import { LLMProviderError } from '../errors';
2
2
  import { ProviderRegistry } from '../providers/registry';
3
3
  import type { ISpeechProvider } from './interface';
4
4
  import { createElevenLabsSpeechProvider } from './providers/elevenlabs';
5
- import { createGroqSpeechProvider } from './providers/groq';
6
5
  import { MockSpeechProvider } from './providers/mock';
7
6
  import { createOpenAISpeechProvider } from './providers/openai';
8
7
  import type { SpeechServiceConfig, TranscribeRequest, TranscriptResult } from './types';
@@ -12,7 +11,6 @@ import type { SpeechServiceConfig, TranscribeRequest, TranscriptResult } from '.
12
11
  * `registerSpeechProvider('name', factory)` — no edit to `SpeechService`.
13
12
  */
14
13
  export const speechProviders = new ProviderRegistry<ISpeechProvider>()
15
- .register('groq', () => createGroqSpeechProvider())
16
14
  .register('openai', () => createOpenAISpeechProvider())
17
15
  .register('elevenlabs', () => createElevenLabsSpeechProvider());
18
16
 
@@ -76,7 +74,7 @@ export class SpeechService {
76
74
  const order = this.order();
77
75
  if (!order.length) {
78
76
  throw new LLMProviderError(
79
- 'No speech providers available. Set GROQ_API_KEY, OPENAI_API_KEY or ELEVENLABS_API_KEY.',
77
+ 'No speech providers available. Set OPENAI_API_KEY or ELEVENLABS_API_KEY.',
80
78
  'AUTHENTICATION_ERROR', false,
81
79
  );
82
80
  }
@@ -1,35 +0,0 @@
1
- /**
2
- * Groq-hosted Whisper — the cheapest option we benchmarked ($0.04 per audio hour,
3
- * about a fifth of ElevenLabs Scribe). Groq speaks the OpenAI transcription API,
4
- * so this is the OpenAI provider pointed at a different host: that is the whole
5
- * file, and the template for any other OpenAI-compatible vendor.
6
- * Env: GROQ_API_KEY.
7
- */
8
- import { OpenAISpeechProvider } from './openai';
9
- import type { OpenAISpeechOptions } from './openai';
10
-
11
- const GROQ_PRICES: Record<string, number> = {
12
- 'whisper-large-v3-turbo': 0.04 / 60,
13
- 'whisper-large-v3': 0.111 / 60,
14
- };
15
-
16
- export class GroqSpeechProvider extends OpenAISpeechProvider {
17
- constructor(options: Pick<OpenAISpeechOptions, 'apiKey' | 'fetchImpl' | 'baseUrl'> = {}) {
18
- super({
19
- ...options,
20
- name: 'groq',
21
- baseUrl: options.baseUrl ?? 'https://api.groq.com/openai',
22
- defaultModel: 'whisper-large-v3-turbo',
23
- // Groq's Whisper models return word timings from verbose_json themselves.
24
- wordTimestampModel: 'whisper-large-v3-turbo',
25
- prices: GROQ_PRICES,
26
- fallbackCostPerMinuteUsd: 0.111 / 60,
27
- });
28
- }
29
- }
30
-
31
- export function createGroqSpeechProvider(
32
- env: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {},
33
- ): GroqSpeechProvider | null {
34
- return env.GROQ_API_KEY ? new GroqSpeechProvider({ apiKey: env.GROQ_API_KEY }) : null;
35
- }