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, "", " ") }