Overview

ISONantic brings the power of Pydantic to ISON, providing:

Type-Safe Models

Define ISON schemas as Python classes with full type hints and IDE support.

Automatic Validation

Constraints, patterns, and custom validators ensure data integrity.

LLM Integration

Parse LLM outputs with error recovery and validation feedback.

Reference Resolution

Typed references between tables with automatic resolution.

Feature Pydantic ISONantic
Data Format JSON ISON (30-70% fewer tokens)
Model Definition BaseModel TableModel, ObjectModel
Field Constraints Field() Field() (compatible API)
Cross-References Manual Reference[T] with auto-resolution
LLM Parsing Not built-in parse_llm_output() with recovery

Installation

pip install isonantic

Or include directly in your project:

from isonantic import (
    TableModel, ObjectModel, Field, Reference,
    parse_ison, parse_llm_output, prompt_for_model
)

Models

ISONantic provides three model types for different ISON block types:

TableModel

For ISON tables with multiple rows:

from isonantic import TableModel, Field, Reference

class User(TableModel):
    __ison_block__ = "table.users"

    id: int = Field(primary_key=True)
    name: str = Field(min_length=1, max_length=100)
    email: str = Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
    team: Reference["Team"]
    active: bool = True

ObjectModel

For single-row configuration objects:

from isonantic import ObjectModel, Field

class Config(ObjectModel):
    __ison_block__ = "object.config"

    timeout: int = Field(ge=0, default=30)
    debug: bool = False
    version: str = "1.0"

Fields & Constraints

The Field() function supports Pydantic-compatible constraints:

from isonantic import Field

class Product(TableModel):
    __ison_block__ = "table.products"

    id: int = Field(primary_key=True)
    name: str = Field(min_length=1, max_length=200)
    price: float = Field(ge=0, le=10000)
    quantity: int = Field(ge=0, default=0)
    status: str = Field(choices=["active", "discontinued"])
    sku: str = Field(pattern=r"^[A-Z]{3}-\d{4}$")

Available Constraints

Parameter Type Description
primary_keyboolMark as primary key
uniqueboolValues must be unique
nullableboolAllow null values
defaultAnyDefault value
gt, ge, lt, lefloatNumeric bounds
min_length, max_lengthintString length
patternstrRegex pattern
choicesListAllowed values
descriptionstrField documentation

References

ISONantic provides typed references for cross-table relationships:

from isonantic import TableModel, Field, Reference

class Team(TableModel):
    __ison_block__ = "table.teams"
    id: int = Field(primary_key=True)
    name: str

class User(TableModel):
    __ison_block__ = "table.users"
    id: int = Field(primary_key=True)
    name: str
    team: Reference[Team]  # Typed reference!

# ISON data with references
ison = """
table.teams
id name
10 Engineering
20 "AI Research"

table.users
id name team
1 Alice :10
2 Bob :20
"""

# Parse and resolve
doc = MyDocument.parse(ison)
user = doc.users[0]
team = user.team.resolve(doc)  # Returns Team object
print(team.name)  # "Engineering"

Validators

@validator

Field-level validation and transformation:

from isonantic import TableModel, validator

class User(TableModel):
    __ison_block__ = "table.users"
    email: str

    @validator("email")
    def lowercase_email(cls, v):
        return v.lower()  # Transform value

    @validator("email")
    def validate_domain(cls, v):
        if not v.endswith("@company.com"):
            raise ValueError("Must use company email")
        return v

@root_validator

Cross-field validation:

from isonantic import TableModel, root_validator

class DateRange(TableModel):
    __ison_block__ = "table.ranges"
    start: int
    end: int

    @root_validator
    def validate_range(cls, values):
        if values.get("start", 0) > values.get("end", 0):
            raise ValueError("start must be <= end")
        return values

@computed

Computed properties derived from other fields:

from isonantic import TableModel, computed

class Order(TableModel):
    __ison_block__ = "table.orders"
    subtotal: float
    tax_rate: float

    @computed
    def tax(self) -> float:
        return round(self.subtotal * self.tax_rate, 2)

    @computed
    def total(self) -> float:
        return self.subtotal + self.tax

Parsing ISON

parse_ison()

Parse ISON string into validated model instances:

from isonantic import parse_ison

ison = """
table.users
id name email active
1 Alice alice@test.com true
2 Bob bob@test.com false
"""

users = parse_ison(ison, User)
for user in users:
    print(f"{user.name}: {user.email}")

parse_ison_safe()

Parse with error recovery - returns valid rows even if some fail:

from isonantic import parse_ison_safe

result = parse_ison_safe(ison, User)

if result.success:
    print(f"Parsed {len(result.data)} users")
else:
    print(f"Errors: {result.errors}")
    print(f"Valid rows: {len(result.partial_data)}")

LLM Output Parsing

ISONantic excels at parsing LLM-generated ISON, handling common formatting issues:

parse_llm_output()

from isonantic import parse_llm_output, LLMParseError

llm_response = """
Based on your query, here are the results:

table.products
id name price category
101 "Widget Pro" 29.99 electronics
102 "Gadget Plus" 49.99 electronics

Let me know if you need more details!
"""

