In the previous article, we learned that Chain of Thought (CoT) improves the reasoning ability of Large Language Models by encouraging them to think through a problem step by step instead of jumping directly to an answer.
Understanding the theory is important, but the best way to appreciate its value is to build a real AI agent that uses this reasoning pattern.
In this article, we'll build a Logic Puzzle Solver using LangChain4j and a local LLM running with Ollama. Rather than simply producing an answer, our agent will analyze the puzzle, identify the known facts, apply logical constraints, eliminate impossible options, and then arrive at the final solution through a structured reasoning process.
By the end of this article, you'll have a working reasoning agent and a solid understanding of how prompt engineering can guide an LLM to solve problems more systematically.
1. Why a Logic Puzzle Solver?
When learning a new AI concept, choosing the right example is just as important as understanding the theory. Since this article focuses on Chain of Thought (CoT) reasoning, we need a problem that requires the model to think through multiple steps before arriving at the correct answer.
A logic puzzle is one of the best examples for this purpose.
Unlike simple question-answering tasks, logic puzzles cannot usually be solved by immediately producing an answer. Instead, they require the model to carefully analyze the available information, identify relationships, eliminate impossible options, and gradually work toward the correct solution.
This makes them an ideal way to demonstrate how Chain of Thought reasoning works.
Logic Puzzles Require Reasoning
Consider the following puzzle: Alice, Bob, and Charlie each own a different pet: a cat, a dog, and a rabbit.
Alice does not own the dog.
Bob does not own the cat.
Charlie owns the rabbit.
Who owns the cat? A human doesn't instantly know the answer. Instead, they naturally think through the clues:
· Charlie already owns the rabbit.
· That leaves the cat and dog for Alice and Bob.
· Alice cannot own the dog.
· Therefore, Alice must own the cat.
· Bob gets the remaining pet, the dog.
This is exactly the kind of structured reasoning we want our AI agent to perform. Rather than jumping directly to the answer, we want the model to explain how it reached its conclusion.
No External Tools Are Required
Another reason logic puzzles are perfect for demonstrating Chain of Thought is that they rely entirely on reasoning.
The model doesn't need to:
· Search the internet
· Query a database
· Call a weather API
· Read a PDF
· Access company documents
Everything needed to solve the puzzle is already contained in the question itself. This allows us to focus entirely on the model's reasoning process without introducing additional concepts such as tools, APIs, or Retrieval-Augmented Generation (RAG).
Later in this series, we'll build agents that interact with external systems. For now, we want to isolate one capability: thinking step by step.
2. What We'll Build
Before we start writing code, let's first understand the overall architecture of the application we're going to build.
Although this project is intentionally simple, it demonstrates one of the most important concepts in modern AI agents: using prompt engineering to guide a Foundation Model through a structured reasoning process.
Our application consists of just a few components working together.
User
│
▼
LogicPuzzleSolver Agent
│
▼
System Prompt
│
▼
Foundation Model (LLM)
│
▼
Step-by-Step Reasoning
│
▼
Final Answer
Step 1: The User Provides a Logic Puzzle
Everything begins with the user. The user enters a logic puzzle using natural language. At this stage, the application does not attempt to solve the puzzle itself. Its job is simply to pass the user's request to the AI agent.
Step 2: The LogicPuzzleSolver Agent Receives the Request
The user's puzzle is handled by our LogicPuzzleSolver Agent. Think of this agent as the coordinator of the application. It doesn't contain complex reasoning algorithms or manually coded logic.
Instead, its responsibility is to:
· Receive the user's puzzle.
· Send the appropriate instructions to the Foundation Model.
· Receive the model's response.
· Return the final result to the user.
Using LangChain4j, we'll implement this agent as an AI Service interface, allowing us to interact with the LLM using simple Java methods rather than manually constructing prompts for every request.
Step 3: The System Prompt Programs the Agent's Brain
This is where the real magic happens. Before the user's puzzle reaches the Foundation Model, we provide it with a carefully designed System Prompt.
The System Prompt tells the model:
· What role it should play
· How it should approach the problem
· How it should structure its reasoning
· How the final response should be formatted
For example, instead of simply saying, Solve this puzzle. We might instruct the model to:
· Carefully understand the puzzle
· Identify all known facts
· Determine the logical constraints
· Eliminate impossible possibilities
· Reason step by step
· Explain the final answer clearly
Notice that we're not teaching the model new knowledge. We're simply guiding how it should use the knowledge it already has. This is the essence of prompt engineering.
Step 4: The Foundation Model Performs the Reasoning
After receiving both the user's puzzle and the system instructions, the Foundation Model begins its reasoning process.
Internally, the model generates one token at a time. However, because we've instructed it to think in a structured way, the generated response naturally follows a logical sequence.
Instead of immediately producing an answer, the model works through the problem step by step. For our educational example, the reasoning might look like this:
Understand the puzzle
↓
Identify the known facts
↓
Apply logical constraints
↓
Eliminate impossible options
↓
Determine the remaining possibility
↓
Generate the final answer
This structured approach is what makes the reasoning process easy to understand.
Step 5: The Agent Returns the Final Answer
Once the Foundation Model completes its reasoning, the LogicPuzzleSolver Agent returns the response to the user. Instead of receiving only the final answer, the user also sees the reasoning process that led to it.
a. LogicPuzzleSolver Agent
If you've been developing software for years, the code for our Logic Puzzle Solver might look surprisingly small.
public interface LogicPuzzleSolver { @SystemMessage(""" ... """) String chat( @MemoryId String memoryId, @UserMessage String puzzle ); }
· Where is the algorithm?
· Where are the loops?
· Where are the if-else statements?
· Where is the logic that actually solves the puzzle?
The answer is simple: There isn't one. This is one of the biggest mindset shifts when building AI-powered applications. Instead of writing algorithms that solve the problem ourselves, we describe how the AI should solve the problem. The intelligence lives inside the Foundation Model. Our responsibility is to guide that intelligence using well-designed prompts.
Full agent with system instructions looks like this:
public interface LogicPuzzleSolver { @SystemMessage( """ You are an expert Logic Puzzle Solver. Your job is to solve logic puzzles by thinking through the problem in a structured, step-by-step manner before arriving at the final answer. Follow these reasoning principles: 1. Carefully understand the puzzle. 2. Identify all known facts. 3. Identify constraints or rules. 4. Eliminate impossible possibilities. 5. Combine the remaining facts logically. 6. Continue reasoning until only one valid solution remains. 7. Finally provide a clear explanation. Format every response exactly like this: ==================================== 🧩 Understanding the Puzzle ==================================== <Brief summary> ==================================== 🧠 Step-by-Step Reasoning ==================================== Step 1: ... Step 2: ... Step 3: ... ==================================== ✅ Final Answer ==================================== <Answer> Keep the reasoning clear, simple, and easy for beginners to understand. Never skip reasoning steps. """) String chat(@MemoryId String memoryId, @UserMessage String puzzle); }
The heart of our Logic Puzzle Solver isn't the Java code, it's the System Prompt. At first glance, it may look like a block of instructions, but it actually implements one of the most effective reasoning techniques in modern AI: Chain of Thought (CoT).
We Don't Ask for the Answer
Imagine we wrote a very simple system prompt like this:
""" You are a logic puzzle solver. Solve the following puzzle. """
The model would probably produce the correct answer for simple puzzles. However, it would likely jump directly to the conclusion.
For example:
· Alice owns the cat.
· Bob owns the dog.
· Charlie owns the rabbit.
Although the answer is correct, we don't know how the model arrived at it.
· Did it consider all the clues?
· Did it accidentally ignore one of the constraints?
· Did it simply guess?
There is no visibility into its reasoning process.
We Ask the Model to Think Before Answering
Now look at our actual prompt. Instead of asking for only the final answer, we provide a reasoning process.
· Carefully understand the puzzle.
· Identify all known facts.
· Identify constraints or rules.
· Eliminate impossible possibilities.
· Combine the remaining facts logically.
· Continue reasoning until only one valid solution remains.
· Finally provide a clear explanation.
Notice what changed. We're no longer asking: "What is the answer?". We're asking, "How should you solve the problem?". This is exactly what Chain of Thought prompting is about.
Rather than encouraging the model to jump directly to the destination, we encourage it to walk through the journey.
4. Building the Application from Scratch
Follow below step-by-step procedure to build the complete working agent.
Step 1: Create story-teller application by executing following command.
mvn archetype:generate \ -DgroupId=com.sample.app \ -DartifactId=chain-of-thought-agent \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
Project structure looks like below.
$ tree chain-of-thought-agent /
story-teller/
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── sample
│ │ └── app
│ │ └── App.java
│ └── test
│ └── java
│ └── com
│ └── sample
│ └── app
│ └── AppTest.java
└── target
├── classes
│ └── META-INF
│ └── MANIFEST.MF
└── test-classes
16 directories, 4 files
Remove src/test/java folder, let’s not have any test cases to make the things simple.
Step 2: Update pom.xml with maven dependencies.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sample.app</groupId> <artifactId>chain-of-thought-agent</artifactId> <version>1.0.0</version> <properties> <!-- Java --> <maven.compiler.release>23</maven.compiler.release> <jline.version>3.30.6</jline.version> <jansi.version>2.4.2</jansi.version> <jackson.version>2.20.0</jackson.version> <slf4j.version>2.0.17</slf4j.version> <logback.version>1.5.20</logback.version> <langchain4j.version>1.17.2</langchain4j.version> </properties> <dependencies> <!-- ========================= --> <!-- LangChain4j --> <!-- ========================= --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- Source: https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-ollama --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-ollama</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- ========================= --> <!-- Console UI --> <!-- ========================= --> <!-- Interactive Terminal --> <dependency> <groupId>org.jline</groupId> <artifactId>jline</artifactId> <version>${jline.version}</version> </dependency> <!-- ASCII Banner --> <dependency> <groupId>com.github.lalyos</groupId> <artifactId>jfiglet</artifactId> <version>0.0.9</version> </dependency> <!-- ========================= --> <!-- JSON --> <!-- ========================= --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- ========================= --> <!-- Logging --> <!-- ========================= --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <!-- ========================= --> <!-- Testing --> <!-- ========================= --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.13.4</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!-- Java Compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.1</version> <configuration> <release>23</release> </configuration> </plugin> <!-- Unit Tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.4</version> </plugin> <!-- Build Executable JAR --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.6.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass> com.sample.app.App </mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.diffplug.spotless</groupId> <artifactId>spotless-maven-plugin</artifactId> <version>2.46.1</version> <!-- Use the latest version --> <configuration> <!-- Target only relevant files --> <java> <includes> <include>src/main/java/**/*.java</include> <include>src/test/java/**/*.java</include> </includes> <!-- Apply Google Java Format --> <googleJavaFormat> <version>1.24.0</version> <style>GOOGLE</style> <!-- Or AOSP for 4-space indents --> </googleJavaFormat> <!-- Extra clean-up steps --> <removeUnusedImports /> <trimTrailingWhitespace /> <endWithNewline /> </java> </configuration> <executions> <execution> <!-- Automatically check style during the compile phase --> <goals> <goal>check</goal> </goals> <phase>compile</phase> </execution> </executions> </plugin> </plugins> </build> </project>
Step 3: Define LogicPuzzleSolver interface.
LogicPuzzleSolver.java
package com.sample.app.agent; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; /** * AI Logic Puzzle Solver. * * <p>Demonstrates Chain of Thought prompting by encouraging the model to solve logic problems * through explicit step-by-step reasoning. * * <p>This example is intended for educational purposes to illustrate reasoning patterns in AI * agents. */ public interface LogicPuzzleSolver { @SystemMessage( """ You are an expert Logic Puzzle Solver. Your job is to solve logic puzzles by thinking through the problem in a structured, step-by-step manner before arriving at the final answer. Follow these reasoning principles: 1. Carefully understand the puzzle. 2. Identify all known facts. 3. Identify constraints or rules. 4. Eliminate impossible possibilities. 5. Combine the remaining facts logically. 6. Continue reasoning until only one valid solution remains. 7. Finally provide a clear explanation. Format every response exactly like this: ==================================== 🧩 Understanding the Puzzle ==================================== <Brief summary> ==================================== 🧠 Step-by-Step Reasoning ==================================== Step 1: ... Step 2: ... Step 3: ... ==================================== ✅ Final Answer ==================================== <Answer> Keep the reasoning clear, simple, and easy for beginners to understand. Never skip reasoning steps. """) String chat(@MemoryId String memoryId, @UserMessage String puzzle); }
Step 4: Define ConsoleColors and ConsoleRenderer classes to print the messages properly.
ConsoleColors.java
package com.sample.app.console; /** * JLine 3 colour index constants for use with {@link * org.jline.utils.AttributedStyle#foreground(int)}. * * <p>Indices 0–7 map to standard 4-bit ANSI colours ({@code \033[3Xm}). Adding {@code BRIGHT} (8) * gives the bright variant ({@code \033[9Xm}). Indices 16–255 use the 256-colour escape ({@code * \033[38;5;Xm}). */ public final class ConsoleColors { public static final int BLACK = 0; public static final int RED = 1; public static final int GREEN = 2; public static final int YELLOW = 3; public static final int BLUE = 4; public static final int MAGENTA = 5; public static final int CYAN = 6; public static final int WHITE = 7; /** Add to any colour index to get its bright/high-intensity variant. */ public static final int BRIGHT = 8; // Semantic aliases public static final int USER = CYAN | BRIGHT; // 14 public static final int AGENT = GREEN | BRIGHT; // 10 public static final int INFO = CYAN | BRIGHT; // 14 public static final int ERROR = RED | BRIGHT; // 9 public static final int STORY = WHITE; // 7 public static final int GRAY = BLACK | BRIGHT; // 8 (dark gray) public static final int ORANGE = 208; // 256-colour private ConsoleColors() {} }
ConsoleRenderer.java
package com.sample.app.console; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; /** * Renders rich, coloured output to the terminal using JLine 3. * * <p>Supports a lightweight Markdown → ANSI subset: H1/H2/H3 headings, horizontal rules, * ordered/unordered lists, blockquotes, and inline <strong>bold</strong>, <em>italic</em>, and * {@code code} spans. Any unrecognised line is printed verbatim. */ public final class ConsoleRenderer { // ── JLine AttributedStyle palette ───────────────────────────────────────── // // JLine colour indices: 0-7 → standard \033[3Xm // 8-15 → bright \033[9(X-8)m // 16+ → 256-colour \033[38;5;Xm // // Convenience: BRIGHT (=8) | colour gives the bright variant. private static final AttributedStyle RESET = AttributedStyle.DEFAULT; // Banner / headers private static final AttributedStyle BANNER = bold(AttributedStyle.MAGENTA | AttributedStyle.BRIGHT); private static final AttributedStyle H1 = bold(AttributedStyle.YELLOW | AttributedStyle.BRIGHT); private static final AttributedStyle H2 = bold(AttributedStyle.CYAN | AttributedStyle.BRIGHT); private static final AttributedStyle H3 = bold(AttributedStyle.CYAN); // Structural chrome private static final AttributedStyle CHROME = AttributedStyle.DEFAULT.foreground( AttributedStyle.BLACK | AttributedStyle.BRIGHT); // dark gray private static final AttributedStyle AGENT_LABEL = bold(AttributedStyle.GREEN | AttributedStyle.BRIGHT); private static final AttributedStyle PROMPT_STYLE = bold(AttributedStyle.CYAN | AttributedStyle.BRIGHT); // Lists private static final AttributedStyle BULLET = bold(AttributedStyle.GREEN | AttributedStyle.BRIGHT); private static final AttributedStyle SUB_BULLET = AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN); // Inline private static final AttributedStyle BOLD_SPAN = AttributedStyle.DEFAULT.bold(); private static final AttributedStyle ITALIC_SPAN = AttributedStyle.DEFAULT.italic(); private static final AttributedStyle CODE_SPAN = AttributedStyle.DEFAULT.foreground(208).background(236); // orange on dark // Status private static final AttributedStyle INFO_STYLE = AttributedStyle.DEFAULT.foreground(AttributedStyle.CYAN | AttributedStyle.BRIGHT); private static final AttributedStyle THINK_STYLE = AttributedStyle.DEFAULT.italic().foreground(AttributedStyle.BLACK | AttributedStyle.BRIGHT); private static final AttributedStyle ERROR_STYLE = bold(AttributedStyle.RED | AttributedStyle.BRIGHT); // Blockquote private static final AttributedStyle BQUOTE_STYLE = AttributedStyle.DEFAULT.italic().foreground(AttributedStyle.WHITE); // ── Widths ───────────────────────────────────────────────────────────────── private static final int BANNER_W = 52; private static final int RULE_W = 50; // ── Inline Markdown pattern (groups in declaration order) ───────────────── // 1: **bold** 2: __bold__ 3: *italic* 4: _italic_ 5: `code` private static final Pattern INLINE_PAT = Pattern.compile( "\\*\\*(.+?)\\*\\*" // 1 **bold** + "|__(.+?)__" // 2 __bold__ + "|(?<![*])\\*(?![*])(.+?)(?<![*])\\*(?![*])" // 3 *italic* + "|(?<![\\w_])_(?!_)(.+?)(?<!_)_(?![\\w_])" // 4 _italic_ + "|`(.+?)`" // 5 `code` ); private ConsoleRenderer() {} // ── Public API ───────────────────────────────────────────────────────────── /** Prints the application welcome banner. */ public static void printBanner() { String bar = "═".repeat(BANNER_W); String title = "✨ Logic Puzzle Solver AI ✨"; System.out.println(); System.out.println(s(BANNER, " ╔" + bar + "╗")); System.out.println(s(BANNER, " ║" + center(title, BANNER_W) + "║")); System.out.println(s(BANNER, " ╚" + bar + "╝")); System.out.println(); } /** Prints the "You ❯" input prompt (no trailing newline). */ public static void printPrompt() { System.out.print(s(PROMPT_STYLE, " You ❯ ")); } /** * Prints an agent reply, rendering any Markdown to coloured ANSI output. * * @param text raw text or Markdown returned by the AI agent */ public static void agent(String text) { System.out.println(); System.out.println(s(AGENT_LABEL, " 📖 Logic Puzzle Solver")); System.out.println(s(CHROME, " " + "─".repeat(RULE_W))); System.out.println(renderMarkdown(text)); } /** Prints an informational line. */ public static void info(String text) { System.out.println(s(INFO_STYLE, " ℹ " + text)); } /** Prints the "Thinking…" spinner line. */ public static void thinking() { System.out.println(); System.out.println(s(THINK_STYLE, " ⏳ Thinking…")); System.out.println(); } /** Prints an error line. */ public static void error(String text) { System.out.println(s(ERROR_STYLE, " ✖ " + text)); } // ── Markdown → ANSI ─────────────────────────────────────────────────────── /** * Converts Markdown text to ANSI-coloured terminal output. * * <p>Supported constructs: {@code #} H1–H3, {@code ---} rules, {@code -/*/+} bullets, * {@code 1.} ordered lists, {@code >} blockquotes, {@code **bold**}, {@code *italic*}, and {@code * `code`}. Unrecognised lines are printed verbatim. */ public static String renderMarkdown(String text) { if (text == null || text.isBlank()) return ""; StringBuilder out = new StringBuilder(); boolean prevBlank = false; for (String line : text.split("\n", -1)) { String rendered = renderLine(line); if (rendered.isEmpty()) { if (!prevBlank) out.append('\n'); prevBlank = true; } else { out.append(rendered).append('\n'); prevBlank = false; } } return out.toString(); } // ── Line rendering ───────────────────────────────────────────────────────── private static String renderLine(String line) { // ── Headings ────────────────────────────────────────────────────────── if (line.startsWith("# ")) { return s(H1, " " + line.substring(2).trim().toUpperCase()); } if (line.startsWith("## ")) { return s(H2, " ◆ " + line.substring(3).trim()); } if (line.startsWith("### ")) { return s(H3, " ▸ " + line.substring(4).trim()); } // ── Horizontal rule ─────────────────────────────────────────────────── if (line.matches("^[-─═*]{3,}\\s*$")) { return s(CHROME, " " + "─".repeat(RULE_W)); } // ── Lists ───────────────────────────────────────────────────────────── if (line.matches("^ [\\-*+] .+")) { // indented bullet return s(SUB_BULLET, " ◦ ") + inline(line.substring(4).trim()); } if (line.matches("^[\\-*+] .+")) { // top-level bullet return s(BULLET, " • ") + inline(line.substring(2).trim()); } if (line.matches("^\\d+\\.\\s.+")) { // ordered int dot = line.indexOf(". "); String num = line.substring(0, dot + 1); return s(BULLET, " " + num + " ") + inline(line.substring(dot + 2).trim()); } // ── Blockquote ──────────────────────────────────────────────────────── if (line.startsWith("> ")) { return s(CHROME, " │ ") + s(BQUOTE_STYLE, line.substring(2).trim()); } // ── Empty ───────────────────────────────────────────────────────────── if (line.isBlank()) return ""; // ── Normal text ─────────────────────────────────────────────────────── return " " + inline(line); } // ── Inline Markdown ──────────────────────────────────────────────────────── /** * Applies inline Markdown spans to a raw text string. Bold is matched before italic to avoid * partial `**` collisions. */ private static String inline(String text) { StringBuilder sb = new StringBuilder(); Matcher m = INLINE_PAT.matcher(text); int cursor = 0; while (m.find()) { sb.append(text, cursor, m.start()); // literal text before span if (m.group(1) != null) sb.append(s(BOLD_SPAN, m.group(1))); // **bold** else if (m.group(2) != null) sb.append(s(BOLD_SPAN, m.group(2))); // __bold__ else if (m.group(3) != null) sb.append(s(ITALIC_SPAN, m.group(3))); // *italic* else if (m.group(4) != null) sb.append(s(ITALIC_SPAN, m.group(4))); // _italic_ else if (m.group(5) != null) sb.append(s(CODE_SPAN, " " + m.group(5) + " ")); // `code` cursor = m.end(); } sb.append(text.substring(cursor)); // trailing literal text return sb.toString(); } // ── Helpers ──────────────────────────────────────────────────────────────── /** Builds a styled ANSI string: applies {@code style}, appends {@code text}, then resets. */ private static String s(AttributedStyle style, String text) { return new AttributedStringBuilder().style(style).append(text).style(RESET).toAnsi(); } /** Convenience: DEFAULT foreground + bold. */ private static AttributedStyle bold(int color) { return AttributedStyle.DEFAULT.bold().foreground(color); } /** Centers {@code text} within a field of {@code width} characters. */ private static String center(String text, int width) { int pad = width - text.length(); int left = Math.max(0, pad / 2); int right = Math.max(0, pad - left); return " ".repeat(left) + text + " ".repeat(right); } }
Step 5: Define main application class.
App.java
package com.sample.app; import com.sample.app.agent.LogicPuzzleSolver; import com.sample.app.console.ConsoleRenderer; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore; import java.time.Duration; import java.util.Scanner; import java.util.UUID; public class App { public static void main(String[] args) { try { // ---------------------------------------------------------- // Generate Conversation Id // ---------------------------------------------------------- String conversationId = UUID.randomUUID().toString(); // ---------------------------------------------------------- // Welcome Banner // ---------------------------------------------------------- ConsoleRenderer.printBanner(); ConsoleRenderer.info("Conversation : " + conversationId); ConsoleRenderer.info("Type 'exit' to quit."); System.out.println(); // ---------------------------------------------------------- // Configure Ollama // ---------------------------------------------------------- ChatModel model = OllamaChatModel.builder() .baseUrl("http://localhost:11434") .modelName("llama3.2") .timeout(Duration.ofMinutes(2)) .build(); // ---------------------------------------------------------- // Shared Memory Store // ---------------------------------------------------------- InMemoryChatMemoryStore memoryStore = new InMemoryChatMemoryStore(); // ---------------------------------------------------------- // Memory Provider // ---------------------------------------------------------- ChatMemoryProvider memoryProvider = id -> MessageWindowChatMemory.builder() .id(id) .maxMessages(20) .chatMemoryStore(memoryStore) .build(); // ---------------------------------------------------------- // Build AI Agent // ---------------------------------------------------------- LogicPuzzleSolver logicPuzzleSolver = AiServices.builder(LogicPuzzleSolver.class) .chatModel(model) .chatMemoryProvider(memoryProvider) .build(); // ---------------------------------------------------------- // Conversation Loop // ---------------------------------------------------------- Scanner scanner = new Scanner(System.in); while (true) { // Prompt ConsoleRenderer.printPrompt(); String input = scanner.nextLine().trim(); if (input.isBlank()) { continue; } if ("exit".equalsIgnoreCase(input) || "quit".equalsIgnoreCase(input)) { break; } ConsoleRenderer.thinking(); String response = logicPuzzleSolver.chat(conversationId, input); ConsoleRenderer.agent(response); } System.out.println(); ConsoleRenderer.info("Thanks for using Logic Puzzle Solver! Happy reading 📚✨"); } catch (Exception ex) { ConsoleRenderer.error("Unexpected error"); ConsoleRenderer.error(ex.getMessage()); } } }
Build the Project
Open a terminal and navigate to the directory containing the `pom.xml` file. Then run the following command to build the project:
mvn clean package
Run the Application
After the build completes successfully, the `chain-of-thought-agent-1.0.0.jar ` file will be available in the `target` directory. Start the application by running:
java -jar ./target/ chain-of-thought-agent-1.0.0.jar
$ java -jar ./target/chain-of-thought-agent-1.0.0.jar ╔════════════════════════════════════════════════════╗ ║ ✨ Logic Puzzle Solver AI ✨ ║ ╚════════════════════════════════════════════════════╝ ℹ Conversation : 23b10ab6-d096-45b3-a52b-77293403e328 ℹ Type 'exit' to quit. You ❯ Enter following puzzle Alice, Bob, and Charlie each own a different pet: a cat, a dog, and a rabbit. Alice does not own the dog. Bob does not own the cat. Charlie owns the rabbit. Who owns the cat? You can observe response like below. You ❯ Alice, Bob, and Charlie each own a different pet: a cat, a dog, and a rabbit. Alice does not own the dog. Bob does not own the cat. Charlie owns the rabbit. Who owns the cat? ⏳ Thinking… 📖 Logic Puzzle Solver ────────────────────────────────────────────────── ==================================== 🧩 Understanding the Puzzle ==================================== The puzzle states that three people (Alice, Bob, and Charlie) each own a different pet: a cat, a dog, and a rabbit. We need to figure out who owns which pet. We also know some additional information: • Alice does not own the dog. • Bob does not own the cat. • Charlie owns the rabbit. ==================================== 🧠 Step-by-Step Reasoning ==================================== Step 1: Since Charlie owns the rabbit, we can start with this fact. The remaining pets are the cat and the dog. Step 2: Now, let's look at Alice. We know she doesn't own the dog, so she must own either the cat or the rabbit. However, since Charlie already owns the rabbit, it means Alice must own the cat. Step 3: We don't need to consider Bob's pet yet because we have found a solution for Alice and Charlie. ==================================== ✅ Final Answer ==================================== Alice owns the cat.
This simple example demonstrates a powerful idea. Prompt engineering is not just about asking better questions, it is about designing better thinking processes. By setting the system instructions, we changed the way the model approaches the problem. The Foundation Model remains exactly the same. The Java code remains exactly the same. The only thing we changed was the reasoning strategy embedded in the System Prompt.
You can download this application from this link.
Previous Next Home
No comments:
Post a Comment