agno 1.7.6__py3-none-any.whl → 1.7.8__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 CHANGED
@@ -274,6 +274,10 @@ class Agent:
274
274
  parser_model: Optional[Model] = None
275
275
  # Provide a prompt for the parser model
276
276
  parser_model_prompt: Optional[str] = None
277
+ # Provide an output model to structure the response from the main model
278
+ output_model: Optional[Model] = None
279
+ # Provide a prompt for the output model
280
+ output_model_prompt: Optional[str] = None
277
281
  # If True, the response from the Model is converted into the response_model
278
282
  # Otherwise, the response is returned as a JSON string
279
283
  parse_response: bool = True
@@ -414,6 +418,8 @@ class Agent:
414
418
  parser_model_prompt: Optional[str] = None,
415
419
  response_model: Optional[Type[BaseModel]] = None,
416
420
  parse_response: bool = True,
421
+ output_model: Optional[Model] = None,
422
+ output_model_prompt: Optional[str] = None,
417
423
  structured_outputs: Optional[bool] = None,
418
424
  use_json_mode: bool = False,
419
425
  save_response_to_file: Optional[str] = None,
@@ -517,6 +523,8 @@ class Agent:
517
523
  self.parser_model_prompt = parser_model_prompt
518
524
  self.response_model = response_model
519
525
  self.parse_response = parse_response
526
+ self.output_model = output_model
527
+ self.output_model_prompt = output_model_prompt
520
528
 
521
529
  self.structured_outputs = structured_outputs
522
530
 
@@ -713,15 +721,6 @@ class Agent:
713
721
  if self.workflow_session_state is not None:
714
722
  self.workflow_session_state["current_user_id"] = user_id
715
723
 
716
- def _reset_session_state(self) -> None:
717
- """Reset the session state for the agent."""
718
- if self.team_session_state is not None:
719
- self.team_session_state.pop("current_session_id", None)
720
- self.team_session_state.pop("current_user_id", None)
721
- if self.session_state is not None:
722
- self.session_state.pop("current_session_id", None)
723
- self.session_state.pop("current_user_id", None)
724
-
725
724
  def _initialize_session(
726
725
  self,
727
726
  session_id: Optional[str] = None,
@@ -800,6 +799,8 @@ class Agent:
800
799
  tool_call_limit=self.tool_call_limit,
801
800
  response_format=response_format,
802
801
  )
802
+ # If an output model is provided, generate output using the output model
803
+ self._generate_response_with_output_model(model_response, run_messages)
803
804
 
804
805
  # If a parser model is provided, structure the response separately
805
806
  self._parse_response_with_parser_model(model_response, run_messages)
@@ -883,13 +884,33 @@ class Agent:
883
884
  index_of_last_user_message = len(run_messages.messages)
884
885
 
885
886
  # 2. Process model response
886
- for event in self._handle_model_response_stream(
887
- run_response=run_response,
888
- run_messages=run_messages,
889
- response_format=response_format,
890
- stream_intermediate_steps=stream_intermediate_steps,
891
- ):
892
- yield event
887
+ if self.output_model is None:
888
+ for event in self._handle_model_response_stream(
889
+ run_response=run_response,
890
+ run_messages=run_messages,
891
+ response_format=response_format,
892
+ stream_intermediate_steps=stream_intermediate_steps,
893
+ ):
894
+ yield event
895
+ else:
896
+ from agno.utils.events import RunResponseContentEvent
897
+
898
+ for event in self._handle_model_response_stream(
899
+ run_response=run_response,
900
+ run_messages=run_messages,
901
+ response_format=response_format,
902
+ stream_intermediate_steps=stream_intermediate_steps,
903
+ ):
904
+ if isinstance(event, RunResponseContentEvent):
905
+ if stream_intermediate_steps:
906
+ yield event
907
+ else:
908
+ yield event
909
+
910
+ # If an output model is provided, generate output using the output model
911
+ yield from self._generate_response_with_output_model_stream(
912
+ run_response=run_response, run_messages=run_messages
913
+ )
893
914
 
894
915
  # If a parser model is provided, structure the response separately
895
916
  yield from self._parse_response_with_parser_model_stream(
@@ -1162,8 +1183,6 @@ class Agent:
1162
1183
  )
