{
"cells": [
{
"cell_type": "markdown",
"id": "java-intro",
"metadata": {},
"source": [
"# Building a Java Multi-Turn, Multi-Agent Conversational System with LangGraph4j\n",
"\n",
"Welcome to the tutorial for building java agentic apps powered by LangChain4j and LangGraph4j.\n",
"\n",
"We will build a conversational application with two specialized agents: a career advisor and an education advisor. The graph keeps the conversation history in an in-memory checkpoint and hands the conversation between advisors when the user's intent changes.\n",
"\n",
"You will learn how to:\n",
"\n",
"* Define LangChain4j tools in Java\n",
"* Wrap specialized agents in LangGraph4j nodes\n",
"* Pause between turns while retaining conversation state\n",
"* Route a follow-up turn to another agent using the same thread ID"
]
},
{
"cell_type": "markdown",
"id": "java-prerequisites",
"metadata": {},
"source": [
"## Prerequisites\n",
"\n",
"Install the following before running the notebook:\n",
"\n",
"* **Java 11 or newer**. Check with `java -version`.\n",
"* **Python and JupyterLab or Jupyter Notebook**. On macOS, the Jupyter documentation's Homebrew recipe is `brew install jupyter`. On Windows, install Python from [python.org](https://www.python.org/downloads/windows/) and then run `pip install jupyterlab` (or `pip install notebook`).\n",
"* **The JJava kernel**. Download `jjava-${version}-kernelspec.zip` from the [JJava GitHub releases](https://github.com/dflib/jjava/releases), unzip it, and install the kernel from the directory containing the unzipped folder:\n",
"\n",
"```bash\n",
"jupyter kernelspec install jjava-${version}-kernelspec --user --name=java\n",
"```\n",
"\n",
"* **Maven 3.9 or newer**, available as `mvn` on `PATH`. The dependency cell uses it to download LangGraph4j, LangChain4j, and their transitive dependencies.\n",
"* **Network access to Maven Central** for the first dependency download.\n",
"* An `OPENAI_API_KEY` environment variable. The key is read by Java and is never stored in this notebook.\n",
"\n",
"Verify the installation with `jupyter kernelspec list`, then start Jupyter with `jupyter lab` or `jupyter notebook` and select the **Java (jjava)** kernel. If another Java kernel is already installed under the same name, remove it first with `jupyter kernelspec remove java`.\n",
"\n",
"These requirements follow the [JJava prerequisites](https://dflib.org/jjava/docs/1.x/#_prerequisites)."
]
},