Serialization

Copy Markdown

Sycophant provides JSON round-trip serialization for all core structs via the Sycophant.Serializable protocol. This enables persisting conversation state to a database and restoring it later.

Round-trip Example

alias Sycophant.{Context, Serializable.Decoder}

# After a conversation
{:ok, response} = Sycophant.generate_text("openai:gpt-4o-mini", messages)

# Serialize to JSON
json = Decoder.encode(response)

# Store in database, cache, etc.
MyRepo.insert(%Conversation{state: json})

# Later, restore and continue
json = MyRepo.get(Conversation, id).state
restored = Decoder.decode(json)

ctx = restored.context |> Context.add(Message.user("Continue our chat"))
{:ok, continued} = Sycophant.generate_text("openai:gpt-4o-mini", ctx)

Supported Structs

All core structs implement the Sycophant.Serializable protocol:

  • Response
  • Context
  • Message
  • Message.Content.Text
  • Message.Content.Image
  • Tool
  • ToolCall
  • Usage
  • Pricing
  • Pricing.Component
  • Reasoning
  • EmbeddingRequest
  • EmbeddingResponse
  • EmbeddingParams

Type Discriminators

Each serialized map includes a "__type__" key that identifies the struct type for deserialization:

Sycophant.Serializable.to_map(%Sycophant.Usage{input_tokens: 10, output_tokens: 25})
#=> %{"__type__" => "Usage", "input_tokens" => 10, "output_tokens" => 25}

Tool Registry

Function references cannot be serialized to JSON. When decoding Tool structs, pass a :tool_registry option to restore function references:

registry = %{
  "get_weather" => &MyApp.Weather.get/1,
  "search" => &MyApp.Search.query/1
}

restored = Decoder.decode(json, tool_registry: registry)

Tools decoded without a registry will have function: nil and behave as manual tools.

Working with Maps

Use from_map/2 when you already have parsed maps (e.g., from a JSON column in your database):

map = %{"__type__" => "Usage", "input_tokens" => 10, "output_tokens" => 25}
usage = Decoder.from_map(map)
#=> %Sycophant.Usage{input_tokens: 10, output_tokens: 25}