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
| Crate | JsonSchema support |
|---|---|
serde_json::Value | built-in |
uuid::Uuid | via schemars uuid feature |
chrono::DateTime | via schemars chrono feature |
std::collections::BTreeMap | built-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:
- Swagger UI: http://localhost:8000/api/swagger
- OpenAPI JSON: http://localhost:8000/api/openapi.json