Skip to content

Commit c9a2acb

Browse files
ASCE-Dgithub-actions[bot]nndn
authored
cleanup up version (#482)
* cleanup up version * chore: Auto-fix pre-commit issues * code rabbit suggestions * update litellm version * chore: Auto-fix pre-commit issues --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Nandan <[email protected]>
1 parent c41d981 commit c9a2acb

File tree

9 files changed

+285
-2
lines changed

9 files changed

+285
-2
lines changed

.env.template

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ POSTHOG_API_KEY=
7171
POSTHOG_HOST=
7272
FIRECRAWL_API_KEY=
7373

74+
# Phoenix Tracing Configuration
75+
# Set PHOENIX_ENABLED=false to disable tracing
76+
PHOENIX_ENABLED=true
77+
# Phoenix collector endpoint (default: local Phoenix server)
78+
PHOENIX_COLLECTOR_ENDPOINT=http://localhost:6006
79+
# Project name shown in Phoenix UI (default: "potpie-ai")
80+
PHOENIX_PROJECT_NAME=potpie-ai
7481
# GitHub Authentication Configuration
7582
# GH_TOKEN_LIST: Personal Access Tokens for GitHub.com (comma-separated for token pool)
7683
# GITHUB_APP_ID + GITHUB_PRIVATE_KEY: GitHub App credentials (recommended for production)

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ Potpie provides a set of tools that agents can use to interact with the knowledg
264264
- Start the FastAPI application
265265
- Start the Celery worker
266266

267+
**Optional: Phoenix Tracing Setup (Local Only)**
268+
269+
To monitor LLM traces and agent operations with Phoenix in local development:
270+
```bash
271+
phoenix serve
272+
```
273+
Run this in a new terminal to start the Phoenix server. Traces will be available at `http://localhost:6006` (default). Phoenix tracing is automatically initialized when Potpie starts, but you need to run `phoenix serve` separately to view the traces. **Note:** This setup is for local development only.
274+
267275
3. **Stop Potpie**
268276

269277
To stop all Potpie services:

app/celery/celery_app.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,22 @@ def configure_celery(queue_prefix: str):
8080

8181
configure_celery(queue_name)
8282

83-
# Import the lock decorator
83+
84+
def setup_phoenix_tracing():
85+
"""Initialize Phoenix tracing for LLM monitoring in Celery workers."""
86+
try:
87+
from app.modules.intelligence.tracing.phoenix_tracer import (
88+
initialize_phoenix_tracing,
89+
)
90+
91+
initialize_phoenix_tracing()
92+
except Exception as e:
93+
logger.warning(
94+
f"Phoenix tracing initialization failed in Celery worker (non-fatal): {e}"
95+
)
96+
97+
98+
setup_phoenix_tracing()
8499

85100
# Import the lock decorator
86101
from celery.contrib.abortable import AbortableTask # noqa

app/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def __init__(self):
5252
)
5353
exit(1)
5454
self.setup_sentry()
55+
self.setup_phoenix_tracing()
5556
self.app = FastAPI()
5657
self.setup_cors()
5758
self.include_routers()
@@ -64,6 +65,16 @@ def setup_sentry(self):
6465
profiles_sample_rate=1.0,
6566
)
6667

