agno 2.1.2__py3-none-any.whl → 2.3.13__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.
Files changed (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
1
- from typing import Optional
1
+ from typing import Optional, Union
2
2
 
3
3
  from fastapi import HTTPException
4
4
 
5
5
  from agno.agent.agent import Agent
6
- from agno.db.base import BaseDb
6
+ from agno.db.base import AsyncBaseDb, BaseDb
7
7
  from agno.eval.accuracy import AccuracyEval
8
+ from agno.eval.agent_as_judge import AgentAsJudgeEval
8
9
  from agno.eval.performance import PerformanceEval
9
10
  from agno.eval.reliability import ReliabilityEval
10
11
  from agno.models.base import Model
@@ -14,7 +15,7 @@ from agno.team.team import Team
14
15
 
15
16
  async def run_accuracy_eval(
16
17
  eval_run_input: EvalRunInput,
17
- db: BaseDb,
18
+ db: Union[BaseDb, AsyncBaseDb],
18
19
  agent: Optional[Agent] = None,
19
20
  team: Optional[Team] = None,
20
21
  default_model: Optional[Model] = None,
@@ -33,6 +34,7 @@ async def run_accuracy_eval(
33
34
  additional_context=eval_run_input.additional_context,
34
35
  num_iterations=eval_run_input.num_iterations or 1,
35
36
  name=eval_run_input.name,
37
+ model=default_model,
36
38
  )
37
39
 
38
40
  result = await accuracy_eval.arun(print_results=False, print_summary=False)
@@ -41,6 +43,71 @@ async def run_accuracy_eval(
41
43
 
42
44
  eval_run = EvalSchema.from_accuracy_eval(accuracy_eval=accuracy_eval, result=result)
43
45
 
46
+ # Restore original model after eval
47
+ if default_model is not None:
48
+ if agent is not None:
49
+ agent.model = default_model
50
+ elif team is not None:
51
+ team.model = default_model
52
+
53
+ return eval_run
54
+
55
+
56
+ async def run_agent_as_judge_eval(
57
+ eval_run_input: EvalRunInput,
58
+ db: Union[BaseDb, AsyncBaseDb],
59
+ agent: Optional[Agent] = None,
60
+ team: Optional[Team] = None,
61
+ default_model: Optional[Model] = None,
62
+ ) -> EvalSchema:
63
+ """Run an AgentAsJudge evaluation for the given agent or team"""
64
+ if not eval_run_input.criteria:
65
+ raise HTTPException(status_code=400, detail="criteria is required for agent-as-judge evaluation")
66
+
67
+ # Run agent/team to get output
68
+ if agent:
69
+ agent_response = await agent.arun(eval_run_input.input, stream=False)
70
+ output = str(agent_response.content) if agent_response.content else ""
71
+ model_id = agent.model.id if agent and agent.model else None
72
+ model_provider = agent.model.provider if agent and agent.model else None
73
+ agent_id = agent.id
74
+ team_id = None
75
+ elif team:
76
+ team_response = await team.arun(eval_run_input.input, stream=False)
77
+ output = str(team_response.content) if team_response.content else ""
78
+ model_id = team.model.id if team and team.model else None
79
+ model_provider = team.model.provider if team and team.model else None
80
+ agent_id = None
81
+ team_id = team.id
82
+ else:
83
+ raise HTTPException(status_code=400, detail="Either agent_id or team_id must be provided")
84
+
85
+ agent_as_judge_eval = AgentAsJudgeEval(
86
+ db=db,
87
+ criteria=eval_run_input.criteria,
88
+ scoring_strategy=eval_run_input.scoring_strategy or "binary",
89
+ threshold=eval_run_input.threshold or 7,
90
+ additional_guidelines=eval_run_input.additional_guidelines,
91
+ name=eval_run_input.name,
92
+ model=default_model,
93
+ )
94
+
95
+ result = await agent_as_judge_eval.arun(
96
+ input=eval_run_input.input, output=output, print_results=False, print_summary=False
97
+ )
98
+ if not result:
99
+ raise HTTPException(status_code=500, detail="Failed to run agent as judge evaluation")
100
+
101
+ eval_run = EvalSchema.from_agent_as_judge_eval(
102
+ agent_as_judge_eval=agent_as_judge_eval,
103
+ result=result,
104
+ agent_id=agent_id,
105
+ team_id=team_id,
106
+ model_id=model_id,
107
+ model_provider=model_provider,
108
+ )
109
+
110
+ # Restore original model after eval
44
111
  if default_model is not None:
45
112
  if agent is not None:
46
113
  agent.model = default_model
@@ -52,7 +119,7 @@ async def run_accuracy_eval(
52
119
 
53
120
  async def run_performance_eval(
54
121
  eval_run_input: EvalRunInput,
55
- db: BaseDb,
122
+ db: Union[BaseDb, AsyncBaseDb],
56
123
  agent: Optional[Agent] = None,
57
124
  team: Optional[Team] = None,
58
125
  default_model: Optional[Model] = None,
@@ -61,15 +128,15 @@ async def run_performance_eval(
61
128
  if agent:
62
129
 
63
130
  async def run_component(): # type: ignore
64
- return await agent.arun(eval_run_input.input)
131
+ return await agent.arun(eval_run_input.input, stream=False)
65
132
 
66
133
  model_id = agent.model.id if agent and agent.model else None
67
134
  model_provider = agent.model.provider if agent and agent.model else None
68
135
 
69
136
  elif team:
70
137
 
71
- async def run_component():
72
- return await team.arun(eval_run_input.input)
138
+ async def run_component(): # type: ignore
139
+ return await team.arun(eval_run_input.input, stream=False)
73
140
 
74
141
  model_id = team.model.id if team and team.model else None
75
142
  model_provider = team.model.provider if team and team.model else None
@@ -85,6 +152,7 @@ async def run_performance_eval(
85
152
  model_id=model_id,
86
153
  model_provider=model_provider,
87
154
  )
155
+
88
156
  result = await performance_eval.arun(print_results=False, print_summary=False)
89
157
  if not result:
90
158
  raise HTTPException(status_code=500, detail="Failed to run performance evaluation")
@@ -98,6 +166,7 @@ async def run_performance_eval(
98
166
  model_provider=model_provider,
99
167
  )
100
168
 
169
+ # Restore original model after eval
101
170
  if default_model is not None:
102
171
  if agent is not None:
103
172
  agent.model = default_model
@@ -109,7 +178,7 @@ async def run_performance_eval(
109
178
 
110
179
  async def run_reliability_eval(
111
180
  eval_run_input: EvalRunInput,
112
- db: BaseDb,
181
+ db: Union[BaseDb, AsyncBaseDb],
113
182
  agent: Optional[Agent] = None,
114
183
  team: Optional[Team] = None,
115
184
  default_model: Optional[Model] = None,
@@ -119,7 +188,7 @@ async def run_reliability_eval(
119
188
  raise HTTPException(status_code=400, detail="expected_tool_calls is required for reliability evaluations")
120
189
 
121
190
  if agent:
122
- agent_response = await agent.arun(eval_run_input.input)
191
+ agent_response = await agent.arun(eval_run_input.input, stream=False)
123
192
  reliability_eval = ReliabilityEval(
124
193
  db=db,
125
194
  name=eval_run_input.name,
@@ -130,7 +199,7 @@ async def run_reliability_eval(
130
199
  model_provider = agent.model.provider if agent and agent.model else None
131
200
 
132
201
  elif team:
133
- team_response = await team.arun(eval_run_input.input)
202
+ team_response = await team.arun(eval_run_input.input, stream=False)
134
203
  reliability_eval = ReliabilityEval(
135
204
  db=db,
136
205
  name=eval_run_input.name,
@@ -152,6 +221,7 @@ async def run_reliability_eval(
152
221
  model_provider=model_provider,
153
222
  )
154
223
 
224
+ # Restore original model after eval
155
225
  if default_model is not None:
156
226
  if agent is not None:
157
227
  agent.model = default_model
agno/os/routers/health.py CHANGED
@@ -1,13 +1,17 @@
1
+ from datetime import datetime, timezone
2
+
1
3
  from fastapi import APIRouter
2
4
 
3
5
  from agno.os.schema import HealthResponse
4
6
 
5
7
 
6
- def get_health_router() -> APIRouter:
8
+ def get_health_router(health_endpoint: str = "/health") -> APIRouter:
7
9
  router = APIRouter(tags=["Health"])
8
10
 
11
+ started_time_stamp = datetime.now(timezone.utc).timestamp()
12
+
9
13
  @router.get(
10
- "/health",
14
+ health_endpoint,
11
15
  operation_id="health_check",
12
16
  summary="Health Check",
13
17
  description="Check the health status of the AgentOS API. Returns a simple status indicator.",
@@ -15,11 +19,13 @@ def get_health_router() -> APIRouter:
15
19
  responses={
16
20
  200: {
17
21
  "description": "API is healthy and operational",
18
- "content": {"application/json": {"example": {"status": "ok"}}},
22
+ "content": {
23
+ "application/json": {"example": {"status": "ok", "instantiated_at": str(started_time_stamp)}}
24
+ },
19
25
  }
20
26
  },
21
27
  )
22
28
  async def health_check() -> HealthResponse:
23
- return HealthResponse(status="ok")
29
+ return HealthResponse(status="ok", instantiated_at=str(started_time_stamp))
24
30
 
25
31
  return router
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import math
4
- from typing import Dict, List, Optional
4
+ from typing import Any, Dict, List, Optional
5
5
 
6
6
  from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
7
7
 
@@ -19,6 +19,9 @@ from agno.os.routers.knowledge.schemas import (
19
19
  ContentStatusResponse,
20
20
  ContentUpdateSchema,
21
21
  ReaderSchema,
22
+ VectorDbSchema,
23
+ VectorSearchRequestSchema,
24
+ VectorSearchResult,
22
25
  )
23
26
  from agno.os.schema import (
24
27
  BadRequestResponse,
@@ -99,6 +102,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
99
102
  text_content: Optional[str] = Form(None, description="Raw text content to process"),
100
103
  reader_id: Optional[str] = Form(None, description="ID of the reader to use for content processing"),
101
104
  chunker: Optional[str] = Form(None, description="Chunking strategy to apply during processing"),
105
+ chunk_size: Optional[int] = Form(None, description="Chunk size to use for processing"),
106
+ chunk_overlap: Optional[int] = Form(None, description="Chunk overlap to use for processing"),
102
107
  db_id: Optional[str] = Query(default=None, description="Database ID to use for content storage"),
103
108
  ):
104
109
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
@@ -169,7 +174,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
169
174
  content.content_hash = content_hash
170
175
  content.id = generate_id(content_hash)
171
176
 
172
- background_tasks.add_task(process_content, knowledge, content, reader_id, chunker)
177
+ background_tasks.add_task(process_content, knowledge, content, reader_id, chunker, chunk_size, chunk_overlap)
173
178
 
174
179
  response = ContentResponseSchema(
175
180
  id=content.id,
@@ -303,7 +308,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
303
308
  }
304
309
  },
305
310
  )
306
- def get_content(
311
+ async def get_content(
307
312
  limit: Optional[int] = Query(default=20, description="Number of content entries to return"),
308
313
  page: Optional[int] = Query(default=1, description="Page number"),
309
314
  sort_by: Optional[str] = Query(default="created_at", description="Field to sort by"),
@@ -311,7 +316,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
311
316
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
312
317
  ) -> PaginatedResponse[ContentResponseSchema]:
313
318
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
314
- contents, count = knowledge.get_content(limit=limit, page=page, sort_by=sort_by, sort_order=sort_order)
319
+ contents, count = await knowledge.aget_content(limit=limit, page=page, sort_by=sort_by, sort_order=sort_order)
315
320
 
316
321
  return PaginatedResponse(
317
322
  data=[
@@ -371,13 +376,13 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
371
376
  404: {"description": "Content not found", "model": NotFoundResponse},
372
377
  },
373
378
  )
374
- def get_content_by_id(
379
+ async def get_content_by_id(
375
380
  content_id: str,
376
381
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
377
382
  ) -> ContentResponseSchema:
378
383
  log_info(f"Getting content by id: {content_id}")
379
384
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
380
- content = knowledge.get_content_by_id(content_id=content_id)
385
+ content = await knowledge.aget_content_by_id(content_id=content_id)
381
386
  if not content:
382
387
  raise HTTPException(status_code=404, detail=f"Content not found: {content_id}")
383
388
  response = ContentResponseSchema.from_dict(
@@ -411,12 +416,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
411
416
  500: {"description": "Failed to delete content", "model": InternalServerErrorResponse},
412
417
  },
413
418
  )
414
- def delete_content_by_id(
419
+ async def delete_content_by_id(
415
420
  content_id: str,
416
421
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
417
422
  ) -> ContentResponseSchema:
418
423
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
419
- knowledge.remove_content_by_id(content_id=content_id)
424
+ await knowledge.aremove_content_by_id(content_id=content_id)
420
425
  log_info(f"Deleting content by id: {content_id}")
421
426
 
422
427
  return ContentResponseSchema(
@@ -443,7 +448,6 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
443
448
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
444
449
  log_info("Deleting all content")
445
450
  knowledge.remove_all_content()
446
-
447
451
  return "success"
448
452
 
449
453
  @router.get(
@@ -476,13 +480,13 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
476
480
  404: {"description": "Content not found", "model": NotFoundResponse},
477
481
  },
478
482
  )
479
- def get_content_status(
483
+ async def get_content_status(
480
484
  content_id: str,
481
485
  db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
482
486
  ) -> ContentStatusResponse:
483
487
  log_info(f"Getting content status: {content_id}")
484
488
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
485
- knowledge_status, status_message = knowledge.get_content_status(content_id=content_id)
489
+ knowledge_status, status_message = await knowledge.aget_content_status(content_id=content_id)
486
490
 
487
491
  # Handle the case where content is not found
488
492
  if knowledge_status is None:
@@ -513,11 +517,107 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
513
517
 
514
518
  return ContentStatusResponse(status=status, status_message=status_message or "")
515
519
 
520
+ @router.post(
521
+ "/knowledge/search",
522
+ status_code=200,
523
+ operation_id="search_knowledge",
524
+ summary="Search Knowledge",
525
+ description="Search the knowledge base for relevant documents using query, filters and search type.",
526
+ response_model=PaginatedResponse[VectorSearchResult],
527
+ responses={
528
+ 200: {
529
+ "description": "Search results retrieved successfully",
530
+ "content": {
531
+ "application/json": {
532
+ "example": {
533
+ "data": [
534
+ {
535
+ "id": "doc_123",
536
+ "content": "Jordan Mitchell - Software Engineer with skills in JavaScript, React, Python",
537
+ "name": "cv_1",
538
+ "meta_data": {"page": 1, "chunk": 1},
539
+ "usage": {"total_tokens": 14},
540
+ "reranking_score": 0.95,
541
+ "content_id": "content_456",
542
+ }
543
+ ],
544
+ "meta": {"page": 1, "limit": 20, "total_pages": 2, "total_count": 35},
545
+ }
546
+ }
547
+ },
548
+ },
549
+ 400: {"description": "Invalid search parameters"},
550
+ 404: {"description": "No documents found"},
551
+ },
552
+ )
553
+ def search_knowledge(request: VectorSearchRequestSchema) -> PaginatedResponse[VectorSearchResult]:
554
+ import time
555
+
556
+ start_time = time.time()
557
+
558
+ knowledge = get_knowledge_instance_by_db_id(knowledge_instances, request.db_id)
559
+
560
+ # For now, validate the vector db ids exist in the knowledge base
561
+ # We will add more logic around this once we have multi vectordb support
562
+ # If vector db ids are provided, check if any of them match the knowledge's vector db
563
+ if request.vector_db_ids:
564
+ if knowledge.vector_db and knowledge.vector_db.id:
565
+ if knowledge.vector_db.id not in request.vector_db_ids:
566
+ raise HTTPException(
567
+ status_code=400,
568
+ detail=f"None of the provided Vector DB IDs {request.vector_db_ids} match the knowledge base Vector DB ID {knowledge.vector_db.id}",
569
+ )
570
+ else:
571
+ raise HTTPException(status_code=400, detail="Knowledge base has no vector database configured")
572
+
573
+ # Calculate pagination parameters
574
+ meta = request.meta
575
+ limit = meta.limit if meta and meta.limit is not None else 20
576
+ page = meta.page if meta and meta.page is not None else 1
577
+
578
+ # Use max_results if specified, otherwise use a higher limit for search then paginate
579
+ search_limit = request.max_results
580
+
581
+ results = knowledge.search(
582
+ query=request.query, max_results=search_limit, filters=request.filters, search_type=request.search_type
583
+ )
584
+
585
+ # Calculate pagination
586
+ total_results = len(results)
587
+ start_idx = (page - 1) * limit
588
+
589
+ # Ensure start_idx doesn't exceed the total results
590
+ if start_idx >= total_results and total_results > 0:
591
+ # If page is beyond available results, return empty results
592
+ paginated_results = []
593
+ else:
594
+ end_idx = min(start_idx + limit, total_results)
595
+ paginated_results = results[start_idx:end_idx]
596
+
597
+ search_time_ms = (time.time() - start_time) * 1000
598
+
599
+ # Convert Document objects to serializable format
600
+ document_results = [VectorSearchResult.from_document(doc) for doc in paginated_results]
601
+
602
+ # Calculate pagination info
603
+ total_pages = (total_results + limit - 1) // limit # Ceiling division
604
+
605
+ return PaginatedResponse(
606
+ data=document_results,
607
+ meta=PaginationInfo(
608
+ page=page,
609
+ limit=limit,
610
+ total_pages=total_pages,
611
+ total_count=total_results,
612
+ search_time_ms=search_time_ms,
613
+ ),
614
+ )
615
+
516
616
  @router.get(
517
617
  "/knowledge/config",
518
618
  status_code=200,
519
619
  operation_id="get_knowledge_config",
520
- summary="Get Knowledge Configuration",
620
+ summary="Get Config",
521
621
  description=(
522
622
  "Retrieve available readers, chunkers, and configuration options for content processing. "
523
623
  "This endpoint provides metadata about supported file types, processing strategies, and filters."
@@ -703,38 +803,65 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
703
803
  "key": "AgenticChunker",
704
804
  "name": "AgenticChunker",
705
805
  "description": "Chunking strategy that uses an LLM to determine natural breakpoints in the text",
806
+ "metadata": {"chunk_size": 5000},
706
807
  },
707
808
  "DocumentChunker": {
708
809
  "key": "DocumentChunker",
709
810
  "name": "DocumentChunker",
710
811
  "description": "A chunking strategy that splits text based on document structure like paragraphs and sections",
711
- },
712
- "RecursiveChunker": {
713
- "key": "RecursiveChunker",
714
- "name": "RecursiveChunker",
715
- "description": "Chunking strategy that recursively splits text into chunks by finding natural break points",
716
- },
717
- "SemanticChunker": {
718
- "key": "SemanticChunker",
719
- "name": "SemanticChunker",
720
- "description": "Chunking strategy that splits text into semantic chunks using chonkie",
812
+ "metadata": {
813
+ "chunk_size": 5000,
814
+ "chunk_overlap": 0,
815
+ },
721
816
  },
722
817
  "FixedSizeChunker": {
723
818
  "key": "FixedSizeChunker",
724
819
  "name": "FixedSizeChunker",
725
820
  "description": "Chunking strategy that splits text into fixed-size chunks with optional overlap",
821
+ "metadata": {
822
+ "chunk_size": 5000,
823
+ "chunk_overlap": 0,
824
+ },
825
+ },
826
+ "MarkdownChunker": {
827
+ "key": "MarkdownChunker",
828
+ "name": "MarkdownChunker",
829
+ "description": "A chunking strategy that splits markdown based on structure like headers, paragraphs and sections",
830
+ "metadata": {
831
+ "chunk_size": 5000,
832
+ "chunk_overlap": 0,
833
+ },
834
+ },
835
+ "RecursiveChunker": {
836
+ "key": "RecursiveChunker",
837
+ "name": "RecursiveChunker",
838
+ "description": "Chunking strategy that recursively splits text into chunks by finding natural break points",
839
+ "metadata": {
840
+ "chunk_size": 5000,
841
+ "chunk_overlap": 0,
842
+ },
726
843
  },
727
844
  "RowChunker": {
728
845
  "key": "RowChunker",
729
846
  "name": "RowChunker",
730
847
  "description": "RowChunking chunking strategy",
848
+ "metadata": {},
731
849
  },
732
- "MarkdownChunker": {
733
- "key": "MarkdownChunker",
734
- "name": "MarkdownChunker",
735
- "description": "A chunking strategy that splits markdown based on structure like headers, paragraphs and sections",
850
+ "SemanticChunker": {
851
+ "key": "SemanticChunker",
852
+ "name": "SemanticChunker",
853
+ "description": "Chunking strategy that splits text into semantic chunks using chonkie",
854
+ "metadata": {"chunk_size": 5000},
736
855
  },
737
856
  },
857
+ "vector_dbs": [
858
+ {
859
+ "id": "vector_db_1",
860
+ "name": "Vector DB 1",
861
+ "description": "Vector DB 1 description",
862
+ "search_types": ["vector", "keyword", "hybrid"],
863
+ }
864
+ ],
738
865
  "filters": ["filter_tag_1", "filter_tag2"],
739
866
  }
740
867
  }
@@ -747,8 +874,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
747
874
  ) -> ConfigResponseSchema:
748
875
  knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
749
876
 
750
- # Get factory readers info
751
- readers_info = get_all_readers_info()
877
+ # Get factory readers info (including custom readers from this knowledge instance)
878
+ readers_info = get_all_readers_info(knowledge)
752
879
  reader_schemas = {}
753
880
  # Add factory readers
754
881
  for reader_info in readers_info:
@@ -760,7 +887,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
760
887
  )
761
888
 
762
889
  # Add custom readers from knowledge.readers
763
- readers_dict: Dict[str, Reader] = knowledge.get_readers() or {}
890
+ readers_result: Any = knowledge.get_readers() or {}
891
+ # Ensure readers_dict is a dictionary (defensive check)
892
+ if not isinstance(readers_result, dict):
893
+ readers_dict: Dict[str, Reader] = {}
894
+ else:
895
+ readers_dict = readers_result
764
896
  if readers_dict:
765
897
  for reader_id, reader in readers_dict.items():
766
898
  # Get chunking strategies from the reader
@@ -780,8 +912,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
780
912
  chunkers=chunking_strategies,
781
913
  )
782
914
 
783
- # Get content types to readers mapping
784
- types_of_readers = get_content_types_to_readers_mapping()
915
+ # Get content types to readers mapping (including custom readers from this knowledge instance)
916
+ types_of_readers = get_content_types_to_readers_mapping(knowledge)
785
917
  chunkers_list = get_all_chunkers_info()
786
918
 
787
919
  # Convert chunkers list to dictionary format expected by schema
@@ -790,14 +922,32 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
790
922
  chunker_key = chunker_info.get("key")
791
923
  if chunker_key:
792
924
  chunkers_dict[chunker_key] = ChunkerSchema(
793
- key=chunker_key, name=chunker_info.get("name"), description=chunker_info.get("description")
925
+ key=chunker_key,
926
+ name=chunker_info.get("name"),
927
+ description=chunker_info.get("description"),
928
+ metadata=chunker_info.get("metadata", {}),
794
929
  )
795
930
 
931
+ vector_dbs = []
932
+ if knowledge.vector_db:
933
+ search_types = knowledge.vector_db.get_supported_search_types()
934
+ name = knowledge.vector_db.name
935
+ db_id = knowledge.vector_db.id
936
+ vector_dbs.append(
937
+ VectorDbSchema(
938
+ id=db_id,
939
+ name=name,
940
+ description=knowledge.vector_db.description,
941
+ search_types=search_types,
942
+ )
943
+ )
944
+
796
945
  return ConfigResponseSchema(
797
946
  readers=reader_schemas,
947
+ vector_dbs=vector_dbs,
798
948
  readersForType=types_of_readers,
799
949
  chunkers=chunkers_dict,
800
- filters=knowledge.get_filters(),
950
+ filters=knowledge.get_valid_filters(),
801
951
  )
802
952
 
803
953
  return router
@@ -808,33 +958,41 @@ async def process_content(
808
958
  content: Content,
809
959
  reader_id: Optional[str] = None,
810
960
  chunker: Optional[str] = None,
961
+ chunk_size: Optional[int] = None,
962
+ chunk_overlap: Optional[int] = None,
811
963
  ):
812
964
  """Background task to process the content"""
813
965
 
814
966
  try:
815
967
  if reader_id:
816
968
  reader = None
817
- if knowledge.readers and reader_id in knowledge.readers:
818
- reader = knowledge.readers[reader_id]
969
+ # Use get_readers() to ensure we get a dict (handles list conversion)
970
+ custom_readers = knowledge.get_readers()
971
+ if custom_readers and reader_id in custom_readers:
972
+ reader = custom_readers[reader_id]
973
+ log_debug(f"Found custom reader: {reader.__class__.__name__}")
819
974
  else:
975
+ # Try to resolve from factory readers
820
976
  key = reader_id.lower().strip().replace("-", "_").replace(" ", "_")
821
977
  candidates = [key] + ([key[:-6]] if key.endswith("reader") else [])
822
978
  for cand in candidates:
823
979
  try:
824
980
  reader = ReaderFactory.create_reader(cand)
825
- log_debug(f"Resolved reader: {reader.__class__.__name__}")
981
+ log_debug(f"Resolved reader from factory: {reader.__class__.__name__}")
826
982
  break
827
983
  except Exception:
828
984
  continue
829
985
  if reader:
830
986
  content.reader = reader
987
+ else:
988
+ log_debug(f"Could not resolve reader with id: {reader_id}")
831
989
  if chunker and content.reader:
832
990
  # Set the chunker name on the reader - let the reader handle it internally
833
- content.reader.set_chunking_strategy_from_string(chunker)
991
+ content.reader.set_chunking_strategy_from_string(chunker, chunk_size=chunk_size, overlap=chunk_overlap)
834
992
  log_debug(f"Set chunking strategy: {chunker}")
835
993
 
836
994
  log_debug(f"Using reader: {content.reader.__class__.__name__}")
837
- await knowledge._load_content(content, upsert=False, skip_if_exists=True)
995
+ await knowledge._load_content_async(content, upsert=False, skip_if_exists=True)
838
996
  log_info(f"Content {content.id} processed successfully")
839
997
  except Exception as e:
840
998
  log_info(f"Error processing content: {e}")