216 lines
6.5 KiB
Go
216 lines
6.5 KiB
Go
package openai
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"opencatd-open/pkg/tokenizer"
|
|
"opencatd-open/store"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func ChatProxy(c *gin.Context, chatReq *ChatCompletionRequest) {
|
|
usagelog := store.Tokens{Model: chatReq.Model}
|
|
|
|
token, _ := c.Get("localuser")
|
|
|
|
lu, err := store.GetUserByToken(token.(string))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": gin.H{
|
|
"message": err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
usagelog.UserID = int(lu.ID)
|
|
|
|
var prompt string
|
|
for _, msg := range chatReq.Messages {
|
|
switch ct := msg.Content.(type) {
|
|
case string:
|
|
prompt += "<" + msg.Role + ">: " + msg.Content.(string) + "\n"
|
|
case []any:
|
|
for _, item := range ct {
|
|
if m, ok := item.(map[string]interface{}); ok {
|
|
if m["type"] == "text" {
|
|
prompt += "<" + msg.Role + ">: " + m["text"].(string) + "\n"
|
|
} else if m["type"] == "image_url" {
|
|
if url, ok := m["image_url"].(map[string]interface{}); ok {
|
|
fmt.Printf(" URL: %v\n", url["url"])
|
|
if strings.HasPrefix(url["url"].(string), "http") {
|
|
fmt.Println("网络图片:", url["url"].(string))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": gin.H{
|
|
"message": "Invalid content type",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
if len(chatReq.Tools) > 0 {
|
|
tooljson, _ := json.Marshal(chatReq.Tools)
|
|
prompt += "<tools>: " + string(tooljson) + "\n"
|
|
}
|
|
}
|
|
switch chatReq.Model {
|
|
case "gpt-4o", "gpt-4o-mini", "chatgpt-4o-latest":
|
|
chatReq.MaxTokens = 16384
|
|
}
|
|
if chatReq.Stream {
|
|
chatReq.StreamOptions = &StreamOption{IncludeUsage: true}
|
|
}
|
|
|
|
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
|
|
|
|
// onekey, err := store.SelectKeyCache("openai")
|
|
onekey, err := store.SelectKeyCacheByModel(chatReq.Model)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var req *http.Request
|
|
|
|
switch onekey.ApiType {
|
|
case "github":
|
|
req, err = http.NewRequest(c.Request.Method, Github_Marketplace, bytes.NewReader(chatReq.ToByteJson()))
|
|
req.Header = c.Request.Header
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
|
case "azure":
|
|
var buildurl string
|
|
if onekey.EndPoint != "" {
|
|
buildurl = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatReq.Model), AzureApiVersion)
|
|
} else {
|
|
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatReq.Model), AzureApiVersion)
|
|
}
|
|
req, err = http.NewRequest(c.Request.Method, buildurl, bytes.NewReader(chatReq.ToByteJson()))
|
|
req.Header = c.Request.Header
|
|
req.Header.Set("api-key", onekey.Key)
|
|
default:
|
|
req, err = http.NewRequest(c.Request.Method, OpenAI_Endpoint, bytes.NewReader(chatReq.ToByteJson())) // default endpoint
|
|
|
|
if AIGateWay_Endpoint != "" { // cloudflare gateway的endpoint
|
|
req, err = http.NewRequest(c.Request.Method, AIGateWay_Endpoint, bytes.NewReader(chatReq.ToByteJson()))
|
|
}
|
|
if Custom_Endpoint != "" { // 自定义endpoint
|
|
req, err = http.NewRequest(c.Request.Method, Custom_Endpoint, bytes.NewReader(chatReq.ToByteJson()))
|
|
}
|
|
if onekey.EndPoint != "" { // 优先key的endpoint
|
|
req, err = http.NewRequest(c.Request.Method, onekey.EndPoint+c.Request.RequestURI, bytes.NewReader(chatReq.ToByteJson()))
|
|
}
|
|
|
|
req.Header = c.Request.Header
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result string
|
|
if chatReq.Stream {
|
|
for key, value := range resp.Header {
|
|
for _, v := range value {
|
|
c.Writer.Header().Add(key, v)
|
|
}
|
|
}
|
|
c.Writer.WriteHeader(resp.StatusCode)
|
|
teeReader := io.TeeReader(resp.Body, c.Writer)
|
|
// 流式响应
|
|
scanner := bufio.NewScanner(teeReader)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
if len(line) > 0 && bytes.HasPrefix(line, []byte("data: ")) {
|
|
if bytes.HasPrefix(line, []byte("data: [DONE]")) {
|
|
break
|
|
}
|
|
var opiResp ChatCompletionStreamResponse
|
|
line = bytes.Replace(line, []byte("data: "), []byte(""), -1)
|
|
line = bytes.TrimSpace(line)
|
|
if err := json.Unmarshal(line, &opiResp); err != nil {
|
|
continue
|
|
}
|
|
|
|
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
|
|
if opiResp.Choices[0].Delta.Role != "" {
|
|
result += "<" + opiResp.Choices[0].Delta.Role + "> "
|
|
}
|
|
result += opiResp.Choices[0].Delta.Content // 计算Content Token
|
|
|
|
if len(opiResp.Choices[0].Delta.ToolCalls) > 0 { // 计算ToolCalls token
|
|
if opiResp.Choices[0].Delta.ToolCalls[0].Function.Name != "" {
|
|
result += "name:" + opiResp.Choices[0].Delta.ToolCalls[0].Function.Name + " arguments:"
|
|
}
|
|
result += opiResp.Choices[0].Delta.ToolCalls[0].Function.Arguments
|
|
}
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
|
|
// 处理非流式响应
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
fmt.Println("Error reading response body:", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
var opiResp ChatCompletionResponse
|
|
if err := json.Unmarshal(body, &opiResp); err != nil {
|
|
log.Println("Error parsing JSON:", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
|
|
if opiResp.Choices[0].Message.Role != "" {
|
|
result += "<" + opiResp.Choices[0].Message.Role + "> "
|
|
}
|
|
result += opiResp.Choices[0].Message.Content
|
|
|
|
if len(opiResp.Choices[0].Message.ToolCalls) > 0 {
|
|
if opiResp.Choices[0].Message.ToolCalls[0].Function.Name != "" {
|
|
result += "name:" + opiResp.Choices[0].Message.ToolCalls[0].Function.Name + " arguments:"
|
|
}
|
|
result += opiResp.Choices[0].Message.ToolCalls[0].Function.Arguments
|
|
}
|
|
|
|
}
|
|
for k, v := range resp.Header {
|
|
c.Writer.Header().Set(k, v[0])
|
|
}
|
|
|
|
c.JSON(http.StatusOK, opiResp)
|
|
}
|
|
usagelog.CompletionCount = tokenizer.NumTokensFromStr(result, chatReq.Model)
|
|
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
|
|
if err := store.Record(&usagelog); err != nil {
|
|
log.Println(err)
|
|
}
|
|
if err := store.SumDaily(usagelog.UserID); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|