| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- package dify
- import (
- "bytes"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "mime/multipart"
- "net/http"
- "one-api/common"
- "one-api/constant"
- "one-api/dto"
- relaycommon "one-api/relay/common"
- "one-api/relay/helper"
- "one-api/service"
- "one-api/types"
- "os"
- "strings"
- "github.com/gin-gonic/gin"
- )
- func uploadDifyFile(c *gin.Context, info *relaycommon.RelayInfo, user string, media dto.MediaContent) *DifyFile {
- uploadUrl := fmt.Sprintf("%s/v1/files/upload", info.BaseUrl)
- switch media.Type {
- case dto.ContentTypeImageURL:
- // Decode base64 data
- imageMedia := media.GetImageMedia()
- base64Data := imageMedia.Url
- // Remove base64 prefix if exists (e.g., "data:image/jpeg;base64,")
- if idx := strings.Index(base64Data, ","); idx != -1 {
- base64Data = base64Data[idx+1:]
- }
- // Decode base64 string
- decodedData, err := base64.StdEncoding.DecodeString(base64Data)
- if err != nil {
- common.SysError("failed to decode base64: " + err.Error())
- return nil
- }
- // Create temporary file
- tempFile, err := os.CreateTemp("", "dify-upload-*")
- if err != nil {
- common.SysError("failed to create temp file: " + err.Error())
- return nil
- }
- defer tempFile.Close()
- defer os.Remove(tempFile.Name())
- // Write decoded data to temp file
- if _, err := tempFile.Write(decodedData); err != nil {
- common.SysError("failed to write to temp file: " + err.Error())
- return nil
- }
- // Create multipart form
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
- // Add user field
- if err := writer.WriteField("user", user); err != nil {
- common.SysError("failed to add user field: " + err.Error())
- return nil
- }
- // Create form file with proper mime type
- mimeType := imageMedia.MimeType
- if mimeType == "" {
- mimeType = "image/jpeg" // default mime type
- }
- // Create form file
- part, err := writer.CreateFormFile("file", fmt.Sprintf("image.%s", strings.TrimPrefix(mimeType, "image/")))
- if err != nil {
- common.SysError("failed to create form file: " + err.Error())
- return nil
- }
- // Copy file content to form
- if _, err = io.Copy(part, bytes.NewReader(decodedData)); err != nil {
- common.SysError("failed to copy file content: " + err.Error())
- return nil
- }
- writer.Close()
- // Create HTTP request
- req, err := http.NewRequest("POST", uploadUrl, body)
- if err != nil {
- common.SysError("failed to create request: " + err.Error())
- return nil
- }
- req.Header.Set("Content-Type", writer.FormDataContentType())
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", info.ApiKey))
- // Send request
- client := service.GetHttpClient()
- resp, err := client.Do(req)
- if err != nil {
- common.SysError("failed to send request: " + err.Error())
- return nil
- }
- defer resp.Body.Close()
- // Parse response
- var result struct {
- Id string `json:"id"`
- }
- if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
- common.SysError("failed to decode response: " + err.Error())
- return nil
- }
- return &DifyFile{
- UploadFileId: result.Id,
- Type: "image",
- TransferMode: "local_file",
- }
- }
- return nil
- }
- func requestOpenAI2Dify(c *gin.Context, info *relaycommon.RelayInfo, request dto.GeneralOpenAIRequest) *DifyChatRequest {
- difyReq := DifyChatRequest{
- Inputs: make(map[string]interface{}),
- AutoGenerateName: false,
- }
- user := request.User
- if user == "" {
- user = helper.GetResponseID(c)
- }
- difyReq.User = user
- files := make([]DifyFile, 0)
- var content strings.Builder
- for _, message := range request.Messages {
- if message.Role == "system" {
- content.WriteString("SYSTEM: \n" + message.StringContent() + "\n")
- } else if message.Role == "assistant" {
- content.WriteString("ASSISTANT: \n" + message.StringContent() + "\n")
- } else {
- parseContent := message.ParseContent()
- for _, mediaContent := range parseContent {
- switch mediaContent.Type {
- case dto.ContentTypeText:
- content.WriteString("USER: \n" + mediaContent.Text + "\n")
- case dto.ContentTypeImageURL:
- media := mediaContent.GetImageMedia()
- var file *DifyFile
- if media.IsRemoteImage() {
- file.Type = media.MimeType
- file.TransferMode = "remote_url"
- file.URL = media.Url
- } else {
- file = uploadDifyFile(c, info, difyReq.User, mediaContent)
- }
- if file != nil {
- files = append(files, *file)
- }
- }
- }
- }
- }
- difyReq.Query = content.String()
- difyReq.Files = files
- mode := "blocking"
- if request.Stream {
- mode = "streaming"
- }
- difyReq.ResponseMode = mode
- return &difyReq
- }
- func streamResponseDify2OpenAI(difyResponse DifyChunkChatCompletionResponse) *dto.ChatCompletionsStreamResponse {
- response := dto.ChatCompletionsStreamResponse{
- Object: "chat.completion.chunk",
- Created: common.GetTimestamp(),
- Model: "dify",
- }
- var choice dto.ChatCompletionsStreamResponseChoice
- if strings.HasPrefix(difyResponse.Event, "workflow_") {
- if constant.DifyDebug {
- text := "Workflow: " + difyResponse.Data.WorkflowId
- if difyResponse.Event == "workflow_finished" {
- text += " " + difyResponse.Data.Status
- }
- choice.Delta.SetReasoningContent(text + "\n")
- }
- } else if strings.HasPrefix(difyResponse.Event, "node_") {
- if constant.DifyDebug {
- text := "Node: " + difyResponse.Data.NodeType
- if difyResponse.Event == "node_finished" {
- text += " " + difyResponse.Data.Status
- }
- choice.Delta.SetReasoningContent(text + "\n")
- }
- } else if difyResponse.Event == "message" || difyResponse.Event == "agent_message" {
- if difyResponse.Answer == "<details style=\"color:gray;background-color: #f8f8f8;padding: 8px;border-radius: 4px;\" open> <summary> Thinking... </summary>\n" {
- difyResponse.Answer = "<think>"
- } else if difyResponse.Answer == "</details>" {
- difyResponse.Answer = "</think>"
- }
- choice.Delta.SetContentString(difyResponse.Answer)
- }
- response.Choices = append(response.Choices, choice)
- return &response
- }
- func difyStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
- var responseText string
- usage := &dto.Usage{}
- var nodeToken int
- helper.SetEventStreamHeaders(c)
- helper.StreamScannerHandler(c, resp, info, func(data string) bool {
- var difyResponse DifyChunkChatCompletionResponse
- err := json.Unmarshal([]byte(data), &difyResponse)
- if err != nil {
- common.SysError("error unmarshalling stream response: " + err.Error())
- return true
- }
- var openaiResponse dto.ChatCompletionsStreamResponse
- if difyResponse.Event == "message_end" {
- usage = &difyResponse.MetaData.Usage
- return false
- } else if difyResponse.Event == "error" {
- return false
- } else {
- openaiResponse = *streamResponseDify2OpenAI(difyResponse)
- if len(openaiResponse.Choices) != 0 {
- responseText += openaiResponse.Choices[0].Delta.GetContentString()
- if openaiResponse.Choices[0].Delta.ReasoningContent != nil {
- nodeToken += 1
- }
- }
- }
- err = helper.ObjectData(c, openaiResponse)
- if err != nil {
- common.SysError(err.Error())
- }
- return true
- })
- helper.Done(c)
- if usage.TotalTokens == 0 {
- usage = service.ResponseText2Usage(responseText, info.UpstreamModelName, info.PromptTokens)
- }
- usage.CompletionTokens += nodeToken
- return usage, nil
- }
- func difyHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
- var difyResponse DifyChatCompletionResponse
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
- }
- common.CloseResponseBodyGracefully(resp)
- err = json.Unmarshal(responseBody, &difyResponse)
- if err != nil {
- return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
- }
- fullTextResponse := dto.OpenAITextResponse{
- Id: difyResponse.ConversationId,
- Object: "chat.completion",
- Created: common.GetTimestamp(),
- Usage: difyResponse.MetaData.Usage,
- }
- choice := dto.OpenAITextResponseChoice{
- Index: 0,
- Message: dto.Message{
- Role: "assistant",
- Content: difyResponse.Answer,
- },
- FinishReason: "stop",
- }
- fullTextResponse.Choices = append(fullTextResponse.Choices, choice)
- jsonResponse, err := json.Marshal(fullTextResponse)
- if err != nil {
- return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
- }
- c.Writer.Header().Set("Content-Type", "application/json")
- c.Writer.WriteHeader(resp.StatusCode)
- c.Writer.Write(jsonResponse)
- return &difyResponse.MetaData.Usage, nil
- }
|