Skip to content

Commit 0a9f989

Browse files
author
Test
committed
feat(observer): track LLM token usage in execution summary
Added TokenUsage struct to ChatResponse and populated from API response. Editor now emits LLMRequestEvent with timing and token counts. Summary now shows: LLM USAGE ---------------------------------------- API Calls: 2 Tokens In: 3.3K Tokens Out: 290 Total Tokens: 3.6K Est. Cost: $0.0036 Changes: - llm/provider.go: Added TokenUsage struct to ChatResponse - llm/chat_completion.go: Parse usage from API response - agents/editor.go: Emit LLMRequestEvent after each Chat call
1 parent 7bec5f9 commit 0a9f989

File tree

3 files changed

+37
-2
lines changed

3 files changed

+37
-2
lines changed

internal/agents/editor.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"strings"
9+
"time"
910

1011
"gptcode/internal/llm"
1112
"gptcode/internal/observability"
@@ -254,16 +255,29 @@ func (e *EditorAgent) Execute(ctx context.Context, history []llm.ChatMessage, st
254255
// Set to 10 to allow complex tasks: 3-4 discovery calls + 2-3 reads + 2-3 writes
255256
maxToolChainDepth := 10
256257
for iteration := 0; iteration < maxToolChainDepth; iteration++ {
258+
llmStart := time.Now()
257259
resp, err := e.provider.Chat(ctx, llm.ChatRequest{
258260
SystemPrompt: editorPrompt,
259261
Messages: messages,
260262
Tools: toolDefs,
261263
Model: e.model,
262264
})
265+
llmDuration := time.Since(llmStart)
263266
if err != nil {
264267
return "", nil, err
265268
}
266269

270+
// Emit LLM request event to observer
271+
if e.observer != nil && resp.TokenUsage != nil {
272+
e.observer.Emit(&observability.LLMRequestEvent{
273+
BaseEvent: observability.BaseEvent{Time: time.Now()},
274+
Model: e.model,
275+
TokensIn: resp.TokenUsage.PromptTokens,
276+
TokensOut: resp.TokenUsage.CompletionTokens,
277+
Duration: llmDuration,
278+
})
279+
}
280+
267281
if os.Getenv("GPTCODE_DEBUG") == "1" {
268282
fmt.Fprintf(os.Stderr, "[EDITOR] Response text length: %d\n", len(resp.Text))
269283
fmt.Fprintf(os.Stderr, "[EDITOR] Tool calls: %d\n", len(resp.ToolCalls))

internal/llm/chat_completion.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ type chatCompletionResponse struct {
113113
ToolCalls []ToolCall `json:"tool_calls"`
114114
} `json:"message"`
115115
} `json:"choices"`
116+
Usage *struct {
117+
PromptTokens int `json:"prompt_tokens"`
118+
CompletionTokens int `json:"completion_tokens"`
119+
TotalTokens int `json:"total_tokens"`
120+
} `json:"usage"`
116121
Error *struct {
117122
Message string `json:"message"`
118123
} `json:"error"`
@@ -363,5 +368,14 @@ func (c *ChatCompletionProvider) Chat(ctx context.Context, req ChatRequest) (*Ch
363368
}
364369
}
365370

371+
// Populate token usage if available
372+
if apiResp.Usage != nil {
373+
response.TokenUsage = &TokenUsage{
374+
PromptTokens: apiResp.Usage.PromptTokens,
375+
CompletionTokens: apiResp.Usage.CompletionTokens,
376+
TotalTokens: apiResp.Usage.TotalTokens,
377+
}
378+
}
379+
366380
return response, nil
367381
}

internal/llm/provider.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ type ChatMessage struct {
2424
}
2525

2626
type ChatResponse struct {
27-
Text string
28-
ToolCalls []ChatToolCall
27+
Text string
28+
ToolCalls []ChatToolCall
29+
TokenUsage *TokenUsage
30+
}
31+
32+
type TokenUsage struct {
33+
PromptTokens int
34+
CompletionTokens int
35+
TotalTokens int
2936
}
3037

3138
type ChatToolCall struct {

0 commit comments

Comments
 (0)