oki692 commited on
Commit
dfb9d66
·
verified ·
1 Parent(s): 1595dc3

Update main.go

Browse files
Files changed (1) hide show
  1. main.go +141 -180
main.go CHANGED
@@ -61,14 +61,13 @@ type UpstreamRequest struct {
61
  ExtraBody map[string]interface{} `json:"extra_body,omitempty"`
62
  }
63
 
64
- // SSE chunk types for parsing upstream stream
65
  type RawChunk struct {
66
- ID string `json:"id"`
67
- Object string `json:"object"`
68
- Created int64 `json:"created"`
69
- Model string `json:"model"`
70
- Choices []RawChoice `json:"choices"`
71
- Usage interface{} `json:"usage,omitempty"`
72
  }
73
 
74
  type RawChoice struct {
@@ -78,9 +77,9 @@ type RawChoice struct {
78
  }
79
 
80
  type RawDelta struct {
81
- Role string `json:"role,omitempty"`
82
- Content *string `json:"content,omitempty"`
83
- ToolCalls []RawToolCall `json:"tool_calls,omitempty"`
84
  }
85
 
86
  type RawToolCall struct {
@@ -170,108 +169,6 @@ func handleBaseURL(w http.ResponseWriter, r *http.Request) {
170
  fmt.Fprintf(w, `{"url":"https://%s/v1"}`, host)
171
  }
172
 
173
- func doUpstream(upstream UpstreamRequest) (*http.Response, error) {
174
- body, err := json.Marshal(upstream)
175
- if err != nil {
176
- return nil, err
177
- }
178
- req, err := http.NewRequest(http.MethodPost, NvidiaBaseURL+"/chat/completions", bytes.NewReader(body))
179
- if err != nil {
180
- return nil, err
181
- }
182
- req.Header.Set("Content-Type", "application/json")
183
- req.Header.Set("Authorization", "Bearer "+NvidiaAPIKey)
184
- req.Header.Set("Accept", "text/event-stream")
185
- client := &http.Client{Timeout: 300 * time.Second}
186
- return client.Do(req)
187
- }
188
-
189
- // collectStream reads SSE lines, accumulates tool_calls, returns all chunks + assembled tool calls
190
- func collectStream(body io.Reader) ([]RawChunk, map[int]*AccumToolCall, error) {
191
- scanner := bufio.NewScanner(body)
192
- scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
193
-
194
- var chunks []RawChunk
195
- accum := make(map[int]*AccumToolCall)
196
-
197
- for scanner.Scan() {
198
- line := scanner.Text()
199
- if !strings.HasPrefix(line, "data: ") {
200
- continue
201
- }
202
- data := strings.TrimPrefix(line, "data: ")
203
- if data == "[DONE]" {
204
- break
205
- }
206
- var chunk RawChunk
207
- if err := json.Unmarshal([]byte(data), &chunk); err != nil {
208
- continue
209
- }
210
- chunks = append(chunks, chunk)
211
-
212
- for _, choice := range chunk.Choices {
213
- for _, tc := range choice.Delta.ToolCalls {
214
- acc, ok := accum[tc.Index]
215
- if !ok {
216
- acc = &AccumToolCall{Index: tc.Index}
217
- accum[tc.Index] = acc
218
- }
219
- if tc.ID != "" {
220
- acc.ID = tc.ID
221
- }
222
- if tc.Type != "" {
223
- acc.Type = tc.Type
224
- }
225
- acc.Name += tc.Function.Name
226
- acc.Args += tc.Function.Arguments
227
- }
228
- }
229
- }
230
- return chunks, accum, scanner.Err()
231
- }
232
-
233
- // hasToolCalls returns true if any chunk contains tool_calls
234
- func hasToolCallsInChunks(chunks []RawChunk) bool {
235
- for _, chunk := range chunks {
236
- for _, choice := range chunk.Choices {
237
- if len(choice.Delta.ToolCalls) > 0 {
238
- return true
239
- }
240
- if choice.FinishReason != nil && *choice.FinishReason == "tool_calls" {
241
- return true
242
- }
243
- }
244
- }
245
- return false
246
- }
247
-
248
- func assembleToolCalls(accum map[int]*AccumToolCall) []map[string]interface{} {
249
- indices := make([]int, 0, len(accum))
250
- for idx := range accum {
251
- indices = append(indices, idx)
252
- }
253
- sort.Ints(indices)
254
-
255
- result := make([]map[string]interface{}, 0, len(indices))
256
- for _, idx := range indices {
257
- acc := accum[idx]
258
- tcType := acc.Type
259
- if tcType == "" {
260
- tcType = "function"
261
- }
262
- result = append(result, map[string]interface{}{
263
- "index": idx,
264
- "id": acc.ID,
265
- "type": tcType,
266
- "function": map[string]string{
267
- "name": acc.Name,
268
- "arguments": acc.Args,
269
- },
270
- })
271
- }
272
- return result
273
- }
274
-
275
  func handleChat(w http.ResponseWriter, r *http.Request) {
276
  if !authenticate(r) {
277
  http.Error(w, `{"error":{"message":"Unauthorized"}}`, http.StatusUnauthorized)
@@ -281,6 +178,7 @@ func handleChat(w http.ResponseWriter, r *http.Request) {
281
  http.Error(w, `{"error":{"message":"Method not allowed"}}`, http.StatusMethodNotAllowed)
282
  return
283
  }
 
284
  var req ChatRequest
285
  if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
286
  http.Error(w, `{"error":{"message":"Invalid request body"}}`, http.StatusBadRequest)
@@ -288,28 +186,54 @@ func handleChat(w http.ResponseWriter, r *http.Request) {
288
  }
289
 
290
  modelID := resolveModel(req.Model)
291
- messages := injectSystemPrompt(req.Messages, modelID)
292
-
293
- buildUpstream := func(msgs []Message) UpstreamRequest {
294
- u := UpstreamRequest{
295
- Model: modelID,
296
- Messages: msgs,
297
- Stream: true,
298
- Tools: req.Tools,
299
- ToolChoice: req.ToolChoice,
300
- Temperature: req.Temperature,
301
- MaxTokens: req.MaxTokens,
302
- TopP: req.TopP,
303
- Stop: req.Stop,
304
- }
305
- if modelID == "z-ai/glm4.7" {
306
- u.ExtraBody = map[string]interface{}{
307
- "chat_template_kwargs": map[string]interface{}{
308
- "enable_thinking": false,
309
- },
310
- }
311
  }
312
- return u
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }
314
 
315
  w.Header().Set("Content-Type", "text/event-stream")
@@ -326,36 +250,90 @@ func handleChat(w http.ResponseWriter, r *http.Request) {
326
  }
327
  }
328
 
329
- // Agentic loop — handle tool_calls rounds
330
- for {
331
- resp, err := doUpstream(buildUpstream(messages))
332
- if err != nil {
333
- emit(fmt.Sprintf("data: {\"error\":{\"message\":\"%s\"}}\n\n", err.Error()))
334
- return
335
- }
336
 
337
- if resp.StatusCode != http.StatusOK {
338
- body, _ := io.ReadAll(resp.Body)
339
- resp.Body.Close()
340
- emit("data: " + string(body) + "\n\n")
341
- return
 
 
 
 
 
342
  }
343
 
344
- chunks, accum, _ := collectStream(resp.Body)
345
- resp.Body.Close()
 
 
 
 
346
 
347
- if len(chunks) == 0 {
348
- break
 
 
349
  }
 
350
 
351
- lastChunk := chunks[len(chunks)-1]
 
352
 
353
- if hasToolCallsInChunks(chunks) && len(req.Tools) > 0 {
354
- // Emit tool_calls chunk to client in OpenAI format
355
- assembled := assembleToolCalls(accum)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
  fr := "tool_calls"
358
- toolChunk := map[string]interface{}{
359
  "id": lastChunk.ID,
360
  "object": "chat.completion.chunk",
361
  "created": lastChunk.Created,
@@ -371,37 +349,20 @@ func handleChat(w http.ResponseWriter, r *http.Request) {
371
  "finish_reason": fr,
372
  },
373
  },
374
- }
375
- out, _ := json.Marshal(toolChunk)
376
- emit("data: " + string(out) + "\n\n")
377
-
378
- // Add assistant tool_calls message to history
379
- messages = append(messages, Message{
380
- Role: "assistant",
381
- Content: nil,
382
- ToolCalls: assembled,
383
  })
384
-
385
- // Add placeholder tool results — client must re-call with results
386
- // For now signal finish so client can handle tool execution
387
- emit("data: [DONE]\n\n")
388
- return
389
  }
390
 
391
- // No tool_calls stream content chunks directly to client
392
- for _, chunk := range chunks {
393
- // Remap model alias
394
- chunk.Model = req.Model
395
- out, err := json.Marshal(chunk)
396
- if err != nil {
397
- continue
398
- }
399
- emit("data: " + string(out) + "\n\n")
400
  }
401
- break
402
- }
403
 
404
- emit("data: [DONE]\n\n")
 
 
405
  }
406
 
407
  func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
 
61
  ExtraBody map[string]interface{} `json:"extra_body,omitempty"`
62
  }
