Skip to content

Examples

TypeCast ships two layers of examples. The capability showcase is eight focused demos, each proving one feature in isolation; every snippet that carries a Verified by … pointer is kept compile-true by an executable suite under modules/examples. The real-world apps are four complete, runnable services that show the same features carrying production weight — typed config, budgets, failure quarantine, HTTP layers, concurrency, and cross-build-tool consumption.

Part 1 — Capability showcase

The eight examples form a narrative arc, each isolating one capability. Full code, wow-moments, and the LangChain4j comparison live in EXAMPLES.md; this is the index.

  1. Hello world — invoice extraction. Zero-boilerplate derives LlmSchema over types LangChain4j can't handle (BigDecimal, LocalDate, nested lists, Option), with provider-native structured output used automatically. See structured-output.md. (Verified by modules/examples/src/test/scala/typecast/examples/ex1/.)

  2. The flagship — sealed traits as the model's decision space. Discriminated unions as the model's output space, with handling code the compiler exhaustiveness-checks: add a variant and the build breaks until every dispatch site handles it, so output space and handling code cannot silently diverge. (Verified by modules/examples/src/test/scala/typecast/examples/ex2/.)

  3. Refinements → targeted retry — clinical intake form. One Iron refinement is at once a wire constraint, a validator, and — on violation — a reprompt addressed at exactly the failing path; the printed ExtractionReport shows repairs, alignments, the failed validation, and the targeted second attempt. See retry-and-repair.md. (Verified by modules/examples/src/test/scala/typecast/examples/ex3/.)

  4. The benchmark — making small models production-reliable. The BenchMain sweep across the Raw / Repair / RepairRetry tiers on the same extraction code over multiple providers, quantifying repair-before-retry economics and capability lowering. See benchmarks.md. (Verified by modules/examples/src/test/scala/typecast/examples/ex4/Example4BenchmarkSuite.scala, which drives the real BenchMain sweep and parses its table.md.)

  5. Streaming partial decode — typed optimistic UI. StreamingExtract.stream emits Partial[A] snapshots as chunks arrive: each already-complete field is already validated, and the terminal element is identical to the strict extract[A] result. See streaming.md. (Verified by modules/examples/src/test/scala/typecast/examples/ex5/Example5StreamingSuite.scala.)

  6. LLM-as-judge + evals as CI. A judge is itself structured extraction, so its verdict can't be malformed and an out-of-range score is retried before any eval sees it; the report plus a typed structural diff become a CI gate, with a ScalaCheck property over typed outputs. (Verified by modules/examples/src/test/scala/typecast/examples/ex6/Example6JudgeSuite.scala.)

  7. Tools + MCP from the same derives clause. derives LlmTool turns a case class into a tool definition; malformed tool arguments get the same repair/retry treatment as extractions, the handler runs only on validated arguments, and McpServer.from(tools).serveStdio() exports the registry as an MCP stdio server. See tools-and-mcp.md. (Verified by modules/examples/src/test/scala/typecast/examples/ex7/Example7ToolsSuite.scala, including an in-process MCP initialize → tools/list → tools/call session.)

  8. The Java story — Spring Boot, records, sealed interfaces. Jakarta annotations (@Positive, @NotBlank) drive the same constraint → validate → targeted-retry loop as Iron does in Scala, and an exhaustive switch (Java 21) or sealed instanceof chain (Java 17) gives Java shops the drift protection of example 2. See java.md. (Verified by modules/examples/src/test/java/typecast/examples/ex8/ and modules/examples/src/test/scala/typecast/examples/ex8/Example8JavaSuite.scala.)

A capability-to-example coverage map closes EXAMPLES.md.

Part 2 — Real-world apps

Four complete applications under examples-realworld/. Each has its own README, scripted-transport test suites (no network), and one live suite tagged Ollama that runs the real pipeline and skips itself when nothing answers on localhost:11434. All four default to a local Ollama (llama3.2) so they run with no API key.

invoice-pipeline — batch email → ledger ingestion (Scala)

Scenario. A back-office batch service reads a directory of inbound invoice emails (plain text) and produces a validated ledger. Every document ends in exactly one of two append-only files: ledger.jsonl (one canonical re-encoded invoice per validated document, with provenance) or failures.jsonl (the rendered ExtractionError plus the full report transcript — the audit log a reviewer reads before re-keying by hand).

