-
Notifications
You must be signed in to change notification settings - Fork 209
Add temporal-spring-ai module #2829
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
50 commits
Select commit
Hold shift + click to select a range
4f4df9e
Add temporal-spring-ai module for Spring AI integration
donald-pinckney 31bc77e
Document callback registry lifecycle risk and add stream() override
donald-pinckney 079089a
Add tests for temporal-spring-ai (T1-T4)
donald-pinckney b62adfa
Update TASK_QUEUE.json: T1-T4, T9, T11 completed
donald-pinckney e538674
Add T14 (NPE bug) to TASK_QUEUE.json
donald-pinckney c98af78
Fix UUID non-determinism, null metadata NPE, and unbounded tool loop
donald-pinckney 54a5d40
Split SpringAiPlugin into conditional auto-configuration (T6)
donald-pinckney 58804ad
Update TASK_QUEUE.json: T5, T6, T7, T10, T14 completed
donald-pinckney f4b1028
Update TASK_QUEUE.json: T12 completed
donald-pinckney e509673
Replace fragile string matching with instanceof in TemporalStubUtil (T8)
donald-pinckney 0cc143e
Update TASK_QUEUE.json: T8 completed
donald-pinckney b09d2ff
Use WorkflowReplayer for proper replay determinism tests
donald-pinckney 8ba4eb0
Simplify stream() exception message
donald-pinckney 4b7aa19
Revert tool call iteration limit, match Spring AI's recursive pattern
donald-pinckney 969aabd
Fix javadoc reference for publishToMavenLocal
donald-pinckney 615ff92
Use SimplePlugin builder for VectorStore and EmbeddingModel plugins
donald-pinckney f6d781c
Clean up TASK_QUEUE.json: remove completed tasks, add T15
donald-pinckney 336cc7b
Add link to proposed design for T15
donald-pinckney 6d4d166
Triage Copilot and DABH review comments into TASK_QUEUE
donald-pinckney 32e6f99
T16: Guard assistantMessage.getMedia() against null
donald-pinckney d9b5002
T17: Include Nexus stubs in unrecognized tool type error message
donald-pinckney bec50f8
T18: Use ObjectProvider to fix NoUniqueBeanDefinitionException
donald-pinckney 9ebc25b
T19: Make replay test exercise tool calls
donald-pinckney 6c3a107
T20: Handle duplicate MCP client names with clear error
donald-pinckney 4e6003d
T21: Use float[] instead of List<Double> in EmbeddingModelTypes
donald-pinckney 5d19df9
T18 fix: Use getIfUnique() instead of getIfAvailable()
donald-pinckney 52c6d4b
Add T28: Restore plugin classes as public API per tconley review
donald-pinckney 01fec57
Clean up TASK_QUEUE: remove completed, mark superseded
donald-pinckney 1b5c56e
T28: Restore VectorStorePlugin and EmbeddingModelPlugin as public cla…
donald-pinckney bc6275f
T23: Resolved — MCP capability caching is correct
donald-pinckney 60f2941
T24: Change from discussion to fix — rawContent should be String
donald-pinckney 528e538
T22: Defer starter artifact to after PR merge
donald-pinckney d58700f
T25: Replied to DABH about docs. Added T29 for README follow-up.
donald-pinckney 4e600da
T29: Bump to high priority, do in this PR
donald-pinckney e19463b
T15: Also remove LocalActivityToolCallbackWrapper and ExecuteToolLoca…
donald-pinckney 2c97806
T24: Change ChatModelTypes.Message rawContent from Object to String
donald-pinckney 70c8594
T29: Add README with compatibility matrix and quick start
donald-pinckney b11249a
T30: Fix edge CI Java version mismatch
donald-pinckney 42f46d6
T31: Skip temporal-spring-ai on JDK < 17
donald-pinckney 7cb1dbc
T15: Plain tools execute in workflow context by default
donald-pinckney f0d0f9a
Clean up TASK_QUEUE: remove completed and superseded
donald-pinckney 7e7e5ad
Update README with tool execution model
donald-pinckney 8e505f7
README: mention programmatic plugin setup for optional integrations
donald-pinckney bc7f5b1
Merge branch 'master' into d/20260406-164203
donald-pinckney 024126d
Merge branch 'master' into d/20260406-164203
donald-pinckney bbcb85e
Remove TASK_QUEUE.json
donald-pinckney 88910d6
Publish temporal-spring-ai from the JDK 11 release job via toolchains
donald-pinckney 0fbfec4
Merge remote-tracking branch 'origin/master' into d/20260406-164203
donald-pinckney b6de0a7
Align markdown tables
donald-pinckney 48780de
Skip temporal-spring-ai tests when testJavaVersion < 17
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
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,111 @@ | ||
| # temporal-spring-ai | ||
|
|
||
| Integrates [Spring AI](https://docs.spring.io/spring-ai/reference/) with [Temporal](https://temporal.io/) workflows, making AI model calls, tool execution, vector store operations, embeddings, and MCP tool calls durable Temporal primitives. | ||
|
|
||
| ## Compatibility | ||
|
|
||
| | Dependency | Minimum Version | | ||
| |-------------------|-----------------| | ||
| | Java | 17 | | ||
| | Spring Boot | 3.x | | ||
| | Spring AI | 1.1.0 | | ||
| | Temporal Java SDK | 1.33.0 | | ||
|
|
||
| ## Quick Start | ||
|
|
||
| Add the dependency (Maven): | ||
|
|
||
| ```xml | ||
| <dependency> | ||
| <groupId>io.temporal</groupId> | ||
| <artifactId>temporal-spring-ai</artifactId> | ||
| <version>${temporal-sdk.version}</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| You also need `temporal-spring-boot-starter` and a Spring AI model starter (e.g. `spring-ai-starter-model-openai`). | ||
|
|
||
| The plugin auto-registers `ChatModelActivity` with all Temporal workers. In your workflow: | ||
|
|
||
| ```java | ||
| @WorkflowInit | ||
| public MyWorkflowImpl(String goal) { | ||
| ActivityChatModel chatModel = ActivityChatModel.forDefault(); | ||
|
|
||
| WeatherActivity weather = Workflow.newActivityStub(WeatherActivity.class, opts); | ||
|
|
||
| this.chatClient = TemporalChatClient.builder(chatModel) | ||
| .defaultSystem("You are a helpful assistant.") | ||
| .defaultTools(weather, new MyTools()) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public String run(String goal) { | ||
| return chatClient.prompt().user(goal).call().content(); | ||
| } | ||
| ``` | ||
|
|
||
| ## Tool Types | ||
|
|
||
| Tools passed to `defaultTools()` are handled based on their type: | ||
|
|
||
| ### Activity stubs | ||
|
|
||
| Interfaces annotated with both `@ActivityInterface` and `@Tool` methods. Auto-detected and executed as durable Temporal activities with retries and timeouts. | ||
|
|
||
| ```java | ||
| @ActivityInterface | ||
| public interface WeatherActivity { | ||
| @Tool(description = "Get weather for a city") @ActivityMethod | ||
| String getWeather(String city); | ||
| } | ||
| ``` | ||
|
|
||
| ### `@SideEffectTool` | ||
|
|
||
| Classes annotated with `@SideEffectTool`. Each `@Tool` method is wrapped in `Workflow.sideEffect()` — the result is recorded in history on first execution and replayed from history on subsequent replays. Use for cheap non-deterministic operations (timestamps, UUIDs). | ||
|
|
||
| ```java | ||
| @SideEffectTool | ||
| public class TimestampTools { | ||
| @Tool(description = "Get current time") | ||
| public String now() { return Instant.now().toString(); } | ||
| } | ||
| ``` | ||
|
|
||
| ### Plain tools | ||
|
|
||
| Any class with `@Tool` methods that isn't a stub or `@SideEffectTool`. Executes directly in the workflow thread. The user is responsible for determinism — call activities, `Workflow.sideEffect()`, child workflows, etc. as needed. | ||
|
|
||
| ```java | ||
| public class MyTools { | ||
| @Tool(description = "Process data") | ||
| public String process(String input) { | ||
| SomeActivity act = Workflow.newActivityStub(SomeActivity.class, opts); | ||
| return act.doWork(input); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Nexus service stubs | ||
|
|
||
| Auto-detected and executed as Nexus operations, similar to activity stubs. | ||
|
|
||
| ## Optional Integrations | ||
|
|
||
| Auto-configured when their dependencies are on the classpath: | ||
|
|
||
| | Feature | Dependency | What it registers | | ||
| |--------------|-----------------|--------------------------| | ||
| | Vector Store | `spring-ai-rag` | `VectorStoreActivity` | | ||
| | Embeddings | `spring-ai-rag` | `EmbeddingModelActivity` | | ||
| | MCP | `spring-ai-mcp` | `McpClientActivity` | | ||
|
|
||
| These can also be set up programmatically without auto-configuration: | ||
|
|
||
| ```java | ||
| new VectorStorePlugin(vectorStore) | ||
| new EmbeddingModelPlugin(embeddingModel) | ||
| new McpPlugin() | ||
| ``` |
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,63 @@ | ||
| description = '''Temporal Java SDK Spring AI Plugin''' | ||
|
|
||
| ext { | ||
| springAiVersion = '1.1.0' | ||
| // Spring AI requires Spring Boot 3.x / Java 17+ | ||
| springBootVersionForSpringAi = "$springBoot3Version" | ||
| } | ||
|
|
||
| // Spring AI requires Java 17+, override the default Java 8 target from java.gradle. | ||
| // When edgeDepsTest is set, use 21 to match other modules. | ||
| ext { | ||
| springAiReleaseInt = project.hasProperty("edgeDepsTest") ? 21 : 17 | ||
| } | ||
|
|
||
| compileJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.release = springAiReleaseInt | ||
| } | ||
|
|
||
| compileTestJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.release = springAiReleaseInt | ||
| } | ||
|
|
||
| dependencies { | ||
| api(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersionForSpringAi")) | ||
| api(platform("org.springframework.ai:spring-ai-bom:$springAiVersion")) | ||
|
|
||
| // this module shouldn't carry temporal-sdk with it, especially for situations when users may be using a shaded artifact | ||
| compileOnly project(':temporal-sdk') | ||
|
donald-pinckney marked this conversation as resolved.
|
||
| compileOnly project(':temporal-spring-boot-autoconfigure') | ||
|
|
||
| api 'org.springframework.boot:spring-boot-autoconfigure' | ||
| api 'org.springframework.ai:spring-ai-client-chat' | ||
|
brianstrauch marked this conversation as resolved.
|
||
|
|
||
| implementation 'org.springframework.boot:spring-boot-starter' | ||
|
|
||
| // Optional: Vector store support | ||
| compileOnly 'org.springframework.ai:spring-ai-rag' | ||
|
|
||
| // Optional: MCP (Model Context Protocol) support | ||
| compileOnly 'org.springframework.ai:spring-ai-mcp' | ||
|
|
||
| testImplementation project(':temporal-sdk') | ||
| testImplementation project(':temporal-testing') | ||
| testImplementation "org.mockito:mockito-core:${mockitoVersion}" | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testImplementation 'org.springframework.ai:spring-ai-rag' | ||
|
|
||
| testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}" | ||
| testRuntimeOnly "org.junit.platform:junit-platform-launcher" | ||
| } | ||
|
|
||
| tasks.test { | ||
| useJUnitPlatform() | ||
| // This module's bytecode targets Java 17+, so it can't run on older JVMs. | ||
| // The unit_test_jdk8 CI job sets -PtestJavaVersion=11 to verify Java 8 bytecode | ||
| // works on JDK 11 — skip our tests there since the classes won't even load. | ||
| if (project.hasProperty("testJavaVersion") | ||
| && (project.property("testJavaVersion") as int) < springAiReleaseInt) { | ||
| enabled = false | ||
| } | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
temporal-spring-ai/src/main/java/io/temporal/springai/activity/ChatModelActivity.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,25 @@ | ||
| package io.temporal.springai.activity; | ||
|
|
||
| import io.temporal.activity.ActivityInterface; | ||
| import io.temporal.activity.ActivityMethod; | ||
| import io.temporal.springai.model.ChatModelTypes; | ||
|
|
||
| /** | ||
| * Temporal activity interface for calling Spring AI chat models. | ||
| * | ||
| * <p>This activity wraps a Spring AI {@link org.springframework.ai.chat.model.ChatModel} and makes | ||
| * it callable from within Temporal workflows. The activity handles serialization of prompts and | ||
| * responses, enabling durable AI conversations with automatic retries and timeout handling. | ||
| */ | ||
| @ActivityInterface | ||
| public interface ChatModelActivity { | ||
|
|
||
| /** | ||
| * Calls the chat model with the given input. | ||
| * | ||
| * @param input the chat model input containing messages, options, and tool definitions | ||
| * @return the chat model output containing generated responses and metadata | ||
| */ | ||
| @ActivityMethod | ||
| ChatModelTypes.ChatModelActivityOutput callChatModel(ChatModelTypes.ChatModelActivityInput input); | ||
| } |
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.