LLM Providers
Loopstack supports multiple LLM providers through a runtime registry. Provider modules self-register at startup. Workflows and tools resolve providers by name — swap or use multiple providers in parallel without changing workflow code.
Quick Start
Import LlmProviderModule for the adapter tools and a provider module (e.g. ClaudeModule) to register the LLM backend:
import { ClaudeModule } from '@loopstack/claude-module';
import { LlmProviderModule } from '@loopstack/llm-provider-module';
@Module({
imports: [LoopstackModule.forRoot(), LlmProviderModule.forRoot({}), ClaudeModule],
})
export class AppModule {}Module-Level Defaults
Use LlmProviderModule.forRoot() to set a default model for all LLM calls in your app. Use forFeature() to override per-module:
// app.module.ts — global default model
@Module({
imports: [
LoopstackModule.forRoot(),
LlmProviderModule.forRoot({ model: 'claude-sonnet-4-5' }),
ClaudeModule,
],
})
export class AppModule {}// premium-feature.module.ts — this module uses a stronger model
@Module({
imports: [LlmProviderModule.forFeature({ model: 'claude-opus-4-6' })],
providers: [PremiumWorkflow],
})
export class PremiumFeatureModule {}Per-Call Configuration
Override provider and model at individual call sites via options.config. Per-call config always takes priority over module defaults.
export class MyWorkflow extends BaseWorkflow {
constructor(
private readonly llmGenerateText: LlmGenerateTextTool,
private readonly llmDelegateToolCalls: LlmDelegateToolCallsTool,
) {
super();
}
}const result = await this.llmGenerateText.call(
{ prompt: 'Hello!' },
{
config: {
provider: 'claude',
model: 'claude-opus-4-6',
system: 'You are a helpful assistant.',
messagesSearchTag: 'message',
tools: ['get_weather'],
},
},
);Args vs Config
LLM tools separate args (per-request data) from config (provider/model/behavior settings):
| Parameter | Location | Description |
|---|---|---|
prompt | args | Simple prompt string |
messages | args | Explicit message array |
outputSchema | args | JSON Schema (generate object only) |
provider | config | LLM provider name (e.g. 'claude') |
model | config | Model name (e.g. 'claude-sonnet-4-6') |
system | config | System prompt |
messagesSearchTag | config | Load messages from documents by tag |
tools | config | Tool names the LLM can call |
Using Multiple Providers
Import both modules and configure each call with its provider:
@Module({
imports: [LoopstackModule.forRoot(), ClaudeModule, OpenAiModule],
})
export class AppModule {}// Use Claude for complex tasks
const smartResult = await this.llmGenerateText.call(
{ prompt: 'Analyze this code...' },
{ config: { provider: 'claude', model: 'claude-opus-4-6' } },
);
// Use OpenAI for simple tasks
const fastResult = await this.llmGenerateText.call(
{ prompt: 'Summarize in one line...' },
{ config: { provider: 'openai', model: 'gpt-4o-mini' } },
);Adapter Tools
All LLM interactions go through adapter tools from @loopstack/llm-provider-module. This ensures validation, interceptors, and logging apply to every LLM call.
| Tool | Purpose |
|---|---|
LlmGenerateTextTool | Text generation with optional tool calling |
LlmGenerateObjectTool | Structured output conforming to a JSON Schema |
LlmDelegateToolCallsTool | Execute tool calls from an LLM response |
LlmUpdateToolResultTool | Handle async tool completion callbacks |
Message Documents
All providers share a single LlmMessageDocument with normalized content. Native API responses are stored in entity.meta.response for provider-specific round-trips.
| Document | Content Format | Widget |
|---|---|---|
LlmMessageDocument | Normalized (text, thinking, tool_call blocks) | llm-message |
Environment Variables
| Variable | Provider | Description |
|---|---|---|
ANTHROPIC_API_KEY | Claude | API key |
OPENAI_API_KEY | OpenAI | API key |
CLAUDE_MODEL | Claude | Default model fallback |
OPENAI_MODEL | OpenAI | Default model fallback |
Available Providers
| Provider | Module | ID |
|---|---|---|
| Anthropic Claude | @loopstack/claude-module | 'claude' |
| OpenAI | @loopstack/openai-module | 'openai' |