63
 
 
64
  type RawChunk struct {
65
+ ID string `json:"id"`
66
+ Object string `json:"object"`
67
+ Created int64 `json:"created"`
68
+ Model string `json:"model"`
69
+ Choices []RawChoice `json:"choices"`
70
+ Usage interface{} `json:"usage,omitempty"`
71
  }
72
 
73
  type RawChoice struct {
 
77
  }
78
 
79
  type RawDelta struct {
80
+ Role string `json:"role,omitempty"`
81
+ Content *string `json:"content,omitempty"`
82
+ ToolCalls []RawToolCall `json:"tool_calls,omitempty"`
83
  }
84
 
85
  type RawToolCall struct {
 
169
  fmt.Fprintf(w, `{"url":"https://%s/v1"}`, host)
170
  }
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  func handleChat(w http.ResponseWriter, r *http.Request) {
173
  if !authenticate(r) {
174
  http.Error(w, `{"error":{"message":"Unauthorized"}}`, http.StatusUnauthorized)
 
178
  http.Error(w, `{"error":{"message":"Method not allowed"}}`, http.StatusMethodNotAllowed)
179
  return
180
  }
181
+
182
  var req ChatRequest
183
  if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
184
  http.Error(w, `{"error":{"message":"Invalid request body"}}`, http.StatusBadRequest)
 
186
  }
