sanbo1200 commited on
Commit
060ff19
·
verified ·
1 Parent(s): 2789f1c

Create main.go

Browse files
Files changed (1) hide show
  1. main.go +709 -0
main.go ADDED
@@ -0,0 +1,709 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io"
9
+ "log"
10
+ "net/http"
11
+ "os"
12
+ "regexp"
13
+ "strings"
14
+ "time"
15
+ )
16
+
17
+ // 配置变量(从环境变量读取)
18
+ var (
19
+ UPSTREAM_URL string
20
+ DEFAULT_KEY string
21
+ UPSTREAM_TOKEN string
22
+ MODEL_NAME string
23
+ PORT string
24
+ DEBUG_MODE bool
25
+ DEFAULT_STREAM bool
26
+ )
27
+
28
+ // 思考内容处理策略
29
+ const (
30
+ THINK_TAGS_MODE = "strip" // strip: 去除<details>标签;think: 转为<think>标签;raw: 保留原样
31
+ )
32
+
33
+ // 伪装前端头部(来自抓包)
34
+ const (
35
+ X_FE_VERSION = "prod-fe-1.0.70"
36
+ BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0"
37
+ SEC_CH_UA = "\"Not;A=Brand\";v=\"99\", \"Microsoft Edge\";v=\"139\", \"Chromium\";v=\"139\""
38
+ SEC_CH_UA_MOB = "?0"
39
+ SEC_CH_UA_PLAT = "\"Windows\""
40
+ ORIGIN_BASE = "https://chat.z.ai"
41
+ )
42
+
43
+ // 匿名token开关
44
+ const ANON_TOKEN_ENABLED = true
45
+
46
+ // 从环境变量初始化配置
47
+ func initConfig() {
48
+ UPSTREAM_URL = getEnv("UPSTREAM_URL", "https://chat.z.ai/api/chat/completions")
49
+ DEFAULT_KEY = getEnv("DEFAULT_KEY", "sk-your-key")
50
+ UPSTREAM_TOKEN = getEnv("UPSTREAM_TOKEN", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMxNmJjYjQ4LWZmMmYtNGExNS04NTNkLWYyYTI5YjY3ZmYwZiIsImVtYWlsIjoiR3Vlc3QtMTc1NTg0ODU4ODc4OEBndWVzdC5jb20ifQ.PktllDySS3trlyuFpTeIZf-7hl8Qu1qYF3BxjgIul0BrNux2nX9hVzIjthLXKMWAf9V0qM8Vm_iyDqkjPGsaiQ")
51
+ MODEL_NAME = getEnv("MODEL_NAME", "GLM-4.5")
52
+ PORT = getEnv("PORT", "7860")
53
+
54
+ // 处理PORT格式,确保有冒号前缀
55
+ if !strings.HasPrefix(PORT, ":") {
56
+ PORT = ":" + PORT
57
+ }
58
+
59
+ DEBUG_MODE = getEnv("DEBUG_MODE", "true") == "true"
60
+ DEFAULT_STREAM = getEnv("DEFAULT_STREAM", "true") == "true"
61
+ }
62
+
63
+ // 获取环境变量,如果不存在则返回默认值
64
+ func getEnv(key, defaultValue string) string {
65
+ if value := os.Getenv(key); value != "" {
66
+ return value
67
+ }
68
+ return defaultValue
69
+ }
70
+
71
+ // OpenAI 请求结构
72
+ type OpenAIRequest struct {
73
+ Model string `json:"model"`
74
+ Messages []Message `json:"messages"`
75
+ Stream bool `json:"stream,omitempty"`
76
+ Temperature float64 `json:"temperature,omitempty"`
77
+ MaxTokens int `json:"max_tokens,omitempty"`
78
+ }
79
+
80
+ type Message struct {
81
+ Role string `json:"role"`
82
+ Content string `json:"content"`
83
+ }
84
+
85
+ // 上游请求结构
86
+ type UpstreamRequest struct {
87
+ Stream bool `json:"stream"`
88
+ Model string `json:"model"`
89
+ Messages []Message `json:"messages"`
90
+ Params map[string]interface{} `json:"params"`
91
+ Features map[string]interface{} `json:"features"`
92
+ BackgroundTasks map[string]bool `json:"background_tasks,omitempty"`
93
+ ChatID string `json:"chat_id,omitempty"`
94
+ ID string `json:"id,omitempty"`
95
+ MCPServers []string `json:"mcp_servers,omitempty"`
96
+ ModelItem struct {
97
+ ID string `json:"id"`
98
+ Name string `json:"name"`
99
+ OwnedBy string `json:"owned_by"`
100
+ } `json:"model_item,omitempty"`
101
+ ToolServers []string `json:"tool_servers,omitempty"`
102
+ Variables map[string]string `json:"variables,omitempty"`
103
+ }
104
+
105
+ // OpenAI 响应结构
106
+ type OpenAIResponse struct {
107
+ ID string `json:"id"`
108
+ Object string `json:"object"`
109
+ Created int64 `json:"created"`
110
+ Model string `json:"model"`
111
+ Choices []Choice `json:"choices"`
112
+ Usage Usage `json:"usage,omitempty"`
113
+ }
114
+
115
+ type Choice struct {
116
+ Index int `json:"index"`
117
+ Message Message `json:"message,omitempty"`
118
+ Delta Delta `json:"delta,omitempty"`
119
+ FinishReason string `json:"finish_reason,omitempty"`
120
+ }
121
+
122
+ type Delta struct {
123
+ Role string `json:"role,omitempty"`
124
+ Content string `json:"content,omitempty"`
125
+ }
126
+
127
+ type Usage struct {
128
+ PromptTokens int `json:"prompt_tokens"`
129
+ CompletionTokens int `json:"completion_tokens"`
130
+ TotalTokens int `json:"total_tokens"`
131
+ }
132
+
133
+ // 上游SSE响应结构
134
+ type UpstreamData struct {
135
+ Type string `json:"type"`
136
+ Data struct {
137
+ DeltaContent string `json:"delta_content"`
138
+ Phase string `json:"phase"`
139
+ Done bool `json:"done"`
140
+ Usage Usage `json:"usage,omitempty"`
141
+ Error *UpstreamError `json:"error,omitempty"`
142
+ Inner *struct {
143
+ Error *UpstreamError `json:"error,omitempty"`
144
+ } `json:"data,omitempty"`
145
+ } `json:"data"`
146
+ Error *UpstreamError `json:"error,omitempty"`
147
+ }
148
+
149
+ type UpstreamError struct {
150
+ Detail string `json:"detail"`
151
+ Code int `json:"code"`
152
+ }
153
+
154
+ // 模型列表响应
155
+ type ModelsResponse struct {
156
+ Object string `json:"object"`
157
+ Data []Model `json:"data"`
158
+ }
159
+
160
+ type Model struct {
161
+ ID string `json:"id"`
162
+ Object string `json:"object"`
163
+ Created int64 `json:"created"`
164
+ OwnedBy string `json:"owned_by"`
165
+ }
166
+
167
+ // debug日志函数
168
+ func debugLog(format string, args ...interface{}) {
169
+ if DEBUG_MODE {
170
+ log.Printf("[DEBUG] "+format, args...)
171
+ }
172
+ }
173
+
174
+ // 获取匿名token(每次对话使用不同token,避免共享记忆)
175
+ func getAnonymousToken() (string, error) {
176
+ client := &http.Client{Timeout: 10 * time.Second}
177
+ req, err := http.NewRequest("GET", ORIGIN_BASE+"/api/v1/auths/", nil)
178
+ if err != nil {
179
+ return "", err
180
+ }
181
+ // 伪装浏览器头
182
+ req.Header.Set("User-Agent", BROWSER_UA)
183
+ req.Header.Set("Accept", "*/*")
184
+ req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
185
+ req.Header.Set("X-FE-Version", X_FE_VERSION)
186
+ req.Header.Set("sec-ch-ua", SEC_CH_UA)
187
+ req.Header.Set("sec-ch-ua-mobile", SEC_CH_UA_MOB)
188
+ req.Header.Set("sec-ch-ua-platform", SEC_CH_UA_PLAT)
189
+ req.Header.Set("Origin", ORIGIN_BASE)
190
+ req.Header.Set("Referer", ORIGIN_BASE+"/")
191
+
192
+ resp, err := client.Do(req)
193
+ if err != nil {
194
+ return "", err
195
+ }
196
+ defer resp.Body.Close()
197
+ if resp.StatusCode != http.StatusOK {
198
+ return "", fmt.Errorf("anon token status=%d", resp.StatusCode)
199
+ }
200
+ var body struct {
201
+ Token string `json:"token"`
202
+ }
203
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
204
+ return "", err
205
+ }
206
+ if body.Token == "" {
207
+ return "", fmt.Errorf("anon token empty")
208
+ }
209
+ return body.Token, nil
210
+ }
211
+
212
+ func main() {
213
+ // 初始化配置
214
+ initConfig()
215
+
216
+ // http.HandleFunc("/v1/models", handleModels)
217
+ // http.HandleFunc("/v1/chat/completions", handleChatCompletions)
218
+ http.HandleFunc("/api/v1/models", handleModels)
219
+ http.HandleFunc("/api/v1/chat/completions", handleChatCompletions)
220
+ http.HandleFunc("/", handleOptions)
221
+
222
+ log.Printf("OpenAI兼容API服务器启动在端口%s", PORT)
223
+ log.Printf("模型: %s", MODEL_NAME)
224
+ log.Printf("上游: %s", UPSTREAM_URL)
225
+ log.Printf("Debug模式: %v", DEBUG_MODE)
226
+ log.Printf("默认流式响应: %v", DEFAULT_STREAM)
227
+ log.Fatal(http.ListenAndServe(PORT, nil))
228
+ }
229
+
230
+ func handleOptions(w http.ResponseWriter, r *http.Request) {
231
+ setCORSHeaders(w)
232
+ if r.Method == "OPTIONS" {
233
+ w.WriteHeader(http.StatusOK)
234
+ return
235
+ }
236
+ w.WriteHeader(http.StatusNotFound)
237
+ }
238
+
239
+ func setCORSHeaders(w http.ResponseWriter) {
240
+ w.Header().Set("Access-Control-Allow-Origin", "*")
241
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
242
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
243
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
244
+ }
245
+
246
+ func handleModels(w http.ResponseWriter, r *http.Request) {
247
+ setCORSHeaders(w)
248
+ if r.Method == "OPTIONS" {
249
+ w.WriteHeader(http.StatusOK)
250
+ return
251
+ }
252
+
253
+ response := ModelsResponse{
254
+ Object: "list",
255
+ Data: []Model{
256
+ {
257
+ ID: MODEL_NAME,
258
+ Object: "model",
259
+ Created: time.Now().Unix(),
260
+ OwnedBy: "z.ai",
261
+ },
262
+ },
263
+ }
264
+
265
+ w.Header().Set("Content-Type", "application/json")
266
+ json.NewEncoder(w).Encode(response)
267
+ }
268
+
269
+ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
270
+ setCORSHeaders(w)
271
+ if r.Method == "OPTIONS" {
272
+ w.WriteHeader(http.StatusOK)
273
+ return
274
+ }
275
+
276
+ debugLog("收到chat completions请求")
277
+
278
+ // // 验证API Key
279
+ // authHeader := r.Header.Get("Authorization")
280
+ // if !strings.HasPrefix(authHeader, "Bearer ") {
281
+ // debugLog("缺少或无效的Authorization头")
282
+ // http.Error(w, "Missing or invalid Authorization header", http.StatusUnauthorized)
283
+ // return
284
+ // }
285
+ // // 去除key验证
286
+ // apiKey := strings.TrimPrefix(authHeader, "Bearer ")
287
+ // if apiKey != DEFAULT_KEY {
288
+ // debugLog("无效的API key: %s", apiKey)
289
+ // http.Error(w, "Invalid API key", http.StatusUnauthorized)
290
+ // return
291
+ // }
292
+
293
+ // debugLog("API key验证通过")
294
+
295
+ // 读取请求体
296
+ body, err := io.ReadAll(r.Body)
297
+ if err != nil {
298
+ debugLog("读取请求体失败: %v", err)
299
+ http.Error(w, "Failed to read request body", http.StatusBadRequest)
300
+ return
301
+ }
302
+
303
+ // 解析请求
304
+ var req OpenAIRequest
305
+ if err := json.Unmarshal(body, &req); err != nil {
306
+ debugLog("JSON解析失败: %v", err)
307
+ http.Error(w, "Invalid JSON", http.StatusBadRequest)
308
+ return
309
+ }
310
+
311
+ // 如果客户端没有明确指定stream参数,使用默认值
312
+ if !bytes.Contains(body, []byte(`"stream"`)) {
313
+ req.Stream = DEFAULT_STREAM
314
+ debugLog("客户端未指定stream参数,使用默认值: %v", DEFAULT_STREAM)
315
+ }
316
+
317
+ debugLog("请求解析成功 - 模型: %s, 流式: %v, 消息数: %d", req.Model, req.Stream, len(req.Messages))
318
+
319
+ // 生成会话相关ID
320
+ chatID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Unix())
321
+ msgID := fmt.Sprintf("%d", time.Now().UnixNano())
322
+
323
+ // 构造上游请求
324
+ upstreamReq := UpstreamRequest{
325
+ Stream: true, // 总是使用流式从上游获取
326
+ ChatID: chatID,
327
+ ID: msgID,
328
+ Model: "0727-360B-API", // 上游实际模型ID
329
+ Messages: req.Messages,
330
+ Params: map[string]interface{}{},
331
+ Features: map[string]interface{}{
332
+ "enable_thinking": true,
333
+ },
334
+ BackgroundTasks: map[string]bool{
335
+ "title_generation": false,
336
+ "tags_generation": false,
337
+ },
338
+ MCPServers: []string{},
339
+ ModelItem: struct {
340
+ ID string `json:"id"`
341
+ Name string `json:"name"`
342
+ OwnedBy string `json:"owned_by"`
343
+ }{ID: "0727-360B-API", Name: "GLM-4.5", OwnedBy: "openai"},
344
+ ToolServers: []string{},
345
+ Variables: map[string]string{
346
+ "{{USER_NAME}}": "User",
347
+ "{{USER_LOCATION}}": "Unknown",
348
+ "{{CURRENT_DATETIME}}": time.Now().Format("2006-01-02 15:04:05"),
349
+ },
350
+ }
351
+
352
+ // 选择本次对话使用的token
353
+ authToken := UPSTREAM_TOKEN
354
+ if ANON_TOKEN_ENABLED {
355
+ if t, err := getAnonymousToken(); err == nil {
356
+ authToken = t
357
+ debugLog("匿名token获取成功: %s...", func() string {
358
+ if len(t) > 10 {
359
+ return t[:10]
360
+ }
361
+ return t
362
+ }())
363
+ } else {
364
+ debugLog("匿名token获取失败,回退固定token: %v", err)
365
+ }
366
+ }
367
+
368
+ // 调用上游API
369
+ if req.Stream {
370
+ handleStreamResponseWithIDs(w, upstreamReq, chatID, authToken)
371
+ } else {
372
+ handleNonStreamResponseWithIDs(w, upstreamReq, chatID, authToken)
373
+ }
374
+ }
375
+
376
+ func callUpstreamWithHeaders(upstreamReq UpstreamRequest, refererChatID string, authToken string) (*http.Response, error) {
377
+ reqBody, err := json.Marshal(upstreamReq)
378
+ if err != nil {
379
+ debugLog("上游请求序列化失败: %v", err)
380
+ return nil, err
381
+ }
382
+
383
+ debugLog("调用上游API: %s", UPSTREAM_URL)
384
+ debugLog("上游请求体: %s", string(reqBody))
385
+
386
+ req, err := http.NewRequest("POST", UPSTREAM_URL, bytes.NewBuffer(reqBody))
387
+ if err != nil {
388
+ debugLog("创建HTTP请求失败: %v", err)
389
+ return nil, err
390
+ }
391
+
392
+ req.Header.Set("Content-Type", "application/json")
393
+ req.Header.Set("Accept", "application/json, text/event-stream")
394
+ req.Header.Set("User-Agent", BROWSER_UA)
395
+ req.Header.Set("Authorization", "Bearer "+authToken)
396
+ req.Header.Set("Accept-Language", "zh-CN")
397
+ req.Header.Set("sec-ch-ua", SEC_CH_UA)
398
+ req.Header.Set("sec-ch-ua-mobile", SEC_CH_UA_MOB)
399
+ req.Header.Set("sec-ch-ua-platform", SEC_CH_UA_PLAT)
400
+ req.Header.Set("X-FE-Version", X_FE_VERSION)
401
+ req.Header.Set("Origin", ORIGIN_BASE)
402
+ req.Header.Set("Referer", ORIGIN_BASE+"/c/"+refererChatID)
403
+
404
+ client := &http.Client{Timeout: 60 * time.Second}
405
+ resp, err := client.Do(req)
406
+ if err != nil {
407
+ debugLog("上游请求失败: %v", err)
408
+ return nil, err
409
+ }
410
+
411
+ debugLog("上游响应状态: %d %s", resp.StatusCode, resp.Status)
412
+ return resp, nil
413
+ }
414
+
415
+ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string) {
416
+ debugLog("开始处理流式响应 (chat_id=%s)", chatID)
417
+
418
+ resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
419
+ if err != nil {
420
+ debugLog("调用上游失败: %v", err)
421
+ http.Error(w, "Failed to call upstream", http.StatusBadGateway)
422
+ return
423
+ }
424
+ defer resp.Body.Close()
425
+
426
+ if resp.StatusCode != http.StatusOK {
427
+ debugLog("上游返回错误状态: %d", resp.StatusCode)
428
+ // 读取错误响应体
429
+ if DEBUG_MODE {
430
+ body, _ := io.ReadAll(resp.Body)
431
+ debugLog("上游错误响应: %s", string(body))
432
+ }
433
+ http.Error(w, "Upstream error", http.StatusBadGateway)
434
+ return
435
+ }
436
+
437
+ // 用于策略2:总是展示thinking(配合标签处理)
438
+ transformThinking := func(s string) string {
439
+ // 去 <summary>…</summary>
440
+ s = regexp.MustCompile(`(?s)<summary>.*?</summary>`).ReplaceAllString(s, "")
441
+ // 清理残留自定义标签,如 </thinking>、<Full> 等
442
+ s = strings.ReplaceAll(s, "</thinking>", "")
443
+ s = strings.ReplaceAll(s, "<Full>", "")
444
+ s = strings.ReplaceAll(s, "</Full>", "")
445
+ s = strings.TrimSpace(s)
446
+ switch THINK_TAGS_MODE {
447
+ case "think":
448
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "<think>")
449
+ s = strings.ReplaceAll(s, "</details>", "</think>")
450
+ case "strip":
451
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "")
452
+ s = strings.ReplaceAll(s, "</details>", "")
453
+ }
454
+ // 处理每行前缀 "> "(包括起始位置)
455
+ s = strings.TrimPrefix(s, "> ")
456
+ s = strings.ReplaceAll(s, "\n> ", "\n")
457
+ return strings.TrimSpace(s)
458
+ }
459
+
460
+ // 设置SSE头部
461
+ w.Header().Set("Content-Type", "text/event-stream")
462
+ w.Header().Set("Cache-Control", "no-cache")
463
+ w.Header().Set("Connection", "keep-alive")
464
+
465
+ flusher, ok := w.(http.Flusher)
466
+ if !ok {
467
+ http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
468
+ return
469
+ }
470
+
471
+ // 发送第一个chunk(role)
472
+ firstChunk := OpenAIResponse{
473
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
474
+ Object: "chat.completion.chunk",
475
+ Created: time.Now().Unix(),
476
+ Model: MODEL_NAME,
477
+ Choices: []Choice{
478
+ {
479
+ Index: 0,
480
+ Delta: Delta{Role: "assistant"},
481
+ },
482
+ },
483
+ }
484
+ writeSSEChunk(w, firstChunk)
485
+ flusher.Flush()
486
+
487
+ // 读取上游SSE流
488
+ debugLog("开始读取上游SSE流")
489
+ scanner := bufio.NewScanner(resp.Body)
490
+ lineCount := 0
491
+
492
+ for scanner.Scan() {
493
+ line := scanner.Text()
494
+ lineCount++
495
+
496
+ if !strings.HasPrefix(line, "data: ") {
497
+ continue
498
+ }
499
+
500
+ dataStr := strings.TrimPrefix(line, "data: ")
501
+ if dataStr == "" {
502
+ continue
503
+ }
504
+
505
+ debugLog("收到SSE数据 (第%d行): %s", lineCount, dataStr)
506
+
507
+ var upstreamData UpstreamData
508
+ if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
509
+ debugLog("SSE数据解析失败: %v", err)
510
+ continue
511
+ }
512
+
513
+ // 错误检测(data.error 或 data.data.error 或 顶层error)
514
+ if (upstreamData.Error != nil) || (upstreamData.Data.Error != nil) || (upstreamData.Data.Inner != nil && upstreamData.Data.Inner.Error != nil) {
515
+ errObj := upstreamData.Error
516
+ if errObj == nil {
517
+ errObj = upstreamData.Data.Error
518
+ }
519
+ if errObj == nil && upstreamData.Data.Inner != nil {
520
+ errObj = upstreamData.Data.Inner.Error
521
+ }
522
+ debugLog("上游错误: code=%d, detail=%s", errObj.Code, errObj.Detail)
523
+ // 结束下游流
524
+ endChunk := OpenAIResponse{
525
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
526
+ Object: "chat.completion.chunk",
527
+ Created: time.Now().Unix(),
528
+ Model: MODEL_NAME,
529
+ Choices: []Choice{{Index: 0, Delta: Delta{}, FinishReason: "stop"}},
530
+ }
531
+ writeSSEChunk(w, endChunk)
532
+ fmt.Fprintf(w, "data: [DONE]\n\n")
533
+ flusher.Flush()
534
+ break
535
+ }
536
+
537
+ debugLog("解析成功 - 类型: %s, 阶段: %s, 内容长度: %d, 完成: %v",
538
+ upstreamData.Type, upstreamData.Data.Phase, len(upstreamData.Data.DeltaContent), upstreamData.Data.Done)
539
+
540
+ // 策略2:总是展示thinking + answer
541
+ if upstreamData.Data.DeltaContent != "" {
542
+ var out = upstreamData.Data.DeltaContent
543
+ if upstreamData.Data.Phase == "thinking" {
544
+ out = transformThinking(out)
545
+ }
546
+ if out != "" {
547
+ debugLog("发送内容(%s): %s", upstreamData.Data.Phase, out)
548
+ chunk := OpenAIResponse{
549
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
550
+ Object: "chat.completion.chunk",
551
+ Created: time.Now().Unix(),
552
+ Model: MODEL_NAME,
553
+ Choices: []Choice{
554
+ {
555
+ Index: 0,
556
+ Delta: Delta{Content: out},
557
+ },
558
+ },
559
+ }
560
+ writeSSEChunk(w, chunk)
561
+ flusher.Flush()
562
+ }
563
+ }
564
+
565
+ // 检查是否结束
566
+ if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
567
+ debugLog("检测到流结束信号")
568
+ // 发送结束chunk
569
+ endChunk := OpenAIResponse{
570
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
571
+ Object: "chat.completion.chunk",
572
+ Created: time.Now().Unix(),
573
+ Model: MODEL_NAME,
574
+ Choices: []Choice{
575
+ {
576
+ Index: 0,
577
+ Delta: Delta{},
578
+ FinishReason: "stop",
579
+ },
580
+ },
581
+ }
582
+ writeSSEChunk(w, endChunk)
583
+ flusher.Flush()
584
+
585
+ // 发送[DONE]
586
+ fmt.Fprintf(w, "data: [DONE]\n\n")
587
+ flusher.Flush()
588
+ debugLog("流式响应完成,共处理%d行", lineCount)
589
+ break
590
+ }
591
+ }
592
+
593
+ if err := scanner.Err(); err != nil {
594
+ debugLog("扫描器错误: %v", err)
595
+ }
596
+ }
597
+
598
+ func writeSSEChunk(w http.ResponseWriter, chunk OpenAIResponse) {
599
+ data, _ := json.Marshal(chunk)
600
+ fmt.Fprintf(w, "data: %s\n\n", data)
601
+ }
602
+
603
+ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string) {
604
+ debugLog("开始处理非流式响应 (chat_id=%s)", chatID)
605
+
606
+ resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
607
+ if err != nil {
608
+ debugLog("调用上游失败: %v", err)
609
+ http.Error(w, "Failed to call upstream", http.StatusBadGateway)
610
+ return
611
+ }
612
+ defer resp.Body.Close()
613
+
614
+ if resp.StatusCode != http.StatusOK {
615
+ debugLog("上游返回错误状态: %d", resp.StatusCode)
616
+ // 读取错误响应体
617
+ if DEBUG_MODE {
618
+ body, _ := io.ReadAll(resp.Body)
619
+ debugLog("上游错误响应: %s", string(body))
620
+ }
621
+ http.Error(w, "Upstream error", http.StatusBadGateway)
622
+ return
623
+ }
624
+
625
+ // 收集完整响应(策略2:thinking与answer都纳入,thinking转换)
626
+ var fullContent strings.Builder
627
+ scanner := bufio.NewScanner(resp.Body)
628
+ debugLog("开始收集完整响应内容")
629
+
630
+ for scanner.Scan() {
631
+ line := scanner.Text()
632
+ if !strings.HasPrefix(line, "data: ") {
633
+ continue
634
+ }
635
+
636
+ dataStr := strings.TrimPrefix(line, "data: ")
637
+ if dataStr == "" {
638
+ continue
639
+ }
640
+
641
+ var upstreamData UpstreamData
642
+ if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
643
+ continue
644
+ }
645
+
646
+ if upstreamData.Data.DeltaContent != "" {
647
+ out := upstreamData.Data.DeltaContent
648
+ if upstreamData.Data.Phase == "thinking" {
649
+ out = func(s string) string {
650
+ // 同步一份转换逻辑(与流式一致)
651
+ s = regexp.MustCompile(`(?s)<summary>.*?</summary>`).ReplaceAllString(s, "")
652
+ s = strings.ReplaceAll(s, "</thinking>", "")
653
+ s = strings.ReplaceAll(s, "<Full>", "")
654
+ s = strings.ReplaceAll(s, "</Full>", "")
655
+ s = strings.TrimSpace(s)
656
+ switch THINK_TAGS_MODE {
657
+ case "think":
658
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "<think>")
659
+ s = strings.ReplaceAll(s, "</details>", "</think>")
660
+ case "strip":
661
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "")
662
+ s = strings.ReplaceAll(s, "</details>", "")
663
+ }
664
+ s = strings.TrimPrefix(s, "> ")
665
+ s = strings.ReplaceAll(s, "\n> ", "\n")
666
+ return strings.TrimSpace(s)
667
+ }(out)
668
+ }
669
+ if out != "" {
670
+ fullContent.WriteString(out)
671
+ }
672
+ }
673
+
674
+ if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
675
+ debugLog("��测到完成信号,停止收集")
676
+ break
677
+ }
678
+ }
679
+
680
+ finalContent := fullContent.String()
681
+ debugLog("内容收集完成,最终长度: %d", len(finalContent))
682
+
683
+ // 构造完整响应
684
+ response := OpenAIResponse{
685
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
686
+ Object: "chat.completion",
687
+ Created: time.Now().Unix(),
688
+ Model: MODEL_NAME,
689
+ Choices: []Choice{
690
+ {
691
+ Index: 0,
692
+ Message: Message{
693
+ Role: "assistant",
694
+ Content: finalContent,
695
+ },
696
+ FinishReason: "stop",
697
+ },
698
+ },
699
+ Usage: Usage{
700
+ PromptTokens: 0,
701
+ CompletionTokens: 0,
702
+ TotalTokens: 0,
703
+ },
704
+ }
705
+
706
+ w.Header().Set("Content-Type", "application/json")
707
+ json.NewEncoder(w).Encode(response)
708
+ debugLog("非流式响应发送完成")
709
+ }