
{
"cell_type": "markdown",
"id": "java-step-1",
"metadata": {},
"source": [
"### **Step 1: Prepare the Java dependencies**\n",
"\n",
"This notebook uses the Java (jjava) kernel and the LangGraph4j/LangChain4j libraries. The tutorial implementation itself is embedded below; only the third-party JARs need to be placed on the kernel classpath. Run the next Java cell once from this notebook. It writes a standalone Maven POM and downloads the dependencies without relying on the repository's Java source tree.\n",
"\n",
"```bash\n",
"mkdir -p java-notebook-dependencies\n",
"cat > java-notebook-dependencies/pom.xml <<'EOF'\n",
"\n",
"\n",
" 4.0.0\n",
" local.notebook\n",
" langgraph4j-notebook-dependencies\n",
" 1.0.0\n",
" \n",
" \n",
" \n",
" dev.langchain4j\n",
" langchain4j-bom\n",
" 1.19.0\n",
" pom\n",
" import\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" org.bsc.langgraph4j\n",
" langgraph4j-agent-executor\n",
" 1.8.26\n",
" \n",
" \n",
" org.bsc.langgraph4j\n",
" langgraph4j-langchain4j\n",
" 1.8.26\n",
" \n",
" \n",
" dev.langchain4j\n",
" langchain4j-open-ai\n",
" \n",
" \n",
"\n",
"EOF\n",
"mvn -f java-notebook-dependencies/pom.xml dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=lib\n",
"```\n",
"\n",
"The standalone POM downloads the direct and transitive dependencies from Maven Central into `java-notebook-dependencies/lib`. The optional shell commands above are equivalent to the runnable Java cell. The following classpath cell adds those JARs to jjava. No application source files are imported by this notebook."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-download-dependencies",
"metadata": {},
"outputs": [],
"source": [
"import java.io.IOException;\n",
"import java.nio.file.Files;\n",
"import java.nio.file.Path;\n",
"\n",
"Path dependencyDirectory = Path.of(\"java-notebook-dependencies\");\n",
"Path pomFile = dependencyDirectory.resolve(\"pom.xml\");\n",
"try {\n",
" Files.createDirectories(dependencyDirectory);\n",
"\n",
"String pom = \"\"\"\n",
" \n",
" \n",
" 4.0.0\n",
" local.notebook\n",
" langgraph4j-notebook-dependencies\n",
" 1.0.0\n",
" \n",
" \n",
" \n",
" dev.langchain4j\n",
" langchain4j-bom\n",
" 1.19.0\n",
" pom\n",
" import\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" org.bsc.langgraph4j\n",
" langgraph4j-agent-executor\n",
" 1.8.26\n",
" \n",
" \n",
" org.bsc.langgraph4j\n",
" langgraph4j-langchain4j\n",
" 1.8.26\n",
" \n",
" \n",
" dev.langchain4j\n",
" langchain4j-open-ai\n",
" \n",
" \n",
" \n",
" \"\"\";\n",
" Files.writeString(pomFile, pom);\n",
"\n",
" Process maven = new ProcessBuilder(\n",
" \"mvn\", \"-f\", pomFile.toString(), \"dependency:copy-dependencies\",\n",
" \"-DincludeScope=runtime\", \"-DoutputDirectory=lib\")\n",
" .inheritIO()\n",
" .start();\n",
" int exitCode = maven.waitFor();\n",
" if (exitCode != 0) {\n",
" throw new IllegalStateException(\"Maven dependency download failed with exit code \" + exitCode);\n",
" }\n",
" System.out.println(\"Dependencies are ready in \" + dependencyDirectory.resolve(\"lib\").toAbsolutePath());\n",
"} catch (IOException | InterruptedException exception) {\n",
" if (exception instanceof InterruptedException) {\n",
" Thread.currentThread().interrupt();\n",
" }\n",
" throw new IllegalStateException(\"Could not download Maven dependencies\", exception);\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-classpath",
"metadata": {},
"outputs": [],
"source": [
"%classpath java-notebook-dependencies/lib/*"
]
},
{
"cell_type": "markdown",
"id": "java-step-2",
"metadata": {},
"source": [
"### **Step 2: Configure the model securely**\n",
"\n",
"Set `OPENAI_API_KEY` in the environment before starting Jupyter. Do not paste an API key into a notebook cell. The model and endpoint can also be overridden for an OpenAI-compatible provider such as DeepInfra."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-config",
"metadata": {},
"outputs": [],
"source": [
"import dev.langchain4j.agent.tool.P;\n",
"import dev.langchain4j.agent.tool.ReturnBehavior;\n",
"import dev.langchain4j.agent.tool.Tool;\n",
"import dev.langchain4j.data.message.AiMessage;\n",
"import dev.langchain4j.data.message.ChatMessage;\n",
"import dev.langchain4j.data.message.SystemMessage;\n",
"import dev.langchain4j.data.message.ToolExecutionResultMessage;\n",
"import dev.langchain4j.data.message.UserMessage;\n",
"import dev.langchain4j.model.chat.ChatModel;\n",
"import dev.langchain4j.model.openai.OpenAiChatModel;\n",
"import org.bsc.langgraph4j.CompiledGraph;\n",
"import org.bsc.langgraph4j.GraphDefinition;\n",
"import org.bsc.langgraph4j.GraphInput;\n",
"import org.bsc.langgraph4j.GraphStateException;\n",
"import org.bsc.langgraph4j.RunnableConfig;\n",
"import org.bsc.langgraph4j.StateGraph;\n",
"import org.bsc.langgraph4j.agentexecutor.AgentExecutor;\n",
"import org.bsc.langgraph4j.action.AsyncNodeAction;\n",
"import org.bsc.langgraph4j.checkpoint.MemorySaver;\n",
"import org.bsc.langgraph4j.langchain4j.serializer.std.LC4jStateSerializer;\n",
"import org.bsc.langgraph4j.prebuilt.MessagesState;\n",
"import org.bsc.langgraph4j.prebuilt.MessagesStateGraph;\n",
"import java.util.Objects;\n",
"import java.util.concurrent.CompletableFuture;\n",
"import java.util.List;\n",
"import java.util.Map;\n",
"\n",
"String apiKey = System.getenv(\"OPENAI_API_KEY\");\n",
"if (apiKey == null || apiKey.isBlank()) {\n",
" throw new IllegalStateException(\"Set OPENAI_API_KEY before running the model cells.\");\n",
"}\n",
"\n",
"String modelName = System.getenv().getOrDefault(\"OPENAI_MODEL\", \"gpt-4o-mini\");\n",
"String baseUrl = System.getenv().getOrDefault(\"OPENAI_BASE_URL\", \"https://api.openai.com/v1\");\n",
"System.out.println(\"Using model: \" + modelName);\n",
"System.out.println(\"Using endpoint: \" + baseUrl);"
]
},
{
"cell_type": "markdown",
"id": "java-step-3",
"metadata": {},
"source": [
"### **Step 3: Define the agent tools**\n",
"\n",
"The Java implementation expresses tools with LangChain4j's `@Tool` and `@P` annotations. All tool classes are defined in the next cell, so the notebook does not depend on application source files. The advisor-specific wrappers expose only the tools that belong to each agent: `getCareerPaths`, `getLearningResources`, `transfer_to_education_advisor`, and `transfer_to_career_advisor`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-tools",
"metadata": {},
"outputs": [],
"source": [
"interface Advisor {\n",
" List respond(List messages);\n",
"}\n",
"\n",
"class CareerEducationTools {\n",
" private static final List CAREER_PATHS =\n",
" List.of(\"data science\", \"product management\", \"cybersecurity\");\n",
" private static final Map> LEARNING_RESOURCES = Map.of(\n",
" \"data science\", List.of(\"Coursera: IBM Data Science\", \"edX: Harvard's Data Science Series\"),\n",
" \"product management\", List.of(\"Udemy: Become a Product Manager\", \"Reforge Programs\"),\n",
" \"cybersecurity\", List.of(\"Cybrary\", \"CompTIA Security+ Certification\"));\n",
"\n",
" @Tool(\"Suggest career options based on general user interest.\")\n",
" public String getCareerPaths() {\n",
" return CAREER_PATHS.get(java.util.concurrent.ThreadLocalRandom.current()\n",
" .nextInt(CAREER_PATHS.size()));\n",
" }\n",
"\n",
" @Tool(\"Provide online resources or certifications for a given career path.\")\n",
" public List getLearningResources(\n",
" @P(\"One of: data science, product management, cybersecurity\") String career) {\n",
" List resources = LEARNING_RESOURCES.get(career.toLowerCase());\n",
" if (resources == null) {\n",
" throw new IllegalArgumentException(\"Unsupported career path: \" + career);\n",
" }\n",
" return resources;\n",
" }\n",
"\n",
" @Tool(value = \"Ask the education advisor agent for help.\",\n",
" returnBehavior = ReturnBehavior.IMMEDIATE)\n",
" public String transferToEducationAdvisor() {\n",
" return \"Successfully transferred to education advisor.\";\n",
" }\n",
"\n",
" @Tool(value = \"Ask the career advisor agent for help.\",\n",
" returnBehavior = ReturnBehavior.IMMEDIATE)\n",
" public String transferToCareerAdvisor() {\n",
" return \"Successfully transferred to career advisor.\";\n",
" }\n",
"}\n",
"\n",
"class CareerAdvisorTools {\n",
" private final CareerEducationTools tools;\n",
"\n",
" CareerAdvisorTools(CareerEducationTools tools) {\n",
" this.tools = tools;\n",
" }\n",
"\n",
" @Tool(\"Suggest career options based on general user interest.\")\n",
" public String getCareerPaths() {\n",
" return tools.getCareerPaths();\n",
" }\n",
"\n",
" @Tool(name = \"transfer_to_education_advisor\",\n",
" value = \"Ask the education advisor agent for help.\",\n",
" returnBehavior = ReturnBehavior.IMMEDIATE)\n",
" public String transferToEducationAdvisor() {\n",
" return tools.transferToEducationAdvisor();\n",
" }\n",
"}\n",
"\n",
"class EducationAdvisorTools {\n",
" private final CareerEducationTools tools;\n",
"\n",
" EducationAdvisorTools(CareerEducationTools tools) {\n",
" this.tools = tools;\n",
" }\n",
"\n",
" @Tool(\"Provide online resources or certifications for a given career path.\")\n",
" public List getLearningResources(\n",
" @P(\"One of: data science, product management, cybersecurity\") String career) {\n",
" return tools.getLearningResources(career);\n",
" }\n",
"\n",
" @Tool(name = \"transfer_to_career_advisor\",\n",
" value = \"Ask the career advisor agent for help.\",\n",
" returnBehavior = ReturnBehavior.IMMEDIATE)\n",
" public String transferToCareerAdvisor() {\n",
" return tools.transferToCareerAdvisor();\n",
" }\n",
"}\n",
"\n",
"CareerEducationTools toolSet = new CareerEducationTools();\n",
"CareerAdvisorTools careerTools = new CareerAdvisorTools(toolSet);\n",
"EducationAdvisorTools educationTools = new EducationAdvisorTools(toolSet);\n",
"\n",
"System.out.println(\"Example career: \" + toolSet.getCareerPaths());\n",
"System.out.println(\"Data science resources: \" + toolSet.getLearningResources(\"data science\"));"
]
},
{
"cell_type": "markdown",
"id": "java-step-4",
"metadata": {},
"source": [
"### **Step 4: Create the specialized agents**\n",
"\n",
"Each agent uses the same chat model but has a different system prompt and tool set. `AgentExecutor` provides the ReAct-style tool-calling loop, while the embedded graph controller will decide which advisor receives the next turn."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-agents",
"metadata": {},
"outputs": [],
"source": [
"ChatModel model = OpenAiChatModel.builder()\n",
" .apiKey(apiKey)\n",
" .modelName(modelName)\n",
" .baseUrl(baseUrl)\n",
" .temperature(0.0)\n",
" .maxRetries(2)\n",
" .build();\n",
"\n",
"final CompiledGraph careerAgent;\n",
"final CompiledGraph educationAgent;\n",
"try {\n",
" careerAgent = AgentExecutor.builder()\n",
" .chatModel(model)\n",
" .systemMessage(SystemMessage.from(\n",
" \"You are a career expert. Help users explore career options. \"\n",
" + \"If they ask about courses or education, transfer to the education advisor. \"\n",
" + \"Always explain your reasoning before transferring.\"))\n",
" .toolsFromObject(careerTools)\n",
" .build()\n",
" .compile();\n",
"\n",
" educationAgent = AgentExecutor.builder()\n",
" .chatModel(model)\n",
" .systemMessage(SystemMessage.from(\n",
" \"You are an education expert. Recommend learning paths for specific careers. \"\n",
" + \"If the user changes their career preference, transfer back to the career advisor. \"\n",
" + \"Always explain your reasoning before transferring.\"))\n",
" .toolsFromObject(educationTools)\n",
" .build()\n",
" .compile();\n",
"} catch (GraphStateException exception) {\n",
" throw new IllegalStateException(\"Could not compile the advisor agents\", exception);\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "java-step-5",
"metadata": {},
"source": [
"### **Step 5: Wrap the agents in LangGraph4j advisors**\n",
"\n",
"The embedded `Advisor` functional interface accepts the complete `ChatMessage` history and returns the messages generated by an agent. This is the Java equivalent of the Python `@task` functions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-advisor-functions",
"metadata": {},
"outputs": [],
"source": [
"Advisor careerAdvisor = messages -> careerAgent.invoke(Map.of(\"messages\", messages))\n",
" .orElseThrow(() -> new IllegalStateException(\"Career advisor produced no state\"))\n",
" .messages();\n",
"\n",
"Advisor educationAdvisor = messages -> educationAgent.invoke(Map.of(\"messages\", messages))\n",
" .orElseThrow(() -> new IllegalStateException(\"Education advisor produced no state\"))\n",
" .messages();"
]
},
{
"cell_type": "markdown",
"id": "java-step-6",
"metadata": {},
"source": [
"### **Step 6: Create the multi-turn controller**\n",
"\n",
"The embedded `CareerEducationGraph` is the LangGraph4j controller. It starts at the career advisor, stores messages in a `MemorySaver`, interrupts after each answer, and routes the next turn to the education advisor when the user asks about courses, learning, or resources. A request using the same thread ID resumes the checkpointed conversation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-graph",
"metadata": {},
"outputs": [],
"source": [
"class CareerEducationGraph {\n",
" static final String CAREER_ADVISOR = \"career_advisor\";\n",
" static final String EDUCATION_ADVISOR = \"education_advisor\";\n",
" private static final String CAREER_WAIT = \"career_wait\";\n",
" private static final String EDUCATION_WAIT = \"education_wait\";\n",
"\n",
" private final CompiledGraph> graph;\n",
"\n",
" CareerEducationGraph(Advisor careerAdvisor, Advisor educationAdvisor) {\n",
" Objects.requireNonNull(careerAdvisor, \"careerAdvisor\");\n",
" Objects.requireNonNull(educationAdvisor, \"educationAdvisor\");\n",
" try {\n",
" var serializer = new LC4jStateSerializer>(MessagesState::new);\n",
" StateGraph> workflow = new MessagesStateGraph<>(serializer);\n",
" workflow.addNode(CAREER_ADVISOR,\n",
" AsyncNodeAction.node_async(state -> invoke(careerAdvisor, state)));\n",
" workflow.addNode(EDUCATION_ADVISOR,\n",
" AsyncNodeAction.node_async(state -> invoke(educationAdvisor, state)));\n",
" workflow.addNode(CAREER_WAIT, AsyncNodeAction.node_async(state -> Map.of()));\n",
" workflow.addNode(EDUCATION_WAIT, AsyncNodeAction.node_async(state -> Map.of()));\n",
" workflow.addEdge(GraphDefinition.START, CAREER_ADVISOR);\n",
" workflow.addEdge(CAREER_ADVISOR, CAREER_WAIT);\n",
" workflow.addEdge(EDUCATION_ADVISOR, EDUCATION_WAIT);\n",
" workflow.addConditionalEdges(CAREER_WAIT,\n",
" state -> CompletableFuture.completedFuture(nextAdvisor(state, CAREER_ADVISOR)),\n",
" Map.of(CAREER_ADVISOR, CAREER_ADVISOR, EDUCATION_ADVISOR, EDUCATION_ADVISOR));\n",
" workflow.addConditionalEdges(EDUCATION_WAIT,\n",
" state -> CompletableFuture.completedFuture(nextAdvisor(state, EDUCATION_ADVISOR)),\n",
" Map.of(CAREER_ADVISOR, CAREER_ADVISOR, EDUCATION_ADVISOR, EDUCATION_ADVISOR));\n",
" graph = workflow.compile(org.bsc.langgraph4j.CompileConfig.builder()\n",
" .checkpointSaver(new MemorySaver())\n",
" .interruptAfter(CAREER_WAIT, EDUCATION_WAIT)\n",
" .interruptBeforeEdge(true)\n",
" .releaseThread(false)\n",
" .build());\n",
" } catch (GraphStateException exception) {\n",
" throw new IllegalStateException(\"Could not compile the career conversation graph\", exception);\n",
" }\n",
" }\n",
"\n",
" ConversationTurn turn(String threadId, String userInput) {\n",
" if (threadId == null || threadId.isBlank()) {\n",
" throw new IllegalArgumentException(\"threadId must not be blank\");\n",
" }\n",
" if (userInput == null || userInput.isBlank()) {\n",
" throw new IllegalArgumentException(\"userInput must not be blank\");\n",
" }\n",
" var config = RunnableConfig.builder().threadId(threadId).build();\n",
" Map input = Map.of(MessagesState.MESSAGES_STATE,\n",
" List.of(UserMessage.from(userInput)));\n",
" GraphInput graphInput = graph.stateOf(config).isPresent()\n",
" ? GraphInput.resume(input) : GraphInput.args(input);\n",
" var outputs = graph.stream(graphInput, config).stream().toList();\n",
" if (outputs.isEmpty()) {\n",
" throw new IllegalStateException(\"The conversation graph produced no output\");\n",
" }\n",
" var output = outputs.get(outputs.size() - 1);\n",
" String response = output.state().messages().stream()\n",
" .filter(AiMessage.class::isInstance).map(AiMessage.class::cast)\n",
" .reduce((first, second) -> second).map(AiMessage::text).orElse(\"\");\n",
" String advisor = CAREER_WAIT.equals(output.node()) ? CAREER_ADVISOR\n",
" : EDUCATION_WAIT.equals(output.node()) ? EDUCATION_ADVISOR : output.node();\n",
" return new ConversationTurn(threadId, advisor, response, true);\n",
" }\n",
"\n",
" private static Map invoke(Advisor advisor, MessagesState state) {\n",
" int previousSize = state.messages().size();\n",
" List generated = advisor.respond(List.copyOf(state.messages()));\n",
" if (generated == null || generated.isEmpty()) {\n",
" throw new IllegalStateException(\"Advisor produced no messages\");\n",
" }\n",
" List newMessages = generated.size() >= previousSize\n",
" && generated.subList(0, previousSize).equals(state.messages())\n",
" ? generated.subList(previousSize, generated.size()) : generated;\n",
" return Map.of(MessagesState.MESSAGES_STATE, List.copyOf(newMessages));\n",
" }\n",
"\n",
" private static String nextAdvisor(MessagesState state, String currentAdvisor) {\n",
" for (int index = state.messages().size() - 1; index >= 0; index--) {\n",
" var message = state.messages().get(index);\n",
" if (message instanceof ToolExecutionResultMessage toolResult) {\n",
" if (\"transfer_to_education_advisor\".equals(toolResult.toolName())) {\n",
" return EDUCATION_ADVISOR;\n",
" }\n",
" if (\"transfer_to_career_advisor\".equals(toolResult.toolName())) {\n",
" return CAREER_ADVISOR;\n",
" }\n",
" }\n",
" }\n",
" String latestUserText = state.messages().stream()\n",
" .filter(UserMessage.class::isInstance).map(UserMessage.class::cast)\n",
" .reduce((first, second) -> second).map(UserMessage::singleText)\n",
" .orElse(\"\").toLowerCase();\n",
" if (CAREER_ADVISOR.equals(currentAdvisor)\n",
" && containsAny(latestUserText, \"course\", \"education\", \"learn\", \"resource\")) {\n",
" return EDUCATION_ADVISOR;\n",
" }\n",
" if (EDUCATION_ADVISOR.equals(currentAdvisor)\n",
" && containsAny(latestUserText, \"career\", \"change\", \"different path\")) {\n",
" return CAREER_ADVISOR;\n",
" }\n",
" return currentAdvisor;\n",
" }\n",
"\n",
" private static boolean containsAny(String text, String... terms) {\n",
" for (String term : terms) {\n",
" if (text.contains(term)) return true;\n",
" }\n",
" return false;\n",
" }\n",
"\n",
" record ConversationTurn(String threadId, String advisor, String response,\n",
" boolean waitingForUser) {}\n",
"}\n",
"\n",
"CareerEducationGraph conversation = new CareerEducationGraph(careerAdvisor, educationAdvisor);\n",
"String threadId = java.util.UUID.randomUUID().toString();\n",
"System.out.println(\"Conversation thread: \" + threadId);"
]
},
{
"cell_type": "markdown",
"id": "java-step-7",
"metadata": {},
"source": [
"### **Step 7: Test the multi-turn conversation**\n",
"\n",
"The three prompts below use one stable thread ID. The first turn starts with the career advisor; the second asks about courses and is routed to the education advisor; the third remains in the education conversation. This is the Java equivalent of resuming the Python graph with `Command(resume=...)`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "java-conversation",
"metadata": {},
"outputs": [],
"source": [
"List prompts = List.of(\n",
" \"I'm interested in technology but not sure what career fits me.\",\n",
" \"That sounds good. What courses should I take to get started?\",\n",
" \"Awesome! Are these resources beginner friendly?\");\n",
"\n",
"for (int index = 0; index < prompts.size(); index++) {\n",
" String prompt = prompts.get(index);\n",
" var turn = conversation.turn(threadId, prompt);\n",
" System.out.println(\"\\n--- Conversation Turn \" + (index + 1) + \" ---\");\n",
" System.out.println(\"User: \" + prompt);\n",
" System.out.println(\"Advisor: \" + turn.advisor());\n",
" System.out.println(\"Assistant: \" + turn.response());\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "java-notes",
"metadata": {},
"source": [
"### Notes\n",
"\n",
"`MemorySaver` is process-local and is intended for this tutorial. For a deployed application, replace it with a persistent checkpoint saver and keep the thread ID stable across requests. The same graph is also available through the Java service endpoint documented in `java/README.md`."
]
},
{
"cell_type": "raw",
"id": "99028f88-688f-41f6-9112-e59612efb75b",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Java (jjava)",
"language": "java",
"name": "java"
},
"language_info": {
"codemirror_mode": "java",
"file_extension": ".jshell",
"mimetype": "text/x-java-source",
"name": "Java",
"pygments_lexer": "java",
"version": "21.0.10+8-LTS-217"
}
},
"nbformat": 4,
"nbformat_minor": 5
}