Skip to content

Commit 38fe1e2

Browse files
committed
working on some telemetry
1 parent db94d56 commit 38fe1e2

3 files changed

Lines changed: 104 additions & 4 deletions

File tree

pkg/aiusechat/uctypes/usechat-types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,21 @@ type AIUsage struct {
187187
OutputTokens int `json:"outputtokens,omitempty"`
188188
}
189189

190+
type AIMetrics struct {
191+
Usage AIUsage `json:"usage"`
192+
RequestCount int `json:"requestcount"`
193+
ToolUseCount int `json:"toolusecount"`
194+
PremiumReqCount int `json:"premiumreqcount"`
195+
ProxyReqCount int `json:"proxyreqcount"`
196+
HadError bool `json:"haderror"`
197+
ImageCount int `json:"imagecount"`
198+
PDFCount int `json:"pdfcount"`
199+
TextDocCount int `json:"textdoccount"`
200+
TextLen int `json:"textlen"`
201+
FirstByteLatency int `json:"firstbytelatency"` // ms
202+
RequestDuration int `json:"requestduration"` // ms
203+
}
204+
190205
// GenAIMessage interface for messages stored in conversations
191206
// All messages must have a unique identifier for idempotency checks
192207
type GenAIMessage interface {

pkg/aiusechat/usechat.go

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
2020
"github.com/wavetermdev/waveterm/pkg/aiusechat/openai"
2121
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
22+
"github.com/wavetermdev/waveterm/pkg/telemetry"
23+
"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
2224
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
2325
"github.com/wavetermdev/waveterm/pkg/waveobj"
2426
"github.com/wavetermdev/waveterm/pkg/web/sse"
@@ -232,8 +234,14 @@ func processToolResults(stopReason *uctypes.WaveStopReason, chatOpts uctypes.Wav
232234
}
233235
}
234236

235-
func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctypes.WaveChatOpts) error {
237+
func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctypes.WaveChatOpts) (*uctypes.AIMetrics, error) {
236238
log.Printf("RunAIChat\n")
239+
metrics := &uctypes.AIMetrics{
240+
Usage: uctypes.AIUsage{
241+
APIType: chatOpts.Config.APIType,
242+
Model: chatOpts.Config.Model,
243+
},
244+
}
237245
firstStep := true
238246
var cont *uctypes.WaveContinueResponse
239247
for {
@@ -245,14 +253,28 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
245253
}
246254
}
247255
stopReason, rtnMessage, err := runAIChatStep(ctx, sseHandler, chatOpts, cont)
256+
metrics.RequestCount++
257+
if chatOpts.Config.IsPremiumModel() {
258+
metrics.PremiumReqCount++
259+
}
260+
if chatOpts.Config.IsWaveProxy() {
261+
metrics.ProxyReqCount++
262+
}
248263
if len(rtnMessage) > 0 {
249264
usage := getUsage(rtnMessage)
250265
log.Printf("usage: input=%d output=%d\n", usage.InputTokens, usage.OutputTokens)
266+
metrics.Usage.InputTokens += usage.InputTokens
267+
metrics.Usage.OutputTokens += usage.OutputTokens
268+
if usage.Model != "" && metrics.Usage.Model != usage.Model {
269+
metrics.Usage.Model = "mixed"
270+
}
251271
}
252272
if firstStep && err != nil {
253-
return fmt.Errorf("failed to stream %s chat: %w", chatOpts.Config.APIType, err)
273+
metrics.HadError = true
274+
return metrics, fmt.Errorf("failed to stream %s chat: %w", chatOpts.Config.APIType, err)
254275
}
255276
if err != nil {
277+
metrics.HadError = true
256278
_ = sseHandler.AiMsgError(err.Error())
257279
_ = sseHandler.AiMsgFinish("", nil)
258280
break
@@ -274,6 +296,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
274296
continue
275297
}
276298
if stopReason != nil && stopReason.Kind == uctypes.StopKindToolUse {
299+
metrics.ToolUseCount += len(stopReason.ToolCalls)
277300
processToolResults(stopReason, chatOpts, sseHandler)
278301

279302
var messageID string
@@ -290,7 +313,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
290313
}
291314
break
292315
}
293-
return nil
316+
return metrics, nil
294317
}
295318

296319
// ResolveToolCall resolves a single tool call and returns an AIToolResult
@@ -359,6 +382,7 @@ func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpt
359382