68+
def setup_phoenix_tracing(self):
69+
try:
70+
from app.modules.intelligence.tracing.phoenix_tracer import (
71+
initialize_phoenix_tracing,
72+
)
73+
74+
initialize_phoenix_tracing()
75+
except Exception as e:
76+
logging.warning(f"Phoenix tracing initialization failed (non-fatal): {e}")
77+
6778
def setup_cors(self):
6879
origins = ["*"]
6980
self.app.add_middleware(

app/modules/integrations/integrations_router.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ async def linear_oauth_callback(
321321

322322
# Save the integration (this will exchange code for tokens)
323323
save_result = await integrations_service.save_linear_integration(
324-
save_request,
324+
save_request,
325325
user_id,
326326
)
327327

app/modules/intelligence/agents/chat_agents/pydantic_agent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def _create_agent(self, ctx: ChatContext) -> Agent:
139139
"defer_model_check": True,
140140
"end_strategy": "exhaustive",
141141
"model_settings": {"max_tokens": 14000},
142+
"instrument": True,
142143
}
143144

144145
if not allow_parallel_tools:

app/modules/intelligence/tracing/__init__.py

Whitespace-only changes.
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
"""
2+
Phoenix Tracing Integration for LLM Monitoring
3+
4+
This module sets up Arize Phoenix tracing for monitoring:
5+
- Pydantic AI agent operations (agent runs, delegations, structured outputs)
6+
- LLM API calls (via LiteLLM - Anthropic, OpenAI, etc.)
7+
- Token usage and costs
8+
- Multi-agent delegations (supervisor → subagents)
9+
- Tool calls and results
10+
- Performance metrics and latency
11+
12+
CRITICAL SETUP ORDER:
13+
1. Call initialize_phoenix_tracing() at application startup (in main.py)
14+
2. This registers Phoenix and instruments Pydantic AI BEFORE any agents are created
15+
3. Create agents with instrument=True to enable tracing
16+
17+
Usage:
18+
from app.modules.intelligence.tracing.phoenix_tracer import initialize_phoenix_tracing
19+
20+
# Initialize once at application startup (BEFORE creating any agents)
21+
initialize_phoenix_tracing()
22+
23+
# Then create agents with instrument=True
24+
agent = Agent(model=..., tools=..., instrument=True)
25+
26+
What gets traced:
27+
- Pydantic AI: Agent.run(), Agent.run_sync(), agent.iter(), structured outputs, retries
28+
- LiteLLM: completion(), acompletion(), streaming calls
29+
- Multi-agent system: All supervisor and subagent interactions
30+
- Tool calls: Function calls and results
31+
- Tokens: Usage and cost tracking
32+
"""
33+
34+
import os
35+
from typing import Optional
36+
from app.modules.utils.logger import setup_logger
37+
38+
logger = setup_logger(__name__)
39+
40+
# Global flag to track if Phoenix is initialized
41+
_PHOENIX_INITIALIZED = False
42+
43+
44+
def initialize_phoenix_tracing(
45+
project_name: Optional[str] = None,
46+
endpoint: Optional[str] = None,
47+
api_key: Optional[str] = None,
48+
auto_instrument: bool = True,
49+
) -> bool:
50+
"""
51+
Initialize Phoenix tracing for the application.
52+
53+
This should be called once at application startup, ideally in main.py
54+
before any LLM calls are made.
55+
56+
Args:
57+
project_name: Name of the project in Phoenix UI. If None, reads from PHOENIX_PROJECT_NAME env var
58+
(default: "potpie-ai" if env var not set). Function parameter takes precedence over env var.
59+
endpoint: Phoenix collector endpoint. If None, reads from PHOENIX_COLLECTOR_ENDPOINT env var
60+
(default: http://localhost:6006 for local Phoenix)
61+
api_key: Phoenix API key. If None, reads from PHOENIX_API_KEY env var
62+
(required for Phoenix Cloud, optional for local)
63+
auto_instrument: Whether to automatically instrument LiteLLM (default: True)
64+
65+
Returns:
66+
bool: True if initialization successful, False otherwise
67+
68+
Environment Variables:
69+
PHOENIX_ENABLED: Set to "false" to disable Phoenix tracing (default: "true")
70+
PHOENIX_COLLECTOR_ENDPOINT: Phoenix collector URL (default: http://localhost:6006)
71+
PHOENIX_API_KEY: API key for Phoenix Cloud (optional for local)
72+
PHOENIX_PROJECT_NAME: Project name (used only if project_name parameter is None, default: "potpie-ai")
73+
ENV: Environment identifier (e.g., "development", "production", "staging", "testing") - used as "source" attribute in traces (default: "local")
74+
"""
75+
global _PHOENIX_INITIALIZED
76+
77+
# Check if Phoenix is disabled
78+
if os.getenv("PHOENIX_ENABLED", "true").lower() == "false":
79+
logger.info("Phoenix tracing is disabled via PHOENIX_ENABLED=false")
80+
return False
81+
82+
# Check if already initialized
83+
if _PHOENIX_INITIALIZED:
84+
logger.info("Phoenix tracing already initialized")
85+
return True
86+
87+
try:
88+
# Import required modules
89+
from opentelemetry import trace
90+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
91+
OTLPSpanExporter,
92+
)
93+
from opentelemetry.sdk.trace import TracerProvider
94+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
95+
from opentelemetry.sdk.resources import Resource
96+
from openinference.instrumentation.pydantic_ai import OpenInferenceSpanProcessor
97+
from openinference.instrumentation.litellm import LiteLLMInstrumentor
98+
99+
# Get configuration from environment variables
100+
endpoint = endpoint or os.getenv(
101+
"PHOENIX_COLLECTOR_ENDPOINT", "http://localhost:6006"
102+
)
103+
api_key = api_key or os.getenv("PHOENIX_API_KEY")
104+
# Function parameter takes precedence over environment variable
105+
project_name = project_name or os.getenv("PHOENIX_PROJECT_NAME", "potpie-ai")
106+
# Get the environment/source from ENV variable (defaults to "local" if not set)
107+
source = os.getenv("ENV", "local")
108+
109+
logger.info(
110+
"Initializing Phoenix tracing:\n"
111+
" Project: %s\n"
112+
" Endpoint: %s\n"
113+
" Source: %s\n"
114+
" Auto-instrument: %s",
115+
project_name,
116+
endpoint,
117+
source,
118+
auto_instrument,
119+
)
120+
121+
# STEP 1: Create and set up the tracer provider with resource attributes
122+
resource = Resource.create(
123+
{
124+
"service.name": project_name,
125+
"source": source,
126+
}
127+
)
128+
tracer_provider = TracerProvider(resource=resource)
129+
trace.set_tracer_provider(tracer_provider)
130+
131+
# STEP 2: Set up OTLP exporter to send traces to Phoenix
132+
otlp_endpoint = f"{endpoint}/v1/traces"
133+
headers = {}
134+
if api_key:
135+
headers["Authorization"] = f"Bearer {api_key}"
136+
137+
exporter = OTLPSpanExporter(endpoint=otlp_endpoint, headers=headers)
138+
139+
# STEP 3: Add span processors
140+
# OpenInferenceSpanProcessor for Pydantic AI (adds semantic conventions)
141+
tracer_provider.add_span_processor(OpenInferenceSpanProcessor())
142+
# SimpleSpanProcessor to export spans to Phoenix
143+
tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
144+
logger.info("✅ Added OpenInference span processor for Pydantic AI tracing")
145+
146+
# STEP 4: Conditionally instrument LiteLLM (for underlying LLM API calls)
147+
if auto_instrument:
148+
litellm_instrumentor = LiteLLMInstrumentor()
149+
litellm_instrumentor.instrument(tracer_provider=tracer_provider)
150+
logger.info("✅ Instrumented LiteLLM for Phoenix tracing")
151+
else:
152+
logger.debug("Skipped LiteLLM instrumentation: auto_instrument=False")
153+
154+
_PHOENIX_INITIALIZED = True
155+
156+
logger.info(
157+
"✅ Phoenix tracing initialized successfully!\n" " View traces at: %s",
158+
endpoint,
159+
)
160+
161+
return True
162+
163+
except ImportError as e:
164+
logger.warning(
165+
"Phoenix tracing not available (missing dependencies): %s\n"
166+
"Install with: pip install arize-phoenix arize-phoenix-otel openinference-instrumentation-pydantic-ai openinference-instrumentation-litellm",
167+
e,
168+
)
169+
return False
170+
171+
except Exception as e:
172+
logger.error("Failed to initialize Phoenix tracing: %s", e, exc_info=True)
173+
return False
174+
175+
176+
def get_tracer(name: str = __name__):
177+
"""
178+
Get an OpenTelemetry tracer for manual span creation.
179+
180+
Args:
181+
name: Name for the tracer (usually __name__ of the module)
182+
183+
Returns:
184+
Tracer instance or None if Phoenix not initialized
185+
186+
Example:
187+
tracer = get_tracer(__name__)
188+
189+
def my_function(input: str) -> str:
190+
return process(input)
191+
"""
192+
try:
193+
from opentelemetry import trace
194+
195+
if not _PHOENIX_INITIALIZED:
196+
logger.debug("Phoenix not initialized, returning default tracer")
197+
198+
return trace.get_tracer(name)
199+
200+
except ImportError:
201+
logger.debug("OpenTelemetry not available")
202+
return None
203+
204+
205+
def is_phoenix_enabled() -> bool:
206+
"""Check if Phoenix tracing is enabled and initialized."""
207+
return _PHOENIX_INITIALIZED
208+
209+
210+
def shutdown_phoenix_tracing():
211+
"""
212+
Shutdown Phoenix tracing and flush any pending traces.
213+
214+
This should be called on application shutdown to ensure all traces are sent.
215+
"""
216+
global _PHOENIX_INITIALIZED
217+
218+
if not _PHOENIX_INITIALIZED:
219+
return
220+
221+
try:
222+
from opentelemetry import trace
223+
224+
# Get the tracer provider and shutdown
225+
tracer_provider = trace.get_tracer_provider()
226+
if hasattr(tracer_provider, "shutdown"):
227+
tracer_provider.shutdown()
228+
logger.info("Phoenix tracing shutdown successfully")
229+
230+
_PHOENIX_INITIALIZED = False
231+
232+
except Exception as e:
233+
logger.error("Error shutting down Phoenix tracing: %s", e)

requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,11 @@ pytest-asyncio
7373
Pillow==10.0.1
7474
python-multipart>=0.0.7
7575
asyncpg==0.30.0
76+
arize-phoenix==12.12.0
77+
arize-phoenix-client==1.22.0
78+
arize-phoenix-evals==2.5.0
79+
arize-phoenix-otel==0.13.1
80+
openinference-instrumentation-litellm==0.1.28
81+
openinference-instrumentation-openai==0.1.40
82+
openinference-instrumentation-pydantic-ai==0.1.9
83+
anthropic==0.72.0

0 commit comments

Comments
 (0)