187
 
188
  modelID := resolveModel(req.Model)
189
+ upstream := UpstreamRequest{
190
+ Model: modelID,
191
+ Messages: injectSystemPrompt(req.Messages, modelID),
192
+ Stream: true,
193
+ Tools: req.Tools,
194
+ ToolChoice: req.ToolChoice,
195
+ Temperature: req.Temperature,
196
+ MaxTokens: req.MaxTokens,
197
+ TopP: req.TopP,
198
+ Stop: req.Stop,
199
+ }
200
+ if modelID == "z-ai/glm4.7" {
201
+ upstream.ExtraBody = map[string]interface{}{
202
+ "chat_template_kwargs": map[string]interface{}{
203
+ "enable_thinking": false,
204
+ },
 
 
 
 
205
  }
206
+ }
207
+
208
+ body, err := json.Marshal(upstream)
209
+ if err != nil {
210
+ http.Error(w, `{"error":{"message":"Failed to marshal request"}}`, http.StatusInternalServerError)
211
+ return
212
+ }
213
+
214
+ upstreamReq, err := http.NewRequest(http.MethodPost, NvidiaBaseURL+"/chat/completions", bytes.NewReader(body))
215
+ if err != nil {
216
+ http.Error(w, `{"error":{"message":"Failed to create upstream request"}}`, http.StatusInternalServerError)
217
+ return
218
+ }
219
+ upstreamReq.Header.Set("Content-Type", "application/json")
220
+ upstreamReq.Header.Set("Authorization", "Bearer "+NvidiaAPIKey)
221
+ upstreamReq.Header.Set("Accept", "text/event-stream")
222
+
223
+ client := &http.Client{Timeout: 300 * time.Second}
224
+ resp, err := client.Do(upstreamReq)
225
+ if err != nil {
226
+ http.Error(w, fmt.Sprintf(`{"error":{"message":"%s"}}`, err.Error()), http.StatusBadGateway)
227
+ return
228
+ }
229
+ defer resp.Body.Close()
230
+
231
+ if resp.StatusCode != http.StatusOK {
232
+ upstreamBody, _ := io.ReadAll(resp.Body)
233
+ w.Header().Set("Content-Type", "application/json")
234
+ w.WriteHeader(resp.StatusCode)
235
+ w.Write(upstreamBody)
236
+ return
237
  }
