Conversation
- Remove runtimeOnly spring-ai-rag and spring-ai-mcp from shared config (no longer needed after T6 plugin split in sdk-java) - Fix workflow class package references (old prototype packages) - Add web-application-type: none to RAG and multimodel configs - Exclude conflicting chat auto-configs in multimodel sample All 5 samples now boot successfully against a Temporal dev server. MCP sample requires Node.js/npx for the MCP server. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds multiple new Spring AI sample modules demonstrating temporal-spring-ai integrations (basic chat tools, MCP tools, multi-provider, RAG/vector store, and sandboxing), plus shared Gradle configuration and a local composite build substitution for sdk-java.
Changes:
- Introduces 5 new Spring Boot sample modules (
springai*) with workflows/apps showing differenttemporal-spring-aiusage patterns. - Adds shared Gradle config (
gradle/springai.gradle) and wires new modules intosettings.gradle. - Updates the SSL sample to use
AdvancedTlsX509KeyManagerand adds a gRPC dependency incore.
Reviewed changes
Copilot reviewed 38 out of 67 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| springai/src/main/resources/application.yml | Spring AI sample runtime config (Temporal worker + OpenAI). |
| springai/src/main/java/io/temporal/samples/springai/chat/WeatherActivityImpl.java | Mock weather activity/tool implementation. |
| springai/src/main/java/io/temporal/samples/springai/chat/WeatherActivity.java | Activity + Spring AI tool annotations for weather tools. |
| springai/src/main/java/io/temporal/samples/springai/chat/TimestampTools.java | Side-effect tools for nondeterministic values in workflows. |
| springai/src/main/java/io/temporal/samples/springai/chat/StringTools.java | Deterministic tools for workflow-safe string utilities. |
| springai/src/main/java/io/temporal/samples/springai/chat/ChatWorkflowImpl.java | Chat workflow using TemporalChatClient + tools + memory. |
| springai/src/main/java/io/temporal/samples/springai/chat/ChatWorkflow.java | Workflow interface for interactive chat (update + signal). |
| springai/src/main/java/io/temporal/samples/springai/chat/ChatExampleApplication.java | CLI-style Spring Boot app to drive the chat workflow. |
| springai/build/resources/main/application.yml | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/WeatherActivityImpl.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/WeatherActivity.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/TimestampTools.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/StringTools.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/ChatWorkflowImpl.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/ChatWorkflow.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/ChatRunner.class | Generated build output (should not be committed). |
| springai/build/classes/java/main/io/temporal/samples/springai/chat/ChatExampleApplication.class | Generated build output (should not be committed). |
| springai/build/bootRunMainClassName | Generated build output (should not be committed). |
| springai/build.gradle | Spring AI sample module Gradle dependencies. |
| springai-sandboxing/src/main/resources/application.yml | Sandboxing sample runtime config. |
| springai-sandboxing/src/main/java/io/temporal/samples/springai/sandboxing/UnsafeTools.java | Demonstrates unsafe (unannotated) tools for sandboxing. |
| springai-sandboxing/src/main/java/io/temporal/samples/springai/sandboxing/SandboxingWorkflowImpl.java | Workflow demonstrating SandboxingAdvisor behavior. |
| springai-sandboxing/src/main/java/io/temporal/samples/springai/sandboxing/SandboxingWorkflow.java | Workflow interface for sandboxing demo. |
| springai-sandboxing/src/main/java/io/temporal/samples/springai/sandboxing/SandboxingApplication.java | CLI-style app to drive sandboxing workflow. |
| springai-sandboxing/build/classes/java/main/io/temporal/samples/springai/sandboxing/UnsafeTools.class | Generated build output (should not be committed). |
| springai-sandboxing/build/classes/java/main/io/temporal/samples/springai/sandboxing/SandboxingWorkflowImpl.class | Generated build output (should not be committed). |
| springai-sandboxing/build/classes/java/main/io/temporal/samples/springai/sandboxing/SandboxingWorkflow.class | Generated build output (should not be committed). |
| springai-sandboxing/build/classes/java/main/io/temporal/samples/springai/sandboxing/SandboxingRunner.class | Generated build output (should not be committed). |
| springai-sandboxing/build/classes/java/main/io/temporal/samples/springai/sandboxing/SandboxingApplication.class | Generated build output (should not be committed). |
| springai-sandboxing/build.gradle | Sandboxing sample module Gradle dependencies. |
| springai-rag/src/main/resources/application.yaml | RAG sample runtime config (chat + embeddings). |
| springai-rag/src/main/java/io/temporal/samples/springai/rag/VectorStoreConfig.java | Spring config for in-memory vector store. |
| springai-rag/src/main/java/io/temporal/samples/springai/rag/RagWorkflowImpl.java | Workflow implementing vector-store-backed RAG flow. |
| springai-rag/src/main/java/io/temporal/samples/springai/rag/RagWorkflow.java | RAG workflow interface (signals + queries). |
| springai-rag/src/main/java/io/temporal/samples/springai/rag/RagApplication.java | CLI-style app to drive RAG workflow. |
| springai-rag/build/classes/java/main/io/temporal/samples/springai/rag/VectorStoreConfig.class | Generated build output (should not be committed). |
| springai-rag/build/classes/java/main/io/temporal/samples/springai/rag/RagWorkflowImpl.class | Generated build output (should not be committed). |
| springai-rag/build/classes/java/main/io/temporal/samples/springai/rag/RagWorkflow.class | Generated build output (should not be committed). |
| springai-rag/build/classes/java/main/io/temporal/samples/springai/rag/RagApplication.class | Generated build output (should not be committed). |
| springai-rag/build.gradle | RAG sample module Gradle dependencies. |
| springai-multimodel/src/main/resources/application.yaml | Multi-provider sample runtime config (OpenAI + Anthropic). |
| springai-multimodel/src/main/java/io/temporal/samples/springai/multimodel/MultiModelWorkflowImpl.java | Workflow routing messages to different providers/models. |
| springai-multimodel/src/main/java/io/temporal/samples/springai/multimodel/MultiModelWorkflow.java | Interface for multi-model chat workflow. |
| springai-multimodel/src/main/java/io/temporal/samples/springai/multimodel/MultiModelApplication.java | CLI-style app to drive multi-model workflow. |
| springai-multimodel/src/main/java/io/temporal/samples/springai/multimodel/ChatModelConfig.java | Spring beans configuring OpenAI + Anthropic ChatModel beans. |
| springai-multimodel/build/classes/java/main/io/temporal/samples/springai/multimodel/MultiModelWorkflowImpl.class | Generated build output (should not be committed). |
| springai-multimodel/build/classes/java/main/io/temporal/samples/springai/multimodel/MultiModelWorkflow.class | Generated build output (should not be committed). |
| springai-multimodel/build/classes/java/main/io/temporal/samples/springai/multimodel/MultiModelApplication.class | Generated build output (should not be committed). |
| springai-multimodel/build/classes/java/main/io/temporal/samples/springai/multimodel/ChatModelConfig.class | Generated build output (should not be committed). |
| springai-multimodel/build.gradle | Multi-model sample module Gradle dependencies. |
| springai-mcp/src/main/resources/application.yaml | MCP sample runtime config (OpenAI + MCP filesystem server). |
| springai-mcp/src/main/java/io/temporal/samples/springai/mcp/McpWorkflowImpl.java | Workflow discovering MCP tools and exposing them to chat. |
| springai-mcp/src/main/java/io/temporal/samples/springai/mcp/McpWorkflow.java | MCP workflow interface (signals + queries). |
| springai-mcp/src/main/java/io/temporal/samples/springai/mcp/McpApplication.java | CLI-style app to drive MCP workflow. |
| springai-mcp/build/classes/java/main/io/temporal/samples/springai/mcp/McpWorkflowImpl.class | Generated build output (should not be committed). |
| springai-mcp/build/classes/java/main/io/temporal/samples/springai/mcp/McpWorkflow.class | Generated build output (should not be committed). |
| springai-mcp/build/classes/java/main/io/temporal/samples/springai/mcp/McpApplication.class | Generated build output (should not be committed). |
| springai-mcp/build.gradle | MCP sample module Gradle dependencies. |
| settings.gradle | Adds new sample modules + composite build substitution for sdk-java. |
| gradle/springai.gradle | Shared Gradle config for all Spring AI sample modules. |
| core/src/main/java/io/temporal/samples/ssl/Starter.java | Updates SSL sample to use gRPC AdvancedTlsX509KeyManager refresh API. |
| core/build.gradle | Adds gRPC util dependency for SSL sample. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| spring: | ||
| application: | ||
| name: spring-ai-temporal-example | ||
| main: | ||
| web-application-type: none |
There was a problem hiding this comment.
Generated build output is being committed (springai/build/**). These files should not be tracked; remove the build directory from the PR and add ignore rules for the new sample modules’ build/ outputs (e.g., */build, or /springai/build) to prevent future commits.
| // so we substitute all SDK modules from the local build. | ||
| includeBuild('../sdk-java') { | ||
| dependencySubstitution { | ||
| substitute module('io.temporal:temporal-spring-ai') using project(':temporal-spring-ai') | ||
| substitute module('io.temporal:temporal-sdk') using project(':temporal-sdk') | ||
| substitute module('io.temporal:temporal-serviceclient') using project(':temporal-serviceclient') | ||
| substitute module('io.temporal:temporal-spring-boot-autoconfigure') using project(':temporal-spring-boot-autoconfigure') | ||
| substitute module('io.temporal:temporal-spring-boot-starter') using project(':temporal-spring-boot-starter') | ||
| substitute module('io.temporal:temporal-testing') using project(':temporal-testing') | ||
| substitute module('io.temporal:temporal-opentracing') using project(':temporal-opentracing') |
There was a problem hiding this comment.
includeBuild('../sdk-java') will fail for anyone who doesn’t have that sibling directory (including CI and most contributors). Consider gating this behind an existence check (e.g., if (file('../sdk-java').exists())) and/or a Gradle property so the build works out-of-the-box.
| // so we substitute all SDK modules from the local build. | |
| includeBuild('../sdk-java') { | |
| dependencySubstitution { | |
| substitute module('io.temporal:temporal-spring-ai') using project(':temporal-spring-ai') | |
| substitute module('io.temporal:temporal-sdk') using project(':temporal-sdk') | |
| substitute module('io.temporal:temporal-serviceclient') using project(':temporal-serviceclient') | |
| substitute module('io.temporal:temporal-spring-boot-autoconfigure') using project(':temporal-spring-boot-autoconfigure') | |
| substitute module('io.temporal:temporal-spring-boot-starter') using project(':temporal-spring-boot-starter') | |
| substitute module('io.temporal:temporal-testing') using project(':temporal-testing') | |
| substitute module('io.temporal:temporal-opentracing') using project(':temporal-opentracing') | |
| // so we substitute all SDK modules from the local build when the sibling checkout is available. | |
| if (file('../sdk-java').exists()) { | |
| includeBuild('../sdk-java') { | |
| dependencySubstitution { | |
| substitute module('io.temporal:temporal-spring-ai') using project(':temporal-spring-ai') | |
| substitute module('io.temporal:temporal-sdk') using project(':temporal-sdk') | |
| substitute module('io.temporal:temporal-serviceclient') using project(':temporal-serviceclient') | |
| substitute module('io.temporal:temporal-spring-boot-autoconfigure') using project(':temporal-spring-boot-autoconfigure') | |
| substitute module('io.temporal:temporal-spring-boot-starter') using project(':temporal-spring-boot-starter') | |
| substitute module('io.temporal:temporal-testing') using project(':temporal-testing') | |
| substitute module('io.temporal:temporal-opentracing') using project(':temporal-opentracing') | |
| } |
| 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" | ||
| } |
There was a problem hiding this comment.
This script pins a Spring Boot BOM version (springBootVersionForSpringAi) that can diverge from the Spring Boot Gradle plugin version used by the build (springBootPluginVersion in gradle.properties). That mismatch can cause dependency/plugin incompatibilities. Consider deriving the BOM version from the same property, or updating the build to consistently use a single Spring Boot version for all modules.
| implementation "io.temporal:temporal-envconfig:$javaSDKVersion" | ||
|
|
||
| // Needed for SSL sample (AdvancedTlsX509KeyManager) | ||
| implementation "io.grpc:grpc-util" |
There was a problem hiding this comment.
implementation "io.grpc:grpc-util" is declared without a version or any imported gRPC platform/constraints in this module. Gradle typically can’t resolve versionless dependencies unless a BOM/constraint supplies it. Add a version (ideally via a gRPC BOM/enforcedPlatform) or dependency constraint so builds are reproducible and don’t fail resolution.
| implementation "io.grpc:grpc-util" | |
| implementation "io.grpc:grpc-util:1.65.1" |
| @SuppressWarnings("InlineMeInliner") | ||
| var unused = | ||
| clientKeyManager.updateIdentityCredentialsFromFile( | ||
| clientKeyFile, | ||
| clientCertFile, | ||
| refreshPeriod, | ||
| TimeUnit.MINUTES, | ||
| Executors.newScheduledThreadPool(1)); |
There was a problem hiding this comment.
The return value of updateIdentityCredentialsFromFile(...) is assigned to unused but never read. With Error Prone + -Werror, this is likely to be flagged as an unused variable and fail compilation, and it also drops the handle you’d need to cancel the scheduled refresh. Prefer either (1) keep a meaningfully named variable and cancel it on shutdown, or (2) don’t assign and instead suppress the specific Error Prone warning you’re addressing.
| @SuppressWarnings("InlineMeInliner") | |
| var unused = | |
| clientKeyManager.updateIdentityCredentialsFromFile( | |
| clientKeyFile, | |
| clientCertFile, | |
| refreshPeriod, | |
| TimeUnit.MINUTES, | |
| Executors.newScheduledThreadPool(1)); | |
| java.util.concurrent.ScheduledExecutorService refreshExecutor = | |
| Executors.newScheduledThreadPool(1); | |
| var credentialRefreshHandle = | |
| clientKeyManager.updateIdentityCredentialsFromFile( | |
| clientKeyFile, | |
| clientCertFile, | |
| refreshPeriod, | |
| TimeUnit.MINUTES, | |
| refreshExecutor); | |
| Runtime.getRuntime() | |
| .addShutdownHook( | |
| new Thread( | |
| () -> { | |
| try { | |
| credentialRefreshHandle.close(); | |
| } catch (java.io.IOException e) { | |
| // Ignore shutdown cleanup errors in this sample. | |
| } | |
| refreshExecutor.shutdown(); | |
| })); |
| @Override | ||
| public String run(String systemPrompt) { | ||
| // Wait until the chat is ended | ||
| Workflow.await(() -> ended); | ||
| return "Chat ended after " + messageCount + " messages."; | ||
| } |
There was a problem hiding this comment.
run(String systemPrompt) doesn’t use its systemPrompt parameter (the prompt is only consumed in the @WorkflowInit constructor). To avoid confusion for callers, either remove the parameter (and pass it only via init), or build/configure the chat client from the run(...) argument and drop it from the constructor.
| /** | ||
| * Sends a message to a specific model. | ||
| * | ||
| * @param modelName the name of the model to use ("fast", "smart", or "default") | ||
| * @param message the user message | ||
| */ | ||
| @SignalMethod | ||
| void chat(String modelName, String message); | ||
|
|
There was a problem hiding this comment.
Javadoc says modelName is "fast", "smart", or "default", but the implementation uses "openai", "anthropic", and "default". Update the documentation to match the actual accepted values so users don’t send unsupported names.
| /** | ||
| * Implementation of the RAG workflow. | ||
| * | ||
| * <p>This demonstrates: | ||
| * | ||
| * <ul> | ||
| * <li>Using {@link EmbeddingModelActivity} to generate embeddings | ||
| * <li>Using {@link VectorStoreActivity} to store and search documents | ||
| * <li>Combining vector search with chat for RAG | ||
| * </ul> | ||
| * | ||
| * <p>All operations are durable Temporal activities - if the worker restarts, the workflow will | ||
| * continue from where it left off. | ||
| */ |
There was a problem hiding this comment.
The class-level Javadoc mentions using EmbeddingModelActivity, but this workflow only uses VectorStoreActivity (and no EmbeddingModelActivity appears in code). Please update the documentation to reflect what the sample actually does (or add the missing activity usage if that was intended).
|
|
||
| messageCount++; | ||
|
|
||
| // MessageChatMemoryAdvisor automatically handles conversation history |
There was a problem hiding this comment.
Comment references MessageChatMemoryAdvisor, but the code uses PromptChatMemoryAdvisor. Update the comment to the correct advisor name to avoid confusion.
| // MessageChatMemoryAdvisor automatically handles conversation history | |
| // PromptChatMemoryAdvisor automatically handles conversation history |
| @SuppressWarnings("InlineMeInliner") | ||
| var unused = |
| // Start a new workflow | ||
| String workflowId = "mcp-example-" + UUID.randomUUID().toString().substring(0, 8); | ||
| McpWorkflow workflow = | ||
| workflowClient.newWorkflowStub( |
There was a problem hiding this comment.
What's the significance of a workflow stub vs a regular workflow? Why do we always opt for a stub?
| .defaultSystem( | ||
| """ | ||
| You are a helpful assistant with access to file system tools. |
There was a problem hiding this comment.
Weird tabulation here, is it intentional?
| if (!initialized) { | ||
| lastResponse = "Workflow is still initializing. Please wait a moment."; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Another option could be to poll until it's ready?
| modelName = "default"; | ||
| message = input.substring(8).trim(); | ||
| } else { | ||
| // Default to openai if no prefix | ||
| modelName = "openai"; | ||
| message = input; |
There was a problem hiding this comment.
Why is there a model name called "default", but at the same time it looks like the default is hardcoded to "openai"?
| TemporalChatClient.builder(openAiModel) | ||
| .defaultSystem( | ||
| "You are a helpful assistant powered by OpenAI. " | ||
| + "Keep answers concise. You are GPT-4o-mini.") |
There was a problem hiding this comment.
Why do we need to tell the model its version?
| continue; | ||
| } | ||
|
|
||
| if (input.startsWith("ask ")) { |
There was a problem hiding this comment.
Input could just be "ask" (no trailing space) and we would still want to print the usage
Summary
temporal-spring-aiintegration:springai— Basic chat with activity tools, deterministic tools, and side-effect toolsspringai-mcp— MCP (Model Context Protocol) integration with filesystem serverspringai-multimodel— Multiple AI providers (OpenAI + Anthropic)springai-rag— Retrieval-Augmented Generation with vector storespringai-sandboxing— Unsafe tool sandboxing demogradle/springai.gradleincludeBuild('../sdk-java')for the unpublishedtemporal-spring-aiartifact (to be removed once published)Related
Running a sample
Test plan
springai— boots, registers ChatModelActivityspringai-rag— boots, registers ChatModelActivity + VectorStoreActivityspringai-sandboxing— boots, registers ChatModelActivityspringai-multimodel— boots, registers ChatModelActivity (2 models)springai-mcp— boots (requires Node.js/npx for MCP server)🤖 Generated with Claude Code