Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions libs/agno/agno/agent/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,12 +1517,12 @@ def handle_model_response_chunk(
if model_response.images is None:
model_response.images = []
model_response.images.extend(model_response_event.images)
# Store media in run_response if store_media is enabled
if agent.store_media:
for image in model_response_event.images:
if run_response.images is None:
run_response.images = []
run_response.images.append(image)
# Always store media in run_response for the caller;
# store_media only controls DB persistence (handled by cleanup_and_store)
for image in model_response_event.images:
if run_response.images is None:
run_response.images = []
run_response.images.append(image)

# Handle tool interruption events (HITL flow)
elif model_response_event.event == ModelResponseEvent.tool_call_paused.value:
Expand Down
61 changes: 38 additions & 23 deletions libs/agno/agno/agent/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,9 +556,8 @@ def _run(
user_id=user_id,
)

# 8. Store media if enabled
if agent.store_media:
store_media_util(run_response, model_response)
# 8. Store media in run output for the caller
store_media_util(run_response, model_response)

# 9. Convert the response to the structured format if needed
convert_response_to_structured_format(agent, run_response, run_context=run_context)
Expand Down Expand Up @@ -1635,9 +1634,8 @@ async def _arun(
# 11. Convert the response to the structured format if needed
convert_response_to_structured_format(agent, run_response, run_context=run_context)

# 12. Store media if enabled
if agent.store_media:
store_media_util(run_response, model_response)
# 12. Store media in run output for the caller
store_media_util(run_response, model_response)

# 13. Execute post-hooks (after output is generated but before response is returned)
if agent.post_hooks is not None:
Expand Down Expand Up @@ -2953,9 +2951,8 @@ def _continue_run(
# 4. Convert the response to the structured format if needed
convert_response_to_structured_format(agent, run_response, run_context=run_context)

# 5. Store media if enabled
if agent.store_media:
store_media_util(run_response, model_response)
# 5. Store media in run output for the caller
store_media_util(run_response, model_response)

# 6. Execute post-hooks
if agent.post_hooks is not None:
Expand Down Expand Up @@ -3710,9 +3707,8 @@ async def _acontinue_run(
# 10. Convert the response to the structured format if needed
convert_response_to_structured_format(agent, run_response, run_context=run_context)

# 11. Store media if enabled
if agent.store_media:
store_media_util(run_response, model_response)
# 11. Store media in run output for the caller
store_media_util(run_response, model_response)

await araise_if_cancelled(run_response.run_id) # type: ignore

Expand Down Expand Up @@ -4402,11 +4398,20 @@ def cleanup_and_store(
run_context: Optional[RunContext] = None,
user_id: Optional[str] = None,
) -> None:
import copy

from agno.agent import _session
from agno.run.approval import update_approval_run_status

# Scrub the stored run based on storage flags
scrub_run_output_for_storage(agent, run_response)
# Scrub a shallow copy for storage — the original run_response is never
# mutated so the caller always sees generated media regardless of store_media.
storage_copy = copy.copy(run_response)
scrub_run_output_for_storage(agent, storage_copy)
if not agent.store_media:
storage_copy.images = None
storage_copy.videos = None
storage_copy.audio = None
storage_copy.files = None

# Stop the timer for the Run duration
if run_response.metrics:
Expand All @@ -4416,18 +4421,19 @@ def cleanup_and_store(
# This ensures RunOutput reflects all tool modifications
if run_context is not None and run_context.session_state is not None:
run_response.session_state = run_context.session_state
storage_copy.session_state = run_context.session_state

# Optional: Save output to file if save_response_to_file is set
save_run_response_to_file(
agent,
run_response=run_response,
run_response=storage_copy,
input=run_response.input.input_content_string() if run_response.input else "",
session_id=session.session_id,
user_id=user_id,
)

# Add RunOutput to Agent Session
session.upsert_run(run=run_response)
# Add scrubbed RunOutput to Agent Session
session.upsert_run(run=storage_copy)

# Calculate session metrics
update_session_metrics(agent, session=session, run_response=run_response)
Expand Down Expand Up @@ -4455,32 +4461,41 @@ async def acleanup_and_store(
run_context: Optional[RunContext] = None,
user_id: Optional[str] = None,
) -> None:
import copy

from agno.agent import _session
from agno.run.approval import aupdate_approval_run_status

# Scrub the stored run based on storage flags
scrub_run_output_for_storage(agent, run_response)
# Scrub a shallow copy for storage — the original run_response is never
# mutated so the caller always sees generated media regardless of store_media.
storage_copy = copy.copy(run_response)
scrub_run_output_for_storage(agent, storage_copy)
if not agent.store_media:
storage_copy.images = None
storage_copy.videos = None
storage_copy.audio = None
storage_copy.files = None

# Stop the timer for the Run duration
if run_response.metrics:
run_response.metrics.stop_timer()

# Update run_response.session_state before saving
# This ensures RunOutput reflects all tool modifications
if run_context is not None and run_context.session_state is not None:
run_response.session_state = run_context.session_state
storage_copy.session_state = run_context.session_state

# Optional: Save output to file if save_response_to_file is set
save_run_response_to_file(
agent,
run_response=run_response,
run_response=storage_copy,
input=run_response.input.input_content_string() if run_response.input else "",
session_id=session.session_id,
user_id=user_id,
)

# Add RunOutput to Agent Session
session.upsert_run(run=run_response)
# Add scrubbed RunOutput to Agent Session
session.upsert_run(run=storage_copy)

# Calculate session metrics
update_session_metrics(agent, session=session, run_response=run_response)
Expand Down
64 changes: 39 additions & 25 deletions libs/agno/agno/team/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ def _run_tasks(

# === Post-loop ===

# Store media if enabled
if team.store_media and model_response is not None:
# Always add media to run_response for caller availability
if model_response is not None:
store_media_util(run_response, model_response)

# Convert response to structured format
Expand Down Expand Up @@ -1152,9 +1152,8 @@ def _run(
team, run_response=run_response, session=session, run_context=run_context
)

# 8. Store media if enabled
if team.store_media:
store_media_util(run_response, model_response)
# 8. Always add media to run_response for caller availability
store_media_util(run_response, model_response)

# 9. Convert response to structured format
_convert_response_to_structured_format(team, run_response=run_response, run_context=run_context)
Expand Down Expand Up @@ -2143,8 +2142,8 @@ async def _arun_tasks(

# === Post-loop ===

# Store media if enabled
if team.store_media and model_response is not None:
# Always add media to run_response for caller availability
if model_response is not None:
store_media_util(run_response, model_response)

# Convert response to structured format
Expand Down Expand Up @@ -2986,9 +2985,8 @@ async def _arun(
team, run_response=run_response, session=team_session, run_context=run_context
)

# 8. Store media if enabled
if team.store_media:
store_media_util(run_response, model_response)
# 8. Always add media to run_response for caller availability
store_media_util(run_response, model_response)

# 9. Convert response to structured format
_convert_response_to_structured_format(team, run_response=run_response, run_context=run_context)
Expand Down Expand Up @@ -3916,11 +3914,20 @@ def _cleanup_and_store(
session: TeamSession,
run_context: Optional[RunContext] = None,
) -> None:
# Scrub the stored run based on storage flags
import copy

from agno.run.approval import update_approval_run_status
from agno.team._session import update_session_metrics

scrub_run_output_for_storage(team, run_response)
# Scrub a shallow copy for storage — the original run_response is never
# mutated so the caller always sees generated media regardless of store_media.
storage_copy = copy.copy(run_response)
scrub_run_output_for_storage(team, storage_copy)
if not team.store_media:
storage_copy.images = None
storage_copy.videos = None
storage_copy.audio = None
storage_copy.files = None

# Stop the timer for the Run duration
if run_response.metrics:
Expand All @@ -3929,9 +3936,10 @@ def _cleanup_and_store(
# Update run_response.session_state before saving
if run_context is not None and run_context.session_state is not None:
run_response.session_state = run_context.session_state
storage_copy.session_state = run_context.session_state

# Add RunOutput to Team Session
session.upsert_run(run_response=run_response)
# Add scrubbed RunOutput to Team Session
session.upsert_run(run_response=storage_copy)

# Calculate session metrics
update_session_metrics(team, session=session, run_response=run_response)
Expand All @@ -3947,7 +3955,6 @@ def _cleanup_and_store(
team.save_session(session=session)

# Update approval run_status if this run has an associated approval.
# This is a no-op if no approval exists for this run_id.
if run_response.status is not None and run_response.run_id is not None:
update_approval_run_status(team.db, run_response.run_id, run_response.status)

Expand All @@ -3958,11 +3965,20 @@ async def _acleanup_and_store(
session: TeamSession,
run_context: Optional[RunContext] = None,
) -> None:
# Scrub the stored run based on storage flags
import copy

from agno.run.approval import aupdate_approval_run_status
from agno.team._session import update_session_metrics

scrub_run_output_for_storage(team, run_response)
# Scrub a shallow copy for storage — the original run_response is never
# mutated so the caller always sees generated media regardless of store_media.
storage_copy = copy.copy(run_response)
scrub_run_output_for_storage(team, storage_copy)
if not team.store_media:
storage_copy.images = None
storage_copy.videos = None
storage_copy.audio = None
storage_copy.files = None

# Stop the timer for the Run duration
if run_response.metrics:
Expand All @@ -3971,9 +3987,10 @@ async def _acleanup_and_store(
# Update run_response.session_state before saving
if run_context is not None and run_context.session_state is not None:
run_response.session_state = run_context.session_state
storage_copy.session_state = run_context.session_state

# Add RunOutput to Team Session
session.upsert_run(run_response=run_response)
# Add scrubbed RunOutput to Team Session
session.upsert_run(run_response=storage_copy)

# Calculate session metrics
update_session_metrics(team, session=session, run_response=run_response)
Expand All @@ -3989,7 +4006,6 @@ async def _acleanup_and_store(
await team.asave_session(session=session)

# Update approval run_status if this run has an associated approval.
# This is a no-op if no approval exists for this run_id.
if run_response.status is not None and run_response.run_id is not None:
await aupdate_approval_run_status(team.db, run_response.run_id, run_response.status)

Expand Down Expand Up @@ -4967,9 +4983,8 @@ def _continue_run(
# Convert to structured format
_convert_response_to_structured_format(team, run_response=run_response, run_context=run_context)

# Store media
if team.store_media:
store_media_util(run_response, model_response)
# Always add media to run_response for caller availability
store_media_util(run_response, model_response)

# Execute post-hooks
if team.post_hooks is not None:
Expand Down Expand Up @@ -5621,8 +5636,7 @@ async def _acontinue_run(

_convert_response_to_structured_format(team, run_response=run_response, run_context=run_context)

if team.store_media:
store_media_util(run_response, model_response)
store_media_util(run_response, model_response)

elif member_results:
# Member-only: re-run team with results
Expand Down
7 changes: 7 additions & 0 deletions libs/agno/agno/utils/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,13 @@ def scrub_media_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -
for message in run_response.reasoning_messages:
scrub_media_from_message(message)

# 6. Null top-level output media fields
# Consumed by cleanup_and_store (with save/restore) and _scrub_member_responses (permanent)
run_response.images = None
run_response.videos = None
run_response.audio = None
run_response.files = None


def scrub_media_from_message(message: Message) -> None:
"""Remove all media from a Message object."""
Expand Down
Loading
Loading