Merge pull request #1860 from pipecat-ai/mb/organize-otel-demos
Reorganize OpenTelemetry demos, add top-level README
This commit is contained in:
@@ -1,140 +0,0 @@
|
|||||||
# Langfuse Tracing for Pipecat via OpenTelemetry
|
|
||||||
|
|
||||||
This demo showcases [Langfuse](https://langfuse.com) tracing integration for Pipecat services via OpenTelemetry, allowing you to visualize service calls, performance metrics, and dependencies.
|
|
||||||
|
|
||||||
This is a fork of the [OpenTelemetry Tracing for Pipecat](../open-telemetry-tracing) demo, but uses Langfuse instead of Jaeger. In contrast to the original demo, this demo uses the `opentelemetry-exporter-otlp-proto-http` exporter as the `grpc` exporter is not supported by Langfuse.
|
|
||||||
|
|
||||||
Pipecat trace in Langfuse:
|
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/13dd7431-bf5e-42e3-8d6d-2ed84c51195d
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Hierarchical Tracing**: Track entire conversations, turns, and service calls
|
|
||||||
- **Service Tracing**: Detailed spans for TTS, STT, and LLM services with rich context
|
|
||||||
- **TTFB Metrics**: Capture Time To First Byte metrics for latency analysis
|
|
||||||
- **Usage Statistics**: Track character counts for TTS and token usage for LLMs
|
|
||||||
|
|
||||||
## Trace Structure
|
|
||||||
|
|
||||||
Traces are organized hierarchically:
|
|
||||||
|
|
||||||
```
|
|
||||||
Conversation (conversation-uuid)
|
|
||||||
├── turn-1
|
|
||||||
│ ├── stt_deepgramsttservice
|
|
||||||
│ ├── llm_openaillmservice
|
|
||||||
│ └── tts_cartesiattsservice
|
|
||||||
└── turn-2
|
|
||||||
├── stt_deepgramsttservice
|
|
||||||
├── llm_openaillmservice
|
|
||||||
└── tts_cartesiattsservice
|
|
||||||
turn-N
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
This organization helps you track conversation-to-conversation and turn-to-turn.
|
|
||||||
|
|
||||||
## Setup Instructions
|
|
||||||
|
|
||||||
### 1. Create a Langfuse Project and get API keys
|
|
||||||
|
|
||||||
[Self-host](https://langfuse.com/self-hosting) Langfuse or create a free [Langfuse Cloud](https://cloud.langfuse.com) account.
|
|
||||||
Create a new project and get the API keys.
|
|
||||||
|
|
||||||
### 2. Environment Configuration
|
|
||||||
|
|
||||||
Base64 encode your Langfuse public and secret key:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo -n "pk-lf-1234567890:sk-lf-1234567890" | base64
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a `.env` file with your API keys to enable tracing:
|
|
||||||
|
|
||||||
```
|
|
||||||
ENABLE_TRACING=true
|
|
||||||
# OTLP endpoint (defaults to localhost:4317 if not set)
|
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://cloud.langfuse.com/api/public/otel
|
|
||||||
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20<base64_encoded_api_key>
|
|
||||||
# Set to any value to enable console output for debugging
|
|
||||||
# OTEL_CONSOLE_EXPORT=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configure Your Pipeline Task
|
|
||||||
|
|
||||||
Enable tracing in your Pipecat application:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Initialize OpenTelemetry with your chosen exporter
|
|
||||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
||||||
|
|
||||||
# Configured automatically from .env
|
|
||||||
exporter = OTLPSpanExporter()
|
|
||||||
|
|
||||||
setup_tracing(
|
|
||||||
service_name="pipecat-demo",
|
|
||||||
exporter=exporter,
|
|
||||||
console_export=os.getenv("OTEL_CONSOLE_EXPORT", "false").lower() == "true",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enable tracing in your PipelineTask
|
|
||||||
task = PipelineTask(
|
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
allow_interruptions=True,
|
|
||||||
enable_metrics=True, # Required for some service metrics
|
|
||||||
),
|
|
||||||
enable_tracing=True, # Enables both turn and conversation tracing
|
|
||||||
conversation_id="customer-123", # Optional - will auto-generate if not provided
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Run the Demo
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python bot.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. View Traces in Langfuse
|
|
||||||
|
|
||||||
Open your browser to [https://cloud.langfuse.com](https://cloud.langfuse.com) to view traces.
|
|
||||||
|
|
||||||
## Understanding the Traces
|
|
||||||
|
|
||||||
- **Conversation Spans**: The top-level span representing an entire conversation
|
|
||||||
- **Turn Spans**: Child spans of conversations that represent each turn in the dialog
|
|
||||||
- **Service Spans**: Detailed service operations nested under turns
|
|
||||||
- **Service Attributes**: Each service includes rich context about its operation:
|
|
||||||
- **TTS**: Voice ID, character count, service type
|
|
||||||
- **STT**: Transcription text, language, model
|
|
||||||
- **LLM**: Messages, tokens used, model, service configuration
|
|
||||||
- **Metrics**: Performance data like `metrics.ttfb_ms` and processing durations
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The tracing system consists of:
|
|
||||||
|
|
||||||
1. **TurnTrackingObserver**: Detects conversation turns
|
|
||||||
2. **TurnTraceObserver**: Creates spans for turns and conversations
|
|
||||||
3. **Service Decorators**: `@traced_tts`, `@traced_stt`, `@traced_llm` for service-specific tracing
|
|
||||||
4. **Context Providers**: Share context between different parts of the pipeline
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **No Traces in Langfuse**: Ensure that your credentials are correct and follow this [troubleshooting guide](https://langfuse.com/faq/all/missing-traces)
|
|
||||||
- **Debugging Traces**: Set `OTEL_CONSOLE_EXPORT=true` to print traces to the console for debugging
|
|
||||||
- **Missing Metrics**: Check that `enable_metrics=True` in PipelineParams
|
|
||||||
- **Connection Errors**: Verify network connectivity to Langfuse
|
|
||||||
- **Exporter Issues**: Try the Console exporter (`OTEL_CONSOLE_EXPORT=true`) to verify tracing works
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [OpenTelemetry Python Documentation](https://opentelemetry-python.readthedocs.io/)
|
|
||||||
- [Langfuse OpenTelemetry Documentation](https://langfuse.com/docs/opentelemetry/get-started)
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
# OpenTelemetry Tracing for Pipecat
|
|
||||||
|
|
||||||
This demo showcases OpenTelemetry tracing integration for Pipecat services, allowing you to visualize service calls, performance metrics, and dependencies in a Jaeger dashboard.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Hierarchical Tracing**: Track entire conversations, turns, and service calls
|
|
||||||
- **Service Tracing**: Detailed spans for TTS, STT, and LLM services with rich context
|
|
||||||
- **TTFB Metrics**: Capture Time To First Byte metrics for latency analysis
|
|
||||||
- **Usage Statistics**: Track character counts for TTS and token usage for LLMs
|
|
||||||
- **Flexible Exporters**: Use Jaeger, Zipkin, or any OpenTelemetry-compatible backend
|
|
||||||
|
|
||||||
## Trace Structure
|
|
||||||
|
|
||||||
Traces are organized hierarchically:
|
|
||||||
|
|
||||||
```
|
|
||||||
Conversation (conversation-uuid)
|
|
||||||
├── turn-1
|
|
||||||
│ ├── stt_deepgramsttservice
|
|
||||||
│ ├── llm_openaillmservice
|
|
||||||
│ └── tts_cartesiattsservice
|
|
||||||
└── turn-2
|
|
||||||
├── stt_deepgramsttservice
|
|
||||||
├── llm_openaillmservice
|
|
||||||
└── tts_cartesiattsservice
|
|
||||||
turn-N
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
This organization helps you track conversation-to-conversation and turn-to-turn.
|
|
||||||
|
|
||||||
## Setup Instructions
|
|
||||||
|
|
||||||
### 1. Start the Jaeger Container
|
|
||||||
|
|
||||||
Run Jaeger in Docker to collect and visualize traces:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d --name jaeger \
|
|
||||||
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
|
|
||||||
-p 16686:16686 \
|
|
||||||
-p 4317:4317 \
|
|
||||||
-p 4318:4318 \
|
|
||||||
jaegertracing/all-in-one:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Environment Configuration
|
|
||||||
|
|
||||||
Create a `.env` file with your API keys and enable tracing:
|
|
||||||
|
|
||||||
```
|
|
||||||
ENABLE_TRACING=true
|
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # Point to your preferred backend
|
|
||||||
# OTEL_CONSOLE_EXPORT=true # Set to any value for debug output to console
|
|
||||||
|
|
||||||
# Service API keys
|
|
||||||
DEEPGRAM_API_KEY=your_key_here
|
|
||||||
CARTESIA_API_KEY=your_key_here
|
|
||||||
OPENAI_API_KEY=your_key_here
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configure Your Pipeline Task
|
|
||||||
|
|
||||||
Enable tracing in your Pipecat application:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Initialize OpenTelemetry with your chosen exporter
|
|
||||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
||||||
|
|
||||||
exporter = OTLPSpanExporter(
|
|
||||||
endpoint="http://localhost:4317", # Jaeger OTLP endpoint
|
|
||||||
insecure=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
setup_tracing(
|
|
||||||
service_name="pipecat-demo",
|
|
||||||
exporter=exporter,
|
|
||||||
console_export=os.getenv("OTEL_CONSOLE_EXPORT", "false").lower() == "true",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enable tracing in your PipelineTask
|
|
||||||
task = PipelineTask(
|
|
||||||
pipeline,
|
|
||||||
params=PipelineParams(
|
|
||||||
allow_interruptions=True,
|
|
||||||
enable_metrics=True, # Required for some service metrics
|
|
||||||
),
|
|
||||||
enable_tracing=True, # Enables both turn and conversation tracing
|
|
||||||
conversation_id="customer-123", # Optional - will auto-generate if not provided
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Exporter Options
|
|
||||||
|
|
||||||
While this demo uses Jaeger, you can configure any OpenTelemetry-compatible exporter:
|
|
||||||
|
|
||||||
#### Jaeger (Default for the demo)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
||||||
|
|
||||||
exporter = OTLPSpanExporter(
|
|
||||||
endpoint="http://localhost:4317", # Jaeger OTLP endpoint
|
|
||||||
insecure=True,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cloud Providers
|
|
||||||
|
|
||||||
Many cloud providers offer OpenTelemetry-compatible observability services:
|
|
||||||
|
|
||||||
- AWS X-Ray
|
|
||||||
- Google Cloud Trace
|
|
||||||
- Azure Monitor
|
|
||||||
- Datadog APM
|
|
||||||
|
|
||||||
See the OpenTelemetry documentation for specific exporter configurations:
|
|
||||||
https://opentelemetry.io/ecosystem/vendors/
|
|
||||||
|
|
||||||
#### LLM Tracing and Evaluation Providers
|
|
||||||
|
|
||||||
Many LLM-focused tracing and evaluation projects support OpenTelemetry, for example:
|
|
||||||
|
|
||||||
- Langfuse ([integration example](../open-telemetry-tracing-langfuse/))
|
|
||||||
- Arize Phoenix
|
|
||||||
|
|
||||||
### 5. Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Run the Demo
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python bot.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. View Traces in Jaeger
|
|
||||||
|
|
||||||
Open your browser to [http://localhost:16686](http://localhost:16686) and select the "pipecat-demo" service to view traces.
|
|
||||||
|
|
||||||
## Understanding the Traces
|
|
||||||
|
|
||||||
- **Conversation Spans**: The top-level span representing an entire conversation
|
|
||||||
- **Turn Spans**: Child spans of conversations that represent each turn in the dialog
|
|
||||||
- **Service Spans**: Detailed service operations nested under turns
|
|
||||||
- **Service Attributes**: Each service includes rich context about its operation:
|
|
||||||
- **TTS**: Voice ID, character count, service type
|
|
||||||
- **STT**: Transcription text, language, model
|
|
||||||
- **LLM**: Messages, tokens used, model, service configuration
|
|
||||||
- **Metrics**: Performance data like `metrics.ttfb_ms` and processing durations
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The tracing system consists of:
|
|
||||||
|
|
||||||
1. **TurnTrackingObserver**: Detects conversation turns
|
|
||||||
2. **TurnTraceObserver**: Creates spans for turns and conversations
|
|
||||||
3. **Service Decorators**: `@traced_tts`, `@traced_stt`, `@traced_llm` for service-specific tracing
|
|
||||||
4. **Context Providers**: Share context between different parts of the pipeline
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **No Traces in Jaeger**: Ensure the Docker container is running and the OTLP endpoint is correct
|
|
||||||
- **Debugging Traces**: Set `OTEL_CONSOLE_EXPORT=true` to print traces to the console for debugging
|
|
||||||
- **Missing Metrics**: Check that `enable_metrics=True` in PipelineParams
|
|
||||||
- **Connection Errors**: Verify network connectivity to the Jaeger container
|
|
||||||
- **Exporter Issues**: Try the Console exporter (`OTEL_CONSOLE_EXPORT=true`) to verify tracing works
|
|
||||||
- **Other Backends**: If using a different backend, ensure you've configured the correct exporter and endpoint
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [OpenTelemetry Python Documentation](https://opentelemetry-python.readthedocs.io/)
|
|
||||||
- [Jaeger Documentation](https://www.jaegertracing.io/docs/latest/)
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2024–2025, Daily
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
|
||||||
#
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from inspect import iscoroutinefunction, signature
|
|
||||||
from typing import Any, Callable, Dict, Optional, Tuple
|
|
||||||
|
|
||||||
import uvicorn
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from fastapi import BackgroundTasks, FastAPI
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
from loguru import logger
|
|
||||||
from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
|
|
||||||
|
|
||||||
from pipecat.transports.network.webrtc_connection import IceServer, SmallWebRTCConnection
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv(override=True)
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
# Store connections by pc_id
|
|
||||||
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
|
||||||
|
|
||||||
ice_servers = [
|
|
||||||
IceServer(
|
|
||||||
urls="stun:stun.l.google.com:19302",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Mount the frontend at /
|
|
||||||
app.mount("/client", SmallWebRTCPrebuiltUI)
|
|
||||||
|
|
||||||
# Store program arguments
|
|
||||||
args: argparse.Namespace = argparse.Namespace()
|
|
||||||
|
|
||||||
# Store the bot module and function info
|
|
||||||
bot_module: Any = None
|
|
||||||
run_bot_func: Optional[Callable] = None
|
|
||||||
is_webrtc_bot: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
def import_bot_file(file_path: str) -> Tuple[Any, Callable, bool]:
|
|
||||||
"""Dynamically import the bot file and determine how to run it.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple: (module, run_function, is_webrtc_bot)
|
|
||||||
- module: The imported module
|
|
||||||
- run_function: Either run_bot or main function
|
|
||||||
- is_webrtc_bot: True if run_bot function exists and accepts a WebRTC connection
|
|
||||||
"""
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
raise FileNotFoundError(f"Bot file not found: {file_path}")
|
|
||||||
|
|
||||||
# Extract module name without extension
|
|
||||||
module_name = os.path.splitext(os.path.basename(file_path))[0]
|
|
||||||
|
|
||||||
# Load the module
|
|
||||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
||||||
if not spec or not spec.loader:
|
|
||||||
raise ImportError(f"Could not load spec for {file_path}")
|
|
||||||
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
sys.modules[module_name] = module
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
|
|
||||||
# Check for run_bot function first
|
|
||||||
if hasattr(module, "run_bot"):
|
|
||||||
run_func = module.run_bot
|
|
||||||
# Check if the function accepts a WebRTC connection
|
|
||||||
sig = signature(run_func)
|
|
||||||
is_webrtc = len(sig.parameters) > 0
|
|
||||||
return module, run_func, is_webrtc
|
|
||||||
|
|
||||||
# Fall back to main function
|
|
||||||
if hasattr(module, "main") and iscoroutinefunction(module.main):
|
|
||||||
return module, module.main, False
|
|
||||||
|
|
||||||
raise AttributeError(f"No run_bot or async main function found in {file_path}")
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", include_in_schema=False)
|
|
||||||
async def root_redirect():
|
|
||||||
return RedirectResponse(url="/client/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/offer")
|
|
||||||
async def offer(request: dict, background_tasks: BackgroundTasks):
|
|
||||||
global run_bot_func, is_webrtc_bot
|
|
||||||
|
|
||||||
if not run_bot_func:
|
|
||||||
raise RuntimeError("No bot file has been loaded")
|
|
||||||
|
|
||||||
if not is_webrtc_bot:
|
|
||||||
return {
|
|
||||||
"error": "This bot doesn't support WebRTC connections, it's running in standalone mode"
|
|
||||||
}
|
|
||||||
|
|
||||||
pc_id = request.get("pc_id")
|
|
||||||
|
|
||||||
if pc_id and pc_id in pcs_map:
|
|
||||||
pipecat_connection = pcs_map[pc_id]
|
|
||||||
logger.info(f"Reusing existing connection for pc_id: {pc_id}")
|
|
||||||
await pipecat_connection.renegotiate(
|
|
||||||
sdp=request["sdp"], type=request["type"], restart_pc=request.get("restart_pc", False)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
pipecat_connection = SmallWebRTCConnection(ice_servers)
|
|
||||||
await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
|
|
||||||
|
|
||||||
@pipecat_connection.event_handler("closed")
|
|
||||||
async def handle_disconnected(webrtc_connection: SmallWebRTCConnection):
|
|
||||||
logger.info(f"Discarding peer connection for pc_id: {webrtc_connection.pc_id}")
|
|
||||||
pcs_map.pop(webrtc_connection.pc_id, None)
|
|
||||||
|
|
||||||
# We've already checked that run_bot_func exists
|
|
||||||
assert run_bot_func is not None
|
|
||||||
background_tasks.add_task(run_bot_func, pipecat_connection, args)
|
|
||||||
|
|
||||||
answer = pipecat_connection.get_answer()
|
|
||||||
# Updating the peer connection inside the map
|
|
||||||
pcs_map[answer["pc_id"]] = pipecat_connection
|
|
||||||
|
|
||||||
return answer
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
yield # Run app
|
|
||||||
coros = [pc.close() for pc in pcs_map.values()]
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
pcs_map.clear()
|
|
||||||
|
|
||||||
|
|
||||||
async def run_standalone_bot() -> None:
|
|
||||||
"""Run a standalone bot that doesn't require WebRTC"""
|
|
||||||
global run_bot_func
|
|
||||||
if run_bot_func is not None:
|
|
||||||
await run_bot_func()
|
|
||||||
else:
|
|
||||||
raise RuntimeError("No bot function available to run")
|
|
||||||
|
|
||||||
|
|
||||||
def main(parser: Optional[argparse.ArgumentParser] = None):
|
|
||||||
global args
|
|
||||||
|
|
||||||
if not parser:
|
|
||||||
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
|
||||||
parser.add_argument("bot_file", nargs="?", help="Path to the bot file", default=None)
|
|
||||||
parser.add_argument(
|
|
||||||
"--host", default="localhost", help="Host for HTTP server (default: localhost)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--port", type=int, default=7860, help="Port for HTTP server (default: 7860)"
|
|
||||||
)
|
|
||||||
parser.add_argument("--verbose", "-v", action="count", default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logger.remove(0)
|
|
||||||
if args.verbose:
|
|
||||||
logger.add(sys.stderr, level="TRACE")
|
|
||||||
else:
|
|
||||||
logger.add(sys.stderr, level="DEBUG")
|
|
||||||
|
|
||||||
# Infer the bot file from the caller if not provided explicitly
|
|
||||||
bot_file = args.bot_file
|
|
||||||
if bot_file is None:
|
|
||||||
# Get the __file__ of the script that called main()
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
caller_frame = inspect.stack()[1]
|
|
||||||
caller_globals = caller_frame.frame.f_globals
|
|
||||||
bot_file = caller_globals.get("__file__")
|
|
||||||
|
|
||||||
if not bot_file:
|
|
||||||
print("❌ Could not determine the bot file. Pass it explicitly to main().")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Import the bot file
|
|
||||||
try:
|
|
||||||
global run_bot_func, bot_module, is_webrtc_bot
|
|
||||||
bot_module, run_bot_func, is_webrtc_bot = import_bot_file(bot_file)
|
|
||||||
logger.info(f"Successfully loaded bot from {bot_file}")
|
|
||||||
|
|
||||||
if is_webrtc_bot:
|
|
||||||
logger.info("Detected WebRTC-compatible bot, starting web server...")
|
|
||||||
uvicorn.run(app, host=args.host, port=args.port)
|
|
||||||
else:
|
|
||||||
logger.info("Detected standalone bot, running directly...")
|
|
||||||
asyncio.run(run_standalone_bot())
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error loading bot file: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
69
examples/open-telemetry/README.md
Normal file
69
examples/open-telemetry/README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# OpenTelemetry Tracing with Pipecat
|
||||||
|
|
||||||
|
This repository demonstrates OpenTelemetry tracing integration for Pipecat services, with examples for different backends.
|
||||||
|
|
||||||
|
## Tracing Features in Pipecat
|
||||||
|
|
||||||
|
- **Hierarchical Tracing**: Track entire conversations, turns, and service calls
|
||||||
|
- **Service Tracing**: Detailed spans for TTS, STT, and LLM services with rich context
|
||||||
|
- **TTFB Metrics**: Capture Time To First Byte metrics for latency analysis
|
||||||
|
- **Usage Statistics**: Track character counts for TTS and token usage for LLMs
|
||||||
|
|
||||||
|
## Trace Structure
|
||||||
|
|
||||||
|
Traces are organized hierarchically:
|
||||||
|
|
||||||
|
```
|
||||||
|
Conversation (conversation)
|
||||||
|
├── turn
|
||||||
|
│ ├── stt_deepgramsttservice
|
||||||
|
│ ├── llm_openaillmservice
|
||||||
|
│ └── tts_cartesiattsservice
|
||||||
|
└── turn
|
||||||
|
├── stt_deepgramsttservice
|
||||||
|
├── llm_openaillmservice
|
||||||
|
└── tts_cartesiattsservice
|
||||||
|
turn
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This organization helps you track conversation-to-conversation and turn-to-turn interactions.
|
||||||
|
|
||||||
|
## Available Demos
|
||||||
|
|
||||||
|
| Demo | Description |
|
||||||
|
| ------------------------------- | ------------------------------------------------------------------------- |
|
||||||
|
| [Jaeger Tracing](./jaeger/) | Tracing with Jaeger, an open-source end-to-end distributed tracing system |
|
||||||
|
| [Langfuse Tracing](./langfuse/) | Tracing with Langfuse, a specialized platform for LLM observability |
|
||||||
|
|
||||||
|
## Common Requirements
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- Pipecat and its dependencies
|
||||||
|
- API keys for the services used (Deepgram, Cartesia, OpenAI)
|
||||||
|
- The appropriate OpenTelemetry exporters
|
||||||
|
|
||||||
|
## How Tracing Works
|
||||||
|
|
||||||
|
The tracing system consists of:
|
||||||
|
|
||||||
|
1. **TurnTrackingObserver**: Detects conversation turns
|
||||||
|
2. **TurnTraceObserver**: Creates spans for turns and conversations
|
||||||
|
3. **Service Decorators**: `@traced_tts`, `@traced_stt`, `@traced_llm` for service-specific tracing
|
||||||
|
4. **Context Providers**: Share context between different parts of the pipeline
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Choose one of the demos from the table above
|
||||||
|
2. Follow the README instructions in the respective directory
|
||||||
|
|
||||||
|
## Common Troubleshooting
|
||||||
|
|
||||||
|
- **Debugging Traces**: Set `OTEL_CONSOLE_EXPORT=true` to print traces to the console for debugging
|
||||||
|
- **Missing Metrics**: Check that `enable_metrics=True` in PipelineParams
|
||||||
|
- **API Key Issues**: Verify your API keys are set correctly in the .env file
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [OpenTelemetry Python Documentation](https://opentelemetry-python.readthedocs.io/)
|
||||||
|
- [Pipecat Documentation](https://docs.pipecat.ai/server/utilities/opentelemetry)
|
||||||
80
examples/open-telemetry/jaeger/README.md
Normal file
80
examples/open-telemetry/jaeger/README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Jaeger Tracing for Pipecat
|
||||||
|
|
||||||
|
This demo showcases OpenTelemetry tracing integration for Pipecat services using Jaeger, allowing you to visualize service calls, performance metrics, and dependencies.
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Start the Jaeger Container
|
||||||
|
|
||||||
|
Run Jaeger in Docker to collect and visualize traces:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name jaeger \
|
||||||
|
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
|
||||||
|
-p 16686:16686 \
|
||||||
|
-p 4317:4317 \
|
||||||
|
-p 4318:4318 \
|
||||||
|
jaegertracing/all-in-one:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Environment Configuration
|
||||||
|
|
||||||
|
Create a `.env` file with your API keys and enable tracing:
|
||||||
|
|
||||||
|
```
|
||||||
|
ENABLE_TRACING=true
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # Point to your Jaeger backend
|
||||||
|
# OTEL_CONSOLE_EXPORT=true # Set to any value for debug output to console
|
||||||
|
|
||||||
|
# Service API keys
|
||||||
|
DEEPGRAM_API_KEY=your_key_here
|
||||||
|
CARTESIA_API_KEY=your_key_here
|
||||||
|
OPENAI_API_KEY=your_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run the Demo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. View Traces in Jaeger
|
||||||
|
|
||||||
|
Open your browser to [http://localhost:16686](http://localhost:16686) and select the "pipecat-demo" service to view traces.
|
||||||
|
|
||||||
|
## Jaeger-Specific Configuration
|
||||||
|
|
||||||
|
In the `bot.py` file, note the GRPC exporter configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||||
|
|
||||||
|
# Create the exporter
|
||||||
|
otlp_exporter = OTLPSpanExporter(
|
||||||
|
endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"),
|
||||||
|
insecure=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up tracing with the exporter
|
||||||
|
setup_tracing(
|
||||||
|
service_name="pipecat-demo",
|
||||||
|
exporter=otlp_exporter,
|
||||||
|
console_export=bool(os.getenv("OTEL_CONSOLE_EXPORT")),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **No Traces in Jaeger**: Ensure the Docker container is running and the OTLP endpoint is correct
|
||||||
|
- **Connection Errors**: Verify network connectivity to the Jaeger container
|
||||||
|
- **Exporter Issues**: Try the Console exporter (`OTEL_CONSOLE_EXPORT=true`) to verify tracing works
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Jaeger Documentation](https://www.jaegertracing.io/docs/latest/)
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -154,6 +155,7 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
from run import main
|
from run import main
|
||||||
|
|
||||||
main()
|
main()
|
||||||
82
examples/open-telemetry/langfuse/README.md
Normal file
82
examples/open-telemetry/langfuse/README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Langfuse Tracing for Pipecat
|
||||||
|
|
||||||
|
This demo showcases [Langfuse](https://langfuse.com) tracing integration for Pipecat services via OpenTelemetry, allowing you to visualize service calls, performance metrics, and dependencies with a focus on LLM observability.
|
||||||
|
|
||||||
|
Pipecat trace in Langfuse:
|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/13dd7431-bf5e-42e3-8d6d-2ed84c51195d
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Create a Langfuse Project and get API keys
|
||||||
|
|
||||||
|
[Self-host](https://langfuse.com/self-hosting) Langfuse or create a free [Langfuse Cloud](https://cloud.langfuse.com) account.
|
||||||
|
Create a new project and get the API keys.
|
||||||
|
|
||||||
|
### 2. Environment Configuration
|
||||||
|
|
||||||
|
Base64 encode your Langfuse public and secret key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo -n "pk-lf-1234567890:sk-lf-1234567890" | base64
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `.env` file with your API keys to enable tracing:
|
||||||
|
|
||||||
|
```
|
||||||
|
ENABLE_TRACING=true
|
||||||
|
# OTLP endpoint for Langfuse
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://cloud.langfuse.com/api/public/otel
|
||||||
|
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20<base64_encoded_api_key>
|
||||||
|
# Set to any value to enable console output for debugging
|
||||||
|
# OTEL_CONSOLE_EXPORT=true
|
||||||
|
|
||||||
|
# Service API keys
|
||||||
|
DEEPGRAM_API_KEY=your_key_here
|
||||||
|
CARTESIA_API_KEY=your_key_here
|
||||||
|
OPENAI_API_KEY=your_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run the Demo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. View Traces in Langfuse
|
||||||
|
|
||||||
|
Open your browser to [https://cloud.langfuse.com](https://cloud.langfuse.com) to view traces.
|
||||||
|
|
||||||
|
## Langfuse-Specific Configuration
|
||||||
|
|
||||||
|
In the `bot.py` file, note the HTTP exporter configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||||
|
|
||||||
|
# Create the exporter - configured from environment variables
|
||||||
|
otlp_exporter = OTLPSpanExporter()
|
||||||
|
|
||||||
|
# Set up tracing with the exporter
|
||||||
|
setup_tracing(
|
||||||
|
service_name="pipecat-demo",
|
||||||
|
exporter=otlp_exporter,
|
||||||
|
console_export=bool(os.getenv("OTEL_CONSOLE_EXPORT")),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **No Traces in Langfuse**: Ensure that your credentials are correct and follow this [troubleshooting guide](https://langfuse.com/faq/all/missing-traces)
|
||||||
|
- **Connection Errors**: Verify network connectivity to Langfuse
|
||||||
|
- **Authorization Issues**: Check that your base64 encoding is correct and the API keys are valid
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Langfuse OpenTelemetry Documentation](https://langfuse.com/docs/opentelemetry/get-started)
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -151,6 +152,7 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
from run import main
|
from run import main
|
||||||
|
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user