agno 1.7.2__py3-none-any.whl → 1.7.4__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 (63) hide show
  1. agno/agent/agent.py +264 -155
  2. agno/api/schemas/agent.py +1 -0
  3. agno/api/schemas/team.py +1 -0
  4. agno/app/base.py +0 -22
  5. agno/app/discord/client.py +134 -56
  6. agno/app/fastapi/app.py +0 -11
  7. agno/app/playground/app.py +3 -24
  8. agno/app/playground/async_router.py +97 -28
  9. agno/app/playground/operator.py +25 -19
  10. agno/app/playground/schemas.py +1 -0
  11. agno/app/playground/sync_router.py +93 -26
  12. agno/document/reader/gcs/__init__.py +0 -0
  13. agno/document/reader/gcs/pdf_reader.py +44 -0
  14. agno/embedder/langdb.py +9 -5
  15. agno/knowledge/document.py +199 -8
  16. agno/knowledge/gcs/__init__.py +0 -0
  17. agno/knowledge/gcs/base.py +39 -0
  18. agno/knowledge/gcs/pdf.py +21 -0
  19. agno/models/langdb/langdb.py +8 -5
  20. agno/run/base.py +2 -0
  21. agno/run/response.py +4 -4
  22. agno/run/team.py +6 -6
  23. agno/run/v2/__init__.py +0 -0
  24. agno/run/v2/workflow.py +563 -0
  25. agno/storage/base.py +4 -4
  26. agno/storage/dynamodb.py +74 -10
  27. agno/storage/firestore.py +6 -1
  28. agno/storage/gcs_json.py +8 -2
  29. agno/storage/json.py +20 -5
  30. agno/storage/mongodb.py +14 -5
  31. agno/storage/mysql.py +56 -17
  32. agno/storage/postgres.py +55 -13
  33. agno/storage/redis.py +25 -5
  34. agno/storage/session/__init__.py +3 -1
  35. agno/storage/session/agent.py +3 -0
  36. agno/storage/session/team.py +3 -0
  37. agno/storage/session/v2/__init__.py +5 -0
  38. agno/storage/session/v2/workflow.py +89 -0
  39. agno/storage/singlestore.py +74 -12
  40. agno/storage/sqlite.py +64 -18
  41. agno/storage/yaml.py +26 -6
  42. agno/team/team.py +198 -243
  43. agno/tools/scrapegraph.py +8 -10
  44. agno/utils/log.py +12 -0
  45. agno/utils/message.py +5 -1
  46. agno/utils/openai.py +20 -5
  47. agno/utils/pprint.py +32 -8
  48. agno/workflow/v2/__init__.py +21 -0
  49. agno/workflow/v2/condition.py +554 -0
  50. agno/workflow/v2/loop.py +602 -0
  51. agno/workflow/v2/parallel.py +659 -0
  52. agno/workflow/v2/router.py +521 -0
  53. agno/workflow/v2/step.py +861 -0
  54. agno/workflow/v2/steps.py +465 -0
  55. agno/workflow/v2/types.py +347 -0
  56. agno/workflow/v2/workflow.py +3134 -0
  57. agno/workflow/workflow.py +15 -147
  58. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/METADATA +1 -1
  59. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/RECORD +63 -45
  60. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/WHEEL +0 -0
  61. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/entry_points.txt +0 -0
  62. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/licenses/LICENSE +0 -0
  63. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/top_level.txt +0 -0
agno/storage/dynamodb.py CHANGED
@@ -7,6 +7,7 @@ from agno.storage.base import Storage
7
7
  from agno.storage.session import Session
8
8
  from agno.storage.session.agent import AgentSession
9
9
  from agno.storage.session.team import TeamSession
10
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
10
11
  from agno.storage.session.workflow import WorkflowSession
11
12
  from agno.utils.log import log_debug, log_info, logger
12
13
 
@@ -28,7 +29,7 @@ class DynamoDbStorage(Storage):
28
29
  aws_secret_access_key: Optional[str] = None,
29
30
  endpoint_url: Optional[str] = None,
30
31
  create_table_if_not_exists: bool = True,
31
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
32
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
32
33
  ):