238
 
239
  w.Header().Set("Content-Type", "text/event-stream")
 
250
  }
251
  }
252
 
253
+ scanner := bufio.NewScanner(resp.Body)
254
+ scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
 
 
 
 
 
255
 
256
+ // Accumulate tool_calls across delta chunks, stream content chunks immediately
257
+ accum := make(map[int]*AccumToolCall)
258
+ var lastChunk RawChunk
259
+
260
+ for scanner.Scan() {
261
+ line := scanner.Text()
262
+
263
+ if !strings.HasPrefix(line, "data: ") {
264
+ emit(line + "\n")
265
+ continue
266
  }
267
 
268
+ data := strings.TrimPrefix(line, "data: ")
269
+
270
+ if data == "[DONE]" {
271
+ emit("data: [DONE]\n\n")
272
+ continue
273
+ }
274
 
275
+ var chunk RawChunk
276
+ if err := json.Unmarshal([]byte(data), &chunk); err != nil {
277
+ emit(line + "\n")
278
+ continue
279
  }
280
+ lastChunk = chunk
281
 
282
+ isToolChunk := false
283
+ isFinishToolCalls := false
284
 
285
+ for _, choice := range chunk.Choices {
286
+ if len(choice.Delta.ToolCalls) > 0 {
287
+ isToolChunk = true
288
+ for _, tc := range choice.Delta.ToolCalls {
289
+ acc, ok := accum[tc.Index]
290
+ if !ok {
291
+ acc = &AccumToolCall{Index: tc.Index}
292
+ accum[tc.Index] = acc
293
+ }
294
+ if tc.ID != "" {
295
+ acc.ID = tc.ID
296
+ }
297
+ if tc.Type != "" {
298
+ acc.Type = tc.Type
299
+ }
300
+ acc.Name += tc.Function.Name
301
+ acc.Args += tc.Function.Arguments
302
+ }
303
+ }
304
+ if choice.FinishReason != nil && *choice.FinishReason == "tool_calls" {
305
+ isFinishToolCalls = true
306
+ }
307
+ }
308
+
309
+ if isFinishToolCalls {
310
+ // Emit one complete tool_calls chunk with all assembled tool calls
311
+ indices := make([]int, 0, len(accum))
312
+ for idx := range accum {
313
+ indices = append(indices, idx)
314
+ }
315
+ sort.Ints(indices)
316
+
317
+ assembled := make([]map[string]interface{}, 0, len(indices))
318
+ for _, idx := range indices {
319
+ acc := accum[idx]
320
+ tcType := acc.Type
321
+ if tcType == "" {
322
+ tcType = "function"
323
+ }
324
+ assembled = append(assembled, map[string]interface{}{
325
+ "index": idx,
326
+ "id": acc.ID,
327
+ "type": tcType,
328
+ "function": map[string]string{
329
+ "name": acc.Name,
330
+ "arguments": acc.Args,
331
+ },
332
+ })
333
+ }
334
 
335
  fr := "tool_calls"
336
+ out, _ := json.Marshal(map[string]interface{}{
337
  "id": lastChunk.ID,
338
  "object": "chat.completion.chunk",
339
  "created": lastChunk.Created,
 
349
  "finish_reason": fr,
350
  },
351
  },
 
 
 
 
 
 
 
 
 
352
  })
353
+ emit("data: " + string(out) + "\n\n")
354
+ accum = make(map[int]*AccumToolCall)
355
+ continue
 
 
356
  }
357
 
358
+ // Skip intermediate tool_call delta chunks (already accumulating)
359
+ if isToolChunk {
360
+ continue
 
 
 
 
 
 
361
  }
 
 
362
 
363
+ // Regular content chunk — stream immediately as-is
364
+ emit("data: " + data + "\n\n")
365
+ }
366
  }
367
 
368
  func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {