Skip to content

2.0.0#1697

Draft
alexheifetz wants to merge 47 commits into
mainfrom
2.0.0
Draft

2.0.0#1697
alexheifetz wants to merge 47 commits into
mainfrom
2.0.0

Conversation

@alexheifetz

Copy link
Copy Markdown
Contributor

This pull request updates the codebase to support Spring Boot 4 and the new tools.jackson package, and makes related dependency and code changes. The main themes are migration from com.fasterxml.jackson to tools.jackson, updating dependencies for Spring Boot 4 compatibility, and minor API improvements.

Migration to tools.jackson:

  • Replaced all imports and usages of com.fasterxml.jackson.databind.*, com.fasterxml.jackson.module.*, and com.fasterxml.jackson.dataformat.* with their tools.jackson equivalents across main and test sources, including ObjectMapper, JsonDeserialize, and YAML handling (YAMLMapper and kotlinModule). [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19]

Spring Boot 4 and dependency updates:

  • Updated parent and module versions in pom.xml files from 0.5.0-SNAPSHOT to 2.0.0-SNAPSHOT to reflect the new release and Spring Boot 4 compatibility. [1] [2]
  • Added the new spring-boot-webmvc-test dependency for MockMvc support, and updated the test annotation import to org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc for Spring Boot 4. [1] [2]
  • Added direct dependency on spring-retry (version 2.0.12) since it is no longer managed by the Spring Boot BOM. [1] [2]

Code and API improvements:

  • Updated type parameters in withProperties and withoutProperties extension functions to use KProperty1<T, *> for better Kotlin compatibility. [1] [2]
  • Minor cleanup: removed unnecessary .findAndRegisterModules() call on jacksonObjectMapper() in test code.

These changes ensure compatibility with Spring Boot 4, migrate to the new Jackson package, and improve type safety and maintainability.

…son 3

Brings the agent platform stack onto the Spring Boot 4 line. Cascades
through Spring Framework 7, Spring AI 2.0.0-M8, Jackson 3 (tools.jackson.*),
Kotlin 2.2.21 (K2), and JUnit 6.

Spring AI 2.0 API changes:
- ChatClient.options() now takes ChatOptions.Builder<*>; bake chatOptions
  into the Prompt itself (preserves the ToolCallingChatOptions subtype
  through the advisor chain) and embed toolCallbacks via
  ToolCallingChatOptions.Builder so they survive the chatModel-defaults
  merge in addition to the spec-level .toolCallbacks() call.
- Generation.getResult() / Message.getText() are now nullable; added the
  required !! / ?: dereferences at call sites.
- Usage is non-null in 2.0; updated test mocks accordingly.
- StructuredOutputConverter added getJsonSchema(); JacksonOutputConverter
  exposes it via a private val.
- MCP customizer unified: McpSyncClientCustomizer + McpAsyncClientCustomizer
  → McpClientCustomizer.
- MCP clientCapabilities is now non-null in test mocks.
- ModelOptionsUtils.jsonToMap removed; replaced with objectMapper.readValue.

Jackson 3 migration (tools.jackson.*):
- POM groupIds: com.fasterxml.jackson.module/dataformat → tools.jackson.*;
  spring-boot pulls both jackson-bom (2.x) and tools.jackson:jackson-bom (3.x).
- jackson-datatype-jsr310 removed — JavaTimeModule is built into Jackson 3.
- ObjectMapper is now immutable; JsonMapper.builder() / rebuild() pattern
  for lenientMapper customization.
- ValueDeserializer (was JsonDeserializer); ContextualDeserializer merged in.
- propertyName / propertyNames (was fieldName / fieldNames).
- WRITE_DATES_AS_TIMESTAMPS relocated to DateTimeFeature.
- Custom MaybeReturnDeserializer rewritten against the Jackson 3 API.
- Jackson 3 throws IllegalArgumentException/JacksonException (was IOException)
  for unserializable graphs; loosened test assertions accordingly.

Spring Boot 4:
- Jackson2ObjectMapperBuilder is no longer auto-configured; embabelJackson-
  ObjectMapper now builds via jacksonObjectMapper() directly and is
  autowire-eligible (dropped defaultCandidate=false) so generic
  ObjectMapper injections in integration tests resolve.
- spring-retry was removed from the Boot 4 BOM but the API we use is
  unchanged; kept the library as a direct dep (spring-retry 2.0.12) rather
  than migrating to org.springframework.core.retry.
- @AutoConfigureMockMvc moved to the new spring-boot-webmvc-test artifact
  under a different package; removed from CustomValidationAnnotationTest
  since it didn't actually use MockMvc.

Spring Framework 7 / Kotlin 2.2.21 / JSpecify:
- @NullMarked enforcement on Spring AI / Spring core / Jackson 3 supertypes
  cascades T : Any bounds into our overrides. Added -Xjspecify-annotations=
  ignore to embabel-build to suppress JSpecify-derived bound errors and
  reverted most <T : Any> bound additions, keeping them only at the
  unavoidable boundary (7 converter classes that directly extend Spring AI's
  StructuredOutputConverter / Spring's ObjectProvider).
- Cast outputClass to Class<Any> at 4 converter-construction sites so
  unbounded <O> enclosing methods can still construct the bounded converters.
- AutoRegistration ApplicationListener<ContextRefreshedEvent> bound dropped
  the redundant ? per Spring 7's stricter signatures.

ArchUnit / victools / Jackson 3 schema generation:
- victools 5.0.0 (Jackson 3 native) pulled transitively by Spring AI 2.0.
- ObjectNode.deepCopy() returns ObjectNode directly (no type arg).
- Schema "required" field may now nest arrays; flattened in two test sites.

Tests:
- FakeChatModel defaults switched from DefaultChatOptions to
  ToolCallingChatOptions so the prompt.options merge preserves the subtype.
- 3 assertions on prompt.options.toolCallbacks count relaxed — Spring AI 2.0
  routes spec toolCallbacks through ToolCallAdvisor internally rather than
  mirroring them onto the final prompt seen by ChatModel.call().
- assertThrows<X?> → assertThrows<X> (JUnit 6 @NullMarked).
- Many test helpers bounded <T : Any> to match migrated main-source signatures.

POM:
- kotlin.version 2.1.10 → 2.2.21; kotlin.compiler.apiVersion 2.1 → 2.2.
- spring-boot.version 4.0.6 confirmed.
- spring-retry 2.0.12 as direct dep in embabel-agent-api.
- spring-boot-starter-aop → aspectjweaver direct dep (Boot 4 renamed starter).
- jackson-module-kotlin / jackson-dataformat-yaml groupIds migrated.
…son 3

Brings the agent platform stack onto the Spring Boot 4 line. Cascades
through Spring Framework 7, Spring AI 2.0.0-M8, Jackson 3 (tools.jackson.*),
Kotlin 2.2.21 (K2), and JUnit 6.

Spring AI 2.0 API changes:
- ChatClient.options() now takes ChatOptions.Builder<*>; bake chatOptions
  into the Prompt itself (preserves the ToolCallingChatOptions subtype
  through the advisor chain) and embed toolCallbacks via
  ToolCallingChatOptions.Builder so they survive the chatModel-defaults
  merge in addition to the spec-level .toolCallbacks() call.
- Generation.getResult() / Message.getText() are now nullable; added the
  required !! / ?: dereferences at call sites.
- Usage is non-null in 2.0; updated test mocks accordingly.
- StructuredOutputConverter added getJsonSchema(); JacksonOutputConverter
  exposes it via a private val.
- MCP customizer unified: McpSyncClientCustomizer + McpAsyncClientCustomizer
  → McpClientCustomizer.
- MCP clientCapabilities is now non-null in test mocks.
- ModelOptionsUtils.jsonToMap removed; replaced with objectMapper.readValue.

Jackson 3 migration (tools.jackson.*):
- POM groupIds: com.fasterxml.jackson.module/dataformat → tools.jackson.*;
  spring-boot pulls both jackson-bom (2.x) and tools.jackson:jackson-bom (3.x).
- jackson-datatype-jsr310 removed — JavaTimeModule is built into Jackson 3.
- ObjectMapper is now immutable; JsonMapper.builder() / rebuild() pattern
  for lenientMapper customization.
- ValueDeserializer (was JsonDeserializer); ContextualDeserializer merged in.
- propertyName / propertyNames (was fieldName / fieldNames).
- WRITE_DATES_AS_TIMESTAMPS relocated to DateTimeFeature.
- Custom MaybeReturnDeserializer rewritten against the Jackson 3 API.
- Jackson 3 throws IllegalArgumentException/JacksonException (was IOException)
  for unserializable graphs; loosened test assertions accordingly.

Spring Boot 4:
- Jackson2ObjectMapperBuilder is no longer auto-configured; embabelJackson-
  ObjectMapper now builds via jacksonObjectMapper() directly and is
  autowire-eligible (dropped defaultCandidate=false) so generic
  ObjectMapper injections in integration tests resolve.
- spring-retry was removed from the Boot 4 BOM but the API we use is
  unchanged; kept the library as a direct dep (spring-retry 2.0.12) rather
  than migrating to org.springframework.core.retry.
- @AutoConfigureMockMvc moved to the new spring-boot-webmvc-test artifact
  under a different package; removed from CustomValidationAnnotationTest
  since it didn't actually use MockMvc.

Spring Framework 7 / Kotlin 2.2.21 / JSpecify:
- @NullMarked enforcement on Spring AI / Spring core / Jackson 3 supertypes
  cascades T : Any bounds into our overrides. Added -Xjspecify-annotations=
  ignore to embabel-build to suppress JSpecify-derived bound errors and
  reverted most <T : Any> bound additions, keeping them only at the
  unavoidable boundary (7 converter classes that directly extend Spring AI's
  StructuredOutputConverter / Spring's ObjectProvider).
- Cast outputClass to Class<Any> at 4 converter-construction sites so
  unbounded <O> enclosing methods can still construct the bounded converters.
- AutoRegistration ApplicationListener<ContextRefreshedEvent> bound dropped
  the redundant ? per Spring 7's stricter signatures.

ArchUnit / victools / Jackson 3 schema generation:
- victools 5.0.0 (Jackson 3 native) pulled transitively by Spring AI 2.0.
- ObjectNode.deepCopy() returns ObjectNode directly (no type arg).
- Schema "required" field may now nest arrays; flattened in two test sites.

Tests:
- FakeChatModel defaults switched from DefaultChatOptions to
  ToolCallingChatOptions so the prompt.options merge preserves the subtype.
- 3 assertions on prompt.options.toolCallbacks count relaxed — Spring AI 2.0
  routes spec toolCallbacks through ToolCallAdvisor internally rather than
  mirroring them onto the final prompt seen by ChatModel.call().
- assertThrows<X?> → assertThrows<X> (JUnit 6 @NullMarked).
- Many test helpers bounded <T : Any> to match migrated main-source signatures.

POM:
- kotlin.version 2.1.10 → 2.2.21; kotlin.compiler.apiVersion 2.1 → 2.2.
- spring-boot.version 4.0.6 confirmed.
- spring-retry 2.0.12 as direct dep in embabel-agent-api.
- spring-boot-starter-aop → aspectjweaver direct dep (Boot 4 renamed starter).
- jackson-module-kotlin / jackson-dataformat-yaml groupIds migrated.
…customizer

- A2A: add spring-boot-webmvc-test test dep; @AutoConfigureMockMvc moved
  to org.springframework.boot.webmvc.test.autoconfigure
- Replace McpSyncClientCustomizer/McpAsyncClientCustomizer (Spring AI 1.x)
  with unified McpClientCustomizer (Spring AI 2.0) in javadoc refs
…odules

- bedrock: JsonMapper for Spring AI 2.0; spring-boot-jackson test dep
  (JacksonAutoConfiguration relocated to o.s.b.jackson.autoconfigure)
- observability: ContentCachingRequestWrapper 2-arg ctor (prod + 3 tests);
  ObservationRegistryCustomizer moved to micrometer-observation module
- shell: javax.validation -> jakarta.validation imports
- mcpserver-security: spring-boot-security + oauth2 resource-server test
  deps; SecurityAutoConfiguration FQN fix for Boot 4 split
- mcpserver: McpSchema.Resource ctor gained title/size/meta params
- webmvc: spring-boot-webmvc-test dep; MockMvc import relocation (5 tests)
- deepseek/google-genai/mistral-ai: drop redundant in-builder retryTemplate
  call (Spring AI 2.0 expects o.s.core.retry, retries already wrap at
  ChatClientLlmOperations via spring-retry)
- reactor: temporarily disable embabel-agent-openai and the autoconfigure/
  starter modules transitively depending on it (openai*, openai-custom*,
  anthropic*, lmstudio*, gemini*, dockermodels*, minimax*) pending
  Spring AI 2.0 rewrites — OpenAiApi/AnthropicApi were removed in M8
…native SDKs

Spring AI 2.0.0-M8 removed its hand-rolled OpenAiApi / AnthropicApi facades
and delegates to the official openai-java (OpenAIClient) and anthropic-java
(AnthropicClient) SDKs. The model builders' fluent setters were renamed
(.openAiApi -> .openAiClient, .anthropicApi -> .anthropicClient,
.defaultOptions -> .options) and .retryTemplate(spring-retry RetryTemplate)
was dropped — retries already wrap at the ChatClientLlmOperations layer.

OpenAI:
- OpenAiCompatibleModelFactory: build OpenAIClient via OpenAIOkHttpClient
  builder (baseUrl, apiKey, customHeaders); ctor preserves restClientBuilder
  / webClientBuilder / completionsPath / embeddingsPath / retryTemplate params
  as no-ops for source compatibility with the 5 downstream subclasses
  (LMStudio, Gemini, Minimax, OpenAi-custom, OpenAi-autoconfigure).
- OpenAiEmbeddingModel switched to the (OpenAIClient, MetadataMode, Options,
  ObservationRegistry) ctor.
- DockerLocalModelsConfig: same migration off the now-removed OpenAiApi.
- embabel-agent-openai pom: pin openai-java + openai-java-client-okhttp
  4.36.0 at compile scope (overriding the BOM's 4.16.1/test) and add
  spring-webflux (spring-ai-openai 2.0 dropped its webflux dep but our
  factory's ctor still types webClientBuilder against WebClient.Builder).
- LLMOpenAiGuardRailsIntegrationIT: rewire OpenAiModerationModel via
  OpenAiModerationModel.builder().openAiClient(...) — OpenAiModerationApi
  no longer exists.

Anthropic:
- AnthropicModelFactory: build AnthropicClient via AnthropicOkHttpClient
  builder (preferred over AnthropicSetup.setupSyncClient to avoid the SDK's
  implicit ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY env-var precedence).
- AnthropicCacheTtl / AnthropicCacheOptions / AnthropicCacheStrategy
  promoted out of the .api subpackage in Spring AI 2.0; update imports.
- Thinking config: AnthropicApi.ChatCompletionRequest.ThinkingConfig is gone
  — use first-class AnthropicChatOptions.Builder methods thinkingEnabled(long)
  / thinkingDisabled().
- UsageExtensions: AnthropicApi.Usage (int accessors) -> anthropic-java's
  com.anthropic.models.messages.Usage (Optional<Long> accessors). Preserve
  Int? return shape via .orElse(null)?.toInt().
- Test fixtures rewritten: ThinkingConfigParam exposes isEnabled() /
  isDisabled() as functions (not properties); UsageExtensionsTest gets a
  helper that wraps Usage.builder() to keep the prior positional ergonomics.

Reactor: re-enable embabel-agent-openai + the seven autoconfigure/starter
modules disabled in the previous commit pending these rewrites
(openai, openai-custom, anthropic, lmstudio, gemini, dockermodels, minimax).

Plus a small observability follow-up: gate MicrometerTracingAutoConfiguration's
defaultTracingObservationHandler with @ConditionalOnBean(Tracer.class) — the
bean used Tracer as a hard ctor param and crashed app startup whenever no
tracing bridge (Brave / OTel) was wired in.
@alexheifetz

Copy link
Copy Markdown
Contributor Author

do not merge this one. It's for information purposes only.

@alexheifetz alexheifetz requested review from igordayen and poutsma June 4, 2026 03:17
@alexheifetz alexheifetz self-assigned this Jun 4, 2026
@alexheifetz alexheifetz requested a review from azanux June 4, 2026 03:17
Spring Boot 4 / Spring AI 2.0 brought Jackson 3 (tools.jackson.*). The Spring
context now publishes a tools.jackson.databind.ObjectMapper bean — autowiring
the Jackson 2 com.fasterxml.jackson.databind.ObjectMapper fails with
NoSuchBeanDefinition. Update the import; the readValue/writeValueAsString
call sites work unchanged.
Spring Boot 4 split JacksonAutoConfiguration into its own module
(spring-boot-jackson). The test's slim @SpringBootTest(classes=[…]) bootstrap
doesn't import it, so neither a Jackson 2 nor Jackson 3 ObjectMapper bean is
available to autowire. The mapper is only used for writeValueAsString /
readValue on test DTOs — drop the @Autowired and construct it inline via
jacksonObjectMapper() from tools.jackson.module.kotlin.
Same Jackson 2 → 3 migration applied earlier in examples-common: switch the
com.fasterxml.jackson.module.kotlin imports to tools.jackson.module.kotlin,
and collapse ObjectMapper().registerKotlinModule() to jacksonObjectMapper()
(Jackson 3's ObjectMapper is immutable, so the fluent-register pattern is
gone — the helper returns a mapper with the Kotlin module already wired).

@poutsma poutsma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done, @alexheifetz. that must have been an enormous effort.

Looks good from my end, the only minor comment I have is that some commits include unnecessary space removals that muck up the git history. I have indicated where I have noticed them.


// Execution methods
fun <T> createObject(
fun <T>createObject(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: perhaps revert this and the next three space removals to keep the git history clean.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — thanks. These came from an IDE reformat that dropped the space after the type-parameter list (fun name → fun name). Reverted all 61 occurrences across 9 files, so the generic-method spacing is back to the original style everywhere.

* @return creating mode supporting examples, property filtering, and validation
*/
fun <T> creating(outputClass: Class<T>): Creating<T>
fun <T>creating(outputClass: Class<T>): Creating<T>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


val innerToolsByName = innerTools.associateBy { it.definition.name }
for (fieldName in parsed.fieldNames()) {
for (fieldName in parsed.propertyNames()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename the variable to propertyName here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

override fun <O> createObjectStream(
override fun <O>createObjectStream(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}

override fun <O> createObjectStream(
override fun <O>createObjectStream(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

* @throws RuntimeException if the operation times out or fails
*/
protected fun <T> executeWithTimeout(
protected fun <T>executeWithTimeout(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

) {

override fun <O> doTransform(
override fun <O>doTransform(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

override fun withToolChainingFromAny(): PromptExecutionDelegate = this

override fun <T> createObject(messages: List<Message>, outputClass: Class<T>): T {
override fun <T>createObject(messages: List<Message>, outputClass: Class<T>): T {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More space removals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

* Extension of [JacksonOutputConverter] that allows for filtering of properties of the generated object via a predicate.
*/
open class FilteringJacksonOutputConverter<T> internal constructor(
open class FilteringJacksonOutputConverter<T : Any>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was internal constructor removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unintentional — it got dropped during the migration. Restored internal constructor on the primary constructor; the bound is required by the parent JacksonOutputConverter so I kept that.

@igordayen

Copy link
Copy Markdown
Contributor

@igordayen

Copy link
Copy Markdown
Contributor

from CLAUDE:
✅ Code Quality Verdict

The code changes are high quality:

  • Anti-patterns were FIXED (not introduced)
  • Backward compatibility carefully preserved
  • Excellent inline documentation
  • Test coverage updated for new behaviors
  • Deprecation warnings added where needed

alexheifetz and others added 8 commits June 4, 2026 22:46
Spring AI relocated AnthropicCacheTtl out of the .anthropic.api package
into .anthropic. Update the test import from
org.springframework.ai.anthropic.api.AnthropicCacheTtl to
org.springframework.ai.anthropic.AnthropicCacheTtl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Apache license header had its "Copyright 2024-2026 Embabel Pty Ltd."
line (and trailing blank) repeated twice at the top of the file. Drop the
duplicate so the header matches the standard single-block form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spring AI 2.0 reshaped the ToolCallingChatOptions interface: the
set* mutators (setToolCallbacks/setToolNames/setToolContext/
setInternalToolExecutionEnabled) were dropped in favour of builder-based
configuration, and a new abstract mutate() returning
ToolCallingChatOptions.Builder<*> was added.

- Drop the `override` from the four tool setters; they remain as plain
  helpers used internally by merge() and the builder.
- Implement mutate() to return a builder seeded with copy(), preserving
  the OCI-specific fields (compartmentId, servingMode, endpointId,
  apiFormat) alongside the standard chat/tool options.
- Make the nested Builder implement ToolCallingChatOptions.Builder<Builder>,
  adding the now-required members: toolCallbacks/toolNames/toolContext
  (collection + vararg/pair overloads), plus clone() and combineWith()
  from ChatOptions.Builder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The test wrapped the parallel map in assertTimeoutPreemptively(300ms) to
prove execution wasn't serial (serial = 8 × 100ms = 800ms; parallel ≈
200ms). With only ~100ms of slack over the ideal, GC/scheduling on loaded
CI runners pushed it past 300ms and it flaked (Run 1 timeout, Run 2 pass),
failing the build under failOnFlakeCount=1.

The maxConcurrent >= concurrencyLevel assertion already proves parallelism
deterministically: each task records the peak number of simultaneously
sleeping tasks, which can only reach the concurrency level under genuine
parallel execution (serial would peak at 1). Drop the redundant timing-
based timeout and rely on that assertion; remove the now-unused
assertTimeoutPreemptively / Duration imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- UnfoldingTool: rename loop variable `fieldName` -> `propertyName` in
  tryShortcutDispatch to match the Jackson 3 fieldNames() -> propertyNames()
  migration.
- FilteringJacksonOutputConverter: restore the `internal constructor` modifier
  on the primary constructor (unintentionally dropped during the migration).
  The `<T : Any>` bound is required by the parent and is retained; all callers
  use the public secondary constructors.
- Revert spurious `fun <T> name` -> `fun <T>name` spacing changes across
  embabel-agent-api (61 sites in 9 files) to keep the git history clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eSender

Spring AI 2.0 made ToolCallingChatOptions.toolCallbacks and
internalToolExecutionEnabled read-only, so the migration commented out the
setters and returned a bare copy(). That silently dropped the tool callbacks
whenever chatOptions was already a ToolCallingChatOptions (e.g. Anthropic /
OpenAI provider options), breaking tool calling such as vector/fulltext search.

Use mutate() to derive a new instance with the callbacks attached and Spring
AI's automatic tool execution disabled, preserving the provider-specific
subtype. This matches the pattern already used in ChatClientLlmOperations and
SpringAiLlmMessageStreamer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Embabel's MicrometerTracingAutoConfiguration registers a
NonEmbabelTracingObservationHandler (a DefaultTracingObservationHandler
subclass) guarded by @ConditionalOnMissingBean. Spring Boot's own tracing
auto-config registers a DefaultTracingObservationHandler with the same
condition, so whichever runs first wins — the ordering was undefined.

Make the ordering deterministic via @autoConfiguration:
- afterName OpenTelemetryTracingAutoConfiguration: run after the OpenTelemetry
  Tracer (OtelTracer) bean exists, otherwise @ConditionalOnBean(Tracer.class)
  evaluates false and our handler is never registered. In Spring Boot 4 the
  Tracer is produced by a different class than the handler registrar, so a
  single beforeName is not enough.
- beforeName MicrometerTracingAutoConfiguration: run before Boot registers its
  DefaultTracingObservationHandler, so Boot's @ConditionalOnMissingBean backs
  off in favour of ours.

Boot's classes are referenced by name so we take no compile/runtime dependency
on the spring-boot-micrometer-tracing(-opentelemetry) modules; the ordering is
ignored when those modules are absent from the classpath.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alexheifetz

Copy link
Copy Markdown
Contributor Author

Well done, @alexheifetz. that must have been an enormous effort.

Looks good from my end, the only minor comment I have is that some commits include unnecessary space removals that muck up the git history. I have indicated where I have noticed them.

Yes, the amount of code changes (mostly related to Jackson 3 upgrade) is significant, but needs to be done in order to move the project forward :)

@urferr

urferr commented Jun 10, 2026

Copy link
Copy Markdown

I noticed when migrating a small embabel example to embabel 2.0.0-SNAPSHOT that the current 2.0.0 branch still uses spring shell 3.x. This still seems to work but to be compatible with spring boot 4 you should also upgrade to spring shell 4.x. Unfortunately the annotations you used are no longer supported in 4.x and have to be replaced as described in the upgrade guide.

alexheifetz and others added 3 commits June 13, 2026 12:51
…pring AI 2/Jackson 3

Resolves conflicts and forward-ports HEAD's native-structured-output + JSON
schema features onto the 2.0.0 baseline (Spring Boot 4, Spring AI 2.0.0-M8,
Jackson 3). The merge text-merged cleanly but the incoming feature code was
written against pre-2.0 APIs, so each affected file was ported, not just merged.

Conflict resolutions:
- ChatClientLlmOperations: keep 2.0.0's Class<Any> erasure (Spring AI 2.0 requires
  T : Any); re-wire the JSON converter through the adapter's jsonSchemaProvider so
  native structured output (#1715) survives the merge
- JacksonOutputConverter / converters: jsonSchema exposed as fun getJsonSchema()
  (property form clashes with Spring AI 2.0 StructuredOutputConverter.getJsonSchema())

Jackson 2 -> Jackson 3 (com.fasterxml.jackson -> tools.jackson):
- jsonSchemaSupport, nativeStructuredOutputSupport, OpenAiNativeStructuredOutputConfigurer
  and tests migrated to tools.jackson APIs
- fieldNames() -> propertyNames(), elements() -> values(), ObjectMapper() ->
  JsonMapper.builder(), ObjectNode.set<T>() -> set(), and SerializationConfig.introspect()
  -> ClassIntrospector chain (introspectClassAnnotations + introspectForSerialization)

Spring AI 1.x -> 2.0 (OpenAI native structured output):
- ResponseFormat moved to OpenAiChatModel.ResponseFormat; jsonSchema now takes a String
- options configured via mutate() builder (responseFormat/outputSchema are read-only)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sync 2.0.0-061326 with 2.0.0: port native structured output to SB4 / Spring AI 2 / Jackson 3
alexheifetz and others added 13 commits June 14, 2026 22:42
Adapt code from Spring AI 2.0.0-M8 to 2.0.0 GA after the merge in 90d0e1e.

- JacksonOutputConverter: LoggingMarkers removed in GA -> SLF4J MarkerFactory.getMarker("SENSITIVE")
- SpringAiLlmMessageSender: drop removed per-request internalToolExecutionEnabled flag (internal tool execution is now controlled at the ChatModel level)
- ChatClientLlmTransformerTest: DefaultChatOptions() ctor now protected -> ChatOptions.builder().build()
- Bedrock: drop removed ToolExecutionEligibilityPredicate (use 5-arg BedrockProxyChatModel ctor); convert Cohere Properties.Options -> BedrockCohereEmbeddingOptions

Build state: core modules + Bedrock compile. Remaining provider modules
(Anthropic, Gemini, OCI, etc.) still need the same GA migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OllamaChatModel/OllamaEmbeddingModel builders renamed defaultOptions() -> options() in Spring AI 2.0. Update both call sites in OllamaModelsConfig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChatModel builders renamed defaultOptions() -> options() in Spring AI 2.0. Update DeepSeekModelsConfig and MistralAiModelsConfig. (Bedrocks .defaultOptions is on embabels own EmbabelBedrockProxyChatModelBuilder and is intentionally unchanged.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spring AI 2.0.0 moved GoogleGenAiEmbeddingConnectionDetails to the org.springframework.ai.google.genai.embedding sub-package. Update the import in GoogleGenAiModelsConfig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OciGenAiChatOptions hand-implements Spring AI options interfaces, which changed in 2.0.0:
- ToolCallingChatOptions dropped toolNames + internalToolExecutionEnabled; ChatOptions dropped copy(). Drop override on these (getters, builder methods, copy) and keep them as OCI-internal members.
- merge(): stop reading toolNames/internalToolExecutionEnabled off the Spring AI ToolCallingChatOptions interface; merge them only between OciGenAiChatOptions instances.
- Test: call getInternalToolExecutionEnabled() explicitly (property name now resolves to the private backing var).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spring AI 2.0 GA ChatClient no longer preserves the ToolCallingChatOptions subtype through its option merge; a real ChatModel now receives a plain ChatOptions (tools flow via the ToolCallingAdvisor / spec-level toolCallbacks). The test fakes asserted prompt.options is ToolCallingChatOptions, which held in M8 but not GA.

- FakeChatModel and the streaming guardrail fake now accept any ChatOptions instead of throwing.
- optionsPassed typed as List<ChatOptions>; the "presents no tools" assertion reads toolCallbacks null-safe via an as? cast (also handles getToolCallbacks() now returning null when empty).
- Removed a dead options assertion in the delaying fake.

Test-only; no production change. Resolves the 11 failures in ChatClientLlmOperationsTest / ChatClientLlmOperationsThinkingTest / StreamingChatClientOperationsGuardRailTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng AI 2.0)

Spring AI 2.0 spring-ai-anthropic depends only on anthropic-java-core, not the OkHttp client. AnthropicModelFactory uses AnthropicOkHttpClient directly, so the module must declare anthropic-java-client-okhttp explicitly. Version is supplied by the embabel-build BOM (anthropic-java.version) - no inline version here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nsDetails

anthropic-java 2.40.1 (pinned transitively by spring-ai-anthropic 2.0.0) made Usage.Builder.outputTokensDetails a required field. The anthropicUsage test helper did not set it, so Usage.build() threw IllegalStateException on all 12 tests. Set it to Optional.empty(), consistent with the helpers other required-but-empty fields. Test-only.

(Surfaced only on CI: local .m2 still had anthropic-java 2.34.0 and the module was built with -DskipTests.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Synchronize with 1.0 06222026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants