Normandy.LLM.JsonDeserializer (normandy v0.2.0)

View Source

JSON deserialization helper with automatic error recovery.

This module provides robust JSON deserialization with:

  • Automatic retry on JSON parse errors
  • Error feedback to LLM via system prompt augmentation
  • Configurable retry attempts
  • Support for nested/double-encoded JSON

Usage with Agent

# Enable in agent config
agent = BaseAgent.init(%{
  client: client,
  model: "claude-3-5-sonnet-20241022",
  temperature: 0.7,
  enable_json_retry: true,           # Enable automatic retry
  json_retry_max_attempts: 3         # Optional: default is 2
})

# The agent will automatically retry on JSON parse errors
{agent, response} = BaseAgent.run(agent, input)

Manual Usage

# With default retries (2)
{:ok, schema} = JsonDeserializer.deserialize_with_retry(
  raw_content,
  schema,
  client,
  model,
  temperature,
  max_tokens,
  messages
)

# With custom retries
{:ok, schema} = JsonDeserializer.deserialize_with_retry(
  raw_content,
  schema,
  client,
  model,
  temperature,
  max_tokens,
  messages,
  max_retries: 3
)

How It Works

  1. Attempts to parse JSON response
  2. On error, augments system prompt with error details
  3. Retries LLM call with error feedback
  4. Repeats until success or max_retries reached

Configuration

Options:

  • :max_retries - Maximum retry attempts (default: 2)
  • :tools - Tool schemas to include in retry
  • :adapter - JSON adapter module (default: from :normandy app config)

Summary

Functions

Parse and validate JSON content without retry.

Functions

deserialize_with_retry(content, schema, client, model, temperature, max_tokens, messages, opts \\ [])

@spec deserialize_with_retry(
  String.t(),
  struct(),
  struct(),
  String.t(),
  float() | nil,
  integer() | nil,
  list(),
  keyword()
) :: {:ok, struct()} | {:error, term()}

Deserialize JSON content with automatic retry on errors.

If initial deserialization fails, this function:

  1. Extracts the error message
  2. Augments the system prompt with error details
  3. Calls the LLM again with feedback
  4. Attempts deserialization again

Parameters

  • content - Raw string content from LLM
  • schema - Target schema struct to populate
  • client - LLM client
  • model - Model name
  • temperature - Temperature setting
  • max_tokens - Max tokens
  • messages - Original message history
  • opts - Options (:max_retries, :tools, :adapter)

Returns

  • {:ok, populated_schema} - Success
  • {:error, reason} - Failed after all retries

parse_and_validate(content, schema, opts \\ [])

@spec parse_and_validate(String.t(), struct(), keyword()) ::
  {:ok, struct()} | {:error, term()}

Parse and validate JSON content without retry.

This is a simpler version of deserialize_with_retry/8 that performs one-shot parsing and validation without LLM retry. Useful for parsing final LLM responses where retry is not needed.

Parameters

  • content - Raw string content from LLM
  • schema - Target schema struct to populate
  • opts - Options (:adapter)

Returns

  • {:ok, populated_schema} - Success
  • {:error, reason} - Parsing or validation failed

Examples

iex> schema = %BaseAgentOutputSchema{}
iex> JsonDeserializer.parse_and_validate(~s({"chat_message": "Hello"}), schema)
{:ok, %BaseAgentOutputSchema{chat_message: "Hello"}}

iex> JsonDeserializer.parse_and_validate("invalid json", schema)
{:error, {:json_parse_error, reason, content}}