try:
    products = parse_llm_output(llm_response, Product)
    for p in products:
        print(f"{p.name}: ${p.price}")
except LLMParseError as e:
    print(f"Parse failed: {e}")
    print(f"Suggestion: {e.suggestion}")

Features

  • Extracts ISON from surrounding text
  • Handles markdown code blocks
  • Auto-fixes common LLM mistakes (trailing commas, etc.)
  • Provides suggestions for retry prompts

prompt_for_model()

Generate format instructions for LLMs:

from isonantic import prompt_for_model

prompt = prompt_for_model(Product)
# Output:
# Output format (ISON):
# table.products
# id name price category
# <int> <str> <float> <str>
#
# Constraints:
#   id: primary key
#   price: >= 0

Documents

ISONDocument handles multi-block ISON with reference resolution:

from isonantic import ISONDocument, TableModel
from typing import List

class Team(TableModel):
    __ison_block__ = "table.teams"
    id: int
    name: str

class User(TableModel):
    __ison_block__ = "table.users"
    id: int
    name: str
    team: Reference[Team]

class MyApp(ISONDocument):
    teams: List[Team]
    users: List[User]

    class Config:
        block_order = ["teams", "users"]

# Parse full document
doc = MyApp.parse(ison_data)

# Access data
for user in doc.users:
    team = user.team.resolve(doc)
    print(f"{user.name} -> {team.name}")

# Serialize back to ISON
output = doc.to_ison()

Schema Generation

Generate JSON Schema for OpenAPI compatibility:

# JSON Schema for a single model
schema = User.model_json_schema()
print(schema)
# {
#   "$schema": "https://json-schema.org/draft/2020-12/schema",
#   "title": "User",
#   "type": "object",
#   "properties": {
#     "id": {"type": "integer"},
#     "name": {"type": "string", "minLength": 1, "maxLength": 100},
#     "email": {"type": "string", "pattern": "..."},
#     ...
#   }
# }

# JSON Schema as string
json_str = User.model_json_schema_json(indent=2)

# OpenAPI-compatible schema
from isonantic import openapi_schema
api_schema = openapi_schema(MyApp)

ISONantic vs Pydantic

ISONantic and Pydantic serve different purposes and work together beautifully. Here's when to use each:

ISONantic Advantages

Advantage Impact
Token Efficiency 30-70% smaller context → lower API costs
First-Class References Type-safe Reference[Team] vs manual team_id: int
Reference Validation Automatic validation on parse → no orphan foreign keys
Multi-Block Documents Related tables in one schema (users + teams + orders)
LLM Output Parsing Built-in parse_llm_output() with auto-fix for malformed output
Database Alignment Tables → ISON → LLM naturally maps relational data

Pydantic Advantages

Advantage Impact
Ecosystem FastAPI, Django, massive community support
JSON Native Browser/frontend compatibility out of the box
Maturity Battle-tested, well-documented, stable API
Tooling IDE support, linters, type checkers

Use Both Together

The key insight: Pydantic for the edges (API), ISONantic for the core (LLM). They complement each other perfectly!

┌─────────────────────────────────────────────────┐
│           FastAPI / Django (Pydantic)           │
│         JSON Request  →  JSON Response          │
└───────────────────────┬─────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│             LLM Layer (ISONantic)               │
│   Token-Efficient Context → Validated Output    │
└─────────────────────────────────────────────────┘

Example: Full-Stack AI App

from fastapi import FastAPI
from pydantic import BaseModel
from isonantic import TableModel, parse_llm_output, prompt_for_model

app = FastAPI()

# Pydantic for API layer (JSON - browser compatible)
class SearchRequest(BaseModel):
    query: str
    max_results: int = 10

class SearchResponse(BaseModel):
    results: list
    tokens_used: int
    tokens_saved: int

# ISONantic for LLM layer (token-efficient)
class Product(TableModel):
    __ison_block__ = "table.products"
    id: int
    name: str
    price: float
    category: str

@app.post("/search")
def search(request: SearchRequest) -> SearchResponse:
    # Build token-efficient context (30-70% savings!)
    ison_context = products_doc.to_ison()
    json_equivalent = products_doc.to_json()

    # Call LLM with ISON context
    response = llm.complete(f"""
        {ison_context}

        Query: {request.query}

        {prompt_for_model(Product)}
    """)

    # Parse and validate LLM output
    results = parse_llm_output(response, Product)

    # Return Pydantic response (JSON for frontend)
    return SearchResponse(
        results=[p.dict() for p in results],
        tokens_used=len(ison_context) // 4,
        tokens_saved=len(json_equivalent) // 4 - len(ison_context) // 4
    )

Decision Guide

Use Case Best Choice
REST API endpoints Pydantic - JSON native, FastAPI integration
LLM context building ISONantic - 30-70% token savings
Database → LLM pipelines ISONantic - natural table mapping
Multi-agent messaging ISONantic - compact, structured data
Full-stack AI applications Both together - best of both worlds