agno 2.3.13__py3-none-any.whl → 2.3.15__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 +1149 -1392
- agno/db/migrations/manager.py +3 -3
- agno/eval/__init__.py +21 -8
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/models/anthropic/claude.py +9 -4
- agno/models/base.py +8 -4
- agno/models/metrics.py +12 -0
- agno/models/openai/chat.py +2 -0
- agno/models/openai/responses.py +2 -2
- agno/os/app.py +59 -2
- agno/os/auth.py +40 -3
- agno/os/interfaces/a2a/router.py +619 -9
- agno/os/interfaces/a2a/utils.py +31 -32
- agno/os/middleware/jwt.py +5 -5
- agno/os/router.py +1 -57
- agno/os/routers/agents/schema.py +14 -1
- agno/os/routers/database.py +150 -0
- agno/os/routers/teams/schema.py +14 -1
- agno/os/settings.py +3 -0
- agno/os/utils.py +61 -53
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +91 -1
- agno/reasoning/gemini.py +81 -1
- agno/reasoning/groq.py +103 -1
- agno/reasoning/manager.py +1244 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +113 -1
- agno/reasoning/vertexai.py +85 -1
- agno/run/agent.py +21 -0
- agno/run/base.py +20 -1
- agno/run/team.py +21 -0
- agno/session/team.py +0 -3
- agno/team/team.py +1211 -1445
- agno/tools/toolkit.py +119 -8
- agno/utils/events.py +99 -4
- agno/utils/hooks.py +4 -10
- agno/utils/print_response/agent.py +26 -0
- agno/utils/print_response/team.py +11 -0
- agno/utils/prompts.py +8 -6
- agno/utils/string.py +46 -0
- agno/utils/team.py +1 -1
- agno/vectordb/milvus/milvus.py +32 -3
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/METADATA +3 -2
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/RECORD +49 -47
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/WHEEL +0 -0
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/top_level.txt +0 -0
agno/os/utils.py
CHANGED
|
@@ -26,12 +26,21 @@ from agno.workflow.workflow import Workflow
|
|
|
26
26
|
|
|
27
27
|
async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[str, Any]:
|
|
28
28
|
"""Given a Request and an endpoint function, return a dictionary with all extra form data fields.
|
|
29
|
+
|
|
29
30
|
Args:
|
|
30
31
|
request: The FastAPI Request object
|
|
31
32
|
endpoint_func: The function exposing the endpoint that received the request
|
|
32
33
|
|
|
34
|
+
Supported form parameters:
|
|
35
|
+
- session_state: JSON string of session state dict
|
|
36
|
+
- dependencies: JSON string of dependencies dict
|
|
37
|
+
- metadata: JSON string of metadata dict
|
|
38
|
+
- knowledge_filters: JSON string of knowledge filters
|
|
39
|
+
- output_schema: JSON schema string (converted to Pydantic model by default)
|
|
40
|
+
- use_json_schema: If "true", keeps output_schema as dict instead of converting to Pydantic model
|
|
41
|
+
|
|
33
42
|
Returns:
|
|
34
|
-
A dictionary of kwargs
|
|
43
|
+
A dictionary of kwargs to pass to Agent/Team run methods
|
|
35
44
|
"""
|
|
36
45
|
import inspect
|
|
37
46
|
|
|
@@ -101,15 +110,25 @@ async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[
|
|
|
101
110
|
kwargs.pop("knowledge_filters")
|
|
102
111
|
log_warning(f"Invalid FilterExpr in knowledge_filters: {e}")
|
|
103
112
|
|
|
104
|
-
# Handle output_schema - convert JSON schema to
|
|
113
|
+
# Handle output_schema - convert JSON schema to Pydantic model or keep as dict
|
|
114
|
+
# use_json_schema is a control flag consumed here (not passed to Agent/Team)
|
|
115
|
+
# When true, output_schema stays as dict for direct JSON output
|
|
116
|
+
use_json_schema = kwargs.pop("use_json_schema", False)
|
|
117
|
+
if isinstance(use_json_schema, str):
|
|
118
|
+
use_json_schema = use_json_schema.lower() == "true"
|
|
119
|
+
|
|
105
120
|
if output_schema := kwargs.get("output_schema"):
|
|
106
121
|
try:
|
|
107
122
|
if isinstance(output_schema, str):
|
|
108
|
-
from agno.os.utils import json_schema_to_pydantic_model
|
|
109
|
-
|
|
110
123
|
schema_dict = json.loads(output_schema)
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
|
|
125
|
+
if use_json_schema:
|
|
126
|
+
# Keep as dict schema for direct JSON output
|
|
127
|
+
kwargs["output_schema"] = schema_dict
|
|
128
|
+
else:
|
|
129
|
+
# Convert to Pydantic model (default behavior)
|
|
130
|
+
dynamic_model = json_schema_to_pydantic_model(schema_dict)
|
|
131
|
+
kwargs["output_schema"] = dynamic_model
|
|
113
132
|
except json.JSONDecodeError:
|
|
114
133
|
kwargs.pop("output_schema")
|
|
115
134
|
log_warning(f"Invalid output_schema JSON: {output_schema}")
|
|
@@ -281,56 +300,45 @@ def get_session_name(session: Dict[str, Any]) -> str:
|
|
|
281
300
|
if session_data is not None and session_data.get("session_name") is not None:
|
|
282
301
|
return session_data["session_name"]
|
|
283
302
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
runs = session.get("runs", []) or []
|
|
287
|
-
|
|
288
|
-
# For teams, identify the first Team run and avoid using the first member's run
|
|
289
|
-
if session.get("session_type") == "team":
|
|
290
|
-
run = None
|
|
291
|
-
for r in runs:
|
|
292
|
-
# If agent_id is not present, it's a team run
|
|
293
|
-
if not r.get("agent_id"):
|
|
294
|
-
run = r
|
|
295
|
-
break
|
|
296
|
-
|
|
297
|
-
# Fallback to first run if no team run found
|
|
298
|
-
if run is None and runs:
|
|
299
|
-
run = runs[0]
|
|
300
|
-
|
|
301
|
-
elif session.get("session_type") == "workflow":
|
|
302
|
-
try:
|
|
303
|
-
workflow_run = runs[0]
|
|
304
|
-
workflow_input = workflow_run.get("input")
|
|
305
|
-
if isinstance(workflow_input, str):
|
|
306
|
-
return workflow_input
|
|
307
|
-
elif isinstance(workflow_input, dict):
|
|
308
|
-
try:
|
|
309
|
-
import json
|
|
310
|
-
|
|
311
|
-
return json.dumps(workflow_input)
|
|
312
|
-
except (TypeError, ValueError):
|
|
313
|
-
pass
|
|
314
|
-
|
|
315
|
-
workflow_name = session.get("workflow_data", {}).get("name")
|
|
316
|
-
return f"New {workflow_name} Session" if workflow_name else ""
|
|
317
|
-
except (KeyError, IndexError, TypeError):
|
|
318
|
-
return ""
|
|
319
|
-
|
|
320
|
-
# For agents, use the first run
|
|
321
|
-
else:
|
|
322
|
-
run = runs[0] if runs else None
|
|
303
|
+
runs = session.get("runs", []) or []
|
|
304
|
+
session_type = session.get("session_type")
|
|
323
305
|
|
|
324
|
-
|
|
306
|
+
# Handle workflows separately
|
|
307
|
+
if session_type == "workflow":
|
|
308
|
+
if not runs:
|
|
325
309
|
return ""
|
|
310
|
+
workflow_run = runs[0]
|
|
311
|
+
workflow_input = workflow_run.get("input")
|
|
312
|
+
if isinstance(workflow_input, str):
|
|
313
|
+
return workflow_input
|
|
314
|
+
elif isinstance(workflow_input, dict):
|
|
315
|
+
try:
|
|
316
|
+
return json.dumps(workflow_input)
|
|
317
|
+
except (TypeError, ValueError):
|
|
318
|
+
pass
|
|
319
|
+
workflow_name = session.get("workflow_data", {}).get("name")
|
|
320
|
+
return f"New {workflow_name} Session" if workflow_name else ""
|
|
321
|
+
|
|
322
|
+
# For team, filter to team runs (runs without agent_id); for agents, use all runs
|
|
323
|
+
if session_type == "team":
|
|
324
|
+
runs_to_check = [r for r in runs if not r.get("agent_id")]
|
|
325
|
+
else:
|
|
326
|
+
runs_to_check = runs
|
|
326
327
|
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
# Find the first user message across runs
|
|
329
|
+
for r in runs_to_check:
|
|
330
|
+
if r is None:
|
|
331
|
+
continue
|
|
332
|
+
run_dict = r if isinstance(r, dict) else r.to_dict()
|
|
333
|
+
|
|
334
|
+
for message in run_dict.get("messages") or []:
|
|
335
|
+
if message.get("role") == "user" and message.get("content"):
|
|
336
|
+
return message["content"]
|
|
337
|
+
|
|
338
|
+
run_input = r.get("input")
|
|
339
|
+
if run_input is not None:
|
|
340
|
+
return run_input.get("input_content")
|
|
329
341
|
|
|
330
|
-
if run and run.get("messages"):
|
|
331
|
-
for message in run["messages"]:
|
|
332
|
-
if message["role"] == "user":
|
|
333
|
-
return message["content"]
|
|
334
342
|
return ""
|
|
335
343
|
|
|
336
344
|
|
|
@@ -862,7 +870,7 @@ def _get_python_type_from_json_schema(field_schema: Dict[str, Any], field_name:
|
|
|
862
870
|
# Unknown or unspecified type - fallback to Any
|
|
863
871
|
if json_type:
|
|
864
872
|
logger.warning(f"Unknown JSON schema type '{json_type}' for field '{field_name}', using Any")
|
|
865
|
-
return Any
|
|
873
|
+
return Any # type: ignore
|
|
866
874
|
|
|
867
875
|
|
|
868
876
|
def json_schema_to_pydantic_model(schema: Dict[str, Any]) -> Type[BaseModel]:
|
agno/reasoning/anthropic.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -51,6 +51,48 @@ def get_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -
|
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def get_anthropic_reasoning_stream(
|
|
55
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
56
|
+
messages: List[Message],
|
|
57
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
58
|
+
"""
|
|
59
|
+
Stream reasoning content from Anthropic Claude model.
|
|
60
|
+
|
|
61
|
+
Yields:
|
|
62
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
63
|
+
- During streaming: (reasoning_content_delta, None)
|
|
64
|
+
- At the end: (None, final_message)
|
|
65
|
+
"""
|
|
66
|
+
from agno.run.agent import RunEvent
|
|
67
|
+
|
|
68
|
+
reasoning_content: str = ""
|
|
69
|
+
redacted_reasoning_content: Optional[str] = None
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
73
|
+
if hasattr(event, "event"):
|
|
74
|
+
if event.event == RunEvent.run_content:
|
|
75
|
+
# Stream reasoning content as it arrives
|
|
76
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
77
|
+
reasoning_content += event.reasoning_content
|
|
78
|
+
yield (event.reasoning_content, None)
|
|
79
|
+
elif event.event == RunEvent.run_completed:
|
|
80
|
+
pass
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f"Reasoning error: {e}")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Yield final message
|
|
86
|
+
if reasoning_content:
|
|
87
|
+
final_message = Message(
|
|
88
|
+
role="assistant",
|
|
89
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
90
|
+
reasoning_content=reasoning_content,
|
|
91
|
+
redacted_reasoning_content=redacted_reasoning_content,
|
|
92
|
+
)
|
|
93
|
+
yield (None, final_message)
|
|
94
|
+
|
|
95
|
+
|
|
54
96
|
async def aget_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
|
|
55
97
|
"""Get reasoning from an Anthropic Claude model asynchronously."""
|
|
56
98
|
from agno.run.agent import RunOutput
|
|
@@ -78,3 +120,45 @@ async def aget_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Mess
|
|
|
78
120
|
reasoning_content=reasoning_content,
|
|
79
121
|
redacted_reasoning_content=redacted_reasoning_content,
|
|
80
122
|
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def aget_anthropic_reasoning_stream(
|
|
126
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
127
|
+
messages: List[Message],
|
|
128
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
129
|
+
"""
|
|
130
|
+
Stream reasoning content from Anthropic Claude model asynchronously.
|
|
131
|
+
|
|
132
|
+
Yields:
|
|
133
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
134
|
+
- During streaming: (reasoning_content_delta, None)
|
|
135
|
+
- At the end: (None, final_message)
|
|
136
|
+
"""
|
|
137
|
+
from agno.run.agent import RunEvent
|
|
138
|
+
|
|
139
|
+
reasoning_content: str = ""
|
|
140
|
+
redacted_reasoning_content: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
144
|
+
if hasattr(event, "event"):
|
|
145
|
+
if event.event == RunEvent.run_content:
|
|
146
|
+
# Stream reasoning content as it arrives
|
|
147
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
148
|
+
reasoning_content += event.reasoning_content
|
|
149
|
+
yield (event.reasoning_content, None)
|
|
150
|
+
elif event.event == RunEvent.run_completed:
|
|
151
|
+
pass
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.warning(f"Reasoning error: {e}")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Yield final message
|
|
157
|
+
if reasoning_content:
|
|
158
|
+
final_message = Message(
|
|
159
|
+
role="assistant",
|
|
160
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
161
|
+
reasoning_content=reasoning_content,
|
|
162
|
+
redacted_reasoning_content=redacted_reasoning_content,
|
|
163
|
+
)
|
|
164
|
+
yield (None, final_message)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -65,3 +65,95 @@ async def aget_ai_foundry_reasoning(reasoning_agent: "Agent", messages: List[Mes
|
|
|
65
65
|
return Message(
|
|
66
66
|
role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
|
|
67
67
|
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_ai_foundry_reasoning_stream(
|
|
71
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
72
|
+
messages: List[Message],
|
|
73
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
74
|
+
"""
|
|
75
|
+
Stream reasoning content from Azure AI Foundry model.
|
|
76
|
+
|
|
77
|
+
For DeepSeek-R1 models, we use the main content output as reasoning content.
|
|
78
|
+
|
|
79
|
+
Yields:
|
|
80
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
81
|
+
- During streaming: (reasoning_content_delta, None)
|
|
82
|
+
- At the end: (None, final_message)
|
|
83
|
+
"""
|
|
84
|
+
from agno.run.agent import RunEvent
|
|
85
|
+
|
|
86
|
+
reasoning_content: str = ""
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
90
|
+
if hasattr(event, "event"):
|
|
91
|
+
if event.event == RunEvent.run_content:
|
|
92
|
+
# Check for reasoning_content attribute first (native reasoning)
|
|
93
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
94
|
+
reasoning_content += event.reasoning_content
|
|
95
|
+
yield (event.reasoning_content, None)
|
|
96
|
+
# Use the main content as reasoning content
|
|
97
|
+
elif hasattr(event, "content") and event.content:
|
|
98
|
+
reasoning_content += event.content
|
|
99
|
+
yield (event.content, None)
|
|
100
|
+
elif event.event == RunEvent.run_completed:
|
|
101
|
+
pass
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning(f"Reasoning error: {e}")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Yield final message
|
|
107
|
+
if reasoning_content:
|
|
108
|
+
final_message = Message(
|
|
109
|
+
role="assistant",
|
|
110
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
111
|
+
reasoning_content=reasoning_content,
|
|
112
|
+
)
|
|
113
|
+
yield (None, final_message)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def aget_ai_foundry_reasoning_stream(
|
|
117
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
118
|
+
messages: List[Message],
|
|
119
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
120
|
+
"""
|
|
121
|
+
Stream reasoning content from Azure AI Foundry model asynchronously.
|
|
122
|
+
|
|
123
|
+
For DeepSeek-R1 models, we use the main content output as reasoning content.
|
|
124
|
+
|
|
125
|
+
Yields:
|
|
126
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
127
|
+
- During streaming: (reasoning_content_delta, None)
|
|
128
|
+
- At the end: (None, final_message)
|
|
129
|
+
"""
|
|
130
|
+
from agno.run.agent import RunEvent
|
|
131
|
+
|
|
132
|
+
reasoning_content: str = ""
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
136
|
+
if hasattr(event, "event"):
|
|
137
|
+
if event.event == RunEvent.run_content:
|
|
138
|
+
# Check for reasoning_content attribute first (native reasoning)
|
|
139
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
140
|
+
reasoning_content += event.reasoning_content
|
|
141
|
+
yield (event.reasoning_content, None)
|
|
142
|
+
# Use the main content as reasoning content
|
|
143
|
+
elif hasattr(event, "content") and event.content:
|
|
144
|
+
reasoning_content += event.content
|
|
145
|
+
yield (event.content, None)
|
|
146
|
+
elif event.event == RunEvent.run_completed:
|
|
147
|
+
pass
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.warning(f"Reasoning error: {e}")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Yield final message
|
|
153
|
+
if reasoning_content:
|
|
154
|
+
final_message = Message(
|
|
155
|
+
role="assistant",
|
|
156
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
157
|
+
reasoning_content=reasoning_content,
|
|
158
|
+
)
|
|
159
|
+
yield (None, final_message)
|
agno/reasoning/deepseek.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -37,6 +37,51 @@ def get_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Message]) ->
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def get_deepseek_reasoning_stream(
|
|
41
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
42
|
+
messages: List[Message],
|
|
43
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
44
|
+
"""
|
|
45
|
+
Stream reasoning content from DeepSeek model.
|
|
46
|
+
|
|
47
|
+
Yields:
|
|
48
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
49
|
+
- During streaming: (reasoning_content_delta, None)
|
|
50
|
+
- At the end: (None, final_message)
|
|
51
|
+
"""
|
|
52
|
+
from agno.run.agent import RunEvent
|
|
53
|
+
|
|
54
|
+
# Update system message role to "system"
|
|
55
|
+
for message in messages:
|
|
56
|
+
if message.role == "developer":
|
|
57
|
+
message.role = "system"
|
|
58
|
+
|
|
59
|
+
reasoning_content: str = ""
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
63
|
+
if hasattr(event, "event"):
|
|
64
|
+
if event.event == RunEvent.run_content:
|
|
65
|
+
# Stream reasoning content as it arrives
|
|
66
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
67
|
+
reasoning_content += event.reasoning_content
|
|
68
|
+
yield (event.reasoning_content, None)
|
|
69
|
+
elif event.event == RunEvent.run_completed:
|
|
70
|
+
pass
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(f"Reasoning error: {e}")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# Yield final message
|
|
76
|
+
if reasoning_content:
|
|
77
|
+
final_message = Message(
|
|
78
|
+
role="assistant",
|
|
79
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
80
|
+
reasoning_content=reasoning_content,
|
|
81
|
+
)
|
|
82
|
+
yield (None, final_message)
|
|
83
|
+
|
|
84
|
+
|
|
40
85
|
async def aget_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
|
|
41
86
|
from agno.run.agent import RunOutput
|
|
42
87
|
|
|
@@ -61,3 +106,48 @@ async def aget_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Messa
|
|
|
61
106
|
return Message(
|
|
62
107
|
role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
|
|
63
108
|
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def aget_deepseek_reasoning_stream(
|
|
112
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
113
|
+
messages: List[Message],
|
|
114
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
115
|
+
"""
|
|
116
|
+
Stream reasoning content from DeepSeek model asynchronously.
|
|
117
|
+
|
|
118
|
+
Yields:
|
|
119
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
120
|
+
- During streaming: (reasoning_content_delta, None)
|
|
121
|
+
- At the end: (None, final_message)
|
|
122
|
+
"""
|
|
123
|
+
from agno.run.agent import RunEvent
|
|
124
|
+
|
|
125
|
+
# Update system message role to "system"
|
|
126
|
+
for message in messages:
|
|
127
|
+
if message.role == "developer":
|
|
128
|
+
message.role = "system"
|
|
129
|
+
|
|
130
|
+
reasoning_content: str = ""
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
134
|
+
if hasattr(event, "event"):
|
|
135
|
+
if event.event == RunEvent.run_content:
|
|
136
|
+
# Stream reasoning content as it arrives
|
|
137
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
138
|
+
reasoning_content += event.reasoning_content
|
|
139
|
+
yield (event.reasoning_content, None)
|
|
140
|
+
elif event.event == RunEvent.run_completed:
|
|
141
|
+
pass
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.warning(f"Reasoning error: {e}")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Yield final message
|
|
147
|
+
if reasoning_content:
|
|
148
|
+
final_message = Message(
|
|
149
|
+
role="assistant",
|
|
150
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
151
|
+
reasoning_content=reasoning_content,
|
|
152
|
+
)
|
|
153
|
+
yield (None, final_message)
|
agno/reasoning/gemini.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -71,3 +71,83 @@ async def aget_gemini_reasoning(reasoning_agent: "Agent", messages: List[Message
|
|
|
71
71
|
return Message(
|
|
72
72
|
role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
|
|
73
73
|
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_gemini_reasoning_stream(
|
|
77
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
78
|
+
messages: List[Message],
|
|
79
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
80
|
+
"""
|
|
81
|
+
Stream reasoning content from Gemini model.
|
|
82
|
+
|
|
83
|
+
Yields:
|
|
84
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
85
|
+
- During streaming: (reasoning_content_delta, None)
|
|
86
|
+
- At the end: (None, final_message)
|
|
87
|
+
"""
|
|
88
|
+
from agno.run.agent import RunEvent
|
|
89
|
+
|
|
90
|
+
reasoning_content: str = ""
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
94
|
+
if hasattr(event, "event"):
|
|
95
|
+
if event.event == RunEvent.run_content:
|
|
96
|
+
# Stream reasoning content as it arrives
|
|
97
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
98
|
+
reasoning_content += event.reasoning_content
|
|
99
|
+
yield (event.reasoning_content, None)
|
|
100
|
+
elif event.event == RunEvent.run_completed:
|
|
101
|
+
pass
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning(f"Reasoning error: {e}")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Yield final message
|
|
107
|
+
if reasoning_content:
|
|
108
|
+
final_message = Message(
|
|
109
|
+
role="assistant",
|
|
110
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
111
|
+
reasoning_content=reasoning_content,
|
|
112
|
+
)
|
|
113
|
+
yield (None, final_message)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def aget_gemini_reasoning_stream(
|
|
117
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
118
|
+
messages: List[Message],
|
|
119
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
120
|
+
"""
|
|
121
|
+
Stream reasoning content from Gemini model asynchronously.
|
|
122
|
+
|
|
123
|
+
Yields:
|
|
124
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
125
|
+
- During streaming: (reasoning_content_delta, None)
|
|
126
|
+
- At the end: (None, final_message)
|
|
127
|
+
"""
|
|
128
|
+
from agno.run.agent import RunEvent
|
|
129
|
+
|
|
130
|
+
reasoning_content: str = ""
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
134
|
+
if hasattr(event, "event"):
|
|
135
|
+
if event.event == RunEvent.run_content:
|
|
136
|
+
# Stream reasoning content as it arrives
|
|
137
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
138
|
+
reasoning_content += event.reasoning_content
|
|
139
|
+
yield (event.reasoning_content, None)
|
|
140
|
+
elif event.event == RunEvent.run_completed:
|
|
141
|
+
pass
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.warning(f"Reasoning error: {e}")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Yield final message
|
|
147
|
+
if reasoning_content:
|
|
148
|
+
final_message = Message(
|
|
149
|
+
role="assistant",
|
|
150
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
151
|
+
reasoning_content=reasoning_content,
|
|
152
|
+
)
|
|
153
|
+
yield (None, final_message)
|
agno/reasoning/groq.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -69,3 +69,105 @@ async def aget_groq_reasoning(reasoning_agent: "Agent", messages: List[Message])
|
|
|
69
69
|
return Message(
|
|
70
70
|
role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
|
|
71
71
|
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_groq_reasoning_stream(
|
|
75
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
76
|
+
messages: List[Message],
|
|
77
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
78
|
+
"""
|
|
79
|
+
Stream reasoning content from Groq model.
|
|
80
|
+
|
|
81
|
+
For DeepSeek models on Groq, we use the main content output as reasoning content.
|
|
82
|
+
|
|
83
|
+
Yields:
|
|
84
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
85
|
+
- During streaming: (reasoning_content_delta, None)
|
|
86
|
+
- At the end: (None, final_message)
|
|
87
|
+
"""
|
|
88
|
+
from agno.run.agent import RunEvent
|
|
89
|
+
|
|
90
|
+
# Update system message role to "system"
|
|
91
|
+
for message in messages:
|
|
92
|
+
if message.role == "developer":
|
|
93
|
+
message.role = "system"
|
|
94
|
+
|
|
95
|
+
reasoning_content: str = ""
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
99
|
+
if hasattr(event, "event"):
|
|
100
|
+
if event.event == RunEvent.run_content:
|
|
101
|
+
# Check for reasoning_content attribute first (native reasoning)
|
|
102
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
103
|
+
reasoning_content += event.reasoning_content
|
|
104
|
+
yield (event.reasoning_content, None)
|
|
105
|
+
# Use the main content as reasoning content
|
|
106
|
+
elif hasattr(event, "content") and event.content:
|
|
107
|
+
reasoning_content += event.content
|
|
108
|
+
yield (event.content, None)
|
|
109
|
+
elif event.event == RunEvent.run_completed:
|
|
110
|
+
pass
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.warning(f"Reasoning error: {e}")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Yield final message
|
|
116
|
+
if reasoning_content:
|
|
117
|
+
final_message = Message(
|
|
118
|
+
role="assistant",
|
|
119
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
120
|
+
reasoning_content=reasoning_content,
|
|
121
|
+
)
|
|
122
|
+
yield (None, final_message)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def aget_groq_reasoning_stream(
|
|
126
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
127
|
+
messages: List[Message],
|
|
128
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
129
|
+
"""
|
|
130
|
+
Stream reasoning content from Groq model asynchronously.
|
|
131
|
+
|
|
132
|
+
For DeepSeek models on Groq, we use the main content output as reasoning content.
|
|
133
|
+
|
|
134
|
+
Yields:
|
|
135
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
136
|
+
- During streaming: (reasoning_content_delta, None)
|
|
137
|
+
- At the end: (None, final_message)
|
|
138
|
+
"""
|
|
139
|
+
from agno.run.agent import RunEvent
|
|
140
|
+
|
|
141
|
+
# Update system message role to "system"
|
|
142
|
+
for message in messages:
|
|
143
|
+
if message.role == "developer":
|
|
144
|
+
message.role = "system"
|
|
145
|
+
|
|
146
|
+
reasoning_content: str = ""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
150
|
+
if hasattr(event, "event"):
|
|
151
|
+
if event.event == RunEvent.run_content:
|
|
152
|
+
# Check for reasoning_content attribute first (native reasoning)
|
|
153
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
154
|
+
reasoning_content += event.reasoning_content
|
|
155
|
+
yield (event.reasoning_content, None)
|
|
156
|
+
# Use the main content as reasoning content
|
|
157
|
+
elif hasattr(event, "content") and event.content:
|
|
158
|
+
reasoning_content += event.content
|
|
159
|
+
yield (event.content, None)
|
|
160
|
+
elif event.event == RunEvent.run_completed:
|
|
161
|
+
pass
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.warning(f"Reasoning error: {e}")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
# Yield final message
|
|
167
|
+
if reasoning_content:
|
|
168
|
+
final_message = Message(
|
|
169
|
+
role="assistant",
|
|
170
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
171
|
+
reasoning_content=reasoning_content,
|
|
172
|
+
)
|
|
173
|
+
yield (None, final_message)
|