Design patterns are reusable solutions to common problems in software design. They represent best practices evolved over time by experienced software developers to solve recurring design challenges.
The concept was popularized by the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their influential 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software."
In this post we are going to look at some of design patterns for LLM.
Simple Chat
This is most simple pattern where text is send as input into LLM and Text is return. Every one start with this.
Lets look at code example
var service = GenerativeAIDriverManager.create(GoogleAIFactory.NAME, "https://generativelanguage.googleapis.com", properties);
var messages = new ChatRequest.ChatMessage("user", "Top 5 Country by GPD");
var conversation = ChatRequest.create("gemini-2.0-flash", List.of(messages));
var reply = service.chat(conversation);
System.out.println(reply.message());
Output
Okay, here are the top 5 countries by GDP (Nominal) according to the latest estimates from the International Monetary Fund (IMF) as of October 2023:
1. **United States:** $26.95 trillion
2. **China:** $17.72 trillion
3. **Germany:** $4.43 trillion
4. **Japan:** $4.23 trillion
5. **India:** $3.73 trillion
It's important to note:
* **Source:** I'm using the IMF's World Economic Outlook Database, October 2023 edition. These are estimates and projections, and are subject to change.
* **Nominal GDP:** This is GDP measured at current market prices, without adjusting for inflation.
* **Data Availability:** The most current, definitive GDP figures are usually released with a bit of a lag.
* **GDP (PPP):** It's also worth knowing that if you look at GDP based on Purchasing Power Parity (PPP), the rankings can shift somewhat, with China often being very close to, or even exceeding, the United States.
Simple Chat with Some Structure
You may wonder how to use LLM responses programmatically when looking at the output. This is precisely the problem we'll solve with this pattern. By making a small adjustment to your prompt, you can instruct the LLM to return JSON output. The revised prompt would look like: "List the top 5 countries by GDP. Reply in JSON format."
With just a small change to the prompt, the LLM can return structured output, making it function more like a proper API with a degree of type safety. Types are essential in programming—without them, code becomes difficult to maintain and debug as applications grow in complexity.
Lets look at output of prompt
```json
{
"top_5_countries_by_gdp": [
{
"rank": 1,
"country": "United States",
"gdp_usd": "Approximately $25+ Trillion (USD)"
},
{
"rank": 2,
"country": "China",
"gdp_usd": "Approximately $17+ Trillion (USD)"
},
{
"rank": 3,
"country": "Japan",
"gdp_usd": "Approximately $4+ Trillion (USD)"
},
{
"rank": 4,
"country": "Germany",
"gdp_usd": "Approximately $4+ Trillion (USD)"
},
{
"rank": 5,
"country": "India",
"gdp_usd": "Approximately $3+ Trillion (USD)"
}
],
"note": "GDP figures are approximate and based on the most recent available data (typically from organizations like the World Bank and the IMF). These values fluctuate and can vary slightly depending on the source and the date the data was collected."
}
```
Chat with My Custom Structure
Now you know where we are going. JSON output is good but you need more control and consistency over what is structure of output. One more thing to note without enforcing specific type structure LLM is free to return data in any structure and that will break your API contract. This is also achieved by changing prompt to
Top 5 Country by GPD. Reply in JSON format
Example:
{
"countries":[
{"name":"country 1","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 2","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 3","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 4","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 5","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country}
]
}
```
Code Sample
var prompt = """
Top 5 Country by GPD. Reply in JSON format
Example:
{
"countries":[
{"name":"country 1","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 2","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 3","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 4","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 5","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country}
]
}
""";
var messages = new ChatRequest.ChatMessage("user", prompt);
var conversation = ChatRequest.create("gemini-2.0-flash", List.of(messages));
var reply = service.chat(conversation);
System.out.println(reply.message());
```json
{
"countries": [
{
"name": "United States",
"gdp": 26.95,
"unit": "trillion USD",
"rank": 1
},
{
"name": "China",
"gdp": 17.73,
"unit": "trillion USD",
"rank": 2
},
{
"name": "Japan",
"gdp": 4.23,
"unit": "trillion USD",
"rank": 3
},
{
"name": "Germany",
"gdp": 4.07,
"unit": "trillion USD",
"rank": 4
},
{
"name": "India",
"gdp": 3.42,
"unit": "trillion USD",
"rank": 5
}
]
}
```
Strongly Typesafe Chat
We've established a solid foundation to approach type safety, and now we reach the final step: converting the LLM's string output by passing it through a TypeConverter to create a strongly typed object. This completes our transformation from unstructured text to programmatically usable data.
Changes for type safety is done in library - llmapi
```
<dependency> <groupId>org.llm</groupId> <artifactId>llmapi</artifactId> <version>1.2.1</version> </dependency>
```
Sample Code
var prompt = """
Top 5 Country by GPD. Reply in JSON format
Example:
{
"countries":[
{"name":"country 1","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 2","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 3","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 4","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 5","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country}
]
}
""";
var messages = new ChatRequest.ChatMessage("user", prompt);
var conversation = ChatRequest.create("gemini-2.0-flash", List.of(messages));
var reply = service.chat(conversation, CountryGdp.class);
System.out.println(reply);
Only change in the code is using typesafe chat function from llmapi
public interface GenerativeAIService {
ChatMessageReply chat(ChatRequest var1);
default EmbeddingReply embedding(EmbeddingRequest embedding) {
throw new UnsupportedOperationException("Not Supported");
}
default <T> T chat(ChatRequest conversation, Class<T> returnType) {
....
}
default <T> Optional<T> chat(ChatRequest conversation, Class<T> returnType, BiConsumer<String, Exception> onFailedParsing) {
....
}
}
Output is instance of CountryGdp Object.
Parameter based chat
As your LLM applications grow in complexity, your prompts will inevitably become more sophisticated. One essential feature for managing this complexity is parameter support, similar to what you find in JDBC. The next pattern addresses this need, demonstrating how prompts can contain parameters that are dynamically replaced at runtime, allowing for more flexible and reusable prompt templates.
var prompt = """
Top {{no_of_country}} Country by GPD. Reply in JSON format
Example:
{
"countries":[
{"name":"country 1","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 2","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 3","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 4","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country},
{"name":"country 5","gdp":gpd , "unit":"trillion or billion etc","rank":rank of country}
]
}
""";
var messages = new ChatRequest.ChatMessage("user", prompt);
var conversation = ChatRequest.create("gemini-2.0-flash", List.of(messages));
var preparedConversion = service.prepareRequest(conversation, Map.of("no_of_country", "10"));
var reply = service.chat(preparedConversion, CountryGdp.class);
System.out.println(reply);
Conculsion
We have only scratched the surface of LLM patterns. In this post, I've covered some basic to intermediate concepts, but my next post will delve into more advanced patterns that build upon these fundamentals.
All the code used in this post is available @ llmpatterns git project
No comments:
Post a Comment