agno 1.7.7__py3-none-any.whl → 1.7.9__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
 
@@ -791,6 +799,8 @@ class Agent:
791
799
  tool_call_limit=self.tool_call_limit,
792
800
  response_format=response_format,
793
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)
794
804
 
795
805
  # If a parser model is provided, structure the response separately
796
806
  self._parse_response_with_parser_model(model_response, run_messages)
@@ -874,13 +884,36 @@ class Agent:
874
884
  index_of_last_user_message = len(run_messages.messages)
875
885
 
876
886
  # 2. Process model response
877
- for event in self._handle_model_response_stream(
878
- run_response=run_response,
879
- run_messages=run_messages,
880
- response_format=response_format,
881
- stream_intermediate_steps=stream_intermediate_steps,
882
- ):
883
- 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.run.response import IntermediateRunResponseContentEvent, 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 IntermediateRunResponseContentEvent(
907
+ content=event.content,
908
+ content_type=event.content_type,
909
+ )
910
+ else:
911
+ yield event
912
+
913
+ # If an output model is provided, generate output using the output model
914
+ yield from self._generate_response_with_output_model_stream(
915
+ run_response=run_response, run_messages=run_messages
916
+ )
884
917
 
885
918
  # If a parser model is provided, structure the response separately
886
919
  yield from self._parse_response_with_parser_model_stream(
@@ -1208,6 +1241,9 @@ class Agent:
1208
1241
  response_format=response_format,
1209
1242
  )
1210
1243
 
1244
+ # If an output model is provided, generate output using the output model
1245
+ await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
1246
+
1211
1247
  # If a parser model is provided, structure the response separately
1212
1248
  await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
1213
1249
 
@@ -1289,13 +1325,39 @@ class Agent:
1289
1325
  index_of_last_user_message = len(run_messages.messages)
1290
1326
 
1291
1327
  # 2. Generate a response from the Model
1292
- async for event in self._ahandle_model_response_stream(
1293
- run_response=run_response,
1294
- run_messages=run_messages,
1295
- response_format=response_format,
1296
- stream_intermediate_steps=stream_intermediate_steps,
1297
- ):
1298
- yield event
1328
+ if self.output_model is None:
1329
+ async for event in self._ahandle_model_response_stream(
1330
+ run_response=run_response,
1331
+ run_messages=run_messages,
1332
+ response_format=response_format,
1333
+ stream_intermediate_steps=stream_intermediate_steps,
1334
+ ):
1335
+ yield event
1336
+ else:
1337
+ from agno.run.response import IntermediateRunResponseContentEvent, RunResponseContentEvent
1338
+
1339
+ async for event in self._ahandle_model_response_stream(
1340
+ run_response=run_response,
1341
+ run_messages=run_messages,
1342
+ response_format=response_format,
1343
+ stream_intermediate_steps=stream_intermediate_steps,
1344
+ ):
1345
+ if isinstance(event, RunResponseContentEvent):
1346
+ if stream_intermediate_steps:
1347
+ yield IntermediateRunResponseContentEvent(
1348
+ content=event.content,
1349
+ content_type=event.content_type,
1350
+ )
1351
+ else:
1352
+ yield event
1353
+
1354
+ # If an output model is provided, generate output using the output model
1355
+ async for event in self._agenerate_response_with_output_model_stream(
1356
+ run_response=run_response,
1357
+ run_messages=run_messages,
1358
+ stream_intermediate_steps=stream_intermediate_steps,
1359
+ ):
1360
+ yield event
1299
1361
 
1300
1362
  # If a parser model is provided, structure the response separately
