|
@@ -1,10 +1,12 @@
|
|
|
package claude
|
|
package claude
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "encoding/base64"
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"io"
|
|
"io"
|
|
|
"net/http"
|
|
"net/http"
|
|
|
|
|
+ "path/filepath"
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/common"
|
|
@@ -44,6 +46,61 @@ func maybeMarkClaudeRefusal(c *gin.Context, stopReason string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func createClaudeFileSource(file *dto.MessageFile) *types.FileSource {
|
|
|
|
|
+ if file == nil || file.FileData == "" {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ if strings.HasPrefix(file.FileData, "http://") || strings.HasPrefix(file.FileData, "https://") {
|
|
|
|
|
+ return types.NewURLFileSource(file.FileData)
|
|
|
|
|
+ }
|
|
|
|
|
+ mimeType := ""
|
|
|
|
|
+ if ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(file.FileName)), "."); ext != "" {
|
|
|
|
|
+ if detected := service.GetMimeTypeByExtension(ext); detected != "application/octet-stream" {
|
|
|
|
|
+ mimeType = detected
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return types.NewBase64FileSource(file.FileData, mimeType)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func buildClaudeFileMessage(c *gin.Context, file *dto.MessageFile) (*dto.ClaudeMediaMessage, error) {
|
|
|
|
|
+ source := createClaudeFileSource(file)
|
|
|
|
|
+ if source == nil {
|
|
|
|
|
+ return nil, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ base64Data, mimeType, err := service.GetBase64Data(c, source, "formatting document for Claude")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("get file data failed: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ switch strings.ToLower(mimeType) {
|
|
|
|
|
+ case "application/pdf":
|
|
|
|
|
+ return &dto.ClaudeMediaMessage{
|
|
|
|
|
+ Type: "document",
|
|
|
|
|
+ Source: &dto.ClaudeMessageSource{
|
|
|
|
|
+ Type: "base64",
|
|
|
|
|
+ MediaType: mimeType,
|
|
|
|
|
+ Data: base64Data,
|
|
|
|
|
+ },
|
|
|
|
|
+ }, nil
|
|
|
|
|
+ case "text/plain":
|
|
|
|
|
+ decodedData, err := base64.StdEncoding.DecodeString(base64Data)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("decode text file data failed: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return &dto.ClaudeMediaMessage{
|
|
|
|
|
+ Type: "text",
|
|
|
|
|
+ Text: common.GetPointer(string(decodedData)),
|
|
|
|
|
+ }, nil
|
|
|
|
|
+ default:
|
|
|
|
|
+ msg := fmt.Sprintf("claude: skip unsupported file content, filename=%q, mime=%q", file.FileName, mimeType)
|
|
|
|
|
+ if c != nil {
|
|
|
|
|
+ logger.LogInfo(c, msg)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ common.SysLog(msg)
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil, nil
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) {
|
|
func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) {
|
|
|
claudeTools := make([]any, 0, len(textRequest.Tools))
|
|
claudeTools := make([]any, 0, len(textRequest.Tools))
|
|
|
|
|
|
|
@@ -343,16 +400,22 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
|
|
} else {
|
|
} else {
|
|
|
claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0)
|
|
claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0)
|
|
|
for _, mediaMessage := range message.ParseContent() {
|
|
for _, mediaMessage := range message.ParseContent() {
|
|
|
- claudeMediaMessage := dto.ClaudeMediaMessage{
|
|
|
|
|
- Type: mediaMessage.Type,
|
|
|
|
|
- }
|
|
|
|
|
- if mediaMessage.Type == "text" {
|
|
|
|
|
- claudeMediaMessage.Text = common.GetPointer[string](mediaMessage.Text)
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+ switch mediaMessage.Type {
|
|
|
|
|
+ case "text":
|
|
|
|
|
+ claudeMediaMessages = append(claudeMediaMessages, dto.ClaudeMediaMessage{
|
|
|
|
|
+ Type: "text",
|
|
|
|
|
+ Text: common.GetPointer[string](mediaMessage.Text),
|
|
|
|
|
+ })
|
|
|
|
|
+ case dto.ContentTypeImageURL:
|
|
|
|
|
+ claudeMediaMessage := dto.ClaudeMediaMessage{
|
|
|
|
|
+ Type: "image",
|
|
|
|
|
+ Source: &dto.ClaudeMessageSource{
|
|
|
|
|
+ Type: "base64",
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
imageUrl := mediaMessage.GetImageMedia()
|
|
imageUrl := mediaMessage.GetImageMedia()
|
|
|
- claudeMediaMessage.Type = "image"
|
|
|
|
|
- claudeMediaMessage.Source = &dto.ClaudeMessageSource{
|
|
|
|
|
- Type: "base64",
|
|
|
|
|
|
|
+ if imageUrl == nil {
|
|
|
|
|
+ continue
|
|
|
}
|
|
}
|
|
|
// 使用统一的文件服务获取图片数据
|
|
// 使用统一的文件服务获取图片数据
|
|
|
var source *types.FileSource
|
|
var source *types.FileSource
|
|
@@ -367,8 +430,19 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
|
|
}
|
|
}
|
|
|
claudeMediaMessage.Source.MediaType = mimeType
|
|
claudeMediaMessage.Source.MediaType = mimeType
|
|
|
claudeMediaMessage.Source.Data = base64Data
|
|
claudeMediaMessage.Source.Data = base64Data
|
|
|
|
|
+ claudeMediaMessages = append(claudeMediaMessages, claudeMediaMessage)
|
|
|
|
|
+ // FIXME
|
|
|
|
|
+ //case dto.ContentTypeFile:
|
|
|
|
|
+ // claudeFileMessage, err := buildClaudeFileMessage(c, mediaMessage.GetFile())
|
|
|
|
|
+ // if err != nil {
|
|
|
|
|
+ // return nil, err
|
|
|
|
|
+ // }
|
|
|
|
|
+ // if claudeFileMessage != nil {
|
|
|
|
|
+ // claudeMediaMessages = append(claudeMediaMessages, *claudeFileMessage)
|
|
|
|
|
+ // }
|
|
|
|
|
+ default:
|
|
|
|
|
+ continue
|
|
|
}
|
|
}
|
|
|
- claudeMediaMessages = append(claudeMediaMessages, claudeMediaMessage)
|
|
|
|
|
}
|
|
}
|
|
|
if message.ToolCalls != nil {
|
|
if message.ToolCalls != nil {
|
|
|
for _, toolCall := range message.ParseToolCalls() {
|
|
for _, toolCall := range message.ParseToolCalls() {
|