Skip to content

Bug: AttributeError: 'dict' object has no attribute 'inline_data' when uploading a file to a custom ADK Agent via AgentSpace #3495

@Yash-rai-29

Description

@Yash-rai-29

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_artifact should be resilient to the type of artifact provided by the upstream call.
  • If artifact is 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_artifact implementation inspects the artifact to infer mime_type:

    • artifact.inline_data.mime_type
    • artifact.text
    • artifact.file_data.mime_type
  • This only works if artifact is an object with those attributes (e.g. a types.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, so artifact.inline_data raises AttributeError.

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 if artifact is a dict or any object without that attribute.
  • We preserve the newer behavior (versioning, canonical URI, setting mime_type) when the artifact is a proper types.Part.
  • We gracefully fall back to "application/octet-stream" when the artifact is not a types.Part, instead of crashing.

Metadata

Metadata

Labels

agent engine[Component] This issue is related to Agent Engine deploymenthelp wanted[Community] Extra attention is neededservices[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions