Jack698 commited on
Commit
cc3fa57
·
verified ·
1 Parent(s): 2a226cc

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .dockerignore +4 -0
  2. .env +3 -0
  3. Dockerfile +48 -0
  4. go.mod +5 -0
  5. go.sum +0 -0
  6. main.go +707 -0
.dockerignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .git
2
+ README.md
3
+ *.log
4
+ main
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ DEFAULT_KEY=sk-159357
2
+ MODEL_NAME=GLM-4.5
3
+ PORT=7860
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 多阶段构建 Dockerfile,适用于 Hugging Face Spaces
2
+ # 第一阶段:构建 Go 应用
3
+ FROM golang:1.23-alpine AS builder
4
+
5
+ # 安装必要的工具
6
+ RUN apk add --no-cache git ca-certificates
7
+
8
+ # 设置工作目录
9
+ WORKDIR /app
10
+
11
+ # 复制 go mod 文件
12
+ COPY go.mod go.sum ./
13
+
14
+ # 下载依赖
15
+ RUN go mod download
16
+
17
+ # 复制源代码
18
+ COPY . .
19
+
20
+ # 构建 Go 应用(禁用 CGO)
21
+ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
22
+
23
+ # 第二阶段:运行时镜像
24
+ FROM alpine:latest
25
+
26
+ # 安装 ca-certificates(用于 HTTPS)
27
+ RUN apk --no-cache add ca-certificates tzdata
28
+
29
+ # 设置时区
30
+ ENV TZ=Asia/Shanghai
31
+
32
+ # 创建非 root 用户
33
+ RUN addgroup -g 1000 appgroup && adduser -u 1000 -G appgroup -s /bin/sh -D appuser
34
+
35
+ # 设置工作目录
36
+ WORKDIR /app
37
+
38
+ # 从构建阶段复制二进制文件
39
+ COPY --from=builder /app/main .
40
+
41
+ # 切换到非 root 用户
42
+ USER appuser
43
+
44
+ # 暴露端口(Hugging Face Spaces 默认端口)
45
+ EXPOSE 7860
46
+
47
+ # 启动命令
48
+ CMD ["./main"]
go.mod ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ module z2api
2
+
3
+ go 1.23.0
4
+
5
+ toolchain go1.23.4
go.sum ADDED
File without changes
main.go ADDED
@@ -0,0 +1,707 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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", "8080")
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("/", handleOptions)
219
+
220
+ log.Printf("OpenAI兼容API服务器启动在端口%s", PORT)
221
+ log.Printf("模型: %s", MODEL_NAME)
222
+ log.Printf("上游: %s", UPSTREAM_URL)
223
+ log.Printf("Debug模式: %v", DEBUG_MODE)
224
+ log.Printf("默认流式响应: %v", DEFAULT_STREAM)
225
+ log.Fatal(http.ListenAndServe(PORT, nil))
226
+ }
227
+
228
+ func handleOptions(w http.ResponseWriter, r *http.Request) {
229
+ setCORSHeaders(w)
230
+ if r.Method == "OPTIONS" {
231
+ w.WriteHeader(http.StatusOK)
232
+ return
233
+ }
234
+ w.WriteHeader(http.StatusNotFound)
235
+ }
236
+
237
+ func setCORSHeaders(w http.ResponseWriter) {
238
+ w.Header().Set("Access-Control-Allow-Origin", "*")
239
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
240
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
241
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
242
+ }
243
+
244
+ func handleModels(w http.ResponseWriter, r *http.Request) {
245
+ setCORSHeaders(w)
246
+ if r.Method == "OPTIONS" {
247
+ w.WriteHeader(http.StatusOK)
248
+ return
249
+ }
250
+
251
+ response := ModelsResponse{
252
+ Object: "list",
253
+ Data: []Model{
254
+ {
255
+ ID: MODEL_NAME,
256
+ Object: "model",
257
+ Created: time.Now().Unix(),
258
+ OwnedBy: "z.ai",
259
+ },
260
+ },
261
+ }
262
+
263
+ w.Header().Set("Content-Type", "application/json")
264
+ json.NewEncoder(w).Encode(response)
265
+ }
266
+
267
+ func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
268
+ setCORSHeaders(w)
269
+ if r.Method == "OPTIONS" {
270
+ w.WriteHeader(http.StatusOK)
271
+ return
272
+ }
273
+
274
+ debugLog("收到chat completions请求")
275
+
276
+ // 验证API Key
277
+ authHeader := r.Header.Get("Authorization")
278
+ if !strings.HasPrefix(authHeader, "Bearer ") {
279
+ debugLog("缺少或无效的Authorization头")
280
+ http.Error(w, "Missing or invalid Authorization header", http.StatusUnauthorized)
281
+ return
282
+ }
283
+
284
+ apiKey := strings.TrimPrefix(authHeader, "Bearer ")
285
+ if apiKey != DEFAULT_KEY {
286
+ debugLog("无效的API key: %s", apiKey)
287
+ http.Error(w, "Invalid API key", http.StatusUnauthorized)
288
+ return
289
+ }
290
+
291
+ debugLog("API key验证通过")
292
+
293
+ // 读取请求体
294
+ body, err := io.ReadAll(r.Body)
295
+ if err != nil {
296
+ debugLog("读取请求体失败: %v", err)
297
+ http.Error(w, "Failed to read request body", http.StatusBadRequest)
298
+ return
299
+ }
300
+
301
+ // 解析请求
302
+ var req OpenAIRequest
303
+ if err := json.Unmarshal(body, &req); err != nil {
304
+ debugLog("JSON解析失败: %v", err)
305
+ http.Error(w, "Invalid JSON", http.StatusBadRequest)
306
+ return
307
+ }
308
+
309
+ // 如果客户端没有明确指定stream参数,使用默认值
310
+ if !bytes.Contains(body, []byte(`"stream"`)) {
311
+ req.Stream = DEFAULT_STREAM
312
+ debugLog("客户端未指定stream参数,使用默认值: %v", DEFAULT_STREAM)
313
+ }
314
+
315
+ debugLog("请求解析成功 - 模型: %s, 流式: %v, 消息数: %d", req.Model, req.Stream, len(req.Messages))
316
+
317
+ // 生成会话相关ID
318
+ chatID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Unix())
319
+ msgID := fmt.Sprintf("%d", time.Now().UnixNano())
320
+
321
+ // 构造上游请求
322
+ upstreamReq := UpstreamRequest{
323
+ Stream: true, // 总是使用流式从上游获取
324
+ ChatID: chatID,
325
+ ID: msgID,
326
+ Model: "0727-360B-API", // 上游实际模型ID
327
+ Messages: req.Messages,
328
+ Params: map[string]interface{}{},
329
+ Features: map[string]interface{}{
330
+ "enable_thinking": true,
331
+ },
332
+ BackgroundTasks: map[string]bool{
333
+ "title_generation": false,
334
+ "tags_generation": false,
335
+ },
336
+ MCPServers: []string{},
337
+ ModelItem: struct {
338
+ ID string `json:"id"`
339
+ Name string `json:"name"`
340
+ OwnedBy string `json:"owned_by"`
341
+ }{ID: "0727-360B-API", Name: "GLM-4.5", OwnedBy: "openai"},
342
+ ToolServers: []string{},
343
+ Variables: map[string]string{
344
+ "{{USER_NAME}}": "User",
345
+ "{{USER_LOCATION}}": "Unknown",
346
+ "{{CURRENT_DATETIME}}": time.Now().Format("2006-01-02 15:04:05"),
347
+ },
348
+ }
349
+
350
+ // 选择本次对话使用的token
351
+ authToken := UPSTREAM_TOKEN
352
+ if ANON_TOKEN_ENABLED {
353
+ if t, err := getAnonymousToken(); err == nil {
354
+ authToken = t
355
+ debugLog("匿名token获取成功: %s...", func() string {
356
+ if len(t) > 10 {
357
+ return t[:10]
358
+ }
359
+ return t
360
+ }())
361
+ } else {
362
+ debugLog("匿名token获取失败,回退固定token: %v", err)
363
+ }
364
+ }
365
+
366
+ // 调用上游API
367
+ if req.Stream {
368
+ handleStreamResponseWithIDs(w, upstreamReq, chatID, authToken)
369
+ } else {
370
+ handleNonStreamResponseWithIDs(w, upstreamReq, chatID, authToken)
371
+ }
372
+ }
373
+
374
+ func callUpstreamWithHeaders(upstreamReq UpstreamRequest, refererChatID string, authToken string) (*http.Response, error) {
375
+ reqBody, err := json.Marshal(upstreamReq)
376
+ if err != nil {
377
+ debugLog("上游请求序列化失败: %v", err)
378
+ return nil, err
379
+ }
380
+
381
+ debugLog("调用上游API: %s", UPSTREAM_URL)
382
+ debugLog("上游请求体: %s", string(reqBody))
383
+
384
+ req, err := http.NewRequest("POST", UPSTREAM_URL, bytes.NewBuffer(reqBody))
385
+ if err != nil {
386
+ debugLog("创建HTTP请求失败: %v", err)
387
+ return nil, err
388
+ }
389
+
390
+ req.Header.Set("Content-Type", "application/json")
391
+ req.Header.Set("Accept", "application/json, text/event-stream")
392
+ req.Header.Set("User-Agent", BROWSER_UA)
393
+ req.Header.Set("Authorization", "Bearer "+authToken)
394
+ req.Header.Set("Accept-Language", "zh-CN")
395
+ req.Header.Set("sec-ch-ua", SEC_CH_UA)
396
+ req.Header.Set("sec-ch-ua-mobile", SEC_CH_UA_MOB)
397
+ req.Header.Set("sec-ch-ua-platform", SEC_CH_UA_PLAT)
398
+ req.Header.Set("X-FE-Version", X_FE_VERSION)
399
+ req.Header.Set("Origin", ORIGIN_BASE)
400
+ req.Header.Set("Referer", ORIGIN_BASE+"/c/"+refererChatID)
401
+
402
+ client := &http.Client{Timeout: 60 * time.Second}
403
+ resp, err := client.Do(req)
404
+ if err != nil {
405
+ debugLog("上游请求失败: %v", err)
406
+ return nil, err
407
+ }
408
+
409
+ debugLog("上游响应状态: %d %s", resp.StatusCode, resp.Status)
410
+ return resp, nil
411
+ }
412
+
413
+ func handleStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string) {
414
+ debugLog("开始处理流式响应 (chat_id=%s)", chatID)
415
+
416
+ resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
417
+ if err != nil {
418
+ debugLog("调用上游失败: %v", err)
419
+ http.Error(w, "Failed to call upstream", http.StatusBadGateway)
420
+ return
421
+ }
422
+ defer resp.Body.Close()
423
+
424
+ if resp.StatusCode != http.StatusOK {
425
+ debugLog("上游返回错误状态: %d", resp.StatusCode)
426
+ // 读取错误响应体
427
+ if DEBUG_MODE {
428
+ body, _ := io.ReadAll(resp.Body)
429
+ debugLog("上游错误响应: %s", string(body))
430
+ }
431
+ http.Error(w, "Upstream error", http.StatusBadGateway)
432
+ return
433
+ }
434
+
435
+ // 用于策略2:总是展示thinking(配合标签处理)
436
+ transformThinking := func(s string) string {
437
+ // 去 <summary>…</summary>
438
+ s = regexp.MustCompile(`(?s)<summary>.*?</summary>`).ReplaceAllString(s, "")
439
+ // 清理残留自定义标签,如 </thinking>、<Full> 等
440
+ s = strings.ReplaceAll(s, "</thinking>", "")
441
+ s = strings.ReplaceAll(s, "<Full>", "")
442
+ s = strings.ReplaceAll(s, "</Full>", "")
443
+ s = strings.TrimSpace(s)
444
+ switch THINK_TAGS_MODE {
445
+ case "think":
446
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "<think>")
447
+ s = strings.ReplaceAll(s, "</details>", "</think>")
448
+ case "strip":
449
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "")
450
+ s = strings.ReplaceAll(s, "</details>", "")
451
+ }
452
+ // 处理每行前缀 "> "(包括起始位置)
453
+ s = strings.TrimPrefix(s, "> ")
454
+ s = strings.ReplaceAll(s, "\n> ", "\n")
455
+ return strings.TrimSpace(s)
456
+ }
457
+
458
+ // 设置SSE头部
459
+ w.Header().Set("Content-Type", "text/event-stream")
460
+ w.Header().Set("Cache-Control", "no-cache")
461
+ w.Header().Set("Connection", "keep-alive")
462
+
463
+ flusher, ok := w.(http.Flusher)
464
+ if !ok {
465
+ http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
466
+ return
467
+ }
468
+
469
+ // 发送第一个chunk(role)
470
+ firstChunk := OpenAIResponse{
471
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
472
+ Object: "chat.completion.chunk",
473
+ Created: time.Now().Unix(),
474
+ Model: MODEL_NAME,
475
+ Choices: []Choice{
476
+ {
477
+ Index: 0,
478
+ Delta: Delta{Role: "assistant"},
479
+ },
480
+ },
481
+ }
482
+ writeSSEChunk(w, firstChunk)
483
+ flusher.Flush()
484
+
485
+ // 读取上游SSE流
486
+ debugLog("开始读取上游SSE流")
487
+ scanner := bufio.NewScanner(resp.Body)
488
+ lineCount := 0
489
+
490
+ for scanner.Scan() {
491
+ line := scanner.Text()
492
+ lineCount++
493
+
494
+ if !strings.HasPrefix(line, "data: ") {
495
+ continue
496
+ }
497
+
498
+ dataStr := strings.TrimPrefix(line, "data: ")
499
+ if dataStr == "" {
500
+ continue
501
+ }
502
+
503
+ debugLog("收到SSE数据 (第%d行): %s", lineCount, dataStr)
504
+
505
+ var upstreamData UpstreamData
506
+ if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
507
+ debugLog("SSE数据解析失败: %v", err)
508
+ continue
509
+ }
510
+
511
+ // 错误检测(data.error 或 data.data.error 或 顶层error)
512
+ if (upstreamData.Error != nil) || (upstreamData.Data.Error != nil) || (upstreamData.Data.Inner != nil && upstreamData.Data.Inner.Error != nil) {
513
+ errObj := upstreamData.Error
514
+ if errObj == nil {
515
+ errObj = upstreamData.Data.Error
516
+ }
517
+ if errObj == nil && upstreamData.Data.Inner != nil {
518
+ errObj = upstreamData.Data.Inner.Error
519
+ }
520
+ debugLog("上游错误: code=%d, detail=%s", errObj.Code, errObj.Detail)
521
+ // 结束下游流
522
+ endChunk := OpenAIResponse{
523
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
524
+ Object: "chat.completion.chunk",
525
+ Created: time.Now().Unix(),
526
+ Model: MODEL_NAME,
527
+ Choices: []Choice{{Index: 0, Delta: Delta{}, FinishReason: "stop"}},
528
+ }
529
+ writeSSEChunk(w, endChunk)
530
+ fmt.Fprintf(w, "data: [DONE]\n\n")
531
+ flusher.Flush()
532
+ break
533
+ }
534
+
535
+ debugLog("解析成功 - 类型: %s, 阶段: %s, 内容长度: %d, 完成: %v",
536
+ upstreamData.Type, upstreamData.Data.Phase, len(upstreamData.Data.DeltaContent), upstreamData.Data.Done)
537
+
538
+ // 策略2:总是展示thinking + answer
539
+ if upstreamData.Data.DeltaContent != "" {
540
+ var out = upstreamData.Data.DeltaContent
541
+ if upstreamData.Data.Phase == "thinking" {
542
+ out = transformThinking(out)
543
+ }
544
+ if out != "" {
545
+ debugLog("发送内容(%s): %s", upstreamData.Data.Phase, out)
546
+ chunk := OpenAIResponse{
547
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
548
+ Object: "chat.completion.chunk",
549
+ Created: time.Now().Unix(),
550
+ Model: MODEL_NAME,
551
+ Choices: []Choice{
552
+ {
553
+ Index: 0,
554
+ Delta: Delta{Content: out},
555
+ },
556
+ },
557
+ }
558
+ writeSSEChunk(w, chunk)
559
+ flusher.Flush()
560
+ }
561
+ }
562
+
563
+ // 检查是否结束
564
+ if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
565
+ debugLog("检测到流结束信号")
566
+ // 发送结束chunk
567
+ endChunk := OpenAIResponse{
568
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
569
+ Object: "chat.completion.chunk",
570
+ Created: time.Now().Unix(),
571
+ Model: MODEL_NAME,
572
+ Choices: []Choice{
573
+ {
574
+ Index: 0,
575
+ Delta: Delta{},
576
+ FinishReason: "stop",
577
+ },
578
+ },
579
+ }
580
+ writeSSEChunk(w, endChunk)
581
+ flusher.Flush()
582
+
583
+ // 发送[DONE]
584
+ fmt.Fprintf(w, "data: [DONE]\n\n")
585
+ flusher.Flush()
586
+ debugLog("流式响应完成,共处理%d行", lineCount)
587
+ break
588
+ }
589
+ }
590
+
591
+ if err := scanner.Err(); err != nil {
592
+ debugLog("扫描器错误: %v", err)
593
+ }
594
+ }
595
+
596
+ func writeSSEChunk(w http.ResponseWriter, chunk OpenAIResponse) {
597
+ data, _ := json.Marshal(chunk)
598
+ fmt.Fprintf(w, "data: %s\n\n", data)
599
+ }
600
+
601
+ func handleNonStreamResponseWithIDs(w http.ResponseWriter, upstreamReq UpstreamRequest, chatID string, authToken string) {
602
+ debugLog("开始处理非流式响应 (chat_id=%s)", chatID)
603
+
604
+ resp, err := callUpstreamWithHeaders(upstreamReq, chatID, authToken)
605
+ if err != nil {
606
+ debugLog("调用上游失败: %v", err)
607
+ http.Error(w, "Failed to call upstream", http.StatusBadGateway)
608
+ return
609
+ }
610
+ defer resp.Body.Close()
611
+
612
+ if resp.StatusCode != http.StatusOK {
613
+ debugLog("上游返回错误状态: %d", resp.StatusCode)
614
+ // 读取错误响应体
615
+ if DEBUG_MODE {
616
+ body, _ := io.ReadAll(resp.Body)
617
+ debugLog("上游错误响应: %s", string(body))
618
+ }
619
+ http.Error(w, "Upstream error", http.StatusBadGateway)
620
+ return
621
+ }
622
+
623
+ // 收集完整响应(策略2:thinking与answer都纳入,thinking转换)
624
+ var fullContent strings.Builder
625
+ scanner := bufio.NewScanner(resp.Body)
626
+ debugLog("开始收集完整响应内容")
627
+
628
+ for scanner.Scan() {
629
+ line := scanner.Text()
630
+ if !strings.HasPrefix(line, "data: ") {
631
+ continue
632
+ }
633
+
634
+ dataStr := strings.TrimPrefix(line, "data: ")
635
+ if dataStr == "" {
636
+ continue
637
+ }
638
+
639
+ var upstreamData UpstreamData
640
+ if err := json.Unmarshal([]byte(dataStr), &upstreamData); err != nil {
641
+ continue
642
+ }
643
+
644
+ if upstreamData.Data.DeltaContent != "" {
645
+ out := upstreamData.Data.DeltaContent
646
+ if upstreamData.Data.Phase == "thinking" {
647
+ out = func(s string) string {
648
+ // 同步一份转换逻辑(与流式一致)
649
+ s = regexp.MustCompile(`(?s)<summary>.*?</summary>`).ReplaceAllString(s, "")
650
+ s = strings.ReplaceAll(s, "</thinking>", "")
651
+ s = strings.ReplaceAll(s, "<Full>", "")
652
+ s = strings.ReplaceAll(s, "</Full>", "")
653
+ s = strings.TrimSpace(s)
654
+ switch THINK_TAGS_MODE {
655
+ case "think":
656
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "<think>")
657
+ s = strings.ReplaceAll(s, "</details>", "</think>")
658
+ case "strip":
659
+ s = regexp.MustCompile(`<details[^>]*>`).ReplaceAllString(s, "")
660
+ s = strings.ReplaceAll(s, "</details>", "")
661
+ }
662
+ s = strings.TrimPrefix(s, "> ")
663
+ s = strings.ReplaceAll(s, "\n> ", "\n")
664
+ return strings.TrimSpace(s)
665
+ }(out)
666
+ }
667
+ if out != "" {
668
+ fullContent.WriteString(out)
669
+ }
670
+ }
671
+
672
+ if upstreamData.Data.Done || upstreamData.Data.Phase == "done" {
673
+ debugLog("检测到完成信号,停止收集")
674
+ break
675
+ }
676
+ }
677
+
678
+ finalContent := fullContent.String()
679
+ debugLog("内容收集完成,最终长度: %d", len(finalContent))
680
+
681
+ // 构造完整响应
682
+ response := OpenAIResponse{
683
+ ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
684
+ Object: "chat.completion",
685
+ Created: time.Now().Unix(),
686
+ Model: MODEL_NAME,
687
+ Choices: []Choice{
688
+ {
689
+ Index: 0,
690
+ Message: Message{
691
+ Role: "assistant",
692
+ Content: finalContent,
693
+ },
694
+ FinishReason: "stop",
695
+ },
696
+ },
697
+ Usage: Usage{
698
+ PromptTokens: 0,
699
+ CompletionTokens: 0,
700
+ TotalTokens: 0,
701
+ },
702
+ }
703
+
704
+ w.Header().Set("Content-Type", "application/json")
705
+ json.NewEncoder(w).Encode(response)
706
+ debugLog("非流式响应发送完成")
707
+ }