chore: sync upstream v0.3.6 changes
This commit is contained in:
1 parent
9da521ee4e
commit
22132a4d21
34 files changed
+897
-127
No files matched your search
+43
-4
@@ -39,10 +39,10 @@ func TaskIDFrom(ctx context.Context) string {
|
||||
|
||||
// Recorder wraps an llm.Provider and records every completion call.
|
||||
type Recorder struct {
|
||||
inner llm.Provider
|
||||
pg *db.DB
|
||||
model string // model name (from config, not in CompletionRequest)
|
||||
prof string // LLM profile name (from llm_profiles)
|
||||
inner llm.Provider
|
||||
pg *db.DB
|
||||
model string // model name (from config, not in CompletionRequest)
|
||||
prof string // LLM profile name (from llm_profiles)
|
||||
// thinkingType / reasoningEffort 是配置级思考参数(思考开关 / 思考强度)。它们在
|
||||
// norma 的 buildBody() 里从 provider 配置注入真正的 HTTP body,不出现在
|
||||
// CompletionRequest 上,故 Recorder 需在此单独带一份,序列化时写进录制。
|
||||
@@ -179,6 +179,45 @@ func (r *Recorder) Stream(ctx context.Context, req llm.CompletionRequest) iter.S
|
||||
}
|
||||
}
|
||||
|
||||
// Complete implements the atomic completion path while preserving the same
|
||||
// usage metering and optional body recording semantics as Stream.
|
||||
func (r *Recorder) Complete(ctx context.Context, req llm.CompletionRequest) (llm.Message, string, llm.Usage, error) {
|
||||
recordBodies := r.enabled == nil || r.enabled()
|
||||
start := time.Now()
|
||||
session := transcript.SessionIDFrom(ctx)
|
||||
parsedID, worker := parseSession(session)
|
||||
expID := db.ParseExpID(parsedID)
|
||||
taskID := TaskIDFrom(ctx)
|
||||
if taskID == "" {
|
||||
taskID = parsedID
|
||||
}
|
||||
|
||||
reqBody := ""
|
||||
if recordBodies {
|
||||
reqBody = r.serializeRequest(req)
|
||||
}
|
||||
|
||||
msg, stopReason, usage, err := r.inner.Complete(ctx, req)
|
||||
status := "ok"
|
||||
if err != nil {
|
||||
status = "error"
|
||||
}
|
||||
r.recordUsage(taskID, expID, worker, usage, int(time.Since(start).Milliseconds()), status)
|
||||
if recordBodies {
|
||||
var textBuf, thinkingBuf strings.Builder
|
||||
for _, block := range msg.Content {
|
||||
switch block.Type {
|
||||
case llm.BlockText:
|
||||
textBuf.WriteString(block.Text)
|
||||
case llm.BlockThinking:
|
||||
thinkingBuf.WriteString(block.Thinking)
|
||||
}
|
||||
}
|
||||
r.record(req, session, taskID, worker, reqBody, start, textBuf.String(), thinkingBuf.String(), usage, stopReason, err)
|
||||
}
|
||||
return msg, stopReason, usage, err
|
||||
}
|
||||
|
||||
// recordUsage appends one lightweight metering row to llm_usage (no bodies). Skips
|
||||
// zero-token calls with no model, which carry nothing worth metering.
|
||||
func (r *Recorder) recordUsage(taskID string, expID int64, worker string, usage llm.Usage, latencyMs int, status string) {
|
||||
|
||||
@@ -2,9 +2,28 @@ package llmrec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"testing"
|
||||
|
||||
"github.com/Autumn-27/norma/llm"
|
||||
)
|
||||
|
||||
type completeProvider struct{}
|
||||
|
||||
func (completeProvider) Stream(context.Context, llm.CompletionRequest) iter.Seq2[llm.StreamEvent, error] {
|
||||
return func(func(llm.StreamEvent, error) bool) {}
|
||||
}
|
||||
|
||||
func (completeProvider) Complete(context.Context, llm.CompletionRequest) (llm.Message, string, llm.Usage, error) {
|
||||
return llm.Message{
|
||||
Role: llm.RoleAssistant,
|
||||
Content: []llm.ContentBlock{
|
||||
{Type: llm.BlockThinking, Thinking: "reasoning"},
|
||||
llm.TextBlock("answer"),
|
||||
},
|
||||
}, "stop", llm.Usage{InputTokens: 7, OutputTokens: 3}, nil
|
||||
}
|
||||
|
||||
func TestTaskIDContextUsesExplicitRegistryID(t *testing.T) {
|
||||
ctx := WithTaskID(context.Background(), " 42 ")
|
||||
if got := TaskIDFrom(ctx); got != "42" {
|
||||
@@ -27,3 +46,20 @@ func TestParseSessionFallbackIsExplorationScoped(t *testing.T) {
|
||||
t.Fatalf("unexpected non-session parse: (%q,%q)", taskID, worker)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteForwardsAtomicResponse(t *testing.T) {
|
||||
recorder := Wrap(completeProvider{}, nil, "model", "profile", "", "", func() bool { return false })
|
||||
msg, stopReason, usage, err := recorder.Complete(context.Background(), llm.CompletionRequest{})
|
||||
if err != nil {
|
||||
t.Fatalf("Complete() error: %v", err)
|
||||
}
|
||||
if got := msg.Text(); got != "answer" {
|
||||
t.Fatalf("Complete() text=%q want answer", got)
|
||||
}
|
||||
if stopReason != "stop" {
|
||||
t.Fatalf("Complete() stop reason=%q want stop", stopReason)
|
||||
}
|
||||
if usage.InputTokens != 7 || usage.OutputTokens != 3 {
|
||||
t.Fatalf("Complete() usage=%+v", usage)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user