-
Notifications
You must be signed in to change notification settings - Fork 183
Add Spring AI samples #775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
e80d5f0
Spring AI samples
donald-pinckney de64249
Fix sample configs and remove runtimeOnly workarounds
donald-pinckney ddc7720
Update samples for T15: remove @DeterministicTool and sandboxing sample
donald-pinckney df3ec0e
Clean up Spring AI samples PR for merge
donald-pinckney 7011199
Use mavenLocal for temporal-spring-ai instead of composite build
donald-pinckney 7f4acba
Add TASK_QUEUE.json tracking remaining PR work
donald-pinckney c941ccc
Updated after changes to Spring AI
donald-pinckney fbae80a
Add snipsync markers to Spring AI samples
donald-pinckney 5354c42
spring-ai samples: pin to released temporal-spring-ai 1.35.0
donald-pinckney bb4f3cf
Merge remote-tracking branch 'origin/main' into d/20260406-164121
donald-pinckney a3c05a6
Group spring-ai samples under a single springai/ directory
donald-pinckney 79a9966
springai/mcp: await initialization in chat signal handler
donald-pinckney 3a4e2ab
springai/multimodel: drop redundant default: CLI prefix
donald-pinckney 73bb79a
springai/rag: show usage when add/search are typed without args
donald-pinckney 1b47bd6
Add ai-sdk to CODEOWNERS for springai
donald-pinckney 2e02881
springai/multimodel: include "think" in chat() javadoc model names
donald-pinckney 1e9c367
springai/basic: explain why run()'s systemPrompt parameter is unused
donald-pinckney 6b8f7ac
gradle/springai: document the Spring Boot plugin/BOM version skew
donald-pinckney 00ea6f3
.gitignore: collapse per-module build/out entries into globs
donald-pinckney bd76c1a
Merge remote-tracking branch 'origin/main' into d/20260406-164121
donald-pinckney 7d3c067
README: document Spring AI samples and Java 17 requirement
donald-pinckney 273a2e2
springai samples: address Copilot review nits
donald-pinckney 903fd2c
Untrack .vscode/ IDE settings
donald-pinckney 5292b27
undo gitignore change
donald-pinckney 5f91882
springai/multimodel: use LinkedHashMap for chatClients
donald-pinckney 4d32397
springai/rag: capture lastResponse before signaling, not after
donald-pinckney c3a3c88
springai/multimodel: capture previous response before signaling
donald-pinckney c606d2a
Remove TASK_QUEUE.json
donald-pinckney 24ba8c0
Update README.md
donald-pinckney File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,10 @@ | ||
| # Primary owners | ||
|
|
||
| * @tsurdilo @temporalio/sdk @antmendoza | ||
| * @tsurdilo @temporalio/sdk @antmendoza | ||
|
|
||
| # Below are owners for samples for modules | ||
| # that are owned by teams other than the SDK team. | ||
| # For each one, we add the owning team, as well as | ||
| # @temporalio/sdk, so the SDK team can continue to | ||
| # manage repo-wide concerns | ||
| /springai/ @temporalio/ai-sdk @temporalio/sdk |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Shared configuration for all Spring AI sample modules. | ||
| // Applied via: apply from: "$rootDir/gradle/springai.gradle" | ||
| // | ||
| // Note on Spring Boot version skew: the root build.gradle pins the | ||
| // org.springframework.boot Gradle plugin at $springBootPluginVersion (currently | ||
| // 2.7.13) for the legacy springboot/ samples. Spring AI 1.1.0 requires Spring | ||
| // Boot 3.5.x, which we get by importing the spring-boot-dependencies BOM at | ||
| // $springBootVersionForSpringAi below. The plugin and the BOM are independent — | ||
| // the plugin contributes bootJar/bootRun task wiring, the BOM dictates | ||
| // dependency versions — so this works in practice even though the two version | ||
| // numbers don't match. Long-term fix is to either move the plugin declaration | ||
| // out of the root plugins block (so each module applies its own version) or | ||
| // migrate the legacy springboot/ samples to Spring Boot 3.x; until one of those | ||
| // happens, this skew is intentional. | ||
|
|
||
| apply plugin: 'org.springframework.boot' | ||
| apply plugin: 'io.spring.dependency-management' | ||
|
|
||
| ext { | ||
| springBootVersionForSpringAi = '3.5.3' | ||
| springAiVersion = '1.1.0' | ||
| } | ||
|
|
||
| java { | ||
| sourceCompatibility = JavaVersion.VERSION_17 | ||
| targetCompatibility = JavaVersion.VERSION_17 | ||
| } | ||
|
|
||
| dependencyManagement { | ||
| imports { | ||
| mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersionForSpringAi" | ||
| mavenBom "org.springframework.ai:spring-ai-bom:$springAiVersion" | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation "io.temporal:temporal-spring-boot-starter:$javaSDKVersion" | ||
| implementation "io.temporal:temporal-spring-ai:$javaSDKVersion" | ||
| // temporal-spring-ai declares temporal-sdk as compileOnly, so bring it in explicitly. | ||
| implementation "io.temporal:temporal-sdk:$javaSDKVersion" | ||
|
|
||
| // Spring Boot | ||
| implementation 'org.springframework.boot:spring-boot-starter' | ||
|
|
||
| dependencies { | ||
| errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') | ||
| errorprone('com.google.errorprone:error_prone_core:2.28.0') | ||
| } | ||
| } | ||
|
|
||
| bootJar { | ||
| enabled = false | ||
| } | ||
|
|
||
| jar { | ||
| enabled = true | ||
| } | ||
|
|
||
| bootRun { | ||
| standardInput = System.in | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| rootProject.name = 'temporal-java-samples' | ||
| include 'core' | ||
| include 'springai:basic' | ||
| include 'springai:mcp' | ||
| include 'springai:multimodel' | ||
| include 'springai:rag' | ||
| include 'springboot' | ||
| include 'springboot-basic' | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| apply from: "$rootDir/gradle/springai.gradle" | ||
|
|
||
| dependencies { | ||
| implementation 'org.springframework.ai:spring-ai-starter-model-openai' | ||
| } |
87 changes: 87 additions & 0 deletions
87
springai/basic/src/main/java/io/temporal/samples/springai/chat/ChatExampleApplication.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package io.temporal.samples.springai.chat; | ||
|
|
||
| import io.temporal.client.WorkflowClient; | ||
| import io.temporal.client.WorkflowOptions; | ||
| import java.util.Scanner; | ||
| import java.util.UUID; | ||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
| import org.springframework.context.event.EventListener; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| /** | ||
| * Example application demonstrating the Spring AI Temporal plugin. | ||
| * | ||
| * <p>Starts an interactive chat workflow where each AI call is a durable Temporal activity with | ||
| * automatic retries and timeout handling. | ||
| */ | ||
| @SpringBootApplication | ||
| public class ChatExampleApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.run(ChatExampleApplication.class, args); | ||
| } | ||
| } | ||
|
|
||
| @Component | ||
| class ChatRunner { | ||
|
|
||
| private final WorkflowClient workflowClient; | ||
|
|
||
| ChatRunner(WorkflowClient workflowClient) { | ||
| this.workflowClient = workflowClient; | ||
| } | ||
|
|
||
| @EventListener(ApplicationReadyEvent.class) | ||
| public void run() { | ||
| String workflowId = "chat-" + UUID.randomUUID().toString().substring(0, 8); | ||
|
|
||
| System.out.println("\n==========================================="); | ||
| System.out.println(" Spring AI + Temporal Chat Demo"); | ||
| System.out.println("==========================================="); | ||
| System.out.println("Workflow ID: " + workflowId); | ||
| System.out.println("Type messages, or 'quit' to exit.\n"); | ||
|
|
||
| // Start the chat workflow | ||
| ChatWorkflow workflow = | ||
| workflowClient.newWorkflowStub( | ||
| ChatWorkflow.class, | ||
| WorkflowOptions.newBuilder() | ||
| .setWorkflowId(workflowId) | ||
| .setTaskQueue("spring-ai-example") | ||
| .build()); | ||
|
|
||
| WorkflowClient.start(workflow::run, "You are a helpful assistant. Be concise."); | ||
|
|
||
| // Get stub for the running workflow | ||
| ChatWorkflow chat = workflowClient.newWorkflowStub(ChatWorkflow.class, workflowId); | ||
|
|
||
| // Interactive loop | ||
| try (Scanner scanner = new Scanner(System.in, java.nio.charset.StandardCharsets.UTF_8)) { | ||
| while (true) { | ||
| System.out.print("You: "); | ||
| String input = scanner.nextLine().trim(); | ||
|
|
||
| if (input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("exit")) { | ||
| chat.end(); | ||
| break; | ||
| } | ||
|
|
||
| if (input.isEmpty()) { | ||
| continue; | ||
| } | ||
|
|
||
| try { | ||
| String response = chat.chat(input); | ||
| System.out.println("Assistant: " + response + "\n"); | ||
| } catch (Exception e) { | ||
| System.err.println("Error: " + e.getMessage() + "\n"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| System.out.println("Goodbye!"); | ||
| System.exit(0); | ||
| } | ||
| } |
38 changes: 38 additions & 0 deletions
38
springai/basic/src/main/java/io/temporal/samples/springai/chat/ChatWorkflow.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package io.temporal.samples.springai.chat; | ||
|
|
||
| import io.temporal.workflow.SignalMethod; | ||
| import io.temporal.workflow.UpdateMethod; | ||
| import io.temporal.workflow.WorkflowInterface; | ||
| import io.temporal.workflow.WorkflowMethod; | ||
|
|
||
| /** | ||
| * A chat workflow that maintains a conversation with an AI model. | ||
| * | ||
| * <p>The workflow runs until explicitly ended via the {@link #end()} signal. Messages can be sent | ||
| * via the {@link #chat(String)} update method, which returns the AI's response synchronously. | ||
| */ | ||
| @WorkflowInterface | ||
| public interface ChatWorkflow { | ||
|
|
||
| /** | ||
| * Starts the chat workflow and waits until ended. | ||
| * | ||
| * @param systemPrompt the system prompt that defines the AI's behavior | ||
| * @return a summary when the chat ends | ||
| */ | ||
| @WorkflowMethod | ||
| String run(String systemPrompt); | ||
|
|
||
| /** | ||
| * Sends a message to the AI and returns its response. | ||
| * | ||
| * @param message the user's message | ||
| * @return the AI's response | ||
| */ | ||
| @UpdateMethod | ||
| String chat(String message); | ||
|
|
||
| /** Ends the chat session. */ | ||
| @SignalMethod | ||
| void end(); | ||
| } |
111 changes: 111 additions & 0 deletions
111
springai/basic/src/main/java/io/temporal/samples/springai/chat/ChatWorkflowImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package io.temporal.samples.springai.chat; | ||
|
|
||
| import io.temporal.activity.ActivityOptions; | ||
| import io.temporal.common.RetryOptions; | ||
| import io.temporal.springai.chat.TemporalChatClient; | ||
| import io.temporal.springai.model.ActivityChatModel; | ||
| import io.temporal.workflow.Workflow; | ||
| import io.temporal.workflow.WorkflowInit; | ||
| import java.time.Duration; | ||
| import org.springframework.ai.chat.client.ChatClient; | ||
| import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; | ||
| import org.springframework.ai.chat.memory.ChatMemory; | ||
| import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; | ||
| import org.springframework.ai.chat.memory.MessageWindowChatMemory; | ||
|
|
||
| /** | ||
| * Implementation of the chat workflow using Spring AI's ChatClient with Temporal tools. | ||
| * | ||
| * <p>This demonstrates how to use the Spring AI plugin within a Temporal workflow: | ||
| * | ||
| * <ol> | ||
| * <li>Build an {@link ActivityChatModel} via its factory to get a standard Spring AI ChatModel | ||
| * backed by a durable Temporal activity | ||
| * <li>Create activity stubs for tools (e.g., {@link WeatherActivity}) | ||
| * <li>Create deterministic tools (e.g., {@link StringTools}) | ||
| * <li>Create side-effect tools (e.g., {@link TimestampTools}) | ||
| * <li>Use {@link TemporalChatClient} to build a tool-aware chat client | ||
| * </ol> | ||
| * | ||
| * <p>The AI model can call: | ||
| * | ||
| * <ul> | ||
| * <li>{@code getWeather(city)} - Executes as a durable Temporal activity | ||
| * <li>{@code getForecast(city, days)} - Executes as a durable Temporal activity | ||
| * <li>{@code reverse(text)}, {@code countWords(text)}, etc. - Execute directly in workflow (plain | ||
| * workflow tool) | ||
| * <li>{@code getCurrentDateTime()}, {@code generateUuid()}, etc. - Wrapped in sideEffect | ||
| * (@SideEffectTool) | ||
| * </ul> | ||
| */ | ||
| public class ChatWorkflowImpl implements ChatWorkflow { | ||
|
|
||
| private final ChatClient chatClient; | ||
| private boolean ended = false; | ||
| private int messageCount = 0; | ||
|
|
||
| // @@@SNIPSTART samples-java-spring-ai-chat-workflow-init | ||
| @WorkflowInit | ||
| public ChatWorkflowImpl(String systemPrompt) { | ||
| // Build an activity-backed chat model. The factory creates the activity stub | ||
| // internally and registers per-call Summaries on the Temporal UI. | ||
| ActivityChatModel activityChatModel = ActivityChatModel.forDefault(); | ||
|
|
||
| // Create an activity stub for weather tools - these execute as durable activities | ||
| WeatherActivity weatherTool = | ||
| Workflow.newActivityStub( | ||
| WeatherActivity.class, | ||
| ActivityOptions.newBuilder() | ||
| .setStartToCloseTimeout(Duration.ofSeconds(30)) | ||
| .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(3).build()) | ||
| .build()); | ||
|
|
||
| // Create deterministic tools - these execute directly in the workflow | ||
| StringTools stringTools = new StringTools(); | ||
|
|
||
| // Create side-effect tools - these are wrapped in Workflow.sideEffect() | ||
| // The result is recorded in history, making replay deterministic | ||
| TimestampTools timestampTools = new TimestampTools(); | ||
|
|
||
| // Create chat memory - uses in-memory storage that gets rebuilt on replay | ||
| ChatMemory chatMemory = | ||
| MessageWindowChatMemory.builder() | ||
| .chatMemoryRepository(new InMemoryChatMemoryRepository()) | ||
| .maxMessages(20) | ||
| .build(); | ||
|
|
||
| // Build a TemporalChatClient with tools and memory | ||
| // - Activity stubs (weatherTool) become durable AI tools | ||
| // - plain workflow tool classes (stringTools) execute directly in workflow | ||
| // - @SideEffectTool classes (timestampTools) are wrapped in sideEffect() | ||
| // - PromptChatMemoryAdvisor maintains conversation history | ||
| this.chatClient = | ||
| TemporalChatClient.builder(activityChatModel) | ||
| .defaultSystem(systemPrompt) | ||
| .defaultTools(weatherTool, stringTools, timestampTools) | ||
| .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) | ||
| .build(); | ||
| } | ||
|
|
||
| // @@@SNIPEND | ||
|
|
||
| @Override | ||
| public String run(String systemPrompt) { | ||
| // systemPrompt is unused here on purpose — @WorkflowInit requires the constructor | ||
| // and the @WorkflowMethod to share a parameter list, and the constructor above | ||
| // already consumed it to build the chat client. | ||
| Workflow.await(() -> ended); | ||
| return "Chat ended after " + messageCount + " messages."; | ||
| } | ||
|
donald-pinckney marked this conversation as resolved.
|
||
|
|
||
| @Override | ||
| public String chat(String message) { | ||
| messageCount++; | ||
| return chatClient.prompt().user(message).call().content(); | ||
| } | ||
|
|
||
| @Override | ||
| public void end() { | ||
| ended = true; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.