Skip to content

Commit 3092a51

Browse files
authored
feat: chat-responses-reasoning (#602)
* feat: chat-responses-reasoning * fix
1 parent 739bbfa commit 3092a51

12 files changed

Lines changed: 586 additions & 54 deletions

File tree

core/controller/relay-controller.go

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -401,23 +401,14 @@ func recordResult(
401401
firstByteAt = result.BodyDetail.FirstByteAt
402402
}
403403

404-
if config.GetSaveAllLogDetail() || meta.ModelConfig.ForceSaveDetail || code != http.StatusOK {
405-
if result.BodyDetail != nil {
406-
requestBodyMaxSize := effectiveDetailBodyMaxSize(
407-
meta.ModelConfig.RequestBodyStorageMaxSize,
408-
config.GetLogDetailRequestBodyMaxSize(),
409-
)
410-
responseBodyMaxSize := effectiveDetailBodyMaxSize(
411-
meta.ModelConfig.ResponseBodyStorageMaxSize,
412-
config.GetLogDetailResponseBodyMaxSize(),
413-
)
414-
415-
detail = &model.RequestDetail{
416-
RequestBody: result.BodyDetail.RequestBody,
417-
ResponseBody: result.BodyDetail.ResponseBody,
418-
}
419-
detail.ApplyBodySizeLimits(requestBodyMaxSize, responseBodyMaxSize)
420-
}
404+
forceSaveDetail := config.GetSaveAllLogDetail() || meta.ModelConfig.ForceSaveDetail
405+
if forceSaveDetail || code != http.StatusOK {
406+
detail = buildRequestDetailForLog(
407+
result.BodyDetail,
408+
meta.ModelConfig,
409+
code,
410+
forceSaveDetail,
411+
)
421412
}
422413

423414
gbc := middleware.GetGroupBalanceConsumerFromContext(c)
@@ -502,6 +493,40 @@ func effectiveDetailBodyMaxSize(modelLimit, globalLimit int64) int64 {
502493
return globalLimit
503494
}
504495

496+
func buildRequestDetailForLog(
497+
bodyDetail *controller.BodyDetail,
498+
modelConfig model.ModelConfig,
499+
code int,
500+
forceSaveDetail bool,
501+
) *model.RequestDetail {
502+
if bodyDetail == nil {
503+
return nil
504+
}
505+
506+
requestBodyMaxSize := effectiveDetailBodyMaxSize(
507+
modelConfig.RequestBodyStorageMaxSize,
508+
config.GetLogDetailRequestBodyMaxSize(),
509+
)
510+
responseBodyMaxSize := effectiveDetailBodyMaxSize(
511+
modelConfig.ResponseBodyStorageMaxSize,
512+
config.GetLogDetailResponseBodyMaxSize(),
513+
)
514+
515+
detail := &model.RequestDetail{
516+
RequestBody: bodyDetail.RequestBody,
517+
ResponseBody: bodyDetail.ResponseBody,
518+
}
519+
detail.DropInvalidUTF8Bodies()
520+
521+
if controller.ShouldSkipRequestBodyDetailForStatus(code) && !forceSaveDetail {
522+
detail.RequestBody = ""
523+
}
524+
525+
detail.ApplyBodySizeLimits(requestBodyMaxSize, responseBodyMaxSize)
526+
527+
return detail
528+
}
529+
505530
func buildBodyDetailOption(meta *meta.Meta) controller.BodyDetailOption {
506531
requestBodyMaxSize := effectiveDetailBodyMaxSize(
507532
meta.ModelConfig.RequestBodyStorageMaxSize,

core/controller/relay-controller_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package controller
33

44
import (
5+
"net/http"
56
"reflect"
67
"testing"
78
"time"
@@ -209,3 +210,85 @@ func TestSaveAsyncUsageInfoDoesNotStoreInitialUsage(t *testing.T) {
209210
require.Zero(t, captured.Usage.TotalTokens)
210211
require.Equal(t, "priority", captured.UsageContext.ServiceTier)
211212
}
213+
214+
func TestBuildRequestDetailForLogSkipsRequestBodyForUpstreamOnlyStatuses(t *testing.T) {
215+
t.Parallel()
216+
217+
bodyDetail := &relaycontroller.BodyDetail{
218+
RequestBody: `{"prompt":"secret"}`,
219+
ResponseBody: `{"error":"upstream"}`,
220+
}
221+
222+
for _, statusCode := range []int{
223+
http.StatusUnauthorized,
224+
http.StatusPaymentRequired,
225+
http.StatusForbidden,
226+
http.StatusNotFound,
227+
http.StatusMethodNotAllowed,
228+
http.StatusTooManyRequests,
229+
} {
230+
t.Run(http.StatusText(statusCode), func(t *testing.T) {
231+
t.Parallel()
232+
233+
detail := buildRequestDetailForLog(bodyDetail, model.ModelConfig{}, statusCode, false)
234+
235+
require.NotNil(t, detail)
236+
assert.Empty(t, detail.RequestBody)
237+
assert.Equal(t, `{"error":"upstream"}`, detail.ResponseBody)
238+
})
239+
}
240+
}
241+
242+
func TestBuildRequestDetailForLogKeepsRequestBodyWhenForced(t *testing.T) {
243+
t.Parallel()
244+
245+
detail := buildRequestDetailForLog(
246+
&relaycontroller.BodyDetail{
247+
RequestBody: `{"prompt":"secret"}`,
248+
ResponseBody: `{"error":"limited"}`,
249+
},
250+
model.ModelConfig{},
251+
http.StatusTooManyRequests,
252+
true,
253+
)
254+
255+
require.NotNil(t, detail)
256+
assert.Equal(t, `{"prompt":"secret"}`, detail.RequestBody)
257+
assert.Equal(t, `{"error":"limited"}`, detail.ResponseBody)
258+
}
259+
260+
func TestBuildRequestDetailForLogKeepsRequestBodyForClientPayloadErrors(t *testing.T) {
261+
t.Parallel()
262+
263+
detail := buildRequestDetailForLog(
264+
&relaycontroller.BodyDetail{
265+
RequestBody: `{"prompt":"secret"}`,
266+
ResponseBody: `{"error":"bad request"}`,
267+
},
268+
model.ModelConfig{},
269+
http.StatusBadRequest,
270+
false,
271+
)
272+
273+
require.NotNil(t, detail)
274+
assert.Equal(t, `{"prompt":"secret"}`, detail.RequestBody)
275+
assert.Equal(t, `{"error":"bad request"}`, detail.ResponseBody)
276+
}
277+
278+
func TestBuildRequestDetailForLogDropsInvalidUTF8Bodies(t *testing.T) {
279+
t.Parallel()
280+
281+
detail := buildRequestDetailForLog(
282+
&relaycontroller.BodyDetail{
283+
RequestBody: string([]byte{0xff, 0xfe}),
284+
ResponseBody: string([]byte{'o', 'k', 0xff}),
285+
},
286+
model.ModelConfig{},
287+
http.StatusBadRequest,
288+
false,
289+
)
290+
291+
require.NotNil(t, detail)
292+
assert.Empty(t, detail.RequestBody)
293+
assert.Empty(t, detail.ResponseBody)
294+
}

core/model/log.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77
"time"
8+
"unicode/utf8"
89

910
"github.com/bytedance/sonic"
1011
"github.com/labring/aiproxy/core/common"
@@ -45,6 +46,18 @@ func (d *RequestDetail) ApplyBodySizeLimits(requestMaxSize, responseMaxSize int6
4546
d.ResponseBody, d.ResponseBodyTruncated = truncateDetailBody(d.ResponseBody, responseMaxSize)
4647
}
4748

49+
func (d *RequestDetail) DropInvalidUTF8Bodies() {
50+
if d.RequestBody != "" && !utf8.ValidString(d.RequestBody) {
51+
d.RequestBody = ""
52+
d.RequestBodyTruncated = false
53+
}
54+
55+
if d.ResponseBody != "" && !utf8.ValidString(d.ResponseBody) {
56+
d.ResponseBody = ""
57+
d.ResponseBodyTruncated = false
58+
}
59+
}
60+
4861
type Log struct {
4962
RequestDetail *RequestDetail `gorm:"foreignKey:LogID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"request_detail,omitempty"`
5063
RequestAt time.Time ` json:"request_at"`

core/model/log_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,33 @@ func TestRequestDetailApplyBodySizeLimitsZeroKeepsOriginalBody(t *testing.T) {
5858
}
5959
}
6060

61+
func TestRequestDetailDropInvalidUTF8Bodies(t *testing.T) {
62+
detail := &model.RequestDetail{
63+
RequestBody: string([]byte{0xff, 0xfe}),
64+
ResponseBody: "valid",
65+
RequestBodyTruncated: true,
66+
ResponseBodyTruncated: true,
67+
}
68+
69+
detail.DropInvalidUTF8Bodies()
70+
71+
if detail.RequestBody != "" {
72+
t.Fatalf("expected invalid request body to be cleared, got %q", detail.RequestBody)
73+
}
74+
75+
if detail.RequestBodyTruncated {
76+
t.Fatal("expected request body truncated flag to be cleared")
77+
}
78+
79+
if detail.ResponseBody != "valid" {
80+
t.Fatalf("expected valid response body to remain unchanged, got %q", detail.ResponseBody)
81+
}
82+
83+
if !detail.ResponseBodyTruncated {
84+
t.Fatal("expected valid response body truncated flag to remain unchanged")
85+
}
86+
}
87+
6188
func TestRecordConsumeLogPersistsWebSearchCount(t *testing.T) {
6289
db, err := model.OpenSQLite(filepath.Join(t.TempDir(), "logs.db"))
6390
if err != nil {

0 commit comments

Comments
 (0)