agno 2.0.10__py3-none-any.whl → 2.0.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
agno/agent/agent.py CHANGED
@@ -1178,6 +1178,7 @@ class Agent:
1178
1178
  run_id=run_id,
1179
1179
  session_id=session_id,
1180
1180
  agent_id=self.id,
1181
+ user_id=user_id,
1181
1182
  agent_name=self.name,
1182
1183
  metadata=metadata,
1183
1184
  input=run_input,
@@ -1811,6 +1812,7 @@ class Agent:
1811
1812
  run_id=run_id,
1812
1813
  session_id=session_id,
1813
1814
  agent_id=self.id,
1815
+ user_id=user_id,
1814
1816
  agent_name=self.name,
1815
1817
  metadata=metadata,
1816
1818
  input=run_input,
@@ -38,6 +38,10 @@ class LiteLLM(Model):
38
38
  max_tokens: Optional[int] = None
39
39
  temperature: float = 0.7
40
40
  top_p: float = 1.0
41
+ metadata: Optional[Dict[str, Any]] = None
42
+ extra_headers: Optional[Dict[str, Any]] = None
43
+ extra_query: Optional[Dict[str, Any]] = None
44
+ extra_body: Optional[Dict[str, Any]] = None
41
45
  request_params: Optional[Dict[str, Any]] = None
42
46
 
43
47
  client: Optional[Any] = None
@@ -148,10 +152,23 @@ class LiteLLM(Model):
148
152
  base_params["api_key"] = self.api_key
149
153
  if self.api_base:
150
154
  base_params["api_base"] = self.api_base
155
+ if self.extra_headers:
156
+ base_params["extra_headers"] = self.extra_headers
157
+ if self.extra_query:
158
+ base_params["extra_query"] = self.extra_query
151
159
  if tools:
152
160
  base_params["tools"] = tools
153
161
  base_params["tool_choice"] = "auto"
154
162
 
163
+ # Handle metadata via extra_body as per LiteLLM docs
164
+ if self.metadata:
165
+ if self.extra_body:
166
+ base_params["extra_body"] = {**self.extra_body, "metadata": self.metadata}
167
+ else:
168
+ base_params["extra_body"] = {"metadata": self.metadata}
169
+ elif self.extra_body:
170
+ base_params["extra_body"] = self.extra_body
171
+
155
172
  # Add additional request params if provided
156
173
  request_params: Dict[str, Any] = {k: v for k, v in base_params.items() if v is not None}
157
174
  if self.request_params:
agno/os/app.py CHANGED
@@ -12,6 +12,7 @@ from rich.panel import Panel
12
12
  from starlette.requests import Request
13
13
 
14
14
  from agno.agent.agent import Agent
