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.
-
Hello world — invoice extraction. Zero-boilerplate
derives LlmSchemaover types LangChain4j can't handle (BigDecimal,LocalDate, nested lists,Option), with provider-native structured output used automatically. See structured-output.md. (Verified bymodules/examples/src/test/scala/typecast/examples/ex1/.) -
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/.) -
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
ExtractionReportshows repairs, alignments, the failed validation, and the targeted second attempt. See retry-and-repair.md. (Verified bymodules/examples/src/test/scala/typecast/examples/ex3/.) -
The benchmark — making small models production-reliable. The
BenchMainsweep across theRaw/Repair/RepairRetrytiers on the same extraction code over multiple providers, quantifying repair-before-retry economics and capability lowering. See benchmarks.md. (Verified bymodules/examples/src/test/scala/typecast/examples/ex4/Example4BenchmarkSuite.scala, which drives the realBenchMainsweep and parses itstable.md.) -
Streaming partial decode — typed optimistic UI.
StreamingExtract.streamemitsPartial[A]snapshots as chunks arrive: each already-complete field is already validated, and the terminal element is identical to the strictextract[A]result. See streaming.md. (Verified bymodules/examples/src/test/scala/typecast/examples/ex5/Example5StreamingSuite.scala.) -
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.) -
Tools + MCP from the same
derivesclause.derives LlmToolturns 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, andMcpServer.from(tools).serveStdio()exports the registry as an MCP stdio server. See tools-and-mcp.md. (Verified bymodules/examples/src/test/scala/typecast/examples/ex7/Example7ToolsSuite.scala, including an in-process MCP initialize → tools/list → tools/call session.) -
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 exhaustiveswitch(Java 21) or sealedinstanceofchain (Java 17) gives Java shops the drift protection of example 2. See java.md. (Verified bymodules/examples/src/test/java/typecast/examples/ex8/andmodules/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.
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.
Then POST tickets to localhost:8080/api/triage.
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, …).
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
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 |