mirror of
https://github.com/hanzoai/dashscope-go-sdk.git
synced 2026-09-30 21:39:25 +00:00
221 lines
6.1 KiB
Go
221 lines
6.1 KiB
Go
package dashscopego
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"log"
|
|
"strings"
|
|
|
|
embedding "github.com/hanzoai/dashscope-go-sdk/embedding"
|
|
httpclient "github.com/hanzoai/dashscope-go-sdk/httpclient"
|
|
"github.com/hanzoai/dashscope-go-sdk/paraformer"
|
|
"github.com/hanzoai/dashscope-go-sdk/qwen"
|
|
"github.com/hanzoai/dashscope-go-sdk/wanx"
|
|
)
|
|
|
|
type TongyiClient struct {
|
|
Model string
|
|
token string
|
|
httpCli httpclient.IHttpClient
|
|
}
|
|
|
|
func NewTongyiClient(model string, token string) *TongyiClient {
|
|
httpcli := httpclient.NewHTTPClient()
|
|
return newTongyiCLientWithHTTPCli(model, token, httpcli)
|
|
}
|
|
|
|
func newTongyiCLientWithHTTPCli(model string, token string, httpcli httpclient.IHttpClient) *TongyiClient {
|
|
return &TongyiClient{
|
|
Model: model,
|
|
httpCli: httpcli,
|
|
token: token,
|
|
}
|
|
}
|
|
|
|
// duplicate: CreateCompletion and CreateVLCompletion are the same but with different payload types.
|
|
// maybe this can be change in the future.
|
|
//
|
|
// nolint:lll
|
|
func (q *TongyiClient) CreateCompletion(ctx context.Context, payload *qwen.Request[*qwen.TextContent]) (*TextQwenResponse, error) {
|
|
payload = paylosdPreCheck(q, payload)
|
|
return genericCompletion(ctx, payload, q.httpCli, qwen.URLQwen(), q.token)
|
|
}
|
|
|
|
//nolint:lll
|
|
func (q *TongyiClient) CreateVLCompletion(ctx context.Context, payload *qwen.Request[*qwen.VLContentList]) (*VLQwenResponse, error) {
|
|
payload = paylosdPreCheck(q, payload)
|
|
|
|
for _, vMsg := range payload.Input.Messages {
|
|
tmpImageContent, hasImg := vMsg.Content.PopImageContent()
|
|
if hasImg && vMsg.Role == "user" {
|
|
filepath := tmpImageContent.Image
|
|
|
|
ossURL, hasUploadOss, err := checkIfNeedUploadFile(ctx, filepath, payload.Model, q.token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasUploadOss {
|
|
payload.HasUploadOss = true
|
|
}
|
|
vMsg.Content.SetImage(ossURL)
|
|
}
|
|
}
|
|
|
|
return genericCompletion(ctx, payload, q.httpCli, qwen.URLQwenVL(), q.token)
|
|
}
|
|
|
|
//nolint:lll
|
|
func (q *TongyiClient) CreateAudioCompletion(ctx context.Context, payload *qwen.Request[*qwen.AudioContentList]) (*AudioQwenResponse, error) {
|
|
payload = paylosdPreCheck(q, payload)
|
|
for _, acMsg := range payload.Input.Messages {
|
|
tmpAudioContent, hasAudio := acMsg.Content.PopAudioContent()
|
|
|
|
if hasAudio && acMsg.Role == "user" {
|
|
filepath := tmpAudioContent.Audio
|
|
|
|
ossURL, hasUploadOss, err := checkIfNeedUploadFile(ctx, filepath, payload.Model, q.token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if hasUploadOss {
|
|
payload.HasUploadOss = true
|
|
}
|
|
acMsg.Content.SetAudio(ossURL)
|
|
}
|
|
}
|
|
|
|
return genericCompletion(ctx, payload, q.httpCli, qwen.URLQwenVL(), q.token)
|
|
}
|
|
|
|
func checkIfNeedUploadFile(ctx context.Context, filepath string, model, token string) (string, bool, error) {
|
|
var err error
|
|
var ossURL string
|
|
var hasUploadOss bool
|
|
switch {
|
|
case strings.Contains(filepath, "dashscope.oss"):
|
|
// 使用了官方案例中的格式(https://dashscope.oss...).
|
|
ossURL = filepath
|
|
case strings.HasPrefix(filepath, "oss://"):
|
|
// 已经在 oss 中的不必上传.
|
|
ossURL = filepath
|
|
case strings.HasPrefix(filepath, "file://"):
|
|
// 本地文件.
|
|
filepath = strings.TrimPrefix(filepath, "file://")
|
|
ossURL, err = qwen.UploadLocalFile(ctx, filepath, model, token)
|
|
hasUploadOss = true
|
|
case strings.HasPrefix(filepath, "https://") || strings.HasPrefix(filepath, "http://"):
|
|
// 文件的 URL 链接.
|
|
ossURL, err = qwen.UploadFileFromURL(ctx, filepath, model, token)
|
|
hasUploadOss = true
|
|
}
|
|
|
|
return ossURL, hasUploadOss, err
|
|
}
|
|
|
|
//nolint:lll
|
|
func genericCompletion[T qwen.IQwenContent](ctx context.Context, payload *qwen.Request[T], httpcli httpclient.IHttpClient, url, token string) (*qwen.OutputResponse[T], error) {
|
|
if payload.Model == "" {
|
|
return nil, ErrModelNotSet
|
|
}
|
|
|
|
// use streaming if streaming func is set
|
|
if payload.StreamingFn != nil {
|
|
payload.Parameters.SetIncrementalOutput(true)
|
|
return qwen.SendMessageStream(ctx, payload, httpcli, url, token)
|
|
}
|
|
|
|
return qwen.SendMessage(ctx, payload, httpcli, url, token)
|
|
}
|
|
|
|
// TODO: intergrate wanx.Request into qwen.IQwenContent(or should rename to ITongyiContent)
|
|
//
|
|
//nolint:lll
|
|
func (q *TongyiClient) CreateImageGeneration(ctx context.Context, payload *wanx.ImageSynthesisRequest) ([]*wanx.ImgBlob, error) {
|
|
if payload.Model == "" {
|
|
if q.Model == "" {
|
|
return nil, ErrModelNotSet
|
|
}
|
|
payload.Model = q.Model
|
|
}
|
|
return wanx.CreateImageGeneration(ctx, payload, q.httpCli, q.token)
|
|
}
|
|
|
|
/*
|
|
func (q *TongyiClient) CreateVoiceFileToTextGeneration(ctx context.Context, request *paraformer.Request) (any, error) {
|
|
if request.Payload.Model == "" {
|
|
if q.Model == "" {
|
|
return nil, ErrModelNotSet
|
|
}
|
|
request.Payload.Model = q.Model
|
|
}
|
|
|
|
return
|
|
panic("not implemented")
|
|
}
|
|
*/
|
|
|
|
func (q *TongyiClient) CreateSpeechToTextGeneration(ctx context.Context, request *paraformer.Request, reader *bufio.Reader) error {
|
|
if request.Payload.Model == "" {
|
|
if q.Model == "" {
|
|
return ErrModelNotSet
|
|
}
|
|
request.Payload.Model = q.Model
|
|
}
|
|
|
|
wsCli, err := paraformer.ConnRecognitionClient(request, q.token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// handle response by stream callback
|
|
go paraformer.HandleRecognitionResult(ctx, wsCli, request.StreamingFn)
|
|
|
|
for {
|
|
// this buf can not be reused,
|
|
// otherwise the data will be overwritten, voice became disorder.
|
|
buf := make([]byte, 1024)
|
|
n, errRead := reader.Read(buf)
|
|
if n == 0 {
|
|
break
|
|
}
|
|
if errRead != nil {
|
|
log.Printf("read line error: %v\n", errRead)
|
|
err = errRead
|
|
return err
|
|
}
|
|
|
|
paraformer.SendRadioData(wsCli, buf)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (q *TongyiClient) CreateEmbedding(ctx context.Context, r *embedding.Request) ([][]float32, error) {
|
|
resp, err := embedding.CreateEmbedding(ctx, r, q.httpCli, q.token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(resp.Output.Embeddings) == 0 {
|
|
return nil, ErrEmptyResponse
|
|
}
|
|
|
|
embeddings := make([][]float32, 0)
|
|
for i := 0; i < len(resp.Output.Embeddings); i++ {
|
|
embeddings = append(embeddings, resp.Output.Embeddings[i].Embedding)
|
|
}
|
|
return embeddings, nil
|
|
}
|
|
|
|
func paylosdPreCheck[T qwen.IQwenContent](q *TongyiClient, payload *qwen.Request[T]) *qwen.Request[T] {
|
|
if payload.Model == "" {
|
|
payload.Model = q.Model
|
|
}
|
|
|
|
if payload.Parameters == nil {
|
|
payload.Parameters = qwen.DefaultParameters()
|
|
}
|
|
|
|
return payload
|
|
}
|