Skip to content

Commit 63f6306

Browse files
authored
Merge branch 'main' into release/v2.2.4
2 parents fca60ae + f15f69d commit 63f6306

File tree

2 files changed

+151
-42
lines changed

2 files changed

+151
-42
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from contextlib import asynccontextmanager
2+
3+
from agno.agent.agent import Agent
4+
from agno.db.postgres import PostgresDb
5+
from agno.os import AgentOS
6+
7+
# First agent. We will add this to the AgentOS on initialization.
8+
agent1 = Agent(
9+
id="first-agent",
10+
name="First Agent",
11+
markdown=True,
12+
)
13+
14+
# Second agent. We will add this to the AgentOS in the lifespan function.
15+
agent2 = Agent(
16+
id="second-agent",
17+
name="Second Agent",
18+
markdown=True,
19+
db=PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai"),
20+
)
21+
22+
23+
# Lifespan function receiving the AgentOS instance as parameter.
24+
@asynccontextmanager
25+
async def lifespan(app, agent_os):
26+
# Add the new Agent
27+
agent_os.agents.append(agent2)
28+
29+
# Resync the AgentOS
30+
agent_os.resync()
31+
32+
yield
33+
34+
35+
# Setup our AgentOS with the lifespan function and the first agent.
36+
agent_os = AgentOS(
37+
lifespan=lifespan,
38+
agents=[agent1],
39+
)
40+
41+
# Get our app.
42+
app = agent_os.get_app()
43+
44+
# Serve the app.
45+
if __name__ == "__main__":
46+
agent_os.serve(app="update_from_lifespan:app", reload=True)

libs/agno/agno/os/app.py

Lines changed: 105 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -187,54 +187,50 @@ def __init__(
187187
self.mcp_tools: List[Any] = []
188188
self._mcp_app: Optional[Any] = None
189189

190-
if self.agents:
191-
for agent in self.agents:
192-
# Track all MCP tools to later handle their connection
193-
if agent.tools:
194-
for tool in agent.tools:
195-
# Checking if the tool is a MCPTools or MultiMCPTools instance
196-
type_name = type(tool).__name__
197-
if type_name in ("MCPTools", "MultiMCPTools"):
198-
if tool not in self.mcp_tools:
199-
self.mcp_tools.append(tool)
200-
201-
agent.initialize_agent()
190+
self._initialize_agents()
191+
self._initialize_teams()
192+
self._initialize_workflows()
202193

203-
# Required for the built-in routes to work
204-
agent.store_events = True
205-
206-
if self.teams:
207-
for team in self.teams:
208-
# Track all MCP tools recursively
209-
collect_mcp_tools_from_team(team, self.mcp_tools)
194+
if self.telemetry:
195+
from agno.api.os import OSLaunch, log_os_telemetry
210196

211-
team.initialize_team()
197+
log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
212198

213-
for member in team.members:
214-
if isinstance(member, Agent):
215-
member.team_id = None
216-
member.initialize_agent()
217-
elif isinstance(member, Team):
218-
member.initialize_team()
199+
def _add_agent_os_to_lifespan_function(self, lifespan):
200+
"""
201+
Inspect a lifespan function and wrap it to pass agent_os if it accepts it.
219202
220-
# Required for the built-in routes to work
221-
team.store_events = True
203+
Returns:
204+
A wrapped lifespan that passes agent_os if the lifespan function expects it.
205+
"""
206+
# Getting the actual function inside the lifespan
207+
lifespan_function = lifespan
208+
if hasattr(lifespan, "__wrapped__"):
209+
lifespan_function = lifespan.__wrapped__
222210

223-
if self.workflows:
224-
for workflow in self.workflows:
225-
# Track MCP tools recursively in workflow members
226-
collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
211+
try:
212+
from inspect import signature
227213

228-
if not workflow.id:
229-
workflow.id = generate_id_from_name(workflow.name)
214+
# Inspecting the lifespan function signature to find its parameters
215+
sig = signature(lifespan_function)
216+
params = list(sig.parameters.keys())
230217

231-
# Required for the built-in routes to work
232-
workflow.store_events = True
218+
# If the lifespan function expects the 'agent_os' parameter, add it
219+
if "agent_os" in params:
220+
return partial(lifespan, agent_os=self)
221+
else:
222+
return lifespan
233223

234-
if self.telemetry:
235-
from agno.api.os import OSLaunch, log_os_telemetry
224+
except (ValueError, TypeError):
225+
return lifespan
236226

237-
log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
227+
def resync(self) -> None:
228+
"""Resync the AgentOS to discover, initialize and configure: agents, teams, workflows, databases and knowledge bases."""
229+
self._initialize_agents()
230+
self._initialize_teams()
231+
self._initialize_workflows()
232+
self._auto_discover_databases()
233+
self._auto_discover_knowledge_instances()
238234

239235
def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
240236
# Adjust the FastAPI app lifespan to handle MCP connections if relevant
@@ -265,6 +261,63 @@ async def combined_lifespan(app: FastAPI):
265261
lifespan=app_lifespan,
266262
)
267263

264+
def _initialize_agents(self) -> None:
265+
"""Initialize and configure all agents for AgentOS usage."""
266+
if not self.agents:
267+
return
268+
269+
for agent in self.agents:
270+
# Track all MCP tools to later handle their connection
271+
if agent.tools:
272+
for tool in agent.tools:
273+
# Checking if the tool is a MCPTools or MultiMCPTools instance
274+
type_name = type(tool).__name__
275+
if type_name in ("MCPTools", "MultiMCPTools"):
276+
if tool not in self.mcp_tools:
277+
self.mcp_tools.append(tool)
278+
279+
agent.initialize_agent()
280+
281+
# Required for the built-in routes to work
282+
agent.store_events = True
283+
284+
def _initialize_teams(self) -> None:
285+
"""Initialize and configure all teams for AgentOS usage."""
286+
if not self.teams:
287+
return
288+
289+
for team in self.teams:
290+
# Track all MCP tools recursively
291+
collect_mcp_tools_from_team(team, self.mcp_tools)
292+
293+
team.initialize_team()
294+
295+
for member in team.members:
296+
if isinstance(member, Agent):
297+
member.team_id = None
298+
member.initialize_agent()
299+
elif isinstance(member, Team):
300+
member.initialize_team()
301+
302+
# Required for the built-in routes to work
303+
team.store_events = True
304+
305+
def _initialize_workflows(self) -> None:
306+
"""Initialize and configure all workflows for AgentOS usage."""
307+
if not self.workflows:
308+
return
309+
310+
if self.workflows:
311+
for workflow in self.workflows:
312+
# Track MCP tools recursively in workflow members
313+
collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
314+
315+
if not workflow.id:
316+
workflow.id = generate_id_from_name(workflow.name)
317+
318+
# Required for the built-in routes to work
319+
workflow.store_events = True
320+
268321
def get_app(self) -> FastAPI:
269322
if self.base_app:
270323
fastapi_app = self.base_app
@@ -288,7 +341,9 @@ def get_app(self) -> FastAPI:
288341
lifespans.append(self._mcp_app.lifespan)
289342

290343
if self.lifespan:
291-
lifespans.append(self.lifespan)
344+
# Wrap the user lifespan with agent_os parameter
345+
wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
346+
lifespans.append(wrapped_lifespan)
292347

293348
# Combine lifespans and set them in the app
294349
if lifespans:
@@ -304,19 +359,27 @@ def get_app(self) -> FastAPI:
304359

305360
final_lifespan = self._mcp_app.lifespan # type: ignore
306361
if self.lifespan is not None:
362+
# Wrap the user lifespan with agent_os parameter
363+
wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
364+
307365
# Combine both lifespans
308366
@asynccontextmanager
309367
async def combined_lifespan(app: FastAPI):
310368
# Run both lifespans
311-
async with self.lifespan(app): # type: ignore
369+
async with wrapped_lifespan(app): # type: ignore
312370
async with self._mcp_app.lifespan(app): # type: ignore
313371
yield
314372

315373
final_lifespan = combined_lifespan # type: ignore
316374

317375
fastapi_app = self._make_app(lifespan=final_lifespan)
318376
else:
319-
fastapi_app = self._make_app(lifespan=self.lifespan)
377+
# Wrap the user lifespan with agent_os parameter
378+
wrapped_user_lifespan = None
379+
if self.lifespan is not None:
380+
wrapped_user_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
381+
382+
fastapi_app = self._make_app(lifespan=wrapped_user_lifespan)
320383

321384
# Add routes
322385
self._add_router(fastapi_app, get_base_router(self, settings=self.settings))

0 commit comments

Comments
 (0)