Skip to content

fix(spring-ai): restore binary compatibility with Spring AI 1.1.x (TH-7477) - #204

Open
nik13 wants to merge 7 commits into
devfrom
fix/TH-7477-spring-ai-1.1
Open

nik13 wants to merge 7 commits into
devfrom
fix/TH-7477-spring-ai-1.1

Conversation

@nik13

@nik13 nik13 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

traceai-spring-ai was compiled against Spring AI 1.0.0-M4 and called APIs removed before GA (Message.getContent(), Usage.getGenerationTokens()), so any customer on a current Spring AI release hit NoSuchMethodError on the first chat call. This ports the wrapper to Spring AI 1.1.x.

  • Compile against Spring AI 1.1.8 / Spring Boot 3.5.15; depend on spring-ai-model (provided) instead of spring-ai-core
  • Read text with getText() and completion usage with getCompletionTokens()
  • stream(prompt) starts no span and does not call the delegate until subscription; each subscription owns one span ended exactly once
  • Token counts are recorded only when the provider reports them (EmptyUsage zeros are absence, measured zero is preserved)
  • Every embedding overload delegates directly, including the 1.1.8-only getEmbeddingContent (no span, unreachable on 1.1.0)
  • SpringAITracingContext.withContext binds an explicit parent span and TraceAI attributes to a reactive subscription
  • Four privacy switches stay independent; stream capture is bounded to 32,000 characters
  • Starter auto-configuration covered by four test classes, each in a fresh JVM (disabled context, custom tracer bean, uninitialized wrapper, default wiring)
  • Parent java/pom.xml: Surefire no longer hardcodes a -javaagent path to a ByteBuddy JAR in ~/.m2 (it crashed the forked test JVM on any machine without that file). This applies to every Java module, so the whole reactor was run (below).
  • New workflow Java Spring AI compatibility (runs on changes under the Spring modules, java/pom.xml, the compatibility cells and the example): module and starter suites, the same built JAR against Spring AI 1.1.8 and 1.1.0, the 1.1.8-only accessor, and the example compile. Migration notes are in java/compatibility/spring-ai/MIGRATION.md.

Closes the SDK side of TH-7477. The docs warning is the separate PR future-agi/docs#885, which does not depend on this one.

Test plan

  • CI on head c1e3c6b: run 37600098101 passed: core 13, Spring AI module 48 (5 credential-gated E2E skips), starter 7, both matrix cells (Spring AI 1.1.8 and 1.1.0 on the same JAR), the 1.1.8-only accessor, and the example compile
  • Whole Java reactor, mvn -B -ntp -fae test in the CI's pinned maven:3.9.11-eclipse-temurin-17 image on 31a84b8 (same Java sources as c1e3c6b): BUILD SUCCESS across all 25 modules, 172 tests, 0 failures, 109 skipped (credential-gated E2E). This checks the parent Surefire change on the modules the new workflow does not run.
  • Real local agent (Ollama 0.35.0 / qwen3:1.7b, actual @Tool invocation, sync and stream) exported a span carrying the run marker; logs are in the TH-7477 evidence bundle on Linear
  • Independent review of b085ce0 found one blocker (starter auto-configuration untested) and three minor notes; all fixed in 31a84b8 and re-verified with no blockers. c1e3c6b only adds the starter tests to the CI step.

Not in this PR

No release, no JitPack coordinate. Spring AI 2.0 / Spring Boot 4 and an M4 compatibility bridge are out of scope. The rest of the Java modules have no CI in this repo; the reactor run above is a one-off local check.

Video demo

60-second narrated demo recorded at 31a84b8 (the head before the CI-only change): the removed APIs gone, the test suite, the identical JAR passing on Spring AI 1.1.8 and 1.1.0, the local Ollama tool agent on sync and stream, and the passing CI run. The video is private to Future AGI and attached to Linear issue TH-7477 with its transcript.

nik13 added 6 commits October 3, 2026 20:45
The wrapper was compiled against Spring AI 1.0.0-M4 and called APIs removed
before GA (Message.getContent, Usage.getGenerationTokens), so every current
release threw NoSuchMethodError on the first chat call.

- Compile against Spring AI 1.1.8 / Spring Boot 3.5.15; depend on spring-ai-model
- Read text with getText() and completion usage with getCompletionTokens()
- Defer stream spans and delegate calls until subscription; one span per
  subscription, ended exactly once on complete, error or cancel
- Record token counts only when the provider reports them; treat EmptyUsage
  zeros as absent and preserve measured zero
- Delegate every embedding overload directly, including the 1.1.8-only
  getEmbeddingContent accessor (no span)
- Add SpringAITracingContext.withContext for explicit parent/attribute binding
- Bound stream capture and keep the four privacy switches independent
- Prove it with a deterministic suite plus a same-binary consumer on 1.1.8 and
  1.1.0, and a real local Ollama tool-agent (sync and stream)

TH-7477
The parent Surefire argLine pinned -javaagent to a ByteBuddy agent JAR at a
fixed path in the local repository. On a clean CI cache that path does not
exist, so the forked test JVM dies during initialization before running
anything ("Error occurred during initialization of VM"), failing
traceai-java-core with zero tests executed.

Java 17 runs the suite without a startup agent. Verified traceai-java-core
(13 tests) green with an empty local repository.

TH-7477
The compatibility consumer POMs pointed at /candidate-jars, which only exists
inside the local evidence runner's mount, so CI failed dependency resolution
after the suite itself passed. Resolve from <repo>/candidate-jars via the
module directory instead, and recompile the consumers in CI.

TH-7477
The example lives outside the reactor and depends on
traceai-spring-boot-starter:1.0.0, which is not published. CI resolved
everything else and then failed only on that dependency. Install the
just-built modules into the local repository first.

TH-7477
Review of b085ce0 found the approved AC-12 starter checks were never written,
so a disabled context, a custom tracer bean and the uninitialized wrapper were
unverified. Add them, each in its own class because TraceAI's global
registration cannot be reset and the starter forks a fresh JVM per class.

Also close the three non-blocking notes: record a real EmptyUsage as absent
while keeping a measured zero, reject a null upstream subscription and end the
span even when recording the error throws, and skip null texts in the
embedding preview.

TH-7477
The deterministic step only ran traceai-spring-ai (and its dependencies),
so the four starter auto-configuration test classes added in 31a84b8 never
ran in CI; the starter was only packaged and installed with -DskipTests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nik13
nik13 requested a review from NVJKKartik October 7, 2026 09:23
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.

1 participant