This commit is contained in:
yanweidong
2026-02-02 19:04:32 +08:00
parent b267b10697
commit 827ec7bf5a
13 changed files with 337 additions and 541 deletions

View File

@@ -0,0 +1,266 @@
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, "", " ")
}