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,13 +1,16 @@
1
1
  import logging
2
- from typing import List, Optional, Union
2
+ import time
3
+ from typing import Any, List, Optional, Union, cast
4
+ from uuid import uuid4
3
5
 
4
6
  from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request
5
7
 
6
- from agno.db.base import BaseDb, SessionType
8
+ from agno.db.base import AsyncBaseDb, BaseDb, SessionType
7
9
  from agno.os.auth import get_authentication_dependency
8
10
  from agno.os.schema import (
9
11
  AgentSessionDetailSchema,
10
12
  BadRequestResponse,
13
+ CreateSessionRequest,
11
14
  DeleteSessionRequest,
12
15
  InternalServerErrorResponse,
13
16
  NotFoundResponse,
@@ -19,17 +22,21 @@ from agno.os.schema import (
19
22
  TeamRunSchema,
20
23
  TeamSessionDetailSchema,
21
24
  UnauthenticatedResponse,
25
+ UpdateSessionRequest,
22
26
  ValidationErrorResponse,
23
27
  WorkflowRunSchema,
24
28
  WorkflowSessionDetailSchema,
25
29
  )
26
30
  from agno.os.settings import AgnoAPISettings
27
31
  from agno.os.utils import get_db
32
+ from agno.session import AgentSession, TeamSession, WorkflowSession
28
33
 
29
34
  logger = logging.getLogger(__name__)
30
35
 
31
36
 
32
- def get_session_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAPISettings()) -> APIRouter:
37
+ def get_session_router(
38
+ dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings()
39
+ ) -> APIRouter:
33
40
  """Create session router with comprehensive OpenAPI documentation for session management endpoints."""
34
41
  session_router = APIRouter(
35
42
  dependencies=[Depends(get_authentication_dependency(settings))],
@@ -45,7 +52,7 @@ def get_session_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoA
45
52
  return attach_routes(router=session_router, dbs=dbs)
46
53
 
47
54
 
48
- def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
55
+ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
49
56
  @router.get(
50
57
  "/sessions",
51
58
  response_model=PaginatedResponse[SessionSchema],
@@ -82,6 +89,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
82
89
  },
83
90
  },
84
91
  400: {"description": "Invalid session type or filter parameters", "model": BadRequestResponse},
92
+ 404: {"description": "Not found", "model": NotFoundResponse},
85
93
  422: {"description": "Validation error in query parameters", "model": ValidationErrorResponse},
86
94
  },
87
95
  )
@@ -102,23 +110,41 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
102
110
  sort_by: Optional[str] = Query(default="created_at", description="Field to sort sessions by"),
103
111
  sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
104
112
  db_id: Optional[str] = Query(default=None, description="Database ID to query sessions from"),
113
+ table: Optional[str] = Query(default=None, description="The database table to use"),
105
114
  ) -> PaginatedResponse[SessionSchema]:
106
- db = get_db(dbs, db_id)
115
+ try:
116
+ db = await get_db(dbs, db_id, table)
117
+ except Exception as e:
118
+ raise HTTPException(status_code=404, detail=f"{e}")
107
119
 
108
120
  if hasattr(request.state, "user_id"):
109
121
  user_id = request.state.user_id
110
122
 
111
- sessions, total_count = db.get_sessions(
112
- session_type=session_type,
113
- component_id=component_id,
114
- user_id=user_id,
115
- session_name=session_name,
116
- limit=limit,
117
- page=page,
118
- sort_by=sort_by,
119
- sort_order=sort_order,
120
- deserialize=False,
121
- )
123
+ if isinstance(db, AsyncBaseDb):
124
+ db = cast(AsyncBaseDb, db)
125
+ sessions, total_count = await db.get_sessions(
126
+ session_type=session_type,
127
+ component_id=component_id,
128
+ user_id=user_id,
129
+ session_name=session_name,
130
+ limit=limit,
131
+ page=page,
132
+ sort_by=sort_by,
133
+ sort_order=sort_order,
134
+ deserialize=False,
135
+ )
136
+ else:
137
+ sessions, total_count = db.get_sessions( # type: ignore
138
+ session_type=session_type,
139
+ component_id=component_id,
140
+ user_id=user_id,
141
+ session_name=session_name,
142
+ limit=limit,
143
+ page=page,
144
+ sort_by=sort_by,
145
+ sort_order=sort_order,
146
+ deserialize=False,
147
+ )
122
148
 
123
149
  return PaginatedResponse(
124
150
  data=[SessionSchema.from_dict(session) for session in sessions], # type: ignore
@@ -130,6 +156,133 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
130
156
  ),
131
157
  )
132
158
 
159
+ @router.post(
160
+ "/sessions",
161
+ response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
162
+ status_code=201,
163
+ operation_id="create_session",
164
+ summary="Create New Session",
165
+ description=(
166
+ "Create a new empty session with optional configuration. "
167
+ "Useful for pre-creating sessions with specific session_state, metadata, or other properties "
168
+ "before running any agent/team/workflow interactions. "
169
+ "The session can later be used by providing its session_id in run requests."
170
+ ),
171
+ responses={
172
+ 201: {
173
+ "description": "Session created successfully",
174
+ "content": {
175
+ "application/json": {
176
+ "examples": {
177
+ "agent_session_example": {
178
+ "summary": "Example created agent session",
179
+ "value": {
180
+ "user_id": "user-123",
181
+ "agent_session_id": "new-session-id",
182
+ "session_id": "new-session-id",
183
+ "session_name": "New Session",
184
+ "session_state": {"key": "value"},
185
+ "metadata": {"key": "value"},
186
+ "agent_id": "agent-1",
187
+ "created_at": "2025-10-21T12:00:00Z",
188
+ "updated_at": "2025-10-21T12:00:00Z",
189
+ },
190
+ }
191
+ }
192
+ }
193
+ },
194
+ },
195
+ 400: {"description": "Invalid request parameters", "model": BadRequestResponse},
196
+ 422: {"description": "Validation error", "model": ValidationErrorResponse},
197
+ 500: {"description": "Failed to create session", "model": InternalServerErrorResponse},
198
+ },
199
+ )
200
+ async def create_session(
201
+ request: Request,
202
+ session_type: SessionType = Query(
203
+ default=SessionType.AGENT, alias="type", description="Type of session to create (agent, team, or workflow)"
204
+ ),
205
+ create_session_request: CreateSessionRequest = Body(
206
+ default=CreateSessionRequest(), description="Session configuration data"
207
+ ),
208
+ db_id: Optional[str] = Query(default=None, description="Database ID to create session in"),
209
+ ) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
210
+ db = await get_db(dbs, db_id)
211
+
212
+ # Get user_id from request state if available (from auth middleware)
213
+ user_id = create_session_request.user_id
214
+ if hasattr(request.state, "user_id"):
215
+ user_id = request.state.user_id
216
+
217
+ # Generate session_id if not provided
218
+ session_id = create_session_request.session_id or str(uuid4())
219
+
220
+ # Prepare session_data with session_state and session_name
221
+ session_data: dict[str, Any] = {}
222
+ if create_session_request.session_state is not None:
223
+ session_data["session_state"] = create_session_request.session_state
224
+ if create_session_request.session_name is not None:
225
+ session_data["session_name"] = create_session_request.session_name
226
+
227
+ current_time = int(time.time())
228
+
229
+ # Create the appropriate session type
230
+ session: Union[AgentSession, TeamSession, WorkflowSession]
231
+ if session_type == SessionType.AGENT:
232
+ session = AgentSession(
233
+ session_id=session_id,
234
+ agent_id=create_session_request.agent_id,
235
+ user_id=user_id,
236
+ session_data=session_data if session_data else None,
237
+ metadata=create_session_request.metadata,
238
+ created_at=current_time,
239
+ updated_at=current_time,
240
+ )
241
+ elif session_type == SessionType.TEAM:
242
+ session = TeamSession(
243
+ session_id=session_id,
244
+ team_id=create_session_request.team_id,
245
+ user_id=user_id,
246
+ session_data=session_data if session_data else None,
247
+ metadata=create_session_request.metadata,
248
+ created_at=current_time,
249
+ updated_at=current_time,
250
+ )
251
+ elif session_type == SessionType.WORKFLOW:
252
+ session = WorkflowSession(
253
+ session_id=session_id,
254
+ workflow_id=create_session_request.workflow_id,
255
+ user_id=user_id,
256
+ session_data=session_data if session_data else None,
257
+ metadata=create_session_request.metadata,
258
+ created_at=current_time,
259
+ updated_at=current_time,
260
+ )
261
+ else:
262
+ raise HTTPException(status_code=400, detail=f"Invalid session type: {session_type}")
263
+
264
+ # Upsert the session to the database
265
+ try:
266
+ if isinstance(db, AsyncBaseDb):
267
+ db = cast(AsyncBaseDb, db)
268
+ created_session = await db.upsert_session(session, deserialize=True)
269
+ else:
270
+ created_session = db.upsert_session(session, deserialize=True)
271
+
272
+ if not created_session:
273
+ raise HTTPException(status_code=500, detail="Failed to create session")
274
+
275
+ # Return appropriate schema based on session type
276
+ if session_type == SessionType.AGENT:
277
+ return AgentSessionDetailSchema.from_session(created_session) # type: ignore
278
+ elif session_type == SessionType.TEAM:
279
+ return TeamSessionDetailSchema.from_session(created_session) # type: ignore
280
+ else:
281
+ return WorkflowSessionDetailSchema.from_session(created_session) # type: ignore
282
+ except Exception as e:
283
+ logger.error(f"Error creating session: {e}")
284
+ raise HTTPException(status_code=500, detail=f"Failed to create session: {str(e)}")
285
+
133
286
  @router.get(
134
287
  "/sessions/{session_id}",
135
288
  response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
@@ -225,13 +378,18 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
225
378
  ),
226
379
  user_id: Optional[str] = Query(default=None, description="User ID to query session from"),
227
380
  db_id: Optional[str] = Query(default=None, description="Database ID to query session from"),
381
+ table: Optional[str] = Query(default=None, description="Table to query session from"),
228
382
  ) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
229
- db = get_db(dbs, db_id)
383
+ db = await get_db(dbs, db_id, table)
230
384
 
231
385
  if hasattr(request.state, "user_id"):
232
386
  user_id = request.state.user_id
233
387
 
234
- session = db.get_session(session_id=session_id, session_type=session_type, user_id=user_id)
388
+ if isinstance(db, AsyncBaseDb):
389
+ db = cast(AsyncBaseDb, db)
390
+ session = await db.get_session(session_id=session_id, session_type=session_type, user_id=user_id)
391
+ else:
392
+ session = db.get_session(session_id=session_id, session_type=session_type, user_id=user_id)
235
393
  if not session:
236
394
  raise HTTPException(
237
395
  status_code=404, detail=f"{session_type.value.title()} Session with id '{session_id}' not found"
@@ -251,8 +409,9 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
251
409
  operation_id="get_session_runs",
252
410
  summary="Get Session Runs",
253
411
  description=(
254
- "Retrieve all runs (executions) for a specific session. Runs represent individual "
255
- "interactions or executions within a session. Response schema varies based on session type."
412
+ "Retrieve all runs (executions) for a specific session with optional timestamp filtering. "
413
+ "Runs represent individual interactions or executions within a session. "
414
+ "Response schema varies based on session type."
256
415
  ),
257
416
  responses={
258
417
  200: {
@@ -366,27 +525,68 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
366
525
  default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
367
526
  ),
368
527
  user_id: Optional[str] = Query(default=None, description="User ID to query runs from"),
528
+ created_after: Optional[int] = Query(
529
+ default=None,
530
+ description="Filter runs created after this Unix timestamp (epoch time in seconds)",
531
+ ),
532
+ created_before: Optional[int] = Query(
533
+ default=None,
534
+ description="Filter runs created before this Unix timestamp (epoch time in seconds)",
535
+ ),
369
536
  db_id: Optional[str] = Query(default=None, description="Database ID to query runs from"),
537
+ table: Optional[str] = Query(default=None, description="Table to query runs from"),
370
538
  ) -> List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]]:
371
- db = get_db(dbs, db_id)
539
+ db = await get_db(dbs, db_id, table)
372
540
 
373
541
  if hasattr(request.state, "user_id"):
374
542
  user_id = request.state.user_id
375
543
 
376
- session = db.get_session(session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False)
544
+ # Use timestamp filters directly (already in epoch format)
545
+ start_timestamp = created_after
546
+ end_timestamp = created_before
547
+
548
+ if isinstance(db, AsyncBaseDb):
549
+ db = cast(AsyncBaseDb, db)
550
+ session = await db.get_session(
551
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
552
+ )
553
+ else:
554
+ session = db.get_session(
555
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
556
+ )
557
+
377
558
  if not session:
378
559
  raise HTTPException(status_code=404, detail=f"Session with ID {session_id} not found")
379
560
 
380
561
  runs = session.get("runs") # type: ignore
381
562
  if not runs:
382
- raise HTTPException(status_code=404, detail=f"Session with ID {session_id} has no runs")
563
+ return []
564
+
565
+ # Filter runs by timestamp if specified
566
+ # TODO: Move this filtering into the DB layer
567
+ filtered_runs = []
568
+ for run in runs:
569
+ if start_timestamp or end_timestamp:
570
+ run_created_at = run.get("created_at")
571
+ if run_created_at:
572
+ # created_at is stored as epoch int
573
+ if start_timestamp and run_created_at < start_timestamp:
574
+ continue
575
+ if end_timestamp and run_created_at > end_timestamp:
576
+ continue
577
+
578
+ filtered_runs.append(run)
579
+
580
+ if not filtered_runs:
581
+ return []
582
+
583
+ run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = []
383
584
 
384
585
  if session_type == SessionType.AGENT:
385
- return [RunSchema.from_dict(run) for run in runs]
586
+ return [RunSchema.from_dict(run) for run in filtered_runs]
386
587
 
387
588
  elif session_type == SessionType.TEAM:
388
- run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = []
389
- for run in runs:
589
+ for run in filtered_runs:
390
590
  if run.get("agent_id") is not None:
391
591
  run_responses.append(RunSchema.from_dict(run))
392
592
  elif run.get("team_id") is not None:
@@ -394,8 +594,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
394
594
  return run_responses
395
595
 
396
596
  elif session_type == SessionType.WORKFLOW:
397
- run_responses: List[Union[RunSchema, TeamRunSchema, WorkflowRunSchema]] = [] # type: ignore
398
- for run in runs:
597
+ for run in filtered_runs:
399
598
  if run.get("workflow_id") is not None:
400
599
  run_responses.append(WorkflowRunSchema.from_dict(run))
401
600
  elif run.get("team_id") is not None:
@@ -406,6 +605,93 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
406
605
  else:
407
606
  raise HTTPException(status_code=400, detail=f"Invalid session type: {session_type}")
408
607
 
608
+ @router.get(
609
+ "/sessions/{session_id}/runs/{run_id}",
610
+ response_model=Union[RunSchema, TeamRunSchema, WorkflowRunSchema],
611
+ status_code=200,
612
+ operation_id="get_session_run",
613
+ summary="Get Run by ID",
614
+ description=(
615
+ "Retrieve a specific run by its ID from a session. Response schema varies based on the "
616
+ "run type (agent run, team run, or workflow run)."
617
+ ),
618
+ responses={
619
+ 200: {
620
+ "description": "Run retrieved successfully",
621
+ "content": {
622
+ "application/json": {
623
+ "examples": {
624
+ "agent_run": {
625
+ "summary": "Example agent run",
626
+ "value": {
627
+ "run_id": "fcdf50f0-7c32-4593-b2ef-68a558774340",
628
+ "parent_run_id": "80056af0-c7a5-4d69-b6a2-c3eba9f040e0",
629
+ "agent_id": "basic-agent",
630
+ "user_id": "user_123",
631
+ "run_input": "Which tools do you have access to?",
632
+ "content": "I don't have access to external tools.",
633
+ "created_at": 1728499200,
634
+ },
635
+ }
636
+ }
637
+ }
638
+ },
639
+ },
640
+ 404: {"description": "Session or run not found", "model": NotFoundResponse},
641
+ 422: {"description": "Invalid session type", "model": ValidationErrorResponse},
642
+ },
643
+ )
644
+ async def get_session_run(
645
+ request: Request,
646
+ session_id: str = Path(description="Session ID to get run from"),
647
+ run_id: str = Path(description="Run ID to retrieve"),
648
+ session_type: SessionType = Query(
649
+ default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
650
+ ),
651
+ user_id: Optional[str] = Query(default=None, description="User ID to query run from"),
652
+ db_id: Optional[str] = Query(default=None, description="Database ID to query run from"),
653
+ ) -> Union[RunSchema, TeamRunSchema, WorkflowRunSchema]:
654
+ db = await get_db(dbs, db_id)
655
+
656
+ if hasattr(request.state, "user_id"):
657
+ user_id = request.state.user_id
658
+
659
+ if isinstance(db, AsyncBaseDb):
660
+ db = cast(AsyncBaseDb, db)
661
+ session = await db.get_session(
662
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
663
+ )
664
+ else:
665
+ session = db.get_session(
666
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=False
667
+ )
668
+
669
+ if not session:
670
+ raise HTTPException(status_code=404, detail=f"Session with ID {session_id} not found")
671
+
672
+ runs = session.get("runs") # type: ignore
673
+ if not runs:
674
+ raise HTTPException(status_code=404, detail=f"Session with ID {session_id} has no runs")
675
+
676
+ # Find the specific run
677
+ # TODO: Move this filtering into the DB layer
678
+ target_run = None
679
+ for run in runs:
680
+ if run.get("run_id") == run_id:
681
+ target_run = run
682
+ break
683
+
684
+ if not target_run:
685
+ raise HTTPException(status_code=404, detail=f"Run with ID {run_id} not found in session {session_id}")
686
+
687
+ # Return the appropriate schema based on run type
688
+ if target_run.get("workflow_id") is not None:
689
+ return WorkflowRunSchema.from_dict(target_run)
690
+ elif target_run.get("team_id") is not None:
691
+ return TeamRunSchema.from_dict(target_run)
692
+ else:
693
+ return RunSchema.from_dict(target_run)
694
+
409
695
  @router.delete(
410
696
  "/sessions/{session_id}",
411
697
  status_code=204,
@@ -423,9 +709,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
423
709
  async def delete_session(
424
710
  session_id: str = Path(description="Session ID to delete"),
425
711
  db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
712
+ table: Optional[str] = Query(default=None, description="Table to use for deletion"),
426
713
  ) -> None:
427
- db = get_db(dbs, db_id)
428
- db.delete_session(session_id=session_id)
714
+ db = await get_db(dbs, db_id, table)
715
+ if isinstance(db, AsyncBaseDb):
716
+ db = cast(AsyncBaseDb, db)
717
+ await db.delete_session(session_id=session_id)
718
+ else:
719
+ db.delete_session(session_id=session_id)
429
720
 
430
721
  @router.delete(
431
722
  "/sessions",
@@ -451,12 +742,17 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
451
742
  default=SessionType.AGENT, description="Default session type filter", alias="type"
452
743
  ),
453
744
  db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
745
+ table: Optional[str] = Query(default=None, description="Table to use for deletion"),
454
746
  ) -> None:
