Skip to content

Commit 32ec44c

Browse files
wip
1 parent 1336567 commit 32ec44c

8 files changed

Lines changed: 180 additions & 11 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/pkoukk/tiktoken-go v0.1.8
2323
github.com/redis/go-redis/v9 v9.14.0
2424
github.com/sashabaranov/go-openai v1.41.2
25+
github.com/sergei-bronnikov/go-pointer v0.4.0
2526
github.com/stretchr/testify v1.11.1
2627
github.com/tidwall/gjson v1.18.0
2728
github.com/tidwall/sjson v1.2.5

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
190190
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
191191
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
192192
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
193+
github.com/sergei-bronnikov/go-pointer v0.4.0 h1:aMK8zbBLl4WS7vwxOPQL/hTKSyVfANt/dIkUlFXPbrY=
194+
github.com/sergei-bronnikov/go-pointer v0.4.0/go.mod h1:jwCRkImMbgXMcxz3xaKzgm+RYJgqfyYVfLtIOzonUlg=
193195
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
194196
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
195197
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=

internal/authenticator/authenticator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package auth
33
import (
44
"errors"
55
"fmt"
6-
"github.com/bricks-cloud/bricksllm/internal/provider/xcustom"
76
"math/rand"
87
"net/http"
98
"strconv"
109
"strings"
1110

11+
"github.com/bricks-cloud/bricksllm/internal/provider/xcustom"
12+
1213
internal_errors "github.com/bricks-cloud/bricksllm/internal/errors"
1314
"github.com/bricks-cloud/bricksllm/internal/hasher"
1415
"github.com/bricks-cloud/bricksllm/internal/telemetry"

internal/message/handler.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,18 @@ func (h *Handler) decorateEvent(m Message) error {
707707
}
708708
}
709709

