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.
Files changed (49) hide show
  1. agno/agent/agent.py +1149 -1392
  2. agno/db/migrations/manager.py +3 -3
  3. agno/eval/__init__.py +21 -8
  4. agno/knowledge/embedder/azure_openai.py +0 -1
  5. agno/knowledge/embedder/google.py +1 -1
  6. agno/models/anthropic/claude.py +9 -4
  7. agno/models/base.py +8 -4
  8. agno/models/metrics.py +12 -0
  9. agno/models/openai/chat.py +2 -0
  10. agno/models/openai/responses.py +2 -2
  11. agno/os/app.py +59 -2
  12. agno/os/auth.py +40 -3
  13. agno/os/interfaces/a2a/router.py +619 -9
  14. agno/os/interfaces/a2a/utils.py +31 -32
  15. agno/os/middleware/jwt.py +5 -5
  16. agno/os/router.py +1 -57
  17. agno/os/routers/agents/schema.py +14 -1
  18. agno/os/routers/database.py +150 -0
  19. agno/os/routers/teams/schema.py +14 -1
  20. agno/os/settings.py +3 -0
  21. agno/os/utils.py +61 -53
  22. agno/reasoning/anthropic.py +85 -1
  23. agno/reasoning/azure_ai_foundry.py +93 -1
  24. agno/reasoning/deepseek.py +91 -1
  25. agno/reasoning/gemini.py +81 -1
  26. agno/reasoning/groq.py +103 -1
  27. agno/reasoning/manager.py +1244 -0
  28. agno/reasoning/ollama.py +93 -1
  29. agno/reasoning/openai.py +113 -1
  30. agno/reasoning/vertexai.py +85 -1
  31. agno/run/agent.py +21 -0
  32. agno/run/base.py +20 -1
  33. agno/run/team.py +21 -0
  34. agno/session/team.py +0 -3
  35. agno/team/team.py +1211 -1445
  36. agno/tools/toolkit.py +119 -8
  37. agno/utils/events.py +99 -4
  38. agno/utils/hooks.py +4 -10
  39. agno/utils/print_response/agent.py +26 -0
  40. agno/utils/print_response/team.py +11 -0
  41. agno/utils/prompts.py +8 -6
  42. agno/utils/string.py +46 -0
  43. agno/utils/team.py +1 -1
  44. agno/vectordb/milvus/milvus.py +32 -3
  45. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/METADATA +3 -2
  46. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/RECORD +49 -47
  47. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/WHEEL +0 -0
  48. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/licenses/LICENSE +0 -0
  49. {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 dynamic Pydantic model
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
- dynamic_model = json_schema_to_pydantic_model(schema_dict)
112
- kwargs["output_schema"] = dynamic_model
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
- # Otherwise use the original user message
285
- else:
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
- if run is None:
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
- if not isinstance(run, dict):
328
- run = run.to_dict()
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]:
@@ -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)
@@ -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)