-
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
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,151 @@ | ||
| { | ||
| "project": "temporal-spring-ai", | ||
| "tasks": [ | ||
| { | ||
| "id": "T1", | ||
| "title": "Add unit tests for type conversion", | ||
| "description": "Test ChatModelTypes <-> Spring AI types round-trip in ActivityChatModel and ChatModelActivityImpl. Cover messages (all roles), tool calls, media content, model options, embeddings, vector store types.", | ||
| "severity": "high", | ||
| "category": "tests", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T2", | ||
| "title": "Add unit tests for tool detection and conversion", | ||
| "description": "Test TemporalToolUtil.convertTools() with activity stubs, local activity stubs, @DeterministicTool, @SideEffectTool, Nexus stubs, and rejection of unknown types. Test TemporalStubUtil detection methods.", | ||
| "severity": "high", | ||
| "category": "tests", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T3", | ||
| "title": "Add replay test for determinism", | ||
| "description": "Create a workflow that uses ActivityChatModel with tools, run it once to produce history, then replay from that history to verify determinism. Cover activity tools, @DeterministicTool, and @SideEffectTool.", | ||
| "severity": "high", | ||
| "category": "tests", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T4", | ||
| "title": "Add unit tests for plugin registration", | ||
| "description": "Test SpringAiPlugin.initializeWorker() registers correct activities based on available beans. Test single model, multi-model, with/without VectorStore, with/without EmbeddingModel.", | ||
| "severity": "medium", | ||
| "category": "tests", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T5", | ||
| "title": "Fix UUID.randomUUID() in workflow context", | ||
| "description": "Replace UUID.randomUUID() with Workflow.randomUUID() in LocalActivityToolCallbackWrapper.call(). One-line fix.", | ||
| "severity": "high", | ||
| "category": "bugfix", | ||
| "depends_on": [ | ||
| "T3" | ||
| ], | ||
| "status": "completed", | ||
| "notes": "Do after replay test exists so we can verify the fix." | ||
| }, | ||
| { | ||
| "id": "T6", | ||
| "title": "Split SpringAiPlugin for optional deps", | ||
| "description": "Refactor so VectorStore, EmbeddingModel, and MCP are handled by separate @ConditionalOnClass auto-configuration classes. Core SpringAiPlugin only references ChatModel. compileOnly scope stays correct.", | ||
| "severity": "high", | ||
| "category": "refactor", | ||
| "depends_on": [ | ||
| "T4" | ||
| ], | ||
| "status": "completed", | ||
| "notes": "Do after plugin registration tests exist so we can verify the refactor doesn't break registration. Also resolves T10 (unnecessary MCP reflection)." | ||
| }, | ||
| { | ||
| "id": "T14", | ||
| "title": "Fix NPE when ChatResponse metadata is null", | ||
| "description": "ActivityChatModel.toResponse() passes null metadata to ChatResponse.builder().metadata(null), which causes an NPE in Spring AI's builder. Fix: skip .metadata() call when metadata is null, or pass an empty ChatResponseMetadata.", | ||
| "severity": "high", | ||
| "category": "bugfix", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T7", | ||
| "title": "Add max iteration limit to ActivityChatModel tool loop", | ||
| "description": "Add a configurable max iteration count (default ~10) to the recursive call() loop in ActivityChatModel. Throw after limit to prevent infinite recursion from misbehaving models.", | ||
| "severity": "medium", | ||
| "category": "bugfix", | ||
| "depends_on": [ | ||
| "T1" | ||
| ], | ||
| "status": "reverted", | ||
| "notes": "Reverted: Spring AI does not limit tool call iterations either. Temporal activity timeouts and workflow execution timeout provide the safety net." | ||
| }, | ||
| { | ||
| "id": "T8", | ||
| "title": "Replace fragile stub detection with SDK internals", | ||
| "description": "TemporalStubUtil string-matches on internal handler class names. Since the plugin is in the SDK repo, use internal APIs or instanceof checks. Add tests to catch breakage.", | ||
| "severity": "medium", | ||
| "category": "refactor", | ||
| "depends_on": [ | ||
| "T2" | ||
| ], | ||
| "status": "completed", | ||
| "notes": "Do after tool detection tests exist so we can verify the refactor." | ||
| }, | ||
| { | ||
| "id": "T9", | ||
| "title": "Document static CALLBACK_REGISTRY lifecycle", | ||
| "description": "Add javadoc to LocalActivityToolCallbackWrapper explaining the leak risk when workflows are evicted mid-execution. Consider adding a size metric or periodic cleanup.", | ||
| "severity": "medium", | ||
| "category": "improvement", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T10", | ||
| "title": "Remove unnecessary MCP reflection", | ||
| "description": "SpringAiPlugin uses Class.forName() for McpClientActivityImpl which is in the same module. Will be resolved by T6 (split into conditional configs).", | ||
| "severity": "low", | ||
| "category": "refactor", | ||
| "depends_on": [ | ||
| "T6" | ||
| ], | ||
| "status": "completed", | ||
| "notes": "Likely resolved automatically by T6." | ||
| }, | ||
| { | ||
| "id": "T11", | ||
| "title": "Add UnsupportedOperationException for stream()", | ||
| "description": "Override stream() in ActivityChatModel to throw UnsupportedOperationException with a clear message that streaming is not supported through activities.", | ||
| "severity": "low", | ||
| "category": "improvement", | ||
| "depends_on": [], | ||
| "status": "completed" | ||
| }, | ||
| { | ||
| "id": "T12", | ||
| "title": "Verify all 5 samples run end-to-end", | ||
| "description": "Run chat, MCP, multi-model, RAG, and sandboxing samples interactively against a dev server. Verify tool calling works for each.", | ||
| "severity": "medium", | ||
| "category": "testing", | ||
| "depends_on": [ | ||
| "T6" | ||
| ], | ||
| "status": "completed", | ||
| "notes": "All 5 samples boot successfully. MCP requires Node.js/npx for MCP server (environment prereq, not a code issue)." | ||
| }, | ||
| { | ||
| "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." | ||
| } | ||
| ], | ||
| "execution_order_rationale": "Tests first (T1-T4) in parallel since they're independent. Then fixes that benefit from test coverage: T5 (UUID fix, verified by T3), T6 (plugin split, verified by T4), T7 (loop limit, verified by T1), T8 (stub detection, verified by T2). Then downstream: T10 (resolved by T6), T9/T11 (independent improvements). T12 after T6. T13 blocked on release." | ||
| } |
| 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 ;)