15
+ from agno.db.base import BaseDb
15
16
  from agno.os.config import (
16
17
  AgentOSConfig,
17
18
  DatabaseConfig,
@@ -36,7 +37,11 @@ from agno.os.routers.memory import get_memory_router
36
37
  from agno.os.routers.metrics import get_metrics_router
37
38
  from agno.os.routers.session import get_session_router
38
39
  from agno.os.settings import AgnoAPISettings
39
- from agno.os.utils import update_cors_middleware
40
+ from agno.os.utils import (
41
+ collect_mcp_tools_from_team,
42
+ collect_mcp_tools_from_workflow,
43
+ update_cors_middleware,
44
+ )
40
45
  from agno.team.team import Team
41
46
  from agno.utils.log import logger
42
47
  from agno.utils.string import generate_id, generate_id_from_name
@@ -130,7 +135,7 @@ class AgentOS:
130
135
  self.lifespan = lifespan
131
136
 
132
137
  # List of all MCP tools used inside the AgentOS
133
- self.mcp_tools = []
138
+ self.mcp_tools: List[Any] = []
134
139
 
135
140
  if self.agents:
136
141
  for agent in self.agents:
@@ -140,7 +145,8 @@ class AgentOS:
140
145
  # Checking if the tool is a MCPTools or MultiMCPTools instance
141
146
  type_name = type(tool).__name__
142
147
  if type_name in ("MCPTools", "MultiMCPTools"):
143
- self.mcp_tools.append(tool)
148
+ if tool not in self.mcp_tools:
149
+ self.mcp_tools.append(tool)
144
150
 
145
151
  agent.initialize_agent()
146
152
 
@@ -149,13 +155,8 @@ class AgentOS:
149
155
 
150
156
  if self.teams:
151
157
  for team in self.teams:
152
- # Track all MCP tools to later handle their connection
153
- if team.tools:
154
- for tool in team.tools:
155
- # Checking if the tool is a MCPTools or MultiMCPTools instance
156
- type_name = type(tool).__name__
157
- if type_name in ("MCPTools", "MultiMCPTools"):
158
- self.mcp_tools.append(tool)
158
+ # Track all MCP tools recursively
159
+ collect_mcp_tools_from_team(team, self.mcp_tools)
159
160
 
160
161
  team.initialize_team()
161
162
 
@@ -171,7 +172,8 @@ class AgentOS:
171
172
 
172
173
  if self.workflows:
173
174
  for workflow in self.workflows:
174
- # TODO: track MCP tools in workflow members
175
+ # Track MCP tools recursively in workflow members
176
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
175
177
  if not workflow.id:
176
178
  workflow.id = generate_id_from_name(workflow.name)
177
179
 
@@ -415,40 +417,85 @@ class AgentOS:
415
417
 
416
418
  def _auto_discover_databases(self) -> None:
417
419
  """Auto-discover the databases used by all contextual agents, teams and workflows."""
418
- dbs = {}
419
- knowledge_dbs = {} # Track databases specifically used for knowledge
420
+ from agno.db.base import BaseDb
421
+
422
+ dbs: Dict[str, BaseDb] = {}
423
+ knowledge_dbs: Dict[str, BaseDb] = {} # Track databases specifically used for knowledge
420
424
 
421
425
  for agent in self.agents or []:
422
426
  if agent.db:
423
- dbs[agent.db.id] = agent.db
427
+ self._register_db_with_validation(dbs, agent.db)
424
428
  if agent.knowledge and agent.knowledge.contents_db:
425
- knowledge_dbs[agent.knowledge.contents_db.id] = agent.knowledge.contents_db
429
+ self._register_db_with_validation(knowledge_dbs, agent.knowledge.contents_db)
426
430
  # Also add to general dbs if it's used for both purposes
427
431
  if agent.knowledge.contents_db.id not in dbs:
428
- dbs[agent.knowledge.contents_db.id] = agent.knowledge.contents_db
432
+ self._register_db_with_validation(dbs, agent.knowledge.contents_db)
429
433
 
430
434
  for team in self.teams or []:
431
435
  if team.db:
432
- dbs[team.db.id] = team.db
436
+ self._register_db_with_validation(dbs, team.db)
433
437
  if team.knowledge and team.knowledge.contents_db:
434
- knowledge_dbs[team.knowledge.contents_db.id] = team.knowledge.contents_db
438
+ self._register_db_with_validation(knowledge_dbs, team.knowledge.contents_db)
435
439
  # Also add to general dbs if it's used for both purposes
436
440
  if team.knowledge.contents_db.id not in dbs:
437
- dbs[team.knowledge.contents_db.id] = team.knowledge.contents_db
441
+ self._register_db_with_validation(dbs, team.knowledge.contents_db)
438
442
 
439
443
  for workflow in self.workflows or []:
440
444
  if workflow.db:
441
- dbs[workflow.db.id] = workflow.db
445
+ self._register_db_with_validation(dbs, workflow.db)
442
446
 
443
447
  for interface in self.interfaces or []:
444
448
  if interface.agent and interface.agent.db:
445
- dbs[interface.agent.db.id] = interface.agent.db
449
+ self._register_db_with_validation(dbs, interface.agent.db)
446
450
  elif interface.team and interface.team.db:
447
- dbs[interface.team.db.id] = interface.team.db
451
+ self._register_db_with_validation(dbs, interface.team.db)
448
452
 
449
453
  self.dbs = dbs
450
454
  self.knowledge_dbs = knowledge_dbs
451
455
 
456
+ def _register_db_with_validation(self, registered_dbs: Dict[str, Any], db: BaseDb) -> None:
457
+ """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
458
+ if db.id in registered_dbs:
459
+ existing_db = registered_dbs[db.id]
460
+ if not self._are_db_instances_compatible(existing_db, db):
461
+ raise ValueError(
462
+ f"Database ID conflict detected: Two different database instances have the same ID '{db.id}'. "
463
+ f"Database instances with the same ID must point to the same database with identical configuration."
464
+ )
465
+ registered_dbs[db.id] = db
466
+
467
+ def _are_db_instances_compatible(self, db1: BaseDb, db2: BaseDb) -> bool:
468
+ """
469
+ Return True if the two given database objects are compatible
470
+ Two database objects are compatible if they point to the same database with identical configuration.
471
+ """
472
+ # If they're the same object reference, they're compatible
473
+ if db1 is db2:
474
+ return True
475
+
476
+ if type(db1) is not type(db2):
477
+ return False
478
+
479
+ if hasattr(db1, "db_url") and hasattr(db2, "db_url"):
480
+ if db1.db_url != db2.db_url: # type: ignore
481
+ return False
482
+
483
+ if hasattr(db1, "db_file") and hasattr(db2, "db_file"):
484
+ if db1.db_file != db2.db_file: # type: ignore
485
+ return False
486
+
487
+ # If table names are different, they're not compatible
488
+ if (
489
+ db1.session_table_name != db2.session_table_name
490
+ or db1.memory_table_name != db2.memory_table_name
491
+ or db1.metrics_table_name != db2.metrics_table_name
492
+ or db1.eval_table_name != db2.eval_table_name
493
+ or db1.knowledge_table_name != db2.knowledge_table_name
494
+ ):
495
+ return False
496
+
497
+ return True
498
+
452
499
  def _auto_discover_knowledge_instances(self) -> None:
453
500
  """Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
454
501
  knowledge_instances = []
agno/os/utils.py CHANGED
@@ -303,3 +303,101 @@ def update_cors_middleware(app: FastAPI, new_origins: list):
303
303
  allow_headers=["*"],
304
304
  expose_headers=["*"],
305
305
  )