455
747
  if len(request.session_ids) != len(request.session_types):
456
748
  raise HTTPException(status_code=400, detail="Session IDs and session types must have the same length")
457
749
 
458
- db = get_db(dbs, db_id)
459
- db.delete_sessions(session_ids=request.session_ids)
750
+ db = await get_db(dbs, db_id, table)
751
+ if isinstance(db, AsyncBaseDb):
752
+ db = cast(AsyncBaseDb, db)
753
+ await db.delete_sessions(session_ids=request.session_ids)
754
+ else:
755
+ db.delete_sessions(session_ids=request.session_ids)
460
756
 
461
757
  @router.post(
462
758
  "/sessions/{session_id}/rename",
@@ -553,9 +849,16 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
553
849
  ),
554
850
  session_name: str = Body(embed=True, description="New name for the session"),
555
851
  db_id: Optional[str] = Query(default=None, description="Database ID to use for rename operation"),
852
+ table: Optional[str] = Query(default=None, description="Table to use for rename operation"),
556
853
  ) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
557
- db = get_db(dbs, db_id)
558
- session = db.rename_session(session_id=session_id, session_type=session_type, session_name=session_name)
854
+ db = await get_db(dbs, db_id, table)
855
+ if isinstance(db, AsyncBaseDb):
856
+ db = cast(AsyncBaseDb, db)
857
+ session = await db.rename_session(
858
+ session_id=session_id, session_type=session_type, session_name=session_name
859
+ )
860
+ else:
861
+ session = db.rename_session(session_id=session_id, session_type=session_type, session_name=session_name)
559
862
  if not session:
560
863
  raise HTTPException(status_code=404, detail=f"Session with id '{session_id}' not found")
561
864
 
@@ -566,4 +869,129 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
566
869
  else:
567
870
  return WorkflowSessionDetailSchema.from_session(session) # type: ignore
568
871
 
