Replies: 3 comments 8 replies
-
|
Based on the available documentation, the Spring AI integration with Langfuse does not currently support directly linking prompts to generation observations through the OpenTelemetry instrumentation(1). The Spring AI documentation shows how to set For other SDKs, prompt linking is typically done by passing a prompt object when creating the generation observation. For example, in the Python SDK, you would use The issue you're experiencing where the generation observation disappears when adding the prompt attributes suggests that the attributes you're adding may be interfering with the observation creation process. The Spring AI integration uses specific attribute patterns that the Potential workarounds:
The Spring AI integration documentation focuses primarily on basic tracing setup and user/session tracking, but doesn't cover prompt management integration(1). 📚 Sources: Have another question? Just tag @inkeep. |
Beta Was this translation helpful? Give feedback.
-
|
@marcjaner, Hello! Can you please tell if you have managed to find a solution? |
Beta Was this translation helpful? Give feedback.
-
|
Looks like the problem in the type of My reproducible example payload.json (original JSON was taken during the debug of the real spring ai+kotlin app): {
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "test-service"
}
},
{
"key": "telemetry.sdk.language",
"value": {
"stringValue": "java"
}
},
{
"key": "telemetry.sdk.name",
"value": {
"stringValue": "opentelemetry"
}
},
{
"key": "telemetry.sdk.version",
"value": {
"stringValue": "1.38.0"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "org.springframework.boot",
"version": "3.5.7",
"attributes": []
},
"spans": [
{
"traceId": "efc40c98d40d3ba167b6ead920f3e020",
"spanId": "73f0082eae157c08",
"name": "test-parent-span",
"kind": 1,
"startTimeUnixNano": "1763734711496069000",
"endTimeUnixNano": "1763734719607932739",
"attributes": [
{
"key": "langfuse.environment",
"value": {
"stringValue": "test_environment"
}
}
],
"events": [],
"links": [],
"status": {
"code": 1
},
"flags": 257
}
]
}
]
},
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "test-service"
}
},
{
"key": "telemetry.sdk.language",
"value": {
"stringValue": "java"
}
},
{
"key": "telemetry.sdk.name",
"value": {
"stringValue": "opentelemetry"
}
},
{
"key": "telemetry.sdk.version",
"value": {
"stringValue": "1.38.0"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "org.springframework.boot",
"version": "3.5.7",
"attributes": []
},
"spans": [
{
"traceId": "efc40c98d40d3ba167b6ead920f3e020",
"spanId": "54124ffa1eb6f6e9",
"parentSpanId": "73f0082eae157c08",
"name": "chat gemini-2.5-flash",
"kind": 1,
"startTimeUnixNano": "1763734713931838741",
"endTimeUnixNano": "1763734719497912672",
"attributes": [
{
"key": "gen_ai.completion",
"value": {
"stringValue": "Hello"
}
},
{
"key": "gen_ai.operation.name",
"value": {
"stringValue": "chat"
}
},
{
"key": "gen_ai.prompt",
"value": {
"stringValue": "Hello"
}
},
{
"key": "gen_ai.request.model",
"value": {
"stringValue": "gemini-2.5-flash"
}
},
{
"key": "gen_ai.request.temperature",
"value": {
"stringValue": "0.7"
}
},
{
"key": "gen_ai.request.top_p",
"value": {
"stringValue": "1.0"
}
},
{
"key": "gen_ai.response.finish_reasons",
"value": {
"stringValue": "[\"STOP\"]"
}
},
{
"key": "gen_ai.response.model",
"value": {
"stringValue": "gemini-2.5-flash"
}
},
{
"key": "gen_ai.system",
"value": {
"stringValue": "google_genai"
}
},
{
"key": "gen_ai.usage.input_tokens",
"value": {
"stringValue": "1"
}
},
{
"key": "gen_ai.usage.output_tokens",
"value": {
"stringValue": "1"
}
},
{
"key": "gen_ai.usage.total_tokens",
"value": {
"stringValue": "2"
}
},
{
"key": "langfuse.environment",
"value": {
"stringValue": "test_environment"
}
},
{
"key": "langfuse.observation.prompt.name",
"value": {
"stringValue": "ReplaceToYourPromptName"
}
},
{
"key": "langfuse.observation.prompt.version",
"value": {
"stringValue": "1"
}
},
{
"key": "spring.ai.model.request.tool.names",
"value": {
"stringValue": "[\"testTool1\", \"testTool2\", \"testTool3\"]"
}
}
],
"events": [],
"links": [],
"status": {
"code": 1
},
"flags": 257
}
]
}
]
}
]
}post-payload.py: import base64
import json
import os
from time import time_ns
from typing import Tuple
import dotenv
import requests
from google.protobuf.json_format import ParseDict
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
ExportTraceServiceRequest,
)
def load_auth() -> Tuple[str, str]:
dotenv.load_dotenv()
public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
secret_key = os.getenv("LANGFUSE_SECRET_KEY")
if not public_key or not secret_key:
raise SystemExit("Missing LANGFUSE_PUBLIC_KEY or LANGFUSE_SECRET_KEY")
return public_key, secret_key
def main():
base_url = os.getenv("LANGFUSE_BASE_URL", "https://cloud.langfuse.com").rstrip("/")
endpoint = f"{base_url}/api/public/otel/v1/traces"
public_key, secret_key = load_auth()
with open("payload.json") as f:
data = json.load(f)
# Shift all span timestamps to now so they appear in the current UI window.
now = time_ns()
first_span = data["resourceSpans"][0]["scopeSpans"][0]["spans"][0]
delta = now - int(first_span["startTimeUnixNano"])
for resource_span in data.get("resourceSpans", []):
for scope_span in resource_span.get("scopeSpans", []):
for span in scope_span.get("spans", []):
span["startTimeUnixNano"] = str(int(span["startTimeUnixNano"]) + delta)
span["endTimeUnixNano"] = str(int(span["endTimeUnixNano"]) + delta)
req = ExportTraceServiceRequest()
ParseDict(data, req) # fill the protobuf message
payload_bytes = req.SerializeToString()
auth = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode()
resp = requests.post(
endpoint,
headers={
"Content-Type": "application/x-protobuf",
"Accept": "application/json",
"Authorization": f"Basic {auth}",
},
data=payload_bytes,
timeout=10,
)
print("Status:", resp.status_code)
try:
print("Response:", resp.json())
except Exception:
print("Response text:", resp.text)
resp.raise_for_status()
print("Sent protobuf payload to", endpoint)
if __name__ == "__main__":
main()Steps:
I think this problem could be fixed both sides (micrometer or langfuse). @langfuse, @hassiebp, @marliessophie what do you think? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
For a AI agents project that I'm developing in Spring AI (+ Kotlin) we're using Langfuse as our observability tool. One of the goals behind this decision is to be able to measure the performance of each prompt. However, right now we're not able to do so because our
generationobservations are not being linked to a prompt.Some context about the setup and solutions we've tried: The project involves multiple agents so for convenience we've defined a
TracingAspectthat applies to the function in charge of generating the completion of each agent. This aspect 1 handles the creation of a parent trace.Creating this parent trace is necessary in order to define the
userId,sessionIdas well astags. This also allows all the observations from a user <-> agent interaction to be grouped under a parent trace. For context, this would be a simplified example of our aspect:The first approach we've tried is to link the trace to the prompt in this aspect by doing:
However this will not work because
promptmetadata can only be added togenerationobservations 2.The next approach we tried was extending the
ObservationFilterthat we added when configuring Langfuse in our Spring Boot project according to the documentation3 (adapted to kotlin):We updated the above
ObservationFilterto have something like this:However, despite in our logs being able to see the
[completion-filter] Linking ...log, the result in Langfuse is not the expected one: not only the observation is not linked to the prompt, but thegenerationobservation is no longer recorded by Langfuse.Our understanding is that the
promptdetails should be added to the trace/observation resulting from theChatClient.call()method, which is the one that is of typegeneration, but we're unable to get it to work.Has anyone faced a similar issue before? Would be great to get an example of this working. Thanks
Footnotes
Aspect Oriented Programming in SpringBoot ↩
Get started with prompt management ↩
Integrating Langfuse with Spring AI ↩
Beta Was this translation helpful? Give feedback.
All reactions