306
+
307
+
308
+ def collect_mcp_tools_from_team(team: Team, mcp_tools: List[Any]) -> None:
309
+ """Recursively collect MCP tools from a team and its members."""
310
+ # Check the team tools
311
+ if team.tools:
312
+ for tool in team.tools:
313
+ type_name = type(tool).__name__
314
+ if type_name in ("MCPTools", "MultiMCPTools"):
315
+ if tool not in mcp_tools:
316
+ mcp_tools.append(tool)
317
+
318
+ # Recursively check team members
319
+ if team.members:
320
+ for member in team.members:
321
+ if isinstance(member, Agent):
322
+ if member.tools:
323
+ for tool in member.tools:
324
+ type_name = type(tool).__name__
325
+ if type_name in ("MCPTools", "MultiMCPTools"):
326
+ if tool not in mcp_tools:
327
+ mcp_tools.append(tool)
328
+
329
+ elif isinstance(member, Team):
330
+ # Recursively check nested team
331
+ collect_mcp_tools_from_team(member, mcp_tools)
332
+
333
+
334
+ def collect_mcp_tools_from_workflow(workflow: Workflow, mcp_tools: List[Any]) -> None:
335
+ """Recursively collect MCP tools from a workflow and its steps."""
336
+ from agno.workflow.steps import Steps
337
+
338
+ # Recursively check workflow steps
339
+ if workflow.steps:
340
+ if isinstance(workflow.steps, list):
341
+ # Handle list of steps
342
+ for step in workflow.steps:
343
+ collect_mcp_tools_from_workflow_step(step, mcp_tools)
344
+
345
+ elif isinstance(workflow.steps, Steps):
346
+ # Handle Steps container
347
+ if steps := workflow.steps.steps:
348
+ for step in steps:
349
+ collect_mcp_tools_from_workflow_step(step, mcp_tools)
350
+
351
+ elif callable(workflow.steps):
352
+ pass
353
+
354
+
355
+ def collect_mcp_tools_from_workflow_step(step: Any, mcp_tools: List[Any]) -> None:
356
+ """Collect MCP tools from a single workflow step."""
357
+ from agno.workflow.condition import Condition
358
+ from agno.workflow.loop import Loop
359
+ from agno.workflow.parallel import Parallel
360
+ from agno.workflow.router import Router
361
+ from agno.workflow.step import Step
362
+ from agno.workflow.steps import Steps
363
+
364
+ if isinstance(step, Step):
365
+ # Check step's agent
366
+ if step.agent:
367
+ if step.agent.tools:
368
+ for tool in step.agent.tools:
369
+ type_name = type(tool).__name__
370
+ if type_name in ("MCPTools", "MultiMCPTools"):
371
+ if tool not in mcp_tools:
372
+ mcp_tools.append(tool)
373
+ # Check step's team
374
+ if step.team:
375
+ collect_mcp_tools_from_team(step.team, mcp_tools)
376
+
377
+ elif isinstance(step, Steps):
378
+ if steps := step.steps:
379
+ for step in steps:
380
+ collect_mcp_tools_from_workflow_step(step, mcp_tools)
381
+
382
+ elif isinstance(step, (Parallel, Loop, Condition, Router)):
383
+ # These contain other steps - recursively check them
384
+ if hasattr(step, "steps") and step.steps:
385
+ for sub_step in step.steps:
386
+ collect_mcp_tools_from_workflow_step(sub_step, mcp_tools)
387
+
388
+ elif isinstance(step, Agent):
389
+ # Direct agent in workflow steps
390
+ if step.tools:
391
+ for tool in step.tools:
392
+ type_name = type(tool).__name__
393
+ if type_name in ("MCPTools", "MultiMCPTools"):
394
+ if tool not in mcp_tools:
395
+ mcp_tools.append(tool)
396
+
397
+ elif isinstance(step, Team):
398
+ # Direct team in workflow steps
399
+ collect_mcp_tools_from_team(step, mcp_tools)
400
+
401
+ elif isinstance(step, Workflow):
402
+ # Nested workflow
403
+ collect_mcp_tools_from_workflow(step, mcp_tools)
@@ -0,0 +1,3 @@
1
+ from agno.vectordb.llamaindex.llamaindexdb import LlamaIndexVectorDb
2
+
3
+ __all__ = ["LlamaIndexVectorDb"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.0.10
3
+ Version: 2.0.11
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  Project-URL: homepage, https://agno.com
@@ -4,7 +4,7 @@ agno/exceptions.py,sha256=HWuuNFS5J0l1RYJsdUrSx51M22aFEoh9ltoeonXBoBw,2891
4
4
  agno/media.py,sha256=eTfYb_pwhX_PCIVPSrW4VYRqmoxKABEF1aZClrVvQ30,16500
5
5
  agno/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  agno/agent/__init__.py,sha256=s7S3FgsjZxuaabzi8L5n4aSH8IZAiZ7XaNNcySGR-EQ,1051
7
- agno/agent/agent.py,sha256=jeJp0Nx6z3jabzhWIgJuT9eU24WjKwAAmlrGLNmgBhA,330395
7
+ agno/agent/agent.py,sha256=hkbbQr90Puq2Hj2RTkeuSd3iYggpHfOpFpA0SEpTM2s,330453
8
8
  agno/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  agno/api/agent.py,sha256=fKlQ62E_C9Rjd7Zus3Gs3R1RG-IhzFV-ICpkb6SLqYc,932
10
10
  agno/api/api.py,sha256=Z7iWbrjheJcGLeeDYrtTCWiKTVqjH0uJI35UNWOtAXw,973
@@ -190,7 +190,7 @@ agno/models/internlm/internlm.py,sha256=9oX7gbvVS-2GJhGvfug7sVFWMLCg-m1icQyUeFZk
190
190
  agno/models/langdb/__init__.py,sha256=ubh5nDcxyH33_ONwsmY4tWQz5esRwRjHBe68u9hdAIM,45
191
191
  agno/models/langdb/langdb.py,sha256=tTorf22oSTA632LzFor6UtJLqAOP6gsEKtNimNeehZI,1631
192
192
  agno/models/litellm/__init__.py,sha256=5e4yHqepF9-fOE0DMDIKnH6psFV1OcRgfAD5BaoVRgI,353
193
- agno/models/litellm/chat.py,sha256=6JHfOIluv3X73lmF24V2Ej56wjtEMqII6PqiadXKius,18448
193
+ agno/models/litellm/chat.py,sha256=4gaK3YhFeeVQl9-mzicFLFdxWwQ3FM7krZP_AmIPuIg,19209
194
194
  agno/models/litellm/litellm_openai.py,sha256=6TNf6nRbCibPWEyubDmSLnoKpAKcM31afiklAYbujyY,759
195
195
  agno/models/llama_cpp/__init__.py,sha256=oxOZfqEcOdcB74VLwOTO6bPmXHHA88uaeJO-IkXgr8A,84
196
196
  agno/models/llama_cpp/llama_cpp.py,sha256=tWIF0TQH-sK29xCrTe7oy0SjSC-FlOmwmPL1d5IVMPM,673
@@ -232,14 +232,14 @@ agno/models/vllm/vllm.py,sha256=anBt3gshctbmda-OZJ51tOgbHHemsrSbQLMDqB39HIA,2707
232
232
  agno/models/xai/__init__.py,sha256=ukcCxnCHxTtkJNA2bAMTX4MhCv1wJcbiq8ZIfYczIxs,55
233
233
  agno/models/xai/xai.py,sha256=OqqhPySt64hQ4MM_qYhdMPPjoLRNhExyC-01sN86hJw,4158
234
234
  agno/os/__init__.py,sha256=h8oQu7vhD5RZf09jkyM_Kt1Kdq_d5kFB9gJju8QPwcY,55
235
- agno/os/app.py,sha256=acPtlVPAlrdsg6L2QC73SpaaX68-hsWn9X0RH9WNx7Q,24457
235
+ agno/os/app.py,sha256=FWHnivzKhJH1FayoobkQIrfZceQXU51DK8FUWQQz29A,26500
236
236
  agno/os/auth.py,sha256=FyBtAKWtg-qSunCas5m5pK1dVEmikOSZvcCp5r25tTA,1844
237
237
  agno/os/config.py,sha256=u4R9yazQXIcKjR3QzEIZw_XAe_OHp3xn0ff7SVkj2jA,2893
238
238
  agno/os/mcp.py,sha256=5L0TZwInuCWM7TQ7MI_DjMWfVIIB0jxVCqaVEdR62UU,8114
239
239
  agno/os/router.py,sha256=IFgQCyx16MHHA2UAqr1T8I2_dKJ_QqmOOYdnGjWABHE,62517
240
240
  agno/os/schema.py,sha256=bdmGLQoBJyetiAZeajJG_gEKLaQSSEwiDwKy0q4Pa-M,38278
241
241
  agno/os/settings.py,sha256=Cn5_8lZI8Vx1UaUYqs9h6Qp4IMDFn4f3c35uppiaMy4,1343
242
- agno/os/utils.py,sha256=tOszvSQPXbbYCLOWBPsCnHGbELKw0i6mVahxmEzddhI,10810
242
+ agno/os/utils.py,sha256=VRxUQCBhjL62Qk2-KE-fl--RZEh1FDJ2Z0gjYpeIK3M,14528
243
243
  agno/os/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
244
  agno/os/interfaces/base.py,sha256=giU0y-egtXLqokN1HQyQIWl4NBawTk_ua7ytKGFmZRc,527
245
245
  agno/os/interfaces/agui/__init__.py,sha256=1zrGICk4roXUINwSFZfqH6sBsbHmD5KjGYVJMGg4fKQ,66
@@ -488,7 +488,7 @@ agno/vectordb/langchaindb/__init__.py,sha256=BxGs6tcEKTiydbVJL3P5djlnafS5Bbgql3u
488
488
  agno/vectordb/langchaindb/langchaindb.py,sha256=VieHU4W-lRmYN0bLo2vmb7rGkmlNkPtjXILD4_F8818,5505
489
489
  agno/vectordb/lightrag/__init__.py,sha256=fgQpA8pZW-jEHI91SZ_xgmROmv14oKdwCQZ8LpyipaE,84
490
490
  agno/vectordb/lightrag/lightrag.py,sha256=GBReSzGBxpv0u0WTTZYTezZWpJQNj1zwL70ori3_cD4,14390
491
- agno/vectordb/llamaindex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
491
+ agno/vectordb/llamaindex/__init__.py,sha256=fgdWIntUOX3n5YEPcPfr-yPb2kvmSy5ngQ5_UqAIrgM,103
492
492
  agno/vectordb/llamaindex/llamaindexdb.py,sha256=co_GtDJuorGG0cN-taWiuNUXGwNlgE044PQjTT7lfcU,5432
493
493
  agno/vectordb/milvus/__init__.py,sha256=I9V-Rm-rIYxWdRVIs6bKI-6JSJsyOd1-vvasvVpYHuE,127
494
494
  agno/vectordb/milvus/milvus.py,sha256=fbpXXOV_ahxP5kX0KoPZ4_4M3kWC4gjGX0GEtuIVD8o,42394
@@ -520,8 +520,8 @@ agno/workflow/step.py,sha256=15vZFN5HlO3IkHqsxkM-2InUzCWC0Ee8BtoJ-OAhS5w,52485
520
520
  agno/workflow/steps.py,sha256=uRE4oGWs2cA-TrX881AEa69zu6rheXH81mNOZiRrNvg,23719
521
521
  agno/workflow/types.py,sha256=ZEFyaKH42261_3yx810ABfi70RUfjPGSwiIep7n53yg,17485
522
522
  agno/workflow/workflow.py,sha256=yjjVZ0MvkLjGgVaVt8jFWlm2urx7zZdAuJ0We9KRu2U,112738
523
- agno-2.0.10.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
524
- agno-2.0.10.dist-info/METADATA,sha256=PKHm3CQY3sd9WdY5O1VGJyQyVeza95ICLnS3lhPuIA0,22018
525
- agno-2.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
526
- agno-2.0.10.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
527
- agno-2.0.10.dist-info/RECORD,,
523
+ agno-2.0.11.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
524
+ agno-2.0.11.dist-info/METADATA,sha256=rLaQ6VJDCNK1fLPp3sy3-TGYfF68OXGElkkmms5Qvho,22018
525
+ agno-2.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
526
+ agno-2.0.11.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
527
+ agno-2.0.11.dist-info/RECORD,,
File without changes