Repository navigation
Conversation
🤖 GitHub commentsJust comment with:
|
Elastic Docs Style Checker (Vale)Summary: 6 suggestions found 💡 Suggestions (6): Optional style improvements. Apply when helpful.
The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale. |
| .withSubtype("internal") | ||
| .withAction(operation) | ||
| .withOtelAttribute("gen_ai.operation.name", operation) | ||
| .withOtelAttribute("gen_ai.system", provider) |
There was a problem hiding this comment.
| .withOtelAttribute("gen_ai.system", provider) | |
| .withOtelAttribute("gen_ai.provider.name", provider) |
should be 'gen_ai.provider.name' to fit the current semconv
| return values == null || values.isEmpty() ? null : values.toString(); | ||
| } | ||
|
|
||
| private static String providerFor(Object model) { |
There was a problem hiding this comment.
Even if that may work short term, this is a very fragile heuristic, can we find something better and more reliable here ?
If we can't reliably provide it because it's abstracted by spring, it means it's probably better to not capture it, it might also be visible through the lower-level instrumentation like HTTP where equivalent information can be captured.
| target.append("null"); | ||
| return; | ||
| } | ||
| value = truncate(value, coreConfig.getLongFieldMaxLength()); |
There was a problem hiding this comment.
this makes truncating a single string value, however this is called more than once on a single field to reconstruct the whole json, so the effective value can be larger that the one configured.
| if (normalized.contains("postgres") || normalized.contains("pg_vector")) return "postgresql"; | ||
| if (normalized.contains("mongo")) return "mongodb"; | ||
| if (normalized.contains("elastic")) return "elasticsearch"; | ||
| if (normalized.contains("redis")) return "redis"; | ||
| if (normalized.contains("cassandra")) return "cassandra"; | ||
| if (normalized.contains("cosmos")) return "cosmosdb"; |
There was a problem hiding this comment.
similar to provider detection on class name, this looks quite brittle heuristic, if we are unable to capture things reliably it's better to avoid doing so.
| return value.substring(0, value.offsetByCodePoints(0, maxCodePoints)); | ||
| } | ||
|
|
||
| private static String serializeMessages(List<? extends Message> messages) { |
There was a problem hiding this comment.
Do we really have to reconstruct the whole json message here ? At the spring-ai level there should be no json involved at all, so here we re-create an artificial body of what the underlying implementation should send on the wire, is my understanding correct ?
| @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) | ||
| public static Object onEnter(@Advice.This ToolCallback callback, | ||
| @Advice.Argument(0) String toolInput) { | ||
| Span<?> span = SpringAiSpanHelper.startToolSpan(callback, toolInput); |
There was a problem hiding this comment.
If we ever get nested spans due to wrapping, using CallDepth might help to avoid nested calls here and only instrument the outer call.
| @Override | ||
| public ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() { | ||
| return super.getClassLoaderMatcher() | ||
| .and(co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass( |
There was a problem hiding this comment.
| .and(co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass( | |
| .and(CustomElementMatchers.classLoaderCanLoadClass( |
What does this PR do?
Adds experimental auto-instrumentation for Spring AI 2.x.
The new plugin creates spans for:
Chat spans capture GenAI attributes such as provider, model, request parameters, response metadata, finish reasons, and token usage. Streaming spans remain open until the response publisher terminates, so their duration represents the overall response time.
For streaming chat calls, the instrumentation also records time to first chunk as:
gen_ai.response.time_to_first_chunkgen_ai.client.inference.time_to_first_chunk, in secondsStreaming response chunks are aggregated so completed assistant content is captured correctly.
Content capture is disabled by default. The new dynamic
capture_ai_contentconfiguration option enables capture of:The option is documented as introduced in Java agent 1.58.0. Captured values are subject to
long_field_max_length. The documentation also notes the security and performance implications of capturing AI content.The agent supports changing this option dynamically. Configuring it through Kibana central configuration requires corresponding Kibana changes and will be supported in a future Kibana version.
The instrumentation is experimental and disabled by default. It can be enabled using:
elastic.apm.enable_experimental_instrumentations=trueThe shared class-loader matcher requires a Spring AI class introduced in 2.0, preventing unsupported Spring AI 1.x versions from being instrumented. Compatibility tests verify that Spring AI 1.0.8 and 1.1.7 are rejected and Spring AI 2.0.0 is accepted.
Testing performed:
Checklist