A Symfony bundle for integrating with Langfuse, providing AI operation tracing, prompt management, and observability for AI applications built with the Lingoda AI SDK.
- 🔍 Automatic AI Tracing: Traces all AI operations (completions, TTS, STT, translations)
- ⚡ Async/Sync Flushing: Configurable sync or async trace processing via Symfony Messenger
- 📝 Prompt Management: Fetch, cache, and manage prompts with intelligent cascade (Cache → API → Storage)
- 💰 Usage Metrics: Automatic token counting and cost tracking with proper generation support
- 🗄️ Flexible Storage: Path-based or Flysystem storage for prompt fallbacks
- 🔧 Console Commands: Connection testing and prompt management tools
- PHP 8.3 or higher
- Symfony 6.4 or 7.0+
- Lingoda AI Bundle 1.2+ (the bundle decorates the Lingoda AI SDK)
- Dropsolid Langfuse PHP SDK 1.2+
- Install the bundle via Composer:
composer require lingoda/langfuse-bundle- Add the bundle to
config/bundles.php:
return [
// ...
Lingoda\LangfuseBundle\LingodaLangfuseBundle::class => ['all' => true],
];- Configure the bundle in
config/packages/lingoda_langfuse.yaml:
lingoda_langfuse:
connection:
public_key: '%env(LANGFUSE_PUBLIC_KEY)%'
secret_key: '%env(LANGFUSE_SECRET_KEY)%'
host: '%env(default:LANGFUSE_HOST:https://cloud.langfuse.com)%'
timeout: 30
retry:
max_attempts: 3
delay: 1000
tracing:
enabled: true
sampling_rate: 1.0
async_flush:
enabled: false
message_bus: 'messenger.bus.default'
prompts:
caching:
enabled: false
ttl: 3600
service: 'cache.app'
fallback:
enabled: true
storage:
path: '%kernel.project_dir%/var/prompts'The bundle automatically traces all AI operations by decorating the Lingoda AI SDK's PlatformInterface:
use Lingoda\AiSdk\PlatformInterface;
class ContentService
{
public function __construct(
private PlatformInterface $platform
) {}
public function generateContent(string $prompt): string
{
// This call is automatically traced to Langfuse with proper generation and usage metrics
$result = $this->platform->ask($prompt, 'gpt-4');
return $result->getContent();
}
public function generateAudio(string $text): string
{
// TTS operations are also traced
$audio = $this->platform->textToSpeech($text);
return $audio->getContent();
}
}Automatic trace data includes:
- Operation type (ai-completion, text-to-speech, etc.)
- Model information and provider resolution
- Input/output content and metadata
- Duration and timing information
- Usage metrics (prompt tokens, completion tokens, total tokens)
- Proper Langfuse generation structure for cost tracking
- Error handling and status tracking
The bundle automatically extracts and sends usage metrics in the proper format for Langfuse:
- Prompt tokens: Input token count
- Completion tokens: Output token count
- Total tokens: Combined count
- Model information: For accurate cost calculation
- Proper generation structure: Trace → Generation hierarchy
This enables accurate cost tracking and usage analysis in the Langfuse dashboard.
For production applications, enable asynchronous trace processing to eliminate latency impact:
# config/packages/messenger.yaml
framework:
messenger:
transports:
langfuse_traces:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%/langfuse-traces'
retry_strategy:
max_retries: 3
delay: 1000
multiplier: 2
routing:
'Lingoda\LangfuseBundle\Message\FlushLangfuseTrace': langfuse_traces# config/packages/lingoda_langfuse.yaml
lingoda_langfuse:
tracing:
enabled: true
sampling_rate: 1.0
async_flush:
enabled: true
message_bus: 'messenger.bus.default' # optional, defaults to messenger.bus.defaultphp bin/console messenger:consume langfuse_traces --time-limit=3600
# or multiple workers for high throughput
php bin/console messenger:consume langfuse_traces --limit=100
php bin/console messenger:consume langfuse_traces --limit=100
...Benefits:
- Zero latency impact on AI operations
- Higher throughput for AI-intensive applications
- Reliable delivery with retry mechanisms
- Scalable processing with multiple workers
The bundle provides a complete prompt management system with intelligent caching and fallback storage:
use Lingoda\LangfuseBundle\Prompt\PromptRegistryInterface;
use Lingoda\AiSdk\Prompt\Conversation;
class EmailService
{
public function __construct(
private PromptRegistryInterface $prompts
) {}
public function generateWelcomeEmail(string $userName): Conversation
{
// Intelligent cascade: Cache → API → Storage
return $this->prompts->get('welcome_email');
}
public function generateCustomEmail(string $promptName, array $variables): Conversation
{
// Get specific version with variables
$prompt = $this->prompts->getCompiled($promptName, $variables);
return $prompt;
}
}Prompt Flow:
- Cache lookup - Fast in-memory retrieval
- API call - Fresh fetch from Langfuse if cache miss
- Storage fallback - Local storage if API unavailable
- Automatic caching - Cache API results for future use
Path-based Storage (Default):
lingoda_langfuse:
prompts:
fallback:
storage:
path: '%kernel.project_dir%/var/prompts'Flysystem Integration (Cloud Storage):
# Configure Flysystem adapter first
flysystem:
storages:
prompts.storage:
adapter: 's3'
options:
bucket: 'my-prompts-bucket'
region: 'us-east-1'
# Reference in Langfuse config
lingoda_langfuse:
prompts:
fallback:
storage:
service: 'prompts.storage'For manual tracing beyond automatic AI operations:
use Lingoda\LangfuseBundle\Client\TraceClient;
class AnalyticsService
{
public function __construct(
private TraceClient $traceClient
) {}
public function processUserAction(User $user, string $action): void
{
$trace = $this->traceClient->trace([
'name' => 'user-action',
'userId' => $user->getId(),
'metadata' => ['action' => $action],
'input' => ['timestamp' => time()]
]);
// For AI operations, create a generation
if ($action === 'ai-query') {
$generation = $trace->createGeneration(
name: 'ai-completion',
model: 'gpt-4',
input: ['query' => 'user question']
);
// Set usage details for cost tracking
$generation->withUsageDetails([
'prompt_tokens' => 15,
'completion_tokens' => 8,
'total_tokens' => 23
]);
$generation->end(['output' => 'AI response']);
}
$trace->end([
'output' => ['status' => 'completed'],
'statusMessage' => 'Action processed successfully'
]);
// Manually flush if needed (automatic in most cases)
$this->traceClient->flush();
}
}lingoda_langfuse:
# Connection settings
connection:
public_key: string # Required: Langfuse public key
secret_key: string # Required: Langfuse secret key
host: string # Default: https://cloud.langfuse.com
timeout: int # Default: 30 (seconds)
retry:
max_attempts: int # Default: 3
delay: int # Default: 1000 (milliseconds)
# Tracing configuration
tracing:
enabled: bool # Default: true
sampling_rate: float # Default: 1.0 (0.0-1.0)
async_flush:
enabled: bool # Default: false
message_bus: string # Default: 'messenger.default_bus'
# Prompt management
prompts:
caching:
enabled: bool # Default: false
ttl: int # Default: 3600 (seconds)
service: string # Default: 'cache.app'
fallback:
enabled: bool # Default: false (auto-enabled with storage config)
storage:
path: string # File system path
# OR
service: string # Flysystem service IDVerify your Langfuse API credentials and connection:
php bin/console langfuse:test-connectionCache specific prompts from Langfuse to local fallback storage:
# Cache specific prompts
php bin/console langfuse:cache-prompt --prompt=greeting --prompt=goodbye
# Preview without saving
php bin/console langfuse:cache-prompt --prompt=greeting --dry-run
# Force overwrite existing cached prompts
php bin/console langfuse:cache-prompt --prompt=greeting --forceOptions:
--prompt|-p: Specify prompt name(s) to cache (required, can be used multiple times)--dry-run: Preview what would be cached without saving files--force: Overwrite existing cached prompts
Sync Mode (Default):
- Immediate trace delivery
- ~50-100ms latency per operation
- Guaranteed delivery
- Good for low-traffic applications
Async Mode:
- Zero latency impact on AI operations
- Requires message queue infrastructure
- Higher throughput potential
- Eventual consistency
Sampling Configuration:
lingoda_langfuse:
tracing:
sampling_rate: 0.1 # Trace 10% of operations# Install dependencies
composer install
# Run PHPUnit tests
vendor/bin/phpunit
# Run static analysis
vendor/bin/phpstan analyse
# Check code style
vendor/bin/ecs check
# Fix code style
vendor/bin/ecs check --fixThe bundle follows clean architecture principles with focused, single-responsibility services:
- LangfusePlatformDecorator: Decorates AI SDK to enable automatic tracing
- TraceManager: Coordinates trace data preparation and timing
- SyncTraceFlusher: Handles synchronous flushing with generation support
- AsyncTraceFlusher: Dispatches traces to message queue
- FlushLangfuseTraceHandler: Processes async messages (delegates to sync flusher)
- PromptRegistry: Manages prompt lifecycle with caching and storage
- PromptClient: Handles API communication with Langfuse
- TraceClient: Direct trace creation and management
AI Operation → Platform Decorator → TraceManager → FlushService → Langfuse
↓ ↓
TraceFlusherInterface
↓
SyncTraceFlusher
↓
Creates Trace → Generation → Usage Details
↓
Langfuse API
Async Flow:
AI Operation → TraceManager → AsyncTraceFlusher → Message Queue
↓
FlushLangfuseTraceHandler
↓
SyncTraceFlusher
↓
Langfuse API
Request → PromptRegistry → Cache → Langfuse API → Storage
↓ ↓ ↓
Fast Return Fresh Data Fallback
- Direct Langfuse Integration: Clean, direct API communication without intermediary layers
- Simple Trace Structure: AI operations create trace → generation structure
- Async/Sync Flexibility: Choose between immediate or queued processing
The bundle uses a dedicated langfuse Monolog channel for all operations. Configure it in your Monolog configuration:
# config/packages/monolog.yaml
monolog:
channels:
- langfuse
handlers:
langfuse:
type: stream
path: '%kernel.logs_dir%/langfuse.log'
level: debug
channels: [langfuse]- Connection failures: Verify your API keys and host URL using
langfuse:test-connection - Missing prompts: Use
langfuse:cache-promptto cache prompts locally for offline access - Async processing not working: Ensure Symfony Messenger is configured and consumers are running
- Token usage not tracked: Verify the AI SDK returns proper usage metrics
Enable debug logging to troubleshoot issues:
monolog:
handlers:
langfuse:
level: debug # Set to debug levelMIT License. See LICENSE for details.