Bug: AttributeError: 'dict' object has no attribute 'inline_data' when uploading a file to a custom ADK Agent via AgentSpace
Summary
When using AgentSpace and uploading a file, the system crashes during session initialization with:
AttributeError: 'dict' object has no attribute 'inline_data'
This happens in the newer save_artifact implementation of InMemoryArtifactService. The method assumes that the artifact argument is always a types.Part-like object with attributes such as .inline_data, .text, and .file_data, but in practice (during file upload) artifact can be a plain dict.
Because the code accesses artifact.inline_data directly, this raises an AttributeError and breaks the agent flow.
This regression does not happen with the older/previous save_artifact implementation, which simply stored the artifact without inspecting its fields.
Affected code
Current implementation (failing):
@override
async def save_artifact(
self,
*,
app_name: str,
user_id: str,
filename: str,
artifact: types.Part,
session_id: Optional[str] = None,
custom_metadata: Optional[dict[str, Any]] = None,
) -> int:
path = self._artifact_path(app_name, user_id, filename, session_id)
if path not in self.artifacts:
self.artifacts[path] = []
version = len(self.artifacts[path])
if self._file_has_user_namespace(filename):
canonical_uri = (
f"memory://apps/{app_name}/users/{user_id}/artifacts/{filename}/versions/{version}"
)
else:
canonical_uri = (
f"memory://apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{filename}/versions/{version}"
)
artifact_version = ArtifactVersion(
version=version,
canonical_uri=canonical_uri,
)
if custom_metadata:
artifact_version.custom_metadata = custom_metadata
# ── Problem starts here ──
if artifact.inline_data is not None:
artifact_version.mime_type = artifact.inline_data.mime_type
elif artifact.text is not None:
artifact_version.mime_type = "text/plain"
elif artifact.file_data is not None:
if artifact_util.is_artifact_ref(artifact):
if not artifact_util.parse_artifact_uri(artifact.file_data.file_uri):
raise ValueError(
f"Invalid artifact reference URI: {artifact.file_data.file_uri}"
)
# If it's a valid artifact URI, we store the artifact part as-is.
# And we don't know the mime type until we load it.
else:
artifact_version.mime_type = artifact.file_data.mime_type
else:
raise ValueError("Not supported artifact type.")
# ── End problematic block ──
self.artifacts[path].append(
_ArtifactEntry(data=artifact, artifact_version=artifact_version)
)
return version
Expected behavior
- Uploading a file to AgentSpace should create/save an artifact and continue the agent run without crashing.
save_artifactshould be resilient to the type ofartifactprovided by the upstream call.- If
artifactis represented as a dict, it should still be stored and versioned.
This is how the previous implementation behaved:
@override
async def save_artifact(
self,
*,
app_name: str,
user_id: str,
session_id: str,
filename: str,
artifact: types.Part,
) -> int:
path = self._artifact_path(app_name, user_id, session_id, filename)
if path not in self.artifacts:
self.artifacts[path] = []
version = len(self.artifacts[path])
self.artifacts[path].append(artifact)
return version
The previous version did not assume anything about artifact internals.
Actual behavior
When a file is uploaded and an agent session is started, we hit this traceback:
File ".../vertexai/agent_engines/templates/adk.py", line 1106, in streaming_agent_run_with_events
session = await self._init_session(
File ".../vertexai/agent_engines/templates/adk.py", line 636, in _init_session
saved_version = await artifact_service.save_artifact(
File ".../google/adk/artifacts/in_memory_artifact_service.py", line 121, in save_artifact
if artifact.inline_data is not None:
AttributeError: 'dict' object has no attribute 'inline_data'
So artifact is a plain dict, but save_artifact unconditionally dereferences artifact.inline_data.
Root cause
-
The new
save_artifactimplementation inspects the artifact to infermime_type:artifact.inline_data.mime_typeartifact.textartifact.file_data.mime_type
-
This only works if
artifactis an object with those attributes (e.g. atypes.Part/ protocol buffer style object). -
During AgentSpace upload, the artifact is passed in as a plain Python
dict. Dicts do not expose attributes like.inline_data, soartifact.inline_dataraisesAttributeError.
In short: the function assumes a stricter artifact shape than what the caller actually sends.
Impact
- AgentSpace cannot accept uploaded files without crashing.
- This blocks basic “upload context” / “upload document to session” flows.
Proposed fix
Safely access inline_data, text, and file_data using getattr(...) so that dict-like artifacts don’t immediately cause an AttributeError. Fallback to best-effort MIME type inference.
Example patch:
@override
async def save_artifact(
self,
*,
app_name: str,
user_id: str,
filename: str,
artifact: types.Part,
session_id: Optional[str] = None,
custom_metadata: Optional[dict[str, Any]] = None,
) -> int:
path = self._artifact_path(app_name, user_id, filename, session_id)
if path not in self.artifacts:
self.artifacts[path] = []
version = len(self.artifacts[path])
if self._file_has_user_namespace(filename):
canonical_uri = (
f"memory://apps/{app_name}/users/{user_id}/artifacts/{filename}/versions/{version}"
)
else:
canonical_uri = (
f"memory://apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{filename}/versions/{version}"
)
artifact_version = ArtifactVersion(
version=version,
canonical_uri=canonical_uri,
)
if custom_metadata:
artifact_version.custom_metadata = custom_metadata
# --- Safer field extraction ---
inline_data = getattr(artifact, "inline_data", None)
text = getattr(artifact, "text", None)
file_data = getattr(artifact, "file_data", None)
if inline_data is not None:
artifact_version.mime_type = inline_data.mime_type
elif text is not None:
artifact_version.mime_type = "text/plain"
elif file_data is not None:
if artifact_util.is_artifact_ref(artifact):
if not artifact_util.parse_artifact_uri(file_data.file_uri):
raise ValueError(
f"Invalid artifact reference URI: {file_data.file_uri}"
)
# valid artifact ref: keep as-is, mime type may be resolved later
else:
artifact_version.mime_type = file_data.mime_type
else:
# Fallback: dict payloads / unknown structure
# We still want to save the artifact for backward compatibility
artifact_version.mime_type = "application/octet-stream"
self.artifacts[path].append(
_ArtifactEntry(data=artifact, artifact_version=artifact_version)
)
return version
Why this helps:
getattr(...)won’t throw ifartifactis a dict or any object without that attribute.- We preserve the newer behavior (versioning, canonical URI, setting
mime_type) when the artifact is a propertypes.Part. - We gracefully fall back to
"application/octet-stream"when the artifact is not atypes.Part, instead of crashing.
@xuanyang15 Please have a look into it
@DeanChensj Could you please take a look when you get a chance?
Hi eng team from Gemini Enterprise is taking a look at this issue
Is this resolved or not yet?
Very interested as well. When using the BuiltInCodeExecutor, when it generates an image, any follow up question instantly crashes the agent in Gemini Enterprise with Error during async stream generation: 'dict' object has no attribute 'inline_data'.
Using ADK 1.19.0.
same here