malt666/tokenizer
0
1package main2 3import (4 "bytes"5 "context"6 "encoding/json"7 "fmt"8 "io"9 "log"10 "net/http"11 "os"12 "strings"13 "sync"14 "time"15 16 "github.com/gin-gonic/gin"17 "github.com/google/generative-ai-go/genai"18 "google.golang.org/api/option"19)20 21// 配置结构22type Config struct {23 AnthropicKey string24 GoogleKey string25 ServiceURL string26 DeepseekURL string27 OpenAIURL string28}29 30var (31 config Config32 configOnce sync.Once33)34 35// 请求结构36type TokenCountRequest struct {37 Model string `json:"model" binding:"required"`38 Messages []Message `json:"messages" binding:"required"`39 System *string `json:"system,omitempty"`40}41 42type Message struct {43 Role string `json:"role" binding:"required"`44 Content string `json:"content" binding:"required"`45}46 47// 响应结构48type TokenCountResponse struct {49 InputTokens int `json:"input_tokens"`50}51 52// 错误响应结构53type ErrorResponse struct {54 Error string `json:"error"`55}56 57// 模型映射规则58type ModelRule struct {59 Keywords []string60 Target string61}62 63var modelRules = []ModelRule{64 {65 Keywords: []string{"deepseek"},66 Target: "deepseek-v3",67 },68 // 先放更具体的规则69 {70 Keywords: []string{"claude", "3", "5", "sonnet"},71 Target: "claude-3-5-sonnet-latest",72 },73 {74 Keywords: []string{"claude", "3", "5", "haiku"},75 Target: "claude-3-5-haiku-latest",76 },77 {78 Keywords: []string{"claude", "3", "7"},79 Target: "claude-3-7-sonnet-latest",80 },81 {82 Keywords: []string{"claude", "3", "opus"},83 Target: "claude-3-opus-latest",84 },85 {86 Keywords: []string{"claude", "3", "haiku"},87 Target: "claude-3-haiku-20240307",88 },89 // 再放一般规则90 {91 Keywords: []string{"claude", "3", "sonnet"},92 Target: "claude-3-sonnet-20240229",93 },94 {95 Keywords: []string{"gemini", "2.0"},96 Target: "gemini-2.0-flash",97 },98 {99 Keywords: []string{"gemini", "2.5"},100 Target: "gemini-2.0-flash", // 目前使用2.0-flash作为2.5的替代101 },102 {103 Keywords: []string{"gemini", "1.5"},104 Target: "gemini-1.5-flash",105 },106}107 108// 智能匹配模型名称109func matchModelName(input string) string {110 // 转换为小写进行匹配111 input = strings.ToLower(input)112 log.Printf("正在匹配模型名称: %s", input)113 114 // 特殊处理 Claude 3.5 系列115 if strings.Contains(input, "claude") && strings.Contains(input, "3.5") ||116 strings.Contains(input, "claude") && strings.Contains(input, "3") && strings.Contains(input, "5") {117 if strings.Contains(input, "sonnet") {118 log.Printf("匹配到Claude 3.5 Sonnet")119 return "claude-3-5-sonnet-latest"120 } else if strings.Contains(input, "haiku") {121 log.Printf("匹配到Claude 3.5 Haiku")122 return "claude-3-5-haiku-latest"123 } else {124 // 默认为Sonnet125 log.Printf("匹配到Claude 3.5 (默认使用Sonnet)")126 return "claude-3-5-sonnet-latest"127 }128 }129 130 // 特殊处理 Claude 3.7 系列131 if strings.Contains(input, "claude") && strings.Contains(input, "3.7") ||132 strings.Contains(input, "claude") && strings.Contains(input, "3") && strings.Contains(input, "7") {133 log.Printf("匹配到Claude 3.7")134 return "claude-3-7-sonnet-latest"135 }136 137 // 特殊规则:OpenAI GPT-4o138 if (strings.Contains(input, "gpt") && strings.Contains(input, "4o")) ||139 strings.Contains(input, "o1") ||140 strings.Contains(input, "o3") {141 log.Printf("匹配到GPT-4o")142 return "gpt-4o"143 }144 145 // 特殊规则:OpenAI GPT-4146 if (strings.Contains(input, "gpt") && strings.Contains(input, "3") && strings.Contains(input, "5")) ||147 (strings.Contains(input, "gpt") && strings.Contains(input, "4") && !strings.Contains(input, "4o")) {148 log.Printf("匹配到GPT-4")149 return "gpt-4"150 }151 152 // 遍历所有规则153 for _, rule := range modelRules {154 matches := true155 for _, keyword := range rule.Keywords {156 if !strings.Contains(input, strings.ToLower(keyword)) {157 matches = false158 break159 }160 }161 if matches {162 log.Printf("通过规则匹配到: %s", rule.Target)163 return rule.Target164 }165 }166 167 // 如果没有匹配到,返回原始输入168 log.Printf("没有匹配到任何规则,使用原始输入: %s", input)169 return input170}171 172// 加载配置173func loadConfig() Config {174 configOnce.Do(func() {175 // 配置日志格式176 log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)177 log.Println("开始加载配置...")178 179 config.AnthropicKey = os.Getenv("ANTHROPIC_API_KEY")180 if config.AnthropicKey == "" {181 log.Println("警告: ANTHROPIC_API_KEY 环境变量未设置,Claude模型将无法使用")182 } else {183 log.Println("Anthropic API Key已配置")184 }185 186 config.GoogleKey = os.Getenv("GOOGLE_API_KEY")187 if config.GoogleKey == "" {188 log.Println("警告: GOOGLE_API_KEY 环境变量未设置,Gemini模型将无法使用")189 } else {190 log.Println("Google API Key已配置")191 }192 193 // 获取Deepseek服务URL194 config.DeepseekURL = os.Getenv("DEEPSEEK_URL")195 if config.DeepseekURL == "" {196 config.DeepseekURL = "http://127.0.0.1:7861" // 默认本地地址197 log.Println("使用默认Deepseek服务地址:", config.DeepseekURL)198 } else {199 log.Println("使用配置的Deepseek服务地址:", config.DeepseekURL)200 }201 202 // 获取OpenAI服务URL203 config.OpenAIURL = os.Getenv("OPENAI_URL")204 if config.OpenAIURL == "" {205 config.OpenAIURL = "http://127.0.0.1:7862" // 默认本地地址206 log.Println("使用默认OpenAI服务地址:", config.OpenAIURL)207 } else {208 log.Println("使用配置的OpenAI服务地址:", config.OpenAIURL)209 }210 211 // 获取服务URL,用于防休眠212 config.ServiceURL = os.Getenv("SERVICE_URL")213 if config.ServiceURL == "" {214 log.Println("SERVICE_URL 未设置,防休眠功能将被禁用")215 } else {216 log.Println("防休眠URL已配置:", config.ServiceURL)217 }218 219 log.Println("配置加载完成")220 })221 return config222}223 224// 使用Claude API计算token225func countTokensWithClaude(req TokenCountRequest) (TokenCountResponse, error) {226 // 准备请求Anthropic API227 log.Printf("开始Claude API请求: 模型=%s, 消息数量=%d", req.Model, len(req.Messages))228 229 // 验证并过滤空内容的消息230 var filteredMessages []Message231 for i, msg := range req.Messages {232 if msg.Content == "" {233 log.Printf("警告: 消息 #%d 内容为空,将被过滤掉", i)234 continue // 跳过空内容消息235 }236 if msg.Role != "user" && msg.Role != "assistant" {237 log.Printf("警告: 消息 #%d 角色'%s'不是标准角色(user/assistant),可能导致请求失败", i, msg.Role)238 }239 filteredMessages = append(filteredMessages, msg)240 }241 242 if len(filteredMessages) == 0 {243 log.Printf("错误: 过滤后没有有效消息")244 return TokenCountResponse{}, fmt.Errorf("没有有效消息:所有消息内容都为空")245 }246 247 // 创建新请求,使用过滤后的消息248 filteredReq := TokenCountRequest{249 Model: req.Model,250 Messages: filteredMessages,251 System: req.System,252 }253 254 // 记录过滤后的消息数量255 if len(filteredMessages) != len(req.Messages) {256 log.Printf("消息过滤: 原始消息数=%d, 过滤后消息数=%d", len(req.Messages), len(filteredMessages))257 }258 259 client := &http.Client{}260 data, err := json.Marshal(filteredReq)261 if err != nil {262 log.Printf("错误: 序列化Claude请求失败: %v", err)263 return TokenCountResponse{}, fmt.Errorf("序列化请求失败: %v", err)264 }265 266 // 记录请求内容用于调试267 if len(data) < 1000 {268 log.Printf("Claude请求内容: %s", string(data))269 } else {270 log.Printf("Claude请求内容较大,长度=%d字节", len(data))271 }272 273 // 创建请求274 request, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages/count_tokens", bytes.NewBuffer(data))275 if err != nil {276 log.Printf("错误: 创建Claude请求失败: %v", err)277 return TokenCountResponse{}, fmt.Errorf("创建请求失败: %v", err)278 }279 280 // 设置请求头281 request.Header.Set("x-api-key", config.AnthropicKey)282 request.Header.Set("anthropic-version", "2023-06-01")283 request.Header.Set("content-type", "application/json")284 285 // 发送请求286 response, err := client.Do(request)287 if err != nil {288 log.Printf("错误: 发送请求到Anthropic API失败: %v", err)289 return TokenCountResponse{}, fmt.Errorf("发送请求到Anthropic API失败: %v", err)290 }291 defer response.Body.Close()292 293 // 检查响应状态码294 if response.StatusCode != http.StatusOK {295 // 读取错误响应296 var errorBody []byte297 errorBody, _ = io.ReadAll(response.Body)298 log.Printf("错误: Claude API返回非200状态码: %d, 响应体: %s", response.StatusCode, string(errorBody))299 300 // 检查常见错误301 errorStr := string(errorBody)302 if response.StatusCode == http.StatusUnauthorized || strings.Contains(errorStr, "invalid_api_key") {303 log.Printf("错误: Claude API密钥无效或过期")304 return TokenCountResponse{}, fmt.Errorf("Claude API验证失败,请检查API Key是否有效: %s", string(errorBody))305 } else if response.StatusCode == http.StatusBadRequest {306 if strings.Contains(errorStr, "empty content") {307 log.Printf("错误: 请求包含空内容的消息")308 return TokenCountResponse{}, fmt.Errorf("请求格式错误: 消息不能有空内容: %s", string(errorBody))309 } else if strings.Contains(errorStr, "invalid_request_error") {310 log.Printf("错误: 无效的请求格式")311 return TokenCountResponse{}, fmt.Errorf("无效的请求格式: %s", string(errorBody))312 }313 }314 315 return TokenCountResponse{}, fmt.Errorf("Claude API返回错误状态码: %d, 响应: %s", response.StatusCode, string(errorBody))316 }317 318 // 读取响应319 var result TokenCountResponse320 if err := json.NewDecoder(response.Body).Decode(&result); err != nil {321 log.Printf("错误: 解码Claude响应失败: %v", err)322 return TokenCountResponse{}, fmt.Errorf("解码响应失败: %v", err)323 }324 325 log.Printf("Claude API请求成功: 模型=%s, 输入tokens=%d", req.Model, result.InputTokens)326 return result, nil327}328 329// 使用Gemini API计算token330func countTokensWithGemini(req TokenCountRequest) (TokenCountResponse, error) {331 // 检查API密钥332 log.Printf("开始Gemini API请求: 模型=%s, 消息数量=%d", req.Model, len(req.Messages))333 334 if config.GoogleKey == "" {335 log.Printf("错误: Gemini API密钥未设置")336 return TokenCountResponse{}, fmt.Errorf("GOOGLE_API_KEY 未设置")337 }338 339 // 创建Gemini客户端340 ctx := context.Background()341 client, err := genai.NewClient(ctx, option.WithAPIKey(config.GoogleKey))342 if err != nil {343 log.Printf("错误: 创建Gemini客户端失败: %v", err)344 return TokenCountResponse{}, fmt.Errorf("创建Gemini客户端失败: %v", err)345 }346 defer client.Close()347 348 // 使用已经匹配好的模型名称349 modelName := req.Model350 log.Printf("使用Gemini模型: %s", modelName)351 352 // 创建Gemini模型353 model := client.GenerativeModel(modelName)354 355 // 构建提示内容356 var content string357 if req.System != nil && *req.System != "" {358 content += *req.System + "\n\n"359 log.Printf("Gemini请求包含系统提示: %s", *req.System)360 }361 362 for _, msg := range req.Messages {363 if msg.Role == "user" {364 content += "用户: " + msg.Content + "\n"365 } else if msg.Role == "assistant" {366 content += "助手: " + msg.Content + "\n"367 } else {368 content += msg.Role + ": " + msg.Content + "\n"369 }370 }371 372 // 计算token373 log.Printf("开始计算Gemini tokens...")374 tokResp, err := model.CountTokens(ctx, genai.Text(content))375 if err != nil {376 log.Printf("错误: 计算Gemini token失败: %v", err)377 if strings.Contains(err.Error(), "invalid_api_key") || strings.Contains(err.Error(), "permission_denied") {378 log.Printf("错误: Gemini API密钥可能无效或过期")379 }380 return TokenCountResponse{}, fmt.Errorf("计算Gemini token失败: %v", err)381 }382 383 log.Printf("Gemini API请求成功: 模型=%s, 输入tokens=%d", req.Model, tokResp.TotalTokens)384 return TokenCountResponse{InputTokens: int(tokResp.TotalTokens)}, nil385}386 387// 使用Deepseek API计算token388func countTokensWithDeepseek(req TokenCountRequest) (TokenCountResponse, error) {389 log.Printf("开始Deepseek API请求: 模型=%s, 消息数量=%d, 服务地址=%s", req.Model, len(req.Messages), config.DeepseekURL)390 391 // 准备请求392 client := &http.Client{}393 data, err := json.Marshal(req)394 if err != nil {395 log.Printf("错误: 序列化Deepseek请求失败: %v", err)396 return TokenCountResponse{}, fmt.Errorf("序列化请求失败: %v", err)397 }398 399 // 创建请求400 requestURL := config.DeepseekURL + "/count_tokens"401 log.Printf("发送请求到Deepseek服务: %s", requestURL)402 request, err := http.NewRequest("POST", requestURL, bytes.NewBuffer(data))403 if err != nil {404 log.Printf("错误: 创建Deepseek请求失败: %v", err)405 return TokenCountResponse{}, fmt.Errorf("创建请求失败: %v", err)406 }407 408 // 设置请求头409 request.Header.Set("Content-Type", "application/json")410 411 // 发送请求412 response, err := client.Do(request)413 if err != nil {414 log.Printf("错误: 发送请求到Deepseek服务失败: %v", err)415 return TokenCountResponse{}, fmt.Errorf("发送请求到Deepseek服务失败: %v", err)416 }417 defer response.Body.Close()418 419 // 检查响应状态码420 if response.StatusCode != http.StatusOK {421 // 读取错误响应422 var errorBody []byte423 errorBody, _ = io.ReadAll(response.Body)424 log.Printf("错误: Deepseek API返回非200状态码: %d, 响应体: %s", response.StatusCode, string(errorBody))425 return TokenCountResponse{}, fmt.Errorf("Deepseek API返回错误状态码: %d, 响应: %s", response.StatusCode, string(errorBody))426 }427 428 // 读取响应429 var result TokenCountResponse430 if err := json.NewDecoder(response.Body).Decode(&result); err != nil {431 log.Printf("错误: 解码Deepseek响应失败: %v", err)432 return TokenCountResponse{}, fmt.Errorf("解码响应失败: %v", err)433 }434 435 log.Printf("Deepseek API请求成功: 模型=%s, 输入tokens=%d", req.Model, result.InputTokens)436 return result, nil437}438 439// 使用OpenAI API计算token440func countTokensWithOpenAI(req TokenCountRequest) (TokenCountResponse, error) {441 log.Printf("开始OpenAI API请求: 模型=%s, 消息数量=%d, 服务地址=%s", req.Model, len(req.Messages), config.OpenAIURL)442 443 // 准备请求444 client := &http.Client{}445 data, err := json.Marshal(req)446 if err != nil {447 log.Printf("错误: 序列化OpenAI请求失败: %v", err)448 return TokenCountResponse{}, fmt.Errorf("序列化请求失败: %v", err)449 }450 451 // 创建请求452 requestURL := config.OpenAIURL + "/count_tokens"453 log.Printf("发送请求到OpenAI服务: %s", requestURL)454 request, err := http.NewRequest("POST", requestURL, bytes.NewBuffer(data))455 if err != nil {456 log.Printf("错误: 创建OpenAI请求失败: %v", err)457 return TokenCountResponse{}, fmt.Errorf("创建请求失败: %v", err)458 }459 460 // 设置请求头461 request.Header.Set("Content-Type", "application/json")462 463 // 发送请求464 response, err := client.Do(request)465 if err != nil {466 log.Printf("错误: 发送请求到OpenAI服务失败: %v", err)467 return TokenCountResponse{}, fmt.Errorf("发送请求到OpenAI服务失败: %v", err)468 }469 defer response.Body.Close()470 471 // 检查响应状态码472 if response.StatusCode != http.StatusOK {473 // 读取错误响应474 var errorBody []byte475 errorBody, _ = io.ReadAll(response.Body)476 log.Printf("错误: OpenAI API返回非200状态码: %d, 响应体: %s", response.StatusCode, string(errorBody))477 return TokenCountResponse{}, fmt.Errorf("OpenAI API返回错误状态码: %d, 响应: %s", response.StatusCode, string(errorBody))478 }479 480 // 读取响应481 var result struct {482 InputTokens int `json:"input_tokens"`483 Model string `json:"model"`484 Encoding string `json:"encoding"`485 }486 if err := json.NewDecoder(response.Body).Decode(&result); err != nil {487 log.Printf("错误: 解码OpenAI响应失败: %v", err)488 return TokenCountResponse{}, fmt.Errorf("解码响应失败: %v", err)489 }490 491 log.Printf("OpenAI API请求成功: 模型=%s(实际使用=%s), 编码=%s, 输入tokens=%d",492 req.Model, result.Model, result.Encoding, result.InputTokens)493 return TokenCountResponse{InputTokens: result.InputTokens}, nil494}495 496// 计算token497func countTokens(c *gin.Context) {498 var req TokenCountRequest499 if err := c.ShouldBindJSON(&req); err != nil {500 log.Printf("错误: 无效的请求格式: %v", err)501 c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})502 return503 }504 505 // 记录请求详情506 systemPrompt := "无"507 if req.System != nil && *req.System != "" {508 systemPrompt = *req.System509 }510 log.Printf("收到token计算请求: 原始模型=%s, 消息数量=%d, 系统提示=%s",511 req.Model, len(req.Messages), systemPrompt)512 513 // 保存原始模型名称514 originalModel := req.Model515 516 // 检查是否为不支持的模型517 isUnsupportedModel := true518 519 // 检查是否为支持的模型类型520 modelLower := strings.ToLower(req.Model)521 if strings.Contains(modelLower, "gpt") || strings.Contains(modelLower, "openai") ||522 strings.Contains(modelLower, "o1") || strings.Contains(modelLower, "o3") ||523 strings.HasPrefix(modelLower, "claude") ||524 strings.Contains(modelLower, "gemini") ||525 strings.Contains(modelLower, "deepseek") {526 isUnsupportedModel = false527 }528 529 // 智能匹配模型名称530 req.Model = matchModelName(req.Model)531 log.Printf("模型名称匹配结果: 原始=%s -> 匹配=%s", originalModel, req.Model)532 533 var result TokenCountResponse534 var err error535 536 // 优先检查是否为Deepseek模型537 if strings.Contains(strings.ToLower(req.Model), "deepseek") {538 log.Printf("使用Deepseek API计算token")539 // 使用Deepseek API540 result, err = countTokensWithDeepseek(req)541 } else if strings.Contains(strings.ToLower(req.Model), "gpt") || strings.Contains(strings.ToLower(req.Model), "openai") {542 log.Printf("使用OpenAI API计算token")543 // 使用OpenAI API544 result, err = countTokensWithOpenAI(req)545 } else if strings.HasPrefix(strings.ToLower(req.Model), "claude") {546 log.Printf("使用Claude API计算token")547 // 使用Claude API548 if config.AnthropicKey == "" {549 log.Printf("错误: ANTHROPIC_API_KEY未设置")550 c.JSON(http.StatusBadRequest, ErrorResponse{Error: "ANTHROPIC_API_KEY 未设置,无法使用Claude模型"})551 return552 }553 result, err = countTokensWithClaude(req)554 } else if strings.Contains(strings.ToLower(req.Model), "gemini") {555 log.Printf("使用Gemini API计算token")556 // 使用Gemini API557 if config.GoogleKey == "" {558 log.Printf("错误: GOOGLE_API_KEY未设置")559 c.JSON(http.StatusBadRequest, ErrorResponse{Error: "GOOGLE_API_KEY 未设置,无法使用Gemini模型"})560 return561 }562 result, err = countTokensWithGemini(req)563 } else if isUnsupportedModel {564 log.Printf("不支持的模型: %s, 将使用GPT-4o估算", originalModel)565 // 不支持的模型,使用GPT-4o估算566 // 创建新的请求,使用GPT-4o567 gptReq := req568 gptReq.Model = "gpt-4o"569 570 // 使用OpenAI API571 estimatedResult, estimateErr := countTokensWithOpenAI(gptReq)572 573 if estimateErr == nil {574 log.Printf("使用GPT-4o估算成功: 模型=%s, 估算tokens=%d", originalModel, estimatedResult.InputTokens)575 // 返回估算值,但添加警告信息,使用400状态码576 c.JSON(http.StatusBadRequest, gin.H{577 "input_tokens": estimatedResult.InputTokens,578 "warning": fmt.Sprintf("The tokenizer for model '%s' is not supported yet. This is an estimation based on gpt-4o and may not be accurate.", originalModel),579 "estimated_with": "gpt-4o",580 "error": fmt.Sprintf("Unsupported model: %s", originalModel),581 })582 return583 } else {584 log.Printf("使用GPT-4o估算失败: %v", estimateErr)585 c.JSON(http.StatusBadRequest, ErrorResponse{Error: fmt.Sprintf("Failed to estimate tokens for unsupported model: %s", originalModel)})586 return587 }588 } else {589 log.Printf("完全不支持的模型: %s, 将尝试使用GPT-4o估算", originalModel)590 // 完全不支持的情况,返回错误但仍提供估算值591 // 使用GPT-4o进行估算592 gptReq := req593 gptReq.Model = "gpt-4o"594 595 estimatedResult, estimateErr := countTokensWithOpenAI(gptReq)596 if estimateErr == nil {597 log.Printf("使用GPT-4o估算成功: 模型=%s, 估算tokens=%d", originalModel, estimatedResult.InputTokens)598 c.JSON(http.StatusBadRequest, gin.H{599 "input_tokens": estimatedResult.InputTokens,600 "warning": fmt.Sprintf("The tokenizer for model '%s' is not supported yet. This is an estimation based on gpt-4o and may not be accurate.", originalModel),601 "estimated_with": "gpt-4o",602 "error": fmt.Sprintf("Unsupported model: %s", originalModel),603 })604 } else {605 log.Printf("使用GPT-4o估算失败: %v", estimateErr)606 c.JSON(http.StatusBadRequest, ErrorResponse{Error: fmt.Sprintf("The tokenizer for model '%s' is not supported yet.", originalModel)})607 }608 return609 }610 611 if err != nil {612 log.Printf("计算token失败: %v", err)613 614 // 对所有API调用失败的情况尝试使用GPT-4o估算615 log.Printf("API调用失败,尝试使用GPT-4o估算: 原始模型=%s, 错误=%v", req.Model, err)616 617 // 创建新的请求,使用GPT-4o618 gptReq := req619 gptReq.Model = "gpt-4o"620 621 // 使用OpenAI API进行估算622 estimatedResult, estimateErr := countTokensWithOpenAI(gptReq)623 624 if estimateErr == nil {625 log.Printf("使用GPT-4o估算成功: 模型=%s, 估算tokens=%d", originalModel, estimatedResult.InputTokens)626 627 // 返回估算值,但添加警告信息和原始错误,使用400状态码628 c.JSON(http.StatusBadRequest, gin.H{629 "input_tokens": estimatedResult.InputTokens,630 "warning": fmt.Sprintf("Token calculation for model '%s' failed. This is an estimation based on gpt-4o and may not be accurate.", originalModel),631 "estimated_with": "gpt-4o",632 "error": err.Error(),633 })634 return635 } else {636 log.Printf("使用GPT-4o估算也失败: %v", estimateErr)637 // 如果GPT-4o估算也失败,返回原始错误638 c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()})639 return640 }641 }642 643 // 返回结果644 log.Printf("成功计算token: 模型=%s, 输入tokens=%d", req.Model, result.InputTokens)645 c.JSON(http.StatusOK, result)646}647 648// 健康检查649func healthCheck(c *gin.Context) {650 c.JSON(http.StatusOK, gin.H{651 "status": "healthy",652 "time": time.Now().Format(time.RFC3339),653 })654}655 656// 防休眠任务657func startKeepAlive() {658 if config.ServiceURL == "" {659 return660 }661 662 healthURL := fmt.Sprintf("%s/health", config.ServiceURL)663 ticker := time.NewTicker(10 * time.Hour)664 665 // 立即执行一次检查666 go func() {667 log.Printf("Starting keep-alive checks to %s", healthURL)668 for {669 resp, err := http.Get(healthURL)670 if err != nil {671 log.Printf("Keep-alive check failed: %v", err)672 } else {673 resp.Body.Close()674 log.Printf("Keep-alive check successful")675 }676 677 // 等待下一次触发678 <-ticker.C679 }680 }()681}682 683func main() {684 // 加载配置685 loadConfig()686 log.Println("=== Token计算服务启动 ===")687 688 // 设置gin模式689 gin.SetMode(gin.ReleaseMode)690 log.Println("设置Gin为发布模式")691 692 // 创建路由693 r := gin.Default()694 log.Println("创建Gin路由")695 696 // 添加中间件697 r.Use(gin.Recovery())698 r.Use(func(c *gin.Context) {699 // 请求开始时间700 startTime := time.Now()701 702 // 请求信息记录703 log.Printf("收到请求: %s %s 来自 %s", c.Request.Method, c.Request.URL.Path, c.ClientIP())704 705 c.Writer.Header().Set("Access-Control-Allow-Origin", "*")706 c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")707 c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")708 if c.Request.Method == "OPTIONS" {709 c.AbortWithStatus(204)710 return711 }712 713 // 处理请求714 c.Next()715 716 // 请求完成时间717 endTime := time.Now()718 latency := endTime.Sub(startTime)719 720 // 请求结果记录721 log.Printf("请求完成: %s %s 状态=%d 耗时=%v",722 c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency)723 })724 725 // 路由726 r.GET("/health", healthCheck)727 r.POST("/count_tokens", countTokens)728 log.Println("配置路由: GET /health, POST /count_tokens")729 730 // 获取端口731 port := os.Getenv("PORT")732 if port == "" {733 port = "7860" // Hugging Face默认端口734 log.Println("使用默认端口: 7860")735 } else {736 log.Println("使用配置端口:", port)737 }738 739 // 启动防休眠任务740 startKeepAlive()741 742 // 启动服务器743 log.Printf("=== 服务器启动在端口 %s ===", port)744 if err := r.Run(":" + port); err != nil {745 log.Fatalf("服务器启动失败: %v", err)746 }747}748 