Skip to content

fix(artifacts): store text payloads byte-exact in FileArtifactService - #7917

Closed
TINGyu123644 wants to merge 4 commits into
google:mainfrom
TINGyu123644:fix/issue-7914
Closed

TINGyu123644 wants to merge 4 commits into
google:mainfrom
TINGyu123644:fix/issue-7914

Conversation

@TINGyu123644

Copy link
Copy Markdown

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

2. Or, if no issue exists, describe the change:

Problem:
FileArtifactService changes the line endings of text artifacts. A text artifact containing \r\n or a lone \r loads back with \n in their place: the payload is read with Path.read_text, whose universal-newline mode rewrites both, and written with Path.write_text, which translates \n to os.linesep (so on Windows every \n is stored as \r\n). InMemoryArtifactService and GcsArtifactService return the saved text unchanged, so the same artifact differs only on the file backend - which is what adk web and adk run use by default (use_local_storage=True, via create_local_artifact_service). CSV files (RFC 4180 uses CRLF), patches, and Windows-authored text a tool saves therefore load back different from what was saved.

Solution:
Read the payload with read_bytes().decode("utf-8") and write it with write_bytes(text.encode("utf-8")) so the round trip is byte-exact on every platform, matching the in-memory and GCS backends. The staging write already used write_bytes for inline_data; this makes the text path consistent with it. The metadata document is JSON and is unaffected by newline translation, so its reader is left as is.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

Added test_save_load_preserves_crlf_and_cr_line_endings, parametrized over IN_MEMORY, GCS, and FILE, asserting "id,name\r\n1,a\r\n2,b\rc\n" round-trips unchanged. Local results:

# before the fix (new test only)
1 failed, 2 passed
FAILED test_save_load_preserves_crlf_and_cr_line_endings[ArtifactServiceType.FILE]

# after the fix
tests/unittests/artifacts/  ->  985 passed
tests/unittests/cli/utils/test_local_storage.py  ->  passed
tests/unittests/cli/utils/test_service_factory.py  ->  passed
  (3 pre-existing failures from a missing optional `a2a` module in this
   environment, unrelated to the change)

ruff check (repo config) and pyink --check are clean for both touched files.

Manual End-to-End (E2E) Tests:

The issue's reproducer, run against main and against this branch:

import asyncio, tempfile
from google.genai import types
from google.adk.artifacts.file_artifact_service import FileArtifactService
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService

async def main():
  text = "id,name\r\n1,a\r\n2,b\rc\n"
  for svc in (FileArtifactService(root_dir=tempfile.mkdtemp()), InMemoryArtifactService()):
    await svc.save_artifact(app_name="a", user_id="u", session_id="s",
                            filename="t.csv", artifact=types.Part(text=text))
    got = await svc.load_artifact(app_name="a", user_id="u", session_id="s",
                                  filename="t.csv")
    print(type(svc).__name__, repr(got.text), got.text == text)

asyncio.run(main())

Before: FileArtifactService 'id,name\n1,a\n2,b\nc\n' False / InMemoryArtifactService ... True
After: both print True.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

The change was drafted with an AI coding assistant and reviewed before submission.

FileArtifactService rewrote the line endings of text artifacts. The
payload was read with Path.read_text, whose universal-newline mode
translates "\r\n" and "\r" to "\n", and written with Path.write_text,
which translates "\n" to os.linesep - so on Windows every "\n" was
stored as "\r\n". InMemoryArtifactService and GcsArtifactService return
the saved text unchanged, so the same artifact loaded back differently
depending on the backend, and FileArtifactService is the default for
`adk web` and `adk run`. CSV files (RFC 4180 uses CRLF), patches, and
Windows-authored text therefore loaded back different from what was
saved.

Read the payload with read_bytes().decode("utf-8") and write it with
write_bytes(text.encode("utf-8")) so the round trip is byte-exact on
every platform, matching the in-memory backend.

Adds a regression test parametrized over all three backends asserting
"id,name\r\n1,a\r\n2,b\rc\n" round-trips unchanged.

Fixes google#7914

The change was drafted with an AI coding assistant and reviewed before
submission.
@google-cla

google-cla Bot commented Nov 1, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@GWeale GWeale self-assigned this Nov 4, 2026
@GWeale
GWeale self-requested a review November 4, 2026 18:40
@GWeale

GWeale commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

thanks for sending this over, @TINGyu123644. #7915 makes the same change to FileArtifactService to read and write text artifacts as raw utf-8 bytes, and since that one is already approved in review, we're going ahead with it and closing this one. please open a new issue if you still see this once it lands.

@GWeale GWeale closed this Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FileArtifactService changes CRLF and CR line endings in text artifacts

3 participants