We have seen how to get structured data response with low level LangChain4j API in these tutorials. In this tutorial we will learn how AI Services help us to hide the low level API and how easy it is to achieve same results.
Structured Data Extraction with AiService
AI Services can return structured Java objects instead of plain text. When combined with JSON mode, this enables reliable extraction of structured data from unstructured text, which is essential for:
- Extracting entities from documents
- Parsing user requests into command objects
- Converting free-form text into database records
- Generating API request/response objects
Enabling JSON Mode
The model which support JSON mode has a method responseFormat() where we can specify JSON mode for the response.
OllamaChatModel.builder()
.....
.responseFormat(JSON)
.build();
Return Type Support
AI Services can return various structured types:
- Simple POJOs with fields
- Lists of objects
- Enums for classification tasks
- Primitive types (boolean, int, etc.)
- Wrapper types like
Result<T> for metadata
Field Descriptions
You can add @Description annotations to fields to help the LLM understand their purpose and format requirements. This improves the accuracy of JSON-to-Java mapping:
Definition of DescriptionVersion: 1.10.0 package dev.langchain4j.model.output.structured;
@Target({ FIELD, TYPE })
@Retention(RUNTIME)
public @interface Description {
String[] value();
}
Quick Examples
@Description("First name of the person")
String firstName;
@Description("Birth date in YYYY-MM-DD format")
String birthDate;
@Description("Email address in valid format")
String email;
The descriptions serve two purposes:
- Better JSON generation: Helps the LLM generate correct field values
- Field name mapping: Ensures JSON keys match Java field names
Example
The following example uses Ollama and phi3:mini-128k which is good for a demo and learning but not good for production-grade applications because it has limited reasoning capabilities and accuracy for complex tasks.
package com.logicbig.example;
import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.output.structured.Description;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.UserMessage;
import java.time.LocalDate;
import java.util.List;
public class StructuredOutputExample {
record Person(
@Description("First name of the person")
String firstName,
@Description("Last name of the person")
String lastName,
@Description("Birth date in YYYY-MM-DD format, e.g., 1985-03-15")
LocalDate birthDate) {}
record Meeting(
@Description("Meeting title")
String title,
@Description("List of participants as array of strings")
List<String> participants,
@Description("Meeting date in YYYY-MM-DD format, e.g., 2024-12-10")
LocalDate date) {}
interface PersonExtractor {
@UserMessage("Extract person information from: {{it}}. "
+ "Return birth date in YYYY-MM-DD format only.")
Person extractPerson(String text);
}
interface MeetingExtractor {
@UserMessage("Extract meeting details from: {{it}}. "
+ "Return date in YYYY-MM-DD format only.")
Meeting extractMeeting(String text);
}
interface SentimentAnalyzer {
@UserMessage("Analyze sentiment of: {{it}}. "
+ "Return one of: POSITIVE, NEUTRAL, or NEGATIVE.")
String analyzeSentiment(String text);
}
// Alternative with Result<T> for version 1.10.0
interface PersonExtractorWithResult {
@UserMessage("Extract person information from: {{it}}. "
+ "Return birth date in YYYY-MM-DD format only.")
Result<Person> extractPersonWithResult(String text);
}
public static void main(String[] args) {
// Enable JSON mode for structured output
OllamaChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("phi3:mini-128k")
.responseFormat(ResponseFormat.JSON)
.temperature(0.1)
.build();
PersonExtractor personExtractor = AiServices.create(PersonExtractor.class, model);
MeetingExtractor meetingExtractor = AiServices.create(MeetingExtractor.class, model);
SentimentAnalyzer sentimentAnalyzer = AiServices.create(SentimentAnalyzer.class, model);
try {
// Example 1: Person extraction
String personText = "John Doe was born on 1985-03-15 in New York.";
Person person = personExtractor.extractPerson(personText);
System.out.println("Extracted Person: " + person);
} catch (Exception e) {
System.out.println("Error extracting person: " + e.getMessage());
}
try {
// Example 2: Meeting extraction with problematic date
String meetingText = "Team meeting about Q4 planning with "
+ "Alice, Bob, and Charlie scheduled for December 10, 2024.";
Meeting meeting = meetingExtractor.extractMeeting(meetingText);
System.out.println("\nExtracted Meeting: " + meeting);
} catch (Exception e) {
System.out.println("Error extracting meeting: " + e.getMessage());
}
try {
// Example 3: Sentiment analysis
String review = "This product is absolutely amazing! I love it.";
String sentiment = sentimentAnalyzer.analyzeSentiment(review);
System.out.println("\nSentiment: " + sentiment);
} catch (Exception e) {
System.out.println("Error analyzing sentiment: " + e.getMessage());
}
// Alternative approach with Result<T> in 1.10.0
System.out.println("\n=== Alternative Approach (with Result<T>) ===");
PersonExtractorWithResult personExtractorWithResult =
AiServices.create(PersonExtractorWithResult.class, model);
try {
String personText2 = "Jane Smith's birthday is 1990-08-22.";
Result<Person> result = personExtractorWithResult.extractPersonWithResult(personText2);
// In 1.10.0, Result<T> has these methods:
Person person = result.content(); // Get the content
System.out.println("Extracted Person: " + person);
// You can also get metadata
if (result.tokenUsage() != null) {
System.out.println("Token Usage - Input: " + result.tokenUsage().inputTokenCount()
+ ", Output: " + result.tokenUsage().outputTokenCount());
}
if (result.finishReason() != null) {
System.out.println("Finish Reason: " + result.finishReason());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
OutputExtracted Person: Person[firstName=John, lastName=Doe, birthDate=1985-03-15]
Extracted Meeting: Meeting[title=Q4 Planning Meeting, participants=[Alice, Bob, Charlie], date=2024-12-10]
Sentiment: { "sentiment": "POSITIVE" }
=== Alternative Approach (with Result<T>) === Extracted Person: Person[firstName=Jane, lastName=Smith, birthDate=1990-08-22] Token Usage - Input: 146, Output: 39 Finish Reason: STOP
Conclusion
The output shows how the AI Service automatically parses the LLM's JSON response into Java objects. The JSON mode ensures the response follows the expected structure, while the AI Service proxy handles the deserialization. This combination provides type-safe, reliable structured data extraction without manual JSON parsing.
Example ProjectDependencies and Technologies Used: - langchain4j 1.10.0 (Build LLM-powered applications in Java: chatbots, agents, RAG, and much more)
- langchain4j-ollama 1.10.0 (LangChain4j :: Integration :: Ollama)
- slf4j-simple 2.0.9 (SLF4J Simple Provider)
- JDK 17
- Maven 3.9.11
|