Spaces:
Configuration error
Configuration error
BOHE
commited on
Commit
·
3e3f9fc
1
Parent(s):
8f37986
添加对上下文的改动(测试)
Browse files- api/main.go +325 -46
- go.mod +1 -0
- go.sum +4 -0
api/main.go
CHANGED
|
@@ -2,17 +2,37 @@ package handler
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"bufio"
|
|
|
|
| 5 |
"encoding/json"
|
| 6 |
"fmt"
|
|
|
|
|
|
|
| 7 |
"net/http"
|
|
|
|
|
|
|
| 8 |
"strings"
|
| 9 |
"time"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
)
|
| 11 |
|
|
|
|
| 12 |
type YouChatResponse struct {
|
| 13 |
YouChatToken string `json:"youChatToken"`
|
| 14 |
}
|
| 15 |
|
|
|
|
| 16 |
type OpenAIStreamResponse struct {
|
| 17 |
ID string `json:"id"`
|
| 18 |
Object string `json:"object"`
|
|
@@ -21,27 +41,32 @@ type OpenAIStreamResponse struct {
|
|
| 21 |
Choices []Choice `json:"choices"`
|
| 22 |
}
|
| 23 |
|
|
|
|
| 24 |
type Choice struct {
|
| 25 |
Delta Delta `json:"delta"`
|
| 26 |
Index int `json:"index"`
|
| 27 |
FinishReason string `json:"finish_reason"`
|
| 28 |
}
|
| 29 |
|
|
|
|
| 30 |
type Delta struct {
|
| 31 |
Content string `json:"content"`
|
| 32 |
}
|
| 33 |
|
|
|
|
| 34 |
type OpenAIRequest struct {
|
| 35 |
Messages []Message `json:"messages"`
|
| 36 |
Stream bool `json:"stream"`
|
| 37 |
Model string `json:"model"`
|
| 38 |
}
|
| 39 |
|
|
|
|
| 40 |
type Message struct {
|
| 41 |
Role string `json:"role"`
|
| 42 |
Content string `json:"content"`
|
| 43 |
}
|
| 44 |
|
|
|
|
| 45 |
type OpenAIResponse struct {
|
| 46 |
ID string `json:"id"`
|
| 47 |
Object string `json:"object"`
|
|
@@ -50,17 +75,20 @@ type OpenAIResponse struct {
|
|
| 50 |
Choices []OpenAIChoice `json:"choices"`
|
| 51 |
}
|
| 52 |
|
|
|
|
| 53 |
type OpenAIChoice struct {
|
| 54 |
Message Message `json:"message"`
|
| 55 |
Index int `json:"index"`
|
| 56 |
FinishReason string `json:"finish_reason"`
|
| 57 |
}
|
| 58 |
|
|
|
|
| 59 |
type ModelResponse struct {
|
| 60 |
Object string `json:"object"`
|
| 61 |
Data []ModelDetail `json:"data"`
|
| 62 |
}
|
| 63 |
|
|
|
|
| 64 |
type ModelDetail struct {
|
| 65 |
ID string `json:"id"`
|
| 66 |
Object string `json:"object"`
|
|
@@ -68,6 +96,7 @@ type ModelDetail struct {
|
|
| 68 |
OwnedBy string `json:"owned_by"`
|
| 69 |
}
|
| 70 |
|
|
|
|
| 71 |
var modelMap = map[string]string{
|
| 72 |
"deepseek-reasoner": "deepseek_r1",
|
| 73 |
"deepseek-chat": "deepseek_v3",
|
|
@@ -94,6 +123,7 @@ var modelMap = map[string]string{
|
|
| 94 |
"command-r-plus": "command_r_plus",
|
| 95 |
}
|
| 96 |
|
|
|
|
| 97 |
func getReverseModelMap() map[string]string {
|
| 98 |
reverse := make(map[string]string, len(modelMap))
|
| 99 |
for k, v := range modelMap {
|
|
@@ -102,24 +132,43 @@ func getReverseModelMap() map[string]string {
|
|
| 102 |
return reverse
|
| 103 |
}
|
| 104 |
|
|
|
|
| 105 |
func mapModelName(openAIModel string) string {
|
| 106 |
if mappedModel, exists := modelMap[openAIModel]; exists {
|
| 107 |
return mappedModel
|
| 108 |
}
|
| 109 |
-
return "deepseek_v3"
|
| 110 |
}
|
| 111 |
|
|
|
|
| 112 |
func reverseMapModelName(youModel string) string {
|
| 113 |
reverseMap := getReverseModelMap()
|
| 114 |
if mappedModel, exists := reverseMap[youModel]; exists {
|
| 115 |
return mappedModel
|
| 116 |
}
|
| 117 |
-
return "deepseek-chat"
|
| 118 |
}
|
| 119 |
|
|
|
|
| 120 |
var originalModel string
|
| 121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
| 123 |
if r.URL.Path == "/v1/models" || r.URL.Path == "/api/v1/models" {
|
| 124 |
w.Header().Set("Content-Type", "application/json")
|
| 125 |
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
@@ -151,7 +200,8 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 151 |
return
|
| 152 |
}
|
| 153 |
|
| 154 |
-
|
|
|
|
| 155 |
w.Header().Set("Content-Type", "application/json")
|
| 156 |
json.NewEncoder(w).Encode(map[string]string{
|
| 157 |
"status": "You2Api Service Running...",
|
|
@@ -160,6 +210,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 160 |
return
|
| 161 |
}
|
| 162 |
|
|
|
|
| 163 |
w.Header().Set("Access-Control-Allow-Origin", "*")
|
| 164 |
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
| 165 |
w.Header().Set("Access-Control-Allow-Headers", "*")
|
|
@@ -169,13 +220,15 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 169 |
return
|
| 170 |
}
|
| 171 |
|
|
|
|
| 172 |
authHeader := r.Header.Get("Authorization")
|
| 173 |
if !strings.HasPrefix(authHeader, "Bearer ") {
|
| 174 |
http.Error(w, "Missing or invalid authorization header", http.StatusUnauthorized)
|
| 175 |
return
|
| 176 |
}
|
| 177 |
-
dsToken := strings.TrimPrefix(authHeader, "Bearer ")
|
| 178 |
|
|
|
|
| 179 |
var openAIReq OpenAIRequest
|
| 180 |
if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil {
|
| 181 |
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
@@ -183,13 +236,25 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 183 |
}
|
| 184 |
|
| 185 |
originalModel = openAIReq.Model
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
var chatHistory []map[string]interface{}
|
| 188 |
for _, msg := range openAIReq.Messages {
|
| 189 |
chatMsg := map[string]interface{}{
|
| 190 |
"question": msg.Content,
|
| 191 |
"answer": "",
|
| 192 |
}
|
|
|
|
| 193 |
if msg.Role == "assistant" {
|
| 194 |
chatMsg["question"] = ""
|
| 195 |
chatMsg["answer"] = msg.Content
|
|
@@ -197,27 +262,114 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 197 |
chatHistory = append(chatHistory, chatMsg)
|
| 198 |
}
|
| 199 |
|
| 200 |
-
chatHistoryJSON, _ := json.Marshal(chatHistory)
|
| 201 |
|
|
|
|
| 202 |
youReq, _ := http.NewRequest("GET", "https://you.com/api/streamingSearch", nil)
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
|
|
|
| 221 |
youReq.Header = http.Header{
|
| 222 |
"sec-ch-ua-platform": {"Windows"},
|
| 223 |
"Cache-Control": {"no-cache"},
|
|
@@ -227,7 +379,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 227 |
"sec-ch-ua-mobile": {"?0"},
|
| 228 |
"sec-ch-ua-arch": {"x86"},
|
| 229 |
"sec-ch-ua-full-version": {"133.0.3065.39"},
|
| 230 |
-
"Accept": {"text/event-stream"},
|
| 231 |
"User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
| 232 |
"sec-ch-ua-platform-version": {"19.0.0"},
|
| 233 |
"Sec-Fetch-Site": {"same-origin"},
|
|
@@ -236,6 +388,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 236 |
"Host": {"you.com"},
|
| 237 |
}
|
| 238 |
|
|
|
|
| 239 |
cookies := getCookies(dsToken)
|
| 240 |
var cookieStrings []string
|
| 241 |
for name, value := range cookies {
|
|
@@ -243,29 +396,32 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|
| 243 |
}
|
| 244 |
youReq.Header.Add("Cookie", strings.Join(cookieStrings, ";"))
|
| 245 |
|
|
|
|
| 246 |
if !openAIReq.Stream {
|
| 247 |
-
handleNonStreamingResponse(w, youReq)
|
| 248 |
return
|
| 249 |
}
|
| 250 |
|
| 251 |
-
handleStreamingResponse(w, youReq)
|
| 252 |
}
|
| 253 |
|
|
|
|
| 254 |
func getCookies(dsToken string) map[string]string {
|
| 255 |
return map[string]string{
|
| 256 |
"guest_has_seen_legal_disclaimer": "true",
|
| 257 |
"youchat_personalization": "true",
|
| 258 |
-
"DS": dsToken,
|
| 259 |
-
"you_subscription": "youpro_standard_year",
|
| 260 |
"youpro_subscription": "true",
|
| 261 |
-
"ai_model": "deepseek_r1",
|
| 262 |
"youchat_smart_learn": "true",
|
| 263 |
}
|
| 264 |
}
|
| 265 |
|
|
|
|
| 266 |
func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
| 267 |
client := &http.Client{
|
| 268 |
-
Timeout: 60 * time.Second,
|
| 269 |
}
|
| 270 |
resp, err := client.Do(youReq)
|
| 271 |
if err != nil {
|
|
@@ -277,22 +433,24 @@ func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
|
| 277 |
var fullResponse strings.Builder
|
| 278 |
scanner := bufio.NewScanner(resp.Body)
|
| 279 |
|
|
|
|
| 280 |
buf := make([]byte, 0, 64*1024)
|
| 281 |
scanner.Buffer(buf, 1024*1024)
|
| 282 |
|
|
|
|
| 283 |
for scanner.Scan() {
|
| 284 |
line := scanner.Text()
|
| 285 |
if strings.HasPrefix(line, "event: youChatToken") {
|
| 286 |
-
scanner.Scan()
|
| 287 |
data := scanner.Text()
|
| 288 |
if !strings.HasPrefix(data, "data: ") {
|
| 289 |
-
continue
|
| 290 |
}
|
| 291 |
var token YouChatResponse
|
| 292 |
if err := json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token); err != nil {
|
| 293 |
-
continue
|
| 294 |
}
|
| 295 |
-
fullResponse.WriteString(token.YouChatToken)
|
| 296 |
}
|
| 297 |
}
|
| 298 |
|
|
@@ -301,19 +459,20 @@ func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
|
| 301 |
return
|
| 302 |
}
|
| 303 |
|
|
|
|
| 304 |
openAIResp := OpenAIResponse{
|
| 305 |
ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
|
| 306 |
Object: "chat.completion",
|
| 307 |
Created: time.Now().Unix(),
|
| 308 |
-
Model: reverseMapModelName(mapModelName(originalModel)),
|
| 309 |
Choices: []OpenAIChoice{
|
| 310 |
{
|
| 311 |
Message: Message{
|
| 312 |
Role: "assistant",
|
| 313 |
-
Content: fullResponse.String(),
|
| 314 |
},
|
| 315 |
Index: 0,
|
| 316 |
-
FinishReason: "stop",
|
| 317 |
},
|
| 318 |
},
|
| 319 |
}
|
|
@@ -325,8 +484,9 @@ func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
|
| 325 |
}
|
| 326 |
}
|
| 327 |
|
|
|
|
| 328 |
func handleStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
| 329 |
-
client := &http.Client{}
|
| 330 |
resp, err := client.Do(youReq)
|
| 331 |
if err != nil {
|
| 332 |
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
@@ -334,40 +494,159 @@ func handleStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
|
| 334 |
}
|
| 335 |
defer resp.Body.Close()
|
| 336 |
|
|
|
|
| 337 |
w.Header().Set("Content-Type", "text/event-stream")
|
| 338 |
w.Header().Set("Cache-Control", "no-cache")
|
| 339 |
w.Header().Set("Connection", "keep-alive")
|
| 340 |
|
| 341 |
scanner := bufio.NewScanner(resp.Body)
|
|
|
|
| 342 |
for scanner.Scan() {
|
| 343 |
line := scanner.Text()
|
| 344 |
|
| 345 |
if strings.HasPrefix(line, "event: youChatToken") {
|
| 346 |
-
scanner.Scan()
|
| 347 |
-
data := scanner.Text()
|
| 348 |
|
| 349 |
var token YouChatResponse
|
| 350 |
-
json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token)
|
| 351 |
|
|
|
|
| 352 |
openAIResp := OpenAIStreamResponse{
|
| 353 |
ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
|
| 354 |
Object: "chat.completion.chunk",
|
| 355 |
Created: time.Now().Unix(),
|
| 356 |
-
Model: reverseMapModelName(mapModelName(originalModel)),
|
| 357 |
Choices: []Choice{
|
| 358 |
{
|
| 359 |
Delta: Delta{
|
| 360 |
-
Content: token.YouChatToken,
|
| 361 |
},
|
| 362 |
Index: 0,
|
| 363 |
-
FinishReason: "",
|
| 364 |
},
|
| 365 |
},
|
| 366 |
}
|
| 367 |
|
| 368 |
-
respBytes, _ := json.Marshal(openAIResp)
|
| 369 |
-
fmt.Fprintf(w, "data: %s\n\n", string(respBytes))
|
| 370 |
-
w.(http.Flusher).Flush()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
}
|
| 372 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
}
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"bufio"
|
| 5 |
+
"bytes"
|
| 6 |
"encoding/json"
|
| 7 |
"fmt"
|
| 8 |
+
"io"
|
| 9 |
+
"mime/multipart"
|
| 10 |
"net/http"
|
| 11 |
+
"os"
|
| 12 |
+
"path/filepath"
|
| 13 |
"strings"
|
| 14 |
"time"
|
| 15 |
+
|
| 16 |
+
"github.com/google/uuid"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
// TokenCount 定义了 token 计数的结构
|
| 20 |
+
type TokenCount struct {
|
| 21 |
+
PromptTokens int `json:"prompt_tokens"`
|
| 22 |
+
CompletionTokens int `json:"completion_tokens"`
|
| 23 |
+
TotalTokens int `json:"total_tokens"`
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
const (
|
| 27 |
+
MaxContextTokens = 4000 // 最大上下文 token 数
|
| 28 |
)
|
| 29 |
|
| 30 |
+
// YouChatResponse 定义了从 You.com API 接收的单个 token 的结构。
|
| 31 |
type YouChatResponse struct {
|
| 32 |
YouChatToken string `json:"youChatToken"`
|
| 33 |
}
|
| 34 |
|
| 35 |
+
// OpenAIStreamResponse 定义了 OpenAI API 流式响应的结构。
|
| 36 |
type OpenAIStreamResponse struct {
|
| 37 |
ID string `json:"id"`
|
| 38 |
Object string `json:"object"`
|
|
|
|
| 41 |
Choices []Choice `json:"choices"`
|
| 42 |
}
|
| 43 |
|
| 44 |
+
// Choice 定义了 OpenAI 流式响应中 choices 数组的单个元素的结构。
|
| 45 |
type Choice struct {
|
| 46 |
Delta Delta `json:"delta"`
|
| 47 |
Index int `json:"index"`
|
| 48 |
FinishReason string `json:"finish_reason"`
|
| 49 |
}
|
| 50 |
|
| 51 |
+
// Delta 定义了流式响应中表示增量内容的结构。
|
| 52 |
type Delta struct {
|
| 53 |
Content string `json:"content"`
|
| 54 |
}
|
| 55 |
|
| 56 |
+
// OpenAIRequest 定义了 OpenAI API 请求体的结构。
|
| 57 |
type OpenAIRequest struct {
|
| 58 |
Messages []Message `json:"messages"`
|
| 59 |
Stream bool `json:"stream"`
|
| 60 |
Model string `json:"model"`
|
| 61 |
}
|
| 62 |
|
| 63 |
+
// Message 定义了 OpenAI 聊天消息的结构。
|
| 64 |
type Message struct {
|
| 65 |
Role string `json:"role"`
|
| 66 |
Content string `json:"content"`
|
| 67 |
}
|
| 68 |
|
| 69 |
+
// OpenAIResponse 定义了 OpenAI API 非流式响应的结构。
|
| 70 |
type OpenAIResponse struct {
|
| 71 |
ID string `json:"id"`
|
| 72 |
Object string `json:"object"`
|
|
|
|
| 75 |
Choices []OpenAIChoice `json:"choices"`
|
| 76 |
}
|
| 77 |
|
| 78 |
+
// OpenAIChoice 定义了 OpenAI 非流式响应中 choices 数组的单个元素的结构。
|
| 79 |
type OpenAIChoice struct {
|
| 80 |
Message Message `json:"message"`
|
| 81 |
Index int `json:"index"`
|
| 82 |
FinishReason string `json:"finish_reason"`
|
| 83 |
}
|
| 84 |
|
| 85 |
+
// ModelResponse 定义了 /v1/models 响应的结构。
|
| 86 |
type ModelResponse struct {
|
| 87 |
Object string `json:"object"`
|
| 88 |
Data []ModelDetail `json:"data"`
|
| 89 |
}
|
| 90 |
|
| 91 |
+
// ModelDetail 定义了模型列表中单个模型的详细信息。
|
| 92 |
type ModelDetail struct {
|
| 93 |
ID string `json:"id"`
|
| 94 |
Object string `json:"object"`
|
|
|
|
| 96 |
OwnedBy string `json:"owned_by"`
|
| 97 |
}
|
| 98 |
|
| 99 |
+
// modelMap 存储 OpenAI 模型名称到 You.com 模型名称的映射。
|
| 100 |
var modelMap = map[string]string{
|
| 101 |
"deepseek-reasoner": "deepseek_r1",
|
| 102 |
"deepseek-chat": "deepseek_v3",
|
|
|
|
| 123 |
"command-r-plus": "command_r_plus",
|
| 124 |
}
|
| 125 |
|
| 126 |
+
// getReverseModelMap 创建并返回 modelMap 的反向映射(You.com 模型名称 -> OpenAI 模型名称)。
|
| 127 |
func getReverseModelMap() map[string]string {
|
| 128 |
reverse := make(map[string]string, len(modelMap))
|
| 129 |
for k, v := range modelMap {
|
|
|
|
| 132 |
return reverse
|
| 133 |
}
|
| 134 |
|
| 135 |
+
// mapModelName 将 OpenAI 模型名称映射到 You.com 模型名称。
|
| 136 |
func mapModelName(openAIModel string) string {
|
| 137 |
if mappedModel, exists := modelMap[openAIModel]; exists {
|
| 138 |
return mappedModel
|
| 139 |
}
|
| 140 |
+
return "deepseek_v3" // 默认模型
|
| 141 |
}
|
| 142 |
|
| 143 |
+
// reverseMapModelName 将 You.com 模型名称映射回 OpenAI 模型名称。
|
| 144 |
func reverseMapModelName(youModel string) string {
|
| 145 |
reverseMap := getReverseModelMap()
|
| 146 |
if mappedModel, exists := reverseMap[youModel]; exists {
|
| 147 |
return mappedModel
|
| 148 |
}
|
| 149 |
+
return "deepseek-chat" // 默认模型
|
| 150 |
}
|
| 151 |
|
| 152 |
+
// originalModel 存储原始的 OpenAI 模型名称。
|
| 153 |
var originalModel string
|
| 154 |
|
| 155 |
+
// NonceResponse 定义了获取 nonce 的响应结构
|
| 156 |
+
type NonceResponse struct {
|
| 157 |
+
Uuid string `json:"uuid"`
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// UploadResponse 定义了文件上传的响应结构
|
| 161 |
+
type UploadResponse struct {
|
| 162 |
+
Filename string `json:"filename"`
|
| 163 |
+
UserFilename string `json:"user_filename"`
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
// 定义最大查询长度
|
| 167 |
+
const MaxQueryLength = 2000
|
| 168 |
+
|
| 169 |
+
// Handler 是处理所有传入 HTTP 请求的主处理函数。
|
| 170 |
func Handler(w http.ResponseWriter, r *http.Request) {
|
| 171 |
+
// 处理 /v1/models 请求(列出可用模型)
|
| 172 |
if r.URL.Path == "/v1/models" || r.URL.Path == "/api/v1/models" {
|
| 173 |
w.Header().Set("Content-Type", "application/json")
|
| 174 |
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
| 200 |
return
|
| 201 |
}
|
| 202 |
|
| 203 |
+
// 处理非 /v1/chat/completions 请求(服务状态检查)
|
| 204 |
+
if r.URL.Path != "/v1/chat/completions" && r.URL.Path != "/none/v1/chat/completions" && r.URL.Path != "/such/chat/completions" {
|
| 205 |
w.Header().Set("Content-Type", "application/json")
|
| 206 |
json.NewEncoder(w).Encode(map[string]string{
|
| 207 |
"status": "You2Api Service Running...",
|
|
|
|
| 210 |
return
|
| 211 |
}
|
| 212 |
|
| 213 |
+
// 设置 CORS 头部
|
| 214 |
w.Header().Set("Access-Control-Allow-Origin", "*")
|
| 215 |
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
| 216 |
w.Header().Set("Access-Control-Allow-Headers", "*")
|
|
|
|
| 220 |
return
|
| 221 |
}
|
| 222 |
|
| 223 |
+
// 验证 Authorization 头部
|
| 224 |
authHeader := r.Header.Get("Authorization")
|
| 225 |
if !strings.HasPrefix(authHeader, "Bearer ") {
|
| 226 |
http.Error(w, "Missing or invalid authorization header", http.StatusUnauthorized)
|
| 227 |
return
|
| 228 |
}
|
| 229 |
+
dsToken := strings.TrimPrefix(authHeader, "Bearer ") // 提取 DS token
|
| 230 |
|
| 231 |
+
// 解析 OpenAI 请求体
|
| 232 |
var openAIReq OpenAIRequest
|
| 233 |
if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil {
|
| 234 |
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
|
|
| 236 |
}
|
| 237 |
|
| 238 |
originalModel = openAIReq.Model
|
| 239 |
+
|
| 240 |
+
// 转换 system 消息为 user 消息
|
| 241 |
+
openAIReq.Messages = convertSystemToUser(openAIReq.Messages)
|
| 242 |
+
|
| 243 |
+
// 计算 token 数(使用字符估算方法)
|
| 244 |
+
totalTokens, err := countTokens(openAIReq.Messages)
|
| 245 |
+
if err != nil {
|
| 246 |
+
http.Error(w, "Failed to count tokens", http.StatusInternalServerError)
|
| 247 |
+
return
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
// 构建 You.com 聊天历史
|
| 251 |
var chatHistory []map[string]interface{}
|
| 252 |
for _, msg := range openAIReq.Messages {
|
| 253 |
chatMsg := map[string]interface{}{
|
| 254 |
"question": msg.Content,
|
| 255 |
"answer": "",
|
| 256 |
}
|
| 257 |
+
// 如果是 assistant 的消息, 则交换 question 和 answer
|
| 258 |
if msg.Role == "assistant" {
|
| 259 |
chatMsg["question"] = ""
|
| 260 |
chatMsg["answer"] = msg.Content
|
|
|
|
| 262 |
chatHistory = append(chatHistory, chatMsg)
|
| 263 |
}
|
| 264 |
|
| 265 |
+
chatHistoryJSON, _ := json.Marshal(chatHistory) // 将聊天历史序列化为 JSON
|
| 266 |
|
| 267 |
+
// 创建 You.com API 请求
|
| 268 |
youReq, _ := http.NewRequest("GET", "https://you.com/api/streamingSearch", nil)
|
| 269 |
|
| 270 |
+
// 生成必要的 ID
|
| 271 |
+
chatId := uuid.New().String()
|
| 272 |
+
conversationTurnId := uuid.New().String()
|
| 273 |
+
traceId := fmt.Sprintf("%s|%s|%s", chatId, conversationTurnId, time.Now().Format(time.RFC3339))
|
| 274 |
+
|
| 275 |
+
// 如果超过限制,使用文件上传
|
| 276 |
+
if totalTokens > MaxContextTokens {
|
| 277 |
+
// 1. 获取 nonce
|
| 278 |
+
nonceResp, err := getNonce(dsToken)
|
| 279 |
+
if err != nil {
|
| 280 |
+
http.Error(w, "Failed to get nonce", http.StatusInternalServerError)
|
| 281 |
+
return
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// 2. 创建临时文件,包含所有消息内容
|
| 285 |
+
var fileContent strings.Builder
|
| 286 |
+
for i, msg := range openAIReq.Messages {
|
| 287 |
+
if i > 0 {
|
| 288 |
+
fileContent.WriteString("\n\n")
|
| 289 |
+
}
|
| 290 |
+
fileContent.WriteString(fmt.Sprintf("%s: %s", msg.Role, msg.Content))
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
tempFile := fmt.Sprintf("temp_%s.txt", nonceResp.Uuid)
|
| 294 |
+
if err := os.WriteFile(tempFile, []byte(fileContent.String()), 0644); err != nil {
|
| 295 |
+
http.Error(w, "Failed to create temp file", http.StatusInternalServerError)
|
| 296 |
+
return
|
| 297 |
+
}
|
| 298 |
+
defer os.Remove(tempFile)
|
| 299 |
+
|
| 300 |
+
// 3. 上传文件
|
| 301 |
+
uploadResp, err := uploadFile(dsToken, tempFile)
|
| 302 |
+
if err != nil {
|
| 303 |
+
http.Error(w, "Failed to upload file", http.StatusInternalServerError)
|
| 304 |
+
return
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
// 4. 修改消息列表,只保留文件引用
|
| 308 |
+
openAIReq.Messages = []Message{
|
| 309 |
+
{
|
| 310 |
+
Role: "user",
|
| 311 |
+
Content: fmt.Sprintf("Please review the attached file: %s", uploadResp.UserFilename),
|
| 312 |
+
},
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
// 5. 添加文件源信息
|
| 316 |
+
sources := []map[string]interface{}{
|
| 317 |
+
{
|
| 318 |
+
"source_type": "user_file",
|
| 319 |
+
"filename": uploadResp.Filename,
|
| 320 |
+
"user_filename": uploadResp.UserFilename,
|
| 321 |
+
"size_bytes": len(fileContent.String()),
|
| 322 |
+
},
|
| 323 |
+
}
|
| 324 |
+
sourcesJSON, _ := json.Marshal(sources)
|
| 325 |
+
|
| 326 |
+
// 更新查询参数
|
| 327 |
+
q := youReq.URL.Query()
|
| 328 |
+
q.Add("sources", string(sourcesJSON))
|
| 329 |
+
q.Add("chatId", chatId)
|
| 330 |
+
q.Add("queryTraceId", chatId)
|
| 331 |
+
q.Add("conversationTurnId", conversationTurnId)
|
| 332 |
+
q.Add("traceId", traceId)
|
| 333 |
+
q.Add("q", openAIReq.Messages[0].Content)
|
| 334 |
+
q.Add("page", "1")
|
| 335 |
+
q.Add("count", "10")
|
| 336 |
+
q.Add("safeSearch", "Moderate")
|
| 337 |
+
q.Add("mkt", "zh-HK")
|
| 338 |
+
q.Add("enable_worklow_generation_ux", "true")
|
| 339 |
+
q.Add("domain", "youchat")
|
| 340 |
+
q.Add("use_personalization_extraction", "true")
|
| 341 |
+
q.Add("pastChatLength", fmt.Sprintf("%d", len(chatHistory)-1))
|
| 342 |
+
q.Add("selectedChatMode", "custom")
|
| 343 |
+
q.Add("selectedAiModel", mapModelName(openAIReq.Model))
|
| 344 |
+
q.Add("enable_agent_clarification_questions", "true")
|
| 345 |
+
q.Add("use_nested_youchat_updates", "true")
|
| 346 |
+
q.Add("chat", string(chatHistoryJSON))
|
| 347 |
+
youReq.URL.RawQuery = q.Encode()
|
| 348 |
+
} else {
|
| 349 |
+
// 构建常规查询参数
|
| 350 |
+
q := youReq.URL.Query()
|
| 351 |
+
q.Add("q", openAIReq.Messages[len(openAIReq.Messages)-1].Content)
|
| 352 |
+
q.Add("chatId", chatId)
|
| 353 |
+
q.Add("queryTraceId", chatId)
|
| 354 |
+
q.Add("conversationTurnId", conversationTurnId)
|
| 355 |
+
q.Add("traceId", traceId)
|
| 356 |
+
q.Add("page", "1")
|
| 357 |
+
q.Add("count", "10")
|
| 358 |
+
q.Add("safeSearch", "Moderate")
|
| 359 |
+
q.Add("mkt", "zh-HK")
|
| 360 |
+
q.Add("enable_worklow_generation_ux", "true")
|
| 361 |
+
q.Add("domain", "youchat")
|
| 362 |
+
q.Add("use_personalization_extraction", "true")
|
| 363 |
+
q.Add("pastChatLength", fmt.Sprintf("%d", len(chatHistory)-1))
|
| 364 |
+
q.Add("selectedChatMode", "custom")
|
| 365 |
+
q.Add("selectedAiModel", mapModelName(openAIReq.Model))
|
| 366 |
+
q.Add("enable_agent_clarification_questions", "true")
|
| 367 |
+
q.Add("use_nested_youchat_updates", "true")
|
| 368 |
+
q.Add("chat", string(chatHistoryJSON))
|
| 369 |
+
youReq.URL.RawQuery = q.Encode()
|
| 370 |
+
}
|
| 371 |
|
| 372 |
+
// 设置 You.com API 请求头
|
| 373 |
youReq.Header = http.Header{
|
| 374 |
"sec-ch-ua-platform": {"Windows"},
|
| 375 |
"Cache-Control": {"no-cache"},
|
|
|
|
| 379 |
"sec-ch-ua-mobile": {"?0"},
|
| 380 |
"sec-ch-ua-arch": {"x86"},
|
| 381 |
"sec-ch-ua-full-version": {"133.0.3065.39"},
|
| 382 |
+
"Accept": {"text/event-stream"}, // 重要:接受 SSE 流
|
| 383 |
"User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
| 384 |
"sec-ch-ua-platform-version": {"19.0.0"},
|
| 385 |
"Sec-Fetch-Site": {"same-origin"},
|
|
|
|
| 388 |
"Host": {"you.com"},
|
| 389 |
}
|
| 390 |
|
| 391 |
+
// 设置 You.com API 请求的 Cookie
|
| 392 |
cookies := getCookies(dsToken)
|
| 393 |
var cookieStrings []string
|
| 394 |
for name, value := range cookies {
|
|
|
|
| 396 |
}
|
| 397 |
youReq.Header.Add("Cookie", strings.Join(cookieStrings, ";"))
|
| 398 |
|
| 399 |
+
// 根据 OpenAI 请求的 stream 参数选择处理函数
|
| 400 |
if !openAIReq.Stream {
|
| 401 |
+
handleNonStreamingResponse(w, youReq) // 处理非流式响应
|
| 402 |
return
|
| 403 |
}
|
| 404 |
|
| 405 |
+
handleStreamingResponse(w, youReq) // 处理流式响应
|
| 406 |
}
|
| 407 |
|
| 408 |
+
// getCookies 根据提供的 DS token 生成所需的 Cookie。
|
| 409 |
func getCookies(dsToken string) map[string]string {
|
| 410 |
return map[string]string{
|
| 411 |
"guest_has_seen_legal_disclaimer": "true",
|
| 412 |
"youchat_personalization": "true",
|
| 413 |
+
"DS": dsToken, // 关键的 DS token
|
| 414 |
+
"you_subscription": "youpro_standard_year", // 示例订阅信息
|
| 415 |
"youpro_subscription": "true",
|
| 416 |
+
"ai_model": "deepseek_r1", // 示例 AI 模型
|
| 417 |
"youchat_smart_learn": "true",
|
| 418 |
}
|
| 419 |
}
|
| 420 |
|
| 421 |
+
// handleNonStreamingResponse 处理非流式请求。
|
| 422 |
func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
| 423 |
client := &http.Client{
|
| 424 |
+
Timeout: 60 * time.Second, // 设置超时时间
|
| 425 |
}
|
| 426 |
resp, err := client.Do(youReq)
|
| 427 |
if err != nil {
|
|
|
|
| 433 |
var fullResponse strings.Builder
|
| 434 |
scanner := bufio.NewScanner(resp.Body)
|
| 435 |
|
| 436 |
+
// 设置 scanner 的缓冲区大小(可选,但对于大型响应很重要)
|
| 437 |
buf := make([]byte, 0, 64*1024)
|
| 438 |
scanner.Buffer(buf, 1024*1024)
|
| 439 |
|
| 440 |
+
// 逐行扫描响应,寻找 youChatToken 事件
|
| 441 |
for scanner.Scan() {
|
| 442 |
line := scanner.Text()
|
| 443 |
if strings.HasPrefix(line, "event: youChatToken") {
|
| 444 |
+
scanner.Scan() // 读取下一行 (data 行)
|
| 445 |
data := scanner.Text()
|
| 446 |
if !strings.HasPrefix(data, "data: ") {
|
| 447 |
+
continue // 如果不是 data 行,则跳过
|
| 448 |
}
|
| 449 |
var token YouChatResponse
|
| 450 |
if err := json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token); err != nil {
|
| 451 |
+
continue // 如果解析失败,则跳过
|
| 452 |
}
|
| 453 |
+
fullResponse.WriteString(token.YouChatToken) // 将 token 添加到完整响应中
|
| 454 |
}
|
| 455 |
}
|
| 456 |
|
|
|
|
| 459 |
return
|
| 460 |
}
|
| 461 |
|
| 462 |
+
// 构建 OpenAI 格式的非流式响应
|
| 463 |
openAIResp := OpenAIResponse{
|
| 464 |
ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
|
| 465 |
Object: "chat.completion",
|
| 466 |
Created: time.Now().Unix(),
|
| 467 |
+
Model: reverseMapModelName(mapModelName(originalModel)), // 映射回 OpenAI 模型名称
|
| 468 |
Choices: []OpenAIChoice{
|
| 469 |
{
|
| 470 |
Message: Message{
|
| 471 |
Role: "assistant",
|
| 472 |
+
Content: fullResponse.String(), // 完整的响应内容
|
| 473 |
},
|
| 474 |
Index: 0,
|
| 475 |
+
FinishReason: "stop", // 停止原因
|
| 476 |
},
|
| 477 |
},
|
| 478 |
}
|
|
|
|
| 484 |
}
|
| 485 |
}
|
| 486 |
|
| 487 |
+
// handleStreamingResponse 处理流式请求。
|
| 488 |
func handleStreamingResponse(w http.ResponseWriter, youReq *http.Request) {
|
| 489 |
+
client := &http.Client{} // 流式请求不需要设置超时,因为它会持续接收数据
|
| 490 |
resp, err := client.Do(youReq)
|
| 491 |
if err != nil {
|
| 492 |
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
| 494 |
}
|
| 495 |
defer resp.Body.Close()
|
| 496 |
|
| 497 |
+
// 设置流式响应的头部
|
| 498 |
w.Header().Set("Content-Type", "text/event-stream")
|
| 499 |
w.Header().Set("Cache-Control", "no-cache")
|
| 500 |
w.Header().Set("Connection", "keep-alive")
|
| 501 |
|
| 502 |
scanner := bufio.NewScanner(resp.Body)
|
| 503 |
+
// 逐行扫描响应,寻找 youChatToken 事件
|
| 504 |
for scanner.Scan() {
|
| 505 |
line := scanner.Text()
|
| 506 |
|
| 507 |
if strings.HasPrefix(line, "event: youChatToken") {
|
| 508 |
+
scanner.Scan() // 读取下一行 (data 行)
|
| 509 |
+
data := scanner.Text() // 获取数据行
|
| 510 |
|
| 511 |
var token YouChatResponse
|
| 512 |
+
json.Unmarshal([]byte(strings.TrimPrefix(data, "data: ")), &token) // 解析 JSON
|
| 513 |
|
| 514 |
+
// 构建 OpenAI 格式的流式响应块
|
| 515 |
openAIResp := OpenAIStreamResponse{
|
| 516 |
ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()),
|
| 517 |
Object: "chat.completion.chunk",
|
| 518 |
Created: time.Now().Unix(),
|
| 519 |
+
Model: reverseMapModelName(mapModelName(originalModel)), // 映射回 OpenAI 模型名称
|
| 520 |
Choices: []Choice{
|
| 521 |
{
|
| 522 |
Delta: Delta{
|
| 523 |
+
Content: token.YouChatToken, // 增量内容
|
| 524 |
},
|
| 525 |
Index: 0,
|
| 526 |
+
FinishReason: "", // 流式响应中通常为空
|
| 527 |
},
|
| 528 |
},
|
| 529 |
}
|
| 530 |
|
| 531 |
+
respBytes, _ := json.Marshal(openAIResp) // 将响应块序列化为 JSON
|
| 532 |
+
fmt.Fprintf(w, "data: %s\n\n", string(respBytes)) // 写入响应数据
|
| 533 |
+
w.(http.Flusher).Flush() // 立即刷新输出
|
| 534 |
+
}
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
// 获取上传文件所需的 nonce
|
| 540 |
+
func getNonce(dsToken string) (*NonceResponse, error) {
|
| 541 |
+
req, _ := http.NewRequest("GET", "https://you.com/api/get_nonce", nil)
|
| 542 |
+
req.Header.Set("Cookie", fmt.Sprintf("ds_token=%s", dsToken))
|
| 543 |
+
|
| 544 |
+
resp, err := http.DefaultClient.Do(req)
|
| 545 |
+
if err != nil {
|
| 546 |
+
return nil, err
|
| 547 |
+
}
|
| 548 |
+
defer resp.Body.Close()
|
| 549 |
+
|
| 550 |
+
var nonceResp NonceResponse
|
| 551 |
+
if err := json.NewDecoder(resp.Body).Decode(&nonceResp); err != nil {
|
| 552 |
+
return nil, err
|
| 553 |
+
}
|
| 554 |
+
return &nonceResp, nil
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
// 上传文件
|
| 558 |
+
func uploadFile(dsToken, filePath string) (*UploadResponse, error) {
|
| 559 |
+
file, err := os.Open(filePath)
|
| 560 |
+
if err != nil {
|
| 561 |
+
return nil, err
|
| 562 |
+
}
|
| 563 |
+
defer file.Close()
|
| 564 |
+
|
| 565 |
+
body := &bytes.Buffer{}
|
| 566 |
+
writer := multipart.NewWriter(body)
|
| 567 |
+
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
|
| 568 |
+
if err != nil {
|
| 569 |
+
return nil, err
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
if _, err := io.Copy(part, file); err != nil {
|
| 573 |
+
return nil, err
|
| 574 |
+
}
|
| 575 |
+
writer.Close()
|
| 576 |
+
|
| 577 |
+
req, _ := http.NewRequest("POST", "https://you.com/api/upload", body)
|
| 578 |
+
req.Header.Set("Content-Type", writer.FormDataContentType())
|
| 579 |
+
req.Header.Set("Cookie", fmt.Sprintf("ds_token=%s", dsToken))
|
| 580 |
+
|
| 581 |
+
resp, err := http.DefaultClient.Do(req)
|
| 582 |
+
if err != nil {
|
| 583 |
+
return nil, err
|
| 584 |
+
}
|
| 585 |
+
defer resp.Body.Close()
|
| 586 |
+
|
| 587 |
+
var uploadResp UploadResponse
|
| 588 |
+
if err := json.NewDecoder(resp.Body).Decode(&uploadResp); err != nil {
|
| 589 |
+
return nil, err
|
| 590 |
+
}
|
| 591 |
+
return &uploadResp, nil
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
// 计算消息的 token 数(使用字符估算方法)
|
| 595 |
+
func countTokens(messages []Message) (int, error) {
|
| 596 |
+
totalTokens := 0
|
| 597 |
+
for _, msg := range messages {
|
| 598 |
+
content := msg.Content
|
| 599 |
+
englishCount := 0
|
| 600 |
+
chineseCount := 0
|
| 601 |
+
|
| 602 |
+
// 遍历每个字符
|
| 603 |
+
for _, r := range content {
|
| 604 |
+
if r <= 127 { // ASCII 字符(英文和符号)
|
| 605 |
+
englishCount++
|
| 606 |
+
} else { // 非 ASCII 字符(中文等)
|
| 607 |
+
chineseCount++
|
| 608 |
+
}
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
// 计算 tokens:英文字符 * 0.3 + 中文字符 * 0.6
|
| 612 |
+
tokens := int(float64(englishCount)*0.3 + float64(chineseCount)*0.6)
|
| 613 |
+
|
| 614 |
+
// 加上角色名的 token(约 2 个)
|
| 615 |
+
totalTokens += tokens + 2
|
| 616 |
+
}
|
| 617 |
+
return totalTokens, nil
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
// 将 system 消息转换为第一条 user 消息
|
| 621 |
+
func convertSystemToUser(messages []Message) []Message {
|
| 622 |
+
if len(messages) == 0 {
|
| 623 |
+
return messages
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
var systemContent strings.Builder
|
| 627 |
+
var newMessages []Message
|
| 628 |
+
var systemFound bool
|
| 629 |
+
|
| 630 |
+
// 收��所有 system 消息
|
| 631 |
+
for _, msg := range messages {
|
| 632 |
+
if msg.Role == "system" {
|
| 633 |
+
if systemContent.Len() > 0 {
|
| 634 |
+
systemContent.WriteString("\n")
|
| 635 |
+
}
|
| 636 |
+
systemContent.WriteString(msg.Content)
|
| 637 |
+
systemFound = true
|
| 638 |
+
} else {
|
| 639 |
+
newMessages = append(newMessages, msg)
|
| 640 |
}
|
| 641 |
}
|
| 642 |
+
|
| 643 |
+
// 如果有 system 消息,将其作为第一条 user 消息
|
| 644 |
+
if systemFound {
|
| 645 |
+
newMessages = append([]Message{{
|
| 646 |
+
Role: "user",
|
| 647 |
+
Content: systemContent.String(),
|
| 648 |
+
}}, newMessages...)
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
return newMessages
|
| 652 |
}
|
go.mod
CHANGED
|
@@ -3,6 +3,7 @@ module you2api
|
|
| 3 |
go 1.22.2
|
| 4 |
|
| 5 |
require (
|
|
|
|
| 6 |
github.com/prometheus/client_golang v1.18.0
|
| 7 |
go.uber.org/zap v1.26.0
|
| 8 |
)
|
|
|
|
| 3 |
go 1.22.2
|
| 4 |
|
| 5 |
require (
|
| 6 |
+
github.com/google/uuid v1.6.0
|
| 7 |
github.com/prometheus/client_golang v1.18.0
|
| 8 |
go.uber.org/zap v1.26.0
|
| 9 |
)
|
go.sum
CHANGED
|
@@ -8,6 +8,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|
| 8 |
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 9 |
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
| 10 |
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
|
|
|
|
|
| 11 |
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
| 12 |
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
| 13 |
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
@@ -20,6 +22,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne
|
|
| 20 |
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
| 21 |
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
| 22 |
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
|
|
|
|
|
|
| 23 |
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
| 24 |
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 25 |
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
|
|
|
| 8 |
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 9 |
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
| 10 |
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 11 |
+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
| 12 |
+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 13 |
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
| 14 |
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
| 15 |
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
|
|
| 22 |
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
| 23 |
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
| 24 |
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
| 25 |
+
github.com/sashabaranov/go-openai v1.20.2 h1:nilzF2EKzaHyK4Rk2Dbu/aJEZbtIvskDIXvfS4yx+6M=
|
| 26 |
+
github.com/sashabaranov/go-openai v1.20.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
| 27 |
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
| 28 |
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 29 |
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|