mirror of
https://github.com/hanzoai/dashscope-go-sdk.git
synced 2026-09-30 21:39:25 +00:00
237 lines
6.8 KiB
Go
237 lines
6.8 KiB
Go
package qwen
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
httpclient "github.com/hanzoai/dashscope-go-sdk/httpclient"
|
|
"github.com/gabriel-vasile/mimetype"
|
|
)
|
|
|
|
// upload image to aliyun oss that can be described by LLM
|
|
|
|
type CertResponse struct {
|
|
RequestID string `json:"request_id"`
|
|
Data CertOutput `json:"data"`
|
|
}
|
|
|
|
func (c *CertResponse) JSONString() string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
|
|
jsonByte, err := json.Marshal(c)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(jsonByte)
|
|
// return c.RequestID
|
|
}
|
|
|
|
type CertOutput struct {
|
|
Policy string `json:"policy"`
|
|
Signature string `json:"signature"`
|
|
UploadDir string `json:"upload_dir"`
|
|
UploadHost string `json:"upload_host"`
|
|
ExpireInSeconds int `json:"expire_in_seconds"`
|
|
MaxFileSizeMB int `json:"max_file_size_mb"`
|
|
CapacityLimitMB int `json:"capacity_limit_mb"`
|
|
OSSAccessKeyID string `json:"oss_access_key_id"`
|
|
XOSSObjectACL string `json:"x_oss_object_acl"`
|
|
XOSSForbidOverwrite string `json:"x_oss_forbid_overwrite"`
|
|
}
|
|
|
|
// url: https://dashscope.aliyuncs.com/api/v1/uploads
|
|
// header: {'user-agent': 'dashscope/1.14.0; python/3.11.6; platform/macOS-14.2.1-arm64-arm-64bit; processor/arm', 'Authorization': 'Bearer sk-xxxx', 'Accept': 'application/json'}
|
|
// params: {'action': 'getPolicy', 'model': 'qwen-vl-plus'}
|
|
// timeout: 300
|
|
func getUploadCertificate(ctx context.Context, model, apiKey string) (*CertResponse, error) {
|
|
url := DashScopeBaseURL + "/v1/uploads"
|
|
header := map[string]string{
|
|
"Authorization": "Bearer " + apiKey,
|
|
"Accept": "application/json",
|
|
}
|
|
params := map[string]string{
|
|
"action": "getPolicy",
|
|
"model": model,
|
|
}
|
|
|
|
headerOpt := httpclient.WithHeader(header)
|
|
timeoutOpt := httpclient.WithTimeout(3 * time.Second)
|
|
|
|
cli := httpclient.NewHTTPClient()
|
|
respData := &CertResponse{}
|
|
err := cli.Get(ctx, url, params, respData, headerOpt, timeoutOpt)
|
|
if err != nil {
|
|
// panic(err)
|
|
return nil, &WrapMessageError{Message: "certificate Error", Cause: err}
|
|
}
|
|
|
|
return respData, nil
|
|
}
|
|
|
|
type UploadRequest struct {
|
|
File []byte `json:"file"`
|
|
}
|
|
|
|
// TODO:...
|
|
// if has upload resource set headers['X-DashScope-OssResourceResolve'] = 'enable'
|
|
/*
|
|
elem["image"] = ossURL 把原始的local_path 替换成 ossURL
|
|
"content": [
|
|
{"image": ""},
|
|
{"text": ""},
|
|
]
|
|
*/
|
|
|
|
// uploading local image to aliyun oss, a oss url will be returned.
|
|
func UploadLocalFile(ctx context.Context, filePath, model, apiKey string) (string, error) {
|
|
fileBytes, mimeType, err := loadLocalFileWithMimeType(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
fileName := filepath.Base(filePath)
|
|
|
|
return uploadFIle(ctx, fileBytes, fileName, mimeType, model, apiKey)
|
|
}
|
|
|
|
// download and uploading a online image to aliyun oss, a oss url will be returned.
|
|
func UploadFileFromURL(ctx context.Context, fileURL, model, apiKey string) (string, error) {
|
|
fileBytes, mimeType, err := downloadFileWithMimeType(fileURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fileName := filepath.Base(fileURL)
|
|
|
|
return uploadFIle(ctx, fileBytes, fileName, mimeType, model, apiKey)
|
|
}
|
|
|
|
func uploadFIle(ctx context.Context, fileBytes []byte, fileName, mimeType, model, apiKey string) (string, error) {
|
|
certInfo, err := getUploadCertificate(ctx, model, apiKey)
|
|
if err != nil {
|
|
return "", &WrapMessageError{Message: "upload Cert Error", Cause: err}
|
|
}
|
|
|
|
ossKey := certInfo.Data.UploadDir + "/" + fileName
|
|
|
|
formData := buildFormData(certInfo.Data, ossKey, mimeType)
|
|
|
|
header := getUploadHeaders()
|
|
uploadReq, extraHeaders, err := buildMultiPartRequest(fileName, formData, fileBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for k, v := range extraHeaders {
|
|
header[k] = v
|
|
}
|
|
|
|
cli := httpclient.NewHTTPClient()
|
|
headerOpt := httpclient.WithHeader(header)
|
|
|
|
err = cli.Post(ctx, certInfo.Data.UploadHost, uploadReq, nil, headerOpt)
|
|
if err != nil {
|
|
return "", &httpclient.HTTPRequestError{Message: "upload image Error", Cause: err}
|
|
}
|
|
|
|
ossURL := "oss://" + ossKey
|
|
return ossURL, nil
|
|
}
|
|
|
|
func getUploadHeaders() map[string]string {
|
|
headers := make(map[string]string)
|
|
// headers["User-Agent"] = getUserAgent() // Assuming you have a function getUserAgent()
|
|
headers["Accept"] = "application/json"
|
|
headers["Date"] = time.Now().Format(time.RFC1123) // Go's equivalent to Python's format_date_time(mktime(datetime.now().timetuple()))
|
|
return headers
|
|
}
|
|
|
|
func loadLocalFileWithMimeType(filePath string) ([]byte, string, error) {
|
|
imgBytes, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, "", &WrapMessageError{Message: "read file Error", Cause: err}
|
|
}
|
|
|
|
mt, err := mimetype.DetectFile(filePath)
|
|
if err != nil {
|
|
return nil, "", &WrapMessageError{Message: "detectfile mimetype Error", Cause: err}
|
|
}
|
|
|
|
return imgBytes, mt.String(), nil
|
|
}
|
|
|
|
func downloadFileWithMimeType(url string) ([]byte, string, error) {
|
|
resp, err := http.Get(url) //nolint:all
|
|
if err != nil {
|
|
return nil, "", &WrapMessageError{Message: "http get image Error", Cause: err}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
imgBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, "", &WrapMessageError{Message: "read image Error", Cause: err}
|
|
}
|
|
|
|
mt, err := mimetype.DetectReader(bytes.NewReader(imgBytes))
|
|
if err != nil {
|
|
return nil, "", &WrapMessageError{Message: "detect image mimetype Error", Cause: err}
|
|
}
|
|
|
|
return imgBytes, mt.String(), nil
|
|
}
|
|
|
|
// build request body for upload file.
|
|
func buildMultiPartRequest(fileName string, formData map[string]string, fileBytes []byte) (*bytes.Buffer, map[string]string, error) {
|
|
// buffer to save multipart body
|
|
requestBody := new(bytes.Buffer)
|
|
writer := multipart.NewWriter(requestBody)
|
|
|
|
// write form data to buffer
|
|
for key, value := range formData {
|
|
_ = writer.WriteField(key, value)
|
|
}
|
|
|
|
part, err := writer.CreateFormFile("file", fileName)
|
|
if err != nil {
|
|
return requestBody, nil, &WrapMessageError{Message: "create form file Error", Cause: err}
|
|
}
|
|
|
|
// write file bytes to buffer
|
|
_, err = part.Write(fileBytes)
|
|
if err != nil {
|
|
return requestBody, nil, &WrapMessageError{Message: "write Error", Cause: err}
|
|
}
|
|
|
|
// Content-Type header content
|
|
contentType := writer.FormDataContentType()
|
|
err = writer.Close()
|
|
if err != nil {
|
|
return requestBody, nil, &WrapMessageError{Message: "close writer Error", Cause: err}
|
|
}
|
|
|
|
header := make(map[string]string)
|
|
header["Content-Type"] = contentType
|
|
|
|
return requestBody, header, nil
|
|
}
|
|
|
|
func buildFormData(certOutput CertOutput, ossKey, mimeType string) map[string]string {
|
|
formData := make(map[string]string)
|
|
formData["OSSAccessKeyId"] = certOutput.OSSAccessKeyID
|
|
formData["Signature"] = certOutput.Signature
|
|
formData["policy"] = certOutput.Policy
|
|
formData["key"] = ossKey
|
|
formData["x-oss-object-acl"] = certOutput.XOSSObjectACL
|
|
formData["x-oss-forbid-overwrite"] = certOutput.XOSSForbidOverwrite
|
|
formData["success_action_status"] = "200"
|
|
formData["x-oss-content-type"] = mimeType
|
|
return formData
|
|
}
|