1301
1363
  async for event in self._aparse_response_with_parser_model_stream(
@@ -3155,6 +3217,12 @@ class Agent:
3155
3217
  model_response.thinking = (model_response.thinking or "") + model_response_event.thinking
3156
3218
  run_response.thinking = model_response.thinking
3157
3219
 
3220
+ if model_response_event.reasoning_content is not None:
3221
+ model_response.reasoning_content = (
3222
+ model_response.reasoning_content or ""
3223
+ ) + model_response_event.reasoning_content
3224
+ run_response.reasoning_content = model_response.reasoning_content
3225
+
3158
3226
  if model_response_event.redacted_thinking is not None:
3159
3227
  model_response.redacted_thinking = (
3160
3228
  model_response.redacted_thinking or ""
@@ -3180,6 +3248,7 @@ class Agent:
3180
3248
  elif (
3181
3249
  model_response_event.content is not None
3182
3250
  or model_response_event.thinking is not None
3251
+ or model_response_event.reasoning_content is not None
3183
3252
  or model_response_event.redacted_thinking is not None
3184
3253
  or model_response_event.citations is not None
3185
3254
  ):
@@ -3188,6 +3257,7 @@ class Agent:
3188
3257
  from_run_response=run_response,
3189
3258
  content=model_response_event.content,
3190
3259
  thinking=model_response_event.thinking,
3260
+ reasoning_content=model_response_event.reasoning_content,
3191
3261
  redacted_thinking=model_response_event.redacted_thinking,
3192
3262
  citations=model_response_event.citations,
3193
3263
  ),
@@ -4909,9 +4979,26 @@ class Agent:
4909
4979
 
4910
4980
  run_messages.messages += history_copy
4911
4981
 
4912
- # 4.Add user message to run_messages
4982
+ # 4. Add messages to run_messages if provided
4983
+ if messages is not None and len(messages) > 0:
4984
+ for _m in messages:
4985
+ if isinstance(_m, Message):
4986
+ run_messages.messages.append(_m)
4987
+ if run_messages.extra_messages is None:
4988
+ run_messages.extra_messages = []
4989
+ run_messages.extra_messages.append(_m)
4990
+ elif isinstance(_m, dict):
4991
+ try:
4992
+ run_messages.messages.append(Message.model_validate(_m))
4993
+ if run_messages.extra_messages is None:
4994
+ run_messages.extra_messages = []
4995
+ run_messages.extra_messages.append(Message.model_validate(_m))
4996
+ except Exception as e:
4997
+ log_warning(f"Failed to validate message: {e}")
4998
+
4999
+ # 5. Add user message to run_messages
4913
5000
  user_message: Optional[Message] = None
4914
- # 4.1 Build user message if message is None, str or list
5001
+ # 5.1 Build user message if message is None, str or list
4915
5002
  if message is None or isinstance(message, str) or isinstance(message, list):
4916
5003
  user_message = self.get_user_message(
4917
5004
  message=message,
@@ -4922,16 +5009,16 @@ class Agent:
4922
5009
  knowledge_filters=knowledge_filters,
4923
5010
  **kwargs,
4924
5011
  )
4925
- # 4.2 If message is provided as a Message, use it directly
5012
+ # 5.2 If message is provided as a Message, use it directly
4926
5013
  elif isinstance(message, Message):
4927
5014
  user_message = message
4928
- # 4.3 If message is provided as a dict, try to validate it as a Message
5015
+ # 5.3 If message is provided as a dict, try to validate it as a Message
4929
5016
  elif isinstance(message, dict):
4930
5017
  try:
4931
5018
  user_message = Message.model_validate(message)
4932
5019
  except Exception as e:
4933
5020
  log_warning(f"Failed to validate message: {e}")
4934
- # 4.4 If message is provided as a BaseModel, convert it to a Message
5021
+ # 5.4 If message is provided as a BaseModel, convert it to a Message
4935
5022
  elif isinstance(message, BaseModel):
4936
5023
  try:
4937
5024
  # Create a user message with the BaseModel content
@@ -4944,23 +5031,6 @@ class Agent:
4944
5031
  run_messages.user_message = user_message
4945
5032
  run_messages.messages.append(user_message)
4946
5033
 
4947
- # 5. Add messages to run_messages if provided
4948
- if messages is not None and len(messages) > 0:
4949
- for _m in messages:
4950
- if isinstance(_m, Message):
4951
- run_messages.messages.append(_m)
4952
- if run_messages.extra_messages is None:
4953
- run_messages.extra_messages = []
4954
- run_messages.extra_messages.append(_m)
4955
- elif isinstance(_m, dict):
4956
- try:
4957
- run_messages.messages.append(Message.model_validate(_m))
4958
- if run_messages.extra_messages is None:
4959
- run_messages.extra_messages = []
4960
- run_messages.extra_messages.append(Message.model_validate(_m))
4961
- except Exception as e:
4962
- log_warning(f"Failed to validate message: {e}")
4963
-
4964
5034
  return run_messages
4965
5035
 
4966
5036
  def get_continue_run_messages(
@@ -5034,6 +5104,24 @@ class Agent:
5034
5104
  Message(role="user", content=run_response.content),
5035
5105
  ]
5036
5106
 
5107
+ def get_messages_for_output_model(self, messages: List[Message]) -> List[Message]:
5108
+ """Get the messages for the output model."""
5109
+
5110
+ if self.output_model_prompt is not None:
5111
+ system_message_exists = False
5112
+ for message in messages:
5113
+ if message.role == "system":
5114
+ system_message_exists = True
5115
+ message.content = self.output_model_prompt
5116
+ break
5117
+ if not system_message_exists:
5118
+ messages.insert(0, Message(role="system", content=self.output_model_prompt))
5119
+
5120
+ # Remove the last assistant message from the messages list
5121
+ messages.pop(-1)
5122
+
5123
+ return messages
5124
+
5037
5125
  def get_session_summary(self, session_id: Optional[str] = None, user_id: Optional[str] = None):
5038
5126
  """Get the session summary for the given session ID and user ID."""
5039
5127
  if self.memory is None:
@@ -6354,6 +6442,99 @@ class Agent:
6354
6442
  else:
6355
6443
  log_warning("A response model is required to parse the response with a parser model")
6356
6444
 
6445
+ def _generate_response_with_output_model(self, model_response: ModelResponse, run_messages: RunMessages) -> None:
6446
+ """Parse the model response using the output model."""
6447
+ if self.output_model is None:
6448
+ return
6449
+
6450
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6451
+ output_model_response: ModelResponse = self.output_model.response(messages=messages_for_output_model)
6452
+ model_response.content = output_model_response.content
6453
+
6454
+ def _generate_response_with_output_model_stream(
6455
+ self, run_response: RunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
6456
+ ):
6457
+ """Parse the model response using the output model."""
6458
+ from agno.utils.events import (
6459
+ create_output_model_response_completed_event,
6460
+ create_output_model_response_started_event,
6461
+ )
6462
+
6463
+ if self.output_model is None:
6464
+ return
6465
+
6466
+ if stream_intermediate_steps:
6467
+ yield self._handle_event(create_output_model_response_started_event(run_response), run_response)
6468
+
6469
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6470
+
6471
+ model_response = ModelResponse(content="")
6472
+
6473
+ for model_response_event in self.output_model.response_stream(messages=messages_for_output_model):
6474
+ yield from self._handle_model_response_chunk(
6475
+ run_response=run_response,
6476
+ model_response=model_response,
6477
+ model_response_event=model_response_event,
6478
+ )
6479
+
6480
+ if stream_intermediate_steps:
6481
+ yield self._handle_event(create_output_model_response_completed_event(run_response), run_response)
6482
+
6483
+ # Build a list of messages that should be added to the RunResponse
6484
+ messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
6485
+ # Update the RunResponse messages
6486
+ run_response.messages = messages_for_run_response
6487
+ # Update the RunResponse metrics
6488
+ run_response.metrics = self.aggregate_metrics_from_messages(messages_for_run_response)
6489
+
6490
+ async def _agenerate_response_with_output_model(self, model_response: ModelResponse, run_messages: RunMessages):
6491
+ """Parse the model response using the output model."""
6492
+ if self.output_model is None:
6493
+ return
6494
+
6495
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6496
+ output_model_response: ModelResponse = await self.output_model.aresponse(messages=messages_for_output_model)
6497
+ model_response.content = output_model_response.content
6498
+
6499
+ async def _agenerate_response_with_output_model_stream(
6500
+ self, run_response: RunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
6501
+ ):
6502
+ """Parse the model response using the output model."""
6503
+ from agno.utils.events import (
6504
+ create_output_model_response_completed_event,
6505
+ create_output_model_response_started_event,
6506
+ )
6507
+
6508
+ if self.output_model is None:
6509
+ return
6510
+
6511
+ if stream_intermediate_steps:
6512
+ yield self._handle_event(create_output_model_response_started_event(run_response), run_response)
6513
+
6514
+ messages_for_output_model = self.get_messages_for_output_model(run_messages.messages)
6515
+
6516
+ model_response = ModelResponse(content="")
6517
+
6518
+ model_response_stream = self.output_model.aresponse_stream(messages=messages_for_output_model)
6519
+
6520
+ async for model_response_event in model_response_stream:
6521
+ for event in self._handle_model_response_chunk(
6522
+ run_response=run_response,
6523
+ model_response=model_response,
6524
+ model_response_event=model_response_event,
6525
+ ):
6526
+ yield event
6527
+
6528
+ if stream_intermediate_steps:
6529
+ yield self._handle_event(create_output_model_response_completed_event(run_response), run_response)
6530
+
6531
+ # Build a list of messages that should be added to the RunResponse
6532
+ messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
6533
+ # Update the RunResponse messages
6534
+ run_response.messages = messages_for_run_response
6535
+ # Update the RunResponse metrics
6536
+ run_response.metrics = self.aggregate_metrics_from_messages(messages_for_run_response)
6537
+
6357
6538
  def _handle_event(self, event: RunResponseEvent, run_response: RunResponse):
6358
6539
  # We only store events that are not run_response_content events
6359
6540
  events_to_skip = [event.value for event in self.events_to_skip] if self.events_to_skip else []
@@ -6875,6 +7056,7 @@ class Agent:
6875
7056
  if stream:
6876
7057
  _response_content: str = ""
6877
7058
  _response_thinking: str = ""
7059
+ _response_reasoning_content: str = ""
6878
7060
  response_content_batch: Union[str, JSON, Markdown] = ""
6879
7061
  reasoning_steps: List[ReasoningStep] = []
6880
7062
 
@@ -6941,6 +7123,8 @@ class Agent:
6941
7123
  log_warning(f"Failed to convert response to JSON: {e}")
6942
7124
  if hasattr(resp, "thinking") and resp.thinking is not None:
6943
7125
  _response_thinking += resp.thinking
7126
+ if hasattr(resp, "reasoning_content") and resp.reasoning_content is not None:
7127
+ _response_reasoning_content += resp.reasoning_content
6944
7128
  if (
6945
7129
  hasattr(resp, "extra_data")
6946
7130
  and resp.extra_data is not None
@@ -7012,6 +7196,18 @@ class Agent:
7012
7196
  if render:
7013
7197
  live_log.update(Group(*panels))
7014
7198
 
7199
+ if len(_response_reasoning_content) > 0:
7200
+ render = True
7201
+ # Create panel for reasoning content
7202
+ reasoning_panel = create_panel(
7203
+ content=Text(_response_reasoning_content),
7204
+ title=f"Reasoning ({response_timer.elapsed:.1f}s)",
7205
+ border_style="green",
7206
+ )
7207
+ panels.append(reasoning_panel)
7208
+ if render:
7209
+ live_log.update(Group(*panels))
7210
+
7015
7211
  # Add tool calls panel if available
7016
7212
  if (
7017
7213
  self.show_tool_calls
@@ -7324,6 +7520,7 @@ class Agent:
7324
7520
  if stream:
7325
7521
  _response_content: str = ""
7326
7522
  _response_thinking: str = ""
7523
+ _response_reasoning_content: str = ""
7327
7524
  reasoning_steps: List[ReasoningStep] = []
7328
7525
  response_content_batch: Union[str, JSON, Markdown] = ""
7329
7526
 
@@ -7391,6 +7588,8 @@ class Agent:
7391
7588
  log_warning(f"Failed to convert response to JSON: {e}")
7392
7589
  if resp.thinking is not None:
7393
7590
  _response_thinking += resp.thinking
7591
+ if hasattr(resp, "reasoning_content") and resp.reasoning_content is not None:
7592
+ _response_reasoning_content += resp.reasoning_content
7394
7593
 
7395
7594
  if (
7396
7595
  hasattr(resp, "extra_data")
@@ -7464,6 +7663,18 @@ class Agent:
7464
7663
  if render:
7465
7664
  live_log.update(Group(*panels))
7466
7665
 
7666
+ if len(_response_reasoning_content) > 0:
7667
+ render = True
7668
+ # Create panel for reasoning content
7669
+ reasoning_panel = create_panel(
7670
+ content=Text(_response_reasoning_content),
7671
+ title=f"Reasoning ({response_timer.elapsed:.1f}s)",
7672
+ border_style="green",
7673
+ )
7674
+ panels.append(reasoning_panel)
7675
+ if render:
7676
+ live_log.update(Group(*panels))
7677
+
7467
7678
  # Add tool calls panel if available
7468
7679
  if (
7469
7680
  self.show_tool_calls