Prompt chaining¶
Original tutorial: Prompt chaining.
Prompt chaining runs a sequence of LLM calls where each step uses the result of the previous step. This workflow generates a joke, checks whether it has a punchline, and only improves it when needed.
Setup¶
Set OPENAI_API_KEY before running the model calls.
In [1]:
Copied!
var userHomeDir = System.getProperty("user.home");
var localRepoUrl = "file://" + userHomeDir + "/.m2/repository/";
var langchain4jVersion = "1.19.0";
var langgraph4jVersion = "1.9.0-beta6";
var userHomeDir = System.getProperty("user.home");
var localRepoUrl = "file://" + userHomeDir + "/.m2/repository/";
var langchain4jVersion = "1.19.0";
var langgraph4jVersion = "1.9.0-beta6";
In [2]:
Copied!
%dependency /add-repo local \{localRepoUrl} release|never snapshot|always
%dependency /add org.bsc.langgraph4j:langgraph4j-core:\{langgraph4jVersion}
%dependency /add dev.langchain4j:langchain4j-open-ai:\{langchain4jVersion}
%dependency /resolve
%dependency /add-repo local \{localRepoUrl} release|never snapshot|always
%dependency /add org.bsc.langgraph4j:langgraph4j-core:\{langgraph4jVersion}
%dependency /add dev.langchain4j:langchain4j-open-ai:\{langchain4jVersion}
%dependency /resolve
Repository local url: file:///Users/bsorrentino/.m2/repository/ added. Adding dependency org.bsc.langgraph4j:langgraph4j-core:1.9.0-beta6 Adding dependency dev.langchain4j:langchain4j-open-ai:1.19.0 Solving dependencies Resolved artifacts count: 12 Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-core/1.9.0-beta6/langgraph4j-core-1.9.0-beta6.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/async/async-generator/5.0.0/async-generator-5.0.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-open-ai/1.19.0/langchain4j-open-ai-1.19.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-core/1.19.0/langchain4j-core-1.19.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client/1.19.0/langchain4j-http-client-1.19.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client-jdk/1.19.0/langchain4j-http-client-jdk-1.19.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-core/2.22.1/jackson-core-2.22.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-databind/2.22.1/jackson-databind-2.22.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/knuddels/jtokkit/1.1.0/jtokkit-1.1.0.jar
In [3]:
Copied!
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.util.Objects;
var apiKey = Objects.requireNonNull(
System.getenv("OPENAI_API_KEY"),
"Set OPENAI_API_KEY before running this notebook."
);
var model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.temperature(0.0)
.build();
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.util.Objects;
var apiKey = Objects.requireNonNull(
System.getenv("OPENAI_API_KEY"),
"Set OPENAI_API_KEY before running this notebook."
);
var model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.temperature(0.0)
.build();
SLF4J: No SLF4J providers were found. SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
Define the state¶
The state keeps the topic and each version of the joke. Each node updates only the field it produces.
In [4]:
Copied!
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.Channel;
import org.bsc.langgraph4j.state.Channels;
import java.util.Map;
class JokeState extends AgentState {
static final String TOPIC = "topic";
static final String JOKE = "joke";
static final String IMPROVED_JOKE = "improved_joke";
static final String FINAL_JOKE = "final_joke";
static final Map<String, Channel<?>> SCHEMA = Map.of(
TOPIC, Channels.base(() -> ""),
JOKE, Channels.base(() -> ""),
IMPROVED_JOKE, Channels.base(() -> ""),
FINAL_JOKE, Channels.base(() -> "")
);
JokeState(Map<String, Object> initData) {
super(initData);
}
String topic() { return this.<String>value(TOPIC).orElse(""); }
String joke() { return this.<String>value(JOKE).orElse(""); }
String improvedJoke() { return this.<String>value(IMPROVED_JOKE).orElse(""); }
String finalJoke() { return this.<String>value(FINAL_JOKE).orElse(""); }
}
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.Channel;
import org.bsc.langgraph4j.state.Channels;
import java.util.Map;
class JokeState extends AgentState {
static final String TOPIC = "topic";
static final String JOKE = "joke";
static final String IMPROVED_JOKE = "improved_joke";
static final String FINAL_JOKE = "final_joke";
static final Map<String, Channel<?>> SCHEMA = Map.of(
TOPIC, Channels.base(() -> ""),
JOKE, Channels.base(() -> ""),
IMPROVED_JOKE, Channels.base(() -> ""),
FINAL_JOKE, Channels.base(() -> "")
);
JokeState(Map<String, Object> initData) {
super(initData);
}
String topic() { return this.<String>value(TOPIC).orElse(""); }
String joke() { return this.<String>value(JOKE).orElse(""); }
String improvedJoke() { return this.<String>value(IMPROVED_JOKE).orElse(""); }
String finalJoke() { return this.<String>value(FINAL_JOKE).orElse(""); }
}
Define the nodes and routing¶
In [5]:
Copied!
import dev.langchain4j.data.message.UserMessage;
import org.bsc.langgraph4j.action.EdgeAction;
import org.bsc.langgraph4j.action.NodeAction;
NodeAction<JokeState> generateJoke = state -> {
var response = model.chat(UserMessage.from("Write a short joke about " + state.topic()));
return Map.of(JokeState.JOKE, response.aiMessage().text());
};
EdgeAction<JokeState> checkPunchline = state ->
state.joke().contains("?") || state.joke().contains("!") ? "Pass" : "Fail";
NodeAction<JokeState> improveJoke = state -> {
var response = model.chat(UserMessage.from("Make this joke funnier by adding wordplay: " + state.joke()));
return Map.of(JokeState.IMPROVED_JOKE, response.aiMessage().text());
};
NodeAction<JokeState> polishJoke = state -> {
var response = model.chat(UserMessage.from("Add a surprising twist to this joke: " + state.improvedJoke()));
return Map.of(JokeState.FINAL_JOKE, response.aiMessage().text());
};
import dev.langchain4j.data.message.UserMessage;
import org.bsc.langgraph4j.action.EdgeAction;
import org.bsc.langgraph4j.action.NodeAction;
NodeAction<JokeState> generateJoke = state -> {
var response = model.chat(UserMessage.from("Write a short joke about " + state.topic()));
return Map.of(JokeState.JOKE, response.aiMessage().text());
};
EdgeAction<JokeState> checkPunchline = state ->
state.joke().contains("?") || state.joke().contains("!") ? "Pass" : "Fail";
NodeAction<JokeState> improveJoke = state -> {
var response = model.chat(UserMessage.from("Make this joke funnier by adding wordplay: " + state.joke()));
return Map.of(JokeState.IMPROVED_JOKE, response.aiMessage().text());
};
NodeAction<JokeState> polishJoke = state -> {
var response = model.chat(UserMessage.from("Add a surprising twist to this joke: " + state.improvedJoke()));
return Map.of(JokeState.FINAL_JOKE, response.aiMessage().text());
};
Build and run the workflow¶
In [6]:
Copied!
import org.bsc.langgraph4j.StateGraph;
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;
import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
var workflow = new StateGraph<>(JokeState.SCHEMA, JokeState::new)
.addNode("generate_joke", node_async(generateJoke))
.addNode("improve_joke", node_async(improveJoke))
.addNode("polish_joke", node_async(polishJoke))
.addEdge(START, "generate_joke")
.addConditionalEdges("generate_joke", edge_async(checkPunchline), Map.of(
"Pass", END,
"Fail", "improve_joke"
))
.addEdge("improve_joke", "polish_joke")
.addEdge("polish_joke", END);
var chain = workflow.compile();
var result = chain.invoke(Map.of(JokeState.TOPIC, "cats"))
.orElseThrow();
System.out.println("Initial joke:\n" + result.joke());
if (result.improvedJoke().isBlank()) {
System.out.println("\nFinal joke:\n" + result.joke());
} else {
System.out.println("\nImproved joke:\n" + result.improvedJoke());
System.out.println("\nFinal joke:\n" + result.finalJoke());
}
import org.bsc.langgraph4j.StateGraph;
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;
import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
var workflow = new StateGraph<>(JokeState.SCHEMA, JokeState::new)
.addNode("generate_joke", node_async(generateJoke))
.addNode("improve_joke", node_async(improveJoke))
.addNode("polish_joke", node_async(polishJoke))
.addEdge(START, "generate_joke")
.addConditionalEdges("generate_joke", edge_async(checkPunchline), Map.of(
"Pass", END,
"Fail", "improve_joke"
))
.addEdge("improve_joke", "polish_joke")
.addEdge("polish_joke", END);
var chain = workflow.compile();
var result = chain.invoke(Map.of(JokeState.TOPIC, "cats"))
.orElseThrow();
System.out.println("Initial joke:\n" + result.joke());
if (result.improvedJoke().isBlank()) {
System.out.println("\nFinal joke:\n" + result.joke());
} else {
System.out.println("\nImproved joke:\n" + result.improvedJoke());
System.out.println("\nFinal joke:\n" + result.finalJoke());
}
Initial joke: Why was the cat sitting on the computer? Because it wanted to keep an eye on the mouse! Final joke: Why was the cat sitting on the computer? Because it wanted to keep an eye on the mouse!
In [ ]:
Copied!