Skip to content

[bot] Spring AI tool/function execution produces no span — only the surrounding chat model call is traced #159

Description

@braintrust-bot

Summary

Both Spring AI instrumentation modules (springai_1_0_0, springai_2_0_0) only wrap OpenAiChatModel.Builder/AnthropicChatModel.Builder construction (BraintrustSpringAI.wrap()). Spring AI's own local tool-execution machinery — ToolCallingManager/DefaultToolCallingManager, the auto-registered ToolCallingAdvisor, and ToolCallback/MethodToolCallback/FunctionToolCallback — is never touched by either module. When a model response triggers a tool call, Spring AI resolves and invokes the corresponding Java method in-process, but that invocation produces no span: only the LLM call that requested the tool and the follow-up LLM call that consumes the result are traced, with the actual tool execution invisible in between.

This is distinct from the already-filed streaming-reassembly bugs (#78 Anthropic SSE tool_use/thinking blocks, #79 OpenAI SSE tool_calls delta-merging) — those are about the LLM span's own tool_calls output being malformed during streaming. This gap is about the absence of a separate span for the tool's actual execution, which is the "tool-calling during model execution" surface itself, comparable to langchain_1_8_0's TracingToolExecutor which does instrument LangChain4j's equivalent local tool execution.

What is missing

A full-repo search for ToolCalling, ToolCallback, and toolCall under both springai_1_0_0 and springai_2_0_0 returns zero matches. BraintrustSpringAI.java in both modules exposes exactly one entry point:

public static <T> T wrap(OpenTelemetry openTelemetry, T chatModelBuilder) { ... }

which only instruments chat model construction. Neither module registers any instrumentation for:

  • ToolCallingManager.executeToolCalls() — the method that actually resolves and invokes tool callbacks in response to a model's tool-call request
  • ToolCallingAdvisor — the ChatClient advisor auto-registered whenever tools are present, which drives the resolve/execute loop
  • ToolCallback.call() (and its MethodToolCallback/FunctionToolCallback implementations) — the actual per-tool invocation point

For comparison, langchain_1_8_0 instruments LangChain4j's analogous local-execution surface via TracingToolExecutor, producing a dedicated span per tool call — Spring AI has no equivalent.

Braintrust docs status

Upstream sources

Local files inspected

  • braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/BraintrustSpringAI.java — single wrap(OpenTelemetry, T chatModelBuilder) entry point (line 61); no tool-calling instrumentation
  • braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/auto/SpringAIOpenAIInstrumentationModule.java and SpringAIAnthropicInstrumentationModule.java — only instrument the two chat model builders
  • braintrust-sdk/instrumentation/springai_2_0_0/src/main/java/dev/braintrust/instrumentation/springai/v2_0_0/BraintrustSpringAI.java, SpringAIOpenAI.java, SpringAIAnthropic.java, and their auto/*InstrumentationModule.java — same scope as 1.0.0, no tool-calling instrumentation
  • Full-repo grep for ToolCalling, ToolCallback, toolCall scoped to both Spring AI modules — zero matches
  • braintrust-sdk/instrumentation/langchain_1_8_0/ — confirmed as the comparison point: this module does instrument LangChain4j's equivalent local tool-execution surface (TracingToolExecutor)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions