Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/groq-summarize-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/ai-groq': minor
---

feat: add groqSummarize and createGroqSummarize adapters

Groq now exposes tree-shakeable summarize factories that wrap `GroqTextAdapter`
in `ChatStreamSummarizeAdapter`, matching the pattern used by OpenAI, Anthropic,
Gemini, Ollama, Grok, and OpenRouter.
32 changes: 32 additions & 0 deletions docs/adapters/groq.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ const stream = chat({

> If you previously passed `temperature` / `topP` / `maxTokens` at the root of `chat()`, see [Moving Sampling Options into modelOptions](../migration/sampling-options-to-model-options).

## Summarization

Summarize long text content:

```typescript
import { summarize } from "@tanstack/ai";
import { groqSummarize } from "@tanstack/ai-groq";

const result = await summarize({
adapter: groqSummarize("llama-3.3-70b-versatile"),
text: "Your long text to summarize...",
maxLength: 100,
style: "concise", // "concise" | "bullet-points" | "paragraph"
});

console.log(result.summary);
```

### Reasoning

Enable reasoning for models that support it (e.g., `openai/gpt-oss-120b`, `qwen/qwen3-32b`). This allows the model to show its reasoning process, which is streamed as `thinking` chunks:
Expand Down Expand Up @@ -230,10 +248,24 @@ Creates a Groq chat adapter with an explicit API key.

**Returns:** A Groq chat adapter instance.


### `groqSummarize(model, config?)`

Creates a Groq summarization adapter using environment variables.

**Returns:** A Groq summarize adapter instance.

### `createGroqSummarize(model, apiKey, config?)`

Creates a Groq summarization adapter with an explicit API key.

**Returns:** A Groq summarize adapter instance.

### `groqTranscription(model, config?)` / `createGroqTranscription(model, apiKey, config?)`

Creates a Groq transcription (speech-to-text) adapter. The short form reads `GROQ_API_KEY` from the environment; the `create*` form takes an explicit API key. Supported models: `whisper-large-v3-turbo`, `whisper-large-v3`.


## Limitations

- **Text-to-Speech**: Groq does not currently expose a TTS adapter. Use OpenAI, Gemini, ElevenLabs, or fal for speech generation.
Expand Down
2 changes: 1 addition & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1235,4 +1235,4 @@
]
}
]
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"test:build": "nx affected --target=test:build --exclude=examples/**,testing/**",
"test:types": "nx affected --targets=test:types --exclude=examples/**,testing/**",
"test:knip": "knip",
"test:docs": "node scripts/verify-links.ts",
"test:docs": "tsx scripts/verify-links.ts",
"test:kiira": "kiira check",
"test:react-native": "pnpm --filter @tanstack/ai-react-native-smoke smoke",
"test:e2e": "pnpm --filter @tanstack/ai-e2e test:e2e",
Expand Down Expand Up @@ -83,4 +83,4 @@
"vite": "^7.3.3",
"vitest": "^4.0.14"
}
}
}
1 change: 1 addition & 0 deletions packages/ai-groq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const adapter = createGroqText('llama-3.3-70b-versatile', 'gsk_api_key')
- ✅ Structured output (JSON Schema)
- ✅ Function/tool calling
- ✅ Multimodal input (text + images for vision models)
- ✅ Summarization (`groqSummarize`)
- ❌ Embeddings (not supported by Groq)
- ❌ Image generation (not supported by Groq)

Expand Down
77 changes: 77 additions & 0 deletions packages/ai-groq/src/adapters/summarize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'
import { getGroqApiKeyFromEnv } from '../utils'
import { GroqTextAdapter } from './text'
import type { InferTextProviderOptions } from '@tanstack/ai/adapters'
import type { GROQ_CHAT_MODELS } from '../model-meta'
import type { GroqClientConfig } from '../utils'

/**
* Configuration for Groq summarize adapter
*/
export interface GroqSummarizeConfig extends GroqClientConfig {}

/** Model type for Groq summarization */
export type GroqSummarizeModel = (typeof GROQ_CHAT_MODELS)[number]

/**
* Creates a Groq summarize adapter with explicit API key.
* Type resolution happens here at the call site.
*
* @param model - The model name (e.g., 'llama-3.3-70b-versatile')
* @param apiKey - Your Groq API key
* @param config - Optional additional configuration
* @returns Configured Groq summarize adapter instance with resolved types
*
* @example
* ```typescript
* const adapter = createGroqSummarize('llama-3.3-70b-versatile', "gsk_...");
* ```
*/
export function createGroqSummarize<TModel extends GroqSummarizeModel>(
model: TModel,
apiKey: string,
config?: Omit<GroqSummarizeConfig, 'apiKey'>,
): ChatStreamSummarizeAdapter<
TModel,
InferTextProviderOptions<GroqTextAdapter<TModel>>
> {
return new ChatStreamSummarizeAdapter(
new GroqTextAdapter({ apiKey, ...config }, model),
model,
'groq',
)
}