Production concern. Failure as an expected outcome, not a batch abort: per-document Budget(maxTotalTokens = ...) caps, iron-refined domain types whose constraints feed targeted retries, a semantic cross-field validator (stated total must equal the sum of line amounts), failure quarantine, report-as-audit-log, and CostAccounting that reports unknown costs as unknown rather than guessing.

Run it.

ollama serve
ollama pull llama3.2
sbt "realworldInvoice/runMain typecast.realworld.invoices.InvoiceIngestMain examples-realworld/invoice-pipeline/sample-inbox"

Arguments are [inputDir] [outputDir]; pass a second argument to keep outputs out of the repo.

README

spring-triage — a customer-support triage microservice (Java / Spring Boot)

Scenario. A Spring Boot 3 microservice takes raw customer tickets over HTTP and answers with a validated, typed triage decision — refund, escalate, auto-reply, or close as spam — modeled as a Jakarta-constrained sealed interface of records. POST /api/triage returns typed JSON.

Production concern. Extraction failure as an operational event with a typed exception: @ExceptionHandler(TypedExtractionException.class) returns a structured 422 carrying the rendered error and report transcript instead of a stack-traced 500. Provider, model, retry budget, and optional escalation are wired through @ConfigurationProperties from application.yml, and constructing a transport opens no connection so the context boots with no model server running.

Run it.

ollama pull llama3.2 && ollama serve
sbt realworldSpring/run

Then POST tickets to localhost:8080/api/triage.

README

review-classifier — nightly concurrent batch classification (Scala / cats-effect)

Scenario. An e-commerce team classifies the day's inbound product reviews nightly. A reviews.csv goes in; a typed classified.csv, a failures.csv, and a run summary with token/cost accounting come out. Each review becomes a ReviewAnalysis (sentiment and category enums, a 1..5 severity validator, a summary, an action-required flag).

Production concern. Real concurrency via fs2 / cats-effect: Stream.emits(reviews).parEvalMapUnordered(CONCURRENCY)(classify) drives the sans-IO ExtractionSession through CeRunner.runWithReport, so only the wire send runs on the blocking pool while parse/align/validate/retry stay cancellable IO. Per-review Budget caps, rate-limit failures surfaced as TransportError.RateLimited rows rather than crashes, and CostAccounting that reports unknown costs as unknown.

Run it.

ollama pull llama3.2
sbt "realworldClassifier/runMain typecast.realworld.reviews.ClassifyMain examples-realworld/review-classifier/sample-data/reviews.csv"

Configuration is environment-driven (CONCURRENCY, REVIEW_TOKEN_BUDGET, TYPECAST_MODEL, …).

README

kotlin-triage — TypeCast from Kotlin, built with Gradle (Kotlin / Gradle, standalone)

Scenario. The example-8 support-triage scenario reimplemented in idiomatic Kotlin (sealed interface + @JvmRecord data class), consuming the published TypeCast jars from a standalone Gradle Kotlin-DSL build — no sbt in the directory. An exhaustive when (no else) routes the decision.

Production concern. Cross-build-tool and cross-language consumption: TypeCast's artifacts are plain JVM jars that Gradle resolves like any Maven dependency, and Kotlin's @JvmRecord + sealed-interface reflection satisfies the Java facade's contract. It pins the two real interop gotchas as tests — the @field: constraint-target requirement (plain @Positive on a Kotlin record is silently ignored) and implementing the Scala SyncTransport trait from Kotlin.

Run it. First publish the jars from the repo root, then use a system Gradle (no wrapper is committed):

sbt publishM2
gradle test     # full scripted suite, no model needed
gradle run      # live demo against local Ollama

README

I want to see…

I want to see… Go to
The simplest possible extraction Capability 1
Sealed-trait / ADT decisions with compile-time drift protection Capability 2, spring-triage, kotlin-triage
Iron refinements driving targeted retries and a report transcript Capability 3, invoice-pipeline
Repair-before-retry economics across providers (the benchmark) Capability 4
Streaming Partial[A] decode into a UI Capability 5
LLM-as-judge and evals as a CI gate Capability 6
Typed tool dispatch and an MCP stdio server Capability 7
The Java facade with Jakarta constraints Capability 8, spring-triage
A complete batch ingestion pipeline with failure quarantine invoice-pipeline
TypeCast inside an HTTP service returning structured 422s spring-triage
Concurrent batch processing with cats-effect and cost accounting review-classifier
Using TypeCast from Kotlin / Gradle (and the @field: gotcha) kotlin-triage