From c02eb5df2e7e31c69e8e9a18cb1ebbb08ed29dba Mon Sep 17 00:00:00 2001 From: SWAPI03 Date: Thu, 15 Oct 2026 11:48:36 +0530 Subject: [PATCH] fix: save and load chat conversations as UTF-8 The interactive proxy chat /save command wrote conversations with json.dump's default ensure_ascii=True and no file encoding, so non-ASCII content (any non-English message, emoji) was stored as unreadable \uXXXX escapes, and /load read the file with the platform default encoding. Write with ensure_ascii=False and open both /save and /load as UTF-8 so conversations are stored readably and round-trip on every platform, matching how the rest of the codebase persists JSON. --- litellm/proxy/client/cli/commands/chat.py | 6 ++--- .../proxy/client/cli/test_chat_commands.py | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 tests/unit/proxy/client/cli/test_chat_commands.py diff --git a/litellm/proxy/client/cli/commands/chat.py b/litellm/proxy/client/cli/commands/chat.py index 780695a37bb..9eec992b12d 100644 --- a/litellm/proxy/client/cli/commands/chat.py +++ b/litellm/proxy/client/cli/commands/chat.py @@ -301,8 +301,8 @@ def _save_conversation(console: Console, messages: list[dict[str, str]], command filename += ".json" try: - with open(filename, "w") as f: - json.dump(messages, f, indent=2) + with open(filename, "w", encoding="utf-8") as f: + json.dump(messages, f, indent=2, ensure_ascii=False) console.print(f"[green]Conversation saved to {filename}[/green]") except Exception as e: console.print(f"[red]Error saving conversation: {e}[/red]") @@ -320,7 +320,7 @@ def _load_conversation(console: Console, command: str, system: str | None) -> li filename += ".json" try: - with open(filename, "r") as f: + with open(filename, "r", encoding="utf-8") as f: loaded: Final[_MessagesView] = {"messages": json.load(f)} console.print(f"[green]Conversation loaded from {filename}[/green]") return loaded["messages"] diff --git a/tests/unit/proxy/client/cli/test_chat_commands.py b/tests/unit/proxy/client/cli/test_chat_commands.py new file mode 100644 index 00000000000..0cf6c6908e1 --- /dev/null +++ b/tests/unit/proxy/client/cli/test_chat_commands.py @@ -0,0 +1,27 @@ +from pathlib import Path + +from rich.console import Console + +from litellm.proxy.client.cli.commands.chat import ( + _load_conversation, + _save_conversation, +) + + +def test_save_and_load_conversation_roundtrips_non_ascii(tmp_path: Path) -> None: + """/save writes readable UTF-8 (not \\uXXXX escapes) and /load reads it back on any locale.""" + console = Console() + messages = [ + {"role": "user", "content": "Café ☕ 東京"}, + {"role": "assistant", "content": "日本語で返信します"}, + ] + target = tmp_path / "conversation.json" + + _save_conversation(console, messages, f"/save {target}") + + saved = target.read_text(encoding="utf-8") + assert "東京" in saved + assert "\\u" not in saved + + loaded = _load_conversation(console, f"/load {target}", system=None) + assert loaded == messages