Chapter Two
Getting Started with PrOOPt
From an empty pom.xml to your first
governed LLM call, in roughly one cup of coffee.
What You Will Need
Not much, as it happens. PrOOPt runs anywhere Java runs, and the local model runs on a CPU like everybody did in the old days.
| Resource | Requirement |
|---|---|
| Java | 17+ |
| RAM | LOCAL tier: 3 GB minimum (Phi-3.5 Mini INT4 ≈ 2 GB model + JVM). Cloud-only: 512 MB |
| GPU | Not required — CPU inference via ONNX Runtime |
| OS | Any (x86-64 or ARM64); native libraries are auto-selected |
| Maven | 3.9+ |
One Dependency, Four Modules
PrOOPt ships as a small family of Maven artifacts. You will almost
always start with prooopt-core, then add a runtime for
wherever you want your tokens generated.
| Module | Purpose |
|---|---|
prooopt-core | Annotations, AOP, autoboxer, orchestrator, stream API |
prooopt-runtime-local | ONNX Runtime on-device inference — nothing leaves the JVM |
prooopt-runtime-cloud | Anthropic + OpenAI cloud adapters |
prooopt-examples | Worked examples: Legal Analyzer, Linear System, BODMAS solver |
<dependency>
<groupId>io.github.argonizer</groupId>
<artifactId>prooopt-core</artifactId>
<version>${prooopt.version}</version>
</dependency>
<dependency>
<groupId>io.github.argonizer</groupId>
<artifactId>prooopt-runtime-cloud</artifactId>
<version>${prooopt.version}</version>
</dependency>
Your First @PromptFunction
Here is the entire trick, and we will be honest about how strange
it looks the first time: you write a method, and the method returns
null. On purpose. Forever.
public class ContractReader {
@PromptFunction(
model = ModelTier.LOCAL,
prompt = "Extract the contract date: {text}",
description = "Extracts signing date"
)
public LocalDate extractDate(String text) {
text = text.trim(); // optional pre-processing
return null; // PrOOPt takes over
}
}
When the call reaches that return null, PrOOPt
intercepts it, fills the prompt template with your arguments, appends
format instructions derived from your return type, routes the prompt
to the declared model tier, and converts the model's reply into a real
LocalDate. To the rest of your codebase, this is just a
method. It can be mocked, wrapped, profiled and unit-tested like any
other method. That is rather the point.
Pick a Tier (or Let the Router Do It)
Each function carries its own model tier. That is per-function governance, not per-application guesswork.
ModelTier | Meaning |
|---|---|
LOCAL | On-device inference via ONNX Runtime — nothing leaves the JVM |
CLOUD_FAST | Cloud API, cheap and fast model |
CLOUD_ADVANCED | Cloud API, flagship model |
AUTO | The router picks a tier from the estimated token count |
Credentials, the Boring (Important) Way
API keys are never hardcoded. Set them as environment variables or
system properties, and reference them from
prooopt.yaml:
# prooopt.yaml
models:
cloud:
apiKey: ${ANTHROPIC_API_KEY}
Your First Orchestrated Run
Wire it all together with the builder. Register your annotated instances, hand the runtime an input, and let the orchestrator plan the work.
LegalAnalyzer analyzer = new LegalAnalyzer();
PrOOPtRuntime prooopt = PrOOPt.builder()
.router(new CloudModelRouter(anthropicAdapter))
.registerInstance(analyzer)
.build();
Object result = prooopt.orchestrate(analyzer, contractText);
And here is what you will see in the logs — every decision, every tier, every masked argument, every estimated cent:
[PROOOPT][PROMPT_FUNCTION][START] traceId=abc123 function=generateSummary
tier=CLOUD_ADVANCED inputs={text=***CONTRACT-TEXT***}
[PROOOPT][PROMPT_FUNCTION][END] traceId=abc123 function=generateSummary
durationMs=842 result=This mutual non-disclosure...
[PROOOPT][ORCHESTRATOR][SUMMARY] traceId=abc123 totalMs=1204 functions=5
code=2 local=2 cloud=1 tokens=~312 estCost=$0.0020
That last line deserves a moment of appreciation. Five functions ran. Two were plain Java and cost nothing. Two ran on the local model and cost electricity. One went to the cloud and cost a fifth of a cent. You did not have to instrument any of this. The audit trail is a by-product of execution, not a chore you do afterwards.
Where to Next?
You now have a working PrOOPt installation and one slightly
miraculous method that returns null and means it. The
Tutorials walk through the rest of the
toolbox, one short lesson at a time: deterministic tools, the
orchestrator's DAG plans, pipelines, plan caching, and keeping
secrets secret.
