fix(EVO-2181): let images reach the model (not only audio) - #44
Merged
Conversation
process_files built an inline_data Blob for every file and saved it to artifacts,
but appended it to file_parts only `if is_audio`. So an image was blobbed + saved
yet never sent to the LLM, and create_content("", file_parts) returned None ->
the agent replied "No content to process". Append EVERY file part (image/audio/
video/...) to file_parts; keep the is_audio branch only for the log label. Audio
behavior is unchanged (it was already appended). Part of EVO-2178 (image end-to-end).
- runner_utils.py: unconditional file_parts.append after the artifact save.
- tests/unit/test_media_file_parts.py: image appended; audio still appended; both;
create_content("", [image]) is not None (regression guard); create_content("", []) None.
Reviewer's GuideEnsures all uploaded media files (not just audio) are added as parts sent to the LLM and introduces unit tests verifying images, audio, and mixed media are correctly propagated through process_files and create_content. Sequence diagram for process_files sending all media types to the LLMsequenceDiagram
participant Agent as AgentRunner
participant Runner as RunnerUtils
participant Store as ArtifactStore
participant LLM as LLMService
Agent->>Runner: process_files(message_files)
loop for each file_data
Runner->>Store: save_artifact(filename, file_part)
Store-->>Runner: artifact_ref
Runner->>Runner: file_parts.append(file_part)
alt [is_audio]
Runner->>Runner: logger.info(Added audio file ...)
else [not is_audio]
Runner->>Runner: logger.info(Added file ...)
end
end
Agent->>Runner: create_content("", file_parts)
Runner-->>Agent: content
Agent->>LLM: send content
LLM-->>Agent: response
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- Using
asyncio.runinside unit tests can conflict with existing event loops in some runners; consider leveraging pytest’s async support or a shared helper to run coroutines more safely. - The new test helpers (
_utils,_artifacts,_file,_run) are tightly coupled to this single test module; if they’re generally useful, consider centralizing or parametrizing them to reduce duplication and make future media-type extensions easier.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Using `asyncio.run` inside unit tests can conflict with existing event loops in some runners; consider leveraging pytest’s async support or a shared helper to run coroutines more safely.
- The new test helpers (`_utils`, `_artifacts`, `_file`, `_run`) are tightly coupled to this single test module; if they’re generally useful, consider centralizing or parametrizing them to reduce duplication and make future media-type extensions easier.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
The unconditional append from 84aab90 fixed the image but opened a worse failure: every file now goes to the model as inline_data, and google-adk's LiteLlm -- which every LLM agent is built on (llm_agent_builder) -- raises ValueError for a mime type it cannot carry. That ValueError reaches standard_runner's handler and becomes an InternalServerError, so an ordinary WhatsApp document (docx/xlsx/zip) now costs the whole turn: before 84aab90 the same message was answered, the file was just dropped. A caller that omits `mimeType` hits the same path -- a2a_routes.extract_files_from_message defaults to application/octet-stream. - runner_utils.py: `_inline_skip_reason` gates the append on what ADK actually converts (text//image//audio//video/ + application/pdf + application/json) and on per-file / per-request byte ceilings mirroring the bot-runtime bounds (ai_adapter.go). A skipped file is still saved as an artifact and logged with the reason; the rest of the message still gets an answer. - runner_utils.py: the append moved ahead of save_artifact, so a failing artifact store can no longer swallow the file and bring the original bug back. - test_media_file_parts.py: unreadable types stay out (docx/zip/octet-stream/ empty) while the caption still reaches the model; an unreadable file does not drop the image beside it; pdf/text still travel; mime parameters ("audio/webm;codecs=opus") are normalized for the check and verbatim on the Blob; the image survives an artifact store failure; both byte ceilings; and a contract test running every forwarded type through the installed ADK's _get_content, so an ADK bump that narrows the set fails here, not in front of a customer. Unit suite: 239 passed (was 227).
gomessguii
approved these changes
Jul 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
EVO-2181 — imagem não chega ao modelo (gate
if is_audioemprocess_files)Sub-issue de EVO-2178 (agente não processa mídia). Este é o ponto que produz o "No content to process".
Root cause
src/services/adk/runners/runner_utils.pyprocess_filescriaPart(inline_data=Blob(...))e salva no artifact store para todo arquivo, mas ofile_parts.append(file_part)estava dentro deif is_audio:. Para imagem, o Blob é criado e salvo, mas nunca appendado →create_content("", file_parts)recebefile_partsvazio + texto vazio → retornaNone→ "No content to process".Correção
Appendar todo file part já blobado (imagem/áudio/vídeo), mantendo o
save_artifact. Ois_audiofica só para o label do log. Áudio inalterado (já era appendado).extract_files_from_messagenão muda (já aceitabytes).Testes (
tests/unit/test_media_file_parts.py)imagem appendada · áudio ainda appendado · imagem+áudio ambos ·
create_content("", [imagem])não-None(trava a regressão) ·create_content("", [])None.Local:
5 passed; suíte unitária 227 passed (ignorandotest_exception_handlers.pypré-quebrado). CI do repo é docker-only.Depende de (para funcionar ponta-a-ponta)
EVO-2179 (CRM envia attachments) + EVO-2180 (bot_runtime encaminha como file part com
bytes). Sozinho, este fix não regride nada e prepara a imagem para quando os bytes chegarem.Summary by Sourcery
Ensure all uploaded media files are forwarded to the model instead of only audio files.
Bug Fixes:
Enhancements:
Tests: