agno 2.0.8__py3-none-any.whl → 2.0.10__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 +13 -8
- agno/db/base.py +14 -0
- agno/db/dynamo/dynamo.py +107 -27
- agno/db/firestore/firestore.py +109 -33
- agno/db/gcs_json/gcs_json_db.py +100 -20
- agno/db/in_memory/in_memory_db.py +95 -20
- agno/db/json/json_db.py +101 -21
- agno/db/migrations/v1_to_v2.py +181 -35
- agno/db/mongo/mongo.py +251 -26
- agno/db/mysql/mysql.py +307 -6
- agno/db/postgres/postgres.py +279 -33
- agno/db/redis/redis.py +99 -22
- agno/db/singlestore/singlestore.py +319 -38
- agno/db/sqlite/sqlite.py +339 -23
- agno/models/anthropic/claude.py +0 -20
- agno/models/aws/claude.py +1 -1
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/ollama/chat.py +28 -2
- agno/models/openai/chat.py +7 -0
- agno/models/openai/responses.py +8 -8
- agno/os/interfaces/base.py +2 -0
- agno/os/interfaces/slack/router.py +50 -10
- agno/os/interfaces/slack/slack.py +6 -4
- agno/os/interfaces/whatsapp/router.py +7 -4
- agno/os/router.py +18 -0
- agno/os/utils.py +2 -2
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +2 -2
- agno/run/base.py +15 -2
- agno/team/team.py +10 -12
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/whatsapp.py +1 -1
- agno/utils/models/claude.py +2 -2
- agno/utils/print_response/agent.py +2 -2
- agno/utils/print_response/team.py +6 -6
- agno/utils/reasoning.py +22 -1
- agno/utils/string.py +9 -0
- agno/workflow/workflow.py +11 -7
- {agno-2.0.8.dist-info → agno-2.0.10.dist-info}/METADATA +4 -1
- {agno-2.0.8.dist-info → agno-2.0.10.dist-info}/RECORD +47 -47
- agno/utils/models/aws_claude.py +0 -170
- {agno-2.0.8.dist-info → agno-2.0.10.dist-info}/WHEEL +0 -0
- {agno-2.0.8.dist-info → agno-2.0.10.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.8.dist-info → agno-2.0.10.dist-info}/top_level.txt +0 -0
agno/models/openai/responses.py
CHANGED
|
@@ -406,25 +406,25 @@ class OpenAIResponses(Model):
|
|
|
406
406
|
|
|
407
407
|
messages_to_format = messages
|
|
408
408
|
previous_response_id: Optional[str] = None
|
|
409
|
-
|
|
409
|
+
|
|
410
410
|
if self._using_reasoning_model() and self.store is not False:
|
|
411
411
|
# Detect whether we're chaining via previous_response_id. If so, we should NOT
|
|
412
412
|
# re-send prior function_call items; the Responses API already has the state and
|
|
413
413
|
# expects only the corresponding function_call_output items.
|
|
414
|
-
|
|
414
|
+
|
|
415
415
|
for msg in reversed(messages):
|
|
416
416
|
if (
|
|
417
|
-
msg.role == "assistant"
|
|
418
|
-
and hasattr(msg, "provider_data")
|
|
419
|
-
and msg.provider_data
|
|
417
|
+
msg.role == "assistant"
|
|
418
|
+
and hasattr(msg, "provider_data")
|
|
419
|
+
and msg.provider_data
|
|
420
420
|
and "response_id" in msg.provider_data
|
|
421
421
|
):
|
|
422
422
|
previous_response_id = msg.provider_data["response_id"]
|
|
423
423
|
msg_index = messages.index(msg)
|
|
424
|
-
|
|
424
|
+
|
|
425
425
|
# Include messages after this assistant message
|
|
426
|
-
messages_to_format = messages[msg_index + 1:]
|
|
427
|
-
|
|
426
|
+
messages_to_format = messages[msg_index + 1 :]
|
|
427
|
+
|
|
428
428
|
break
|
|
429
429
|
|
|
430
430
|
# Build a mapping from function_call id (fc_*) → call_id (call_*) from prior assistant tool_calls
|
agno/os/interfaces/base.py
CHANGED
|
@@ -5,6 +5,7 @@ from fastapi import APIRouter
|
|
|
5
5
|
|
|
6
6
|
from agno.agent import Agent
|
|
7
7
|
from agno.team import Team
|
|
8
|
+
from agno.workflow.workflow import Workflow
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class BaseInterface(ABC):
|
|
@@ -13,6 +14,7 @@ class BaseInterface(ABC):
|
|
|
13
14
|
router_prefix: str = ""
|
|
14
15
|
agent: Optional[Agent] = None
|
|
15
16
|
team: Optional[Team] = None
|
|
17
|
+
workflow: Optional[Workflow] = None
|
|
16
18
|
|
|
17
19
|
router: APIRouter
|
|
18
20
|
|
|
@@ -1,16 +1,49 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
4
5
|
|
|
5
6
|
from agno.agent.agent import Agent
|
|
6
7
|
from agno.os.interfaces.slack.security import verify_slack_signature
|
|
7
8
|
from agno.team.team import Team
|
|
8
9
|
from agno.tools.slack import SlackTools
|
|
9
10
|
from agno.utils.log import log_info
|
|
11
|
+
from agno.workflow.workflow import Workflow
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
class SlackEventResponse(BaseModel):
|
|
15
|
+
"""Response model for Slack event processing"""
|
|
16
|
+
|
|
17
|
+
status: str = Field(default="ok", description="Processing status")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SlackChallengeResponse(BaseModel):
|
|
21
|
+
"""Response model for Slack URL verification challenge"""
|
|
22
|
+
|
|
23
|
+
challenge: str = Field(description="Challenge string to echo back to Slack")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def attach_routes(
|
|
27
|
+
router: APIRouter, agent: Optional[Agent] = None, team: Optional[Team] = None, workflow: Optional[Workflow] = None
|
|
28
|
+
) -> APIRouter:
|
|
29
|
+
# Determine entity type for documentation
|
|
30
|
+
entity_type = "agent" if agent else "team" if team else "workflow" if workflow else "unknown"
|
|
31
|
+
entity_name = getattr(agent or team or workflow, "name", f"Unnamed {entity_type}")
|
|
32
|
+
|
|
33
|
+
@router.post(
|
|
34
|
+
"/events",
|
|
35
|
+
operation_id=f"slack_events_{entity_type}",
|
|
36
|
+
summary=f"Process Slack Events for {entity_type.title()}",
|
|
37
|
+
description=f"Process incoming Slack events and route them to the configured {entity_type}: {entity_name}",
|
|
38
|
+
tags=["Slack", f"Slack-{entity_type.title()}"],
|
|
39
|
+
response_model=SlackEventResponse,
|
|
40
|
+
response_model_exclude_none=True,
|
|
41
|
+
responses={
|
|
42
|
+
200: {"description": "Event processed successfully"},
|
|
43
|
+
400: {"description": "Missing Slack headers"},
|
|
44
|
+
403: {"description": "Invalid Slack signature"},
|
|
45
|
+
},
|
|
46
|
+
)
|
|
14
47
|
async def slack_events(request: Request, background_tasks: BackgroundTasks):
|
|
15
48
|
body = await request.body()
|
|
16
49
|
timestamp = request.headers.get("X-Slack-Request-Timestamp")
|
|
@@ -26,7 +59,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
26
59
|
|
|
27
60
|
# Handle URL verification
|
|
28
61
|
if data.get("type") == "url_verification":
|
|
29
|
-
return
|
|
62
|
+
return SlackChallengeResponse(challenge=data.get("challenge"))
|
|
30
63
|
|
|
31
64
|
# Process other event types (e.g., message events) asynchronously
|
|
32
65
|
if "event" in data:
|
|
@@ -37,7 +70,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
37
70
|
else:
|
|
38
71
|
background_tasks.add_task(_process_slack_event, event)
|
|
39
72
|
|
|
40
|
-
return
|
|
73
|
+
return SlackEventResponse(status="ok")
|
|
41
74
|
|
|
42
75
|
async def _process_slack_event(event: dict):
|
|
43
76
|
if event.get("type") == "message":
|
|
@@ -57,12 +90,19 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
57
90
|
response = await agent.arun(message_text, user_id=user if user else None, session_id=session_id)
|
|
58
91
|
elif team:
|
|
59
92
|
response = await team.arun(message_text, user_id=user if user else None, session_id=session_id) # type: ignore
|
|
93
|
+
elif workflow:
|
|
94
|
+
response = await workflow.arun(message_text, user_id=user if user else None, session_id=session_id) # type: ignore
|
|
60
95
|
|
|
61
|
-
if response
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
if response:
|
|
97
|
+
if hasattr(response, "reasoning_content") and response.reasoning_content:
|
|
98
|
+
_send_slack_message(
|
|
99
|
+
channel=channel_id,
|
|
100
|
+
message=f"Reasoning: \n{response.reasoning_content}",
|
|
101
|
+
thread_ts=ts,
|
|
102
|
+
italics=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
_send_slack_message(channel=channel_id, message=response.content or "", thread_ts=ts)
|
|
66
106
|
|
|
67
107
|
def _send_slack_message(channel: str, thread_ts: str, message: str, italics: bool = False):
|
|
68
108
|
if len(message) <= 40000:
|
|
@@ -85,6 +125,6 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
85
125
|
formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
|
|
86
126
|
SlackTools().send_message_thread(channel=channel, text=formatted_batch or "", thread_ts=thread_ts)
|
|
87
127
|
else:
|
|
88
|
-
SlackTools().send_message_thread(channel=channel, text=
|
|
128
|
+
SlackTools().send_message_thread(channel=channel, text=batch_message or "", thread_ts=thread_ts)
|
|
89
129
|
|
|
90
130
|
return router
|
|
@@ -7,6 +7,7 @@ from agno.agent.agent import Agent
|
|
|
7
7
|
from agno.os.interfaces.base import BaseInterface
|
|
8
8
|
from agno.os.interfaces.slack.router import attach_routes
|
|
9
9
|
from agno.team.team import Team
|
|
10
|
+
from agno.workflow.workflow import Workflow
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
@@ -16,17 +17,18 @@ class Slack(BaseInterface):
|
|
|
16
17
|
|
|
17
18
|
router: APIRouter
|
|
18
19
|
|
|
19
|
-
def __init__(self, agent: Optional[Agent] = None, team: Optional[Team] = None):
|
|
20
|
+
def __init__(self, agent: Optional[Agent] = None, team: Optional[Team] = None, workflow: Optional[Workflow] = None):
|
|
20
21
|
self.agent = agent
|
|
21
22
|
self.team = team
|
|
23
|
+
self.workflow = workflow
|
|
22
24
|
|
|
23
|
-
if not (self.agent or self.team):
|
|
24
|
-
raise ValueError("Slack requires an agent or
|
|
25
|
+
if not (self.agent or self.team or self.workflow):
|
|
26
|
+
raise ValueError("Slack requires an agent, team or workflow")
|
|
25
27
|
|
|
26
28
|
def get_router(self, **kwargs) -> APIRouter:
|
|
27
29
|
# Cannot be overridden
|
|
28
30
|
self.router = APIRouter(prefix="/slack", tags=["Slack"])
|
|
29
31
|
|
|
30
|
-
self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
|
|
32
|
+
self.router = attach_routes(router=self.router, agent=self.agent, team=self.team, workflow=self.workflow)
|
|
31
33
|
|
|
32
34
|
return self.router
|
|
@@ -19,6 +19,9 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
19
19
|
if agent is None and team is None:
|
|
20
20
|
raise ValueError("Either agent or team must be provided.")
|
|
21
21
|
|
|
22
|
+
# Create WhatsApp tools instance once for reuse
|
|
23
|
+
whatsapp_tools = WhatsAppTools(async_mode=True)
|
|
24
|
+
|
|
22
25
|
@router.get("/status")
|
|
23
26
|
async def status():
|
|
24
27
|
return {"status": "available"}
|
|
@@ -185,9 +188,9 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
185
188
|
if italics:
|
|
186
189
|
# Handle multi-line messages by making each line italic
|
|
187
190
|
formatted_message = "\n".join([f"_{line}_" for line in message.split("\n")])
|
|
188
|
-
await
|
|
191
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_message)
|
|
189
192
|
else:
|
|
190
|
-
await
|
|
193
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=message)
|
|
191
194
|
return
|
|
192
195
|
|
|
193
196
|
# Split message into batches of 4000 characters (WhatsApp message limit is 4096)
|
|
@@ -199,8 +202,8 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
|
|
|
199
202
|
if italics:
|
|
200
203
|
# Handle multi-line messages by making each line italic
|
|
201
204
|
formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
|
|
202
|
-
await
|
|
205
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_batch)
|
|
203
206
|
else:
|
|
204
|
-
await
|
|
207
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=batch_message)
|
|
205
208
|
|
|
206
209
|
return router
|
agno/os/router.py
CHANGED
|
@@ -72,6 +72,24 @@ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict
|
|
|
72
72
|
sig = inspect.signature(endpoint_func)
|
|
73
73
|
known_fields = set(sig.parameters.keys())
|
|
74
74
|
kwargs = {key: value for key, value in form_data.items() if key not in known_fields}
|
|
75
|
+
|
|
76
|
+
# Handle JSON parameters. They are passed as strings and need to be deserialized.
|
|
77
|
+
|
|
78
|
+
if session_state := kwargs.get("session_state"):
|
|
79
|
+
try:
|
|
80
|
+
session_state_dict = json.loads(session_state) # type: ignore
|
|
81
|
+
kwargs["session_state"] = session_state_dict
|
|
82
|
+
except json.JSONDecodeError:
|
|
83
|
+
kwargs.pop("session_state")
|
|
84
|
+
log_warning(f"Invalid session_state parameter couldn't be loaded: {session_state}")
|
|
85
|
+
if dependencies := kwargs.get("dependencies"):
|
|
86
|
+
try:
|
|
87
|
+
dependencies_dict = json.loads(dependencies) # type: ignore
|
|
88
|
+
kwargs["dependencies"] = dependencies_dict
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
kwargs.pop("dependencies")
|
|
91
|
+
log_warning(f"Invalid dependencies parameter couldn't be loaded: {dependencies}")
|
|
92
|
+
|
|
75
93
|
return kwargs
|
|
76
94
|
|
|
77
95
|
|
agno/os/utils.py
CHANGED
|
@@ -57,7 +57,7 @@ def get_run_input(run_dict: Dict[str, Any], is_workflow_run: bool = False) -> st
|
|
|
57
57
|
if is_workflow_run:
|
|
58
58
|
step_executor_runs = run_dict.get("step_executor_runs", [])
|
|
59
59
|
if step_executor_runs:
|
|
60
|
-
for message in step_executor_runs[0].get("messages", []):
|
|
60
|
+
for message in reversed(step_executor_runs[0].get("messages", [])):
|
|
61
61
|
if message.get("role") == "user":
|
|
62
62
|
return message.get("content", "")
|
|
63
63
|
|
|
@@ -70,7 +70,7 @@ def get_run_input(run_dict: Dict[str, Any], is_workflow_run: bool = False) -> st
|
|
|
70
70
|
return str(input_value)
|
|
71
71
|
|
|
72
72
|
if run_dict.get("messages") is not None:
|
|
73
|
-
for message in run_dict["messages"]:
|
|
73
|
+
for message in reversed(run_dict["messages"]):
|
|
74
74
|
if message.get("role") == "user":
|
|
75
75
|
return message.get("content", "")
|
|
76
76
|
|
|
@@ -20,7 +20,7 @@ def get_ai_foundry_reasoning(reasoning_agent: "Agent", messages: List[Message])
|
|
|
20
20
|
from agno.run.agent import RunOutput
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
|
-
reasoning_agent_response: RunOutput = reasoning_agent.run(
|
|
23
|
+
reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
|
|
24
24
|
except Exception as e:
|
|
25
25
|
logger.warning(f"Reasoning error: {e}")
|
|
26
26
|
return None
|
|
@@ -46,7 +46,7 @@ async def aget_ai_foundry_reasoning(reasoning_agent: "Agent", messages: List[Mes
|
|
|
46
46
|
from agno.run.agent import RunOutput
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
|
-
reasoning_agent_response: RunOutput = await reasoning_agent.arun(
|
|
49
|
+
reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
|
|
50
50
|
except Exception as e:
|
|
51
51
|
logger.warning(f"Reasoning error: {e}")
|
|
52
52
|
return None
|
agno/reasoning/deepseek.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Message]) ->
|
|
|
20
20
|
message.role = "system"
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
|
-
reasoning_agent_response: RunOutput = reasoning_agent.run(
|
|
23
|
+
reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
|
|
24
24
|
except Exception as e:
|
|
25
25
|
logger.warning(f"Reasoning error: {e}")
|
|
26
26
|
return None
|
|
@@ -46,7 +46,7 @@ async def aget_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Messa
|
|
|
46
46
|
message.role = "system"
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
|
-
reasoning_agent_response: RunOutput = await reasoning_agent.arun(
|
|
49
|
+
reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
|
|
50
50
|
except Exception as e:
|
|
51
51
|
logger.warning(f"Reasoning error: {e}")
|
|
52
52
|
return None
|
agno/reasoning/groq.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_groq_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Opt
|
|
|
20
20
|
message.role = "system"
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
|
-
reasoning_agent_response: RunOutput = reasoning_agent.run(
|
|
23
|
+
reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
|
|
24
24
|
except Exception as e:
|
|
25
25
|
logger.warning(f"Reasoning error: {e}")
|
|
26
26
|
return None
|
|
@@ -50,7 +50,7 @@ async def aget_groq_reasoning(reasoning_agent: "Agent", messages: List[Message])
|
|
|
50
50
|
message.role = "system"
|
|
51
51
|
|
|
52
52
|
try:
|
|
53
|
-
reasoning_agent_response: RunOutput = await reasoning_agent.arun(
|
|
53
|
+
reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
|
|
54
54
|
except Exception as e:
|
|
55
55
|
logger.warning(f"Reasoning error: {e}")
|
|
56
56
|
return None
|
agno/reasoning/ollama.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_ollama_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> O
|
|
|
20
20
|
from agno.run.agent import RunOutput
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
|
-
reasoning_agent_response: RunOutput = reasoning_agent.run(
|
|
23
|
+
reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
|
|
24
24
|
except Exception as e:
|
|
25
25
|
logger.warning(f"Reasoning error: {e}")
|
|
26
26
|
return None
|
|
@@ -46,7 +46,7 @@ async def aget_ollama_reasoning(reasoning_agent: "Agent", messages: List[Message
|
|
|
46
46
|
from agno.run.agent import RunOutput
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
|
-
reasoning_agent_response: RunOutput = await reasoning_agent.arun(
|
|
49
|
+
reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
|
|
50
50
|
except Exception as e:
|
|
51
51
|
logger.warning(f"Reasoning error: {e}")
|
|
52
52
|
return None
|
agno/reasoning/openai.py
CHANGED
|
@@ -29,7 +29,7 @@ def get_openai_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> O
|
|
|
29
29
|
from agno.run.agent import RunOutput
|
|
30
30
|
|
|
31
31
|
try:
|
|
32
|
-
reasoning_agent_response: RunOutput = reasoning_agent.run(
|
|
32
|
+
reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
|
|
33
33
|
except Exception as e:
|
|
34
34
|
logger.warning(f"Reasoning error: {e}")
|
|
35
35
|
return None
|
|
@@ -60,7 +60,7 @@ async def aget_openai_reasoning(reasoning_agent: "Agent", messages: List[Message
|
|
|
60
60
|
message.role = "system"
|
|
61
61
|
|
|
62
62
|
try:
|
|
63
|
-
reasoning_agent_response: RunOutput = await reasoning_agent.arun(
|
|
63
|
+
reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
|
|
64
64
|
except Exception as e:
|
|
65
65
|
logger.warning(f"Reasoning error: {e}")
|
|
66
66
|
return None
|
agno/run/base.py
CHANGED
|
@@ -117,6 +117,19 @@ class BaseRunOutputEvent:
|
|
|
117
117
|
|
|
118
118
|
def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
|
|
119
119
|
import json
|
|
120
|
+
from datetime import date, datetime, time
|
|
121
|
+
from enum import Enum
|
|
122
|
+
|
|
123
|
+
def json_serializer(obj):
|
|
124
|
+
# Datetime like
|
|
125
|
+
if isinstance(obj, (datetime, date, time)):
|
|
126
|
+
return obj.isoformat()
|
|
127
|
+
# Enums
|
|
128
|
+
if isinstance(obj, Enum):
|
|
129
|
+
v = obj.value
|
|
130
|
+
return v if isinstance(v, (str, int, float, bool, type(None))) else obj.name
|
|
131
|
+
# Fallback to string
|
|
132
|
+
return str(obj)
|
|
120
133
|
|
|
121
134
|
try:
|
|
122
135
|
_dict = self.to_dict()
|
|
@@ -125,9 +138,9 @@ class BaseRunOutputEvent:
|
|
|
125
138
|
raise
|
|
126
139
|
|
|
127
140
|
if indent is None:
|
|
128
|
-
return json.dumps(_dict, separators=separators)
|
|
141
|
+
return json.dumps(_dict, separators=separators, default=json_serializer, ensure_ascii=False)
|
|
129
142
|
else:
|
|
130
|
-
return json.dumps(_dict, indent=indent, separators=separators)
|
|
143
|
+
return json.dumps(_dict, indent=indent, separators=separators, default=json_serializer, ensure_ascii=False)
|
|
131
144
|
|
|
132
145
|
@classmethod
|
|
133
146
|
def from_dict(cls, data: Dict[str, Any]):
|
agno/team/team.py
CHANGED
|
@@ -156,6 +156,8 @@ class Team:
|
|
|
156
156
|
add_session_state_to_context: bool = False
|
|
157
157
|
# Set to True to give the team tools to update the session_state dynamically
|
|
158
158
|
enable_agentic_state: bool = False
|
|
159
|
+
# Set to True to overwrite the stored session_state with the session_state provided in the run
|
|
160
|
+
overwrite_db_session_state: bool = False
|
|
159
161
|
# If True, cache the current Team session in memory for faster access
|
|
160
162
|
cache_session: bool = False
|
|
161
163
|
|
|
@@ -364,6 +366,7 @@ class Team:
|
|
|
364
366
|
session_state: Optional[Dict[str, Any]] = None,
|
|
365
367
|
add_session_state_to_context: bool = False,
|
|
366
368
|
enable_agentic_state: bool = False,
|
|
369
|
+
overwrite_db_session_state: bool = False,
|
|
367
370
|
resolve_in_context: bool = True,
|
|
368
371
|
cache_session: bool = False,
|
|
369
372
|
description: Optional[str] = None,
|
|
@@ -453,6 +456,7 @@ class Team:
|
|
|
453
456
|
self.session_state = session_state
|
|
454
457
|
self.add_session_state_to_context = add_session_state_to_context
|
|
455
458
|
self.enable_agentic_state = enable_agentic_state
|
|
459
|
+
self.overwrite_db_session_state = overwrite_db_session_state
|
|
456
460
|
self.resolve_in_context = resolve_in_context
|
|
457
461
|
self.cache_session = cache_session
|
|
458
462
|
|
|
@@ -1156,7 +1160,7 @@ class Team:
|
|
|
1156
1160
|
self._update_metadata(session=team_session)
|
|
1157
1161
|
|
|
1158
1162
|
# Update session state from DB
|
|
1159
|
-
session_state = self.
|
|
1163
|
+
session_state = self._load_session_state(session=team_session, session_state=session_state)
|
|
1160
1164
|
|
|
1161
1165
|
# Determine runtime dependencies
|
|
1162
1166
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
@@ -1780,7 +1784,7 @@ class Team:
|
|
|
1780
1784
|
self._update_metadata(session=team_session)
|
|
1781
1785
|
|
|
1782
1786
|
# Update session state from DB
|
|
1783
|
-
session_state = self.
|
|
1787
|
+
session_state = self._load_session_state(session=team_session, session_state=session_state)
|
|
1784
1788
|
|
|
1785
1789
|
# Determine run dependencies (runtime override takes priority)
|
|
1786
1790
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
@@ -2037,9 +2041,6 @@ class Team:
|
|
|
2037
2041
|
if model_response.audio is not None:
|
|
2038
2042
|
run_response.response_audio = model_response.audio
|
|
2039
2043
|
|
|
2040
|
-
# Update the run_response created_at with the model response created_at
|
|
2041
|
-
run_response.created_at = model_response.created_at
|
|
2042
|
-
|
|
2043
2044
|
# Build a list of messages that should be added to the RunOutput
|
|
2044
2045
|
messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
|
|
2045
2046
|
|
|
@@ -2099,7 +2100,6 @@ class Team:
|
|
|
2099
2100
|
)
|
|
2100
2101
|
|
|
2101
2102
|
# 3. Update TeamRunOutput
|
|
2102
|
-
run_response.created_at = full_model_response.created_at
|
|
2103
2103
|
if full_model_response.content is not None:
|
|
2104
2104
|
run_response.content = full_model_response.content
|
|
2105
2105
|
if full_model_response.reasoning_content is not None:
|
|
@@ -2189,7 +2189,6 @@ class Team:
|
|
|
2189
2189
|
run_response.content = full_model_response.parsed
|
|
2190
2190
|
|
|
2191
2191
|
# Update TeamRunOutput
|
|
2192
|
-
run_response.created_at = full_model_response.created_at
|
|
2193
2192
|
if full_model_response.content is not None:
|
|
2194
2193
|
run_response.content = full_model_response.content
|
|
2195
2194
|
if full_model_response.reasoning_content is not None:
|
|
@@ -2851,7 +2850,6 @@ class Team:
|
|
|
2851
2850
|
|
|
2852
2851
|
# Update the TeamRunResponse content
|
|
2853
2852
|
run_response.content = model_response.content
|
|
2854
|
-
run_response.created_at = model_response.created_at
|
|
2855
2853
|
|
|
2856
2854
|
if stream_intermediate_steps:
|
|
2857
2855
|
yield self._handle_event(create_team_output_model_response_completed_event(run_response), run_response)
|
|
@@ -2909,7 +2907,6 @@ class Team:
|
|
|
2909
2907
|
|
|
2910
2908
|
# Update the TeamRunResponse content
|
|
2911
2909
|
run_response.content = model_response.content
|
|
2912
|
-
run_response.created_at = model_response.created_at
|
|
2913
2910
|
|
|
2914
2911
|
if stream_intermediate_steps:
|
|
2915
2912
|
yield self._handle_event(create_team_output_model_response_completed_event(run_response), run_response)
|
|
@@ -4277,7 +4274,7 @@ class Team:
|
|
|
4277
4274
|
system_message_content += f"{indent * ' '} - Name: {member.name}\n"
|
|
4278
4275
|
if member.role is not None:
|
|
4279
4276
|
system_message_content += f"{indent * ' '} - Role: {member.role}\n"
|
|
4280
|
-
if member.tools
|
|
4277
|
+
if member.tools and self.add_member_tools_to_context:
|
|
4281
4278
|
system_message_content += f"{indent * ' '} - Member tools:\n"
|
|
4282
4279
|
for _tool in member.tools:
|
|
4283
4280
|
if isinstance(_tool, Toolkit):
|
|
@@ -6111,8 +6108,8 @@ class Team:
|
|
|
6111
6108
|
self._upsert_session(session=session)
|
|
6112
6109
|
log_debug(f"Created or updated TeamSession record: {session.session_id}")
|
|
6113
6110
|
|
|
6114
|
-
def
|
|
6115
|
-
"""Load the
|
|
6111
|
+
def _load_session_state(self, session: TeamSession, session_state: Dict[str, Any]) -> Dict[str, Any]:
|
|
6112
|
+
"""Load and return the stored session_state from the database, optionally merging it with the given one"""
|
|
6116
6113
|
|
|
6117
6114
|
from agno.utils.merge_dict import merge_dictionaries
|
|
6118
6115
|
|
|
@@ -6125,6 +6122,7 @@ class Team:
|
|
|
6125
6122
|
session_state_from_db is not None
|
|
6126
6123
|
and isinstance(session_state_from_db, dict)
|
|
6127
6124
|
and len(session_state_from_db) > 0
|
|
6125
|
+
and not self.overwrite_db_session_state
|
|
6128
6126
|
):
|
|
6129
6127
|
# This preserves precedence: run_params > db_state > agent_defaults
|
|
6130
6128
|
merged_state = session_state_from_db.copy()
|