872
+ @router.patch(
873
+ "/sessions/{session_id}",
874
+ response_model=Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema],
875
+ status_code=200,
876
+ operation_id="update_session",
877
+ summary="Update Session",
878
+ description=(
879
+ "Update session properties such as session_name, session_state, metadata, or summary. "
880
+ "Use this endpoint to modify the session name, update state, add metadata, or update the session summary."
881
+ ),
882
+ responses={
883
+ 200: {
884
+ "description": "Session updated successfully",
885
+ "content": {
886
+ "application/json": {
887
+ "examples": {
888
+ "update_summary": {
889
+ "summary": "Update session summary",
890
+ "value": {
891
+ "summary": {
892
+ "summary": "The user discussed project planning with the agent.",
893
+ "updated_at": "2025-10-21T14:30:00Z",
894
+ }
895
+ },
896
+ },
897
+ "update_metadata": {
898
+ "summary": "Update session metadata",
899
+ "value": {
900
+ "metadata": {
901
+ "tags": ["planning", "project"],
902
+ "priority": "high",
903
+ }
904
+ },
905
+ },
906
+ "update_session_name": {
907
+ "summary": "Update session name",
908
+ "value": {"session_name": "Updated Session Name"},
909
+ },
910
+ "update_session_state": {
911
+ "summary": "Update session state",
912
+ "value": {
913
+ "session_state": {
914
+ "step": "completed",
915
+ "context": "Project planning finished",
916
+ "progress": 100,
917
+ }
918
+ },
919
+ },
920
+ }
921
+ }
922
+ },
923
+ },
924
+ 404: {"description": "Session not found", "model": NotFoundResponse},
925
+ 422: {"description": "Invalid request", "model": ValidationErrorResponse},
926
+ 500: {"description": "Failed to update session", "model": InternalServerErrorResponse},
927
+ },
928
+ )
929
+ async def update_session(
930
+ request: Request,
931
+ session_id: str = Path(description="Session ID to update"),
932
+ session_type: SessionType = Query(
933
+ default=SessionType.AGENT, description="Session type (agent, team, or workflow)", alias="type"
934
+ ),
935
+ update_data: UpdateSessionRequest = Body(description="Session update data"),
936
+ user_id: Optional[str] = Query(default=None, description="User ID"),
937
+ db_id: Optional[str] = Query(default=None, description="Database ID to use for update operation"),
938
+ ) -> Union[AgentSessionDetailSchema, TeamSessionDetailSchema, WorkflowSessionDetailSchema]:
939
+ db = await get_db(dbs, db_id)
940
+
941
+ if hasattr(request.state, "user_id"):
942
+ user_id = request.state.user_id
943
+
944
+ # Get the existing session
945
+ if isinstance(db, AsyncBaseDb):
946
+ db = cast(AsyncBaseDb, db)
947
+ existing_session = await db.get_session(
948
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=True
949
+ )
950
+ else:
951
+ existing_session = db.get_session(
952
+ session_id=session_id, session_type=session_type, user_id=user_id, deserialize=True
953
+ )
954
+
955
+ if not existing_session:
956
+ raise HTTPException(status_code=404, detail=f"Session with id '{session_id}' not found")
957
+
958
+ # Update session properties
959
+ # Handle session_name - stored in session_data
960
+ if update_data.session_name is not None:
961
+ if existing_session.session_data is None: # type: ignore
962
+ existing_session.session_data = {} # type: ignore
963
+ existing_session.session_data["session_name"] = update_data.session_name # type: ignore
964
+
965
+ # Handle session_state - stored in session_data
966
+ if update_data.session_state is not None:
967
+ if existing_session.session_data is None: # type: ignore
968
+ existing_session.session_data = {} # type: ignore
969
+ existing_session.session_data["session_state"] = update_data.session_state # type: ignore
970
+
971
+ if update_data.metadata is not None:
972
+ existing_session.metadata = update_data.metadata # type: ignore
973
+
974
+ if update_data.summary is not None:
975
+ from agno.session.summary import SessionSummary
976
+
977
+ existing_session.summary = SessionSummary.from_dict(update_data.summary) # type: ignore
978
+
979
+ # Upsert the updated session
980
+ if isinstance(db, AsyncBaseDb):
981
+ db = cast(AsyncBaseDb, db)
982
+ updated_session = await db.upsert_session(existing_session, deserialize=True) # type: ignore
983
+ else:
984
+ updated_session = db.upsert_session(existing_session, deserialize=True) # type: ignore
985
+
986
+ if not updated_session:
987
+ raise HTTPException(status_code=500, detail="Failed to update session")
988
+
989
+ # Return appropriate schema based on session type
990
+ if session_type == SessionType.AGENT:
991
+ return AgentSessionDetailSchema.from_session(updated_session) # type: ignore
992
+ elif session_type == SessionType.TEAM:
993
+ return TeamSessionDetailSchema.from_session(updated_session) # type: ignore
994
+ else:
995
+ return WorkflowSessionDetailSchema.from_session(updated_session) # type: ignore
996
+
569
997
  return router
@@ -0,0 +1,3 @@
1
+ from agno.os.routers.teams.router import get_team_router
2
+
3
+ __all__ = ["get_team_router"]