33
34
  """
34
35
  Initialize the DynamoDbStorage.
@@ -41,7 +42,7 @@ class DynamoDbStorage(Storage):
41
42
  aws_secret_access_key (Optional[str]): AWS secret access key.
42
43
  endpoint_url (Optional[str]): The complete URL to use for the constructed client.
43
44
  create_table_if_not_exists (bool): Whether to create the table if it does not exist.
44
- mode (Optional[Literal["agent", "team", "workflow"]]): The mode of the storage.
45
+ mode (Optional[Literal["agent", "team", "workflow", "workflow_v2"]]): The mode of the storage.
45
46
  """
46
47
  super().__init__(mode)
47
48
  self.table_name = table_name
@@ -79,12 +80,12 @@ class DynamoDbStorage(Storage):
79
80
  log_debug(f"Initialized DynamoDbStorage with table '{self.table_name}'")
80
81
 
81
82
  @property
82
- def mode(self) -> Literal["agent", "team", "workflow"]:
83
+ def mode(self) -> Literal["agent", "team", "workflow", "workflow_v2"]:
83
84
  """Get the mode of the storage."""
84
85
  return super().mode
85
86
 
86
87
  @mode.setter
87
- def mode(self, value: Optional[Literal["agent", "team", "workflow"]]) -> None:
88
+ def mode(self, value: Optional[Literal["agent", "team", "workflow", "workflow_v2"]]) -> None:
88
89
  """Set the mode and refresh the table if mode changes."""
89
90
  super(DynamoDbStorage, type(self)).mode.fset(self, value) # type: ignore
90
91
  if value is not None:
@@ -125,7 +126,13 @@ class DynamoDbStorage(Storage):
125
126
  {"AttributeName": "workflow_id", "AttributeType": "S"},
126
127
  {"AttributeName": "created_at", "AttributeType": "N"},
127
128
  ]
128
-
129
+ elif self.mode == "workflow_v2":
130
+ attribute_definitions = [
131
+ {"AttributeName": "session_id", "AttributeType": "S"},
132
+ {"AttributeName": "user_id", "AttributeType": "S"},
133
+ {"AttributeName": "workflow_id", "AttributeType": "S"},
134
+ {"AttributeName": "created_at", "AttributeType": "N"},
135
+ ]
129
136
  secondary_indexes = [
130
137
  {
131
138
  "IndexName": "user_id-index",
@@ -185,7 +192,21 @@ class DynamoDbStorage(Storage):
185
192
  },
186
193
  }
187
194
  )
188
-
195
+ elif self.mode == "workflow_v2":
196
+ secondary_indexes.append(
197
+ {
198
+ "IndexName": "workflow_id-index",
199
+ "KeySchema": [
200
+ {"AttributeName": "workflow_id", "KeyType": "HASH"},
201
+ {"AttributeName": "created_at", "KeyType": "RANGE"},
202
+ ],
203
+ "Projection": {"ProjectionType": "ALL"},
204
+ "ProvisionedThroughput": {
205
+ "ReadCapacityUnits": 5,
206
+ "WriteCapacityUnits": 5,
207
+ },
208
+ }
209
+ )
189
210
  # Create the table
190
211
  self.table = self.dynamodb.create_table(
191
212
  TableName=self.table_name,
@@ -229,6 +250,8 @@ class DynamoDbStorage(Storage):
229
250
  return TeamSession.from_dict(item)
230
251
  elif self.mode == "workflow":
231
252
  return WorkflowSession.from_dict(item)
253
+ elif self.mode == "workflow_v2":
254
+ return WorkflowSessionV2.from_dict(item)
232
255
  except Exception as e:
233
256
  logger.error(f"Error reading session_id '{session_id}' with user_id '{user_id}': {e}")
234
257
  return None
@@ -323,7 +346,13 @@ class DynamoDbStorage(Storage):
323
346
  KeyConditionExpression=Key("user_id").eq(user_id),
324
347
  ProjectionExpression="session_id, workflow_id, user_id, memory, workflow_data, session_data, extra_data, created_at, updated_at",
325
348
  )
326
-
349
+ elif self.mode == "workflow_v2":
350
+ # Query using user_id index
351
+ response = self.table.query(
352
+ IndexName="user_id-index",
353
+ KeyConditionExpression=Key("user_id").eq(user_id),
354
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at",
355
+ )
327
356
  items = response.get("Items", []) # type: ignore
328
357
  for item in items:
329
358
  item = self._deserialize_item(item)
@@ -356,6 +385,13 @@ class DynamoDbStorage(Storage):
356
385
  KeyConditionExpression=Key("workflow_id").eq(entity_id),
