diff --git a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/ChatClientLlmOperations.kt b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/ChatClientLlmOperations.kt index d5151f6ba..afcbddaa4 100644 --- a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/ChatClientLlmOperations.kt +++ b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/ChatClientLlmOperations.kt @@ -74,7 +74,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import javax.annotation.concurrent.ThreadSafe import org.springframework.ai.chat.client.ChatClient -import org.springframework.ai.chat.client.ChatClientCustomizer +import org.springframework.ai.chat.client.ChatClientBuilderCustomizer import org.springframework.ai.chat.client.ResponseEntity import org.springframework.ai.chat.client.advisor.observation.DefaultAdvisorObservationConvention import org.springframework.ai.chat.client.observation.DefaultChatClientObservationConvention @@ -128,7 +128,7 @@ internal class ChatClientLlmOperations( // chat-client spans intact, since this registry stays injected with the real bean. private val observationRegistry: ObservationRegistry = ObservationRegistry.NOOP, instrumentation: AgentInstrumentation = NoOpAgentInstrumentation, - private val customizers: List = emptyList(), + private val customizers: List = emptyList(), asyncer: Asyncer, toolLoopFactory: ToolLoopFactory = ToolLoopFactory.create(ToolLoopConfiguration(), asyncer, AutoCorrectionPolicy()), @Value("\${embabel.agent.platform.streaming.use-legacy-streaming:false}") @@ -363,7 +363,7 @@ internal class ChatClientLlmOperations( // Resolve tool groups and decorate tools val tools = resolveAndDecorateTools(interaction, agentProcess, action) - // Spring AI 2.0: ChatClient merges chatModel.getDefaultOptions() with prompt.options + // Spring AI 2.0: ChatClient merges chatModel.getOptions() with prompt.options // and adds spec-level toolCallbacks last. We bake toolCallbacks into the ToolCallingChatOptions // (preserving the subtype through the merge) AND also pass them via .toolCallbacks() on the // request spec — the latter survives Spring AI's options merge that would otherwise reset @@ -385,7 +385,7 @@ internal class ChatClientLlmOperations( val future = asyncer.async { chatClient .prompt(springAiPrompt) - .toolCallbacks(springAiToolCallbacks) + .tools(springAiToolCallbacks) .call() } @@ -543,7 +543,7 @@ internal class ChatClientLlmOperations( val future = asyncer.async { chatClient .prompt(springAiPrompt) - .toolCallbacks(springAiToolCallbacks) + .tools(springAiToolCallbacks) .call() } diff --git a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModel.kt b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModel.kt index 391298fe4..199e1040f 100644 --- a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModel.kt +++ b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModel.kt @@ -82,7 +82,7 @@ internal class InstrumentedChatModel( // it does NOT delegate Java default methods to the delegate instance. // Instead it calls the interface's own default implementation, which // would silently break: - // - getDefaultOptions() would return a bare ChatOptions instead of + // - getOptions() would return a bare ChatOptions instead of // the delegate's model-specific options (e.g. OpenAiChatOptions) // - stream(Prompt) would throw UnsupportedOperationException // instead of using the delegate's streaming implementation @@ -92,7 +92,7 @@ internal class InstrumentedChatModel( // decision about whether to delegate or instrument. // ------------------------------------------------------------------- - override fun getDefaultOptions(): ChatOptions = delegate.defaultOptions + override fun getOptions(): ChatOptions = delegate.options override fun stream(prompt: Prompt): Flux = delegate.stream(prompt) } diff --git a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamer.kt b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamer.kt index bc3d4598d..dd4a31af2 100644 --- a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamer.kt +++ b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamer.kt @@ -67,7 +67,7 @@ internal class SpringAiLlmMessageStreamer( return chatClient .prompt(prompt) - .toolCallbacks(toolCallbacks) + .tools(toolCallbacks) .stream() .content() } diff --git a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperations.kt b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperations.kt index 734edbe86..eb96c7052 100644 --- a/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperations.kt +++ b/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperations.kt @@ -508,7 +508,7 @@ internal class StreamingChatClientOperations( val promptWithOptions = Prompt(springAiPrompt.instructions, effectiveOptions) chatClient .prompt(promptWithOptions) - .toolCallbacks(springAiToolCallbacks) + .tools(springAiToolCallbacks) .stream() .content() } diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/e2e/LLMStreamingIntegrationTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/e2e/LLMStreamingIntegrationTest.kt index b60dd17d8..e4b51a459 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/e2e/LLMStreamingIntegrationTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/e2e/LLMStreamingIntegrationTest.kt @@ -65,11 +65,11 @@ import reactor.core.publisher.Flux class FakeStreamingChatModel( private val response: String, // Spring AI 2.0 ToolCallAdvisor requires the ChatClientRequest options to be - // ToolCallingChatOptions; the merge is rooted on ChatModel.getDefaultOptions(). + // ToolCallingChatOptions; the merge is rooted on ChatModel.getOptions(). private val options: ChatOptions = ToolCallingChatOptions.builder().build(), ) : ChatModel { - override fun getDefaultOptions(): ChatOptions = options + override fun getOptions(): ChatOptions = options override fun call(prompt: Prompt): ChatResponse { return ChatResponse( diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsGuardRailTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsGuardRailTest.kt index 1fc75d5d7..0d6fd9aab 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsGuardRailTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsGuardRailTest.kt @@ -67,7 +67,7 @@ import org.springframework.ai.chat.messages.AssistantMessage as SpringAssistantM */ class GuardRailTestFakeChatModel( val responses: List, - // Spring AI 2.0: ChatClient merges via getDefaultOptions; use ToolCallingChatOptions + // Spring AI 2.0: ChatClient merges via getOptions; use ToolCallingChatOptions // so the subtype survives the merge into the final Prompt. private val options: ChatOptions = ToolCallingChatOptions.builder().build(), ) : ChatModel { @@ -86,7 +86,7 @@ class GuardRailTestFakeChatModel( val promptsPassed = mutableListOf() val optionsPassed = mutableListOf() - override fun getDefaultOptions(): ChatOptions = options + override fun getOptions(): ChatOptions = options override fun call(prompt: Prompt): ChatResponse { promptsPassed.add(prompt) diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsTest.kt index a2b27f3f4..2030dad4e 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/ChatClientLlmOperationsTest.kt @@ -74,7 +74,7 @@ import kotlin.test.assertEquals */ class FakeChatModel( val responses: List, - // Spring AI 2.0: ChatClient merges options via ChatModel.getDefaultOptions(); the merged + // Spring AI 2.0: ChatClient merges options via ChatModel.getOptions(); the merged // result inherits the default's runtime type. Default to ToolCallingChatOptions so tool // callbacks and the subtype survive the merge — even when the prompt sets its own options. private val options: ChatOptions = ToolCallingChatOptions.builder().build(), @@ -94,7 +94,7 @@ class FakeChatModel( val promptsPassed = mutableListOf() val optionsPassed = mutableListOf() - override fun getDefaultOptions(): ChatOptions = options + override fun getOptions(): ChatOptions = options override fun call(prompt: Prompt): ChatResponse { promptsPassed.add(prompt) @@ -604,12 +604,11 @@ class ChatClientLlmOperationsTest { inner class DelayingFakeChatModel( private val response: String, private val delayMillis: Long, - options: ChatOptions = ToolCallingChatOptions.builder().build(), + private val options: ChatOptions = ToolCallingChatOptions.builder().build(), ) : ChatModel { - private val defaultOptions = options val callCount = java.util.concurrent.atomic.AtomicInteger(0) - override fun getDefaultOptions(): ChatOptions = defaultOptions + override fun getOptions(): ChatOptions = options override fun call(prompt: Prompt): ChatResponse { callCount.incrementAndGet() @@ -937,7 +936,7 @@ class ChatClientLlmOperationsTest { inner class ErrorThrowingChatModel( private val exception: RuntimeException = RuntimeException("401 Unauthorized: Invalid API key") ) : ChatModel { - override fun getDefaultOptions(): ChatOptions = ToolCallingChatOptions.builder().build() + override fun getOptions(): ChatOptions = ToolCallingChatOptions.builder().build() override fun call(prompt: Prompt): ChatResponse = throw exception } diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModelTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModelTest.kt index 548503e01..0d7bf62b7 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModelTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/InstrumentedChatModelTest.kt @@ -168,20 +168,20 @@ class InstrumentedChatModelTest { inner class DefaultOptionsTests { @Test - fun `delegates getDefaultOptions to underlying ChatModel`() { + fun `delegates getOptions to underlying ChatModel`() { val expectedOptions: ChatOptions = mockk() - every { delegate.defaultOptions } returns expectedOptions + every { delegate.options } returns expectedOptions - val result = instrumentedModel.defaultOptions + val result = instrumentedModel.options assertThat(result).isSameAs(expectedOptions) } @Test fun `does not emit any event`() { - every { delegate.defaultOptions } returns mockk() + every { delegate.options } returns mockk() - instrumentedModel.defaultOptions + instrumentedModel.options verify(exactly = 0) { processContext.onProcessEvent(any()) } } diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/SpringAiLlmServiceTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/SpringAiLlmServiceTest.kt index 90849c229..5300b0806 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/SpringAiLlmServiceTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/SpringAiLlmServiceTest.kt @@ -331,7 +331,7 @@ class SpringAiLlmServiceTest { @Nested inner class CreateMessageStreamerTests { - // Relaxed mock needed because ChatClient.create() calls chatModel.getDefaultOptions() + // Relaxed mock needed because ChatClient.create() calls chatModel.getOptions() private val relaxedChatModel: ChatModel = mockk(relaxed = true) @Test diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamerTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamerTest.kt index da49801d4..09090df73 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamerTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/SpringAiLlmMessageStreamerTest.kt @@ -70,7 +70,7 @@ class SpringAiLlmMessageStreamerTest { // survives Spring AI's options merge that would otherwise reset the toolCallbacks // list to the chat model's empty default. every { mockChatClient.prompt(capture(capturedPrompt)) } returns mockRequestSpec - every { mockRequestSpec.toolCallbacks(any>()) } returns mockRequestSpec + every { mockRequestSpec.tools(any>()) } returns mockRequestSpec every { mockRequestSpec.stream() } returns mockStreamSpec } @@ -87,7 +87,7 @@ class SpringAiLlmMessageStreamerTest { // Then verify { mockChatClient.prompt(any()) } - verify { mockRequestSpec.toolCallbacks(any>()) } + verify { mockRequestSpec.tools(any>()) } verify { mockRequestSpec.stream() } // Spring AI 2.0: prompt.options is the rebuilt ToolCallingChatOptions. assertTrue(capturedPrompt.captured.options is ToolCallingChatOptions) diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsGuardRailTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsGuardRailTest.kt index 8ae098205..8ca58262b 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsGuardRailTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsGuardRailTest.kt @@ -69,7 +69,7 @@ import org.springframework.ai.chat.messages.AssistantMessage as SpringAssistantM */ class StreamingGuardRailTestFakeChatModel( val responses: List, - // Spring AI 2.0: ChatClient merges via getDefaultOptions; use ToolCallingChatOptions so + // Spring AI 2.0: ChatClient merges via getOptions; use ToolCallingChatOptions so // the subtype survives the merge. private val options: ChatOptions = ToolCallingChatOptions.builder().build(), ) : ChatModel { @@ -88,7 +88,7 @@ class StreamingGuardRailTestFakeChatModel( val promptsPassed = mutableListOf() val optionsPassed = mutableListOf() - override fun getDefaultOptions(): ChatOptions = options + override fun getOptions(): ChatOptions = options override fun call(prompt: Prompt): ChatResponse { promptsPassed.add(prompt) diff --git a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsTest.kt b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsTest.kt index 01155fbe8..abd29baa9 100644 --- a/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsTest.kt +++ b/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsTest.kt @@ -473,7 +473,7 @@ class StreamingChatClientOperationsTest { val mockContentStreamSpec = mockk(relaxed = true) every { mockChatClient.prompt(any()) } returns mockRequestSpec - every { mockRequestSpec.toolCallbacks(any>()) } returns mockRequestSpec + every { mockRequestSpec.tools(any>()) } returns mockRequestSpec every { mockRequestSpec.options(any()) } returns mockRequestSpec every { mockRequestSpec.stream() } returns mockContentStreamSpec every { mockContentStreamSpec.content() } returns chunkFlux diff --git a/embabel-agent-autoconfigure/models/embabel-agent-dockermodels-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/docker/DockerLocalModelsConfig.kt b/embabel-agent-autoconfigure/models/embabel-agent-dockermodels-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/docker/DockerLocalModelsConfig.kt index a2787f667..c15909378 100644 --- a/embabel-agent-autoconfigure/models/embabel-agent-dockermodels-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/docker/DockerLocalModelsConfig.kt +++ b/embabel-agent-autoconfigure/models/embabel-agent-dockermodels-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/docker/DockerLocalModelsConfig.kt @@ -211,14 +211,14 @@ class DockerLocalModelsConfig( } private fun dockerEmbeddingServiceOf(model: Model): SpringAiEmbeddingService { - val springEmbeddingModel = OpenAiEmbeddingModel( - openAiClient, - MetadataMode.EMBED, - OpenAiEmbeddingOptions.builder() + val springEmbeddingModel = OpenAiEmbeddingModel.builder() + .openAiClient(openAiClient) + .metadataMode(MetadataMode.EMBED) + .options(OpenAiEmbeddingOptions.builder() .model(model.id) - .build(), - observationRegistry.getIfUnique { ObservationRegistry.NOOP }, - ) + .build()) + .observationRegistry(observationRegistry.getIfUnique { ObservationRegistry.NOOP }) + .build() return SpringAiEmbeddingService( name = model.id, diff --git a/embabel-agent-autoconfigure/models/embabel-agent-oci-genai-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/ocigenai/OciGenAiChatModel.kt b/embabel-agent-autoconfigure/models/embabel-agent-oci-genai-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/ocigenai/OciGenAiChatModel.kt index 149daba4a..72559bd75 100644 --- a/embabel-agent-autoconfigure/models/embabel-agent-oci-genai-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/ocigenai/OciGenAiChatModel.kt +++ b/embabel-agent-autoconfigure/models/embabel-agent-oci-genai-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/ocigenai/OciGenAiChatModel.kt @@ -102,7 +102,7 @@ class OciGenAiChatModel( return toSpringChatResponse(response.chatResult, options) } - override fun getDefaultOptions(): ChatOptions = defaultOptions.copy() + override fun getOptions(): ChatOptions = defaultOptions.copy() internal fun chatRequest(prompt: Prompt, options: OciGenAiChatOptions): BaseChatRequest = when (options.apiFormat) { diff --git a/embabel-agent-openai/src/main/kotlin/com/embabel/agent/openai/OpenAiCompatibleModelFactory.kt b/embabel-agent-openai/src/main/kotlin/com/embabel/agent/openai/OpenAiCompatibleModelFactory.kt index 0c0c32fb3..3d9d91c18 100644 --- a/embabel-agent-openai/src/main/kotlin/com/embabel/agent/openai/OpenAiCompatibleModelFactory.kt +++ b/embabel-agent-openai/src/main/kotlin/com/embabel/agent/openai/OpenAiCompatibleModelFactory.kt @@ -312,14 +312,14 @@ open class OpenAiCompatibleModelFactory( configuredDimensions: Int? = null, pricingModel: PricingModel? = null, ): EmbeddingService { - val embeddingModel = OpenAiEmbeddingModel( - openAiClient, - MetadataMode.EMBED, - OpenAiEmbeddingOptions.builder() + val embeddingModel = OpenAiEmbeddingModel.builder() + .openAiClient(openAiClient) + .metadataMode(MetadataMode.EMBED) + .options(OpenAiEmbeddingOptions.builder() .model(model) - .build(), - observationRegistry, - ) + .build()) + .observationRegistry(observationRegistry) + .build() return SpringAiEmbeddingService( name = model, model = embeddingModel,