-
Notifications
You must be signed in to change notification settings - Fork 205
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
base: master
Are you sure you want to change the base?
Changes from all commits
4f4df9e
31bc77e
079089a
b62adfa
e538674
c98af78
54a5d40
58804ad
f4b1028
e509673
0cc143e
b09d2ff
8ba4eb0
4b7aa19
969aabd
615ff92
f6d781c
336cc7b
6d4d166
32e6f99
d9b5002
bec50f8
9ebc25b
6c3a107
4e6003d
5d19df9
52c6d4b
01fec57
1b5c56e
bc6275f
60f2941
528e538
d58700f
4e600da
e19463b
2c97806
70c8594
b11249a
42f46d6
7cb1dbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| { | ||
| "project": "temporal-spring-ai", | ||
| "tasks": [ | ||
| { | ||
| "id": "T13", | ||
| "title": "Remove includeBuild from samples-java", | ||
| "description": "Once temporal-spring-ai is published to Maven Central, remove the includeBuild('../sdk-java') block from samples-java/settings.gradle and the grpc-util workaround from core/build.gradle.", | ||
| "severity": "low", | ||
| "category": "cleanup", | ||
| "depends_on": [], | ||
| "status": "blocked", | ||
| "notes": "Blocked on SDK release. Not actionable yet." | ||
| }, | ||
| { | ||
| "id": "T15", | ||
| "title": "Change default tool execution to run in workflow context", | ||
| "description": "Currently unannotated tools passed to defaultTools() are rejected. Change so they execute directly in workflow context by default \u2014 user is responsible for determinism. Remove @DeterministicTool annotation (no longer needed since direct execution is the default). Remove SandboxingAdvisor, LocalActivityToolCallbackWrapper, ExecuteToolLocalActivity, and ExecuteToolLocalActivityImpl. Remove ExecuteToolLocalActivityImpl registration from SpringAiPlugin. Keep @SideEffectTool as a convenience for wrapping in Workflow.sideEffect(). Keep activity stub / nexus stub auto-detection as shortcuts.", | ||
| "severity": "high", | ||
| "category": "refactor", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Blocked on PR review discussion with tconley1428. Agreed on direction but need to finalize details before implementing. Proposed design: https://github.com/temporalio/sdk-java/pull/2829#discussion_r3060711651" | ||
| }, | ||
| { | ||
| "id": "T22", | ||
| "title": "Discuss: temporal-spring-ai-starter artifact for easier onboarding", | ||
| "description": "Create a temporal-spring-ai-starter artifact (POM-only, no code) that transitively pulls in temporal-spring-ai, temporal-sdk, and temporal-spring-boot-starter. Prevents version mismatches and ClassNotFoundException for users starting from scratch.", | ||
| "severity": "medium", | ||
| "category": "discussion", | ||
| "depends_on": [], | ||
| "status": "blocked", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053808755. Do after merging the main PR \u2014 not urgent for initial landing." | ||
| }, | ||
| { | ||
| "id": "T23", | ||
| "title": "Discuss: ActivityMcpClient capability caching and replay", | ||
| "description": "ActivityMcpClient caches getServerCapabilities() after first call. DABH asks if stale cache is a replay concern. Probably fine (activity result is in history, so replay uses the original value \u2014 which is correct). But worth confirming the design intent.", | ||
| "severity": "low", | ||
| "category": "discussion", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Replied to DABH: cache prevents non-determinism (live vs replay would diverge without it). Standard MCP practice." | ||
| }, | ||
| { | ||
| "id": "T24", | ||
| "title": "Discuss: Change ChatModelTypes.rawContent from Object to String", | ||
| "description": "Change ChatModelTypes.Message rawContent from Object to String. Spring AI's Content.getText() returns String. We always cast to String on both sides anyway. Object type gives false flexibility that would ClassCastException at runtime.", | ||
| "severity": "low", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054049714. Verified Spring AI Message interface uses String, not Object." | ||
| }, | ||
| { | ||
| "id": "T25", | ||
| "title": "Reply: compatibility matrix in docs", | ||
| "description": "DABH suggests documenting the compatibility matrix (Java version, Spring Boot version, Spring AI version). Acknowledge and defer to a docs PR.", | ||
| "severity": "low", | ||
| "category": "reply", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Replied to DABH acknowledging. Follow-up task T29 created." | ||
| }, | ||
| { | ||
| "id": "T26", | ||
| "title": "Reply: SandboxingAdvisor lacks tests", | ||
| "description": "DABH notes SandboxingAdvisor has no tests. Likely moot if T15 removes it. Reply explaining that.", | ||
| "severity": "low", | ||
| "category": "reply", | ||
| "depends_on": [ | ||
| "T15" | ||
| ], | ||
| "status": "superseded", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053836427 Moot once T15 removes these classes." | ||
| }, | ||
| { | ||
| "id": "T27", | ||
| "title": "Reply: ToolContext silently dropped in LocalActivityToolCallbackWrapper", | ||
| "description": "DABH notes ToolContext is silently ignored. Likely moot if T15 removes LocalActivityToolCallbackWrapper. Reply explaining that.", | ||
| "severity": "low", | ||
| "category": "reply", | ||
| "depends_on": [ | ||
| "T15" | ||
| ], | ||
| "status": "superseded", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054036773 Moot once T15 removes these classes." | ||
| }, | ||
| { | ||
| "id": "T28", | ||
| "title": "Restore VectorStorePlugin and EmbeddingModelPlugin as public classes", | ||
| "description": "Restore the plugin subclasses so users not using auto-config can create them manually (e.g. new VectorStorePlugin(vectorStore)). Auto-config uses them too. Reverts the builder-inline approach from the earlier refactor.", | ||
| "severity": "medium", | ||
| "category": "refactor", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "tconley1428 review comments on SpringAiPlugin.java and SpringAiVectorStoreAutoConfiguration.java" | ||
| }, | ||
| { | ||
| "id": "T29", | ||
| "title": "Add README with compatibility matrix to temporal-spring-ai module", | ||
| "description": "Document supported versions: Java 17+, Spring Boot 3.x+, Spring AI 1.1.0, Temporal SDK 1.33.0+.", | ||
| "severity": "high", | ||
| "category": "docs", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Do in this PR, not a follow-up." | ||
| }, | ||
| { | ||
| "id": "T30", | ||
| "title": "Fix CI: Edge build fails with Java version mismatch", | ||
| "description": "Edge CI sets edgeDepsTest which compiles temporal-sdk at Java 21. Our module hardcodes Java 17, causing Gradle to reject the dependency. Fix: use 21 when edgeDepsTest is set, 17 otherwise.", | ||
| "severity": "high", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Edge CI log showed: Dependency resolution is looking for a library compatible with JVM runtime version 17, but temporal-sdk is only compatible with JVM runtime version 21 or newer." | ||
| }, | ||
| { | ||
| "id": "T31", | ||
| "title": "Fix CI: Docker build fails with Java 11 (release 17 not supported)", | ||
| "description": "Docker CI runs Java 11 which cannot compile --release 17. Conditionally exclude temporal-spring-ai from settings.gradle and BOM when JDK < 17.", | ||
| "severity": "high", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "completed", | ||
| "notes": "Docker CI log showed: error: release version 17 not supported. JAVA_HOME was Java 11." | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # 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(); | ||
| this.chatClient = TemporalChatClient.builder(chatModel) | ||
| .defaultSystem("You are a helpful assistant.") | ||
| .defaultTools(myActivityStub) | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public String run(String goal) { | ||
| return chatClient.prompt().user(goal).call().content(); | ||
| } | ||
| ``` | ||
|
|
||
| ## Optional Integrations | ||
|
|
||
| These are 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` | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| description = '''Temporal Java SDK Spring AI Plugin''' | ||
|
|
||
| ext { | ||
| springAiVersion = '1.1.0' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably clarify in the docs/readme the compatibility matrix for the module? (So - maybe not in this PR - but at least in the forthcoming docs PR)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call — will add a compatibility matrix to the module README. Tracking as a follow-up task.
|
||
| // 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 (avoids Gradle JVM compatibility rejection). | ||
| ext { | ||
| springAiJavaVersion = project.hasProperty("edgeDepsTest") ? JavaVersion.VERSION_21 : JavaVersion.VERSION_17 | ||
| springAiRelease = project.hasProperty("edgeDepsTest") ? '21' : '17' | ||
| } | ||
|
|
||
| java { | ||
| sourceCompatibility = springAiJavaVersion | ||
| targetCompatibility = springAiJavaVersion | ||
| } | ||
|
|
||
| compileJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.compilerArgs.addAll(['--release', springAiRelease]) | ||
| } | ||
|
|
||
| compileTestJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.compilerArgs.addAll(['--release', springAiRelease]) | ||
| } | ||
|
|
||
| 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.
Show resolved
Hide 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.
Show resolved
Hide 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() | ||
| } | ||
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obviously will delete this prior to merging ;)