357
386
  ProjectionExpression="session_id, workflow_id, user_id, memory, workflow_data, session_data, extra_data, created_at, updated_at",
358
387
  )
388
+ elif self.mode == "workflow_v2":
389
+ # Query using workflow_id index
390
+ response = self.table.query(
391
+ IndexName="workflow_id-index",
392
+ KeyConditionExpression=Key("workflow_id").eq(entity_id),
393
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at",
394
+ )
359
395
  items = response.get("Items", []) # type: ignore
360
396
  for item in items:
361
397
  item = self._deserialize_item(item)
@@ -379,6 +415,10 @@ class DynamoDbStorage(Storage):
379
415
  response = self.table.scan(
380
416
  ProjectionExpression="session_id, workflow_id, user_id, memory, workflow_data, session_data, extra_data, created_at, updated_at"
381
417
  )
418
+ elif self.mode == "workflow_v2":
419
+ response = self.table.scan(
420
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at"
421
+ )
382
422
  items = response.get("Items", [])
383
423
  for item in items:
384
424
  item = self._deserialize_item(item)
@@ -437,6 +477,14 @@ class DynamoDbStorage(Storage):
437
477
  ScanIndexForward=False,
438
478
  Limit=limit if limit is not None else None,
439
479
  )
480
+ elif self.mode == "workflow_v2":
481
+ response = self.table.query(
482
+ IndexName="user_id-index",
483
+ KeyConditionExpression=Key("user_id").eq(user_id),
484
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at",
485
+ ScanIndexForward=False,
486
+ Limit=limit if limit is not None else None,
487
+ )
440
488
  elif entity_id is not None:
441
489
  if self.mode == "agent":
442
490
  response = self.table.query(
@@ -462,6 +510,14 @@ class DynamoDbStorage(Storage):
462
510
  ScanIndexForward=False,
463
511
  Limit=limit if limit is not None else None,
464
512
  )
513
+ elif self.mode == "workflow_v2":
514
+ response = self.table.query(
515
+ IndexName="workflow_id-index",
516
+ KeyConditionExpression=Key("workflow_id").eq(entity_id),
517
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at",
518
+ ScanIndexForward=False,
519
+ Limit=limit if limit is not None else None,
520
+ )
465
521
  else:
466
522
  # If no filters, scan the table and sort by created_at
467
523
  if self.mode == "agent":
@@ -479,7 +535,11 @@ class DynamoDbStorage(Storage):
479
535
  ProjectionExpression="session_id, workflow_id, user_id, memory, workflow_data, session_data, extra_data, created_at, updated_at",
480
536
  Limit=limit if limit is not None else None,
481
537
  )
482
-
538
+ elif self.mode == "workflow_v2":
539
+ response = self.table.scan(
540
+ ProjectionExpression="session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at",
541
+ Limit=limit if limit is not None else None,
542
+ )
483
543
  items = response.get("Items", [])
484
544
  for item in items:
485
545
  item = self._deserialize_item(item)
@@ -491,7 +551,8 @@ class DynamoDbStorage(Storage):
491
551
  session = TeamSession.from_dict(item)
492
552
  elif self.mode == "workflow":
493
553
  session = WorkflowSession.from_dict(item)
494
-
554
+ elif self.mode == "workflow_v2":
555
+ session = WorkflowSessionV2.from_dict(item)
495
556
  if session is not None:
496
557
  sessions.append(session)
497
558
 
@@ -511,7 +572,10 @@ class DynamoDbStorage(Storage):
511
572
  Optional[Session]: The upserted Session, or None if operation failed.
512
573
  """
513
574
  try:
514
- item = asdict(session)
575
+ if self.mode == "workflow_v2":
576
+ item = session.to_dict()
577
+ else:
578
+ item = asdict(session)
515
579
 
516
580
  # Add timestamps
517
581
  current_time = int(time.time())
agno/storage/firestore.py CHANGED
@@ -6,6 +6,7 @@ from agno.storage.base import Storage
6
6
  from agno.storage.session import Session
7
7
  from agno.storage.session.agent import AgentSession
8
8
  from agno.storage.session.team import TeamSession
9
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
9
10
  from agno.storage.session.workflow import WorkflowSession
10
11
  from agno.utils.log import log_debug, logger
11
12
 
@@ -29,7 +30,7 @@ class FirestoreStorage(Storage):
29
30
  db_name: Optional[str] = "(default)",
30
31
  project_id: Optional[str] = None,
31
32
  client: Optional[Client] = None,
32
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
33
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
33
34
  ):
34
35
  super().__init__(mode)
35
36
  self.collection_name = collection_name
@@ -94,6 +95,8 @@ class FirestoreStorage(Storage):
94
95
  query = query.where(filter=FieldFilter("team_id", "==", entity_id))
95
96
  elif self.mode == "workflow":
96
97
  query = query.where(filter=FieldFilter("workflow_id", "==", entity_id))
98
+ elif self.mode == "workflow_v2":
99
+ query = query.where(filter=FieldFilter("workflow_id", "==", entity_id))
97
100
 
98
101
  return query
99
102
 
@@ -109,6 +112,8 @@ class FirestoreStorage(Storage):
109
112
  return TeamSession.from_dict(doc_data)
110
113
  elif self.mode == "workflow":
111
114
  return WorkflowSession.from_dict(doc_data)
115
+ elif self.mode == "workflow_v2":
116
+ return WorkflowSessionV2.from_dict(doc_data)
112
117
  except Exception as e:
113
118
  logger.error(f"Error parsing session data: {e}")
114
119
  return None
agno/storage/gcs_json.py CHANGED
@@ -6,6 +6,7 @@ from agno.storage.json import JsonStorage, Storage
6
6
  from agno.storage.session import Session
7
7
  from agno.storage.session.agent import AgentSession
8
8
  from agno.storage.session.team import TeamSession
9
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
9
10
  from agno.storage.session.workflow import WorkflowSession
10
11
  from agno.utils.log import logger
11
12
 
@@ -35,7 +36,7 @@ class GCSJsonStorage(JsonStorage):
35
36
  self,
36
37
  bucket_name: str,
37
38
  prefix: Optional[str] = "",
38
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
39
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
39
40
  project: Optional[str] = None,
40
41
  location: Optional[str] = None,
41
42
  credentials: Optional[Any] = None,
@@ -104,6 +105,8 @@ class GCSJsonStorage(JsonStorage):
104
105
  return TeamSession.from_dict(data)
105
106
  elif self.mode == "workflow":
106
107
  return WorkflowSession.from_dict(data)
108
+ elif self.mode == "workflow_v2":
109
+ return WorkflowSessionV2.from_dict(data)
107
110
  return None
108
111
 
109
112
  def get_all_session_ids(self, user_id: Optional[str] = None, entity_id: Optional[str] = None) -> List[str]:
@@ -136,6 +139,8 @@ class GCSJsonStorage(JsonStorage):
136
139
  session = TeamSession.from_dict(data)
137
140
  elif self.mode == "workflow":
138
141
  session = WorkflowSession.from_dict(data)
142
+ elif self.mode == "workflow_v2":
143
+ session = WorkflowSessionV2.from_dict(data)
139
144
  if session is not None:
140
145
  sessions.append(session)
141
146
  except Exception as e:
@@ -199,7 +204,8 @@ class GCSJsonStorage(JsonStorage):
199
204
  session = TeamSession.from_dict(data)
200
205
  elif self.mode == "workflow":
201
206
  session = WorkflowSession.from_dict(data)
202
-
207
+ elif self.mode == "workflow_v2":
208
+ session = WorkflowSessionV2.from_dict(data)
203
209
  if session is not None:
204
210
  sessions.append(session)
205
211
 
agno/storage/json.py CHANGED
@@ -8,12 +8,15 @@ from agno.storage.base import Storage
8
8
  from agno.storage.session import Session
9
9
  from agno.storage.session.agent import AgentSession
10
10
  from agno.storage.session.team import TeamSession
11
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
11
12
  from agno.storage.session.workflow import WorkflowSession
12
13
  from agno.utils.log import logger
13
14
 
14
15
 
15
16
  class JsonStorage(Storage):
16
- def __init__(self, dir_path: Union[str, Path], mode: Optional[Literal["agent", "team", "workflow"]] = "agent"):
17
+ def __init__(
18
+ self, dir_path: Union[str, Path], mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent"
19
+ ):
17
20
  super().__init__(mode)
18
21
  self.dir_path = Path(dir_path)
19
22
  self.dir_path.mkdir(parents=True, exist_ok=True)
@@ -42,6 +45,8 @@ class JsonStorage(Storage):
42
45
  return TeamSession.from_dict(data)
43
46
  elif self.mode == "workflow":
44
47
  return WorkflowSession.from_dict(data)
48
+ elif self.mode == "workflow_v2":
49
+ return WorkflowSessionV2.from_dict(data)
45
50
  except FileNotFoundError:
46
51
  return None
47
52
 
@@ -70,6 +75,8 @@ class JsonStorage(Storage):
70
75
  session_ids.append(data["session_id"])
71
76
  elif self.mode == "workflow" and data["workflow_id"] == entity_id:
72
77
  session_ids.append(data["session_id"])
78
+ elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
79
+ session_ids.append(data["session_id"])
73
80
  else:
74
81
  # No filters applied, add all session_ids
75
82
  session_ids.append(data["session_id"])
@@ -107,7 +114,8 @@ class JsonStorage(Storage):
107
114
  _session = TeamSession.from_dict(data)
108
115
  elif self.mode == "workflow" and data["workflow_id"] == entity_id:
109
116
  _session = WorkflowSession.from_dict(data)
110
-
117
+ elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
118
+ _session = WorkflowSessionV2.from_dict(data)
111
119
  if _session:
112
120
  sessions.append(_session)
113
121
  else:
@@ -118,6 +126,8 @@ class JsonStorage(Storage):
118
126
  _session = TeamSession.from_dict(data)
119
127
  elif self.mode == "workflow":
120
128
  _session = WorkflowSession.from_dict(data)
129
+ elif self.mode == "workflow_v2":
130
+ _session = WorkflowSessionV2.from_dict(data)
121
131
  if _session:
122
132
  sessions.append(_session)
123
133
  return sessions
@@ -158,7 +168,8 @@ class JsonStorage(Storage):
158
168
  continue
159
169
  elif self.mode == "workflow" and data["workflow_id"] != entity_id:
160
170
  continue
161
-
171
+ elif self.mode == "workflow_v2" and data["workflow_id"] != entity_id:
172
+ continue
162
173
  # Store with created_at for sorting
163
174
  created_at = data.get("created_at", 0)
164
175
  session_data.append((created_at, data))
@@ -181,7 +192,8 @@ class JsonStorage(Storage):
181
192
  session = TeamSession.from_dict(data)
182
193
  elif self.mode == "workflow":
183
194
  session = WorkflowSession.from_dict(data)
184
-
195
+ elif self.mode == "workflow_v2":
196
+ session = WorkflowSessionV2.from_dict(data)
185
197
  if session is not None:
186
198
  sessions.append(session)
187
199
 
@@ -190,7 +202,10 @@ class JsonStorage(Storage):
190
202
  def upsert(self, session: Session) -> Optional[Session]:
191
203
  """Insert or update a Session in storage."""
192
204
  try:
193
- data = asdict(session)
205
+ if self.mode == "workflow_v2":
206
+ data = session.to_dict()
207
+ else:
208
+ data = asdict(session)
194
209
  data["updated_at"] = int(time.time())
195
210
  if "created_at" not in data:
196
211
  data["created_at"] = data["updated_at"]
agno/storage/mongodb.py CHANGED
@@ -6,6 +6,7 @@ from agno.storage.base import Storage
6
6
  from agno.storage.session import Session
7
7
  from agno.storage.session.agent import AgentSession
8
8
  from agno.storage.session.team import TeamSession
9
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
9
10
  from agno.storage.session.workflow import WorkflowSession
10
11
  from agno.utils.log import log_debug, logger
11
12
 
@@ -25,7 +26,7 @@ class MongoDbStorage(Storage):
25
26
  db_url: Optional[str] = None,
26
27
  db_name: str = "agno",
27
28
  client: Optional[MongoClient] = None,
28
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
29
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
29
30
  ):
30
31
  """
31
32
  This class provides agent storage using MongoDB.
@@ -64,6 +65,8 @@ class MongoDbStorage(Storage):
64
65
  self.collection.create_index("team_id")
65
66
  elif self.mode == "workflow":
66
67
  self.collection.create_index("workflow_id")
68
+ elif self.mode == "workflow_v2":
69
+ self.collection.create_index("workflow_id")
67
70
  except PyMongoError as e:
68
71
  logger.error(f"Error creating indexes: {e}")
69
72
  raise
@@ -91,6 +94,8 @@ class MongoDbStorage(Storage):
91
94
  return TeamSession.from_dict(doc)
92
95
  elif self.mode == "workflow":
93
96
  return WorkflowSession.from_dict(doc)
97
+ elif self.mode == "workflow_v2":
98
+ return WorkflowSessionV2.from_dict(doc)
94
99
  return None
95
100
  except PyMongoError as e:
96
101
  logger.error(f"Error reading session: {e}")
@@ -115,7 +120,8 @@ class MongoDbStorage(Storage):
115
120
  query["team_id"] = entity_id
116
121
  elif self.mode == "workflow":
117
122
  query["workflow_id"] = entity_id
118
-
123
+ elif self.mode == "workflow_v2":
124
+ query["workflow_id"] = entity_id
119
125
  cursor = self.collection.find(query, {"session_id": 1}).sort("created_at", -1)
