项目文件夹

文件
Simon Willison baefe337ad
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (macos-latest, 3.14) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.14) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.14) (push) Has been cancelled
Initial attempt at log serialization docs
Refs https://github.com/simonw/llm/pull/1411
2026-04-28 16:35:24 -07:00

7.7 KiB

(serialization)=

Serialization wire format

LLM provides JSON-safe serialization for Response, Message, and Part objects through to_dict() / from_dict() methods. The exact shapes of the resulting dicts are defined as TypedDicts in the llm.serialization module.

These TypedDicts:

  • Annotate every to_dict() / from_dict() method, so static type-checkers, IDE autocomplete, and pydantic's TypeAdapter work out of the box.
  • Document the keys that may appear in each payload and which are required.
  • Are erased at runtime — they have zero overhead and no extra dependencies.

(serialization-using)=

Using the TypedDicts

Annotate functions that produce or consume serialized payloads:

import json
from pathlib import Path

import llm
from llm.serialization import ResponseDict


def store_turn(payload: ResponseDict) -> None:
    Path("turn.json").write_text(json.dumps(payload))


model = llm.get_model("gpt-5.4-mini")
response = model.prompt("Hi")
response.text()
store_turn(response.to_dict())

Validate untrusted payloads at runtime via pydantic:

from pydantic import TypeAdapter
from llm.serialization import MessageDict

incoming = json.loads(some_payload)
validated = TypeAdapter(MessageDict).validate_python(incoming)

Or export a JSON Schema for cross-language consumers:

schema = TypeAdapter(MessageDict).json_schema()

(serialization-reference)=

Reference

The Part shapes are listed first, since they nest inside the rest. Required keys must be present in every payload; optional keys may be omitted.

AttachmentDict

Nested attachment payload. All fields optional — an Attachment may carry a type, a url, a path, and/or base64-encoded content.

type optional
str
url optional
str
path optional
str
content optional
str

TextPartDict

type required
Literal['text']
text required
str
provider_metadata optional
Dict[str, Any]

ReasoningPartDict

type required
Literal['reasoning']
text required
str
redacted optional
bool
provider_metadata optional
Dict[str, Any]

ToolCallPartDict

type required
Literal['tool_call']
name required
str
arguments required
Dict[str, Any]
tool_call_id optional
str
server_executed optional
bool
provider_metadata optional
Dict[str, Any]

ToolResultPartDict

type required
Literal['tool_result']
name required
str
output required
str
tool_call_id optional
str
server_executed optional
bool
exception optional
str
attachments optional
List[AttachmentDict]
provider_metadata optional
Dict[str, Any]

AttachmentPartDict

type required
Literal['attachment']
attachment optional
AttachmentDict
provider_metadata optional
Dict[str, Any]

PartDict

Discriminated union of all Part dict shapes — every value of type maps to exactly one TypedDict above.

type: "text"
TextPartDict
type: "reasoning"
ReasoningPartDict
type: "tool_call"
ToolCallPartDict
type: "tool_result"
ToolResultPartDict
type: "attachment"
AttachmentPartDict

MessageDict

JSON-safe form of llm.Message.

role is one of "user", "assistant", "system", "tool" in practice — typed as str here to leave room for provider-specific values.

role required
str
parts required
List[TextPartDict | ReasoningPartDict | ToolCallPartDict | ToolResultPartDict | AttachmentPartDict]
provider_metadata optional
Dict[str, Any]

PromptDict

The prompt sub-dict of Response.to_dict() — captures the full input chain that was sent for this turn plus any options that apply.

messages required
List[MessageDict]
options optional
Dict[str, Any]
system optional
str

UsageDict

Optional usage block on ResponseDict. All fields optional; providers vary in which they report.

input optional
int
output optional
int
details optional
Dict[str, Any]

ResponseDict

JSON-safe form of llm.Response — everything needed for Response.from_dict to rehydrate and response.reply() to continue a conversation across a process boundary.

model required
str
prompt required
PromptDict
messages required
List[MessageDict]
id optional
str
usage optional
UsageDict
datetime_utc optional
str

(serialization-notes)=

Notes

  • All TypedDicts use NotRequired[...] for optional keys (via typing_extensions, which is a transitive dependency through pydantic). On Python 3.11+ this comes from the standard library typing module.
  • AttachmentDict.content is base64-encoded when the attachment was constructed from raw bytes — that's how binary attachments survive the JSON round-trip.
  • ResponseDict.id, usage, and datetime_utc are present on freshly serialized responses but optional on hand-constructed ones — Response.from_dict() will accept either.
  • The provider_metadata key on every Part and Message is opaque by design. It carries provider-specific signatures (Anthropic extended-thinking signatures, Gemini thoughtSignature, OpenAI encrypted_content) that need to round-trip verbatim across turns. See the "Restoring opaque metadata on subsequent requests" section in {doc}plugins/advanced-model-plugins for how plugins use this on the wire.