-
Notifications
You must be signed in to change notification settings - Fork 207
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
f0d0f9a
7e7e5ad
8e505f7
bc7f5b1
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,25 @@ | ||
| { | ||
| "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": "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." | ||
| } | ||
| ] | ||
| } |
| 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() | ||
| ``` |
| 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 ;)