120
126
 
121
127
  return [str(doc["session_id"]) for doc in cursor]
@@ -142,7 +148,8 @@ class MongoDbStorage(Storage):
142
148
  query["team_id"] = entity_id
143
149
  elif self.mode == "workflow":
144
150
  query["workflow_id"] = entity_id
145
-
151
+ elif self.mode == "workflow_v2":
152
+ query["workflow_id"] = entity_id
146
153
  cursor = self.collection.find(query).sort("created_at", -1)
147
154
  sessions: List[Session] = []
148
155
  for doc in cursor:
@@ -193,7 +200,8 @@ class MongoDbStorage(Storage):
193
200
  query["team_id"] = entity_id
194
201
  elif self.mode == "workflow":
195
202
  query["workflow_id"] = entity_id
196
-
203
+ elif self.mode == "workflow_v2":
204
+ query["workflow_id"] = entity_id
197
205
  # Execute query with sort and limit
198
206
  cursor = self.collection.find(query)
199
207
  cursor = cursor.sort("created_at", -1) # Sort by created_at descending
@@ -212,7 +220,8 @@ class MongoDbStorage(Storage):
212
220
  session = TeamSession.from_dict(doc)
213
221
  elif self.mode == "workflow":
214
222
  session = WorkflowSession.from_dict(doc)
215
-
223
+ elif self.mode == "workflow_v2":
224
+ session = WorkflowSessionV2.from_dict(doc)
216
225
  if session is not None:
217
226
  sessions.append(session)
218
227
 
agno/storage/mysql.py CHANGED
@@ -5,6 +5,7 @@ from agno.storage.base import Storage
5
5
  from agno.storage.session import Session
6
6
  from agno.storage.session.agent import AgentSession
7
7
  from agno.storage.session.team import TeamSession
8
+ from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
8
9
  from agno.storage.session.workflow import WorkflowSession
9
10
  from agno.utils.log import log_debug, log_info, log_warning, logger
10
11
 
@@ -29,7 +30,7 @@ class MySQLStorage(Storage):
29
30
  db_engine: Optional[Engine] = None,
30
31
  schema_version: int = 1,
31
32
  auto_upgrade_schema: bool = False,
32
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
33
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
33
34
  ):
34
35
  """
35
36
  This class provides agent storage using a MySQL table.
@@ -46,7 +47,7 @@ class MySQLStorage(Storage):
46
47
  db_engine (Optional[Engine]): The SQLAlchemy database engine to use.
47
48
  schema_version (int): Version of the schema. Defaults to 1.
48
49
  auto_upgrade_schema (bool): Whether to automatically upgrade the schema.
49
- mode (Optional[Literal["agent", "team", "workflow"]]): The mode of the storage.
50
+ mode (Optional[Literal["agent", "team", "workflow", "workflow_v2"]]): The mode of the storage.
50
51
  Raises:
51
52
  ValueError: If neither db_url nor db_engine is provided.
52
53
  """
@@ -79,12 +80,12 @@ class MySQLStorage(Storage):
79
80
  log_debug(f"Created MySQLStorage: '{self.schema}.{self.table_name}'")
80
81
 
81
82
  @property
82
- def mode(self) -> Literal["agent", "team", "workflow"]:
83
+ def mode(self) -> Literal["agent", "team", "workflow", "workflow_v2"]:
83
84
  """Get the mode of the storage."""
84
85
  return super().mode
85
86
 
86
87
  @mode.setter
87
- def mode(self, value: Optional[Literal["agent", "team", "workflow"]]) -> None:
88
+ def mode(self, value: Optional[Literal["agent", "team", "workflow", "workflow_v2"]]) -> None:
88
89
  """Set the mode and refresh the table if mode changes."""
89
90
  super(MySQLStorage, type(self)).mode.fset(self, value) # type: ignore
90
91
  if value is not None:
@@ -127,7 +128,11 @@ class MySQLStorage(Storage):
127
128
  Column("workflow_id", String(255), index=True),
128
129
  Column("workflow_data", JSON),
129
130
  ]
130
-
131
+ elif self.mode == "workflow_v2":
132
+ specific_columns = [
133
+ Column("workflow_id", String(255), index=True),
134
+ Column("workflow_data", JSON),
135
+ ]
131
136
  # Create table with all columns
132
137
  table = Table(
133
138
  self.table_name,
@@ -278,6 +283,8 @@ class MySQLStorage(Storage):
278
283
  return TeamSession.from_dict(result._mapping) if result is not None else None
279
284
  elif self.mode == "workflow":
280
285
  return WorkflowSession.from_dict(result._mapping) if result is not None else None
286
+ elif self.mode == "workflow_v2":
287
+ return WorkflowSessionV2.from_dict(result._mapping) if result is not None else None
281
288
  except Exception as e:
282
289
  if "doesn't exist" in str(e) or "doesn't exist" in str(e):
283
290
  log_debug(f"Table does not exist: {self.table.name}")
@@ -311,7 +318,8 @@ class MySQLStorage(Storage):
311
318
  stmt = stmt.where(self.table.c.team_id == entity_id)
312
319
  elif self.mode == "workflow":
313
320
  stmt = stmt.where(self.table.c.workflow_id == entity_id)
314
-
321
+ elif self.mode == "workflow_v2":
322
+ stmt = stmt.where(self.table.c.workflow_id == entity_id)
315
323
  # order by created_at desc
316
324
  stmt = stmt.order_by(self.table.c.created_at.desc())
317
325
  # execute query
@@ -346,7 +354,9 @@ class MySQLStorage(Storage):
346
354
  stmt = stmt.where(self.table.c.agent_id == entity_id)
347
355
  elif self.mode == "team":
348
356
  stmt = stmt.where(self.table.c.team_id == entity_id)
349
- else:
357
+ elif self.mode == "workflow":
358
+ stmt = stmt.where(self.table.c.workflow_id == entity_id)
359
+ elif self.mode == "workflow_v2":
350
360
  stmt = stmt.where(self.table.c.workflow_id == entity_id)
351
361
  # order by created_at desc
352
362
  stmt = stmt.order_by(self.table.c.created_at.desc())
@@ -357,8 +367,10 @@ class MySQLStorage(Storage):
357
367
  return [AgentSession.from_dict(row._mapping) for row in rows] # type: ignore
358
368
  elif self.mode == "team":
359
369
  return [TeamSession.from_dict(row._mapping) for row in rows] # type: ignore
360
- else:
370
+ elif self.mode == "workflow":
361
371
  return [WorkflowSession.from_dict(row._mapping) for row in rows] # type: ignore
372
+ elif self.mode == "workflow_v2":
373
+ return [WorkflowSessionV2.from_dict(row._mapping) for row in rows] # type: ignore[misc]
362
374
  else:
363
375
  return []
364
376
  except Exception as e:
@@ -399,7 +411,8 @@ class MySQLStorage(Storage):
399
411
  stmt = stmt.where(self.table.c.team_id == entity_id)
400
412
  elif self.mode == "workflow":
401
413
  stmt = stmt.where(self.table.c.workflow_id == entity_id)
402
-
414
+ elif self.mode == "workflow_v2":
415
+ stmt = stmt.where(self.table.c.workflow_id == entity_id)
403
416
  # Order by created_at desc and limit results
404
417
  stmt = stmt.order_by(self.table.c.created_at.desc())
405
418
  if limit is not None:
@@ -417,7 +430,8 @@ class MySQLStorage(Storage):
417
430
  session = TeamSession.from_dict(row._mapping) # type: ignore
418
431
  elif self.mode == "workflow":
419
432
  session = WorkflowSession.from_dict(row._mapping) # type: ignore
420
-
433
+ elif self.mode == "workflow_v2":
434
+ session = WorkflowSessionV2.from_dict(row._mapping) # type: ignore
421
435
  if session is not None:
422
436
  sessions.append(session)
423
437
  return sessions
@@ -494,7 +508,7 @@ class MySQLStorage(Storage):
494
508
  agent_id=session.agent_id, # type: ignore
495
509
  team_session_id=session.team_session_id, # type: ignore
496
510
  user_id=session.user_id,
497
- memory=session.memory,
511
+ memory=getattr(session, "memory", None),
498
512
  agent_data=session.agent_data, # type: ignore
499
513
  session_data=session.session_data,
500
514
  extra_data=session.extra_data,
@@ -505,7 +519,7 @@ class MySQLStorage(Storage):
505
519
  agent_id=session.agent_id, # type: ignore
506
520
  team_session_id=session.team_session_id, # type: ignore
507
521
  user_id=session.user_id,
508
- memory=session.memory,
522
+ memory=getattr(session, "memory", None),
509
523
  agent_data=session.agent_data, # type: ignore
510
524
  session_data=session.session_data,
511
525
  extra_data=session.extra_data,
@@ -517,7 +531,7 @@ class MySQLStorage(Storage):
517
531
  team_id=session.team_id, # type: ignore
518
532
  user_id=session.user_id,
519
533
  team_session_id=session.team_session_id, # type: ignore
520
- memory=session.memory,
534
+ memory=getattr(session, "memory", None),
521
535
  team_data=session.team_data, # type: ignore
522
536
  session_data=session.session_data,
523
537
  extra_data=session.extra_data,
@@ -528,18 +542,18 @@ class MySQLStorage(Storage):
528
542
  team_id=session.team_id, # type: ignore
529
543
  user_id=session.user_id,
530
544
  team_session_id=session.team_session_id, # type: ignore
531
- memory=session.memory,
545
+ memory=getattr(session, "memory", None),
532
546
  team_data=session.team_data, # type: ignore
533
547
  session_data=session.session_data,
534
548
  extra_data=session.extra_data,
535
549
  updated_at=int(time.time()),
536
550
  )
537
- else:
551
+ elif self.mode == "workflow":
538
552
  stmt = mysql.insert(self.table).values(
539
553
  session_id=session.session_id,
540
554
  workflow_id=session.workflow_id, # type: ignore
541
555
  user_id=session.user_id,
542
- memory=session.memory,
556
+ memory=getattr(session, "memory", None),
543
557
  workflow_data=session.workflow_data, # type: ignore
544
558
  session_data=session.session_data,
545
559
  extra_data=session.extra_data,
@@ -549,13 +563,38 @@ class MySQLStorage(Storage):
549
563
  stmt = stmt.on_duplicate_key_update(
550
564
  workflow_id=session.workflow_id, # type: ignore
551
565
  user_id=session.user_id,
552
- memory=session.memory,
566
+ memory=getattr(session, "memory", None),
553
567
  workflow_data=session.workflow_data, # type: ignore
554
568
  session_data=session.session_data,
555
569
  extra_data=session.extra_data,
556
570
  updated_at=int(time.time()),
557
571
  )
572
+ elif self.mode == "workflow_v2":
573
+ # Convert session to dict to ensure proper serialization
574
+ session_dict = session.to_dict()
558
575
 
576
+ stmt = mysql.insert(self.table).values(
577
+ session_id=session.session_id,
578
+ workflow_id=session.workflow_id, # type: ignore
579
+ workflow_name=session.workflow_name, # type: ignore
580
+ user_id=session.user_id,
581
+ runs=session_dict.get("runs"),
582
+ workflow_data=session.workflow_data, # type: ignore
583
+ session_data=session.session_data,
584
+ extra_data=session.extra_data,
585
+ )
586
+ # Define the upsert if the session_id already exists
587
+ # See: https://docs.sqlalchemy.org/en/20/dialects/mysql.html#insert-on-duplicate-key-update
588
+ stmt = stmt.on_duplicate_key_update(
589
+ workflow_id=session.workflow_id, # type: ignore
590
+ workflow_name=session.workflow_name, # type: ignore
591
+ user_id=session.user_id,
592
+ runs=session_dict.get("runs"), # type: ignore
593
+ workflow_data=session.workflow_data, # type: ignore
594
+ session_data=session.session_data,
595
+ extra_data=session.extra_data,
596
+ updated_at=int(time.time()),
597
+ )
559
598
  sess.execute(stmt)
560
599
  except Exception as e:
561
600
  if create_and_retry and not self.table_exists():