710+
//if strings.HasPrefix(e.Event.Path, "/api/providers/openai/v1/responses") {
711+
// responsesReq, ok := e.Request.(*openai.ResponseRequest)
712+
// if !ok {
713+
// telemetry.Incr("bricksllm.message.handler.decorate_event.event_request_parsing_error", nil, 1)
714+
// h.log.Debug("event contains data that cannot be converted to responses api request", zap.Any("data", m.Data))
715+
// return errors.New("event request data cannot be parsed as responses api request")
716+
// }
717+
// if gopointer.ToValueOrDefault(responsesReq.Stream, false) {
718+
// fmt.Println("------------- estimating for responses api request --------------")
719+
// }
720+
//}
721+
710722
if e.Event.Path == "/api/providers/vllm/v1/chat/completions" {
711723
ccr, ok := e.Request.(*vllm.ChatRequest)
712724
if !ok {

internal/provider/openai/cost.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ func (ce *CostEstimator) EstimateResponseApiTotalCost(model string, usage respon
505505

506506
outputCost, err := ce.estimateResponseApiTokensCost("completion", model, outputTokens)
507507

508-
return math.Trunc((inputCost+outputCost+cachedInputCost)*100000) / 100000, err
508+
return inputCost + cachedInputCost + outputCost, err
509509
}
510510

511511
func (ce *CostEstimator) estimateResponseApiTokensCost(costMapKey, model string, tks int64) (float64, error) {

internal/provider/openai/types.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package openai
2+
3+
type ResponseRequest struct {
4+
Background *bool `json:"background,omitzero"`
5+
Conversation *any `json:"conversation,omitzero"`
6+
Include []string `json:"include,omitzero"`
7+
Input *any `json:"input,omitzero"`
8+
Instructions *string `json:"instructions,omitzero"`
9+
MaxOutputTokens *int `json:"max_output_tokens,omitzero"`
10+
MaxToolCalls *int `json:"max_tool_calls,omitzero"`
11+
Metadata *map[string]string `json:"metadata,omitzero"`
12+
Model *string `json:"model,omitzero"`
13+
ParallelToolCalls *bool `json:"parallel_tool_calls,omitzero"`
14+
PreviousResponseId *string `json:"previous_response_id,omitzero"`
15+
Prompt *any `json:"prompt,omitzero"`
16+
PromptCacheKey *string `json:"prompt_cache_key,omitzero"`
17+
Reasoning *any `json:"reasoning,omitzero"`
18+
SafetyIdentifier *string `json:"safety_identifier,omitzero"`
19+
ServiceTier *string `json:"service_tier,omitzero"`
20+
Store *bool `json:"store,omitzero"`
21+
Stream *bool `json:"stream,omitzero"`
22+
StreamOptions *any `json:"stream_options,omitzero"`
23+
Temperature *float32 `json:"temperature,omitzero"`
24+
Text *any `json:"text,omitzero"`
25+
ToolChoice *any `json:"tool_choice,omitzero"`
26+
Tools []any `json:"tools,omitzero"`
27+
TopLogprobs *int `json:"top_logprobs,omitzero"`
28+
TopP *float32 `json:"top_p,omitzero"`
29+
Truncation *string `json:"truncation,omitzero"`
30+
//User *string `json:"user,omitzero"` //Deprecated
31+
}

internal/server/web/proxy/middleware.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030

3131
responsesOpenai "github.com/openai/openai-go/responses"
3232
goopenai "github.com/sashabaranov/go-openai"
33+
34+
"github.com/sergei-bronnikov/go-pointer"
3335
)
3436

3537
type keyMemStorage interface {
@@ -796,21 +798,21 @@ func getMiddleware(cpm CustomProvidersManager, rm routeManager, pm PoliciesManag
796798
}
797799

798800
if strings.HasPrefix(c.FullPath(), "/api/providers/openai/v1/responses") {
799-
responsesReq := &responsesOpenai.ResponseNewParams{}
801+
responsesReq := &openai.ResponseRequest{}
800802
err = json.Unmarshal(body, responsesReq)
801803
if err != nil {
802804
logError(logWithCid, "error when unmarshalling openai responses request", prod, err)
803805
return
804806
}
805807

806-
userId = responsesReq.User.String()
808+
userId = gopointer.ToValueOrDefault(responsesReq.SafetyIdentifier, "")
807809
enrichedEvent.Request = responsesReq
808-
c.Set("model", responsesReq.Model)
810+
c.Set("model", gopointer.ToValueOrDefault(responsesReq.Model, ""))
809811

810812
// TODO: log
811813
//logRequest(logWithCid, prod, private, responsesReq)
812814

813-
if responsesReq.Metadata["stream"] == "true" {
815+
if gopointer.ToValueOrDefault(responsesReq.Stream, false) {
814816
c.Set("stream", true)
815817
}
816818

internal/server/web/proxy/responses.go

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package proxy
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
57
"encoding/json"
8+
"errors"
69
"fmt"
710
"io"
811
"net/http"
@@ -11,7 +14,6 @@ import (
1114
"github.com/bricks-cloud/bricksllm/internal/telemetry"
1215
"github.com/bricks-cloud/bricksllm/internal/util"
1316
"github.com/gin-gonic/gin"
14-
1517
responsesOpenai "github.com/openai/openai-go/responses"
1618
goopenai "github.com/sashabaranov/go-openai"
1719
)
@@ -103,8 +105,21 @@ func getResponsesHandler(prod, private bool, client http.Client, e estimator) gi
103105
}
104106

105107
c.Set("costInUsd", cost)
106-
c.Set("promptTokenCount", resp.Usage.InputTokens)
107-
c.Set("completionTokenCount", resp.Usage.OutputTokens)
108+
109+
promptTokenCount, err := int64ToInt(resp.Usage.InputTokens)
110+
if err != nil {
111+
telemetry.Incr("bricksllm.proxy.get_responses_handler.int64_to_int_error", nil, 1)
112+
logError(log, "error when converting int64 to int for prompt token count", prod, err)
113+
}
114+
115+
completionTokenCount, err := int64ToInt(resp.Usage.OutputTokens)
116+
if err != nil {
117+
telemetry.Incr("bricksllm.proxy.get_responses_handler.int64_to_int_error", nil, 1)
118+
logError(log, "error when converting int64 to int for completion token count", prod, err)
119+
}
120+
121+
c.Set("promptTokenCount", promptTokenCount)
122+
c.Set("completionTokenCount", completionTokenCount)
108123

109124
c.Data(res.StatusCode, "application/json", bytes)
110125
return
@@ -134,9 +149,114 @@ func getResponsesHandler(prod, private bool, client http.Client, e estimator) gi
134149
return
135150
}
136151

137-
// handle streaming response
152+
buffer := bufio.NewReader(res.Body)
153+
content := ""
154+
streamingResponse := [][]byte{}
155+
156+
streamCost := 0.0
157+
streamPromptTokenCount := 0
158+
streamCompletionTokenCount := 0
159+
160+
defer func() {
161+
c.Set("content", content)
162+
c.Set("streaming_response", bytes.Join(streamingResponse, []byte{'\n'}))
163+
164+
c.Set("costInUsd", streamCost)
165+
c.Set("promptTokenCount", streamPromptTokenCount)
166+
c.Set("completionTokenCount", streamCompletionTokenCount)
167+
}()
168+
138169
telemetry.Incr("bricksllm.proxy.get_responses_handler.streaming_requests", nil, 1)
139-
// TODO: implement the actual streaming logic here
170+
c.Stream(func(w io.Writer) bool {
171+
raw, err := buffer.ReadBytes('\n')
172+
if err != nil {
173+
if err == io.EOF {
174+
return false
175+
}
176+
177+
if errors.Is(err, context.DeadlineExceeded) {
178+
telemetry.Incr("bricksllm.proxy.get_responses_handler.context_deadline_exceeded_error", nil, 1)
179+
logError(log, "context deadline exceeded when reading bytes from openai responses api response", prod, err)
180+
181+
return false
182+
}
183+
184+
telemetry.Incr("bricksllm.proxy.get_responses_handler.read_bytes_error", nil, 1)
185+
logError(log, "error when reading bytes from openai responses api response", prod, err)
186+
187+
apiErr := &goopenai.ErrorResponse{
188+
Error: &goopenai.APIError{
189+
Type: "bricksllm_error",
190+
Message: err.Error(),
191+
},
192+
}
193+
194+
errBytes, err := json.Marshal(apiErr)
195+
if err != nil {
196+
telemetry.Incr("bricksllm.proxy.get_responses_handler.json_marshal_error", nil, 1)
197+
logError(log, "error when marshalling bytes for openai streaming responses api error response", prod, err)
198+
return false
199+
}
200+
201+
c.SSEvent("", string(errBytes))
202+
c.SSEvent("", " [DONE]")
203+
return false
204+
}
205+
206+
streamingResponse = append(streamingResponse, raw)
207+
208+
noSpaceLine := bytes.TrimSpace(raw)
209+
if !bytes.HasPrefix(noSpaceLine, headerData) {
210+
return true
211+
}
212+
213+
noPrefixLine := bytes.TrimPrefix(noSpaceLine, headerData)
214+
c.SSEvent("", " "+string(noPrefixLine))
215+
216+
if string(noPrefixLine) == "[DONE]" {
217+
return false
218+
}
219+
220+
responsesStreamResp := &responsesOpenai.ResponseStreamEventUnion{}
221+
err = json.Unmarshal(noPrefixLine, responsesStreamResp)
222+
if err != nil {
223+
telemetry.Incr("bricksllm.proxy.get_responses_handler.completion_response_unmarshall_error", nil, 1)
224+
logError(log, "error when unmarshalling openai responses api stream response", prod, err)
225+
}
226+
if err == nil {
227+
textDelta := responsesStreamResp.AsResponseOutputTextDelta().Delta
228+
if len(textDelta) > 0 {
229+
content += textDelta
230+
}
231+
232+
if responsesStreamResp.Response.Status == "completed" {
233+
streamCost, err = e.EstimateResponseApiTotalCost(model, responsesStreamResp.Response.Usage)
234+
if err != nil {
235+
telemetry.Incr("bricksllm.proxy.get_chat_completion_handler.estimate_total_cost_error", nil, 1)
236+
logError(log, "error when estimating openai cost", prod, err)
237+
}
238+
streamPromptTokenCount, err = int64ToInt(responsesStreamResp.Response.Usage.InputTokens)
239+
if err != nil {
240+
telemetry.Incr("bricksllm.proxy.get_responses_handler.int64_to_int_error", nil, 1)
241+
logError(log, "error when converting int64 to int for prompt token count", prod, err)
242+
}
243+
244+
streamCompletionTokenCount, err = int64ToInt(responsesStreamResp.Response.Usage.OutputTokens)
245+
if err != nil {
246+
telemetry.Incr("bricksllm.proxy.get_responses_handler.int64_to_int_error", nil, 1)
247+
logError(log, "error when converting int64 to int for completion token count", prod, err)
248+
}
249+
}
250+
}
251+
return true
252+
})
140253
telemetry.Timing("bricksllm.proxy.get_chat_completion_handler.streaming_latency", time.Since(start), nil, 1)
141254
}
142255
}
256+
257+
func int64ToInt(src int64) (int, error) {
258+
if src > int64(int(^uint(0)>>1)) {
259+
return 0, fmt.Errorf("int64 value %d overflows int", src)
260+
}
261+
return int(src), nil
262+
}

0 commit comments

Comments
 (0)