feat: 更新项目代码
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -75,8 +76,8 @@ func loadRuntimeDeepSeek() (apiBase, apiKey, defModel string, ok bool) {
|
||||
return "", "", "", false
|
||||
}
|
||||
|
||||
func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string, error) {
|
||||
// 首先尝试从SiteAIRuntime读取配置(向后兼容)
|
||||
// openDeepSeekChatURL 解析 DeepSeek 兼容 /chat/completions 的完整 URL、密钥与最终落库模型名
|
||||
func openDeepSeekChatURL(model string) (fullURL, apiKey, resolvedModel string, err error) {
|
||||
if base, key, defModel, ok := loadRuntimeDeepSeek(); ok {
|
||||
if model == "" {
|
||||
model = defModel
|
||||
@@ -84,65 +85,74 @@ func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string,
|
||||
if model == "" {
|
||||
model = "deepseek-chat"
|
||||
}
|
||||
url := strings.TrimSuffix(base, "/") + "/chat/completions"
|
||||
return callOpenAICompatible(url, key, model, messages, maxRetries, 90*time.Second)
|
||||
return strings.TrimSuffix(base, "/") + "/chat/completions", key, model, nil
|
||||
}
|
||||
|
||||
// 从新的AI配置表读取
|
||||
if apiKey, apiBase, defaultModel, models, ok := loadAIConfig("deepseek"); ok {
|
||||
if model == "" {
|
||||
model = defaultModel
|
||||
}
|
||||
if model == "" {
|
||||
model = "deepseek-chat"
|
||||
}
|
||||
// 验证模型是否在允许列表中
|
||||
if len(models) > 0 {
|
||||
allowed := false
|
||||
for _, m := range models {
|
||||
if m == model {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
model = models[0] // 使用第一个允许的模型
|
||||
apiKey, apiBase, defaultModel, models, ok := loadAIConfig("deepseek")
|
||||
if !ok {
|
||||
return "", "", "", fmt.Errorf("DeepSeek配置未设置,请在管理员后台配置API Key和Base URL")
|
||||
}
|
||||
if model == "" {
|
||||
model = defaultModel
|
||||
}
|
||||
if model == "" {
|
||||
model = "deepseek-chat"
|
||||
}
|
||||
if len(models) > 0 {
|
||||
allowed := false
|
||||
for _, m := range models {
|
||||
if m == model {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
url := strings.TrimSuffix(apiBase, "/") + "/chat/completions"
|
||||
return callOpenAICompatible(url, apiKey, model, messages, maxRetries, 90*time.Second)
|
||||
if !allowed {
|
||||
model = models[0]
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(apiBase, "/") + "/chat/completions", apiKey, model, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("DeepSeek配置未设置,请在管理员后台配置API Key和Base URL")
|
||||
func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string, error) {
|
||||
urlStr, key, m, err := openDeepSeekChatURL(model)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return callOpenAICompatible(urlStr, key, m, messages, maxRetries, 90*time.Second)
|
||||
}
|
||||
|
||||
// openKimiChatURL 解析 Kimi /v1/chat/completions
|
||||
func openKimiChatURL(model string) (fullURL, apiKey, resolvedModel string, err error) {
|
||||
apiKey, apiBase, defaultModel, models, ok := loadAIConfig("kimi")
|
||||
if !ok {
|
||||
return "", "", "", fmt.Errorf("Kimi配置未设置,请在管理员后台配置API Key和Base URL")
|
||||
}
|
||||
if model == "" {
|
||||
model = defaultModel
|
||||
}
|
||||
if model == "" {
|
||||
model = "kimi-k2-0905-preview"
|
||||
}
|
||||
if len(models) > 0 {
|
||||
allowed := false
|
||||
for _, m := range models {
|
||||
if m == model {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
model = models[0]
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(apiBase, "/") + "/v1/chat/completions", apiKey, model, nil
|
||||
}
|
||||
|
||||
func CallKimi(messages []ChatMessage, model string) (string, error) {
|
||||
// 从新的AI配置表读取
|
||||
if apiKey, apiBase, defaultModel, models, ok := loadAIConfig("kimi"); ok {
|
||||
if model == "" {
|
||||
model = defaultModel
|
||||
}
|
||||
if model == "" {
|
||||
model = "kimi-k2-0905-preview"
|
||||
}
|
||||
// 验证模型是否在允许列表中
|
||||
if len(models) > 0 {
|
||||
allowed := false
|
||||
for _, m := range models {
|
||||
if m == model {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
model = models[0] // 使用第一个允许的模型
|
||||
}
|
||||
}
|
||||
url := strings.TrimSuffix(apiBase, "/") + "/v1/chat/completions"
|
||||
return callOpenAICompatible(url, apiKey, model, messages, 1, 30*time.Second)
|
||||
urlStr, key, m, err := openKimiChatURL(model)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Kimi配置未设置,请在管理员后台配置API Key和Base URL")
|
||||
return callOpenAICompatible(urlStr, key, m, messages, 1, 30*time.Second)
|
||||
}
|
||||
|
||||
func callOpenAICompatible(url, apiKey, model string, messages []ChatMessage, maxRetries int, timeout time.Duration) (string, error) {
|
||||
@@ -211,3 +221,49 @@ func CallAI(provider, model string, messages []ChatMessage) (string, error) {
|
||||
return "", fmt.Errorf("不支持的AI提供商: %s,目前支持的提供商: deepseek, kimi", provider)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAIChatStream 向上游发起 stream:true 的请求;返回的 ReadCloser 需由调用方 Close。statusCode 非 200 时 body 已读完并关闭,rc 为 nil。
|
||||
func OpenAIChatStream(ctx context.Context, provider, model string, messages []ChatMessage) (rc io.ReadCloser, statusCode int, err error) {
|
||||
var urlStr, apiKey, m string
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
urlStr, apiKey, m, err = openDeepSeekChatURL(model)
|
||||
case "kimi":
|
||||
urlStr, apiKey, m, err = openKimiChatURL(model)
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("不支持的AI提供商: %s", provider)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
streamBody := map[string]interface{}{
|
||||
"model": m,
|
||||
"messages": messages,
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 2000,
|
||||
"stream": true,
|
||||
}
|
||||
bodyBytes, jerr := json.Marshal(streamBody)
|
||||
if jerr != nil {
|
||||
return nil, 0, fmt.Errorf("序列化请求失败: %w", jerr)
|
||||
}
|
||||
req, rerr := http.NewRequestWithContext(ctx, "POST", urlStr, bytes.NewReader(bodyBytes))
|
||||
if rerr != nil {
|
||||
return nil, 0, rerr
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, derr := client.Do(req)
|
||||
if derr != nil {
|
||||
return nil, 0, derr
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return nil, resp.StatusCode, fmt.Errorf("%s", strings.TrimSpace(string(b)))
|
||||
}
|
||||
return resp.Body, http.StatusOK, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user