agno 2.3.25__py3-none-any.whl → 2.3.26__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 +60 -17
- agno/os/routers/agents/router.py +4 -4
- agno/os/routers/teams/router.py +3 -3
- agno/os/routers/workflows/router.py +5 -5
- agno/os/utils.py +55 -3
- agno/team/team.py +131 -0
- agno/workflow/workflow.py +198 -0
- {agno-2.3.25.dist-info → agno-2.3.26.dist-info}/METADATA +1 -1
- {agno-2.3.25.dist-info → agno-2.3.26.dist-info}/RECORD +12 -12
- {agno-2.3.25.dist-info → agno-2.3.26.dist-info}/WHEEL +0 -0
- {agno-2.3.25.dist-info → agno-2.3.26.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.25.dist-info → agno-2.3.26.dist-info}/top_level.txt +0 -0
agno/agent/agent.py
CHANGED
|
@@ -9877,18 +9877,30 @@ class Agent:
|
|
|
9877
9877
|
fields_for_new_agent: Dict[str, Any] = {}
|
|
9878
9878
|
|
|
9879
9879
|
for f in fields(self):
|
|
9880
|
+
# Skip private fields (not part of __init__ signature)
|
|
9881
|
+
if f.name.startswith("_"):
|
|
9882
|
+
continue
|
|
9883
|
+
|
|
9880
9884
|
field_value = getattr(self, f.name)
|
|
9881
9885
|
if field_value is not None:
|
|
9882
|
-
|
|
9886
|
+
try:
|
|
9887
|
+
fields_for_new_agent[f.name] = self._deep_copy_field(f.name, field_value)
|
|
9888
|
+
except Exception as e:
|
|
9889
|
+
log_warning(f"Failed to deep copy field '{f.name}': {e}. Using original value.")
|
|
9890
|
+
fields_for_new_agent[f.name] = field_value
|
|
9883
9891
|
|
|
9884
9892
|
# Update fields if provided
|
|
9885
9893
|
if update:
|
|
9886
9894
|
fields_for_new_agent.update(update)
|
|
9887
9895
|
|
|
9888
9896
|
# Create a new Agent
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9897
|
+
try:
|
|
9898
|
+
new_agent = self.__class__(**fields_for_new_agent)
|
|
9899
|
+
log_debug(f"Created new {self.__class__.__name__}")
|
|
9900
|
+
return new_agent
|
|
9901
|
+
except Exception as e:
|
|
9902
|
+
log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
|
|
9903
|
+
raise
|
|
9892
9904
|
|
|
9893
9905
|
def _deep_copy_field(self, field_name: str, field_value: Any) -> Any:
|
|
9894
9906
|
"""Helper method to deep copy a field based on its type."""
|
|
@@ -9898,19 +9910,52 @@ class Agent:
|
|
|
9898
9910
|
if field_name == "reasoning_agent":
|
|
9899
9911
|
return field_value.deep_copy()
|
|
9900
9912
|
|
|
9901
|
-
# For
|
|
9902
|
-
|
|
9913
|
+
# For tools, share MCP tools but copy others
|
|
9914
|
+
if field_name == "tools" and field_value is not None:
|
|
9903
9915
|
try:
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9916
|
+
copied_tools = []
|
|
9917
|
+
for tool in field_value:
|
|
9918
|
+
try:
|
|
9919
|
+
# Share MCP tools (they maintain server connections)
|
|
9920
|
+
is_mcp_tool = hasattr(type(tool), "__mro__") and any(
|
|
9921
|
+
c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
|
|
9922
|
+
)
|
|
9923
|
+
if is_mcp_tool:
|
|
9924
|
+
copied_tools.append(tool)
|
|
9925
|
+
else:
|
|
9926
|
+
try:
|
|
9927
|
+
copied_tools.append(deepcopy(tool))
|
|
9928
|
+
except Exception:
|
|
9929
|
+
# Tool can't be deep copied, share by reference
|
|
9930
|
+
copied_tools.append(tool)
|
|
9931
|
+
except Exception:
|
|
9932
|
+
# MCP detection failed, share tool by reference to be safe
|
|
9933
|
+
copied_tools.append(tool)
|
|
9934
|
+
return copied_tools
|
|
9935
|
+
except Exception as e:
|
|
9936
|
+
# If entire tools processing fails, log and return original list
|
|
9937
|
+
log_warning(f"Failed to process tools for deep copy: {e}")
|
|
9938
|
+
return field_value
|
|
9939
|
+
|
|
9940
|
+
# Share heavy resources - these maintain connections/pools that shouldn't be duplicated
|
|
9941
|
+
if field_name in (
|
|
9942
|
+
"db",
|
|
9943
|
+
"model",
|
|
9944
|
+
"reasoning_model",
|
|
9945
|
+
"knowledge",
|
|
9946
|
+
"memory_manager",
|
|
9947
|
+
"parser_model",
|
|
9948
|
+
"output_model",
|
|
9949
|
+
"session_summary_manager",
|
|
9950
|
+
"culture_manager",
|
|
9951
|
+
"compression_manager",
|
|
9952
|
+
"learning",
|
|
9953
|
+
"skills",
|
|
9954
|
+
):
|
|
9955
|
+
return field_value
|
|
9911
9956
|
|
|
9912
9957
|
# For compound types, attempt a deep copy
|
|
9913
|
-
|
|
9958
|
+
if isinstance(field_value, (list, dict, set)):
|
|
9914
9959
|
try:
|
|
9915
9960
|
return deepcopy(field_value)
|
|
9916
9961
|
except Exception:
|
|
@@ -9921,7 +9966,7 @@ class Agent:
|
|
|
9921
9966
|
return field_value
|
|
9922
9967
|
|
|
9923
9968
|
# For pydantic models, attempt a model_copy
|
|
9924
|
-
|
|
9969
|
+
if isinstance(field_value, BaseModel):
|
|
9925
9970
|
try:
|
|
9926
9971
|
return field_value.model_copy(deep=True)
|
|
9927
9972
|
except Exception:
|
|
@@ -9933,8 +9978,6 @@ class Agent:
|
|
|
9933
9978
|
|
|
9934
9979
|
# For other types, attempt a shallow copy first
|
|
9935
9980
|
try:
|
|
9936
|
-
from copy import copy
|
|
9937
|
-
|
|
9938
9981
|
return copy(field_value)
|
|
9939
9982
|
except Exception:
|
|
9940
9983
|
# If copy fails, return as is
|
agno/os/routers/agents/router.py
CHANGED
|
@@ -243,7 +243,7 @@ def get_agent_router(
|
|
|
243
243
|
log_warning("Metadata parameter passed in both request state and kwargs, using request state")
|
|
244
244
|
kwargs["metadata"] = metadata
|
|
245
245
|
|
|
246
|
-
agent = get_agent_by_id(agent_id, os.agents)
|
|
246
|
+
agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
|
|
247
247
|
if agent is None:
|
|
248
248
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
249
249
|
|
|
@@ -405,7 +405,7 @@ def get_agent_router(
|
|
|
405
405
|
agent_id: str,
|
|
406
406
|
run_id: str,
|
|
407
407
|
):
|
|
408
|
-
agent = get_agent_by_id(agent_id, os.agents)
|
|
408
|
+
agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
|
|
409
409
|
if agent is None:
|
|
410
410
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
411
411
|
|
|
@@ -464,7 +464,7 @@ def get_agent_router(
|
|
|
464
464
|
except json.JSONDecodeError:
|
|
465
465
|
raise HTTPException(status_code=400, detail="Invalid JSON in tools field")
|
|
466
466
|
|
|
467
|
-
agent = get_agent_by_id(agent_id, os.agents)
|
|
467
|
+
agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
|
|
468
468
|
if agent is None:
|
|
469
469
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
470
470
|
|
|
@@ -630,7 +630,7 @@ def get_agent_router(
|
|
|
630
630
|
dependencies=[Depends(require_resource_access("agents", "read", "agent_id"))],
|
|
631
631
|
)
|
|
632
632
|
async def get_agent(agent_id: str, request: Request) -> AgentResponse:
|
|
633
|
-
agent = get_agent_by_id(agent_id, os.agents)
|
|
633
|
+
agent = get_agent_by_id(agent_id, os.agents, create_fresh=True)
|
|
634
634
|
if agent is None:
|
|
635
635
|
raise HTTPException(status_code=404, detail="Agent not found")
|
|
636
636
|
|
agno/os/routers/teams/router.py
CHANGED
|
@@ -194,7 +194,7 @@ def get_team_router(
|
|
|
194
194
|
|
|
195
195
|
logger.debug(f"Creating team run: {message=} {session_id=} {monitor=} {user_id=} {team_id=} {files=} {kwargs=}")
|
|
196
196
|
|
|
197
|
-
team = get_team_by_id(team_id, os.teams)
|
|
197
|
+
team = get_team_by_id(team_id, os.teams, create_fresh=True)
|
|
198
198
|
if team is None:
|
|
199
199
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
200
200
|
|
|
@@ -321,7 +321,7 @@ def get_team_router(
|
|
|
321
321
|
team_id: str,
|
|
322
322
|
run_id: str,
|
|
323
323
|
):
|
|
324
|
-
team = get_team_by_id(team_id, os.teams)
|
|
324
|
+
team = get_team_by_id(team_id, os.teams, create_fresh=True)
|
|
325
325
|
if team is None:
|
|
326
326
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
327
327
|
|
|
@@ -526,7 +526,7 @@ def get_team_router(
|
|
|
526
526
|
dependencies=[Depends(require_resource_access("teams", "read", "team_id"))],
|
|
527
527
|
)
|
|
528
528
|
async def get_team(team_id: str, request: Request) -> TeamResponse:
|
|
529
|
-
team = get_team_by_id(team_id, os.teams)
|
|
529
|
+
team = get_team_by_id(team_id, os.teams, create_fresh=True)
|
|
530
530
|
if team is None:
|
|
531
531
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
532
532
|
|
|
@@ -61,7 +61,7 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
|
|
|
61
61
|
return
|
|
62
62
|
|
|
63
63
|
# Get workflow from OS
|
|
64
|
-
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
64
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
|
|
65
65
|
if not workflow:
|
|
66
66
|
await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
|
|
67
67
|
return
|
|
@@ -141,7 +141,7 @@ async def handle_workflow_subscription(websocket: WebSocket, message: dict, os:
|
|
|
141
141
|
if buffer_status is None:
|
|
142
142
|
# Run not in buffer - check database
|
|
143
143
|
if workflow_id and session_id:
|
|
144
|
-
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
144
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
|
|
145
145
|
if workflow and isinstance(workflow, Workflow):
|
|
146
146
|
workflow_run = await workflow.aget_run_output(run_id, session_id)
|
|
147
147
|
|
|
@@ -571,7 +571,7 @@ def get_workflow_router(
|
|
|
571
571
|
dependencies=[Depends(require_resource_access("workflows", "read", "workflow_id"))],
|
|
572
572
|
)
|
|
573
573
|
async def get_workflow(workflow_id: str, request: Request) -> WorkflowResponse:
|
|
574
|
-
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
574
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
|
|
575
575
|
if workflow is None:
|
|
576
576
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
577
577
|
if isinstance(workflow, RemoteWorkflow):
|
|
@@ -650,7 +650,7 @@ def get_workflow_router(
|
|
|
650
650
|
kwargs["metadata"] = metadata
|
|
651
651
|
|
|
652
652
|
# Retrieve the workflow by ID
|
|
653
|
-
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
653
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
|
|
654
654
|
if workflow is None:
|
|
655
655
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
656
656
|
|
|
@@ -716,7 +716,7 @@ def get_workflow_router(
|
|
|
716
716
|
dependencies=[Depends(require_resource_access("workflows", "run", "workflow_id"))],
|
|
717
717
|
)
|
|
718
718
|
async def cancel_workflow_run(workflow_id: str, run_id: str):
|
|
719
|
-
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
719
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows, create_fresh=True)
|
|
720
720
|
|
|
721
721
|
if workflow is None:
|
|
722
722
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
agno/os/utils.py
CHANGED
|
@@ -414,37 +414,89 @@ def extract_format(file: UploadFile) -> Optional[str]:
|
|
|
414
414
|
|
|
415
415
|
|
|
416
416
|
def get_agent_by_id(
|
|
417
|
-
agent_id: str,
|
|
417
|
+
agent_id: str,
|
|
418
|
+
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
419
|
+
create_fresh: bool = False,
|
|
418
420
|
) -> Optional[Union[Agent, RemoteAgent]]:
|
|
421
|
+
"""Get an agent by ID, optionally creating a fresh instance for request isolation.
|
|
422
|
+
|
|
423
|
+
When create_fresh=True, creates a new agent instance using deep_copy() to prevent
|
|
424
|
+
state contamination between concurrent requests. The new instance shares heavy
|
|
425
|
+
resources (db, model, MCP tools) but has isolated mutable state.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
agent_id: The agent ID to look up
|
|
429
|
+
agents: List of agents to search
|
|
430
|
+
create_fresh: If True, creates a new instance using deep_copy()
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
The agent instance (shared or fresh copy based on create_fresh)
|
|
434
|
+
"""
|
|
419
435
|
if agent_id is None or agents is None:
|
|
420
436
|
return None
|
|
421
437
|
|
|
422
438
|
for agent in agents:
|
|
423
439
|
if agent.id == agent_id:
|
|
440
|
+
if create_fresh and isinstance(agent, Agent):
|
|
441
|
+
return agent.deep_copy()
|
|
424
442
|
return agent
|
|
425
443
|
return None
|
|
426
444
|
|
|
427
445
|
|
|
428
446
|
def get_team_by_id(
|
|
429
|
-
team_id: str,
|
|
447
|
+
team_id: str,
|
|
448
|
+
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
449
|
+
create_fresh: bool = False,
|
|
430
450
|
) -> Optional[Union[Team, RemoteTeam]]:
|
|
451
|
+
"""Get a team by ID, optionally creating a fresh instance for request isolation.
|
|
452
|
+
|
|
453
|
+
When create_fresh=True, creates a new team instance using deep_copy() to prevent
|
|
454
|
+
state contamination between concurrent requests. Member agents are also deep copied.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
team_id: The team ID to look up
|
|
458
|
+
teams: List of teams to search
|
|
459
|
+
create_fresh: If True, creates a new instance using deep_copy()
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
The team instance (shared or fresh copy based on create_fresh)
|
|
463
|
+
"""
|
|
431
464
|
if team_id is None or teams is None:
|
|
432
465
|
return None
|
|
433
466
|
|
|
434
467
|
for team in teams:
|
|
435
468
|
if team.id == team_id:
|
|
469
|
+
if create_fresh and isinstance(team, Team):
|
|
470
|
+
return team.deep_copy()
|
|
436
471
|
return team
|
|
437
472
|
return None
|
|
438
473
|
|
|
439
474
|
|
|
440
475
|
def get_workflow_by_id(
|
|
441
|
-
workflow_id: str,
|
|
476
|
+
workflow_id: str,
|
|
477
|
+
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
478
|
+
create_fresh: bool = False,
|
|
442
479
|
) -> Optional[Union[Workflow, RemoteWorkflow]]:
|
|
480
|
+
"""Get a workflow by ID, optionally creating a fresh instance for request isolation.
|
|
481
|
+
|
|
482
|
+
When create_fresh=True, creates a new workflow instance using deep_copy() to prevent
|
|
483
|
+
state contamination between concurrent requests. Steps containing agents/teams are also deep copied.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
workflow_id: The workflow ID to look up
|
|
487
|
+
workflows: List of workflows to search
|
|
488
|
+
create_fresh: If True, creates a new instance using deep_copy()
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
The workflow instance (shared or fresh copy based on create_fresh)
|
|
492
|
+
"""
|
|
443
493
|
if workflow_id is None or workflows is None:
|
|
444
494
|
return None
|
|
445
495
|
|
|
446
496
|
for workflow in workflows:
|
|
447
497
|
if workflow.id == workflow_id:
|
|
498
|
+
if create_fresh and isinstance(workflow, Workflow):
|
|
499
|
+
return workflow.deep_copy()
|
|
448
500
|
return workflow
|
|
449
501
|
return None
|
|
450
502
|
|
agno/team/team.py
CHANGED
|
@@ -9392,3 +9392,134 @@ class Team:
|
|
|
9392
9392
|
)
|
|
9393
9393
|
except Exception as e:
|
|
9394
9394
|
log_debug(f"Could not create Team run telemetry event: {e}")
|
|
9395
|
+
|
|
9396
|
+
def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> "Team":
|
|
9397
|
+
"""Create and return a deep copy of this Team, optionally updating fields.
|
|
9398
|
+
|
|
9399
|
+
This creates a fresh Team instance with isolated mutable state while sharing
|
|
9400
|
+
heavy resources like database connections and models. Member agents are also
|
|
9401
|
+
deep copied to ensure complete isolation.
|
|
9402
|
+
|
|
9403
|
+
Args:
|
|
9404
|
+
update: Optional dictionary of fields to override in the new Team.
|
|
9405
|
+
|
|
9406
|
+
Returns:
|
|
9407
|
+
Team: A new Team instance with copied state.
|
|
9408
|
+
"""
|
|
9409
|
+
from dataclasses import fields
|
|
9410
|
+
|
|
9411
|
+
# Extract the fields to set for the new Team
|
|
9412
|
+
fields_for_new_team: Dict[str, Any] = {}
|
|
9413
|
+
|
|
9414
|
+
for f in fields(self):
|
|
9415
|
+
# Skip private fields (not part of __init__ signature)
|
|
9416
|
+
if f.name.startswith("_"):
|
|
9417
|
+
continue
|
|
9418
|
+
|
|
9419
|
+
field_value = getattr(self, f.name)
|
|
9420
|
+
if field_value is not None:
|
|
9421
|
+
try:
|
|
9422
|
+
fields_for_new_team[f.name] = self._deep_copy_field(f.name, field_value)
|
|
9423
|
+
except Exception as e:
|
|
9424
|
+
log_warning(f"Failed to deep copy field '{f.name}': {e}. Using original value.")
|
|
9425
|
+
fields_for_new_team[f.name] = field_value
|
|
9426
|
+
|
|
9427
|
+
# Update fields if provided
|
|
9428
|
+
if update:
|
|
9429
|
+
fields_for_new_team.update(update)
|
|
9430
|
+
|
|
9431
|
+
# Create a new Team
|
|
9432
|
+
try:
|
|
9433
|
+
new_team = self.__class__(**fields_for_new_team)
|
|
9434
|
+
log_debug(f"Created new {self.__class__.__name__}")
|
|
9435
|
+
return new_team
|
|
9436
|
+
except Exception as e:
|
|
9437
|
+
log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
|
|
9438
|
+
raise
|
|
9439
|
+
|
|
9440
|
+
def _deep_copy_field(self, field_name: str, field_value: Any) -> Any:
|
|
9441
|
+
"""Helper method to deep copy a field based on its type."""
|
|
9442
|
+
from copy import copy, deepcopy
|
|
9443
|
+
|
|
9444
|
+
# For members, deep copy each agent/team
|
|
9445
|
+
if field_name == "members" and field_value is not None:
|
|
9446
|
+
copied_members = []
|
|
9447
|
+
for member in field_value:
|
|
9448
|
+
if hasattr(member, "deep_copy"):
|
|
9449
|
+
copied_members.append(member.deep_copy())
|
|
9450
|
+
else:
|
|
9451
|
+
copied_members.append(member)
|
|
9452
|
+
return copied_members
|
|
9453
|
+
|
|
9454
|
+
# For tools, share MCP tools but copy others
|
|
9455
|
+
if field_name == "tools" and field_value is not None:
|
|
9456
|
+
try:
|
|
9457
|
+
copied_tools = []
|
|
9458
|
+
for tool in field_value:
|
|
9459
|
+
try:
|
|
9460
|
+
# Share MCP tools (they maintain server connections)
|
|
9461
|
+
is_mcp_tool = hasattr(type(tool), "__mro__") and any(
|
|
9462
|
+
c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
|
|
9463
|
+
)
|
|
9464
|
+
if is_mcp_tool:
|
|
9465
|
+
copied_tools.append(tool)
|
|
9466
|
+
else:
|
|
9467
|
+
try:
|
|
9468
|
+
copied_tools.append(deepcopy(tool))
|
|
9469
|
+
except Exception:
|
|
9470
|
+
# Tool can't be deep copied, share by reference
|
|
9471
|
+
copied_tools.append(tool)
|
|
9472
|
+
except Exception:
|
|
9473
|
+
# MCP detection failed, share tool by reference to be safe
|
|
9474
|
+
copied_tools.append(tool)
|
|
9475
|
+
return copied_tools
|
|
9476
|
+
except Exception as e:
|
|
9477
|
+
# If entire tools processing fails, log and return original list
|
|
9478
|
+
log_warning(f"Failed to process tools for deep copy: {e}")
|
|
9479
|
+
return field_value
|
|
9480
|
+
|
|
9481
|
+
# Share heavy resources - these maintain connections/pools that shouldn't be duplicated
|
|
9482
|
+
if field_name in (
|
|
9483
|
+
"db",
|
|
9484
|
+
"model",
|
|
9485
|
+
"reasoning_model",
|
|
9486
|
+
"knowledge",
|
|
9487
|
+
"memory_manager",
|
|
9488
|
+
"parser_model",
|
|
9489
|
+
"output_model",
|
|
9490
|
+
"session_summary_manager",
|
|
9491
|
+
"culture_manager",
|
|
9492
|
+
"compression_manager",
|
|
9493
|
+
"learning",
|
|
9494
|
+
"skills",
|
|
9495
|
+
):
|
|
9496
|
+
return field_value
|
|
9497
|
+
|
|
9498
|
+
# For compound types, attempt a deep copy
|
|
9499
|
+
if isinstance(field_value, (list, dict, set)):
|
|
9500
|
+
try:
|
|
9501
|
+
return deepcopy(field_value)
|
|
9502
|
+
except Exception:
|
|
9503
|
+
try:
|
|
9504
|
+
return copy(field_value)
|
|
9505
|
+
except Exception as e:
|
|
9506
|
+
log_warning(f"Failed to copy field: {field_name} - {e}")
|
|
9507
|
+
return field_value
|
|
9508
|
+
|
|
9509
|
+
# For pydantic models, attempt a model_copy
|
|
9510
|
+
if isinstance(field_value, BaseModel):
|
|
9511
|
+
try:
|
|
9512
|
+
return field_value.model_copy(deep=True)
|
|
9513
|
+
except Exception:
|
|
9514
|
+
try:
|
|
9515
|
+
return field_value.model_copy(deep=False)
|
|
9516
|
+
except Exception as e:
|
|
9517
|
+
log_warning(f"Failed to copy field: {field_name} - {e}")
|
|
9518
|
+
return field_value
|
|
9519
|
+
|
|
9520
|
+
# For other types, attempt a shallow copy first
|
|
9521
|
+
try:
|
|
9522
|
+
return copy(field_value)
|
|
9523
|
+
except Exception:
|
|
9524
|
+
# If copy fails, return as is
|
|
9525
|
+
return field_value
|
agno/workflow/workflow.py
CHANGED
|
@@ -4481,3 +4481,201 @@ class Workflow:
|
|
|
4481
4481
|
session_id=session_id,
|
|
4482
4482
|
**kwargs,
|
|
4483
4483
|
)
|
|
4484
|
+
|
|
4485
|
+
def deep_copy(self, *, update: Optional[Dict[str, Any]] = None) -> "Workflow":
|
|
4486
|
+
"""Create and return a deep copy of this Workflow, optionally updating fields.
|
|
4487
|
+
|
|
4488
|
+
This creates a fresh Workflow instance with isolated mutable state while sharing
|
|
4489
|
+
heavy resources like database connections. Steps containing agents/teams are also
|
|
4490
|
+
deep copied to ensure complete isolation.
|
|
4491
|
+
|
|
4492
|
+
Args:
|
|
4493
|
+
update: Optional dictionary of fields to override in the new Workflow.
|
|
4494
|
+
|
|
4495
|
+
Returns:
|
|
4496
|
+
Workflow: A new Workflow instance with copied state.
|
|
4497
|
+
"""
|
|
4498
|
+
from copy import copy, deepcopy
|
|
4499
|
+
from dataclasses import fields
|
|
4500
|
+
|
|
4501
|
+
from agno.utils.log import log_debug, log_warning
|
|
4502
|
+
|
|
4503
|
+
# Extract the fields to set for the new Workflow
|
|
4504
|
+
fields_for_new_workflow: Dict[str, Any] = {}
|
|
4505
|
+
|
|
4506
|
+
for f in fields(self):
|
|
4507
|
+
# Skip private fields (not part of __init__ signature)
|
|
4508
|
+
if f.name.startswith("_"):
|
|
4509
|
+
continue
|
|
4510
|
+
|
|
4511
|
+
field_value = getattr(self, f.name)
|
|
4512
|
+
if field_value is not None:
|
|
4513
|
+
# Special handling for steps that may contain agents/teams
|
|
4514
|
+
if f.name == "steps" and field_value is not None:
|
|
4515
|
+
fields_for_new_workflow[f.name] = self._deep_copy_steps(field_value)
|
|
4516
|
+
# Special handling for workflow agent
|
|
4517
|
+
elif f.name == "agent" and field_value is not None:
|
|
4518
|
+
if hasattr(field_value, "deep_copy"):
|
|
4519
|
+
fields_for_new_workflow[f.name] = field_value.deep_copy()
|
|
4520
|
+
else:
|
|
4521
|
+
fields_for_new_workflow[f.name] = field_value
|
|
4522
|
+
# Share heavy resources - these maintain connections/pools that shouldn't be duplicated
|
|
4523
|
+
elif f.name == "db":
|
|
4524
|
+
fields_for_new_workflow[f.name] = field_value
|
|
4525
|
+
# For compound types, attempt a deep copy
|
|
4526
|
+
elif isinstance(field_value, (list, dict, set)):
|
|
4527
|
+
try:
|
|
4528
|
+
fields_for_new_workflow[f.name] = deepcopy(field_value)
|
|
4529
|
+
except Exception:
|
|
4530
|
+
try:
|
|
4531
|
+
fields_for_new_workflow[f.name] = copy(field_value)
|
|
4532
|
+
except Exception as e:
|
|
4533
|
+
log_warning(f"Failed to copy field: {f.name} - {e}")
|
|
4534
|
+
fields_for_new_workflow[f.name] = field_value
|
|
4535
|
+
# For pydantic models, attempt a model_copy
|
|
4536
|
+
elif isinstance(field_value, BaseModel):
|
|
4537
|
+
try:
|
|
4538
|
+
fields_for_new_workflow[f.name] = field_value.model_copy(deep=True)
|
|
4539
|
+
except Exception:
|
|
4540
|
+
try:
|
|
4541
|
+
fields_for_new_workflow[f.name] = field_value.model_copy(deep=False)
|
|
4542
|
+
except Exception:
|
|
4543
|
+
fields_for_new_workflow[f.name] = field_value
|
|
4544
|
+
# For other types, attempt a shallow copy
|
|
4545
|
+
else:
|
|
4546
|
+
try:
|
|
4547
|
+
fields_for_new_workflow[f.name] = copy(field_value)
|
|
4548
|
+
except Exception:
|
|
4549
|
+
fields_for_new_workflow[f.name] = field_value
|
|
4550
|
+
|
|
4551
|
+
# Update fields if provided
|
|
4552
|
+
if update:
|
|
4553
|
+
fields_for_new_workflow.update(update)
|
|
4554
|
+
|
|
4555
|
+
# Create a new Workflow
|
|
4556
|
+
try:
|
|
4557
|
+
new_workflow = self.__class__(**fields_for_new_workflow)
|
|
4558
|
+
log_debug(f"Created new {self.__class__.__name__}")
|
|
4559
|
+
return new_workflow
|
|
4560
|
+
except Exception as e:
|
|
4561
|
+
from agno.utils.log import log_error
|
|
4562
|
+
log_error(f"Failed to create deep copy of {self.__class__.__name__}: {e}")
|
|
4563
|
+
raise
|
|
4564
|
+
|
|
4565
|
+
def _deep_copy_steps(self, steps: Any) -> Any:
|
|
4566
|
+
"""Deep copy workflow steps, handling nested agents and teams."""
|
|
4567
|
+
from agno.workflow.steps import Steps
|
|
4568
|
+
|
|
4569
|
+
if steps is None:
|
|
4570
|
+
return None
|
|
4571
|
+
|
|
4572
|
+
# Handle Steps container
|
|
4573
|
+
if isinstance(steps, Steps):
|
|
4574
|
+
copied_steps = []
|
|
4575
|
+
if steps.steps:
|
|
4576
|
+
for step in steps.steps:
|
|
4577
|
+
copied_steps.append(self._deep_copy_single_step(step))
|
|
4578
|
+
return Steps(steps=copied_steps)
|
|
4579
|
+
|
|
4580
|
+
# Handle list of steps
|
|
4581
|
+
if isinstance(steps, list):
|
|
4582
|
+
return [self._deep_copy_single_step(step) for step in steps]
|
|
4583
|
+
|
|
4584
|
+
# Handle callable steps
|
|
4585
|
+
if callable(steps):
|
|
4586
|
+
return steps
|
|
4587
|
+
|
|
4588
|
+
# Handle single step
|
|
4589
|
+
return self._deep_copy_single_step(steps)
|
|
4590
|
+
|
|
4591
|
+
def _deep_copy_single_step(self, step: Any) -> Any:
|
|
4592
|
+
"""Deep copy a single step, handling nested agents and teams."""
|
|
4593
|
+
from copy import copy, deepcopy
|
|
4594
|
+
|
|
4595
|
+
from agno.agent import Agent
|
|
4596
|
+
from agno.team import Team
|
|
4597
|
+
from agno.workflow.condition import Condition
|
|
4598
|
+
from agno.workflow.loop import Loop
|
|
4599
|
+
from agno.workflow.parallel import Parallel
|
|
4600
|
+
from agno.workflow.router import Router
|
|
4601
|
+
from agno.workflow.step import Step
|
|
4602
|
+
from agno.workflow.steps import Steps
|
|
4603
|
+
|
|
4604
|
+
# Handle Step with agent or team
|
|
4605
|
+
if isinstance(step, Step):
|
|
4606
|
+
step_kwargs: Dict[str, Any] = {}
|
|
4607
|
+
if step.name:
|
|
4608
|
+
step_kwargs["name"] = step.name
|
|
4609
|
+
if step.description:
|
|
4610
|
+
step_kwargs["description"] = step.description
|
|
4611
|
+
if step.executor:
|
|
4612
|
+
step_kwargs["executor"] = step.executor
|
|
4613
|
+
if step.agent:
|
|
4614
|
+
step_kwargs["agent"] = step.agent.deep_copy() if hasattr(step.agent, "deep_copy") else step.agent
|
|
4615
|
+
if step.team:
|
|
4616
|
+
step_kwargs["team"] = step.team.deep_copy() if hasattr(step.team, "deep_copy") else step.team
|
|
4617
|
+
# Copy Step configuration attributes
|
|
4618
|
+
for attr in [
|
|
4619
|
+
"max_retries",
|
|
4620
|
+
"timeout_seconds",
|
|
4621
|
+
"skip_on_failure",
|
|
4622
|
+
"strict_input_validation",
|
|
4623
|
+
"add_workflow_history",
|
|
4624
|
+
"num_history_runs",
|
|
4625
|
+
]:
|
|
4626
|
+
if hasattr(step, attr):
|
|
4627
|
+
value = getattr(step, attr)
|
|
4628
|
+
# Only include non-default values to avoid overriding defaults
|
|
4629
|
+
if value is not None:
|
|
4630
|
+
step_kwargs[attr] = value
|
|
4631
|
+
return Step(**step_kwargs)
|
|
4632
|
+
|
|
4633
|
+
# Handle direct Agent
|
|
4634
|
+
if isinstance(step, Agent):
|
|
4635
|
+
return step.deep_copy() if hasattr(step, "deep_copy") else step
|
|
4636
|
+
|
|
4637
|
+
# Handle direct Team
|
|
4638
|
+
if isinstance(step, Team):
|
|
4639
|
+
return step.deep_copy() if hasattr(step, "deep_copy") else step
|
|
4640
|
+
|
|
4641
|
+
# Handle Parallel steps
|
|
4642
|
+
if isinstance(step, Parallel):
|
|
4643
|
+
copied_parallel_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
|
|
4644
|
+
return Parallel(*copied_parallel_steps, name=step.name, description=step.description)
|
|
4645
|
+
|
|
4646
|
+
# Handle Loop steps
|
|
4647
|
+
if isinstance(step, Loop):
|
|
4648
|
+
copied_loop_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
|
|
4649
|
+
return Loop(
|
|
4650
|
+
steps=copied_loop_steps,
|
|
4651
|
+
name=step.name,
|
|
4652
|
+
description=step.description,
|
|
4653
|
+
max_iterations=step.max_iterations,
|
|
4654
|
+
end_condition=step.end_condition,
|
|
4655
|
+
)
|
|
4656
|
+
|
|
4657
|
+
# Handle Condition steps
|
|
4658
|
+
if isinstance(step, Condition):
|
|
4659
|
+
copied_condition_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
|
|
4660
|
+
return Condition(
|
|
4661
|
+
evaluator=step.evaluator, steps=copied_condition_steps, name=step.name, description=step.description
|
|
4662
|
+
)
|
|
4663
|
+
|
|
4664
|
+
# Handle Router steps
|
|
4665
|
+
if isinstance(step, Router):
|
|
4666
|
+
copied_choices = [self._deep_copy_single_step(s) for s in step.choices] if step.choices else []
|
|
4667
|
+
return Router(choices=copied_choices, name=step.name, description=step.description, selector=step.selector)
|
|
4668
|
+
|
|
4669
|
+
# Handle Steps container
|
|
4670
|
+
if isinstance(step, Steps):
|
|
4671
|
+
copied_steps = [self._deep_copy_single_step(s) for s in step.steps] if step.steps else []
|
|
4672
|
+
return Steps(name=step.name, description=step.description, steps=copied_steps)
|
|
4673
|
+
|
|
4674
|
+
# For other types, attempt deep copy
|
|
4675
|
+
try:
|
|
4676
|
+
return deepcopy(step)
|
|
4677
|
+
except Exception:
|
|
4678
|
+
try:
|
|
4679
|
+
return copy(step)
|
|
4680
|
+
except Exception:
|
|
4681
|
+
return step
|
|
@@ -6,7 +6,7 @@ agno/media.py,sha256=PisfrNwkx2yVOW8p6LXlV237jI06Y6kGjd7wUMk5170,17121
|
|
|
6
6
|
agno/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
agno/table.py,sha256=9hHFnInNsrj0ZKtWjGDP5c6kbmNdtQvDDYT2j2CZJ6o,198
|
|
8
8
|
agno/agent/__init__.py,sha256=K1umiV73llHpUidDlng0J3T_YY6tbISfsNKw_2S-L1U,1112
|
|
9
|
-
agno/agent/agent.py,sha256=
|
|
9
|
+
agno/agent/agent.py,sha256=H9scQ5GJp3cMSvAueypf9DjUWqPcg3cAfvp4PUJC27A,528532
|
|
10
10
|
agno/agent/remote.py,sha256=EzaT0hhD_2BkrL3ECnxx0oxWnyyc6nPmDbifqvSgkUo,19172
|
|
11
11
|
agno/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
agno/api/agent.py,sha256=fKlQ62E_C9Rjd7Zus3Gs3R1RG-IhzFV-ICpkb6SLqYc,932
|
|
@@ -306,7 +306,7 @@ agno/os/router.py,sha256=Xx3iwxSUSRDEXsQoq5mFuz9MlZIx2YYOkod7RQAaLzw,12348
|
|
|
306
306
|
agno/os/schema.py,sha256=GmOy3c52pp1EcVp3QznfHySZwIop1qwB8woX41jeSAE,30527
|
|
307
307
|
agno/os/scopes.py,sha256=gH3Obo618gHKt5h-N1OkWqB4UPl2LZygd7GW7pKzdAc,15696
|
|
308
308
|
agno/os/settings.py,sha256=gS9pN1w21wsa5XqDkK-RfQmroAaqoX4KZBfRuhUevkM,1556
|
|
309
|
-
agno/os/utils.py,sha256=
|
|
309
|
+
agno/os/utils.py,sha256=7ym5vG9eRdqppT-ZauFMqGhPVS_ruqeLNhC3BDCqpq0,36860
|
|
310
310
|
agno/os/interfaces/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
311
311
|
agno/os/interfaces/base.py,sha256=N7T-gpSBJeB6xlBn6bhYZNdqG6wXSFj6Uue9TmXhzTE,643
|
|
312
312
|
agno/os/interfaces/a2a/__init__.py,sha256=Fs7--dx9drvtVS9QjsCCm0P7c-hJ7TzU8gNwKTQsZDA,62
|
|
@@ -332,7 +332,7 @@ agno/os/routers/database.py,sha256=PbUq-nQYOROhNiKloKX6qpqKlpqvN6sAnUG31gzgjDk,5
|
|
|
332
332
|
agno/os/routers/health.py,sha256=AO3ec6Xi1wXOwUUFJhEcM45488qu6aZRJTwF2GLz6Ak,992
|
|
333
333
|
agno/os/routers/home.py,sha256=xe8DYJkRgad55qiza0lHt8pUIV5PLSyu2MkybjuPDDE,1708
|
|
334
334
|
agno/os/routers/agents/__init__.py,sha256=nr1H0Mp7NlWPnvu0ccaHVSPHz-lXg43TRMApkUIEXNc,91
|
|
335
|
-
agno/os/routers/agents/router.py,sha256=
|
|
335
|
+
agno/os/routers/agents/router.py,sha256=7TJsIgXHbNp4W3UMAX2g8tgo5O2f39Boau4p8IVZUgo,25833
|
|
336
336
|
agno/os/routers/agents/schema.py,sha256=dtnFw5e0MFzLmfApHvBwXp0ByN1w10F-swYeZyNkN7c,12776
|
|
337
337
|
agno/os/routers/evals/__init__.py,sha256=3s0M-Ftg5A3rFyRfTATs-0aNA6wcbj_5tCvtwH9gORQ,87
|
|
338
338
|
agno/os/routers/evals/evals.py,sha256=9DErwITEV6O1BAr28asB29Yvgy8C5wtzQyz-ys-yJzw,23478
|
|
@@ -350,13 +350,13 @@ agno/os/routers/metrics/schemas.py,sha256=1zE3Mfi15SNfF_Hy3r_tLK86E-Jox_qWzSG0oP
|
|
|
350
350
|
agno/os/routers/session/__init__.py,sha256=du4LO9aZwiY1t59VcV9M6wiAfftFFlUZc-YXsTGy9LI,97
|
|
351
351
|
agno/os/routers/session/session.py,sha256=oDAwi6iZTsqkDwt0MehCCUox5IC838CG7TFdgePHQ4c,57764
|
|
352
352
|
agno/os/routers/teams/__init__.py,sha256=j2aPA09mwwn_vOG8p2uX0sDR-qNbLLRlPlprqM789NY,88
|
|
353
|
-
agno/os/routers/teams/router.py,sha256=
|
|
353
|
+
agno/os/routers/teams/router.py,sha256=L2WnVzxGDUsnPvwqnhRUHRqzKRSLJbRBkMR3ABn0DOM,24601
|
|
354
354
|
agno/os/routers/teams/schema.py,sha256=CaRIiEFXBTF1T5D2RlPCRTIMgyqh2kFlYtbCmkuNYYU,12173
|
|
355
355
|
agno/os/routers/traces/__init__.py,sha256=v-QMRjlrw1iLkh2UawukKWfZ2R66L-OQmAGR-kqFo8I,93
|
|
356
356
|
agno/os/routers/traces/schemas.py,sha256=_O7tfF3aAPR-R1Q9noakWxjbI20UxkemVh7P2Dkw71I,20034
|
|
357
357
|
agno/os/routers/traces/traces.py,sha256=6fXnHacKJQULAG_Sh-Ci2sFGn1XMsXufRJM4GVxywOU,24413
|
|
358
358
|
agno/os/routers/workflows/__init__.py,sha256=1VnCzNTwxT9Z9EHskfqtrl1zhXGPPKuR4TktYdKd1RI,146
|
|
359
|
-
agno/os/routers/workflows/router.py,sha256=
|
|
359
|
+
agno/os/routers/workflows/router.py,sha256=gyk55lZnXER2WoqfLChlYG1JX4T1bP3WtBRYYYTUNFM,31175
|
|
360
360
|
agno/os/routers/workflows/schema.py,sha256=ovFO5RNL4UPXDNHZuQzOHZJsWkiBtoLX5D-Plp__AQ4,5739
|
|
361
361
|
agno/reasoning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
362
362
|
agno/reasoning/anthropic.py,sha256=WzUrGLLdbH31T6uBafYKt7W0wXMYHbXLxYRiVs2ge5U,6334
|
|
@@ -401,7 +401,7 @@ agno/skills/loaders/base.py,sha256=Syt6lIOe-m_Kz68ndwuVed3wZFAPvYDDnJkhypazYJ8,7
|
|
|
401
401
|
agno/skills/loaders/local.py,sha256=7budE7d-JG86XyqnRwo495yiYWDjj16yDV67aiSacOQ,7478
|
|
402
402
|
agno/team/__init__.py,sha256=7oYaEB1iTGbmajpgSd3tjCQAQo6t0PIE8ZT1m5MKTm0,905
|
|
403
403
|
agno/team/remote.py,sha256=BgUEQsTFA4tehDas1YO7lwQCj3cLJrxfoSFVOYm_1po,16979
|
|
404
|
-
agno/team/team.py,sha256=
|
|
404
|
+
agno/team/team.py,sha256=3DMRu7UjaRZubGCkDErQZbFYN5XV2XTzP--1-bZW01E,440036
|
|
405
405
|
agno/tools/__init__.py,sha256=jNll2sELhPPbqm5nPeT4_uyzRO2_KRTW-8Or60kioS0,210
|
|
406
406
|
agno/tools/agentql.py,sha256=S82Z9aTNr-E5wnA4fbFs76COljJtiQIjf2grjz3CkHU,4104
|
|
407
407
|
agno/tools/airflow.py,sha256=uf2rOzZpSU64l_qRJ5Raku-R3Gky-uewmYkh6W0-oxg,2610
|
|
@@ -649,9 +649,9 @@ agno/workflow/router.py,sha256=s0fZ8CHu0Q4Ngygs7N4j-Lq1NHBqBmGExckBURWwZ4I,32340
|
|
|
649
649
|
agno/workflow/step.py,sha256=voTmWWihLKDAMeLgYoie96KLCiVpxPFrZoFHEMhA6QM,75046
|
|
650
650
|
agno/workflow/steps.py,sha256=1UySQjqwHlFHVhUL9r2hqy1K3esISjh2bvtClQk5PiI,27482
|
|
651
651
|
agno/workflow/types.py,sha256=t4304WCKB19QFdV3ixXZICcU8wtBza4EBCIz5Ve6MSQ,18035
|
|
652
|
-
agno/workflow/workflow.py,sha256=
|
|
653
|
-
agno-2.3.
|
|
654
|
-
agno-2.3.
|
|
655
|
-
agno-2.3.
|
|
656
|
-
agno-2.3.
|
|
657
|
-
agno-2.3.
|
|
652
|
+
agno/workflow/workflow.py,sha256=rcpABeUkJ6svT9esj2WxN-tGKobMSDbuLnzBz6s-Xp4,204812
|
|
653
|
+
agno-2.3.26.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
654
|
+
agno-2.3.26.dist-info/METADATA,sha256=EXxNtbLgnNxV7iDN5t2U5hOtmtRU-NiuWfSoE7vf-FE,24218
|
|
655
|
+
agno-2.3.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
656
|
+
agno-2.3.26.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
|
|
657
|
+
agno-2.3.26.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|