Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LlmSchema Derive Macro

The LlmSchema derive macro — provided by the oam-schema crate — generates a JSON Schema description of any Rust struct whose fields are annotated with standard Serde attributes. This schema is used at runtime to build context-aware prompts and to expose entity structure to LLM tool-calls, gRPC clients, and the Python SDK.

How it works

#[derive(LlmSchema)] delegates to schemars::schema_for!() under the hood and exposes a single method:

#![allow(unused)]
fn main() {
pub fn llm_schema() -> schemars::schema::RootSchema
}

The returned RootSchema is fully JSON-serialisable and can be embedded directly in prompts, returned over gRPC, or published to the OpenAPI spec.

SeaORM integration (Rust)

SeaORM entity models are the primary target for LlmSchema. Add both LlmSchema and JsonSchema to your DeriveEntityModel derive list:

#![allow(unused)]
fn main() {
use roam_schema::LlmSchema;
use schemars::JsonSchema;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, LlmSchema, JsonSchema)]
#[sea_orm(table_name = "organizations")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub id: Uuid,
    pub name: String,
    pub slug: String,
    pub description: String,
    pub owner_id: String,
}
}

With this in place, the schema is available at runtime without reflection:

#![allow(unused)]
fn main() {
let schema = organization::Model::llm_schema();
let schema_json = serde_json::to_string_pretty(&schema).unwrap();
}

Registering with a prompt hook

Pass the JSON schema to a PromptHookSchemaContext when resolving a prompt:

#![allow(unused)]
fn main() {
let schema_json = serde_json::to_string(&organization::Model::llm_schema()).unwrap();

let request = PromptHookResolveRequest {
    schema_context: PromptHookSchemaContext {
        database_id: Some("prod-db".to_string()),
        table_names: vec!["organizations".to_string()],
        domain_tags: vec!["identity".to_string()],
    },
    ..Default::default()
};
}

The matching rules in your prompt hook YAML can reference table_names and domain_tags:

schema:
  table_names: ["organizations"]
  domain_tags: ["identity"]

SQLAlchemy integration (Python SDK)

The Python SDK exposes entity schemas through the RoamClient.get_schema() gRPC call. You do not need to replicate SeaORM models in Python — the schema travels over the wire.

Installation

pip install roam-sdk
# or with uv
uv add roam-sdk

Fetching a schema

from roam_sdk import RoamClient

client = RoamClient(host="localhost", port=50051)

# Retrieve the JSON Schema for a specific entity table
schema = client.get_schema(table_name="organizations")
print(schema)
# → {"$schema": "http://json-schema.org/draft-07/schema#", "title": "Model", ...}

Using the schema with SQLAlchemy

The returned dict is a standard JSON Schema object. Pass it to your ORM model for dynamic validation or prompt-context injection:

import json
from sqlalchemy import create_engine, text
from roam_sdk import RoamClient

client = RoamClient(host="localhost", port=50051)
schema = client.get_schema(table_name="organizations")

# Validate a row dict against the schema (e.g. using jsonschema)
import jsonschema
jsonschema.validate(instance=row_dict, schema=schema)

# Or embed the schema directly in an LLM prompt
prompt_context = json.dumps(schema, indent=2)

Injecting schema into a prompt hook

from roam_sdk import RoamClient, PromptHookResolveRequest, SchemaContext

client = RoamClient(host="localhost", port=50051)

request = PromptHookResolveRequest(
    schema_context=SchemaContext(
        database_id="prod-db",
        table_names=["organizations"],
        domain_tags=["identity"],
    )
)

resolution = client.resolve_prompt_hook(request)
print(resolution.rendered_prompt)

Field-level annotations

All standard Serde rename / skip attributes are respected by schemars:

#![allow(unused)]
fn main() {
#[derive(LlmSchema, JsonSchema, Serialize, Deserialize)]
pub struct Model {
    pub id: Uuid,

    /// Human-readable display name
    pub name: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    #[serde(rename = "ownerId")]
    pub owner_id: String,
}
}

The generated schema will include the description from Rust doc-comments, honour rename, and mark description as non-required because it is Option<…>.

Supported crates

CrateJsonSchema support
serde_json::Valuebuilt-in
uuid::Uuidvia schemars uuid feature
chrono::DateTimevia schemars chrono feature
std::collections::BTreeMapbuilt-in

Enable optional features in Cargo.toml:

schemars = { version = "0.8", features = ["derive", "uuid", "chrono"] }

API reference

The live HTTP API exposes all route schemas via Swagger UI. When the backend is running: