ocm: log think level

This commit is contained in:
世界
2026-03-28 02:00:27 +08:00
parent d9c298af1e
commit a87a2b0e2b
4 changed files with 151 additions and 40 deletions

View File

@@ -0,0 +1,62 @@
package ocm
import "encoding/json"
type requestLogMetadata struct {
Model string
ServiceTier string
ReasoningEffort string
}
type requestLogReasoning struct {
Effort string `json:"effort"`
}
type requestLogPayload struct {
Model string `json:"model"`
ServiceTier string `json:"service_tier"`
Reasoning *requestLogReasoning `json:"reasoning"`
ReasoningEffort string `json:"reasoning_effort"`
}
func (p requestLogPayload) metadata() requestLogMetadata {
metadata := requestLogMetadata{
Model: p.Model,
ServiceTier: p.ServiceTier,
}
if p.Reasoning != nil {
metadata.ReasoningEffort = p.Reasoning.Effort
}
if metadata.ReasoningEffort == "" {
metadata.ReasoningEffort = p.ReasoningEffort
}
return metadata
}
func parseRequestLogMetadata(data []byte) requestLogMetadata {
var payload requestLogPayload
if json.Unmarshal(data, &payload) != nil {
return requestLogMetadata{}
}
return payload.metadata()
}
func buildAssignedCredentialLogParts(credentialTag string, sessionID string, username string, metadata requestLogMetadata) []any {
logParts := []any{"assigned credential ", credentialTag}
if sessionID != "" {
logParts = append(logParts, " for session ", sessionID)
}
if username != "" {
logParts = append(logParts, " by user ", username)
}
if metadata.Model != "" {
logParts = append(logParts, ", model=", metadata.Model)
}
if metadata.ReasoningEffort != "" {
logParts = append(logParts, ", think=", metadata.ReasoningEffort)
}
if metadata.ServiceTier == "priority" {
logParts = append(logParts, ", fast")
}
return logParts
}

View File

@@ -0,0 +1,80 @@
package ocm
import (
"strings"
"testing"
F "github.com/sagernet/sing/common/format"
)
func TestParseRequestLogMetadata(t *testing.T) {
t.Parallel()
metadata := parseRequestLogMetadata([]byte(`{
"model":"gpt-5.4",
"service_tier":"priority",
"reasoning":{"effort":"xhigh"}
}`))
if metadata.Model != "gpt-5.4" {
t.Fatalf("expected model gpt-5.4, got %q", metadata.Model)
}
if metadata.ServiceTier != "priority" {
t.Fatalf("expected priority service tier, got %q", metadata.ServiceTier)
}
if metadata.ReasoningEffort != "xhigh" {
t.Fatalf("expected xhigh reasoning effort, got %q", metadata.ReasoningEffort)
}
}
func TestParseRequestLogMetadataFallsBackToTopLevelReasoningEffort(t *testing.T) {
t.Parallel()
metadata := parseRequestLogMetadata([]byte(`{
"model":"gpt-5.4",
"reasoning_effort":"high"
}`))
if metadata.ReasoningEffort != "high" {
t.Fatalf("expected high reasoning effort, got %q", metadata.ReasoningEffort)
}
}
func TestBuildAssignedCredentialLogPartsIncludesThinkLevel(t *testing.T) {
t.Parallel()
message := F.ToString(buildAssignedCredentialLogParts("a", "session-1", "alice", requestLogMetadata{
Model: "gpt-5.4",
ServiceTier: "priority",
ReasoningEffort: "xhigh",
})...)
for _, fragment := range []string{
"assigned credential a",
"for session session-1",
"by user alice",
"model=gpt-5.4",
"think=xhigh",
"fast",
} {
if !strings.Contains(message, fragment) {
t.Fatalf("expected %q in %q", fragment, message)
}
}
}
func TestParseWebSocketResponseCreateRequestIncludesThinkLevel(t *testing.T) {
t.Parallel()
request, ok := parseWebSocketResponseCreateRequest([]byte(`{
"type":"response.create",
"model":"gpt-5.4",
"reasoning":{"effort":"xhigh"}
}`))
if !ok {
t.Fatal("expected websocket response.create request to parse")
}
if request.metadata().ReasoningEffort != "xhigh" {
t.Fatalf("expected xhigh reasoning effort, got %q", request.metadata().ReasoningEffort)
}
}

View File

@@ -170,9 +170,9 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Read body for model extraction and retry buffer when JSON replay is useful.
var bodyBytes []byte
var requestMetadata requestLogMetadata
var requestModel string
var requestServiceTier string
if r.Body != nil && (shouldTrackUsage || canRetryRequest) {
if r.Body != nil && (isNew || shouldTrackUsage || canRetryRequest) {
mediaType, _, parseErr := mime.ParseMediaType(r.Header.Get("Content-Type"))
isJSONRequest := parseErr == nil && (mediaType == "application/json" || strings.HasSuffix(mediaType, "+json"))
if isJSONRequest {
@@ -182,33 +182,14 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "failed to read request body")
return
}
var request struct {
Model string `json:"model"`
ServiceTier string `json:"service_tier"`
}
if json.Unmarshal(bodyBytes, &request) == nil {
requestModel = request.Model
requestServiceTier = request.ServiceTier
}
requestMetadata = parseRequestLogMetadata(bodyBytes)
requestModel = requestMetadata.Model
r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
}
if isNew {
logParts := []any{"assigned credential ", selectedCredential.tagName()}
if sessionID != "" {
logParts = append(logParts, " for session ", sessionID)
}
if username != "" {
logParts = append(logParts, " by user ", username)
}
if requestModel != "" {
logParts = append(logParts, ", model=", requestModel)
}
if requestServiceTier == "priority" {
logParts = append(logParts, ", fast")
}
s.logger.DebugContext(ctx, logParts...)
s.logger.DebugContext(ctx, buildAssignedCredentialLogParts(selectedCredential.tagName(), sessionID, username, requestMetadata)...)
}
requestContext := selectedCredential.wrapRequestContext(ctx)

View File

@@ -50,10 +50,9 @@ func (s *webSocketSession) Close() {
}
type webSocketResponseCreateRequest struct {
Type string `json:"type"`
Model string `json:"model"`
ServiceTier string `json:"service_tier"`
Generate *bool `json:"generate"`
requestLogPayload
Type string `json:"type"`
Generate *bool `json:"generate"`
}
func parseWebSocketResponseCreateRequest(data []byte) (webSocketResponseCreateRequest, bool) {
@@ -364,18 +363,7 @@ func (s *Service) proxyWebSocketClientToUpstream(ctx context.Context, clientConn
isWarmup := request.isWarmup()
if !isWarmup && isNew && !logged {
logged = true
logParts := []any{"assigned credential ", selectedCredential.tagName()}
if sessionID != "" {
logParts = append(logParts, " for session ", sessionID)
}
if username != "" {
logParts = append(logParts, " by user ", username)
}
logParts = append(logParts, ", model=", request.Model)
if request.ServiceTier == "priority" {
logParts = append(logParts, ", fast")
}
s.logger.DebugContext(ctx, logParts...)
s.logger.DebugContext(ctx, buildAssignedCredentialLogParts(selectedCredential.tagName(), sessionID, username, request.metadata())...)
}
if !isWarmup && selectedCredential.usageTrackerOrNil() != nil {
select {