agno 2.0.4__py3-none-any.whl → 2.0.5__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 (41) hide show
  1. agno/agent/agent.py +74 -85
  2. agno/db/dynamo/dynamo.py +2 -2
  3. agno/db/firestore/firestore.py +3 -2
  4. agno/db/gcs_json/gcs_json_db.py +2 -2
  5. agno/db/json/json_db.py +2 -2
  6. agno/db/migrations/v1_to_v2.py +191 -23
  7. agno/db/mongo/mongo.py +61 -2
  8. agno/db/mysql/mysql.py +5 -5
  9. agno/db/mysql/schemas.py +27 -27
  10. agno/db/postgres/postgres.py +5 -5
  11. agno/db/redis/redis.py +2 -2
  12. agno/db/singlestore/singlestore.py +2 -2
  13. agno/db/sqlite/sqlite.py +6 -5
  14. agno/db/utils.py +0 -14
  15. agno/integrations/discord/client.py +1 -0
  16. agno/knowledge/knowledge.py +7 -7
  17. agno/knowledge/reader/reader_factory.py +7 -3
  18. agno/knowledge/reader/web_search_reader.py +12 -6
  19. agno/models/message.py +109 -0
  20. agno/models/openai/responses.py +6 -0
  21. agno/os/app.py +162 -42
  22. agno/os/interfaces/agui/utils.py +98 -134
  23. agno/os/routers/health.py +0 -1
  24. agno/os/routers/home.py +52 -0
  25. agno/os/routers/knowledge/knowledge.py +2 -2
  26. agno/os/schema.py +21 -0
  27. agno/os/utils.py +0 -8
  28. agno/run/agent.py +3 -3
  29. agno/run/team.py +3 -3
  30. agno/team/team.py +33 -38
  31. agno/tools/duckduckgo.py +15 -11
  32. agno/tools/googlesearch.py +1 -1
  33. agno/utils/string.py +32 -0
  34. agno/utils/tools.py +1 -1
  35. agno/workflow/step.py +4 -3
  36. {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/METADATA +6 -5
  37. {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/RECORD +40 -40
  38. agno/knowledge/reader/url_reader.py +0 -128
  39. {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/WHEEL +0 -0
  40. {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/licenses/LICENSE +0 -0
  41. {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/top_level.txt +0 -0
@@ -2,10 +2,9 @@
2
2
 
3
3
  import json
4
4
  import uuid
5
- from collections import deque
6
5
  from collections.abc import Iterator
7
6
  from dataclasses import dataclass
8
- from typing import AsyncIterator, Deque, List, Optional, Set, Tuple, Union
7
+ from typing import AsyncIterator, List, Set, Tuple, Union
9
8
 
10
9
  from ag_ui.core import (
11
10
  BaseEvent,
@@ -34,39 +33,22 @@ from agno.utils.message import get_text_from_message
34
33
  class EventBuffer:
35
34
  """Buffer to manage event ordering constraints, relevant when mapping Agno responses to AG-UI events."""
36
35
 
37
- buffer: Deque[BaseEvent]
38
- blocking_tool_call_id: Optional[str] # The tool call that's currently blocking the buffer
39
36
  active_tool_call_ids: Set[str] # All currently active tool calls
40
37
  ended_tool_call_ids: Set[str] # All tool calls that have ended
41
38
 
42
39
  def __init__(self):
43
- self.buffer = deque()
44
- self.blocking_tool_call_id = None
45
40
  self.active_tool_call_ids = set()
46
41
  self.ended_tool_call_ids = set()
47
42
 
48
- def is_blocked(self) -> bool:
49
- """Check if the buffer is currently blocked by an active tool call."""
50
- return self.blocking_tool_call_id is not None
51
-
52
43
  def start_tool_call(self, tool_call_id: str) -> None:
53
- """Start a new tool call, marking it the current blocking tool call if needed."""
44
+ """Start a new tool call."""
54
45
  self.active_tool_call_ids.add(tool_call_id)
55
- if self.blocking_tool_call_id is None:
56
- self.blocking_tool_call_id = tool_call_id
57
46
 
58
- def end_tool_call(self, tool_call_id: str) -> bool:
59
- """End a tool call, marking it as ended and unblocking the buffer if needed."""
47
+ def end_tool_call(self, tool_call_id: str) -> None:
48
+ """End a tool call."""
60
49
  self.active_tool_call_ids.discard(tool_call_id)
61
50
  self.ended_tool_call_ids.add(tool_call_id)
62
51
 
63
- # Unblock the buffer if the current blocking tool call is the one ending
64
- if tool_call_id == self.blocking_tool_call_id:
65
- self.blocking_tool_call_id = None
66
- return True
67
-
68
- return False
69
-
70
52
 
71
53
  def convert_agui_messages_to_agno_messages(messages: List[AGUIMessage]) -> List[Message]:
72
54
  """Convert AG-UI messages to Agno messages."""
@@ -169,6 +151,12 @@ def _create_events_from_chunk(
169
151
 
170
152
  # Handle starting a new tool call
171
153
  elif chunk.event == RunEvent.tool_call_started:
154
+ # End the current text message if one is active before starting tool calls
155
+ if message_started:
156
+ end_message_event = TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)
157
+ events_to_emit.append(end_message_event)
158
+ message_started = False # Reset message_started state
159
+
172
160
  if chunk.tool is not None: # type: ignore
173
161
  tool_call = chunk.tool # type: ignore
174
162
  start_event = ToolCallStartEvent(
@@ -195,7 +183,7 @@ def _create_events_from_chunk(
195
183
  type=EventType.TOOL_CALL_END,
196
184
  tool_call_id=tool_call.tool_call_id, # type: ignore
197
185
  )
198
- events_to_emit.append(end_event) # type: ignore
186
+ events_to_emit.append(end_event)
199
187
 
200
188
  if tool_call.result is not None:
201
189
  result_event = ToolCallResultEvent(
@@ -205,27 +193,17 @@ def _create_events_from_chunk(
205
193
  role="tool",
206
194
  message_id=str(uuid.uuid4()),
207
195
  )
208
- events_to_emit.append(result_event) # type: ignore
209
-
210
- if tool_call.result is not None:
211
- result_event = ToolCallResultEvent(
212
- type=EventType.TOOL_CALL_RESULT,
213
- tool_call_id=tool_call.tool_call_id, # type: ignore
214
- content=str(tool_call.result),
215
- role="tool",
216
- message_id=str(uuid.uuid4()),
217
- )
218
- events_to_emit.append(result_event) # type: ignore
196
+ events_to_emit.append(result_event)
219
197
 
220
198
  # Handle reasoning
221
199
  elif chunk.event == RunEvent.reasoning_started:
222
- step_started_event = StepStartedEvent(type=EventType.STEP_STARTED, step_name="reasoning") # type: ignore
223
- events_to_emit.append(step_started_event) # type: ignore
200
+ step_started_event = StepStartedEvent(type=EventType.STEP_STARTED, step_name="reasoning")
201
+ events_to_emit.append(step_started_event)
224
202
  elif chunk.event == RunEvent.reasoning_completed:
225
- step_started_event = StepFinishedEvent(type=EventType.STEP_FINISHED, step_name="reasoning") # type: ignore
226
- events_to_emit.append(step_started_event) # type: ignore
203
+ step_finished_event = StepFinishedEvent(type=EventType.STEP_FINISHED, step_name="reasoning")
204
+ events_to_emit.append(step_finished_event)
227
205
 
228
- return events_to_emit, message_started # type: ignore
206
+ return events_to_emit, message_started
229
207
 
230
208
 
231
209
  def _create_completion_events(
@@ -251,7 +229,7 @@ def _create_completion_events(
251
229
  # End the message and run, denoting the end of the session
252
230
  if message_started:
253
231
  end_message_event = TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)
254
- events_to_emit.append(end_message_event) # type: ignore
232
+ events_to_emit.append(end_message_event)
255
233
 
256
234
  # emit frontend tool calls, i.e. external_execution=True
257
235
  if isinstance(chunk, RunPausedEvent) and chunk.tools is not None:
@@ -265,14 +243,14 @@ def _create_completion_events(
265
243
  tool_call_name=tool.tool_name,
266
244
  parent_message_id=message_id,
267
245
  )
268
- events_to_emit.append(start_event) # type: ignore
246
+ events_to_emit.append(start_event)
269
247
 
270
248
  args_event = ToolCallArgsEvent(
271
249
  type=EventType.TOOL_CALL_ARGS,
272
250
  tool_call_id=tool.tool_call_id,
273
251
  delta=json.dumps(tool.tool_args),
274
252
  )
275
- events_to_emit.append(args_event) # type: ignore
253
+ events_to_emit.append(args_event)
276
254
 
277
255
  end_event = ToolCallEndEvent(
278
256
  type=EventType.TOOL_CALL_END,
@@ -280,85 +258,25 @@ def _create_completion_events(
280
258
  )
281
259
  events_to_emit.append(end_event)
282
260
 
283
- # emit frontend tool calls, i.e. external_execution=True
284
- if isinstance(chunk, RunPausedEvent) and chunk.tools is not None:
285
- for tool in chunk.tools:
286
- if tool.tool_call_id is None or tool.tool_name is None:
287
- continue
288
-
289
- start_event = ToolCallStartEvent(
290
- type=EventType.TOOL_CALL_START,
291
- tool_call_id=tool.tool_call_id,
292
- tool_call_name=tool.tool_name,
293
- parent_message_id=message_id,
294
- )
295
- events_to_emit.append(start_event) # type: ignore
296
-
297
- args_event = ToolCallArgsEvent(
298
- type=EventType.TOOL_CALL_ARGS,
299
- tool_call_id=tool.tool_call_id,
300
- delta=json.dumps(tool.tool_args),
301
- )
302
- events_to_emit.append(args_event) # type: ignore
303
-
304
- end_event = ToolCallEndEvent(
305
- type=EventType.TOOL_CALL_END,
306
- tool_call_id=tool.tool_call_id,
307
- )
308
- events_to_emit.append(end_event) # type: ignore
309
-
310
261
  run_finished_event = RunFinishedEvent(type=EventType.RUN_FINISHED, thread_id=thread_id, run_id=run_id)
311
- events_to_emit.append(run_finished_event) # type: ignore
262
+ events_to_emit.append(run_finished_event)
312
263
 
313
- return events_to_emit # type: ignore
264
+ return events_to_emit
314
265
 
315
266
 
316
267
  def _emit_event_logic(event: BaseEvent, event_buffer: EventBuffer) -> List[BaseEvent]:
317
- """Process an event through the buffer and return events to actually emit."""
318
- events_to_emit: List[BaseEvent] = []
319
-
320
- if event_buffer.is_blocked():
321
- # Handle events related to the current blocking tool call
322
- if event.type == EventType.TOOL_CALL_ARGS:
323
- if hasattr(event, "tool_call_id") and event.tool_call_id in event_buffer.active_tool_call_ids: # type: ignore
324
- events_to_emit.append(event)
325
- else:
326
- event_buffer.buffer.append(event)
327
- elif event.type == EventType.TOOL_CALL_END:
328
- tool_call_id = getattr(event, "tool_call_id", None)
329
- if tool_call_id and tool_call_id == event_buffer.blocking_tool_call_id:
330
- events_to_emit.append(event)
331
- event_buffer.end_tool_call(tool_call_id)
332
- # Flush buffered events after ending the blocking tool call
333
- while event_buffer.buffer:
334
- buffered_event = event_buffer.buffer.popleft()
335
- # Recursively process buffered events
336
- nested_events = _emit_event_logic(buffered_event, event_buffer)
337
- events_to_emit.extend(nested_events)
338
- elif tool_call_id and tool_call_id in event_buffer.active_tool_call_ids:
339
- event_buffer.buffer.append(event)
340
- event_buffer.end_tool_call(tool_call_id)
341
- else:
342
- event_buffer.buffer.append(event)
343
- # Handle all other events
344
- elif event.type == EventType.TOOL_CALL_START:
345
- event_buffer.buffer.append(event)
346
- else:
347
- event_buffer.buffer.append(event)
348
- # If the buffer is not blocked, emit the events normally
349
- else:
350
- if event.type == EventType.TOOL_CALL_START:
351
- tool_call_id = getattr(event, "tool_call_id", None)
352
- if tool_call_id:
353
- event_buffer.start_tool_call(tool_call_id)
354
- events_to_emit.append(event)
355
- elif event.type == EventType.TOOL_CALL_END:
356
- tool_call_id = getattr(event, "tool_call_id", None)
357
- if tool_call_id:
358
- event_buffer.end_tool_call(tool_call_id)
359
- events_to_emit.append(event)
360
- else:
361
- events_to_emit.append(event)
268
+ """Process an event and return events to actually emit."""
269
+ events_to_emit: List[BaseEvent] = [event]
270
+
271
+ # Update the event buffer state for tracking purposes
272
+ if event.type == EventType.TOOL_CALL_START:
273
+ tool_call_id = getattr(event, "tool_call_id", None)
274
+ if tool_call_id:
275
+ event_buffer.start_tool_call(tool_call_id)
276
+ elif event.type == EventType.TOOL_CALL_END:
277
+ tool_call_id = getattr(event, "tool_call_id", None)
278
+ if tool_call_id:
279
+ event_buffer.end_tool_call(tool_call_id)
362
280
 
363
281
  return events_to_emit
364
282
 
@@ -370,23 +288,22 @@ def stream_agno_response_as_agui_events(
370
288
  message_id = str(uuid.uuid4())
371
289
  message_started = False
372
290
  event_buffer = EventBuffer()
291
+ stream_completed = False
292
+
293
+ completion_chunk = None
373
294
 
374
295
  for chunk in response_stream:
375
- # Handle the lifecycle end event
296
+ # Check if this is a completion event
376
297
  if (
377
298
  chunk.event == RunEvent.run_completed
378
299
  or chunk.event == TeamRunEvent.run_completed
379
300
  or chunk.event == RunEvent.run_paused
380
301
  ):
381
- completion_events = _create_completion_events(
382
- chunk, event_buffer, message_started, message_id, thread_id, run_id
383
- )
384
- for event in completion_events:
385
- events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
386
- for emit_event in events_to_emit:
387
- yield emit_event
302
+ # Store completion chunk but don't process it yet
303
+ completion_chunk = chunk
304
+ stream_completed = True
388
305
  else:
389
- # Process regular chunk
306
+ # Process regular chunk immediately
390
307
  events_from_chunk, message_started = _create_events_from_chunk(
391
308
  chunk, message_id, message_started, event_buffer
392
309
  )
@@ -396,6 +313,30 @@ def stream_agno_response_as_agui_events(
396
313
  for emit_event in events_to_emit:
397
314
  yield emit_event
398
315
 
316
+ # Process ONLY completion cleanup events, not content from completion chunk
317
+ if completion_chunk:
318
+ completion_events = _create_completion_events(
319
+ completion_chunk, event_buffer, message_started, message_id, thread_id, run_id
320
+ )
321
+ for event in completion_events:
322
+ events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
323
+ for emit_event in events_to_emit:
324
+ yield emit_event
325
+
326
+ # Ensure completion events are always emitted even when stream ends naturally
327
+ if not stream_completed:
328
+ # Create a synthetic completion event to ensure proper cleanup
329
+ from agno.run.agent import RunCompletedEvent
330
+
331
+ synthetic_completion = RunCompletedEvent()
332
+ completion_events = _create_completion_events(
333
+ synthetic_completion, event_buffer, message_started, message_id, thread_id, run_id
334
+ )
335
+ for event in completion_events:
336
+ events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
337
+ for emit_event in events_to_emit:
338
+ yield emit_event
339
+
399
340
 
400
341
  # Async version - thin wrapper
401
342
  async def async_stream_agno_response_as_agui_events(
@@ -407,23 +348,22 @@ async def async_stream_agno_response_as_agui_events(
407
348
  message_id = str(uuid.uuid4())
408
349
  message_started = False
409
350
  event_buffer = EventBuffer()
351
+ stream_completed = False
352
+
353
+ completion_chunk = None
410
354
 
411
355
  async for chunk in response_stream:
412
- # Handle the lifecycle end event
356
+ # Check if this is a completion event
413
357
  if (
414
358
  chunk.event == RunEvent.run_completed
415
359
  or chunk.event == TeamRunEvent.run_completed
416
360
  or chunk.event == RunEvent.run_paused
417
361
  ):
418
- completion_events = _create_completion_events(
419
- chunk, event_buffer, message_started, message_id, thread_id, run_id
420
- )
421
- for event in completion_events:
422
- events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
423
- for emit_event in events_to_emit:
424
- yield emit_event
362
+ # Store completion chunk but don't process it yet
363
+ completion_chunk = chunk
364
+ stream_completed = True
425
365
  else:
426
- # Process regular chunk
366
+ # Process regular chunk immediately
427
367
  events_from_chunk, message_started = _create_events_from_chunk(
428
368
  chunk, message_id, message_started, event_buffer
429
369
  )
@@ -432,3 +372,27 @@ async def async_stream_agno_response_as_agui_events(
432
372
  events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
433
373
  for emit_event in events_to_emit:
434
374
  yield emit_event
375
+
376
+ # Process ONLY completion cleanup events, not content from completion chunk
377
+ if completion_chunk:
378
+ completion_events = _create_completion_events(
379
+ completion_chunk, event_buffer, message_started, message_id, thread_id, run_id
380
+ )
381
+ for event in completion_events:
382
+ events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
383
+ for emit_event in events_to_emit:
384
+ yield emit_event
385
+
386
+ # Ensure completion events are always emitted even when stream ends naturally
387
+ if not stream_completed:
388
+ # Create a synthetic completion event to ensure proper cleanup
389
+ from agno.run.agent import RunCompletedEvent
390
+
391
+ synthetic_completion = RunCompletedEvent()
392
+ completion_events = _create_completion_events(
393
+ synthetic_completion, event_buffer, message_started, message_id, thread_id, run_id
394
+ )
395
+ for event in completion_events:
396
+ events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
397
+ for emit_event in events_to_emit:
398
+ yield emit_event
agno/os/routers/health.py CHANGED
@@ -8,7 +8,6 @@ def get_health_router() -> APIRouter:
8
8
 
9
9
  @router.get(
10
10
  "/health",
11
- tags=["Core"],
12
11
  operation_id="health_check",
13
12
  summary="Health Check",
14
13
  description="Check the health status of the AgentOS API. Returns a simple status indicator.",
@@ -0,0 +1,52 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from fastapi import APIRouter
4
+
5
+ if TYPE_CHECKING:
6
+ from agno.os.app import AgentOS
7
+
8
+
9
+ def get_home_router(os: "AgentOS") -> APIRouter:
10
+ router = APIRouter(tags=["Home"])
11
+
12
+ @router.get(
13
+ "/",
14
+ operation_id="get_api_info",
15
+ summary="API Information",
16
+ description=(
17
+ "Get basic information about this AgentOS API instance, including:\n\n"
18
+ "- API metadata and version\n"
19
+ "- Available capabilities overview\n"
20
+ "- Links to key endpoints and documentation"
21
+ ),
22
+ responses={
23
+ 200: {
24
+ "description": "API information retrieved successfully",
25
+ "content": {
26
+ "application/json": {
27
+ "examples": {
28
+ "home": {
29
+ "summary": "Example home response",
30
+ "value": {
31
+ "name": "AgentOS API",
32
+ "description": "AI Agent Operating System API",
33
+ "os_id": "demo-os",
34
+ "version": "1.0.0",
35
+ },
36
+ }
37
+ }
38
+ }
39
+ },
40
+ }
41
+ },
42
+ )
43
+ async def get_api_info():
44
+ """Get basic API information and available capabilities"""
45
+ return {
46
+ "name": "AgentOS API",
47
+ "description": os.description or "AI Agent Operating System API",
48
+ "os_id": os.os_id or "agno-agentos",
49
+ "version": os.version or "1.0.0",
50
+ }
51
+
52
+ return router
@@ -5,7 +5,6 @@ from typing import Dict, List, Optional
5
5
 
6
6
  from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
7
7
 
8
- from agno.db.utils import generate_deterministic_id
9
8
  from agno.knowledge.content import Content, FileData
10
9
  from agno.knowledge.knowledge import Knowledge
11
10
  from agno.knowledge.reader import ReaderFactory
@@ -34,6 +33,7 @@ from agno.os.schema import (
34
33
  from agno.os.settings import AgnoAPISettings
35
34
  from agno.os.utils import get_knowledge_instance_by_db_id
36
35
  from agno.utils.log import log_debug, log_info
36
+ from agno.utils.string import generate_id
37
37
 
38
38
  logger = logging.getLogger(__name__)
39
39
 
@@ -167,7 +167,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
167
167
  )
168
168
  content_hash = knowledge._build_content_hash(content)
169
169
  content.content_hash = content_hash
170
- content.id = generate_deterministic_id(content_hash)
170
+ content.id = generate_id(content_hash)
171
171
 
172
172
  background_tasks.add_task(process_content, knowledge, content, reader_id, chunker)
173
173
 
agno/os/schema.py CHANGED
@@ -765,6 +765,7 @@ class TeamSessionDetailSchema(BaseModel):
765
765
  team_data: Optional[dict]
766
766
  created_at: Optional[datetime]
767
767
  updated_at: Optional[datetime]
768
+ total_tokens: Optional[int]
768
769
 
769
770
  @classmethod
770
771
  def from_session(cls, session: TeamSession) -> "TeamSessionDetailSchema":
@@ -833,11 +834,14 @@ class RunSchema(BaseModel):
833
834
  content: Optional[Union[str, dict]]
834
835
  run_response_format: Optional[str]
835
836
  reasoning_content: Optional[str]
837
+ reasoning_steps: Optional[List[dict]]
836
838
  metrics: Optional[dict]
837
839
  messages: Optional[List[dict]]
838
840
  tools: Optional[List[dict]]
839
841
  events: Optional[List[dict]]
840
842
  created_at: Optional[datetime]
843
+ references: Optional[List[dict]]
844
+ reasoning_messages: Optional[List[dict]]
841
845
 
842
846
  @classmethod
843
847
  def from_dict(cls, run_dict: Dict[str, Any]) -> "RunSchema":
@@ -852,10 +856,13 @@ class RunSchema(BaseModel):
852
856
  content=run_dict.get("content", ""),
853
857
  run_response_format=run_response_format,
854
858
  reasoning_content=run_dict.get("reasoning_content", ""),
859
+ reasoning_steps=run_dict.get("reasoning_steps", []),
855
860
  metrics=run_dict.get("metrics", {}),
856
861
  messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
857
862
  tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
858
863
  events=[event for event in run_dict["events"]] if run_dict.get("events") else None,
864
+ references=run_dict.get("references", []),
865
+ reasoning_messages=run_dict.get("reasoning_messages", []),
859
866
  created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
860
867
  if run_dict.get("created_at") is not None
861
868
  else None,
@@ -868,6 +875,7 @@ class TeamRunSchema(BaseModel):
868
875
  team_id: Optional[str]
869
876
  content: Optional[Union[str, dict]]
870
877
  reasoning_content: Optional[str]
878
+ reasoning_steps: Optional[List[dict]]
871
879
  run_input: Optional[str]
872
880
  run_response_format: Optional[str]
873
881
  metrics: Optional[dict]
@@ -875,6 +883,8 @@ class TeamRunSchema(BaseModel):
875
883
  messages: Optional[List[dict]]
876
884
  events: Optional[List[dict]]
877
885
  created_at: Optional[datetime]
886
+ references: Optional[List[dict]]
887
+ reasoning_messages: Optional[List[dict]]
878
888
 
879
889
  @classmethod
880
890
  def from_dict(cls, run_dict: Dict[str, Any]) -> "TeamRunSchema":
@@ -888,6 +898,7 @@ class TeamRunSchema(BaseModel):
888
898
  content=run_dict.get("content", ""),
889
899
  run_response_format=run_response_format,
890
900
  reasoning_content=run_dict.get("reasoning_content", ""),
901
+ reasoning_steps=run_dict.get("reasoning_steps", []),
891
902
  metrics=run_dict.get("metrics", {}),
892
903
  messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
893
904
  tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
@@ -895,6 +906,8 @@ class TeamRunSchema(BaseModel):
895
906
  created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
896
907
  if run_dict.get("created_at") is not None
897
908
  else None,
909
+ references=run_dict.get("references", []),
910
+ reasoning_messages=run_dict.get("reasoning_messages", []),
898
911
  )
899
912
 
900
913
 
@@ -910,6 +923,10 @@ class WorkflowRunSchema(BaseModel):
910
923
  step_executor_runs: Optional[list[dict]]
911
924
  metrics: Optional[dict]
912
925
  created_at: Optional[int]
926
+ reasoning_content: Optional[str]
927
+ reasoning_steps: Optional[List[dict]]
928
+ references: Optional[List[dict]]
929
+ reasoning_messages: Optional[List[dict]]
913
930
 
914
931
  @classmethod
915
932
  def from_dict(cls, run_response: Dict[str, Any]) -> "WorkflowRunSchema":
@@ -926,6 +943,10 @@ class WorkflowRunSchema(BaseModel):
926
943
  step_results=run_response.get("step_results", []),
927
944
  step_executor_runs=run_response.get("step_executor_runs", []),
928
945
  created_at=run_response["created_at"],
946
+ reasoning_content=run_response.get("reasoning_content", ""),
947
+ reasoning_steps=run_response.get("reasoning_steps", []),
948
+ references=run_response.get("references", []),
949
+ reasoning_messages=run_response.get("reasoning_messages", []),
929
950
  )
930
951
 
931
952
 
agno/os/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from typing import Any, Callable, Dict, List, Optional, Union
2
- from uuid import uuid4
3
2
 
4
3
  from fastapi import HTTPException, UploadFile
5
4
 
@@ -261,10 +260,3 @@ def _generate_schema_from_params(params: Dict[str, Any]) -> Dict[str, Any]:
261
260
  schema["required"] = required
262
261
 
263
262
  return schema
264
-
265
-
266
- def generate_id(name: Optional[str] = None) -> str:
267
- if name:
268
- return name.lower().replace(" ", "-").replace("_", "-")
269
- else:
270
- return str(uuid4())
agno/run/agent.py CHANGED
@@ -559,7 +559,7 @@ class RunOutput:
559
559
  events = [run_output_event_from_dict(event) for event in events] if events else None
560
560
 
561
561
  messages = data.pop("messages", None)
562
- messages = [Message.model_validate(message) for message in messages] if messages else None
562
+ messages = [Message.from_dict(message) for message in messages] if messages else None
563
563
 
564
564
  citations = data.pop("citations", None)
565
565
  citations = Citations.model_validate(citations) if citations else None
@@ -591,7 +591,7 @@ class RunOutput:
591
591
  additional_input = data.pop("additional_input", None)
592
592
 
593
593
  if additional_input is not None:
594
- additional_input = [Message.model_validate(message) for message in additional_input]
594
+ additional_input = [Message.from_dict(message) for message in additional_input]
595
595
 
596
596
  reasoning_steps = data.pop("reasoning_steps", None)
597
597
  if reasoning_steps is not None:
@@ -599,7 +599,7 @@ class RunOutput:
599
599
 
600
600
  reasoning_messages = data.pop("reasoning_messages", None)
601
601
  if reasoning_messages is not None:
602
- reasoning_messages = [Message.model_validate(message) for message in reasoning_messages]
602
+ reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
603
603
 
604
604
  references = data.pop("references", None)
605
605
  if references is not None:
agno/run/team.py CHANGED
@@ -519,7 +519,7 @@ class TeamRunOutput:
519
519
  events = final_events
520
520
 
521
521
  messages = data.pop("messages", None)
522
- messages = [Message.model_validate(message) for message in messages] if messages else None
522
+ messages = [Message.from_dict(message) for message in messages] if messages else None
523
523
 
524
524
  member_responses = data.pop("member_responses", [])
525
525
  parsed_member_responses: List[Union["TeamRunOutput", RunOutput]] = []
@@ -532,7 +532,7 @@ class TeamRunOutput:
532
532
 
533
533
  additional_input = data.pop("additional_input", None)
534
534
  if additional_input is not None:
535
- additional_input = [Message.model_validate(message) for message in additional_input]
535
+ additional_input = [Message.from_dict(message) for message in additional_input]
536
536
 
537
537
  reasoning_steps = data.pop("reasoning_steps", None)
538
538
  if reasoning_steps is not None:
@@ -540,7 +540,7 @@ class TeamRunOutput:
540
540
 
541
541
  reasoning_messages = data.pop("reasoning_messages", None)
542
542
  if reasoning_messages is not None:
543
- reasoning_messages = [Message.model_validate(message) for message in reasoning_messages]
543
+ reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
544
544
 
545
545
  references = data.pop("references", None)
546
546
  if references is not None: