v0.3.md
1 ## Oxyjen v0.3 Documentation 2 3 **Version:** 0.3.0 4 **Status:** In Development 5 **Focus:** Structured Intelligence — Prompts, Schemas, Reliable Outputs 6 7 --- 8 9 ## Overview 10 11 Oxyjen v0.3 introduces structured intelligence to Java AI pipelines. 12 13 Instead of unpredictable text, you can now enforce JSON schemas, validate model output, and automatically retry until the structure is correct. 14 15 At the core: 16 17 - Prompt templates for reusable prompts 18 - JSON schemas for structured outputs 19 - Automatic validation + retry 20 - SchemaNode for graph integration 21 22 --- 23 24 ## What v0.3 Solves 25 26 ### Before v0.3 27 28 ```java 29 String response = model.chat("Extract person info: John Smith, age 30"); 30 // Could be anything: 31 // "John Smith is 30" 32 // "{ name: John }" 33 // invalid JSON 34 // random formats 35 ``` 36 ### With v0.3 37 ```java 38 JSONSchema schema = JSONSchema.object() 39 .property("name", PropertySchema.string("Person name")) 40 .property("age", PropertySchema.number("Person age")) 41 .required("name", "age") 42 .build(); 43 44 SchemaEnforcer enforcer = new SchemaEnforcer(model, schema); 45 46 String json = enforcer.execute( 47 "Extract person info: John Smith, age 30" 48 ); 49 50 // Always valid JSON: 51 // {"name":"John Smith","age":30} 52 ``` 53 --- 54 55 Core Concepts 56 57 **Prompt Templates** 58 - Prompt templates let you define prompts with variables and fill them at runtime. 59 60 **Basic Usage** 61 ```java 62 PromptTemplate template = PromptTemplate.of( 63 "Hello {{name}}, you are {{age}} years old." 64 ); 65 66 String result = template.render( 67 "name", "Alice", 68 "age", 25 69 ); 70 ``` 71 **Required / Optional Variables** 72 ```java 73 PromptTemplate template = PromptTemplate.of( 74 "Hello {{name}}, role: {{role}}", 75 Variable.required("name"), 76 Variable.optional("role", "guest") 77 ); 78 79 // Uses default role 80 template.render("name", "Alice"); 81 82 // Override default 83 template.render("name", "Bob", "role", "admin"); 84 ``` 85 Variables are provided at execution time, not on nodes. 86 87 **JSON Schema** 88 JSONSchema defines the structure your LLM output must follow. 89 90 ***Creating a Schema*** 91 ```java 92 JSONSchema schema = JSONSchema.object() 93 .description("Person information") 94 .property("name", PropertySchema.string("Full name")) 95 .property("age", PropertySchema.number("Age")) 96 .property("active", PropertySchema.bool("Is active")) 97 .property("status", PropertySchema.enumOf( 98 "Account status", 99 "active", "inactive", "pending" 100 )) 101 .required("name", "age") 102 .build(); 103 ``` 104 **Supported types in v0.3:** 105 - string 106 - number 107 - boolean 108 - enum (string with fixed values) 109 110 **Schema Validation** 111 Validate any JSON string against a schema. 112 ```java 113 SchemaValidator validator = new SchemaValidator(schema); 114 115 ValidationResult result = validator.validate(json); 116 117 if (!result.isValid()) { 118 System.out.println(result.formatErrors()); 119 } 120 ``` 121 ***ValidationResult exposes:*** 122 - isValid() 123 - errors() → List<FieldError> 124 - formatErrors() 125 126 ***Each FieldError includes:*** 127 - fieldPath 128 - errorType 129 - expected 130 - received 131 - message 132 133 **Schema Enforcement** 134 SchemaEnforcer automatically retries the model until valid JSON is produced (or retries are exhausted). 135 ```java 136 SchemaEnforcer enforcer = 137 new SchemaEnforcer(model, schema, 3); 138 139 String json = enforcer.execute( 140 "Extract person info from: Alice Smith is 30" 141 ); 142 ``` 143 Internally: 144 145 LLM -> validate -> retry with errors -> repeat 146 If all retries fail, SchemaException is thrown. 147 148 **SchemaNode (Graph Integration)** 149 SchemaNode combines: 150 151 - ChatModel 152 153 - JSONSchema 154 155 - SchemaEnforcer 156 157 It acts as the boundary between text and structured data. 158 159 Signature (v0.3): 160 161 String → Map<String,Object> 162 Creating a SchemaNode: 163 ```java 164 SchemaNode node = SchemaNode.builder() 165 .model("gpt-4o-mini") 166 .schema(schema) 167 .memory("extractions") 168 .build(); 169 ``` 170 --- 171 172 Public API (v0.3) 173 Prompts 174 - PromptTemplate.of(...) 175 - template.render(...) 176 - Variable.required(...) 177 - Variable.optional(...) 178 179 Schema 180 - JSONSchema.object() 181 - PropertySchema.string(...) 182 - PropertySchema.number(...) 183 - PropertySchema.bool(...) 184 - PropertySchema.enumOf(...) 185 186 Validation 187 - SchemaValidator.validate(...) 188 - ValidationResult 189 - FieldError 190 - Enforcement 191 - SchemaEnforcer.execute(...) 192 - SchemaException 193 194 Graph 195 - SchemaNode.builder()... 196 - SchemaNode.process(...) 197 198 --- 199 200 **Migration from v0.2** 201 202 v0.3 is backward compatible. 203 204 You can still use: 205 206 model.chat(...) 207 208 --- 209 210 **Mental Model** 211 212 PromptTemplate = reusable prompt 213 214 JSONSchema = output contract 215 216 SchemaEnforcer = retry loop 217 218 SchemaNode = graph boundary (text → data) 219 220 --- 221 222 ##Examples 223 224 **Example 1** 225 226 Customer Support Classifier 227 228 Goal: Classify support tickets with structured outputs 229 230 ```java 231 import io.oxyjen.core.*; 232 import io.oxyjen.llm.*; 233 import io.oxyjen.llm.prompts.*; 234 import io.oxyjen.llm.schema.*; 235 236 public class SupportClassifier { 237 238 public static void main(String[] args) { 239 240 PromptTemplate prompt = PromptTemplate.of(""" 241 You are a customer support ticket classifier for {{company_name}}. 242 243 Classify this support ticket: 244 "{{ticket_message}}" 245 246 Determine: 247 - Priority (low, medium, high, critical) 248 - Department (billing, technical, sales, general) 249 - Customer sentiment (happy, neutral, frustrated, angry) 250 - Brief summary 251 252 Respond ONLY with valid JSON. 253 """, 254 Variable.required("company_name"), 255 Variable.required("ticket_message") 256 ); 257 258 JSONSchema schema = JSONSchema.object() 259 .property("priority", PropertySchema.enumOf( 260 "Ticket priority", 261 "low", "medium", "high", "critical" 262 )) 263 .property("department", PropertySchema.enumOf( 264 "Handling department", 265 "billing", "technical", "sales", "general" 266 )) 267 .property("sentiment", PropertySchema.enumOf( 268 "Customer emotion", 269 "happy", "neutral", "frustrated", "angry" 270 )) 271 .property("summary", PropertySchema.string("Brief issue summary")) 272 .required("priority", "department", "sentiment", "summary") 273 .build(); 274 275 SchemaNode classifyNode = SchemaNode.builder() 276 .model("gpt-4o-mini") 277 .schema(schema) 278 .memory("support-tickets") 279 .build(); 280 281 Graph pipeline = GraphBuilder.named("support-classifier") 282 .addNode(classifyNode) 283 .build(); 284 285 NodeContext ctx = new NodeContext(); 286 Executor executor = new Executor(); 287 288 String ticket = 289 "I was charged twice for my subscription! I want a refund NOW!"; 290 291 // Render prompt 292 String renderedPrompt = 293 prompt.render( 294 "company_name", "Acme Corp", 295 "ticket_message", ticket 296 ); 297 298 // Then send rendered prompt to SchemaNode 299 Map<String,Object> result = 300 executor.run(pipeline, renderedPrompt, ctx); 301 302 System.out.println(result); 303 } 304 } 305 ``` 306 307 **Example 2** 308 309 Data Extraction Pipeline 310 311 Goal: Extract structured data from unstructured text. 312 313 ```java 314 import io.oxyjen.core.*; 315 import io.oxyjen.llm.*; 316 import io.oxyjen.llm.schema.*; 317 318 public class DataExtractor { 319 320 public static void main(String[] args) { 321 // Define schema for person data 322 JSONSchema personSchema = JSONSchema.object() 323 .property("name", PropertySchema.string("Full name")) 324 .property("age", PropertySchema.number("Age in years")) 325 .property("email", PropertySchema.string("Email address")) 326 .property("phone", PropertySchema.string("Phone number")) 327 .property("city", PropertySchema.string("City of residence")) 328 .required("name") // Only name is required 329 .build(); 330 331 // Create model with fallback 332 ChatModel model = LLMChain.builder() 333 .primary("gpt-4o") 334 .fallback("gpt-4o-mini") 335 .retry(3) 336 .timeout(Duration.ofSeconds(10)) 337 .build(); 338 339 // Create enforcer 340 SchemaEnforcer enforcer = new SchemaEnforcer(model, personSchema); 341 342 // Extract from different formats 343 String[] inputs = { 344 "John Smith is 35 years old, lives in NYC, email: john@example.com", 345 "Name: Alice Johnson, Age: 28, San Francisco, (415) 555-0123", 346 "Bob Williams, age 42, bob.w@company.com" 347 }; 348 349 for (String input : inputs) { 350 String json = enforcer.execute( 351 "Extract person information from: " + input 352 ); 353 System.out.println(json); 354 } 355 356 /* 357 Output: 358 {"name": "John Smith", "age": 35, "email": "john@example.com", "city": "NYC"} 359 {"name": "Alice Johnson", "age": 28, "city": "San Francisco", "phone": "(415) 555-0123"} 360 {"name": "Bob Williams", "age": 42, "email": "bob.w@company.com"} 361 */ 362 } 363 } 364 ``` 365 366 # Jitter and Retry Cap 367 368 Jitter and Retry Cap are production-grade features that prevent cascading failures and unbounded waits when retrying failed LLM requests. 369 370 --- 371 372 ## The Problem 373 374 ### Without Jitter: The Thundering Herd 375 376 When multiple requests fail simultaneously (for example during an API outage or rate limit), they retry at the same time: 377 378 Time 0s: 1000 requests fail simultaneously 379 Time 1s: 1000 requests retry simultaneously 380 Time 3s: 1000 requests retry simultaneously 381 Time 7s: 1000 requests retry simultaneously 382 383 Result: synchronized retry waves keep hammering the API, preventing recovery. 384 385 --- 386 387 ### Without Retry Cap: Unbounded Waits 388 389 Exponential backoff grows forever: 390 391 Attempt 1: 1s 392 Attempt 2: 2s 393 Attempt 3: 4s 394 Attempt 4: 8s 395 Attempt 5: 16s 396 Attempt 6: 32s 397 Attempt 7: 64s 398 Attempt 8: 128s 399 400 401 Result: 402 403 - users experience unacceptable delays 404 - threads remain blocked 405 - resources are wasted 406 407 --- 408 409 ## The Solution 410 411 ### Jitter: Randomize Retry Delays 412 413 Add randomness to spread retries across time. 414 415 With ±20% jitter: 416 417 Time 0.8–1.2s: ~200 requests retry 418 Time 1.0–1.4s: ~200 requests retry 419 Time 1.2–1.6s: ~200 requests retry 420 Time 1.4–1.8s: ~200 requests retry 421 Time 1.6–2.0s: ~200 requests retry 422 423 Result: smooth distributed load instead of synchronized spikes. 424 425 --- 426 427 ### Retry Cap: Bound Maximum Delay 428 429 Cap exponential backoff at a reasonable maximum. 430 431 With `maxBackoff = 10s`: 432 433 Attempt 1: 1s 434 Attempt 2: 2s 435 Attempt 3: 4s 436 Attempt 4: 8s 437 Attempt 5: 10s (capped) 438 Attempt 6: 10s 439 Attempt 7: 10s 440 441 Result: predictable wait times and better UX. 442 443 --- 444 445 ## Usage 446 447 ### Recommended Defaults 448 449 ```java 450 LLMChain chain = LLMChain.builder() 451 .primary("gpt-4") 452 .fallback("gpt-3.5-turbo") 453 .retry(5) 454 .exponentialBackoff() 455 .maxBackoff(Duration.ofSeconds(10)) 456 .jitter(0.2) 457 .build(); 458 ``` 459 Behavior: 460 461 exponential delays (~1s, ~2s, ~4s, ~8s, ~10s) 462 463 each delay randomized ±20% 464 465 never exceeds 10 seconds 466 467 Production Configuration 468 ```java 469 LLMChain chain = LLMChain.builder() 470 .primary("gpt-4") 471 .fallback("claude-3-sonnet") 472 .fallback("gpt-3.5-turbo") 473 .retry(7) 474 .exponentialBackoff() 475 .maxBackoff(Duration.ofSeconds(15)) 476 .jitter(0.3) 477 .timeout(Duration.ofSeconds(30)) 478 .build(); 479 ``` 480 Use case: critical workloads with bounded waits and aggressive retries. 481 482 Conservative Configuration 483 ```java 484 LLMChain chain = LLMChain.builder() 485 .primary("gpt-3.5-turbo") 486 .retry(3) 487 .exponentialBackoff() 488 .maxBackoff(Duration.ofSeconds(5)) 489 .jitter(0.15) 490 .build(); 491 ``` 492 Use case: interactive applications. 493 494 No Jitter or Cap (v0.2 Behavior) 495 ```java 496 LLMChain chain = LLMChain.builder() 497 .primary("gpt-4") 498 .retry(3) 499 .exponentialBackoff() 500 .build(); 501 ``` 502 Result: deterministic exponential backoff with no upper bound. 503 504 Configuration Parameters 505 maxBackoff(Duration) 506 Sets maximum retry delay. 507 508 Internal Algorithm 509 ```java 510 private long calculateBackoff(int attempt) { 511 Duration base = exponentialBackoff 512 ? Duration.ofSeconds(1L << (attempt - 1)) 513 : Duration.ofSeconds(1); 514 515 if (maxBackoff != null && base.compareTo(maxBackoff) > 0) { 516 base = maxBackoff; 517 } 518 519 if (jitterFactor > 0) { 520 double multiplier = ThreadLocalRandom.current() 521 .nextDouble(1.0 - jitterFactor, 1.0 + jitterFactor); 522 523 base = Duration.ofMillis((long) (base.toMillis() * multiplier)); 524 } 525 526 return base.toMillis(); 527 } 528 ``` 529 530 Key points: 531 - exponential growth via 2^(attempt-1) 532 - cap applied first 533 - jitter applied last 534 - ThreadLocalRandom used for efficiency 535 536 ---