1163
1184
  else:
1164
1185
  return self.run_response
1165
- finally:
1166
- self._reset_session_state()
1167
1186
 
1168
1187
  # If we get here, all retries failed
1169
1188
  if last_exception is not None:
@@ -1219,6 +1238,9 @@ class Agent:
1219
1238
  response_format=response_format,
1220
1239
  )
1221
1240
 
1241
+ # If an output model is provided, generate output using the output model
1242
+ await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
1243
+
1222
1244
  # If a parser model is provided, structure the response separately
1223
1245
  await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
1224
1246
 
@@ -1300,13 +1322,36 @@ class Agent:
1300
1322
  index_of_last_user_message = len(run_messages.messages)
1301
1323
 
1302
1324
  # 2. Generate a response from the Model
1303
- async for event in self._ahandle_model_response_stream(
1304
- run_response=run_response,
1305
- run_messages=run_messages,
1306
- response_format=response_format,
1307
- stream_intermediate_steps=stream_intermediate_steps,
1308
- ):
1309
- yield event
1325
+ if self.output_model is None:
1326
+ async for event in self._ahandle_model_response_stream(
1327
+ run_response=run_response,
1328
+ run_messages=run_messages,
1329
+ response_format=response_format,
1330
+ stream_intermediate_steps=stream_intermediate_steps,
1331
+ ):
1332
+ yield event
1333
+ else:
1334
+ from agno.utils.events import RunResponseContentEvent
1335
+
1336
+ async for event in self._ahandle_model_response_stream(
1337
+ run_response=run_response,
1338
+ run_messages=run_messages,
1339
+ response_format=response_format,
1340
+ stream_intermediate_steps=stream_intermediate_steps,
1341
+ ):
1342
+ if isinstance(event, RunResponseContentEvent):
1343
+ if stream_intermediate_steps:
1344
+ yield event
1345
+ else:
1346
+ yield event
1347
+
1348
+ # If an output model is provided, generate output using the output model
1349
+ async for event in self._agenerate_response_with_output_model_stream(
1350
+ run_response=run_response,
1351
+ run_messages=run_messages,
1352
+ stream_intermediate_steps=stream_intermediate_steps,
1353
+ ):
1354
+ yield event
1310
1355
 
1311
1356
  # If a parser model is provided, structure the response separately
1312
1357
  async for event in self._aparse_response_with_parser_model_stream(
@@ -1540,8 +1585,6 @@ class Agent:
1540
1585
  )
1541
1586
  else:
1542
1587
  return self.run_response
1543
- finally:
1544
- self._reset_session_state()
1545
1588
 
1546
1589
  # If we get here, all retries failed
1547
1590
  if last_exception is not None:
@@ -1797,8 +1840,6 @@ class Agent:
1797
1840
  return self.create_run_response(
1798
1841
  run_state=RunStatus.cancelled, content="Operation cancelled by user", run_response=run_response
1799
1842
  )
1800
- finally:
1801
- self._reset_session_state()
1802
1843
 
1803
1844
  # If we get here, all retries failed
1804
1845
  if last_exception is not None:
@@ -2196,8 +2237,6 @@ class Agent:
2196
2237
  return self.create_run_response(
2197
2238
  run_state=RunStatus.cancelled, content="Operation cancelled by user", run_response=run_response
2198
2239
  )
2199
- finally:
2200
- self._reset_session_state()
2201
2240
 
2202
2241
  # If we get here, all retries failed
2203
2242
  if last_exception is not None:
@@ -3172,6 +3211,12 @@ class Agent:
3172
3211
  model_response.thinking = (model_response.thinking or "") + model_response_event.thinking
3173
3212
  run_response.thinking = model_response.thinking
3174
3213
 
3214
+ if model_response_event.reasoning_content is not None:
3215
+ model_response.reasoning_content = (
3216
+ model_response.reasoning_content or ""
3217
+ ) + model_response_event.reasoning_content
3218
+ run_response.reasoning_content = model_response.reasoning_content
3219
+
3175
3220
  if model_response_event.redacted_thinking is not None:
3176
3221
  model_response.redacted_thinking = (
3177
3222
  model_response.redacted_thinking or ""
@@ -3197,6 +3242,7 @@ class Agent:
3197
3242
  elif (
3198
3243
  model_response_event.content is not None
3199
3244
  or model_response_event.thinking is not None
3245
+ or model_response_event.reasoning_content is not None
3200
3246
  or model_response_event.redacted_thinking is not None
3201
3247
  or model_response_event.citations is not None
3202
3248
  ):
@@ -3205,6 +3251,7 @@ class Agent:
3205
3251
  from_run_response=run_response,
3206
3252
  content=model_response_event.content,
3207
3253
  thinking=model_response_event.thinking,
3254
+ reasoning_content=model_response_event.reasoning_content,
3208
3255
  redacted_thinking=model_response_event.redacted_thinking,
3209
3256
  citations=model_response_event.citations,
3210
3257
  ),
@@ -5051,6 +5098,24 @@ class Agent:
5051
5098
  Message(role="user", content=run_response.content),
5052
5099
  ]
5053
5100
 
5101
+ def get_messages_for_output_model(self, messages: List[Message]) -> List[Message]:
5102
+ """Get the messages for the output model."""
5103
+
5104
+ if self.output_model_prompt is not None:
5105
+ system_message_exists = False
5106
+ for message in messages:
5107
+ if message.role == "system":
5108
+ system_message_exists = True
5109
+ message.content = self.output_model_prompt
5110
+ break
5111
+ if not system_message_exists:
5112
+ messages.insert(0, Message(role="system", content=self.output_model_prompt))
5113
+
5114
+ # Remove the last assistant message from the messages list
5115
+ messages.pop(-1)
5116
+
5117
+ return messages
5118
+
5054
5119
  def get_session_summary(self, session_id: Optional[str] = None, user_id: Optional[str] = None):
5055
5120
  """Get the session summary for the given session ID and user ID."""
5056
5121
  if self.memory is None:
