CoolFace
Apppublic

malt666/tokenizer

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
main.go748 linesDownload Raw Back to root
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