Skip to content

Commit 0924a71

Browse files
committed
profiling: added implementation as plugin for dev install
1 parent 43c26b5 commit 0924a71

File tree

8 files changed

+2418
-2333
lines changed

8 files changed

+2418
-2333
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,6 @@ stream-py/
8484
# Artifacts / assets
8585
*.pt
8686
*.kef
87+
88+
# opencode
89+
/opencode.json

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from ..vad.events import VADAudioEvent
4040
from . import events
4141
from .conversation import Conversation
42+
from ..profiling import Profiler
4243

4344
if TYPE_CHECKING:
4445
from vision_agents.plugins.getstream.stream_edge_transport import StreamEdge
@@ -103,6 +104,7 @@ def __init__(
103104
# MCP servers for external tool and resource access
104105
mcp_servers: Optional[List[MCPBaseServer]] = None,
105106
tracer: Tracer = trace.get_tracer("agents"),
107+
profiler: Optional[Profiler] = None,
106108
):
107109
self.instructions = instructions
108110
self.edge = edge
@@ -146,7 +148,7 @@ def __init__(
146148
self._pending_user_transcripts: Dict[str, str] = {}
147149

148150
# Merge plugin events BEFORE subscribing to any events
149-
for plugin in [stt, tts, turn_detection, vad, llm, edge]:
151+
for plugin in [stt, tts, turn_detection, vad, llm, edge, profiler]:
150152
if plugin and hasattr(plugin, "events"):
151153
self.logger.info(f"Registered plugin {plugin}")
152154
self.events.merge(plugin.events)
@@ -177,6 +179,8 @@ def __init__(
177179
self._setup_stt()
178180
self._setup_turn_detection()
179181

182+
self.events.send(events.AgentInitEvent())
183+
180184
async def simple_response(
181185
self, text: str, participant: Optional[Participant] = None
182186
) -> None:
@@ -441,6 +445,8 @@ async def on_ended(event: CallEndedEvent):
441445
except asyncio.CancelledError:
442446
running_event.clear()
443447

448+
self.events.send(events.AgentFinishEvent())
449+
444450
await asyncio.shield(self.close())
445451

446452
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: 2 additions & 8 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
6+
from vision_agents.core.profiling import Profiler
77
from vision_agents.plugins import cartesia, deepgram, getstream, gemini
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(
@@ -25,6 +23,7 @@ async def start_agent() -> None:
2523
llm=llm,
2624
tts=cartesia.TTS(),
2725
stt=deepgram.STT(),
26+
profiler=Profiler(),
2827
# vad=silero.VAD(),
2928
# realtime version (vad, tts and stt not needed)
3029
# llm=openai.Realtime()
@@ -59,10 +58,5 @@ async def start_agent() -> None:
5958
# run till the call ends
6059
await agent.finish()
6160

62-
profiler.stop()
63-
with open('profiled.html', 'w') as f:
64-
f.write(profiler.output_html())
65-
66-
6761
if __name__ == "__main__":
6862
asyncio.run(start_agent())

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ dev = [
7575
"pytest-timeout>=2.4.0",
7676
"hatch-vcs>=0.5.0",
7777
"hatch>=1.14.2",
78+
"pyinstrument>=5.1.1",
7879
]
7980

8081
[tool.mypy]

uv.lock

Lines changed: 2361 additions & 2324 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)