Skip to content

Commit 69dc3a2

Browse files
JqRrtHearyShen
authored andcommitted
[feat] otel compatible with new structures (#420)
* otel add status_code * otel add gen_ai.input.messages and gen_ai.output.messages * otel runtime support get from attribute * otel add opentelemetry tool input and output
1 parent b2c952d commit 69dc3a2

4 files changed

Lines changed: 52 additions & 17 deletions

File tree

backend/modules/observability/lib/otel/consts.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
otelAttributeInput = "cozeloop.input"
2222
otelAttributeOutput = "cozeloop.output"
2323
otelAttributeLogID = "cozeloop.logid"
24+
otelAttributeStatusCode = "cozeloop.status_code"
2425

2526
apmInput = "gen_ai.input"
2627
apmOutput = "gen_ai.output"
@@ -34,6 +35,9 @@ const (
3435
otelAttributePromptKey = "cozeloop.prompt_key"
3536
otelAttributePromptVersion = "cozeloop.prompt_version"
3637
otelAttributePromptProvider = "cozeloop.prompt_provider"
38+
39+
// system
40+
otelAttributeSystemRuntime = "cozeloop.system_tag_runtime"
3741
)
3842

3943
// openinference attribute key
@@ -96,7 +100,10 @@ const (
96100

97101
// otel attribute key
98102
const (
99-
otelAttributeModelInputTools = "gen_ai.tool.definitions"
103+
otelAttributeModelInputTools = "gen_ai.tool.definitions" // model tools
104+
105+
otelAttributeToolInput = "gen_ai.tool.call.arguments" // tool input
106+
otelAttributeToolOutput = "gen_ai.tool.call.result"
100107
)
101108

102109
var otelMessageEventNameMap = []string{

backend/modules/observability/lib/otel/open_inference/openinference.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ func convertModelMsg(msg map[string]interface{}) map[string]interface{} {
6767
modelMsg["reasoning_content"] = c
6868
}
6969

70-
// contents
70+
// contents or parts
7171
var contents []interface{}
72-
if c, ok := msg["contents"].([]interface{}); ok && len(c) > 0 {
73-
contents = c
74-
} else if c, ok := msg["content"].([]interface{}); ok && len(c) > 0 {
75-
contents = c
72+
partsKey := []string{"contents", "content", "parts"}
73+
for _, key := range partsKey {
74+
if parts, ok := msg[key].([]interface{}); ok && len(parts) > 0 {
75+
contents = parts
76+
break
77+
}
7678
}
7779
if len(contents) > 0 {
7880
parts := make([]interface{}, 0, len(contents))
@@ -84,6 +86,9 @@ func convertModelMsg(msg map[string]interface{}) map[string]interface{} {
8486
}
8587
typ, _ := mcContent["type"]
8688
text, _ := mcContent["text"]
89+
if text == nil {
90+
text, _ = mcContent["content"]
91+
}
8792
image, _ := mcContent["image_url"]
8893
part := map[string]interface{}{}
8994
switch typ {

backend/modules/observability/lib/otel/otel_convert.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525

2626
// FieldConfMap Field configuration, supports configuring data sources and export methods for fields, currently supports attribute, event, is_tag, data_type
2727
// Among them, attributes and events support configuring multiple, while tags and datatypes only support configuring one.
28+
// For AttributeKey and AttributeKeyPrefix, the field at the head has higher priority, and result will be returned once a match is found.
29+
// For Events, the fields have same priority, and result will be fixed by all fields.
2830
// Other types of configurations need to be manually processed in the code.
2931
var (
3032
FieldConfMap = map[string]FieldConf{
@@ -68,6 +70,11 @@ var (
6870
IsTag: true,
6971
DataType: dataTypeString,
7072
},
73+
"status_code": {
74+
AttributeKey: []string{otelAttributeStatusCode},
75+
IsTag: false,
76+
DataType: dataTypeInt64,
77+
},
7178
"psm": {
7279
AttributeKey: []string{"service.name"},
7380
IsTag: false,
@@ -86,11 +93,13 @@ var (
8693
springAIAttributeToolInput,
8794
otelAttributeInput,
8895
apmInput,
96+
otelAttributeToolInput,
8997
},
9098
AttributeKeyPrefix: []string{
9199
openInferenceAttributeModelInputMessages,
92100
openInferenceAttributeToolInput,
93101
string(semconv1_27_0.GenAIPromptKey),
102+
string(semconv.GenAIInputMessagesKey),
94103
},
95104
EventName: []string{otelEventModelSystemMessage, otelEventModelUserMessage, otelEventModelToolMessage, otelEventModelAssistantMessage, otelSpringAIEventModelPrompt},
96105
DataType: dataTypeString,
@@ -113,10 +122,12 @@ var (
113122
springAIAttributeToolOutput,
114123
otelAttributeOutput,
115124
apmOutput,
125+
otelAttributeToolOutput,
116126
},
117127
AttributeKeyPrefix: []string{
118128
openInferenceAttributeModelOutputMessages,
119129
string(semconv1_27_0.GenAICompletionKey),
130+
string(semconv.GenAIOutputMessagesKey),
120131
},
121132
EventName: []string{otelEventModelChoice, otelSpringAIEventModelCompletion},
122133
DataType: dataTypeString,
@@ -343,6 +354,11 @@ func OtelSpanConvertToSendSpan(ctx context.Context, spaceID string, resourceScop
343354
}
344355
if conf.IsTag {
345356
tagsLong[fieldKey] = value
357+
} else {
358+
switch fieldKey {
359+
case "status_code":
360+
statusCode = int32(value)
361+
}
346362
}
347363
case dataTypeBool:
348364
value, ok := srcValue.(bool)
@@ -384,7 +400,7 @@ func OtelSpanConvertToSendSpan(ctx context.Context, spaceID string, resourceScop
384400
// set attributes
385401
calOtherAttribute(ctx, span, tagsString, tagsLong, tagsDouble, tagsBool)
386402
// set runtime
387-
calRuntime(systemTagsString, resourceScopeSpan)
403+
calRuntime(systemTagsString, tagsString, resourceScopeSpan)
388404

389405
result := &LoopSpan{
390406
StartTime: startTimeUnixNanoInt64 / 1000,
@@ -530,12 +546,19 @@ func calOtherAttribute(ctx context.Context, span *Span, tagsString map[string]st
530546
}
531547
}
532548

533-
func calRuntime(systemTagsString map[string]string, resourceScopeSpan *ResourceScopeSpan) {
534-
systemTagsString[tracespec.Runtime_] = getRuntime(resourceScopeSpan)
549+
func calRuntime(systemTagsString map[string]string, tagsString map[string]string, resourceScopeSpan *ResourceScopeSpan) {
550+
systemTagsString[tracespec.Runtime_] = getRuntime(tagsString, resourceScopeSpan)
535551
}
536552

537-
func getRuntime(resourceScopeSpan *ResourceScopeSpan) string {
538-
runtime := processRuntime(resourceScopeSpan)
553+
func getRuntime(tagsString map[string]string, resourceScopeSpan *ResourceScopeSpan) string {
554+
if len(tagsString) > 0 {
555+
if runtime, ok := tagsString[otelAttributeSystemRuntime]; ok && len(runtime) > 0 {
556+
delete(tagsString, otelAttributeSystemRuntime)
557+
return runtime
558+
}
559+
}
560+
561+
runtime := processRuntimeByScope(resourceScopeSpan)
539562
marshalString, err := sonic.MarshalString(runtime)
540563
if err != nil {
541564
return "" // unexpected
@@ -544,7 +567,7 @@ func getRuntime(resourceScopeSpan *ResourceScopeSpan) string {
544567
return marshalString
545568
}
546569

547-
func processRuntime(resourceScopeSpan *ResourceScopeSpan) *tracespec.Runtime {
570+
func processRuntimeByScope(resourceScopeSpan *ResourceScopeSpan) *tracespec.Runtime {
548571
res := &tracespec.Runtime{
549572
Library: tracespec.VLibOpentelemetry,
550573
Scene: "",
@@ -667,7 +690,7 @@ func processAttributePrefix(ctx context.Context, fieldKey string, conf FieldConf
667690
}
668691
}
669692
}
670-
case openInferenceAttributeModelInputMessages: // openInference(or litellm) input message
693+
case openInferenceAttributeModelInputMessages, string(semconv.GenAIInputMessagesKey): // openInference(or litellm) or openTelemetry input message
671694
srcInput, err := open_inference.ConvertToModelInput(srcAttrAggrRes)
672695
if err != nil {
673696
continue
@@ -686,7 +709,7 @@ func processAttributePrefix(ctx context.Context, fieldKey string, conf FieldConf
686709
continue
687710
}
688711
}
689-
case openInferenceAttributeModelOutputMessages: // openInference output message
712+
case openInferenceAttributeModelOutputMessages, string(semconv.GenAIOutputMessagesKey): // openInference(or litellm) or openTelemetry output message
690713
resObject, err := open_inference.ConvertToModelOutput(srcAttrAggrRes)
691714
if err == nil {
692715
toBeMarshalObject = resObject

backend/modules/observability/lib/otel/otel_convert_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,7 @@ func TestProcessRuntime(t *testing.T) {
10361036
panic(r) // Re-panic if unexpected
10371037
}
10381038
}()
1039-
result := processRuntime(tt.resourceScopeSpan)
1039+
result := processRuntimeByScope(tt.resourceScopeSpan)
10401040
tt.validate(t, result)
10411041
})
10421042
}
@@ -1093,7 +1093,7 @@ func TestGetRuntime(t *testing.T) {
10931093
panic(r) // Re-panic if unexpected
10941094
}
10951095
}()
1096-
result := getRuntime(tt.resourceScopeSpan)
1096+
result := getRuntime(nil, tt.resourceScopeSpan)
10971097
tt.validate(t, result)
10981098
})
10991099
}
@@ -1149,7 +1149,7 @@ func TestCalRuntime(t *testing.T) {
11491149

11501150
for _, tt := range tests {
11511151
t.Run(tt.name, func(t *testing.T) {
1152-
calRuntime(tt.systemTagsString, tt.resourceScopeSpan)
1152+
calRuntime(tt.systemTagsString, nil, tt.resourceScopeSpan)
11531153
tt.validate(t, tt.systemTagsString)
11541154
})
11551155
}

0 commit comments

Comments
 (0)