Files
gostock/internal/logic/deepseek/dpsk.go
yanweidong 827ec7bf5a fix bug
2026-02-02 19:04:32 +08:00

267 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package deepseek
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"time"
)
// 配置结构体
type Config struct {
APIKey string `json:"api_key"`
BaseURL string `json:"base_url"`
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
}
// 上传文件响应结构体
type UploadResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Bytes int `json:"bytes"`
CreatedAt int64 `json:"created_at"`
Filename string `json:"filename"`
Purpose string `json:"purpose"`
}
// 消息结构体
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
// 聊天请求结构体
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Stream bool `json:"stream,omitempty"`
}
// 聊天响应结构体
type ChatResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []struct {
Index int `json:"index"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
}
// JSON响应包装器
type JSONResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// 上传文件到DeepSeek
func UploadFile(apiKey, filePath string) (*UploadResponse, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %v", err)
}
defer file.Close()
// 创建multipart表单
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 添加文件字段
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
return nil, fmt.Errorf("创建表单文件失败: %v", err)
}
_, err = io.Copy(part, file)
if err != nil {
return nil, fmt.Errorf("复制文件内容失败: %v", err)
}
// 添加purpose字段
_ = writer.WriteField("purpose", "assistants")
err = writer.Close()
if err != nil {
return nil, fmt.Errorf("关闭writer失败: %v", err)
}
// 创建请求
req, err := http.NewRequest("POST", "https://api.deepseek.com/files", body)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", writer.FormDataContentType())
// 发送请求
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API返回错误: %d %s", resp.StatusCode, string(respBody))
}
// 解析响应
var uploadResp UploadResponse
err = json.Unmarshal(respBody, &uploadResp)
if err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err)
}
return &uploadResp, nil
}
// 发送聊天请求(带文件)
func ChatWithFile(apiKey, fileID, question string, config Config) (*ChatResponse, error) {
// 构建消息内容
content := fmt.Sprintf(`请分析我上传的Markdown文件并回答以下问题
问题:%s
请基于文件内容提供详细的回答。`, question)
// 创建聊天请求
chatReq := ChatRequest{
Model: config.Model,
Messages: []Message{
{
Role: "user",
Content: content,
},
},
MaxTokens: config.MaxTokens,
Temperature: config.Temperature,
Stream: false,
}
// 如果有文件ID添加到消息中
if fileID != "" {
chatReq.Messages[0].Content = fmt.Sprintf(`我上传了一个Markdown文件ID: %s请分析这个文件并回答以下问题
问题:%s
请基于文件内容提供详细的回答。`, fileID, question)
}
// 序列化请求体
reqBody, err := json.Marshal(chatReq)
if err != nil {
return nil, fmt.Errorf("序列化请求失败: %v", err)
}
// 创建请求
req, err := http.NewRequest("POST", config.BaseURL+"/chat/completions", bytes.NewBuffer(reqBody))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API返回错误: %d - %s", resp.StatusCode, string(respBody))
}
// 解析响应
var chatResp ChatResponse
err = json.Unmarshal(respBody, &chatResp)
if err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err)
}
return &chatResp, nil
}
// 处理Markdown文件上传和问答
func ProcessMarkdownQA(config Config, filePath, question string) ([]byte, error) {
var jsonResponse JSONResponse
// 1. 上传文件
fmt.Println("正在上传Markdown文件...")
uploadResp, err := UploadFile(config.APIKey, filePath)
if err != nil {
jsonResponse.Success = false
jsonResponse.Error = fmt.Sprintf("上传文件失败: %v", err)
return json.Marshal(jsonResponse)
}
fmt.Printf("文件上传成功文件ID: %s\n", uploadResp.ID)
// 2. 使用文件进行问答
fmt.Println("正在分析文件并回答问题...")
chatResp, err := ChatWithFile(config.APIKey, uploadResp.ID, question, config)
if err != nil {
jsonResponse.Success = false
jsonResponse.Error = fmt.Sprintf("问答失败: %v", err)
return json.Marshal(jsonResponse)
}
// 3. 构建成功响应
if len(chatResp.Choices) > 0 {
responseData := map[string]interface{}{
"file_info": map[string]interface{}{
"file_id": uploadResp.ID,
"filename": uploadResp.Filename,
"size_bytes": uploadResp.Bytes,
},
"question": question,
"answer": chatResp.Choices[0].Message.Content,
"usage": chatResp.Usage,
"model": chatResp.Model,
}
jsonResponse.Success = true
jsonResponse.Message = "处理成功"
jsonResponse.Data = responseData
} else {
jsonResponse.Success = false
jsonResponse.Error = "未收到有效回答"
}
return json.MarshalIndent(jsonResponse, "", " ")
}