Skip to content

Commit b559017

Browse files
committed
profiling: added implementation as plugin for dev install
1 parent 340cfbc commit b559017

File tree

8 files changed

+2700
-2609
lines changed

8 files changed

+2700
-2609
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,10 @@ stream-py/
8484
# Artifacts / assets
8585
*.pt
8686
*.kef
87+
<<<<<<< HEAD
8788
*.onnx
89+
=======
90+
91+
# opencode
92+
/opencode.json
93+
>>>>>>> 0924a71 (profiling: added implementation as plugin for dev install)

agents-core/vision_agents/core/agents/agents.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from ..vad.events import VADAudioEvent
5252
from . import events
5353
from .conversation import Conversation
54+
from ..profiling import Profiler
5455
from dataclasses import dataclass
5556
from opentelemetry.trace import set_span_in_context
5657
from opentelemetry.trace.propagation import Span, Context
@@ -155,6 +156,7 @@ def __init__(
155156
tracer: Tracer = trace.get_tracer("agents"),
156157
# Configure the default logging for the sdk here. Pass None to leave the config intact.
157158
log_level: Optional[int] = logging.INFO,
159+
profiler: Optional[Profiler] = None,
158160
):
159161
if log_level is not None:
160162
configure_default_logging(level=log_level)
@@ -208,7 +210,7 @@ def __init__(
208210
self._pending_user_transcripts: Dict[str, str] = {}
209211

210212
# Merge plugin events BEFORE subscribing to any events
211-
for plugin in [stt, tts, turn_detection, vad, llm, edge]:
213+
for plugin in [stt, tts, turn_detection, vad, llm, edge, profiler]:
212214
if plugin and hasattr(plugin, "events"):
213215
self.logger.debug(f"Register events from plugin {plugin}")
214216
self.events.merge(plugin.events)
@@ -263,6 +265,8 @@ def end_tracing(self):
263265
def __aexit__(self, exc_type, exc_val, exc_tb):
264266
self.end_tracing()
265267

268+
self.events.send(events.AgentInitEvent())
269+
266270
async def simple_response(
267271
self, text: str, participant: Optional[Participant] = None
268272
) -> None:
@@ -557,6 +561,8 @@ async def on_ended(event: CallEndedEvent):
557561
except asyncio.CancelledError:
558562
running_event.clear()
559563

564+
self.events.send(events.AgentFinishEvent())
565+
560566
await asyncio.shield(self.close())
561567

562568
async def close(self):

agents-core/vision_agents/core/agents/events.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
from typing import Optional, Any, Dict
44

55

6+
@dataclass
7+
class AgentInitEvent(BaseEvent):
8+
"""Event emitted when Agent class initialized."""
9+
10+
type: str = field(default="agent.init", init=False)
11+
12+
13+
@dataclass
14+
class AgentFinishEvent(BaseEvent):
15+
"""Event emitted when agent.finish() call ended."""
16+
17+
type: str = field(default="agent.finish", init=False)
18+
19+
620
@dataclass
721
class AgentSayEvent(PluginBaseEvent):
822
"""Event emitted when the agent wants to say something."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .base import Profiler
2+
3+
__all__ = ["Profiler"]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pyinstrument
2+
3+
import logging
4+
5+
from vision_agents.core.events import EventManager
6+
from vision_agents.core.agents import events
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class Profiler:
12+
def __init__(self, output_path='./profile.html'):
13+
self.output_path = output_path
14+
self.events = EventManager()
15+
self.profiler = pyinstrument.Profiler()
16+
self.events.subscribe(self.on_start)
17+
self.events.subscribe(self.on_finish)
18+
19+
def on_start(self, event: events.AgentInitEvent):
20+
logger.info("Profiler started.")
21+
self.profiler.start()
22+
23+
def on_finish(self, event: events.AgentFinishEvent):
24+
self.profiler.stop()
25+
logger.info(f"Profiler stopped. Time file saved at: {self.output_path}")
26+
with open(self.output_path, 'w') as f:
27+
f.write(self.profiler.output_html())

examples/01_simple_agent_example/simple_agent_example.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import asyncio
22
from uuid import uuid4
33
from dotenv import load_dotenv
4-
import pyinstrument
54

65
from vision_agents.core import User, Agent
76
from vision_agents.plugins import cartesia, deepgram, getstream, gemini, vogent
7+
from vision_agents.core.profiling import Profiler
88

99
load_dotenv()
1010

1111

1212
async def start_agent() -> None:
13-
profiler = pyinstrument.Profiler()
14-
profiler.start()
1513
llm = gemini.LLM("gemini-2.0-flash")
1614
# create an agent to run with Stream's edge, openAI llm
1715
agent = Agent(
@@ -26,6 +24,8 @@ async def start_agent() -> None:
2624
tts=cartesia.TTS(),
2725
stt=deepgram.STT(),
2826
turn_detection=vogent.TurnDetection(),
27+
profiler=Profiler(),
28+
# vad=silero.VAD(),
2929
# realtime version (vad, tts and stt not needed)
3030
# llm=openai.Realtime()
3131
)
@@ -59,10 +59,6 @@ async def start_agent() -> None:
5959
# run till the call ends
6060
await agent.finish()
6161

62-
profiler.stop()
63-
with open('profiled.html', 'w') as f:
64-
f.write(profiler.output_html())
65-
6662

6763
def setup_telemetry():
6864
import atexit

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ dev = [
8080
"pytest-timeout>=2.4.0",
8181
"hatch-vcs>=0.5.0",
8282
"hatch>=1.14.2",
83+
"pyinstrument>=5.1.1",
8384
]
8485

8586
[tool.mypy]

0 commit comments

Comments
 (0)