-
Notifications
You must be signed in to change notification settings - Fork 203
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
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,145 @@ | ||
| { | ||
| "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 — user is responsible for determinism. Remove @DeterministicTool annotation (no longer needed since direct execution is the default). Remove SandboxingAdvisor and LocalActivityToolCallbackWrapper. 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": "blocked", | ||
| "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": "T16", | ||
| "title": "Fix NPE on assistantMessage.getMedia() in ActivityChatModel", | ||
| "description": "ASSISTANT branch in toActivityMessages() calls assistantMessage.getMedia().stream() without null guard. Other places in the module use CollectionUtils.isEmpty(). Add the guard.", | ||
| "severity": "medium", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789704" | ||
| }, | ||
| { | ||
| "id": "T17", | ||
| "title": "Fix error message in TemporalToolUtil to mention Nexus stubs", | ||
| "description": "The IllegalArgumentException for unknown tool types doesn't mention Nexus service stubs as supported, even though they are. Update the message.", | ||
| "severity": "low", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789727. May be partly superseded by T15 if the error message changes entirely." | ||
| }, | ||
| { | ||
| "id": "T18", | ||
| "title": "Fix NoUniqueBeanDefinitionException with multiple ChatModels", | ||
| "description": "SpringAiTemporalAutoConfiguration injects @Autowired(required=false) ChatModel primaryChatModel, which fails with NoUniqueBeanDefinitionException when multiple ChatModel beans exist without @Primary. Use ObjectProvider<ChatModel> instead.", | ||
| "severity": "high", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "Real bug — hit this in the multimodel sample, worked around with auto-config exclusions. Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789744" | ||
| }, | ||
| { | ||
| "id": "T19", | ||
| "title": "Make replay test actually exercise tool calls", | ||
| "description": "workflowWithTools_replaysDeterministically uses a StubChatModel that never requests tool calls, so tools are never invoked. Adjust the stub to return tool call requests first, then a final response, so the test actually validates tool execution determinism.", | ||
| "severity": "medium", | ||
| "category": "tests", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789763" | ||
| }, | ||
| { | ||
| "id": "T20", | ||
| "title": "Handle duplicate MCP client names in McpClientActivityImpl", | ||
| "description": "Collectors.toMap() in the constructor throws IllegalStateException on duplicate keys. Two MCP clients with the same name() would crash. Should validate or handle gracefully.", | ||
| "severity": "medium", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054031855" | ||
| }, | ||
| { | ||
| "id": "T21", | ||
| "title": "Use primitive arrays in EmbeddingModelTypes instead of List<Double>", | ||
| "description": "Converting float[] to List<Double> creates 1536+ boxed Double objects per embedding. Use float[] or double[] in EmbeddingModelTypes to avoid boxing overhead and reduce serialization size.", | ||
| "severity": "low", | ||
| "category": "improvement", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054058971" | ||
| }, | ||
| { | ||
| "id": "T22", | ||
| "title": "Discuss: temporal-spring-ai-starter artifact for easier onboarding", | ||
| "description": "temporal-sdk is compileOnly, so users must declare it separately. DABH asks if a starter that pulls in the right transitive deps would reduce friction. Need to decide if this is worth it or if documentation is sufficient.", | ||
| "severity": "medium", | ||
| "category": "discussion", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053808755. Discuss with Don." | ||
| }, | ||
| { | ||
| "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 — which is correct). But worth confirming the design intent.", | ||
| "severity": "low", | ||
| "category": "discussion", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054027377. Discuss with Don." | ||
| }, | ||
| { | ||
| "id": "T24", | ||
| "title": "Discuss: Change ChatModelTypes.rawContent from Object to String", | ||
| "description": "rawContent is Object but always cast to String. DABH suggests making it String. Cleaner but changes the JSON schema. If we ever need non-string content (multimodal), we'd have to change it back.", | ||
| "severity": "low", | ||
| "category": "discussion", | ||
| "depends_on": [], | ||
| "status": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054049714. Discuss with Don." | ||
| }, | ||
| { | ||
| "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": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053818533" | ||
| }, | ||
| { | ||
| "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": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053836427" | ||
| }, | ||
| { | ||
| "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": "todo", | ||
| "notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054036773" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| 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) |
||
| // 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 | ||
| java { | ||
| sourceCompatibility = JavaVersion.VERSION_17 | ||
| targetCompatibility = JavaVersion.VERSION_17 | ||
| } | ||
|
|
||
| compileJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.compilerArgs.addAll(['--release', '17']) | ||
| } | ||
|
|
||
| compileTestJava { | ||
| options.compilerArgs.removeAll(['--release', '8']) | ||
| options.compilerArgs.addAll(['--release', '17']) | ||
| } | ||
|
|
||
| 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') | ||
|
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. Could this increase friction though if a Spring user is just trying to get started and doesn't realize they need to import the sdk as well? Is there a |
||
| 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 ;)