/**
* Creates a Groq summarize adapter with automatic API key detection from environment variables.
* Type resolution happens here at the call site.
*
* Looks for `GROQ_API_KEY` in:
* - `process.env` (Node.js)
* - `window.env` (Browser with injected env)
*
* @param model - The model name (e.g., 'llama-3.3-70b-versatile')
* @param config - Optional configuration (excluding apiKey which is auto-detected)
* @returns Configured Groq summarize adapter instance with resolved types
* @throws Error if GROQ_API_KEY is not found in environment
*
* @example
* ```typescript
* // Automatically uses GROQ_API_KEY from environment
* const adapter = groqSummarize('llama-3.3-70b-versatile');
*
* await summarize({
* adapter,
* text: "Long article text..."
* });
* ```
*/
export function groqSummarize<TModel extends GroqSummarizeModel>(
model: TModel,
config?: Omit<GroqSummarizeConfig, 'apiKey'>,
): ChatStreamSummarizeAdapter<
TModel,
InferTextProviderOptions<GroqTextAdapter<TModel>>
> {
return createGroqSummarize(model, getGroqApiKeyFromEnv(), config)
}
10 changes: 10 additions & 0 deletions packages/ai-groq/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export {
type GroqTextProviderOptions,
} from './adapters/text'


// Summarize - thin factory functions over @tanstack/ai's ChatStreamSummarizeAdapter
export {
createGroqSummarize,
groqSummarize,
type GroqSummarizeConfig,
type GroqSummarizeModel,
} from './adapters/summarize'

// Transcription adapter
export {
GroqTranscriptionAdapter,
Expand All @@ -39,6 +48,7 @@ export type {
GroqTTSSampleRate,
} from './audio/tts-provider-options'


// Types
export type {
GroqChatModelProviderOptionsByName,
Expand Down
35 changes: 35 additions & 0 deletions packages/ai-groq/tests/groq-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
createGroqText as _realCreateGroqText,
groqText as _realGroqText,
} from '../src/adapters/text'
import {
createGroqSummarize,
groqSummarize,
} from '../src/adapters/summarize'
import { EventType } from '@tanstack/ai'
import type { StreamChunk, Tool } from '@tanstack/ai'
import type { GroqTextProviderOptions } from '../src/index'
Expand Down Expand Up @@ -188,6 +192,37 @@ describe('Groq adapters', () => {
})
})
})

describe('Summarize adapter', () => {
it('creates a summarize adapter with explicit API key', () => {
const adapter = createGroqSummarize(
'llama-3.3-70b-versatile',
'test-api-key',
)

expect(adapter).toBeDefined()
expect(adapter.kind).toBe('summarize')
expect(adapter.name).toBe('groq')
expect(adapter.model).toBe('llama-3.3-70b-versatile')
})

it('creates a summarize adapter from environment variable', () => {
vi.stubEnv('GROQ_API_KEY', 'env-api-key')

const adapter = groqSummarize('llama-3.3-70b-versatile')

expect(adapter).toBeDefined()
expect(adapter.kind).toBe('summarize')
})

it('throws if GROQ_API_KEY is not set when using groqSummarize', () => {
vi.stubEnv('GROQ_API_KEY', '')

expect(() => groqSummarize('llama-3.3-70b-versatile')).toThrow(
'GROQ_API_KEY is required',
)
})
})
})

describe('Groq AG-UI event emission', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/ai/tests/summarize-max-length.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ describe('ChatStreamSummarizeAdapter — maxLength reaches the wrapped adapter u
expect(opts?.['maxTokens']).toBeUndefined()
})

it('Groq adapter receives maxLength as max_completion_tokens', async () => {
const { textAdapter, lastModelOptions } = createRecordingTextAdapter()
const adapter = new ChatStreamSummarizeAdapter(
textAdapter,
'llama-3.3-70b-versatile',
'groq',
)

await adapter.summarize({
model: 'llama-3.3-70b-versatile',
text: 'hi',
maxLength: 256,
logger,
})

const opts = lastModelOptions()
expect(opts?.['max_completion_tokens']).toBe(256)
expect(opts?.['maxTokens']).toBeUndefined()
})

it('Ollama adapter receives maxLength AND the temperature default nested under options', async () => {
const { textAdapter, lastModelOptions } = createRecordingTextAdapter()
const adapter = new ChatStreamSummarizeAdapter(
Expand Down
2 changes: 2 additions & 0 deletions testing/e2e/src/lib/feature-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export const matrix: Record<Feature, Set<Provider>> = {
'anthropic',
'gemini',
'ollama',
'groq',
'grok',
'bedrock',
'bedrock-responses',
Expand All @@ -212,6 +213,7 @@ export const matrix: Record<Feature, Set<Provider>> = {
'anthropic',
'gemini',
'ollama',
'groq',
'grok',
'bedrock',
'bedrock-responses',
Expand Down
6 changes: 6 additions & 0 deletions testing/e2e/src/routes/api.summarize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createOpenaiSummarize } from '@tanstack/ai-openai'
import { createAnthropicSummarize } from '@tanstack/ai-anthropic'
import { createGeminiSummarize } from '@tanstack/ai-gemini'
import { createOllamaSummarize } from '@tanstack/ai-ollama'
import { createGroqSummarize } from '@tanstack/ai-groq'
import { createGrokSummarize } from '@tanstack/ai-grok'
import { createOpenRouterSummarize } from '@tanstack/ai-openrouter'
import type { Provider } from '@/lib/types'
Expand Down Expand Up @@ -46,6 +47,11 @@ function createSummarizeAdapter(
httpOptions: { baseUrl: llmockBase(aimockPort), headers },
}),
ollama: () => createOllamaSummarize('mistral', llmockBase(aimockPort)),
groq: () =>
createGroqSummarize('llama-3.3-70b-versatile', DUMMY_KEY, {
baseURL: `${llmockBase(aimockPort)}/openai/v1`,
defaultHeaders: headers,
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
grok: () =>
createGrokSummarize('grok-build-0.1', DUMMY_KEY, {
baseURL: openaiUrl(aimockPort),
Expand Down