Chapter Three
Developer Tutorials
Six short lessons. Each one builds on the last, and none of them requires a whiteboard.
Lesson 1 — Autoboxing, or: Your Return Type Is the Contract
In Lesson 0 (the Getting Started
page — you did read it, we saw you) you wrote a method that returned
null and got a LocalDate back. The machinery
behind that is the PrOOPtAutoBoxer, and it is patient
with models the way a good teacher is patient with students.
- Primitives — strips commas, strips trailing
.0, extracts the first number from prose. If the model says “The answer is 42, naturally,” you get42. - Booleans —
yes/no,true/false,1/0, case-insensitive. - Enums — scans the prose for the first matching constant name, case-insensitive.
java.time— ISO-8601 parsing forLocalDate,LocalDateTime,ZonedDateTime.- Collections, Maps, POJOs — Jackson JSON, with
markdown fence stripping (because models do love a good
```jsonfence).
The practical upshot: design your @PromptFunction
signatures the way you design any API. The richer and more honest the
return type, the more PrOOPt can enforce on your behalf.
@PromptFunction(
model = ModelTier.CLOUD_FAST,
prompt = "Classify the urgency of this support ticket: {ticket}"
)
public Urgency classify(String ticket) {
return null; // Urgency is an enum: LOW, NORMAL, HIGH, FIRE
}
Lesson 2 — @CodeFunction as Knowledge Injection
Every @CodeFunction does two jobs. The obvious one:
it computes something correctly, every time, for free. The subtle one:
it extends the LLM's effective capability beyond its training
boundary. Your proprietary risk model, your internal scoring
formula, your novel algorithm — none of these exist in any training
corpus. Register them as @CodeFunctions and the
orchestrator can reason about them and invoke them as first-class
tools, without ever seeing inside.
@CodeFunction(
description = "Computes the bank's internal credit score "
+ "from a structured application"
)
public static CreditScore scoreApplication(LoanApplication app) {
// Twenty years of actuarial wisdom, zero tokens.
return CreditModel.evaluate(app);
}
The LLM reads the loan application, classifies the risk tier, and
decides the formula is needed. The @CodeFunction computes
it deterministically. The LLM never sees the formula — only the
result. Your trade secret is used but never exposed.
Lesson 3 — The Orchestrator and Its DAG
When you call orchestrate(...), the orchestrator runs
a tidy little pipeline before any work happens:
User input
│
▼ Phase 1 — Discovery (LOCAL model)
List<String> capabilities
│
▼ Phase 2 — Matching (TF-IDF cosine similarity)
List<ToolDescriptor> (≤ maxTools)
│
▼ Phase 3 — Planning (CLOUD_FAST model)
ExecutionPlan (JSON)
│
▼ Wave-parallel execution
Result
The plan is a DAG. Independent subexpressions fire concurrently;
cross-stream dependencies resolve non-blocking via
CompletableFuture. You declare appetite for parallelism
once, on the orchestrator:
@PromptOrchestrator(
prompt = "You are a legal analyst.",
planMode = PlanMode.DYNAMIC,
model = ModelTier.CLOUD_ADVANCED,
parallel = true
)
@PromptFunctionScan
public class LegalAnalyzer extends BaseOrchestrator {
public static void main(String[] args) {
PrOOPt.run(LegalAnalyzer.class, args);
}
}
A worked example from the library itself: ask the Linear System
solver for help and the LLM does not solve the equations. It
identifies the problem type, selects the strategy, and plans a DAG
where Gaussian elimination runs as a @CodeFunction —
deterministic, reproducible, provably correct. The LLM planned the
solution. Java computed it. The result is guaranteed by the compiler,
not by probability.
Lesson 4 — PromptStream, the Pipeline
Sometimes you do not need a planner; you already know the order of
operations. PromptStream gives you a fluent pipeline
where deterministic and LLM-backed steps snap together like train
cars:
String summary = PromptStream.of(contractText)
.pipe(proxied::normalizeWhitespace) // @CodeFunction
.pipe(proxied::generateSummary) // @PromptFunction CLOUD_ADVANCED
.withTimeout(5_000)
.execute();
Each pipe keeps its own annotation's governance: the
normalizer runs as plain Java, the summary call goes to the cloud
tier it declared, and the whole journey lands in the audit log under
one trace id.
Lesson 5 — Plan Modes: STATIC vs DYNAMIC
@PromptOrchestrator(planMode = ...) controls whether
the execution plan is cached and reused. This is the difference
between paying a planner once and paying a planner hourly.
| Mode | Cloud plan-generation calls | Best for |
|---|---|---|
STATIC (default) | 1 total (first call) | Batch and repetitive workloads — 10,000 loan applications share one plan |
DYNAMIC | 1 per call | Conversational agents, genuinely varied inputs |
Under STATIC, the cached artifact is a plan
template with ${input} placeholders retained. On
each run, PlanInstantiator binds the live input and
assigns a fresh trace id. A warm hit skips discovery and planning
entirely:
[PROOOPT][PLAN_CACHE][HIT] key='Analyze this loan…' similarity=1.00
→ skipping Cloud LLM
[PROOOPT][ORCHESTRATOR][SUMMARY] trace=… mode=STATIC cached=true
plan_generation=0ms cloud_calls=1 …
Plans are memoised by input hash, and the cache strategy can match exact text, semantic similarity, or intent. If the phrase “a JIT compiler for cognition” just crossed your mind, you will enjoy the PromptJIT use case.
Lesson 6 — Keeping Secrets: @SensitiveData and the Audit Log
Mark a parameter as sensitive and the audit log replaces the value with your label. The model still receives the real value — the log is what stays clean:
@PromptFunction(model = ModelTier.CLOUD_ADVANCED, ...)
public String generateSummary(
@SensitiveData(label = "***CONTRACT-TEXT***") String text) {
return null; // PrOOPt supplies the result
}
And when data must not reach the cloud at all, do not write a
policy document. Write an annotation.
@PromptFunction(model = ModelTier.LOCAL) means
this data does not leave the JVM. Period. The developer declared it.
The annotation enforces it. The audit log proves it. Your compliance
officer can read the code and verify the boundary — possibly the
first time anyone has handed them good news in annotation form.
Audit entries are written by the
io.github.argonizer.prooopt.audit logger with daily and
50 MB rotation, with trace ids and per-run cost estimates. Every
decision is reconstructable. The compliance artifact is a by-product
of execution.
Graduation
That is the core curriculum. You can now declare authority zones, inject proprietary knowledge as tools, let an orchestrator plan without computing, pipeline steps, amortise planning costs, and keep your secrets out of the logs. When you are ready to see how far these primitives stretch, the Use Cases section is where things get genuinely strange — in the best way. Or meet the other half of the family in PrOOPt States.
