agno 1.8.1__py3-none-any.whl → 2.0.0__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 (590) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +19 -27
  3. agno/agent/agent.py +3143 -4170
  4. agno/api/agent.py +11 -67
  5. agno/api/api.py +5 -46
  6. agno/api/evals.py +8 -19
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -41
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +5 -21
  11. agno/api/schemas/evals.py +7 -16
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +5 -21
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +11 -7
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +11 -66
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/db/__init__.py +24 -0
  25. agno/db/base.py +245 -0
  26. agno/db/dynamo/__init__.py +3 -0
  27. agno/db/dynamo/dynamo.py +1743 -0
  28. agno/db/dynamo/schemas.py +278 -0
  29. agno/db/dynamo/utils.py +684 -0
  30. agno/db/firestore/__init__.py +3 -0
  31. agno/db/firestore/firestore.py +1432 -0
  32. agno/db/firestore/schemas.py +130 -0
  33. agno/db/firestore/utils.py +278 -0
  34. agno/db/gcs_json/__init__.py +3 -0
  35. agno/db/gcs_json/gcs_json_db.py +1001 -0
  36. agno/db/gcs_json/utils.py +194 -0
  37. agno/db/in_memory/__init__.py +3 -0
  38. agno/db/in_memory/in_memory_db.py +882 -0
  39. agno/db/in_memory/utils.py +172 -0
  40. agno/db/json/__init__.py +3 -0
  41. agno/db/json/json_db.py +1045 -0
  42. agno/db/json/utils.py +196 -0
  43. agno/db/migrations/v1_to_v2.py +162 -0
  44. agno/db/mongo/__init__.py +3 -0
  45. agno/db/mongo/mongo.py +1416 -0
  46. agno/db/mongo/schemas.py +77 -0
  47. agno/db/mongo/utils.py +204 -0
  48. agno/db/mysql/__init__.py +3 -0
  49. agno/db/mysql/mysql.py +1719 -0
  50. agno/db/mysql/schemas.py +124 -0
  51. agno/db/mysql/utils.py +297 -0
  52. agno/db/postgres/__init__.py +3 -0
  53. agno/db/postgres/postgres.py +1710 -0
  54. agno/db/postgres/schemas.py +124 -0
  55. agno/db/postgres/utils.py +280 -0
  56. agno/db/redis/__init__.py +3 -0
  57. agno/db/redis/redis.py +1367 -0
  58. agno/db/redis/schemas.py +109 -0
  59. agno/db/redis/utils.py +288 -0
  60. agno/db/schemas/__init__.py +3 -0
  61. agno/db/schemas/evals.py +33 -0
  62. agno/db/schemas/knowledge.py +40 -0
  63. agno/db/schemas/memory.py +46 -0
  64. agno/db/singlestore/__init__.py +3 -0
  65. agno/db/singlestore/schemas.py +116 -0
  66. agno/db/singlestore/singlestore.py +1712 -0
  67. agno/db/singlestore/utils.py +326 -0
  68. agno/db/sqlite/__init__.py +3 -0
  69. agno/db/sqlite/schemas.py +119 -0
  70. agno/db/sqlite/sqlite.py +1676 -0
  71. agno/db/sqlite/utils.py +268 -0
  72. agno/db/utils.py +88 -0
  73. agno/eval/__init__.py +14 -0
  74. agno/eval/accuracy.py +154 -48
  75. agno/eval/performance.py +88 -23
  76. agno/eval/reliability.py +73 -20
  77. agno/eval/utils.py +23 -13
  78. agno/integrations/discord/__init__.py +3 -0
  79. agno/{app → integrations}/discord/client.py +15 -11
  80. agno/knowledge/__init__.py +2 -2
  81. agno/{document → knowledge}/chunking/agentic.py +2 -2
  82. agno/{document → knowledge}/chunking/document.py +2 -2
  83. agno/{document → knowledge}/chunking/fixed.py +3 -3
  84. agno/{document → knowledge}/chunking/markdown.py +2 -2
  85. agno/{document → knowledge}/chunking/recursive.py +2 -2
  86. agno/{document → knowledge}/chunking/row.py +2 -2
  87. agno/knowledge/chunking/semantic.py +59 -0
  88. agno/knowledge/chunking/strategy.py +121 -0
  89. agno/knowledge/content.py +74 -0
  90. agno/knowledge/document/__init__.py +5 -0
  91. agno/{document → knowledge/document}/base.py +12 -2
  92. agno/knowledge/embedder/__init__.py +5 -0
  93. agno/{embedder → knowledge/embedder}/aws_bedrock.py +127 -1
  94. agno/{embedder → knowledge/embedder}/azure_openai.py +65 -1
  95. agno/{embedder → knowledge/embedder}/base.py +6 -0
  96. agno/{embedder → knowledge/embedder}/cohere.py +72 -1
  97. agno/{embedder → knowledge/embedder}/fastembed.py +17 -1
  98. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  99. agno/{embedder → knowledge/embedder}/google.py +74 -1
  100. agno/{embedder → knowledge/embedder}/huggingface.py +36 -2
  101. agno/{embedder → knowledge/embedder}/jina.py +48 -2
  102. agno/knowledge/embedder/langdb.py +22 -0
  103. agno/knowledge/embedder/mistral.py +139 -0
  104. agno/{embedder → knowledge/embedder}/nebius.py +1 -1
  105. agno/{embedder → knowledge/embedder}/ollama.py +54 -3
  106. agno/knowledge/embedder/openai.py +223 -0
  107. agno/{embedder → knowledge/embedder}/sentence_transformer.py +16 -1
  108. agno/{embedder → knowledge/embedder}/together.py +1 -1
  109. agno/{embedder → knowledge/embedder}/voyageai.py +49 -1
  110. agno/knowledge/knowledge.py +1551 -0
  111. agno/knowledge/reader/__init__.py +7 -0
  112. agno/{document → knowledge}/reader/arxiv_reader.py +32 -4
  113. agno/knowledge/reader/base.py +88 -0
  114. agno/{document → knowledge}/reader/csv_reader.py +47 -65
  115. agno/knowledge/reader/docx_reader.py +83 -0
  116. agno/{document → knowledge}/reader/firecrawl_reader.py +42 -21
  117. agno/{document → knowledge}/reader/json_reader.py +30 -9
  118. agno/{document → knowledge}/reader/markdown_reader.py +58 -9
  119. agno/{document → knowledge}/reader/pdf_reader.py +71 -126
  120. agno/knowledge/reader/reader_factory.py +268 -0
  121. agno/knowledge/reader/s3_reader.py +101 -0
  122. agno/{document → knowledge}/reader/text_reader.py +31 -10
  123. agno/knowledge/reader/url_reader.py +128 -0
  124. agno/knowledge/reader/web_search_reader.py +366 -0
  125. agno/{document → knowledge}/reader/website_reader.py +37 -10
  126. agno/knowledge/reader/wikipedia_reader.py +59 -0
  127. agno/knowledge/reader/youtube_reader.py +78 -0
  128. agno/knowledge/remote_content/remote_content.py +88 -0
  129. agno/{reranker → knowledge/reranker}/base.py +1 -1
  130. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  131. agno/{reranker → knowledge/reranker}/infinity.py +2 -2
  132. agno/{reranker → knowledge/reranker}/sentence_transformer.py +2 -2
  133. agno/knowledge/types.py +30 -0
  134. agno/knowledge/utils.py +169 -0
  135. agno/media.py +269 -268
  136. agno/memory/__init__.py +2 -10
  137. agno/memory/manager.py +1003 -148
  138. agno/models/aimlapi/__init__.py +2 -2
  139. agno/models/aimlapi/aimlapi.py +6 -6
  140. agno/models/anthropic/claude.py +131 -131
  141. agno/models/aws/bedrock.py +110 -182
  142. agno/models/aws/claude.py +64 -18
  143. agno/models/azure/ai_foundry.py +73 -23
  144. agno/models/base.py +346 -290
  145. agno/models/cerebras/cerebras.py +84 -27
  146. agno/models/cohere/chat.py +106 -98
  147. agno/models/google/gemini.py +105 -46
  148. agno/models/groq/groq.py +97 -35
  149. agno/models/huggingface/huggingface.py +92 -27
  150. agno/models/ibm/watsonx.py +72 -13
  151. agno/models/litellm/chat.py +85 -13
  152. agno/models/message.py +46 -151
  153. agno/models/meta/llama.py +85 -49
  154. agno/models/metrics.py +120 -0
  155. agno/models/mistral/mistral.py +90 -21
  156. agno/models/ollama/__init__.py +0 -2
  157. agno/models/ollama/chat.py +85 -47
  158. agno/models/openai/chat.py +154 -37
  159. agno/models/openai/responses.py +178 -105
  160. agno/models/perplexity/perplexity.py +26 -2
  161. agno/models/portkey/portkey.py +0 -7
  162. agno/models/response.py +15 -9
  163. agno/models/utils.py +20 -0
  164. agno/models/vercel/__init__.py +2 -2
  165. agno/models/vercel/v0.py +1 -1
  166. agno/models/vllm/__init__.py +2 -2
  167. agno/models/vllm/vllm.py +3 -3
  168. agno/models/xai/xai.py +10 -10
  169. agno/os/__init__.py +3 -0
  170. agno/os/app.py +497 -0
  171. agno/os/auth.py +47 -0
  172. agno/os/config.py +103 -0
  173. agno/os/interfaces/agui/__init__.py +3 -0
  174. agno/os/interfaces/agui/agui.py +31 -0
  175. agno/{app/agui/async_router.py → os/interfaces/agui/router.py} +16 -16
  176. agno/{app → os/interfaces}/agui/utils.py +77 -33
  177. agno/os/interfaces/base.py +21 -0
  178. agno/os/interfaces/slack/__init__.py +3 -0
  179. agno/{app/slack/async_router.py → os/interfaces/slack/router.py} +3 -5
  180. agno/os/interfaces/slack/slack.py +32 -0
  181. agno/os/interfaces/whatsapp/__init__.py +3 -0
  182. agno/{app/whatsapp/async_router.py → os/interfaces/whatsapp/router.py} +4 -7
  183. agno/os/interfaces/whatsapp/whatsapp.py +29 -0
  184. agno/os/mcp.py +235 -0
  185. agno/os/router.py +1400 -0
  186. agno/os/routers/__init__.py +3 -0
  187. agno/os/routers/evals/__init__.py +3 -0
  188. agno/os/routers/evals/evals.py +393 -0
  189. agno/os/routers/evals/schemas.py +142 -0
  190. agno/os/routers/evals/utils.py +161 -0
  191. agno/os/routers/knowledge/__init__.py +3 -0
  192. agno/os/routers/knowledge/knowledge.py +850 -0
  193. agno/os/routers/knowledge/schemas.py +118 -0
  194. agno/os/routers/memory/__init__.py +3 -0
  195. agno/os/routers/memory/memory.py +410 -0
  196. agno/os/routers/memory/schemas.py +58 -0
  197. agno/os/routers/metrics/__init__.py +3 -0
  198. agno/os/routers/metrics/metrics.py +178 -0
  199. agno/os/routers/metrics/schemas.py +47 -0
  200. agno/os/routers/session/__init__.py +3 -0
  201. agno/os/routers/session/session.py +536 -0
  202. agno/os/schema.py +945 -0
  203. agno/{app/playground → os}/settings.py +7 -15
  204. agno/os/utils.py +270 -0
  205. agno/reasoning/azure_ai_foundry.py +4 -4
  206. agno/reasoning/deepseek.py +4 -4
  207. agno/reasoning/default.py +6 -11
  208. agno/reasoning/groq.py +4 -4
  209. agno/reasoning/helpers.py +4 -6
  210. agno/reasoning/ollama.py +4 -4
  211. agno/reasoning/openai.py +4 -4
  212. agno/run/agent.py +633 -0
  213. agno/run/base.py +53 -77
  214. agno/run/cancel.py +81 -0
  215. agno/run/team.py +243 -96
  216. agno/run/workflow.py +550 -12
  217. agno/session/__init__.py +10 -0
  218. agno/session/agent.py +244 -0
  219. agno/session/summary.py +225 -0
  220. agno/session/team.py +262 -0
  221. agno/{storage/session/v2 → session}/workflow.py +47 -24
  222. agno/team/__init__.py +15 -16
  223. agno/team/team.py +3260 -4824
  224. agno/tools/agentql.py +14 -5
  225. agno/tools/airflow.py +9 -4
  226. agno/tools/api.py +7 -3
  227. agno/tools/apify.py +2 -46
  228. agno/tools/arxiv.py +8 -3
  229. agno/tools/aws_lambda.py +7 -5
  230. agno/tools/aws_ses.py +7 -1
  231. agno/tools/baidusearch.py +4 -1
  232. agno/tools/bitbucket.py +4 -4
  233. agno/tools/brandfetch.py +14 -11
  234. agno/tools/bravesearch.py +4 -1
  235. agno/tools/brightdata.py +43 -23
  236. agno/tools/browserbase.py +13 -4
  237. agno/tools/calcom.py +12 -10
  238. agno/tools/calculator.py +10 -27
  239. agno/tools/cartesia.py +20 -17
  240. agno/tools/{clickup_tool.py → clickup.py} +12 -25
  241. agno/tools/confluence.py +8 -8
  242. agno/tools/crawl4ai.py +7 -1
  243. agno/tools/csv_toolkit.py +9 -8
  244. agno/tools/dalle.py +22 -12
  245. agno/tools/daytona.py +13 -16
  246. agno/tools/decorator.py +6 -3
  247. agno/tools/desi_vocal.py +17 -8
  248. agno/tools/discord.py +11 -8
  249. agno/tools/docker.py +30 -42
  250. agno/tools/duckdb.py +34 -53
  251. agno/tools/duckduckgo.py +8 -7
  252. agno/tools/e2b.py +62 -62
  253. agno/tools/eleven_labs.py +36 -29
  254. agno/tools/email.py +4 -1
  255. agno/tools/evm.py +7 -1
  256. agno/tools/exa.py +19 -14
  257. agno/tools/fal.py +30 -30
  258. agno/tools/file.py +9 -8
  259. agno/tools/financial_datasets.py +25 -44
  260. agno/tools/firecrawl.py +22 -22
  261. agno/tools/function.py +127 -18
  262. agno/tools/giphy.py +23 -11
  263. agno/tools/github.py +48 -126
  264. agno/tools/gmail.py +45 -61
  265. agno/tools/google_bigquery.py +7 -6
  266. agno/tools/google_maps.py +11 -26
  267. agno/tools/googlesearch.py +7 -2
  268. agno/tools/googlesheets.py +21 -17
  269. agno/tools/hackernews.py +9 -5
  270. agno/tools/jina.py +5 -4
  271. agno/tools/jira.py +18 -9
  272. agno/tools/knowledge.py +31 -32
  273. agno/tools/linear.py +19 -34
  274. agno/tools/linkup.py +5 -1
  275. agno/tools/local_file_system.py +8 -5
  276. agno/tools/lumalab.py +32 -20
  277. agno/tools/mcp.py +1 -2
  278. agno/tools/mem0.py +18 -12
  279. agno/tools/memori.py +14 -10
  280. agno/tools/mlx_transcribe.py +3 -2
  281. agno/tools/models/azure_openai.py +33 -15
  282. agno/tools/models/gemini.py +59 -32
  283. agno/tools/models/groq.py +30 -23
  284. agno/tools/models/nebius.py +28 -12
  285. agno/tools/models_labs.py +40 -16
  286. agno/tools/moviepy_video.py +7 -6
  287. agno/tools/neo4j.py +10 -8
  288. agno/tools/newspaper.py +7 -2
  289. agno/tools/newspaper4k.py +8 -3
  290. agno/tools/openai.py +58 -32
  291. agno/tools/openbb.py +12 -11
  292. agno/tools/opencv.py +63 -47
  293. agno/tools/openweather.py +14 -12
  294. agno/tools/pandas.py +11 -3
  295. agno/tools/postgres.py +4 -12
  296. agno/tools/pubmed.py +4 -1
  297. agno/tools/python.py +9 -22
  298. agno/tools/reasoning.py +35 -27
  299. agno/tools/reddit.py +11 -26
  300. agno/tools/replicate.py +55 -42
  301. agno/tools/resend.py +4 -1
  302. agno/tools/scrapegraph.py +15 -14
  303. agno/tools/searxng.py +10 -23
  304. agno/tools/serpapi.py +6 -3
  305. agno/tools/serper.py +13 -4
  306. agno/tools/shell.py +9 -2
  307. agno/tools/slack.py +12 -11
  308. agno/tools/sleep.py +3 -2
  309. agno/tools/spider.py +24 -4
  310. agno/tools/sql.py +7 -6
  311. agno/tools/tavily.py +6 -4
  312. agno/tools/telegram.py +12 -4
  313. agno/tools/todoist.py +11 -31
  314. agno/tools/toolkit.py +1 -1
  315. agno/tools/trafilatura.py +22 -6
  316. agno/tools/trello.py +9 -22
  317. agno/tools/twilio.py +10 -3
  318. agno/tools/user_control_flow.py +6 -1
  319. agno/tools/valyu.py +34 -5
  320. agno/tools/visualization.py +19 -28
  321. agno/tools/webbrowser.py +4 -3
  322. agno/tools/webex.py +11 -7
  323. agno/tools/website.py +15 -46
  324. agno/tools/webtools.py +12 -4
  325. agno/tools/whatsapp.py +5 -9
  326. agno/tools/wikipedia.py +20 -13
  327. agno/tools/x.py +14 -13
  328. agno/tools/yfinance.py +13 -40
  329. agno/tools/youtube.py +26 -20
  330. agno/tools/zendesk.py +7 -2
  331. agno/tools/zep.py +10 -7
  332. agno/tools/zoom.py +10 -9
  333. agno/utils/common.py +1 -19
  334. agno/utils/events.py +100 -123
  335. agno/utils/gemini.py +32 -2
  336. agno/utils/knowledge.py +29 -0
  337. agno/utils/log.py +54 -4
  338. agno/utils/mcp.py +68 -10
  339. agno/utils/media.py +39 -0
  340. agno/utils/message.py +12 -1
  341. agno/utils/models/aws_claude.py +1 -1
  342. agno/utils/models/claude.py +47 -4
  343. agno/utils/models/cohere.py +1 -1
  344. agno/utils/models/mistral.py +8 -7
  345. agno/utils/models/schema_utils.py +3 -3
  346. agno/utils/models/watsonx.py +1 -1
  347. agno/utils/openai.py +1 -1
  348. agno/utils/pprint.py +33 -32
  349. agno/utils/print_response/agent.py +779 -0
  350. agno/utils/print_response/team.py +1669 -0
  351. agno/utils/print_response/workflow.py +1451 -0
  352. agno/utils/prompts.py +14 -14
  353. agno/utils/reasoning.py +87 -0
  354. agno/utils/response.py +42 -42
  355. agno/utils/streamlit.py +481 -0
  356. agno/utils/string.py +8 -22
  357. agno/utils/team.py +50 -0
  358. agno/utils/timer.py +2 -2
  359. agno/vectordb/base.py +33 -21
  360. agno/vectordb/cassandra/cassandra.py +287 -23
  361. agno/vectordb/chroma/chromadb.py +482 -59
  362. agno/vectordb/clickhouse/clickhousedb.py +270 -63
  363. agno/vectordb/couchbase/couchbase.py +309 -29
  364. agno/vectordb/lancedb/lance_db.py +360 -21
  365. agno/vectordb/langchaindb/__init__.py +5 -0
  366. agno/vectordb/langchaindb/langchaindb.py +145 -0
  367. agno/vectordb/lightrag/__init__.py +5 -0
  368. agno/vectordb/lightrag/lightrag.py +374 -0
  369. agno/vectordb/llamaindex/llamaindexdb.py +127 -0
  370. agno/vectordb/milvus/milvus.py +242 -32
  371. agno/vectordb/mongodb/mongodb.py +200 -24
  372. agno/vectordb/pgvector/pgvector.py +319 -37
  373. agno/vectordb/pineconedb/pineconedb.py +221 -27
  374. agno/vectordb/qdrant/qdrant.py +334 -14
  375. agno/vectordb/singlestore/singlestore.py +286 -29
  376. agno/vectordb/surrealdb/surrealdb.py +187 -7
  377. agno/vectordb/upstashdb/upstashdb.py +342 -26
  378. agno/vectordb/weaviate/weaviate.py +227 -165
  379. agno/workflow/__init__.py +17 -13
  380. agno/workflow/{v2/condition.py → condition.py} +135 -32
  381. agno/workflow/{v2/loop.py → loop.py} +115 -28
  382. agno/workflow/{v2/parallel.py → parallel.py} +138 -108
  383. agno/workflow/{v2/router.py → router.py} +133 -32
  384. agno/workflow/{v2/step.py → step.py} +207 -49
  385. agno/workflow/{v2/steps.py → steps.py} +147 -66
  386. agno/workflow/types.py +482 -0
  387. agno/workflow/workflow.py +2410 -696
  388. agno-2.0.0.dist-info/METADATA +494 -0
  389. agno-2.0.0.dist-info/RECORD +515 -0
  390. agno-2.0.0.dist-info/licenses/LICENSE +201 -0
  391. agno/agent/metrics.py +0 -107
  392. agno/api/app.py +0 -35
  393. agno/api/playground.py +0 -92
  394. agno/api/schemas/app.py +0 -12
  395. agno/api/schemas/playground.py +0 -22
  396. agno/api/schemas/user.py +0 -35
  397. agno/api/schemas/workspace.py +0 -46
  398. agno/api/user.py +0 -160
  399. agno/api/workflows.py +0 -33
  400. agno/api/workspace.py +0 -175
  401. agno/app/agui/__init__.py +0 -3
  402. agno/app/agui/app.py +0 -17
  403. agno/app/agui/sync_router.py +0 -120
  404. agno/app/base.py +0 -186
  405. agno/app/discord/__init__.py +0 -3
  406. agno/app/fastapi/__init__.py +0 -3
  407. agno/app/fastapi/app.py +0 -107
  408. agno/app/fastapi/async_router.py +0 -457
  409. agno/app/fastapi/sync_router.py +0 -448
  410. agno/app/playground/app.py +0 -228
  411. agno/app/playground/async_router.py +0 -1050
  412. agno/app/playground/deploy.py +0 -249
  413. agno/app/playground/operator.py +0 -183
  414. agno/app/playground/schemas.py +0 -220
  415. agno/app/playground/serve.py +0 -55
  416. agno/app/playground/sync_router.py +0 -1042
  417. agno/app/playground/utils.py +0 -46
  418. agno/app/settings.py +0 -15
  419. agno/app/slack/__init__.py +0 -3
  420. agno/app/slack/app.py +0 -19
  421. agno/app/slack/sync_router.py +0 -92
  422. agno/app/utils.py +0 -54
  423. agno/app/whatsapp/__init__.py +0 -3
  424. agno/app/whatsapp/app.py +0 -15
  425. agno/app/whatsapp/sync_router.py +0 -197
  426. agno/cli/auth_server.py +0 -249
  427. agno/cli/config.py +0 -274
  428. agno/cli/console.py +0 -88
  429. agno/cli/credentials.py +0 -23
  430. agno/cli/entrypoint.py +0 -571
  431. agno/cli/operator.py +0 -357
  432. agno/cli/settings.py +0 -96
  433. agno/cli/ws/ws_cli.py +0 -817
  434. agno/constants.py +0 -13
  435. agno/document/__init__.py +0 -5
  436. agno/document/chunking/semantic.py +0 -45
  437. agno/document/chunking/strategy.py +0 -31
  438. agno/document/reader/__init__.py +0 -5
  439. agno/document/reader/base.py +0 -47
  440. agno/document/reader/docx_reader.py +0 -60
  441. agno/document/reader/gcs/pdf_reader.py +0 -44
  442. agno/document/reader/s3/pdf_reader.py +0 -59
  443. agno/document/reader/s3/text_reader.py +0 -63
  444. agno/document/reader/url_reader.py +0 -59
  445. agno/document/reader/youtube_reader.py +0 -58
  446. agno/embedder/__init__.py +0 -5
  447. agno/embedder/langdb.py +0 -80
  448. agno/embedder/mistral.py +0 -82
  449. agno/embedder/openai.py +0 -78
  450. agno/file/__init__.py +0 -5
  451. agno/file/file.py +0 -16
  452. agno/file/local/csv.py +0 -32
  453. agno/file/local/txt.py +0 -19
  454. agno/infra/app.py +0 -240
  455. agno/infra/base.py +0 -144
  456. agno/infra/context.py +0 -20
  457. agno/infra/db_app.py +0 -52
  458. agno/infra/resource.py +0 -205
  459. agno/infra/resources.py +0 -55
  460. agno/knowledge/agent.py +0 -702
  461. agno/knowledge/arxiv.py +0 -33
  462. agno/knowledge/combined.py +0 -36
  463. agno/knowledge/csv.py +0 -144
  464. agno/knowledge/csv_url.py +0 -124
  465. agno/knowledge/document.py +0 -223
  466. agno/knowledge/docx.py +0 -137
  467. agno/knowledge/firecrawl.py +0 -34
  468. agno/knowledge/gcs/__init__.py +0 -0
  469. agno/knowledge/gcs/base.py +0 -39
  470. agno/knowledge/gcs/pdf.py +0 -125
  471. agno/knowledge/json.py +0 -137
  472. agno/knowledge/langchain.py +0 -71
  473. agno/knowledge/light_rag.py +0 -273
  474. agno/knowledge/llamaindex.py +0 -66
  475. agno/knowledge/markdown.py +0 -154
  476. agno/knowledge/pdf.py +0 -164
  477. agno/knowledge/pdf_bytes.py +0 -42
  478. agno/knowledge/pdf_url.py +0 -148
  479. agno/knowledge/s3/__init__.py +0 -0
  480. agno/knowledge/s3/base.py +0 -64
  481. agno/knowledge/s3/pdf.py +0 -33
  482. agno/knowledge/s3/text.py +0 -34
  483. agno/knowledge/text.py +0 -141
  484. agno/knowledge/url.py +0 -46
  485. agno/knowledge/website.py +0 -179
  486. agno/knowledge/wikipedia.py +0 -32
  487. agno/knowledge/youtube.py +0 -35
  488. agno/memory/agent.py +0 -423
  489. agno/memory/classifier.py +0 -104
  490. agno/memory/db/__init__.py +0 -5
  491. agno/memory/db/base.py +0 -42
  492. agno/memory/db/mongodb.py +0 -189
  493. agno/memory/db/postgres.py +0 -203
  494. agno/memory/db/sqlite.py +0 -193
  495. agno/memory/memory.py +0 -22
  496. agno/memory/row.py +0 -36
  497. agno/memory/summarizer.py +0 -201
  498. agno/memory/summary.py +0 -19
  499. agno/memory/team.py +0 -415
  500. agno/memory/v2/__init__.py +0 -2
  501. agno/memory/v2/db/__init__.py +0 -1
  502. agno/memory/v2/db/base.py +0 -42
  503. agno/memory/v2/db/firestore.py +0 -339
  504. agno/memory/v2/db/mongodb.py +0 -196
  505. agno/memory/v2/db/postgres.py +0 -214
  506. agno/memory/v2/db/redis.py +0 -187
  507. agno/memory/v2/db/schema.py +0 -54
  508. agno/memory/v2/db/sqlite.py +0 -209
  509. agno/memory/v2/manager.py +0 -437
  510. agno/memory/v2/memory.py +0 -1097
  511. agno/memory/v2/schema.py +0 -55
  512. agno/memory/v2/summarizer.py +0 -215
  513. agno/memory/workflow.py +0 -38
  514. agno/models/ollama/tools.py +0 -430
  515. agno/models/qwen/__init__.py +0 -5
  516. agno/playground/__init__.py +0 -10
  517. agno/playground/deploy.py +0 -3
  518. agno/playground/playground.py +0 -3
  519. agno/playground/serve.py +0 -3
  520. agno/playground/settings.py +0 -3
  521. agno/reranker/__init__.py +0 -0
  522. agno/run/response.py +0 -467
  523. agno/run/v2/__init__.py +0 -0
  524. agno/run/v2/workflow.py +0 -567
  525. agno/storage/__init__.py +0 -0
  526. agno/storage/agent/__init__.py +0 -0
  527. agno/storage/agent/dynamodb.py +0 -1
  528. agno/storage/agent/json.py +0 -1
  529. agno/storage/agent/mongodb.py +0 -1
  530. agno/storage/agent/postgres.py +0 -1
  531. agno/storage/agent/singlestore.py +0 -1
  532. agno/storage/agent/sqlite.py +0 -1
  533. agno/storage/agent/yaml.py +0 -1
  534. agno/storage/base.py +0 -60
  535. agno/storage/dynamodb.py +0 -673
  536. agno/storage/firestore.py +0 -297
  537. agno/storage/gcs_json.py +0 -261
  538. agno/storage/in_memory.py +0 -234
  539. agno/storage/json.py +0 -237
  540. agno/storage/mongodb.py +0 -328
  541. agno/storage/mysql.py +0 -685
  542. agno/storage/postgres.py +0 -682
  543. agno/storage/redis.py +0 -336
  544. agno/storage/session/__init__.py +0 -16
  545. agno/storage/session/agent.py +0 -64
  546. agno/storage/session/team.py +0 -63
  547. agno/storage/session/v2/__init__.py +0 -5
  548. agno/storage/session/workflow.py +0 -61
  549. agno/storage/singlestore.py +0 -606
  550. agno/storage/sqlite.py +0 -646
  551. agno/storage/workflow/__init__.py +0 -0
  552. agno/storage/workflow/mongodb.py +0 -1
  553. agno/storage/workflow/postgres.py +0 -1
  554. agno/storage/workflow/sqlite.py +0 -1
  555. agno/storage/yaml.py +0 -241
  556. agno/tools/thinking.py +0 -73
  557. agno/utils/defaults.py +0 -57
  558. agno/utils/filesystem.py +0 -39
  559. agno/utils/git.py +0 -52
  560. agno/utils/json_io.py +0 -30
  561. agno/utils/load_env.py +0 -19
  562. agno/utils/py_io.py +0 -19
  563. agno/utils/pyproject.py +0 -18
  564. agno/utils/resource_filter.py +0 -31
  565. agno/workflow/v2/__init__.py +0 -21
  566. agno/workflow/v2/types.py +0 -357
  567. agno/workflow/v2/workflow.py +0 -3312
  568. agno/workspace/__init__.py +0 -0
  569. agno/workspace/config.py +0 -325
  570. agno/workspace/enums.py +0 -6
  571. agno/workspace/helpers.py +0 -52
  572. agno/workspace/operator.py +0 -757
  573. agno/workspace/settings.py +0 -158
  574. agno-1.8.1.dist-info/METADATA +0 -982
  575. agno-1.8.1.dist-info/RECORD +0 -566
  576. agno-1.8.1.dist-info/entry_points.txt +0 -3
  577. agno-1.8.1.dist-info/licenses/LICENSE +0 -375
  578. /agno/{app → db/migrations}/__init__.py +0 -0
  579. /agno/{app/playground/__init__.py → db/schemas/metrics.py} +0 -0
  580. /agno/{cli → integrations}/__init__.py +0 -0
  581. /agno/{cli/ws → knowledge/chunking}/__init__.py +0 -0
  582. /agno/{document/chunking → knowledge/remote_content}/__init__.py +0 -0
  583. /agno/{document/reader/gcs → knowledge/reranker}/__init__.py +0 -0
  584. /agno/{document/reader/s3 → os/interfaces}/__init__.py +0 -0
  585. /agno/{app → os/interfaces}/slack/security.py +0 -0
  586. /agno/{app → os/interfaces}/whatsapp/security.py +0 -0
  587. /agno/{file/local → utils/print_response}/__init__.py +0 -0
  588. /agno/{infra → vectordb/llamaindex}/__init__.py +0 -0
  589. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/WHEEL +0 -0
  590. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/top_level.txt +0 -0
agno/os/router.py ADDED
@@ -0,0 +1,1400 @@
1
+ import json
2
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Union, cast
3
+ from uuid import uuid4
4
+
5
+ from fastapi import (
6
+ APIRouter,
7
+ Depends,
8
+ File,
9
+ Form,
10
+ HTTPException,
11
+ UploadFile,
12
+ WebSocket,
13
+ )
14
+ from fastapi.responses import JSONResponse, StreamingResponse
15
+ from pydantic import BaseModel
16
+
17
+ from agno.agent.agent import Agent
18
+ from agno.media import Audio, Image, Video
19
+ from agno.media import File as FileMedia
20
+ from agno.os.auth import get_authentication_dependency
21
+ from agno.os.schema import (
22
+ AgentResponse,
23
+ AgentSummaryResponse,
24
+ BadRequestResponse,
25
+ ConfigResponse,
26
+ HealthResponse,
27
+ InterfaceResponse,
28
+ InternalServerErrorResponse,
29
+ Model,
30
+ NotFoundResponse,
31
+ TeamResponse,
32
+ TeamSummaryResponse,
33
+ UnauthenticatedResponse,
34
+ ValidationErrorResponse,
35
+ WorkflowResponse,
36
+ WorkflowSummaryResponse,
37
+ )
38
+ from agno.os.settings import AgnoAPISettings
39
+ from agno.os.utils import (
40
+ get_agent_by_id,
41
+ get_team_by_id,
42
+ get_workflow_by_id,
43
+ process_audio,
44
+ process_document,
45
+ process_image,
46
+ process_video,
47
+ )
48
+ from agno.run.agent import RunErrorEvent, RunOutput
49
+ from agno.run.team import RunErrorEvent as TeamRunErrorEvent
50
+ from agno.run.workflow import WorkflowErrorEvent
51
+ from agno.team.team import Team
52
+ from agno.utils.log import log_debug, log_error, log_warning, logger
53
+ from agno.workflow.workflow import Workflow
54
+
55
+ if TYPE_CHECKING:
56
+ from agno.os.app import AgentOS
57
+
58
+
59
+ def format_sse_event(json_data: str) -> str:
60
+ """Parse JSON data into SSE-compliant format.
61
+
62
+ Args:
63
+ json_data: JSON string containing the event data
64
+
65
+ Returns:
66
+ SSE-formatted response:
67
+
68
+ ```
69
+ event: EventName
70
+ data: { ... }
71
+
72
+ event: AnotherEventName
73
+ data: { ... }
74
+ ```
75
+ """
76
+ try:
77
+ # Parse the JSON to extract the event type
78
+ data = json.loads(json_data)
79
+ event_type = data.get("event", "message")
80
+
81
+ # Format as SSE: event: <event_type>\ndata: <json_data>\n\n
82
+ return f"event: {event_type}\ndata: {json_data}\n\n"
83
+ except (json.JSONDecodeError, KeyError):
84
+ # Fallback to generic message event if parsing fails
85
+ return f"event: message\ndata: {json_data}\n\n"
86
+
87
+
88
+ class WebSocketManager:
89
+ """Manages WebSocket connections for workflow runs"""
90
+
91
+ active_connections: Dict[str, WebSocket] # {run_id: websocket}
92
+
93
+ def __init__(
94
+ self,
95
+ active_connections: Optional[Dict[str, WebSocket]] = None,
96
+ ):
97
+ # Store active connections: {run_id: websocket}
98
+ self.active_connections = active_connections or {}
99
+
100
+ async def connect(self, websocket: WebSocket):
101
+ """Accept WebSocket connection"""
102
+ await websocket.accept()
103
+ logger.debug("WebSocket connected")
104
+
105
+ # Send connection confirmation
106
+ await websocket.send_text(
107
+ json.dumps(
108
+ {
109
+ "event": "connected",
110
+ "message": "Connected to workflow events",
111
+ }
112
+ )
113
+ )
114
+
115
+ async def register_workflow_websocket(self, run_id: str, websocket: WebSocket):
116
+ """Register a workflow run with its WebSocket connection"""
117
+ self.active_connections[run_id] = websocket
118
+ logger.debug(f"Registered WebSocket for run_id: {run_id}")
119
+
120
+ async def disconnect_by_run_id(self, run_id: str):
121
+ """Remove WebSocket connection by run_id"""
122
+ if run_id in self.active_connections:
123
+ del self.active_connections[run_id]
124
+ logger.debug(f"WebSocket disconnected for run_id: {run_id}")
125
+
126
+ async def get_websocket_for_run(self, run_id: str) -> Optional[WebSocket]:
127
+ """Get WebSocket connection for a workflow run"""
128
+ return self.active_connections.get(run_id)
129
+
130
+
131
+ # Global manager instance
132
+ websocket_manager = WebSocketManager(
133
+ active_connections={},
134
+ )
135
+
136
+
137
+ async def agent_response_streamer(
138
+ agent: Agent,
139
+ message: str,
140
+ session_id: Optional[str] = None,
141
+ user_id: Optional[str] = None,
142
+ images: Optional[List[Image]] = None,
143
+ audio: Optional[List[Audio]] = None,
144
+ videos: Optional[List[Video]] = None,
145
+ files: Optional[List[FileMedia]] = None,
146
+ ) -> AsyncGenerator:
147
+ try:
148
+ run_response = agent.arun(
149
+ input=message,
150
+ session_id=session_id,
151
+ user_id=user_id,
152
+ images=images,
153
+ audio=audio,
154
+ videos=videos,
155
+ files=files,
156
+ stream=True,
157
+ stream_intermediate_steps=True,
158
+ )
159
+ async for run_response_chunk in run_response:
160
+ yield format_sse_event(run_response_chunk.to_json())
161
+
162
+ except Exception as e:
163
+ import traceback
164
+
165
+ traceback.print_exc(limit=3)
166
+ error_response = RunErrorEvent(
167
+ content=str(e),
168
+ )
169
+ yield format_sse_event(error_response.to_json())
170
+
171
+
172
+ async def agent_continue_response_streamer(
173
+ agent: Agent,
174
+ run_id: Optional[str] = None,
175
+ updated_tools: Optional[List] = None,
176
+ session_id: Optional[str] = None,
177
+ user_id: Optional[str] = None,
178
+ ) -> AsyncGenerator:
179
+ try:
180
+ continue_response = agent.acontinue_run(
181
+ run_id=run_id,
182
+ updated_tools=updated_tools,
183
+ session_id=session_id,
184
+ user_id=user_id,
185
+ stream=True,
186
+ stream_intermediate_steps=True,
187
+ )
188
+ async for run_response_chunk in continue_response:
189
+ yield format_sse_event(run_response_chunk.to_json())
190
+
191
+ except Exception as e:
192
+ import traceback
193
+
194
+ traceback.print_exc(limit=3)
195
+ error_response = RunErrorEvent(
196
+ content=str(e),
197
+ )
198
+ yield format_sse_event(error_response.to_json())
199
+ return
200
+
201
+
202
+ async def team_response_streamer(
203
+ team: Team,
204
+ message: str,
205
+ session_id: Optional[str] = None,
206
+ user_id: Optional[str] = None,
207
+ images: Optional[List[Image]] = None,
208
+ audio: Optional[List[Audio]] = None,
209
+ videos: Optional[List[Video]] = None,
210
+ files: Optional[List[FileMedia]] = None,
211
+ ) -> AsyncGenerator:
212
+ """Run the given team asynchronously and yield its response"""
213
+ try:
214
+ run_response = team.arun(
215
+ input=message,
216
+ session_id=session_id,
217
+ user_id=user_id,
218
+ images=images,
219
+ audio=audio,
220
+ videos=videos,
221
+ files=files,
222
+ stream=True,
223
+ stream_intermediate_steps=True,
224
+ )
225
+ async for run_response_chunk in run_response:
226
+ yield format_sse_event(run_response_chunk.to_json())
227
+
228
+ except Exception as e:
229
+ import traceback
230
+
231
+ traceback.print_exc()
232
+ error_response = TeamRunErrorEvent(
233
+ content=str(e),
234
+ )
235
+ yield format_sse_event(error_response.to_json())
236
+ return
237
+
238
+
239
+ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os: "AgentOS"):
240
+ """Handle workflow execution directly via WebSocket"""
241
+ try:
242
+ workflow_id = message.get("workflow_id")
243
+ session_id = message.get("session_id")
244
+ user_message = message.get("message", "")
245
+ user_id = message.get("user_id")
246
+
247
+ if not workflow_id:
248
+ await websocket.send_text(json.dumps({"event": "error", "error": "workflow_id is required"}))
249
+ return
250
+
251
+ # Get workflow from OS
252
+ workflow = get_workflow_by_id(workflow_id, os.workflows)
253
+ if not workflow:
254
+ await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
255
+ return
256
+
257
+ # Generate session_id if not provided
258
+ # Use workflow's default session_id if not provided in message
259
+ if not session_id:
260
+ if workflow.session_id:
261
+ session_id = workflow.session_id
262
+ else:
263
+ session_id = str(uuid4())
264
+
265
+ # Execute workflow in background with streaming
266
+ await workflow.arun(
267
+ input=user_message,
268
+ session_id=session_id,
269
+ user_id=user_id,
270
+ stream=True,
271
+ stream_intermediate_steps=True,
272
+ background=True,
273
+ websocket=websocket,
274
+ )
275
+
276
+ except Exception as e:
277
+ logger.error(f"Error executing workflow via WebSocket: {e}")
278
+ await websocket.send_text(json.dumps({"event": "error", "error": str(e)}))
279
+
280
+
281
+ async def workflow_response_streamer(
282
+ workflow: Workflow,
283
+ input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel]] = None,
284
+ session_id: Optional[str] = None,
285
+ user_id: Optional[str] = None,
286
+ **kwargs: Any,
287
+ ) -> AsyncGenerator:
288
+ try:
289
+ run_response = await workflow.arun(
290
+ input=input,
291
+ session_id=session_id,
292
+ user_id=user_id,
293
+ stream=True,
294
+ stream_intermediate_steps=True,
295
+ **kwargs,
296
+ )
297
+
298
+ async for run_response_chunk in run_response:
299
+ yield format_sse_event(run_response_chunk.to_json())
300
+
301
+ except Exception as e:
302
+ import traceback
303
+
304
+ traceback.print_exc()
305
+ error_response = WorkflowErrorEvent(
306
+ error=str(e),
307
+ )
308
+ yield format_sse_event(error_response.to_json())
309
+ return
310
+
311
+
312
+ def get_base_router(
313
+ os: "AgentOS",
314
+ settings: AgnoAPISettings = AgnoAPISettings(),
315
+ ) -> APIRouter:
316
+ """
317
+ Create the base FastAPI router with comprehensive OpenAPI documentation.
318
+
319
+ This router provides endpoints for:
320
+ - Core system operations (health, config, models)
321
+ - Agent management and execution
322
+ - Team collaboration and coordination
323
+ - Workflow automation and orchestration
324
+ - Real-time WebSocket communications
325
+
326
+ All endpoints include detailed documentation, examples, and proper error handling.
327
+ """
328
+ router = APIRouter(
329
+ dependencies=[Depends(get_authentication_dependency(settings))],
330
+ responses={
331
+ 400: {"description": "Bad Request", "model": BadRequestResponse},
332
+ 401: {"description": "Unauthorized", "model": UnauthenticatedResponse},
333
+ 404: {"description": "Not Found", "model": NotFoundResponse},
334
+ 422: {"description": "Validation Error", "model": ValidationErrorResponse},
335
+ 500: {"description": "Internal Server Error", "model": InternalServerErrorResponse},
336
+ },
337
+ )
338
+
339
+ # -- Main Routes ---
340
+
341
+ @router.get(
342
+ "/health",
343
+ tags=["Core"],
344
+ operation_id="health_check",
345
+ summary="Health Check",
346
+ description="Check the health status of the AgentOS API. Returns a simple status indicator.",
347
+ response_model=HealthResponse,
348
+ responses={
349
+ 200: {
350
+ "description": "API is healthy and operational",
351
+ "content": {"application/json": {"example": {"status": "ok"}}},
352
+ }
353
+ },
354
+ )
355
+ async def health_check() -> HealthResponse:
356
+ return HealthResponse(status="ok")
357
+
358
+ @router.get(
359
+ "/config",
360
+ response_model=ConfigResponse,
361
+ response_model_exclude_none=True,
362
+ tags=["Core"],
363
+ operation_id="get_config",
364
+ summary="Get OS Configuration",
365
+ description=(
366
+ "Retrieve the complete configuration of the AgentOS instance, including:\n\n"
367
+ "- Available models and databases\n"
368
+ "- Registered agents, teams, and workflows\n"
369
+ "- Chat, session, memory, knowledge, and evaluation configurations\n"
370
+ "- Available interfaces and their routes"
371
+ ),
372
+ responses={
373
+ 200: {
374
+ "description": "OS configuration retrieved successfully",
375
+ "content": {
376
+ "application/json": {
377
+ "example": {
378
+ "os_id": "demo",
379
+ "description": "Example AgentOS configuration",
380
+ "available_models": [],
381
+ "databases": ["9c884dc4-9066-448c-9074-ef49ec7eb73c"],
382
+ "session": {
383
+ "dbs": [
384
+ {
385
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
386
+ "domain_config": {"display_name": "Sessions"},
387
+ }
388
+ ]
389
+ },
390
+ "metrics": {
391
+ "dbs": [
392
+ {
393
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
394
+ "domain_config": {"display_name": "Metrics"},
395
+ }
396
+ ]
397
+ },
398
+ "memory": {
399
+ "dbs": [
400
+ {
401
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
402
+ "domain_config": {"display_name": "Memory"},
403
+ }
404
+ ]
405
+ },
406
+ "knowledge": {
407
+ "dbs": [
408
+ {
409
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
410
+ "domain_config": {"display_name": "Knowledge"},
411
+ }
412
+ ]
413
+ },
414
+ "evals": {
415
+ "dbs": [
416
+ {
417
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
418
+ "domain_config": {"display_name": "Evals"},
419
+ }
420
+ ]
421
+ },
422
+ "agents": [
423
+ {
424
+ "id": "main-agent",
425
+ "name": "Main Agent",
426
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
427
+ }
428
+ ],
429
+ "teams": [],
430
+ "workflows": [],
431
+ "interfaces": [],
432
+ }
433
+ }
434
+ },
435
+ }
436
+ },
437
+ )
438
+ async def config() -> ConfigResponse:
439
+ return ConfigResponse(
440
+ os_id=os.os_id or "Unnamed OS",
441
+ description=os.description,
442
+ available_models=os.config.available_models if os.config else [],
443
+ databases=[db.id for db in os.dbs.values()],
444
+ chat=os.config.chat if os.config else None,
445
+ session=os._get_session_config(),
446
+ memory=os._get_memory_config(),
447
+ knowledge=os._get_knowledge_config(),
448
+ evals=os._get_evals_config(),
449
+ metrics=os._get_metrics_config(),
450
+ agents=[AgentSummaryResponse.from_agent(agent) for agent in os.agents] if os.agents else [],
451
+ teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
452
+ workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],
453
+ interfaces=[
454
+ InterfaceResponse(type=interface.type, version=interface.version, route=interface.router_prefix)
455
+ for interface in os.interfaces
456
+ ],
457
+ )
458
+
459
+ @router.get(
460
+ "/models",
461
+ response_model=List[Model],
462
+ response_model_exclude_none=True,
463
+ tags=["Core"],
464
+ operation_id="get_models",
465
+ summary="Get Available Models",
466
+ description=(
467
+ "Retrieve a list of all unique models currently used by agents and teams in this OS instance. "
468
+ "This includes the model ID and provider information for each model."
469
+ ),
470
+ responses={
471
+ 200: {
472
+ "description": "List of models retrieved successfully",
473
+ "content": {
474
+ "application/json": {
475
+ "example": [
476
+ {"id": "gpt-4", "provider": "openai"},
477
+ {"id": "claude-3-sonnet", "provider": "anthropic"},
478
+ ]
479
+ }
480
+ },
481
+ }
482
+ },
483
+ )
484
+ async def get_models() -> List[Model]:
485
+ """Return the list of all models used by agents and teams in the contextual OS"""
486
+ all_components: List[Union[Agent, Team]] = []
487
+ if os.agents:
488
+ all_components.extend(os.agents)
489
+ if os.teams:
490
+ all_components.extend(os.teams)
491
+
492
+ unique_models = {}
493
+ for item in all_components:
494
+ model = cast(Model, item.model)
495
+ if model.id is not None and model.provider is not None:
496
+ key = (model.id, model.provider)
497
+ if key not in unique_models:
498
+ unique_models[key] = Model(id=model.id, provider=model.provider)
499
+
500
+ return list(unique_models.values())
501
+
502
+ # -- Agent routes ---
503
+
504
+ @router.post(
505
+ "/agents/{agent_id}/runs",
506
+ tags=["Agents"],
507
+ operation_id="create_agent_run",
508
+ response_model_exclude_none=True,
509
+ summary="Create Agent Run",
510
+ description=(
511
+ "Execute an agent with a message and optional media files. Supports both streaming and non-streaming responses.\n\n"
512
+ "**Features:**\n"
513
+ "- Text message input with optional session management\n"
514
+ "- Multi-media support: images (PNG, JPEG, WebP), audio (WAV, MP3), video (MP4, WebM, etc.)\n"
515
+ "- Document processing: PDF, CSV, DOCX, TXT, JSON\n"
516
+ "- Real-time streaming responses with Server-Sent Events (SSE)\n"
517
+ "- User and session context preservation\n\n"
518
+ "**Streaming Response:**\n"
519
+ "When `stream=true`, returns SSE events with `event` and `data` fields."
520
+ ),
521
+ responses={
522
+ 200: {
523
+ "description": "Agent run executed successfully",
524
+ "content": {
525
+ "text/event-stream": {
526
+ "examples": {
527
+ "event_strea": {
528
+ "summary": "Example event stream response",
529
+ "value": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n',
530
+ }
531
+ }
532
+ },
533
+ },
534
+ },
535
+ 400: {"description": "Invalid request or unsupported file type", "model": BadRequestResponse},
536
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
537
+ },
538
+ )
539
+ async def create_agent_run(
540
+ agent_id: str,
541
+ message: str = Form(...),
542
+ stream: bool = Form(False),
543
+ session_id: Optional[str] = Form(None),
544
+ user_id: Optional[str] = Form(None),
545
+ files: Optional[List[UploadFile]] = File(None),
546
+ ):
547
+ agent = get_agent_by_id(agent_id, os.agents)
548
+ if agent is None:
549
+ raise HTTPException(status_code=404, detail="Agent not found")
550
+
551
+ if session_id is None or session_id == "":
552
+ log_debug("Creating new session")
553
+ session_id = str(uuid4())
554
+
555
+ base64_images: List[Image] = []
556
+ base64_audios: List[Audio] = []
557
+ base64_videos: List[Video] = []
558
+ input_files: List[FileMedia] = []
559
+
560
+ if files:
561
+ for file in files:
562
+ if file.content_type in ["image/png", "image/jpeg", "image/jpg", "image/webp"]:
563
+ try:
564
+ base64_image = process_image(file)
565
+ base64_images.append(base64_image)
566
+ except Exception as e:
567
+ log_error(f"Error processing image {file.filename}: {e}")
568
+ continue
569
+ elif file.content_type in ["audio/wav", "audio/mp3", "audio/mpeg"]:
570
+ try:
571
+ base64_audio = process_audio(file)
572
+ base64_audios.append(base64_audio)
573
+ except Exception as e:
574
+ log_error(f"Error processing audio {file.filename}: {e}")
575
+ continue
576
+ elif file.content_type in [
577
+ "video/x-flv",
578
+ "video/quicktime",
579
+ "video/mpeg",
580
+ "video/mpegs",
581
+ "video/mpgs",
582
+ "video/mpg",
583
+ "video/mpg",
584
+ "video/mp4",
585
+ "video/webm",
586
+ "video/wmv",
587
+ "video/3gpp",
588
+ ]:
589
+ try:
590
+ base64_video = process_video(file)
591
+ base64_videos.append(base64_video)
592
+ except Exception as e:
593
+ log_error(f"Error processing video {file.filename}: {e}")
594
+ continue
595
+ elif file.content_type in [
596
+ "application/pdf",
597
+ "text/csv",
598
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
599
+ "text/plain",
600
+ "application/json",
601
+ ]:
602
+ # Process document files
603
+ try:
604
+ file_content = await file.read()
605
+ input_files.append(FileMedia(content=file_content))
606
+ except Exception as e:
607
+ log_error(f"Error processing file {file.filename}: {e}")
608
+ continue
609
+ else:
610
+ raise HTTPException(status_code=400, detail="Unsupported file type")
611
+
612
+ if stream:
613
+ return StreamingResponse(
614
+ agent_response_streamer(
615
+ agent,
616
+ message,
617
+ session_id=session_id,
618
+ user_id=user_id,
619
+ images=base64_images if base64_images else None,
620
+ audio=base64_audios if base64_audios else None,
621
+ videos=base64_videos if base64_videos else None,
622
+ files=input_files if input_files else None,
623
+ ),
624
+ media_type="text/event-stream",
625
+ )
626
+ else:
627
+ run_response = cast(
628
+ RunOutput,
629
+ await agent.arun(
630
+ input=message,
631
+ session_id=session_id,
632
+ user_id=user_id,
633
+ images=base64_images if base64_images else None,
634
+ audio=base64_audios if base64_audios else None,
635
+ videos=base64_videos if base64_videos else None,
636
+ files=input_files if input_files else None,
637
+ stream=False,
638
+ ),
639
+ )
640
+ return run_response.to_dict()
641
+
642
+ @router.post(
643
+ "/agents/{agent_id}/runs/{run_id}/cancel",
644
+ tags=["Agents"],
645
+ operation_id="cancel_agent_run",
646
+ response_model_exclude_none=True,
647
+ summary="Cancel Agent Run",
648
+ description=(
649
+ "Cancel a currently executing agent run. This will attempt to stop the agent's execution gracefully.\n\n"
650
+ "**Note:** Cancellation may not be immediate for all operations."
651
+ ),
652
+ responses={
653
+ 200: {},
654
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
655
+ 500: {"description": "Failed to cancel run", "model": InternalServerErrorResponse},
656
+ },
657
+ )
658
+ async def cancel_agent_run(
659
+ agent_id: str,
660
+ run_id: str,
661
+ ):
662
+ agent = get_agent_by_id(agent_id, os.agents)
663
+ if agent is None:
664
+ raise HTTPException(status_code=404, detail="Agent not found")
665
+
666
+ if not agent.cancel_run(run_id=run_id):
667
+ raise HTTPException(status_code=500, detail="Failed to cancel run")
668
+
669
+ return JSONResponse(content={}, status_code=200)
670
+
671
+ @router.post(
672
+ "/agents/{agent_id}/runs/{run_id}/continue",
673
+ tags=["Agents"],
674
+ operation_id="continue_agent_run",
675
+ response_model_exclude_none=True,
676
+ summary="Continue Agent Run",
677
+ description=(
678
+ "Continue a paused or incomplete agent run with updated tool results.\n\n"
679
+ "**Use Cases:**\n"
680
+ "- Resume execution after tool approval/rejection\n"
681
+ "- Provide manual tool execution results\n\n"
682
+ "**Tools Parameter:**\n"
683
+ "JSON string containing array of tool execution objects with results."
684
+ ),
685
+ responses={
686
+ 200: {
687
+ "description": "Agent run continued successfully",
688
+ "content": {
689
+ "text/event-stream": {
690
+ "example": 'event: RunContent\ndata: {"created_at": 1757348314, "run_id": "123..."}\n\n'
691
+ },
692
+ },
693
+ },
694
+ 400: {"description": "Invalid JSON in tools field or invalid tool structure", "model": BadRequestResponse},
695
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
696
+ },
697
+ )
698
+ async def continue_agent_run(
699
+ agent_id: str,
700
+ run_id: str,
701
+ tools: str = Form(...), # JSON string of tools
702
+ session_id: Optional[str] = Form(None),
703
+ user_id: Optional[str] = Form(None),
704
+ stream: bool = Form(True),
705
+ ):
706
+ # Parse the JSON string manually
707
+ try:
708
+ tools_data = json.loads(tools) if tools else None
709
+ except json.JSONDecodeError:
710
+ raise HTTPException(status_code=400, detail="Invalid JSON in tools field")
711
+
712
+ agent = get_agent_by_id(agent_id, os.agents)
713
+ if agent is None:
714
+ raise HTTPException(status_code=404, detail="Agent not found")
715
+
716
+ if session_id is None or session_id == "":
717
+ log_warning(
718
+ "Continuing run without session_id. This might lead to unexpected behavior if session context is important."
719
+ )
720
+
721
+ # Convert tools dict to ToolExecution objects if provided
722
+ updated_tools = None
723
+ if tools_data:
724
+ try:
725
+ from agno.models.response import ToolExecution
726
+
727
+ updated_tools = [ToolExecution.from_dict(tool) for tool in tools_data]
728
+ except Exception as e:
729
+ raise HTTPException(status_code=400, detail=f"Invalid structure or content for tools: {str(e)}")
730
+
731
+ if stream:
732
+ return StreamingResponse(
733
+ agent_continue_response_streamer(
734
+ agent,
735
+ run_id=run_id, # run_id from path
736
+ updated_tools=updated_tools,
737
+ session_id=session_id,
738
+ user_id=user_id,
739
+ ),
740
+ media_type="text/event-stream",
741
+ )
742
+ else:
743
+ run_response_obj = cast(
744
+ RunOutput,
745
+ await agent.acontinue_run(
746
+ run_id=run_id, # run_id from path
747
+ updated_tools=updated_tools,
748
+ session_id=session_id,
749
+ user_id=user_id,
750
+ stream=False,
751
+ ),
752
+ )
753
+ return run_response_obj.to_dict()
754
+
755
+ @router.get(
756
+ "/agents",
757
+ response_model=List[AgentResponse],
758
+ response_model_exclude_none=True,
759
+ tags=["Agents"],
760
+ operation_id="get_agents",
761
+ summary="List All Agents",
762
+ description=(
763
+ "Retrieve a comprehensive list of all agents configured in this OS instance.\n\n"
764
+ "**Returns:**\n"
765
+ "- Agent metadata (ID, name, description)\n"
766
+ "- Model configuration and capabilities\n"
767
+ "- Available tools and their configurations\n"
768
+ "- Session, knowledge, memory, and reasoning settings\n"
769
+ "- Only meaningful (non-default) configurations are included"
770
+ ),
771
+ responses={
772
+ 200: {
773
+ "description": "List of agents retrieved successfully",
774
+ "content": {
775
+ "application/json": {
776
+ "example": [
777
+ {
778
+ "id": "main-agent",
779
+ "name": "Main Agent",
780
+ "db_id": "c6bf0644-feb8-4930-a305-380dae5ad6aa",
781
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
782
+ "tools": None,
783
+ "sessions": {"session_table": "agno_sessions"},
784
+ "knowledge": {"knowledge_table": "main_knowledge"},
785
+ "system_message": {"markdown": True, "add_datetime_to_context": True},
786
+ }
787
+ ]
788
+ }
789
+ },
790
+ }
791
+ },
792
+ )
793
+ async def get_agents() -> List[AgentResponse]:
794
+ """Return the list of all Agents present in the contextual OS"""
795
+ if os.agents is None:
796
+ return []
797
+
798
+ agents = []
799
+ for agent in os.agents:
800
+ agents.append(AgentResponse.from_agent(agent=agent))
801
+
802
+ return agents
803
+
804
+ @router.get(
805
+ "/agents/{agent_id}",
806
+ response_model=AgentResponse,
807
+ response_model_exclude_none=True,
808
+ tags=["Agents"],
809
+ operation_id="get_agent",
810
+ summary="Get Agent Details",
811
+ description=(
812
+ "Retrieve detailed configuration and capabilities of a specific agent.\n\n"
813
+ "**Returns comprehensive agent information including:**\n"
814
+ "- Model configuration and provider details\n"
815
+ "- Complete tool inventory and configurations\n"
816
+ "- Session management settings\n"
817
+ "- Knowledge base and memory configurations\n"
818
+ "- Reasoning capabilities and settings\n"
819
+ "- System prompts and response formatting options"
820
+ ),
821
+ responses={
822
+ 200: {
823
+ "description": "Agent details retrieved successfully",
824
+ "content": {
825
+ "application/json": {
826
+ "example": {
827
+ "id": "main-agent",
828
+ "name": "Main Agent",
829
+ "db_id": "9e064c70-6821-4840-a333-ce6230908a70",
830
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
831
+ "tools": None,
832
+ "sessions": {"session_table": "agno_sessions"},
833
+ "knowledge": {"knowledge_table": "main_knowledge"},
834
+ "system_message": {"markdown": True, "add_datetime_to_context": True},
835
+ }
836
+ }
837
+ },
838
+ },
839
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
840
+ },
841
+ )
842
+ async def get_agent(agent_id: str) -> AgentResponse:
843
+ agent = get_agent_by_id(agent_id, os.agents)
844
+ if agent is None:
845
+ raise HTTPException(status_code=404, detail="Agent not found")
846
+
847
+ return AgentResponse.from_agent(agent)
848
+
849
+ # -- Team routes ---
850
+
851
+ @router.post(
852
+ "/teams/{team_id}/runs",
853
+ tags=["Teams"],
854
+ operation_id="create_team_run",
855
+ response_model_exclude_none=True,
856
+ summary="Create Team Run",
857
+ description=(
858
+ "Execute a team collaboration with multiple agents working together on a task.\n\n"
859
+ "**Features:**\n"
860
+ "- Text message input with optional session management\n"
861
+ "- Multi-media support: images (PNG, JPEG, WebP), audio (WAV, MP3), video (MP4, WebM, etc.)\n"
862
+ "- Document processing: PDF, CSV, DOCX, TXT, JSON\n"
863
+ "- Real-time streaming responses with Server-Sent Events (SSE)\n"
864
+ "- User and session context preservation\n\n"
865
+ "**Streaming Response:**\n"
866
+ "When `stream=true`, returns SSE events with `event` and `data` fields."
867
+ ),
868
+ responses={
869
+ 200: {
870
+ "description": "Team run executed successfully",
871
+ "content": {
872
+ "text/event-stream": {
873
+ "example": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n'
874
+ },
875
+ },
876
+ },
877
+ 400: {"description": "Invalid request or unsupported file type", "model": BadRequestResponse},
878
+ 404: {"description": "Team not found", "model": NotFoundResponse},
879
+ },
880
+ )
881
+ async def create_team_run(
882
+ team_id: str,
883
+ message: str = Form(...),
884
+ stream: bool = Form(True),
885
+ monitor: bool = Form(True),
886
+ session_id: Optional[str] = Form(None),
887
+ user_id: Optional[str] = Form(None),
888
+ files: Optional[List[UploadFile]] = File(None),
889
+ ):
890
+ logger.debug(f"Creating team run: {message} {session_id} {monitor} {user_id} {team_id} {files}")
891
+ team = get_team_by_id(team_id, os.teams)
892
+ if team is None:
893
+ raise HTTPException(status_code=404, detail="Team not found")
894
+
895
+ if session_id is not None and session_id != "":
896
+ logger.debug(f"Continuing session: {session_id}")
897
+ else:
898
+ logger.debug("Creating new session")
899
+ session_id = str(uuid4())
900
+
901
+ base64_images: List[Image] = []
902
+ base64_audios: List[Audio] = []
903
+ base64_videos: List[Video] = []
904
+ document_files: List[FileMedia] = []
905
+
906
+ if files:
907
+ for file in files:
908
+ if file.content_type in ["image/png", "image/jpeg", "image/jpg", "image/webp"]:
909
+ try:
910
+ base64_image = process_image(file)
911
+ base64_images.append(base64_image)
912
+ except Exception as e:
913
+ logger.error(f"Error processing image {file.filename}: {e}")
914
+ continue
915
+ elif file.content_type in ["audio/wav", "audio/mp3", "audio/mpeg"]:
916
+ try:
917
+ base64_audio = process_audio(file)
918
+ base64_audios.append(base64_audio)
919
+ except Exception as e:
920
+ logger.error(f"Error processing audio {file.filename}: {e}")
921
+ continue
922
+ elif file.content_type in [
923
+ "video/x-flv",
924
+ "video/quicktime",
925
+ "video/mpeg",
926
+ "video/mpegs",
927
+ "video/mpgs",
928
+ "video/mpg",
929
+ "video/mpg",
930
+ "video/mp4",
931
+ "video/webm",
932
+ "video/wmv",
933
+ "video/3gpp",
934
+ ]:
935
+ try:
936
+ base64_video = process_video(file)
937
+ base64_videos.append(base64_video)
938
+ except Exception as e:
939
+ logger.error(f"Error processing video {file.filename}: {e}")
940
+ continue
941
+ elif file.content_type in [
942
+ "application/pdf",
943
+ "text/csv",
944
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
945
+ "text/plain",
946
+ "application/json",
947
+ ]:
948
+ document_file = process_document(file)
949
+ if document_file is not None:
950
+ document_files.append(document_file)
951
+ else:
952
+ raise HTTPException(status_code=400, detail="Unsupported file type")
953
+
954
+ if stream:
955
+ return StreamingResponse(
956
+ team_response_streamer(
957
+ team,
958
+ message,
959
+ session_id=session_id,
960
+ user_id=user_id,
961
+ images=base64_images if base64_images else None,
962
+ audio=base64_audios if base64_audios else None,
963
+ videos=base64_videos if base64_videos else None,
964
+ files=document_files if document_files else None,
965
+ ),
966
+ media_type="text/event-stream",
967
+ )
968
+ else:
969
+ run_response = await team.arun(
970
+ input=message,
971
+ session_id=session_id,
972
+ user_id=user_id,
973
+ images=base64_images if base64_images else None,
974
+ audio=base64_audios if base64_audios else None,
975
+ videos=base64_videos if base64_videos else None,
976
+ files=document_files if document_files else None,
977
+ stream=False,
978
+ )
979
+ return run_response.to_dict()
980
+
981
+ @router.post(
982
+ "/teams/{team_id}/runs/{run_id}/cancel",
983
+ tags=["Teams"],
984
+ operation_id="cancel_team_run",
985
+ response_model_exclude_none=True,
986
+ summary="Cancel Team Run",
987
+ description=(
988
+ "Cancel a currently executing team run. This will attempt to stop the team's execution gracefully.\n\n"
989
+ "**Note:** Cancellation may not be immediate for all operations."
990
+ ),
991
+ responses={
992
+ 200: {},
993
+ 404: {"description": "Team not found", "model": NotFoundResponse},
994
+ 500: {"description": "Failed to cancel team run", "model": InternalServerErrorResponse},
995
+ },
996
+ )
997
+ async def cancel_team_run(
998
+ team_id: str,
999
+ run_id: str,
1000
+ ):
1001
+ team = get_team_by_id(team_id, os.teams)
1002
+ if team is None:
1003
+ raise HTTPException(status_code=404, detail="Team not found")
1004
+
1005
+ if not team.cancel_run(run_id=run_id):
1006
+ raise HTTPException(status_code=500, detail="Failed to cancel run")
1007
+
1008
+ return JSONResponse(content={}, status_code=200)
1009
+
1010
+ @router.get(
1011
+ "/teams",
1012
+ response_model=List[TeamResponse],
1013
+ response_model_exclude_none=True,
1014
+ tags=["Teams"],
1015
+ operation_id="get_teams",
1016
+ summary="List All Teams",
1017
+ description=(
1018
+ "Retrieve a comprehensive list of all teams configured in this OS instance.\n\n"
1019
+ "**Returns team information including:**\n"
1020
+ "- Team metadata (ID, name, description, execution mode)\n"
1021
+ "- Model configuration for team coordination\n"
1022
+ "- Team member roster with roles and capabilities\n"
1023
+ "- Knowledge sharing and memory configurations"
1024
+ ),
1025
+ responses={
1026
+ 200: {
1027
+ "description": "List of teams retrieved successfully",
1028
+ "content": {
1029
+ "application/json": {
1030
+ "example": [
1031
+ {
1032
+ "team_id": "basic-team",
1033
+ "name": "Basic Team",
1034
+ "mode": "coordinate",
1035
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1036
+ "tools": [
1037
+ {
1038
+ "name": "transfer_task_to_member",
1039
+ "description": "Use this function to transfer a task to the selected team member.\nYou must provide a clear and concise description of the task the member should achieve AND the expected output.",
1040
+ "parameters": {
1041
+ "type": "object",
1042
+ "properties": {
1043
+ "member_id": {
1044
+ "type": "string",
1045
+ "description": "(str) The ID of the member to transfer the task to. Use only the ID of the member, not the ID of the team followed by the ID of the member.",
1046
+ },
1047
+ "task_description": {
1048
+ "type": "string",
1049
+ "description": "(str) A clear and concise description of the task the member should achieve.",
1050
+ },
1051
+ "expected_output": {
1052
+ "type": "string",
1053
+ "description": "(str) The expected output from the member (optional).",
1054
+ },
1055
+ },
1056
+ "additionalProperties": False,
1057
+ "required": ["member_id", "task_description"],
1058
+ },
1059
+ }
1060
+ ],
1061
+ "members": [
1062
+ {
1063
+ "agent_id": "basic-agent",
1064
+ "name": "Basic Agent",
1065
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI gpt-4o"},
1066
+ "memory": {
1067
+ "app_name": "Memory",
1068
+ "app_url": None,
1069
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1070
+ },
1071
+ "session_table": "agno_sessions",
1072
+ "memory_table": "agno_memories",
1073
+ }
1074
+ ],
1075
+ "enable_agentic_context": False,
1076
+ "memory": {
1077
+ "app_name": "agno_memories",
1078
+ "app_url": "/memory/1",
1079
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1080
+ },
1081
+ "async_mode": False,
1082
+ "session_table": "agno_sessions",
1083
+ "memory_table": "agno_memories",
1084
+ }
1085
+ ]
1086
+ }
1087
+ },
1088
+ }
1089
+ },
1090
+ )
1091
+ async def get_teams() -> List[TeamResponse]:
1092
+ """Return the list of all Teams present in the contextual OS"""
1093
+ if os.teams is None:
1094
+ return []
1095
+
1096
+ teams = []
1097
+ for team in os.teams:
1098
+ teams.append(TeamResponse.from_team(team=team))
1099
+
1100
+ return teams
1101
+
1102
+ @router.get(
1103
+ "/teams/{team_id}",
1104
+ response_model=TeamResponse,
1105
+ response_model_exclude_none=True,
1106
+ tags=["Teams"],
1107
+ operation_id="get_team",
1108
+ summary="Get Team Details",
1109
+ description=("Retrieve detailed configuration and member information for a specific team."),
1110
+ responses={
1111
+ 200: {
1112
+ "description": "Team details retrieved successfully",
1113
+ "content": {
1114
+ "application/json": {
1115
+ "example": {
1116
+ "team_id": "basic-team",
1117
+ "name": "Basic Team",
1118
+ "description": None,
1119
+ "mode": "coordinate",
1120
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1121
+ "tools": [
1122
+ {
1123
+ "name": "transfer_task_to_member",
1124
+ "description": "Use this function to transfer a task to the selected team member.\nYou must provide a clear and concise description of the task the member should achieve AND the expected output.",
1125
+ "parameters": {
1126
+ "type": "object",
1127
+ "properties": {
1128
+ "member_id": {
1129
+ "type": "string",
1130
+ "description": "(str) The ID of the member to transfer the task to. Use only the ID of the member, not the ID of the team followed by the ID of the member.",
1131
+ },
1132
+ "task_description": {
1133
+ "type": "string",
1134
+ "description": "(str) A clear and concise description of the task the member should achieve.",
1135
+ },
1136
+ "expected_output": {
1137
+ "type": "string",
1138
+ "description": "(str) The expected output from the member (optional).",
1139
+ },
1140
+ },
1141
+ "additionalProperties": False,
1142
+ "required": ["member_id", "task_description"],
1143
+ },
1144
+ }
1145
+ ],
1146
+ "instructions": None,
1147
+ "members": [
1148
+ {
1149
+ "agent_id": "basic-agent",
1150
+ "name": "Basic Agent",
1151
+ "description": None,
1152
+ "instructions": None,
1153
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI gpt-4o"},
1154
+ "tools": None,
1155
+ "memory": {
1156
+ "app_name": "Memory",
1157
+ "app_url": None,
1158
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1159
+ },
1160
+ "knowledge": None,
1161
+ "session_table": "agno_sessions",
1162
+ "memory_table": "agno_memories",
1163
+ "knowledge_table": None,
1164
+ }
1165
+ ],
1166
+ "expected_output": None,
1167
+ "dependencies": None,
1168
+ "enable_agentic_context": False,
1169
+ "memory": {
1170
+ "app_name": "Memory",
1171
+ "app_url": None,
1172
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1173
+ },
1174
+ "knowledge": None,
1175
+ "async_mode": False,
1176
+ "session_table": "agno_sessions",
1177
+ "memory_table": "agno_memories",
1178
+ "knowledge_table": None,
1179
+ }
1180
+ }
1181
+ },
1182
+ },
1183
+ 404: {"description": "Team not found", "model": NotFoundResponse},
1184
+ },
1185
+ )
1186
+ async def get_team(team_id: str) -> TeamResponse:
1187
+ team = get_team_by_id(team_id, os.teams)
1188
+ if team is None:
1189
+ raise HTTPException(status_code=404, detail="Team not found")
1190
+
1191
+ return TeamResponse.from_team(team)
1192
+
1193
+ # -- Workflow routes ---
1194
+
1195
+ @router.websocket(
1196
+ "/workflows/ws",
1197
+ name="workflow_websocket",
1198
+ )
1199
+ async def workflow_websocket_endpoint(websocket: WebSocket):
1200
+ """WebSocket endpoint for receiving real-time workflow events"""
1201
+ await websocket_manager.connect(websocket)
1202
+
1203
+ try:
1204
+ while True:
1205
+ data = await websocket.receive_text()
1206
+ message = json.loads(data)
1207
+ action = message.get("action")
1208
+
1209
+ if action == "ping":
1210
+ await websocket.send_text(json.dumps({"event": "pong"}))
1211
+
1212
+ elif action == "start-workflow":
1213
+ # Handle workflow execution directly via WebSocket
1214
+ await handle_workflow_via_websocket(websocket, message, os)
1215
+ except Exception as e:
1216
+ if "1012" not in str(e):
1217
+ logger.error(f"WebSocket error: {e}")
1218
+ finally:
1219
+ # Clean up any run_ids associated with this websocket
1220
+ runs_to_remove = [run_id for run_id, ws in websocket_manager.active_connections.items() if ws == websocket]
1221
+ for run_id in runs_to_remove:
1222
+ await websocket_manager.disconnect_by_run_id(run_id)
1223
+
1224
+ @router.get(
1225
+ "/workflows",
1226
+ response_model=List[WorkflowSummaryResponse],
1227
+ response_model_exclude_none=True,
1228
+ tags=["Workflows"],
1229
+ operation_id="get_workflows",
1230
+ summary="List All Workflows",
1231
+ description=(
1232
+ "Retrieve a comprehensive list of all workflows configured in this OS instance.\n\n"
1233
+ "**Return Information:**\n"
1234
+ "- Workflow metadata (ID, name, description)\n"
1235
+ "- Input schema requirements\n"
1236
+ "- Step sequence and execution flow\n"
1237
+ "- Associated agents and teams"
1238
+ ),
1239
+ responses={
1240
+ 200: {
1241
+ "description": "List of workflows retrieved successfully",
1242
+ "content": {
1243
+ "application/json": {
1244
+ "example": [
1245
+ {
1246
+ "id": "content-creation-workflow",
1247
+ "name": "Content Creation Workflow",
1248
+ "description": "Automated content creation from blog posts to social media",
1249
+ "db_id": "123",
1250
+ }
1251
+ ]
1252
+ }
1253
+ },
1254
+ }
1255
+ },
1256
+ )
1257
+ async def get_workflows() -> List[WorkflowSummaryResponse]:
1258
+ if os.workflows is None:
1259
+ return []
1260
+
1261
+ return [WorkflowSummaryResponse.from_workflow(workflow) for workflow in os.workflows]
1262
+
1263
+ @router.get(
1264
+ "/workflows/{workflow_id}",
1265
+ response_model=WorkflowResponse,
1266
+ response_model_exclude_none=True,
1267
+ tags=["Workflows"],
1268
+ operation_id="get_workflow",
1269
+ summary="Get Workflow Details",
1270
+ description=("Retrieve detailed configuration and step information for a specific workflow."),
1271
+ responses={
1272
+ 200: {
1273
+ "description": "Workflow details retrieved successfully",
1274
+ "content": {
1275
+ "application/json": {
1276
+ "example": {
1277
+ "id": "content-creation-workflow",
1278
+ "name": "Content Creation Workflow",
1279
+ "description": "Automated content creation from blog posts to social media",
1280
+ "db_id": "123",
1281
+ }
1282
+ }
1283
+ },
1284
+ },
1285
+ 404: {"description": "Workflow not found", "model": NotFoundResponse},
1286
+ },
1287
+ )
1288
+ async def get_workflow(workflow_id: str) -> WorkflowResponse:
1289
+ workflow = get_workflow_by_id(workflow_id, os.workflows)
1290
+ if workflow is None:
1291
+ raise HTTPException(status_code=404, detail="Workflow not found")
1292
+
1293
+ return WorkflowResponse.from_workflow(workflow)
1294
+
1295
+ @router.post(
1296
+ "/workflows/{workflow_id}/runs",
1297
+ tags=["Workflows"],
1298
+ operation_id="create_workflow_run",
1299
+ response_model_exclude_none=True,
1300
+ summary="Execute Workflow",
1301
+ description=(
1302
+ "Execute a workflow with the provided input data. Workflows can run in streaming or batch mode.\n\n"
1303
+ "**Execution Modes:**\n"
1304
+ "- **Streaming (`stream=true`)**: Real-time step-by-step execution updates via SSE\n"
1305
+ "- **Non-Streaming (`stream=false`)**: Complete workflow execution with final result\n\n"
1306
+ "**Workflow Execution Process:**\n"
1307
+ "1. Input validation against workflow schema\n"
1308
+ "2. Sequential or parallel step execution based on workflow design\n"
1309
+ "3. Data flow between steps with transformation\n"
1310
+ "4. Error handling and automatic retries where configured\n"
1311
+ "5. Final result compilation and response\n\n"
1312
+ "**Session Management:**\n"
1313
+ "Workflows support session continuity for stateful execution across multiple runs."
1314
+ ),
1315
+ responses={
1316
+ 200: {
1317
+ "description": "Workflow executed successfully",
1318
+ "content": {
1319
+ "text/event-stream": {
1320
+ "example": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n'
1321
+ },
1322
+ },
1323
+ },
1324
+ 400: {"description": "Invalid input data or workflow configuration", "model": BadRequestResponse},
1325
+ 404: {"description": "Workflow not found", "model": NotFoundResponse},
1326
+ 500: {"description": "Workflow execution error", "model": InternalServerErrorResponse},
1327
+ },
1328
+ )
1329
+ async def create_workflow_run(
1330
+ workflow_id: str,
1331
+ message: str = Form(...),
1332
+ stream: bool = Form(True),
1333
+ session_id: Optional[str] = Form(None),
1334
+ user_id: Optional[str] = Form(None),
1335
+ **kwargs: Any,
1336
+ ):
1337
+ # Retrieve the workflow by ID
1338
+ workflow = get_workflow_by_id(workflow_id, os.workflows)
1339
+ if workflow is None:
1340
+ raise HTTPException(status_code=404, detail="Workflow not found")
1341
+
1342
+ if session_id:
1343
+ logger.debug(f"Continuing session: {session_id}")
1344
+ else:
1345
+ logger.debug("Creating new session")
1346
+ session_id = str(uuid4())
1347
+
1348
+ # Return based on stream parameter
1349
+ try:
1350
+ if stream:
1351
+ return StreamingResponse(
1352
+ workflow_response_streamer(
1353
+ workflow,
1354
+ input=message,
1355
+ session_id=session_id,
1356
+ user_id=user_id,
1357
+ **kwargs,
1358
+ ),
1359
+ media_type="text/event-stream",
1360
+ )
1361
+ else:
1362
+ run_response = await workflow.arun(
1363
+ input=message,
1364
+ session_id=session_id,
1365
+ user_id=user_id,
1366
+ stream=False,
1367
+ **kwargs,
1368
+ )
1369
+ return run_response.to_dict()
1370
+ except Exception as e:
1371
+ # Handle unexpected runtime errors
1372
+ raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
1373
+
1374
+ @router.post(
1375
+ "/workflows/{workflow_id}/runs/{run_id}/cancel",
1376
+ tags=["Workflows"],
1377
+ operation_id="cancel_workflow_run",
1378
+ summary="Cancel Workflow Run",
1379
+ description=(
1380
+ "Cancel a currently executing workflow run, stopping all active steps and cleanup.\n"
1381
+ "**Note:** Complex workflows with multiple parallel steps may take time to fully cancel."
1382
+ ),
1383
+ responses={
1384
+ 200: {},
1385
+ 404: {"description": "Workflow or run not found", "model": NotFoundResponse},
1386
+ 500: {"description": "Failed to cancel workflow run", "model": InternalServerErrorResponse},
1387
+ },
1388
+ )
1389
+ async def cancel_workflow_run(workflow_id: str, run_id: str):
1390
+ workflow = get_workflow_by_id(workflow_id, os.workflows)
1391
+
1392
+ if workflow is None:
1393
+ raise HTTPException(status_code=404, detail="Workflow not found")
1394
+
1395
+ if not workflow.cancel_run(run_id=run_id):
1396
+ raise HTTPException(status_code=500, detail="Failed to cancel run")
1397
+
1398
+ return JSONResponse(content={}, status_code=200)
1399
+
1400
+ return router