@@ -5299,6 +5364,8 @@ class Agent:
5299
5364
  """
5300
5365
  from agno.document import Document
5301
5366
 
5367
+ if num_documents is None and self.knowledge is not None:
5368
+ num_documents = self.knowledge.num_documents
5302
5369
  # Validate the filters against known valid filter keys
5303
5370
  if self.knowledge is not None:
5304
5371
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -5338,9 +5405,6 @@ class Agent:
5338
5405
  ):
5339
5406
  return None
5340
5407
 
5341
- if num_documents is None:
5342
- num_documents = self.knowledge.num_documents
5343
-
5344
5408
  log_debug(f"Searching knowledge base with filters: {filters}")
5345
5409
  relevant_docs: List[Document] = self.knowledge.search(
5346
5410
  query=query, num_documents=num_documents, filters=filters
@@ -5361,6 +5425,9 @@ class Agent:
5361
5425
  """Get relevant documents from knowledge base asynchronously."""
5362
5426
  from agno.document import Document
5363
5427
 
5428
+ if num_documents is None and self.knowledge is not None:
5429
+ num_documents = self.knowledge.num_documents
5430
+
5364
5431
  # Validate the filters against known valid filter keys
5365
5432
  if self.knowledge is not None:
5366
5433
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -5404,9 +5471,6 @@ class Agent:
5404
5471
  ):
5405
5472
  return None
5406
5473
 
5407
- if num_documents is None:
5408
- num_documents = self.knowledge.num_documents
5409
-
5410
5474
  log_debug(f"Searching knowledge base with filters: {filters}")
5411
5475
  relevant_docs: List[Document] = await self.knowledge.async_search(
5412
5476
  query=query, num_documents=num_documents, filters=filters
@@ -6372,6 +6436,99 @@ class Agent:
6372
6436
  else:
6373
6437
  log_warning("A response model is required to parse the response with a parser model")
6374
6438
 
6439
+ def _generate_response_with_output_model(self, model_response: ModelResponse, run_messages: RunMessages) -> None:
6440
+ """Parse the model response using the output model."""
6441
+ if self.output_model is None:
6442
+ return
6443
+
6444
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6445
+ output_model_response: ModelResponse = self.output_model.response(messages=messages_for_output_model)
6446
+ model_response.content = output_model_response.content
6447
+
6448
+ def _generate_response_with_output_model_stream(
6449
+ self, run_response: RunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
6450
+ ):
6451
+ """Parse the model response using the output model."""
6452
+ from agno.utils.events import (
6453
+ create_output_model_response_completed_event,
6454
+ create_output_model_response_started_event,
6455
+ )
6456
+
6457
+ if self.output_model is None:
6458
+ return
6459
+
6460
+ if stream_intermediate_steps:
6461
+ yield self._handle_event(create_output_model_response_started_event(run_response), run_response)
6462
+
6463
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6464
+
6465
+ model_response = ModelResponse(content="")
6466
+
6467
+ for model_response_event in self.output_model.response_stream(messages=messages_for_output_model):
6468
+ yield from self._handle_model_response_chunk(
6469
+ run_response=run_response,
6470
+ model_response=model_response,
6471
+ model_response_event=model_response_event,
6472
+ )
6473
+
6474
+ if stream_intermediate_steps:
6475
+ yield self._handle_event(create_output_model_response_completed_event(run_response), run_response)
6476
+
6477
+ # Build a list of messages that should be added to the RunResponse
6478
+ messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
6479
+ # Update the RunResponse messages
6480
+ run_response.messages = messages_for_run_response
6481
+ # Update the RunResponse metrics
6482
+ run_response.metrics = self.aggregate_metrics_from_messages(messages_for_run_response)
6483
+
6484
+ async def _agenerate_response_with_output_model(self, model_response: ModelResponse, run_messages: RunMessages):
6485
+ """Parse the model response using the output model."""
6486
+ if self.output_model is None:
6487
+ return
6488
+
6489
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6490
+ output_model_response: ModelResponse = await self.output_model.aresponse(messages=messages_for_output_model)
6491
+ model_response.content = output_model_response.content
6492
+
6493
+ async def _agenerate_response_with_output_model_stream(
6494
+ self, run_response: RunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
6495
+ ):
6496
+ """Parse the model response using the output model."""
6497
+ from agno.utils.events import (
6498
+ create_output_model_response_completed_event,
6499
+ create_output_model_response_started_event,
6500
+ )
6501
+
6502
+ if self.output_model is None:
6503
+ return
6504
+
6505
+ if stream_intermediate_steps:
6506
+ yield self._handle_event(create_output_model_response_started_event(run_response), run_response)
6507
+
6508
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6509
+
6510
+ model_response = ModelResponse(content="")
6511
+
6512
+ model_response_stream = self.output_model.aresponse_stream(messages=messages_for_output_model)
6513
+
6514
+ async for model_response_event in model_response_stream:
6515
+ for event in self._handle_model_response_chunk(
6516
+ run_response=run_response,
6517
+ model_response=model_response,
6518
+ model_response_event=model_response_event,
6519
+ ):
6520
+ yield event
6521
+
6522
+ if stream_intermediate_steps:
6523
+ yield self._handle_event(create_output_model_response_completed_event(run_response), run_response)
6524
+
6525
+ # Build a list of messages that should be added to the RunResponse
6526
+ messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
6527
+ # Update the RunResponse messages
6528
+ run_response.messages = messages_for_run_response
6529
+ # Update the RunResponse metrics
6530
+ run_response.metrics = self.aggregate_metrics_from_messages(messages_for_run_response)
6531
+
6375
6532
  def _handle_event(self, event: RunResponseEvent, run_response: RunResponse):
6376
6533
  # We only store events that are not run_response_content events
6377
6534
  events_to_skip = [event.value for event in self.events_to_skip] if self.events_to_skip else []
@@ -6893,6 +7050,7 @@ class Agent:
6893
7050
  if stream:
6894
7051
  _response_content: str = ""
6895
7052
  _response_thinking: str = ""
7053
+ _response_reasoning_content: str = ""
6896
7054
  response_content_batch: Union[str, JSON, Markdown] = ""
6897
7055
  reasoning_steps: List[ReasoningStep] = []
6898
7056
 
@@ -6959,6 +7117,8 @@ class Agent:
6959
7117
  log_warning(f"Failed to convert response to JSON: {e}")
6960
7118
  if hasattr(resp, "thinking") and resp.thinking is not None:
6961
7119
  _response_thinking += resp.thinking
7120
+ if hasattr(resp, "reasoning_content") and resp.reasoning_content is not None:
7121
+ _response_reasoning_content += resp.reasoning_content
6962
7122
  if (
6963
7123
  hasattr(resp, "extra_data")
6964
7124
  and resp.extra_data is not None
@@ -7030,6 +7190,18 @@ class Agent:
7030
7190
  if render:
7031
7191
  live_log.update(Group(*panels))
7032
7192
 
7193
+ if len(_response_reasoning_content) > 0:
7194
+ render = True
7195
+ # Create panel for reasoning content
7196
+ reasoning_panel = create_panel(
7197
+ content=Text(_response_reasoning_content),
7198
+ title=f"Reasoning ({response_timer.elapsed:.1f}s)",
7199
+ border_style="green",
7200
+ )
7201
+ panels.append(reasoning_panel)
7202
+ if render:
7203
+ live_log.update(Group(*panels))
7204
+
7033
7205
  # Add tool calls panel if available
7034
7206
  if (
7035
7207
  self.show_tool_calls
@@ -7342,6 +7514,7 @@ class Agent:
7342
7514
  if stream:
7343
7515
  _response_content: str = ""
7344
7516
  _response_thinking: str = ""
7517
+ _response_reasoning_content: str = ""
7345
7518
  reasoning_steps: List[ReasoningStep] = []
7346
7519
  response_content_batch: Union[str, JSON, Markdown] = ""
7347
7520
 
@@ -7409,6 +7582,8 @@ class Agent:
7409
7582
  log_warning(f"Failed to convert response to JSON: {e}")
7410
7583
  if resp.thinking is not None:
7411
7584
  _response_thinking += resp.thinking
7585
+ if hasattr(resp, "reasoning_content") and resp.reasoning_content is not None:
7586
+ _response_reasoning_content += resp.reasoning_content
7412
7587
 
7413
7588
  if (
7414
7589
  hasattr(resp, "extra_data")
@@ -7482,6 +7657,18 @@ class Agent:
7482
7657
  if render:
7483
7658
  live_log.update(Group(*panels))
7484
7659
 
7660
+ if len(_response_reasoning_content) > 0:
7661
+ render = True
7662
+ # Create panel for reasoning content
7663
+ reasoning_panel = create_panel(
7664
+ content=Text(_response_reasoning_content),
7665
+ title=f"Reasoning ({response_timer.elapsed:.1f}s)",
7666
+ border_style="green",
7667
+ )
7668
+ panels.append(reasoning_panel)
7669
+ if render:
7670
+ live_log.update(Group(*panels))
7671
+
7485
7672
  # Add tool calls panel if available
7486
7673
  if (
7487
7674
  self.show_tool_calls
@@ -1,5 +1,5 @@
1
1
  from os import getenv
2
- from typing import Any, Dict, List, Optional, Union
2
+ from typing import Any, Callable, Dict, List, Optional, Union
3
3
  from urllib.parse import quote
4
4
  from uuid import uuid4
5
5
 
@@ -110,13 +110,14 @@ class Playground:
110
110
  def get_async_router(self) -> APIRouter:
111
111
  return get_async_playground_router(self.agents, self.workflows, self.teams, self.app_id)
112
112
 
113
- def get_app(self, use_async: bool = True, prefix: str = "/v1") -> FastAPI:
113
+ def get_app(self, use_async: bool = True, prefix: str = "/v1", lifespan: Optional[Callable] = None) -> FastAPI:
114
114
  if not self.api_app:
115
115
  self.api_app = FastAPI(
116
116
  title=self.settings.title,
117
117
  docs_url="/docs" if self.settings.docs_enabled else None,
118
118
  redoc_url="/redoc" if self.settings.docs_enabled else None,
119
119
  openapi_url="/openapi.json" if self.settings.docs_enabled else None,
120
+ lifespan=lifespan,
120
121
  )
121
122
 
122
123
  if not self.api_app:
@@ -3,7 +3,7 @@ from typing import List
3
3
 
4
4
  from agno.document.base import Document
5
5
  from agno.document.reader.base import Reader
6
- from agno.utils.log import log_info, logger
6
+ from agno.utils.log import log_debug, log_info, logger
7
7
 
8
8
  try:
9
9
  from youtube_transcript_api import YouTubeTranscriptApi
@@ -23,12 +23,16 @@ class YouTubeReader(Reader):
23
23
  log_info(f"Reading transcript for video: {video_id}")
24
24
 
25
25
  # Get transcript
26
- transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
26
+ log_debug(f"Fetching transcript for video: {video_id}")
27
+ # Create an instance of YouTubeTranscriptApi
28
+ ytt_api = YouTubeTranscriptApi()
29
+ transcript_data = ytt_api.fetch(video_id)
27
30
 
28
31
  # Combine transcript segments into full text
29
32
  transcript_text = ""
30
- for segment in transcript_list:
31
- transcript_text += f"{segment['text']} "
33
+
34
+ for segment in transcript_data:
35
+ transcript_text += f"{segment.text} "
32
36
 
33
37
  documents = [
34
38
  Document(
agno/embedder/openai.py CHANGED
@@ -16,7 +16,7 @@ except ImportError:
16
16
  @dataclass
17
17
  class OpenAIEmbedder(Embedder):
18
18
  id: str = "text-embedding-3-small"
19
- dimensions: int = 1536
19
+ dimensions: Optional[int] = None
20
20
  encoding_format: Literal["float", "base64"] = "float"
21
21
  user: Optional[str] = None
22
22
  api_key: Optional[str] = None
@@ -26,6 +26,10 @@ class OpenAIEmbedder(Embedder):
26
26
  client_params: Optional[Dict[str, Any]] = None
27
27
  openai_client: Optional[OpenAIClient] = None
28
28
 
29
+ def __post_init__(self):
30
+ if self.dimensions is None:
31
+ self.dimensions = 3072 if self.id == "text-embedding-3-large" else 1536
32
+
29
33
  @property
30
34
  def client(self) -> OpenAIClient:
31
35
  if self.openai_client:
@@ -449,7 +449,7 @@ class Claude(Model):
449
449
 
450
450
  def get_system_message_for_model(self, tools: Optional[List[Any]] = None) -> Optional[str]:
451
451
  if tools is not None and len(tools) > 0:
452
- tool_call_prompt = "Do not reflect on the quality of the returned search results in your response"
452
+ tool_call_prompt = "Do not reflect on the quality of the returned search results in your response\n\n"
453
453
  return tool_call_prompt
454
454
  return None
455
455
 
@@ -570,8 +570,16 @@ class Claude(Model):
570
570
  }
571
571
 
572
572
  elif isinstance(response, ContentBlockStopEvent):
573
+ # Handle completed thinking content
574
+ if response.content_block.type == "thinking": # type: ignore
575
+ model_response.thinking = response.content_block.thinking # type: ignore
576
+ # Store signature if available
577
+ if hasattr(response.content_block, "signature"): # type: ignore
578
+ model_response.provider_data = {
579
+ "signature": response.content_block.signature, # type: ignore
580
+ }
573
581
  # Handle tool calls
574
- if response.content_block.type == "tool_use": # type: ignore
582
+ elif response.content_block.type == "tool_use": # type: ignore
575
583
  tool_use = response.content_block # type: ignore
576
584
  tool_name = tool_use.name
577
585
  tool_input = tool_use.input
agno/models/base.py CHANGED
@@ -1025,6 +1025,10 @@ class Model(ABC):
1025
1025
  stream_data.response_thinking += model_response_delta.thinking
1026
1026
  should_yield = True
1027
1027
 
1028
+ if model_response_delta.reasoning_content is not None:
1029
+ stream_data.response_thinking += model_response_delta.reasoning_content
1030
+ should_yield = True
1031
+
1028
1032
  if model_response_delta.redacted_thinking is not None:
1029
1033
  stream_data.response_redacted_thinking += model_response_delta.redacted_thinking
1030
1034
  should_yield = True
@@ -713,15 +713,29 @@ class Gemini(Model):
713
713
  if isinstance(text_content, str):
714
714
  # Check if this is a thought summary
715
715
  if hasattr(part, "thought") and part.thought:
716
- model_response.reasoning_content = text_content
716
+ # Add all parts as single message
717
+ if model_response.reasoning_content is None:
718
+ model_response.reasoning_content = text_content
719
+ else:
720
+ model_response.reasoning_content += text_content
717
721
  else:
718
- model_response.content = text_content
722
+ if model_response.content is None:
723
+ model_response.content = text_content
724
+ else:
725
+ model_response.content += text_content
719
726
  else:
720
727
  content_str = str(text_content) if text_content is not None else ""
721
728
  if hasattr(part, "thought") and part.thought:
722
- model_response.reasoning_content = content_str
729
+ # Add all parts as single message
730
+ if model_response.reasoning_content is None:
731
+ model_response.reasoning_content = content_str
732
+ else:
733
+ model_response.reasoning_content += content_str
723
734
  else:
724
- model_response.content = content_str
735
+ if model_response.content is None:
736
+ model_response.content = content_str
737
+ else:
738
+ model_response.content += content_str
725
739
 
726
740
  if hasattr(part, "inline_data") and part.inline_data is not None:
727
741
  model_response.image = ImageArtifact(
@@ -803,9 +817,15 @@ class Gemini(Model):
803
817
  text_content = str(part.text) if part.text is not None else ""
804
818
  # Check if this is a thought summary
805
819
  if hasattr(part, "thought") and part.thought:
806
- model_response.reasoning_content = text_content
820
+ if model_response.reasoning_content is None:
821
+ model_response.reasoning_content = text_content
822
+ else:
823
+ model_response.reasoning_content += text_content
807
824
  else:
808
- model_response.content = text_content
825
+ if model_response.content is None:
826
+ model_response.content = text_content
827
+ else:
828
+ model_response.content += text_content
809
829
 
810
830
  if hasattr(part, "inline_data") and part.inline_data is not None:
811
831
  model_response.image = ImageArtifact(
@@ -9,6 +9,7 @@ from agno.models.base import Model
9
9
  from agno.models.message import Message
10
10
  from agno.models.response import ModelResponse
11
11
  from agno.utils.log import log_debug, log_error, log_warning
12
+ from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
12
13
 
13
14
  try:
14
15
  import litellm
@@ -73,6 +74,31 @@ class LiteLLM(Model):
73
74
  for m in messages:
74
75
  msg = {"role": m.role, "content": m.content if m.content is not None else ""}
75
76
 
77
+ # Handle media
78
+ if (m.images is not None and len(m.images) > 0) or (m.audio is not None and len(m.audio) > 0):
79
+ if isinstance(m.content, str):
80
+ content_list = [{"type": "text", "text": m.content}]
81
+ if m.images is not None:
82
+ content_list.extend(images_to_message(images=m.images))
83
+ if m.audio is not None:
84
+ content_list.extend(audio_to_message(audio=m.audio))
85
+ msg["content"] = content_list
86
+
87
+ if m.videos is not None and len(m.videos) > 0:
88
+ log_warning("Video input is currently unsupported by LLM providers.")
89
+
90
+ # Handle files
91
+ if m.files is not None:
92
+ if isinstance(msg["content"], str):
93
+ content_list = [{"type": "text", "text": msg["content"]}]
94
+ else:
95
+ content_list = msg["content"]
96
+ for file in m.files:
97
+ file_part = _format_file_for_message(file)
98
+ if file_part:
99
+ content_list.append(file_part)
100
+ msg["content"] = content_list
101
+
76
102
  # Handle tool calls in assistant messages
77
103
  if m.role == "assistant" and m.tool_calls:
78
104
  msg["tool_calls"] = [
@@ -95,12 +121,8 @@ class LiteLLM(Model):
95
121
  if m.images is not None and len(m.images) > 0:
96
122
  log_warning("Image input is currently unsupported.")
97
123
 
98
- if m.files is not None and len(m.files) > 0:
99
- log_warning("File input is currently unsupported.")
100
-
101
124
  if m.videos is not None and len(m.videos) > 0:
102
125
  log_warning("Video input is currently unsupported.")
103
-
104
126
  formatted_messages.append(msg)
105
127
 
106
128
  return formatted_messages