360383
func WaveAIPostMessageWrap(ctx context.Context, sseHandler *sse.SSEHandlerCh, message *uctypes.AIMessage, chatOpts uctypes.WaveChatOpts) error {
361384
log.Printf("WaveAIPostMessageWrap\n")
385+
startTime := time.Now()
362386

363387
// Convert AIMessage to Anthropic chat message
364388
var convertedMessage uctypes.GenAIMessage
@@ -383,7 +407,51 @@ func WaveAIPostMessageWrap(ctx context.Context, sseHandler *sse.SSEHandlerCh, me
383407
return fmt.Errorf("failed to store message: %w", err)
384408
}
385409

386-
return RunAIChat(ctx, sseHandler, chatOpts)
410+
metrics, err := RunAIChat(ctx, sseHandler, chatOpts)
411+
if metrics != nil {
412+
metrics.RequestDuration = int(time.Since(startTime).Milliseconds())
413+
for _, part := range message.Parts {
414+
if part.Type == uctypes.AIMessagePartTypeText {
415+
metrics.TextLen += len(part.Text)
416+
} else if part.Type == uctypes.AIMessagePartTypeFile {
417+
mimeType := strings.ToLower(part.MimeType)
418+
if strings.HasPrefix(mimeType, "image/") {
419+
metrics.ImageCount++
420+
} else if mimeType == "application/pdf" {
421+
metrics.PDFCount++
422+
} else {
423+
metrics.TextDocCount++
424+
}
425+
}
426+
}
427+
log.Printf("metrics: requests=%d tools=%d premium=%d proxy=%d images=%d pdfs=%d textdocs=%d textlen=%d duration=%dms error=%v\n",
428+
metrics.RequestCount, metrics.ToolUseCount, metrics.PremiumReqCount, metrics.ProxyReqCount,
429+
metrics.ImageCount, metrics.PDFCount, metrics.TextDocCount, metrics.TextLen, metrics.RequestDuration, metrics.HadError)
430+
431+
sendAIMetricsTelemetry(ctx, metrics)
432+
}
433+
return err
434+
}
435+
436+
func sendAIMetricsTelemetry(ctx context.Context, metrics *uctypes.AIMetrics) {
437+
event := telemetrydata.MakeTEvent("waveai:post", telemetrydata.TEventProps{
438+
WaveAIAPIType: metrics.Usage.APIType,
439+
WaveAIModel: metrics.Usage.Model,
440+
WaveAIInputTokens: metrics.Usage.InputTokens,
441+
WaveAIOutputTokens: metrics.Usage.OutputTokens,
442+
WaveAIRequestCount: metrics.RequestCount,
443+
WaveAIToolUseCount: metrics.ToolUseCount,
444+
WaveAIPremiumReq: metrics.PremiumReqCount,
445+
WaveAIProxyReq: metrics.ProxyReqCount,
446+
WaveAIHadError: metrics.HadError,
447+
WaveAIImageCount: metrics.ImageCount,
448+
WaveAIPDFCount: metrics.PDFCount,
449+
WaveAITextDocCount: metrics.TextDocCount,
450+
WaveAITextLen: metrics.TextLen,
451+
WaveAIFirstByteMs: metrics.FirstByteLatency,
452+
WaveAIRequestDurMs: metrics.RequestDuration,
453+
})
454+
_ = telemetry.RecordTEvent(ctx, event)
387455
}
388456

389457
// PostMessageRequest represents the request body for posting a message

pkg/telemetry/telemetrydata/telemetrydata.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var ValidEventNames = map[string]bool{
2929
"conn:connect": true,
3030
"conn:connecterror": true,
3131
"waveai:enabletelemetry": true,
32+
"waveai:post": true,
3233
}
3334

3435
type TEvent struct {
@@ -97,6 +98,22 @@ type TEventProps struct {
9798
CountWSLConn int `json:"count:wslconn,omitempty"`
9899
CountViews map[string]int `json:"count:views,omitempty"`
99100

101+
WaveAIAPIType string `json:"waveai:apitype,omitempty"`
102+
WaveAIModel string `json:"waveai:model,omitempty"`
103+
WaveAIInputTokens int `json:"waveai:inputtokens,omitempty"`
104+
WaveAIOutputTokens int `json:"waveai:outputtokens,omitempty"`
105+
WaveAIRequestCount int `json:"waveai:requestcount,omitempty"`
106+
WaveAIToolUseCount int `json:"waveai:toolusecount,omitempty"`
107+
WaveAIPremiumReq int `json:"waveai:premiumreq,omitempty"`
108+
WaveAIProxyReq int `json:"waveai:proxyreq,omitempty"`
109+
WaveAIHadError bool `json:"waveai:haderror,omitempty"`
110+
WaveAIImageCount int `json:"waveai:imagecount,omitempty"`
111+
WaveAIPDFCount int `json:"waveai:pdfcount,omitempty"`
112+
WaveAITextDocCount int `json:"waveai:textdoccount,omitempty"`
113+
WaveAITextLen int `json:"waveai:textlen,omitempty"`
114+
WaveAIFirstByteMs int `json:"waveai:firstbytems,omitempty"` // ms
115+
WaveAIRequestDurMs int `json:"waveai:requestdurms,omitempty"` // ms
116+
100117
UserSet *TEventUserProps `json:"$set,omitempty"`
101118
UserSetOnce *TEventUserProps `json:"$set_once,omitempty"`
102119
}

0 commit comments

Comments
 (0)