agno 2.0.0rc2__py3-none-any.whl → 2.3.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 (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/session/workflow.py CHANGED
@@ -2,10 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import time
4
4
  from dataclasses import dataclass
5
- from typing import Any, Dict, List, Mapping, Optional
5
+ from typing import Any, Dict, List, Mapping, Optional, Tuple
6
6
 
7
+ from pydantic import BaseModel
8
+ from pymongo.cursor import Union
9
+
10
+ from agno.models.message import Message
11
+ from agno.run.agent import RunOutput
12
+ from agno.run.base import RunStatus
13
+ from agno.run.team import TeamRunOutput
7
14
  from agno.run.workflow import WorkflowRunOutput
8
- from agno.utils.log import logger
15
+ from agno.utils.log import log_debug, logger
9
16
 
10
17
 
11
18
  @dataclass
@@ -37,44 +44,6 @@ class WorkflowSession:
37
44
  # The unix timestamp when this session was last updated
38
45
  updated_at: Optional[int] = None
39
46
 
40
- def __post_init__(self):
41
- if self.runs is None:
42
- self.runs = []
43
-
44
- # Ensure session_data, workflow_data, and metadata are dictionaries, not None
45
- if self.session_data is None:
46
- self.session_data = {}
47
- if self.workflow_data is None:
48
- self.workflow_data = {}
49
- if self.metadata is None:
50
- self.metadata = {}
51
-
52
- # Set timestamps if they're not already set
53
- current_time = int(time.time())
54
- if self.created_at is None:
55
- self.created_at = current_time
56
- if self.updated_at is None:
57
- self.updated_at = current_time
58
-
59
- def get_run(self, run_id: str) -> Optional[WorkflowRunOutput]:
60
- for run in self.runs or []:
61
- if run.run_id == run_id:
62
- return run
63
- return None
64
-
65
- def upsert_run(self, run: WorkflowRunOutput) -> None:
66
- """Add or update a workflow run (upsert behavior)"""
67
- if self.runs is None:
68
- self.runs = []
69
-
70
- # Find existing run and update it, or append new one
71
- for i, existing_run in enumerate(self.runs):
72
- if existing_run.run_id == run.run_id:
73
- self.runs[i] = run
74
- break
75
- else:
76
- self.runs.append(run)
77
-
78
47
  def to_dict(self) -> Dict[str, Any]:
79
48
  """Convert to dictionary for storage, serializing runs to dicts"""
80
49
 
@@ -86,6 +55,7 @@ class WorkflowSession:
86
55
  runs_data.append(run.to_dict())
87
56
  except Exception as e:
88
57
  raise ValueError(f"Serialization failed: {str(e)}")
58
+
89
59
  return {
90
60
  "session_id": self.session_id,
91
61
  "user_id": self.user_id,
@@ -134,3 +104,399 @@ class WorkflowSession:
134
104
  created_at=data.get("created_at"),
135
105
  updated_at=data.get("updated_at"),
136
106
  )
107
+
108
+ def __post_init__(self):
109
+ if self.runs is None:
110
+ self.runs = []
111
+
112
+ # Ensure session_data, workflow_data, and metadata are dictionaries, not None
113
+ if self.session_data is None:
114
+ self.session_data = {}
115
+ if self.workflow_data is None:
116
+ self.workflow_data = {}
117
+ if self.metadata is None:
118
+ self.metadata = {}
119
+
120
+ # Set timestamps if they're not already set
121
+ current_time = int(time.time())
122
+ if self.created_at is None:
123
+ self.created_at = current_time
124
+ if self.updated_at is None:
125
+ self.updated_at = current_time
126
+
127
+ def get_run(self, run_id: str) -> Optional[WorkflowRunOutput]:
128
+ for run in self.runs or []:
129
+ if run.run_id == run_id:
130
+ return run
131
+ return None
132
+
133
+ def upsert_run(self, run: WorkflowRunOutput) -> None:
134
+ """Add or update a workflow run (upsert behavior)"""
135
+ if self.runs is None:
136
+ self.runs = []
137
+
138
+ # Find existing run and update it, or append new one
139
+ for i, existing_run in enumerate(self.runs):
140
+ if existing_run.run_id == run.run_id:
141
+ self.runs[i] = run
142
+ break
143
+ else:
144
+ self.runs.append(run)
145
+
146
+ def get_workflow_history(self, num_runs: Optional[int] = None) -> List[Tuple[str, str]]:
147
+ """Get workflow history as structured data (input, response pairs)
148
+
149
+ Args:
150
+ num_runs: Number of recent runs to include. If None, returns all available history.
151
+ """
152
+ if not self.runs:
153
+ return []
154
+
155
+ # Get completed runs only (exclude current/pending run)
156
+ completed_runs = [run for run in self.runs if run.status == RunStatus.completed]
157
+
158
+ if num_runs is not None and len(completed_runs) > num_runs:
159
+ recent_runs = completed_runs[-num_runs:]
160
+ else:
161
+ recent_runs = completed_runs
162
+
163
+ if not recent_runs:
164
+ return []
165
+
166
+ # Return structured data as list of (input, response) tuples
167
+ history_data = []
168
+ for run in recent_runs:
169
+ # Get input
170
+ input_str = ""
171
+ if run.input:
172
+ input_str = str(run.input) if not isinstance(run.input, str) else run.input
173
+
174
+ # Get response
175
+ response_str = ""
176
+ if run.content:
177
+ response_str = str(run.content) if not isinstance(run.content, str) else run.content
178
+
179
+ history_data.append((input_str, response_str))
180
+
181
+ return history_data
182
+
183
+ def get_workflow_history_context(self, num_runs: Optional[int] = None) -> Optional[str]:
184
+ """Get formatted workflow history context for steps
185
+
186
+ Args:
187
+ num_runs: Number of recent runs to include. If None, returns all available history.
188
+ """
189
+ history_data = self.get_workflow_history(num_runs)
190
+
191
+ if not history_data:
192
+ return None
193
+
194
+ # Format as workflow context using the structured data
195
+ context_parts = ["<workflow_history_context>"]
196
+
197
+ for i, (input_str, response_str) in enumerate(history_data, 1):
198
+ context_parts.append(f"[Workflow Run-{i}]")
199
+
200
+ if input_str:
201
+ context_parts.append(f"User input: {input_str}")
202
+ if response_str:
203
+ context_parts.append(f"Workflow output: {response_str}")
204
+
205
+ context_parts.append("") # Empty line between runs
206
+
207
+ context_parts.append("</workflow_history_context>")
208
+ context_parts.append("") # Empty line before current input
209
+
210
+ return "\n".join(context_parts)
211
+
212
+ def get_messages_from_agent_runs(
213
+ self,
214
+ runs: List[RunOutput],
215
+ last_n_runs: Optional[int] = None,
216
+ limit: Optional[int] = None,
217
+ skip_roles: Optional[List[str]] = None,
218
+ skip_statuses: Optional[List[RunStatus]] = None,
219
+ skip_history_messages: bool = True,
220
+ ) -> List[Message]:
221
+ """Return the messages belonging to the given agent runs that fit the given criteria.
222
+
223
+ Args:
224
+ runs: The list of agent runs to get the messages from.
225
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
226
+ limit: Number of messages to include. If None, all messages will be included.
227
+ skip_roles: Roles to skip.
228
+ skip_statuses: Statuses to skip.
229
+ skip_history_messages: Whether to skip history messages.
230
+
231
+ Returns:
232
+ A list of messages from the given agent runs.
233
+ """
234
+
235
+ def _should_skip_message(
236
+ message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
237
+ ) -> bool:
238
+ """Logic to determine if a message should be skipped"""
239
+ # Skip messages that were tagged as history in previous runs
240
+ if hasattr(message, "from_history") and message.from_history and skip_history_messages:
241
+ return True
242
+
243
+ # Skip messages with specified role
244
+ if skip_roles and message.role in skip_roles:
245
+ return True
246
+
247
+ return False
248
+
249
+ # Filter by status
250
+ if skip_statuses:
251
+ runs = [run for run in runs if hasattr(run, "status") and run.status not in skip_statuses] # type: ignore
252
+
253
+ messages_from_history = []
254
+ system_message = None
255
+
256
+ # Limit the number of messages returned if limit is set
257
+ if limit is not None:
258
+ for run_response in runs:
259
+ if not run_response or not run_response.messages:
260
+ continue
261
+
262
+ for message in run_response.messages or []:
263
+ if _should_skip_message(message, skip_roles, skip_history_messages):
264
+ continue
265
+
266
+ if message.role == "system":
267
+ # Only add the system message once
268
+ if system_message is None:
269
+ system_message = message
270
+ else:
271
+ messages_from_history.append(message)
272
+
273
+ if system_message:
274
+ messages_from_history = [system_message] + messages_from_history[
275
+ -(limit - 1) :
276
+ ] # Grab one less message then add the system message
277
+ else:
278
+ messages_from_history = messages_from_history[-limit:]
279
+
280
+ # Remove tool result messages that don't have an associated assistant message with tool calls
281
+ while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
282
+ messages_from_history.pop(0)
283
+
284
+ # If limit is not set, return all messages
285
+ else:
286
+ runs_to_process = runs[-last_n_runs:] if last_n_runs is not None else runs
287
+ for run_response in runs_to_process:
288
+ if not run_response or not run_response.messages:
289
+ continue
290
+
291
+ for message in run_response.messages or []:
292
+ if _should_skip_message(message, skip_roles, skip_history_messages):
293
+ continue
294
+
295
+ if message.role == "system":
296
+ # Only add the system message once
297
+ if system_message is None:
298
+ system_message = message
299
+ messages_from_history.append(system_message)
300
+ else:
301
+ messages_from_history.append(message)
302
+
303
+ log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
304
+ return messages_from_history
305
+
306
+ def get_messages_from_team_runs(
307
+ self,
308
+ team_id: str,
309
+ runs: List[TeamRunOutput],
310
+ last_n_runs: Optional[int] = None,
311
+ limit: Optional[int] = None,
312
+ skip_roles: Optional[List[str]] = None,
313
+ skip_statuses: Optional[List[RunStatus]] = None,
314
+ skip_history_messages: bool = True,
315
+ skip_member_messages: bool = True,
316
+ ) -> List[Message]:
317
+ """Return the messages in the given team runs that fit the given criteria.
318
+
319
+ Args:
320
+ team_id: The ID of the contextual team.
321
+ runs: The list of team runs to get the messages from.
322
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
323
+ limit: Number of messages to include. If None, all messages will be included.
324
+ skip_roles: Roles to skip.
325
+ skip_statuses: Statuses to skip.
326
+ skip_history_messages: Whether to skip history messages.
327
+ skip_member_messages: Whether to skip messages from members of the team.
328
+
329
+ Returns:
330
+ A list of messages from the given team runs.
331
+ """
332
+
333
+ def _should_skip_message(
334
+ message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
335
+ ) -> bool:
336
+ """Logic to determine if a message should be skipped"""
337
+ # Skip messages that were tagged as history in previous runs
338
+ if hasattr(message, "from_history") and message.from_history and skip_history_messages:
339
+ return True
340
+
341
+ # Skip messages with specified role
342
+ if skip_roles and message.role in skip_roles:
343
+ return True
344
+
345
+ return False
346
+
347
+ # Filter for top-level runs (main team runs or agent runs when sharing session)
348
+ if skip_member_messages:
349
+ session_runs = [run for run in runs if run.team_id == team_id]
350
+
351
+ # Filter runs by status
352
+ if skip_statuses:
353
+ session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_statuses]
354
+
355
+ messages_from_history = []
356
+ system_message = None
357
+
358
+ # Limit the number of messages returned if limit is set
359
+ if limit is not None:
360
+ for run_response in session_runs:
361
+ if not run_response or not run_response.messages:
362
+ continue
363
+
364
+ for message in run_response.messages or []:
365
+ if _should_skip_message(message, skip_roles, skip_history_messages):
366
+ continue
367
+
368
+ if message.role == "system":
369
+ # Only add the system message once
370
+ if system_message is None:
371
+ system_message = message
372
+ else:
373
+ messages_from_history.append(message)
374
+
375
+ if system_message:
376
+ messages_from_history = [system_message] + messages_from_history[
377
+ -(limit - 1) :
378
+ ] # Grab one less message then add the system message
379
+ else:
380
+ messages_from_history = messages_from_history[-limit:]
381
+
382
+ # Remove tool result messages that don't have an associated assistant message with tool calls
383
+ while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
384
+ messages_from_history.pop(0)
385
+ else:
386
+ # Filter by last_n runs
387
+ runs_to_process = session_runs[-last_n_runs:] if last_n_runs is not None else session_runs
388
+
389
+ for run_response in runs_to_process:
390
+ if not (run_response and run_response.messages):
391
+ continue
392
+
393
+ for message in run_response.messages or []:
394
+ if _should_skip_message(message, skip_roles, skip_history_messages):
395
+ continue
396
+
397
+ if message.role == "system":
398
+ # Only add the system message once
399
+ if system_message is None:
400
+ system_message = message
401
+ messages_from_history.append(system_message)
402
+ else:
403
+ messages_from_history.append(message)
404
+
405
+ log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
406
+ return messages_from_history
407
+
408
+ def get_messages(
409
+ self,
410
+ agent_id: Optional[str] = None,
411
+ team_id: Optional[str] = None,
412
+ last_n_runs: Optional[int] = None,
413
+ limit: Optional[int] = None,
414
+ skip_roles: Optional[List[str]] = None,
415
+ skip_statuses: Optional[List[RunStatus]] = None,
416
+ skip_history_messages: bool = True,
417
+ skip_member_messages: bool = True,
418
+ ) -> List[Message]:
419
+ """Return the messages belonging to the session that fit the given criteria.
420
+
421
+ Args:
422
+ agent_id: The ID of the agent to get the messages for.
423
+ team_id: The ID of the team to get the messages for.
424
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
425
+ limit: Number of messages to include. If None, all messages will be included.
426
+ skip_roles: Roles to skip.
427
+ skip_statuses: Statuses to skip.
428
+ skip_history_messages: Whether to skip history messages.
429
+ skip_member_messages: Whether to skip messages from members of the team.
430
+
431
+ Returns:
432
+ A list of messages from the session.
433
+ """
434
+ if agent_id and team_id:
435
+ raise ValueError("agent_id and team_id cannot be used together")
436
+
437
+ if not self.runs:
438
+ return []
439
+
440
+ if agent_id:
441
+ agent_runs: List[RunOutput] = []
442
+ for run in self.runs:
443
+ if run.step_executor_runs:
444
+ for executor_run in run.step_executor_runs:
445
+ if isinstance(executor_run, RunOutput) and executor_run.agent_id == agent_id:
446
+ agent_runs.append(executor_run)
447
+ return self.get_messages_from_agent_runs(
448
+ runs=agent_runs,
449
+ last_n_runs=last_n_runs,
450
+ limit=limit,
451
+ skip_roles=skip_roles,
452
+ skip_statuses=skip_statuses,
453
+ skip_history_messages=skip_history_messages,
454
+ )
455
+
456
+ elif team_id:
457
+ team_runs: List[TeamRunOutput] = []
458
+ for run in self.runs:
459
+ if run.step_executor_runs:
460
+ for executor_run in run.step_executor_runs:
461
+ if isinstance(executor_run, TeamRunOutput) and executor_run.team_id == team_id:
462
+ team_runs.append(executor_run)
463
+ return self.get_messages_from_team_runs(
464
+ team_id=team_id,
465
+ runs=team_runs,
466
+ last_n_runs=last_n_runs,
467
+ limit=limit,
468
+ skip_roles=skip_roles,
469
+ skip_statuses=skip_statuses,
470
+ skip_history_messages=skip_history_messages,
471
+ skip_member_messages=skip_member_messages,
472
+ )
473
+
474
+ else:
475
+ raise ValueError("agent_id or team_id must be provided")
476
+
477
+ def get_chat_history(self, last_n_runs: Optional[int] = None) -> List[WorkflowChatInteraction]:
478
+ """Return a list of dictionaries containing the input and output for each run in the session.
479
+
480
+ Args:
481
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
482
+
483
+ Returns:
484
+ A list of WorkflowChatInteraction objects.
485
+ """
486
+ if not self.runs:
487
+ return []
488
+
489
+ runs = self.runs
490
+
491
+ if last_n_runs is not None:
492
+ runs = self.runs[-last_n_runs:]
493
+
494
+ return [
495
+ WorkflowChatInteraction(input=run.input, output=run.content) for run in runs if run.input and run.content
496
+ ]
497
+
498
+
499
+ @dataclass
500
+ class WorkflowChatInteraction:
501
+ input: Union[str, Dict[str, Any], List[Any], BaseModel]
502
+ output: Any