FEOP / convert.go
KaThaNg's picture
Upload 12 files
a0e64e6 verified
package main
import (
"encoding/json"
"errors"
"strings"
)
// estimateTokens ước tính số lượng token từ một chuỗi văn bản.
// Hàm này sử dụng một tỉ lệ dựa trên ký tự. Tỉ lệ ~2.5 ký tự/token
// được chọn dựa trên phản hồi thực tế để có kết quả gần đúng hơn.
// Đây vẫn là một ước tính và có thể không khớp chính xác 100%
// với bộ đếm token của các mô hình hoặc ứng dụng cụ thể.
func estimateTokens(text string) int {
if text == "" {
return 0
}
// Tỉ lệ: 1 token ~= 2.5 ký tự.
// Để tránh dùng số thực: len(text) / 2.5 == len(text) * 10 / 25 == len(text) * 2 / 5
estimated := (len(text) * 2) / 5
if estimated == 0 && len(text) > 0 {
return 1
}
return estimated
}
func extractTextFromClaudeContent(content json.RawMessage, role string) string {
var contentStr string
if err := json.Unmarshal(content, &contentStr); err == nil {
return contentStr
}
var contentBlocks []ClaudeContentBlock
if err := json.Unmarshal(content, &contentBlocks); err == nil {
var parts []string
for _, block := range contentBlocks {
if block.Type == "text" {
parts = append(parts, block.Text)
}
}
return strings.Join(parts, "\n")
}
return ""
}
// estimateTokensFromClaudeRequest tính toán tổng số token ước tính từ một yêu cầu Claude.
func estimateTokensFromClaudeRequest(claudeReq *ClaudeRequest) int {
var textBuilder strings.Builder
if len(claudeReq.System) > 0 {
textBuilder.WriteString(extractTextFromClaudeContent(claudeReq.System, "system"))
textBuilder.WriteString("\n")
}
for _, msg := range claudeReq.Messages {
textBuilder.WriteString(extractTextFromClaudeContent(msg.Content, msg.Role))
textBuilder.WriteString("\n")
}
return estimateTokens(textBuilder.String())
}
// --- Fallback Conversion Mode ---
func convertClaudeRequestToOpenAI(claudeReq *ClaudeRequest) (*OpenAIRequest, error) {
openAIMessages := []OpenAIMessage{}
if len(claudeReq.System) > 0 {
systemContent := extractTextFromClaudeContent(claudeReq.System, "system")
if systemContent != "" {
openAIMessages = append(openAIMessages, OpenAIMessage{Role: "system", Content: systemContent})
}
}
for _, msg := range claudeReq.Messages {
if msg.Role != "user" && msg.Role != "assistant" {
continue
}
messageContent := extractTextFromClaudeContent(msg.Content, msg.Role)
if messageContent != "" || msg.Role == "assistant" {
openAIMessages = append(openAIMessages, OpenAIMessage{Role: msg.Role, Content: messageContent})
}
}
if len(openAIMessages) == 0 {
return nil, errors.New("conversion resulted in no valid messages for OpenAI request")
}
openAIReq := &OpenAIRequest{Model: claudeReq.Model, Messages: openAIMessages, Stream: claudeReq.Stream, MaxTokens: claudeReq.MaxTokens, Temperature: claudeReq.Temperature, TopP: claudeReq.TopP, TopK: claudeReq.TopK, Stop: claudeReq.StopSequences}
if openAIReq.Model == "" {
openAIReq.Model = "gpt-3.5-turbo"
}
return openAIReq, nil
}
func mapOpenAIFinishReasonToClaude(openAIFinishReason *string) string {
if openAIFinishReason == nil {
return "end_turn"
}
reason := *openAIFinishReason
switch reason {
case "stop":
return "end_turn"
case "length":
return "max_tokens"
case "function_call", "tool_calls":
return "tool_use"
case "content_filter":
return "stop_sequence"
default:
return "end_turn"
}
}
func convertOpenAIResponseToClaude(openAIResp *OpenAIResponse, claudeRequestID string) (*ClaudeResponse, error) {
if len(openAIResp.Choices) == 0 {
return nil, errors.New("OpenAI response has no choices")
}
choice := openAIResp.Choices[0]
claudeStopReason := mapOpenAIFinishReasonToClaude(choice.FinishReason)
claudeUsage := ClaudeUsage{}
if openAIResp.Usage != nil {
claudeUsage.InputTokens = openAIResp.Usage.PromptTokens
claudeUsage.OutputTokens = openAIResp.Usage.CompletionTokens
}
content := strings.TrimSpace(choice.Message.Content)
claudeContent := []ClaudeContentBlock{{Type: "text", Text: content}}
claudeResp := &ClaudeResponse{
ID: openAIResp.ID,
Type: "message",
Role: "assistant",
Content: claudeContent,
Model: openAIResp.Model,
StopReason: claudeStopReason,
StopSequence: nil,
Usage: claudeUsage,
}
if claudeResp.ID == "" {
claudeResp.ID = claudeRequestID
}
return claudeResp, nil
}