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.
- agno/agent/agent.py +264 -155
- agno/api/schemas/agent.py +1 -0
- agno/api/schemas/team.py +1 -0
- agno/app/base.py +0 -22
- agno/app/discord/client.py +134 -56
- agno/app/fastapi/app.py +0 -11
- agno/app/playground/app.py +3 -24
- agno/app/playground/async_router.py +97 -28
- agno/app/playground/operator.py +25 -19
- agno/app/playground/schemas.py +1 -0
- agno/app/playground/sync_router.py +93 -26
- agno/document/reader/gcs/__init__.py +0 -0
- agno/document/reader/gcs/pdf_reader.py +44 -0
- agno/embedder/langdb.py +9 -5
- agno/knowledge/document.py +199 -8
- agno/knowledge/gcs/__init__.py +0 -0
- agno/knowledge/gcs/base.py +39 -0
- agno/knowledge/gcs/pdf.py +21 -0
- agno/models/langdb/langdb.py +8 -5
- agno/run/base.py +2 -0
- agno/run/response.py +4 -4
- agno/run/team.py +6 -6
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +563 -0
- agno/storage/base.py +4 -4
- agno/storage/dynamodb.py +74 -10
- agno/storage/firestore.py +6 -1
- agno/storage/gcs_json.py +8 -2
- agno/storage/json.py +20 -5
- agno/storage/mongodb.py +14 -5
- agno/storage/mysql.py +56 -17
- agno/storage/postgres.py +55 -13
- agno/storage/redis.py +25 -5
- agno/storage/session/__init__.py +3 -1
- agno/storage/session/agent.py +3 -0
- agno/storage/session/team.py +3 -0
- agno/storage/session/v2/__init__.py +5 -0
- agno/storage/session/v2/workflow.py +89 -0
- agno/storage/singlestore.py +74 -12
- agno/storage/sqlite.py +64 -18
- agno/storage/yaml.py +26 -6
- agno/team/team.py +198 -243
- agno/tools/scrapegraph.py +8 -10
- agno/utils/log.py +12 -0
- agno/utils/message.py +5 -1
- agno/utils/openai.py +20 -5
- agno/utils/pprint.py +32 -8
- agno/workflow/v2/__init__.py +21 -0
- agno/workflow/v2/condition.py +554 -0
- agno/workflow/v2/loop.py +602 -0
- agno/workflow/v2/parallel.py +659 -0
- agno/workflow/v2/router.py +521 -0
- agno/workflow/v2/step.py +861 -0
- agno/workflow/v2/steps.py +465 -0
- agno/workflow/v2/types.py +347 -0
- agno/workflow/v2/workflow.py +3134 -0
- agno/workflow/workflow.py +15 -147
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/METADATA +1 -1
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/RECORD +63 -45
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/WHEEL +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/entry_points.txt +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/top_level.txt +0 -0
agno/storage/postgres.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
|
|
|
@@ -79,12 +80,12 @@ class PostgresStorage(Storage):
|
|
|
79
80
|
log_debug(f"Created PostgresStorage: '{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(PostgresStorage, type(self)).mode.fset(self, value) # type: ignore
|
|
90
91
|
if value is not None:
|
|
@@ -127,6 +128,11 @@ class PostgresStorage(Storage):
|
|
|
127
128
|
Column("workflow_id", String, index=True),
|
|
128
129
|
Column("workflow_data", postgresql.JSONB),
|
|
129
130
|
]
|
|
131
|
+
elif self.mode == "workflow_v2":
|
|
132
|
+
specific_columns = [
|
|
133
|
+
Column("workflow_id", String, index=True),
|
|
134
|
+
Column("workflow_data", postgresql.JSONB),
|
|
135
|
+
]
|
|
130
136
|
|
|
131
137
|
# Create table with all columns
|
|
132
138
|
table = Table(
|
|
@@ -263,6 +269,8 @@ class PostgresStorage(Storage):
|
|
|
263
269
|
return TeamSession.from_dict(result._mapping) if result is not None else None
|
|
264
270
|
elif self.mode == "workflow":
|
|
265
271
|
return WorkflowSession.from_dict(result._mapping) if result is not None else None
|
|
272
|
+
elif self.mode == "workflow_v2":
|
|
273
|
+
return WorkflowSessionV2.from_dict(result._mapping) if result is not None else None
|
|
266
274
|
except Exception as e:
|
|
267
275
|
if "does not exist" in str(e):
|
|
268
276
|
log_debug(f"Table does not exist: {self.table.name}")
|
|
@@ -296,7 +304,8 @@ class PostgresStorage(Storage):
|
|
|
296
304
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
297
305
|
elif self.mode == "workflow":
|
|
298
306
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
299
|
-
|
|
307
|
+
elif self.mode == "workflow_v2":
|
|
308
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
300
309
|
# order by created_at desc
|
|
301
310
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
302
311
|
# execute query
|
|
@@ -331,7 +340,9 @@ class PostgresStorage(Storage):
|
|
|
331
340
|
stmt = stmt.where(self.table.c.agent_id == entity_id)
|
|
332
341
|
elif self.mode == "team":
|
|
333
342
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
334
|
-
|
|
343
|
+
elif self.mode == "workflow":
|
|
344
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
345
|
+
elif self.mode == "workflow_v2":
|
|
335
346
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
336
347
|
# order by created_at desc
|
|
337
348
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
@@ -384,7 +395,8 @@ class PostgresStorage(Storage):
|
|
|
384
395
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
385
396
|
elif self.mode == "workflow":
|
|
386
397
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
387
|
-
|
|
398
|
+
elif self.mode == "workflow_v2":
|
|
399
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
388
400
|
# Order by created_at desc and limit results
|
|
389
401
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
390
402
|
if limit is not None:
|
|
@@ -402,7 +414,8 @@ class PostgresStorage(Storage):
|
|
|
402
414
|
session = TeamSession.from_dict(row._mapping) # type: ignore
|
|
403
415
|
elif self.mode == "workflow":
|
|
404
416
|
session = WorkflowSession.from_dict(row._mapping) # type: ignore
|
|
405
|
-
|
|
417
|
+
elif self.mode == "workflow_v2":
|
|
418
|
+
session = WorkflowSessionV2.from_dict(row._mapping) # type: ignore
|
|
406
419
|
if session is not None:
|
|
407
420
|
sessions.append(session)
|
|
408
421
|
return sessions
|
|
@@ -479,7 +492,7 @@ class PostgresStorage(Storage):
|
|
|
479
492
|
agent_id=session.agent_id, # type: ignore
|
|
480
493
|
team_session_id=session.team_session_id, # type: ignore
|
|
481
494
|
user_id=session.user_id,
|
|
482
|
-
memory=session
|
|
495
|
+
memory=getattr(session, "memory", None),
|
|
483
496
|
agent_data=session.agent_data, # type: ignore
|
|
484
497
|
session_data=session.session_data,
|
|
485
498
|
extra_data=session.extra_data,
|
|
@@ -492,7 +505,7 @@ class PostgresStorage(Storage):
|
|
|
492
505
|
agent_id=session.agent_id, # type: ignore
|
|
493
506
|
team_session_id=session.team_session_id, # type: ignore
|
|
494
507
|
user_id=session.user_id,
|
|
495
|
-
memory=session
|
|
508
|
+
memory=getattr(session, "memory", None),
|
|
496
509
|
agent_data=session.agent_data, # type: ignore
|
|
497
510
|
session_data=session.session_data,
|
|
498
511
|
extra_data=session.extra_data,
|
|
@@ -505,7 +518,7 @@ class PostgresStorage(Storage):
|
|
|
505
518
|
team_id=session.team_id, # type: ignore
|
|
506
519
|
user_id=session.user_id,
|
|
507
520
|
team_session_id=session.team_session_id, # type: ignore
|
|
508
|
-
memory=session
|
|
521
|
+
memory=getattr(session, "memory", None),
|
|
509
522
|
team_data=session.team_data, # type: ignore
|
|
510
523
|
session_data=session.session_data,
|
|
511
524
|
extra_data=session.extra_data,
|
|
@@ -518,19 +531,19 @@ class PostgresStorage(Storage):
|
|
|
518
531
|
team_id=session.team_id, # type: ignore
|
|
519
532
|
user_id=session.user_id,
|
|
520
533
|
team_session_id=session.team_session_id, # type: ignore
|
|
521
|
-
memory=session
|
|
534
|
+
memory=getattr(session, "memory", None),
|
|
522
535
|
team_data=session.team_data, # type: ignore
|
|
523
536
|
session_data=session.session_data,
|
|
524
537
|
extra_data=session.extra_data,
|
|
525
538
|
updated_at=int(time.time()),
|
|
526
539
|
), # The updated value for each column
|
|
527
540
|
)
|
|
528
|
-
|
|
541
|
+
elif self.mode == "workflow":
|
|
529
542
|
stmt = postgresql.insert(self.table).values(
|
|
530
543
|
session_id=session.session_id,
|
|
531
544
|
workflow_id=session.workflow_id, # type: ignore
|
|
532
545
|
user_id=session.user_id,
|
|
533
|
-
memory=session
|
|
546
|
+
memory=getattr(session, "memory", None),
|
|
534
547
|
workflow_data=session.workflow_data, # type: ignore
|
|
535
548
|
session_data=session.session_data,
|
|
536
549
|
extra_data=session.extra_data,
|
|
@@ -542,13 +555,42 @@ class PostgresStorage(Storage):
|
|
|
542
555
|
set_=dict(
|
|
543
556
|
workflow_id=session.workflow_id, # type: ignore
|
|
544
557
|
user_id=session.user_id,
|
|
545
|
-
memory=session
|
|
558
|
+
memory=getattr(session, "memory", None),
|
|
546
559
|
workflow_data=session.workflow_data, # type: ignore
|
|
547
560
|
session_data=session.session_data,
|
|
548
561
|
extra_data=session.extra_data,
|
|
549
562
|
updated_at=int(time.time()),
|
|
550
563
|
), # The updated value for each column
|
|
551
564
|
)
|
|
565
|
+
elif self.mode == "workflow_v2":
|
|
566
|
+
# Convert session to dict to ensure proper serialization
|
|
567
|
+
session_dict = session.to_dict()
|
|
568
|
+
|
|
569
|
+
stmt = postgresql.insert(self.table).values(
|
|
570
|
+
session_id=session.session_id,
|
|
571
|
+
workflow_id=session.workflow_id, # type: ignore
|
|
572
|
+
workflow_name=session.workflow_name, # type: ignore
|
|
573
|
+
user_id=session.user_id,
|
|
574
|
+
runs=session_dict.get("runs"),
|
|
575
|
+
workflow_data=session.workflow_data, # type: ignore
|
|
576
|
+
session_data=session.session_data,
|
|
577
|
+
extra_data=session.extra_data,
|
|
578
|
+
)
|
|
579
|
+
# Define the upsert if the session_id already exists
|
|
580
|
+
# See: https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#postgresql-insert-on-conflict
|
|
581
|
+
stmt = stmt.on_conflict_do_update(
|
|
582
|
+
index_elements=["session_id"],
|
|
583
|
+
set_=dict(
|
|
584
|
+
workflow_id=session.workflow_id, # type: ignore
|
|
585
|
+
workflow_name=session.workflow_name, # type: ignore
|
|
586
|
+
user_id=session.user_id,
|
|
587
|
+
runs=session_dict.get("runs"),
|
|
588
|
+
workflow_data=session.workflow_data, # type: ignore
|
|
589
|
+
session_data=session.session_data,
|
|
590
|
+
extra_data=session.extra_data,
|
|
591
|
+
updated_at=int(time.time()),
|
|
592
|
+
),
|
|
593
|
+
)
|
|
552
594
|
|
|
553
595
|
sess.execute(stmt)
|
|
554
596
|
except Exception as e:
|
agno/storage/redis.py
CHANGED
|
@@ -8,6 +8,7 @@ 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 log_debug, log_info, logger
|
|
13
14
|
|
|
@@ -32,7 +33,7 @@ class RedisStorage(Storage):
|
|
|
32
33
|
port: int = 6379,
|
|
33
34
|
db: int = 0,
|
|
34
35
|
password: Optional[str] = None,
|
|
35
|
-
mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
|
|
36
|
+
mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
|
|
36
37
|
ssl: Optional[bool] = False,
|
|
37
38
|
expire: Optional[int] = None,
|
|
38
39
|
):
|
|
@@ -45,7 +46,7 @@ class RedisStorage(Storage):
|
|
|
45
46
|
port (int): Redis port number
|
|
46
47
|
db (int): Redis database number
|
|
47
48
|
password (Optional[str]): Redis password if authentication is required
|
|
48
|
-
mode (Optional[Literal["agent", "team", "workflow"]]): Storage mode
|
|
49
|
+
mode (Optional[Literal["agent", "team", "workflow", "workflow_v2"]]): Storage mode
|
|
49
50
|
ssl (Optional[bool]): Whether to use SSL for Redis connection
|
|
50
51
|
expire (Optional[int]): TTL (time to live) in seconds for Redis keys. None means no expiration.
|
|
51
52
|
"""
|
|
@@ -104,6 +105,8 @@ class RedisStorage(Storage):
|
|
|
104
105
|
return TeamSession.from_dict(session_data)
|
|
105
106
|
elif self.mode == "workflow":
|
|
106
107
|
return WorkflowSession.from_dict(session_data)
|
|
108
|
+
elif self.mode == "workflow_v2":
|
|
109
|
+
return WorkflowSessionV2.from_dict(session_data)
|
|
107
110
|
|
|
108
111
|
except Exception as e:
|
|
109
112
|
logger.error(f"Error reading session: {e}")
|
|
@@ -128,6 +131,8 @@ class RedisStorage(Storage):
|
|
|
128
131
|
self.mode == "workflow" and data["workflow_id"] == entity_id and data["user_id"] == user_id
|
|
129
132
|
):
|
|
130
133
|
session_ids.append(data["session_id"])
|
|
134
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
|
|
135
|
+
session_ids.append(data["session_id"])
|
|
131
136
|
elif user_id and data["user_id"] == user_id:
|
|
132
137
|
session_ids.append(data["session_id"])
|
|
133
138
|
elif entity_id:
|
|
@@ -137,6 +142,8 @@ class RedisStorage(Storage):
|
|
|
137
142
|
session_ids.append(data["session_id"])
|
|
138
143
|
elif self.mode == "workflow" and data["workflow_id"] == entity_id:
|
|
139
144
|
session_ids.append(data["session_id"])
|
|
145
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
|
|
146
|
+
session_ids.append(data["session_id"])
|
|
140
147
|
else:
|
|
141
148
|
# No filters applied, add all session_ids
|
|
142
149
|
session_ids.append(data["session_id"])
|
|
@@ -166,6 +173,8 @@ class RedisStorage(Storage):
|
|
|
166
173
|
self.mode == "workflow" and data["workflow_id"] == entity_id and data["user_id"] == user_id
|
|
167
174
|
):
|
|
168
175
|
_session = WorkflowSession.from_dict(data)
|
|
176
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
|
|
177
|
+
_session = WorkflowSessionV2.from_dict(data)
|
|
169
178
|
elif user_id and data["user_id"] == user_id:
|
|
170
179
|
if self.mode == "agent":
|
|
171
180
|
_session = AgentSession.from_dict(data)
|
|
@@ -173,6 +182,8 @@ class RedisStorage(Storage):
|
|
|
173
182
|
_session = TeamSession.from_dict(data)
|
|
174
183
|
elif self.mode == "workflow":
|
|
175
184
|
_session = WorkflowSession.from_dict(data)
|
|
185
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
|
|
186
|
+
_session = WorkflowSessionV2.from_dict(data)
|
|
176
187
|
elif entity_id:
|
|
177
188
|
if self.mode == "agent" and data["agent_id"] == entity_id:
|
|
178
189
|
_session = AgentSession.from_dict(data)
|
|
@@ -180,6 +191,8 @@ class RedisStorage(Storage):
|
|
|
180
191
|
_session = TeamSession.from_dict(data)
|
|
181
192
|
elif self.mode == "workflow" and data["workflow_id"] == entity_id:
|
|
182
193
|
_session = WorkflowSession.from_dict(data)
|
|
194
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] == entity_id:
|
|
195
|
+
_session = WorkflowSessionV2.from_dict(data)
|
|
183
196
|
|
|
184
197
|
if _session:
|
|
185
198
|
sessions.append(_session)
|
|
@@ -192,6 +205,8 @@ class RedisStorage(Storage):
|
|
|
192
205
|
_session = TeamSession.from_dict(data)
|
|
193
206
|
elif self.mode == "workflow":
|
|
194
207
|
_session = WorkflowSession.from_dict(data)
|
|
208
|
+
elif self.mode == "workflow_v2":
|
|
209
|
+
_session = WorkflowSessionV2.from_dict(data)
|
|
195
210
|
if _session:
|
|
196
211
|
sessions.append(_session)
|
|
197
212
|
|
|
@@ -237,7 +252,8 @@ class RedisStorage(Storage):
|
|
|
237
252
|
continue
|
|
238
253
|
elif self.mode == "workflow" and data["workflow_id"] != entity_id:
|
|
239
254
|
continue
|
|
240
|
-
|
|
255
|
+
elif self.mode == "workflow_v2" and data["workflow_id"] != entity_id:
|
|
256
|
+
continue
|
|
241
257
|
# Store with created_at for sorting
|
|
242
258
|
created_at = data.get("created_at", 0)
|
|
243
259
|
session_data.append((created_at, data))
|
|
@@ -260,7 +276,8 @@ class RedisStorage(Storage):
|
|
|
260
276
|
session = TeamSession.from_dict(data)
|
|
261
277
|
elif self.mode == "workflow":
|
|
262
278
|
session = WorkflowSession.from_dict(data)
|
|
263
|
-
|
|
279
|
+
elif self.mode == "workflow_v2":
|
|
280
|
+
session = WorkflowSessionV2.from_dict(data)
|
|
264
281
|
if session is not None:
|
|
265
282
|
sessions.append(session)
|
|
266
283
|
|
|
@@ -272,7 +289,10 @@ class RedisStorage(Storage):
|
|
|
272
289
|
def upsert(self, session: Session) -> Optional[Session]:
|
|
273
290
|
"""Insert or update a Session in Redis."""
|
|
274
291
|
try:
|
|
275
|
-
|
|
292
|
+
if self.mode == "workflow_v2":
|
|
293
|
+
data = session.to_dict()
|
|
294
|
+
else:
|
|
295
|
+
data = asdict(session)
|
|
276
296
|
data["updated_at"] = int(time.time())
|
|
277
297
|
if "created_at" not in data:
|
|
278
298
|
data["created_at"] = data["updated_at"]
|
agno/storage/session/__init__.py
CHANGED
|
@@ -2,13 +2,15 @@ from typing import Union
|
|
|
2
2
|
|
|
3
3
|
from agno.storage.session.agent import AgentSession
|
|
4
4
|
from agno.storage.session.team import TeamSession
|
|
5
|
+
from agno.storage.session.v2.workflow import WorkflowSession as WorkflowSessionV2
|
|
5
6
|
from agno.storage.session.workflow import WorkflowSession
|
|
6
7
|
|
|
7
|
-
Session = Union[AgentSession, TeamSession, WorkflowSession]
|
|
8
|
+
Session = Union[AgentSession, TeamSession, WorkflowSession, WorkflowSessionV2]
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"AgentSession",
|
|
11
12
|
"TeamSession",
|
|
12
13
|
"WorkflowSession",
|
|
14
|
+
"WorkflowSessionV2",
|
|
13
15
|
"Session",
|
|
14
16
|
]
|
agno/storage/session/agent.py
CHANGED
|
@@ -16,6 +16,8 @@ class AgentSession:
|
|
|
16
16
|
user_id: Optional[str] = None
|
|
17
17
|
# ID of the team session this agent session is associated with
|
|
18
18
|
team_session_id: Optional[str] = None
|
|
19
|
+
# ID of the workflow session this agent session is associated with
|
|
20
|
+
workflow_session_id: Optional[str] = None
|
|
19
21
|
# Agent Memory
|
|
20
22
|
memory: Optional[Dict[str, Any]] = None
|
|
21
23
|
# Session Data: session_name, session_state, images, videos, audio
|
|
@@ -51,6 +53,7 @@ class AgentSession:
|
|
|
51
53
|
session_id=data.get("session_id"), # type: ignore
|
|
52
54
|
agent_id=data.get("agent_id"),
|
|
53
55
|
team_session_id=data.get("team_session_id"),
|
|
56
|
+
workflow_session_id=data.get("workflow_session_id"),
|
|
54
57
|
user_id=data.get("user_id"),
|
|
55
58
|
memory=data.get("memory"),
|
|
56
59
|
agent_data=data.get("agent_data"),
|
agno/storage/session/team.py
CHANGED
|
@@ -14,6 +14,8 @@ class TeamSession:
|
|
|
14
14
|
session_id: str
|
|
15
15
|
# ID of the team session this team session is associated with (so for sub-teams)
|
|
16
16
|
team_session_id: Optional[str] = None
|
|
17
|
+
# ID of the workflow session this team session is associated with
|
|
18
|
+
workflow_session_id: Optional[str] = None
|
|
17
19
|
# ID of the team that this session is associated with
|
|
18
20
|
team_id: Optional[str] = None
|
|
19
21
|
# ID of the user interacting with this team
|
|
@@ -50,6 +52,7 @@ class TeamSession:
|
|
|
50
52
|
session_id=data.get("session_id"), # type: ignore
|
|
51
53
|
team_id=data.get("team_id"),
|
|
52
54
|
team_session_id=data.get("team_session_id"),
|
|
55
|
+
workflow_session_id=data.get("workflow_session_id"),
|
|
53
56
|
user_id=data.get("user_id"),
|
|
54
57
|
memory=data.get("memory"),
|
|
55
58
|
team_data=data.get("team_data"),
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Dict, List, Mapping, Optional
|
|
5
|
+
|
|
6
|
+
from agno.run.v2.workflow import WorkflowRunResponse
|
|
7
|
+
from agno.utils.log import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class WorkflowSession:
|
|
12
|
+
"""Workflow Session V2 for pipeline-based workflows"""
|
|
13
|
+
|
|
14
|
+
# Session UUID - this is the workflow_session_id that gets set on agents/teams
|
|
15
|
+
session_id: str
|
|
16
|
+
# ID of the user interacting with this workflow
|
|
17
|
+
user_id: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
# ID of the workflow that this session is associated with
|
|
20
|
+
workflow_id: Optional[str] = None
|
|
21
|
+
# Workflow name
|
|
22
|
+
workflow_name: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
# Workflow runs - stores WorkflowRunResponse objects in memory
|
|
25
|
+
runs: Optional[List[WorkflowRunResponse]] = None
|
|
26
|
+
|
|
27
|
+
# Session Data: session_name, session_state, images, videos, audio
|
|
28
|
+
session_data: Optional[Dict[str, Any]] = None
|
|
29
|
+
# Workflow configuration and metadata
|
|
30
|
+
workflow_data: Optional[Dict[str, Any]] = None
|
|
31
|
+
# Extra Data stored with this workflow session
|
|
32
|
+
extra_data: Optional[Dict[str, Any]] = None
|
|
33
|
+
|
|
34
|
+
# The unix timestamp when this session was created
|
|
35
|
+
created_at: Optional[int] = None
|
|
36
|
+
# The unix timestamp when this session was last updated
|
|
37
|
+
updated_at: Optional[int] = None
|
|
38
|
+
|
|
39
|
+
def __post_init__(self):
|
|
40
|
+
if self.runs is None:
|
|
41
|
+
self.runs = []
|
|
42
|
+
|
|
43
|
+
def add_run(self, run_response: WorkflowRunResponse) -> None:
|
|
44
|
+
"""Add a workflow run response to this session"""
|
|
45
|
+
if self.runs is None:
|
|
46
|
+
self.runs = []
|
|
47
|
+
# Store the actual WorkflowRunResponse object
|
|
48
|
+
self.runs.append(run_response)
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
51
|
+
"""Convert to dictionary for storage, serializing runs to dicts"""
|
|
52
|
+
return {
|
|
53
|
+
"session_id": self.session_id,
|
|
54
|
+
"user_id": self.user_id,
|
|
55
|
+
"workflow_id": self.workflow_id,
|
|
56
|
+
"workflow_name": self.workflow_name,
|
|
57
|
+
"runs": [run.to_dict() for run in self.runs] if self.runs else None,
|
|
58
|
+
"session_data": self.session_data,
|
|
59
|
+
"workflow_data": self.workflow_data,
|
|
60
|
+
"extra_data": self.extra_data,
|
|
61
|
+
"created_at": self.created_at,
|
|
62
|
+
"updated_at": self.updated_at,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: Mapping[str, Any]) -> Optional[WorkflowSession]:
|
|
67
|
+
"""Create WorkflowSession from dictionary, deserializing runs from dicts"""
|
|
68
|
+
if data is None or data.get("session_id") is None:
|
|
69
|
+
logger.warning("WorkflowSession is missing session_id")
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
# Deserialize runs from dictionaries back to WorkflowRunResponse objects
|
|
73
|
+
runs_data = data.get("runs")
|
|
74
|
+
runs: Optional[List[WorkflowRunResponse]] = None
|
|
75
|
+
if runs_data is not None:
|
|
76
|
+
runs = [WorkflowRunResponse.from_dict(run_dict) for run_dict in runs_data]
|
|
77
|
+
|
|
78
|
+
return cls(
|
|
79
|
+
session_id=data.get("session_id"), # type: ignore
|
|
80
|
+
user_id=data.get("user_id"),
|
|
81
|
+
workflow_id=data.get("workflow_id"),
|
|
82
|
+
workflow_name=data.get("workflow_name"),
|
|
83
|
+
runs=runs,
|
|
84
|
+
session_data=data.get("session_data"),
|
|
85
|
+
workflow_data=data.get("workflow_data"),
|
|
86
|
+
extra_data=data.get("extra_data"),
|
|
87
|
+
created_at=data.get("created_at"),
|
|
88
|
+
updated_at=data.get("updated_at"),
|
|
89
|
+
)
|
agno/storage/singlestore.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
|
|
|
@@ -30,7 +31,7 @@ class SingleStoreStorage(Storage):
|
|
|
30
31
|
db_engine: Optional[Engine] = None,
|
|
31
32
|
schema_version: int = 1,
|
|
32
33
|
auto_upgrade_schema: bool = False,
|
|
33
|
-
mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
|
|
34
|
+
mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
|
|
34
35
|
):
|
|
35
36
|
"""
|
|
36
37
|
This class provides Agent storage using a singlestore table.
|
|
@@ -46,7 +47,7 @@ class SingleStoreStorage(Storage):
|
|
|
46
47
|
db_engine (Optional[Engine], optional): The database engine. Defaults to None.
|
|
47
48
|
schema_version (int, optional): The schema version. Defaults to 1.
|
|
48
49
|
auto_upgrade_schema (bool, optional): Automatically upgrade the schema. Defaults to False.
|
|
49
|
-
mode (Optional[Literal["agent", "team", "workflow"]], optional): The mode of the storage. Defaults to "agent".
|
|
50
|
+
mode (Optional[Literal["agent", "team", "workflow", "workflow_v2"]], optional): The mode of the storage. Defaults to "agent".
|
|
50
51
|
"""
|
|
51
52
|
super().__init__(mode)
|
|
52
53
|
_engine: Optional[Engine] = db_engine
|
|
@@ -75,12 +76,12 @@ class SingleStoreStorage(Storage):
|
|
|
75
76
|
self.table: Table = self.get_table()
|
|
76
77
|
|
|
77
78
|
@property
|
|
78
|
-
def mode(self) -> Literal["agent", "team", "workflow"]:
|
|
79
|
+
def mode(self) -> Literal["agent", "team", "workflow", "workflow_v2"]:
|
|
79
80
|
"""Get the mode of the storage."""
|
|
80
81
|
return super().mode
|
|
81
82
|
|
|
82
83
|
@mode.setter
|
|
83
|
-
def mode(self, value: Optional[Literal["agent", "team", "workflow"]]) -> None:
|
|
84
|
+
def mode(self, value: Optional[Literal["agent", "team", "workflow", "workflow_v2"]]) -> None:
|
|
84
85
|
"""Set the mode and refresh the table if mode changes."""
|
|
85
86
|
super(SingleStoreStorage, type(self)).mode.fset(self, value) # type: ignore
|
|
86
87
|
if value is not None:
|
|
@@ -115,6 +116,11 @@ class SingleStoreStorage(Storage):
|
|
|
115
116
|
Column("workflow_id", mysql.TEXT),
|
|
116
117
|
Column("workflow_data", mysql.JSON),
|
|
117
118
|
]
|
|
119
|
+
elif self.mode == "workflow_v2":
|
|
120
|
+
specific_columns = [
|
|
121
|
+
Column("workflow_id", mysql.TEXT),
|
|
122
|
+
Column("workflow_data", mysql.JSON),
|
|
123
|
+
]
|
|
118
124
|
|
|
119
125
|
# Create table with all columns
|
|
120
126
|
table = Table(
|
|
@@ -171,6 +177,8 @@ class SingleStoreStorage(Storage):
|
|
|
171
177
|
return TeamSession.from_dict(existing_row._mapping) # type: ignore
|
|
172
178
|
elif self.mode == "workflow":
|
|
173
179
|
return WorkflowSession.from_dict(existing_row._mapping) # type: ignore
|
|
180
|
+
elif self.mode == "workflow_v2":
|
|
181
|
+
return WorkflowSessionV2.from_dict(existing_row._mapping) # type: ignore
|
|
174
182
|
return None
|
|
175
183
|
|
|
176
184
|
def get_all_session_ids(self, user_id: Optional[str] = None, entity_id: Optional[str] = None) -> List[str]:
|
|
@@ -188,6 +196,8 @@ class SingleStoreStorage(Storage):
|
|
|
188
196
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
189
197
|
elif self.mode == "workflow":
|
|
190
198
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
199
|
+
elif self.mode == "workflow_v2":
|
|
200
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
191
201
|
# order by created_at desc
|
|
192
202
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
193
203
|
# execute query
|
|
@@ -214,6 +224,8 @@ class SingleStoreStorage(Storage):
|
|
|
214
224
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
215
225
|
elif self.mode == "workflow":
|
|
216
226
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
227
|
+
elif self.mode == "workflow_v2":
|
|
228
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
217
229
|
# order by created_at desc
|
|
218
230
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
219
231
|
# execute query
|
|
@@ -232,6 +244,10 @@ class SingleStoreStorage(Storage):
|
|
|
232
244
|
_workflow_session = WorkflowSession.from_dict(row._mapping) # type: ignore
|
|
233
245
|
if _workflow_session is not None:
|
|
234
246
|
sessions.append(_workflow_session)
|
|
247
|
+
elif self.mode == "workflow_v2":
|
|
248
|
+
_workflow_session = WorkflowSessionV2.from_dict(row._mapping) # type: ignore
|
|
249
|
+
if _workflow_session is not None:
|
|
250
|
+
sessions.append(_workflow_session)
|
|
235
251
|
except Exception:
|
|
236
252
|
log_debug(f"Table does not exist: {self.table.name}")
|
|
237
253
|
return sessions
|
|
@@ -268,7 +284,8 @@ class SingleStoreStorage(Storage):
|
|
|
268
284
|
stmt = stmt.where(self.table.c.team_id == entity_id)
|
|
269
285
|
elif self.mode == "workflow":
|
|
270
286
|
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
271
|
-
|
|
287
|
+
elif self.mode == "workflow_v2":
|
|
288
|
+
stmt = stmt.where(self.table.c.workflow_id == entity_id)
|
|
272
289
|
# Order by created_at desc and limit results
|
|
273
290
|
stmt = stmt.order_by(self.table.c.created_at.desc())
|
|
274
291
|
if limit is not None:
|
|
@@ -290,7 +307,10 @@ class SingleStoreStorage(Storage):
|
|
|
290
307
|
session = WorkflowSession.from_dict(row._mapping) # type: ignore
|
|
291
308
|
if session is not None:
|
|
292
309
|
sessions.append(session)
|
|
293
|
-
|
|
310
|
+
elif self.mode == "workflow_v2":
|
|
311
|
+
session = WorkflowSessionV2.from_dict(row._mapping) # type: ignore
|
|
312
|
+
if session is not None:
|
|
313
|
+
sessions.append(session)
|
|
294
314
|
except Exception as e:
|
|
295
315
|
if "doesn't exist" in str(e):
|
|
296
316
|
log_debug(f"Table does not exist: {self.table.name}")
|
|
@@ -401,6 +421,25 @@ class SingleStoreStorage(Storage):
|
|
|
401
421
|
updated_at = UNIX_TIMESTAMP();
|
|
402
422
|
"""
|
|
403
423
|
)
|
|
424
|
+
elif self.mode == "workflow_v2":
|
|
425
|
+
# Convert session to dict to ensure proper serialization
|
|
426
|
+
upsert_sql = text(
|
|
427
|
+
f"""
|
|
428
|
+
INSERT INTO {self.schema}.{self.table_name}
|
|
429
|
+
(session_id, workflow_id, user_id, workflow_name, runs, workflow_data, session_data, extra_data, created_at, updated_at)
|
|
430
|
+
VALUES
|
|
431
|
+
(:session_id, :workflow_id, :user_id, :workflow_name, :runs, :workflow_data, :session_data, :extra_data, UNIX_TIMESTAMP(), NULL)
|
|
432
|
+
ON DUPLICATE KEY UPDATE
|
|
433
|
+
workflow_id = VALUES(workflow_id),
|
|
434
|
+
user_id = VALUES(user_id),
|
|
435
|
+
workflow_name = VALUES(workflow_name),
|
|
436
|
+
runs = VALUES(runs),
|
|
437
|
+
workflow_data = VALUES(workflow_data),
|
|
438
|
+
session_data = VALUES(session_data),
|
|
439
|
+
extra_data = VALUES(extra_data),
|
|
440
|
+
updated_at = UNIX_TIMESTAMP();
|
|
441
|
+
"""
|
|
442
|
+
)
|
|
404
443
|
|
|
405
444
|
try:
|
|
406
445
|
if self.mode == "agent":
|
|
@@ -411,8 +450,8 @@ class SingleStoreStorage(Storage):
|
|
|
411
450
|
"agent_id": session.agent_id, # type: ignore
|
|
412
451
|
"team_session_id": session.team_session_id, # type: ignore
|
|
413
452
|
"user_id": session.user_id,
|
|
414
|
-
"memory": json.dumps(session
|
|
415
|
-
if session
|
|
453
|
+
"memory": json.dumps(getattr(session, "memory", None), ensure_ascii=False)
|
|
454
|
+
if getattr(session, "memory", None) is not None
|
|
416
455
|
else None,
|
|
417
456
|
"agent_data": json.dumps(session.agent_data, ensure_ascii=False) # type: ignore
|
|
418
457
|
if session.agent_data is not None # type: ignore
|
|
@@ -433,8 +472,8 @@ class SingleStoreStorage(Storage):
|
|
|
433
472
|
"team_id": session.team_id, # type: ignore
|
|
434
473
|
"user_id": session.user_id,
|
|
435
474
|
"team_session_id": session.team_session_id, # type: ignore
|
|
436
|
-
"memory": json.dumps(session
|
|
437
|
-
if session
|
|
475
|
+
"memory": json.dumps(getattr(session, "memory", None), ensure_ascii=False)
|
|
476
|
+
if getattr(session, "memory", None) is not None
|
|
438
477
|
else None,
|
|
439
478
|
"team_data": json.dumps(session.team_data, ensure_ascii=False) # type: ignore
|
|
440
479
|
if session.team_data is not None # type: ignore
|
|
@@ -454,9 +493,32 @@ class SingleStoreStorage(Storage):
|
|
|
454
493
|
"session_id": session.session_id,
|
|
455
494
|
"workflow_id": session.workflow_id, # type: ignore
|
|
456
495
|
"user_id": session.user_id,
|
|
457
|
-
"memory": json.dumps(session
|
|
458
|
-
if session
|
|
496
|
+
"memory": json.dumps(getattr(session, "memory", None), ensure_ascii=False)
|
|
497
|
+
if getattr(session, "memory", None) is not None
|
|
498
|
+
else None,
|
|
499
|
+
"workflow_data": json.dumps(session.workflow_data, ensure_ascii=False) # type: ignore
|
|
500
|
+
if session.workflow_data is not None # type: ignore
|
|
459
501
|
else None,
|
|
502
|
+
"session_data": json.dumps(session.session_data, ensure_ascii=False)
|
|
503
|
+
if session.session_data is not None
|
|
504
|
+
else None,
|
|
505
|
+
"extra_data": json.dumps(session.extra_data, ensure_ascii=False)
|
|
506
|
+
if session.extra_data is not None
|
|
507
|
+
else None,
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
elif self.mode == "workflow_v2":
|
|
511
|
+
# Convert session to dict to ensure proper serialization
|
|
512
|
+
session_dict = session.to_dict()
|
|
513
|
+
|
|
514
|
+
sess.execute(
|
|
515
|
+
upsert_sql,
|
|
516
|
+
{
|
|
517
|
+
"session_id": session.session_id,
|
|
518
|
+
"workflow_id": session.workflow_id, # type: ignore
|
|
519
|
+
"user_id": session.user_id,
|
|
520
|
+
"workflow_name": session.workflow_name, # type: ignore
|
|
521
|
+
"runs": session_dict.get("runs"),
|
|
460
522
|
"workflow_data": json.dumps(session.workflow_data, ensure_ascii=False) # type: ignore
|
|
461
523
|
if session.workflow_data is not None # type: ignore
|
|
462
524
|
else None,
|