ChatModel - Qwen

Qwen 模型

一个针对 Eino 的 Qwen 模型实现,实现了 ToolCallingChatModel 接口。这使得能够与 Eino 的 LLM 功能无缝集成,以增强自然语言处理和生成能力。

特性

  • 实现了 github.com/cloudwego/eino/components/model.Model
  • 轻松与 Eino 的模型系统集成
  • 可配置的模型参数
  • 支持聊天补全
  • 支持流式响应
  • 自定义响应解析支持
  • 灵活的模型配置

安装

go get github.com/cloudwego/eino-ext/components/model/qwen@latest

快速开始

以下是如何使用 Qwen 模型的快速示例:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    
    "github.com/cloudwego/eino-ext/components/model/qwen"
    "github.com/cloudwego/eino/schema"
)

func main() {
        ctx := context.Background()
        // get api key: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key?spm=a2c4g.11186623.help-menu-2400256.d_3_0.1ebc47bb0ClCgF
        apiKey := os.Getenv("DASHSCOPE_API_KEY")
        modelName := os.Getenv("MODEL_NAME")
        chatModel, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
                BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
                APIKey:      apiKey,
                Timeout:     0,
                Model:       modelName,
                MaxTokens:   of(2048),
                Temperature: of(float32(0.7)),
                TopP:        of(float32(0.7)),
        })

        if err != nil {
                log.Fatalf("NewChatModel of qwen failed, err=%v", err)
        }

        resp, err := chatModel.Generate(ctx, []*schema.Message{
                schema.UserMessage("as a machine, how do you answer user's question?"),
        })
        if err != nil {
                log.Fatalf("Generate of qwen failed, err=%v", err)
        }

        fmt.Printf("output: \n%v", resp)

}

func of[T any](t T) *T {
        return &t
}

配置

可以使用 qwen.ChatModelConfig 结构体配置模型:

type ChatModelConfig struct {

// APIKey is your authentication key
// Use OpenAI API key or Azure API key depending on the service
// Required
APIKey string `json:"api_key"`

// Timeout specifies the maximum duration to wait for API responses
// If HTTPClient is set, Timeout will not be used.
// Optional. Default: no timeout
Timeout time.Duration `json:"timeout"`

// HTTPClient specifies the client to send HTTP requests.
// If HTTPClient is set, Timeout will not be used.
// Optional. Default &http.Client{Timeout: Timeout}
HTTPClient *http.Client `json:"http_client"`

// BaseURL specifies the QWen endpoint URL
// Required. Example: https://dashscope.aliyuncs.com/compatible-mode/v1
BaseURL string `json:"base_url"`

// The following fields correspond to OpenAI's chat completion API parameters
// Ref: https://platform.openai.com/docs/api-reference/chat/create

// Model specifies the ID of the model to use
// Required
Model string `json:"model"`

// MaxTokens limits the maximum number of tokens that can be generated in the chat completion
// Optional. Default: model's maximum
MaxTokens *int `json:"max_tokens,omitempty"`

// Temperature specifies what sampling temperature to use
// Generally recommend altering this or TopP but not both.
// Range: 0.0 to 2.0. Higher values make output more random
// Optional. Default: 1.0
Temperature *float32 `json:"temperature,omitempty"`

// TopP controls diversity via nucleus sampling
// Generally recommend altering this or Temperature but not both.
// Range: 0.0 to 1.0. Lower values make output more focused
// Optional. Default: 1.0
TopP *float32 `json:"top_p,omitempty"`

// Stop sequences where the API will stop generating further tokens
// Optional. Example: []string{"\n", "User:"}
Stop []string `json:"stop,omitempty"`

// PresencePenalty prevents repetition by penalizing tokens based on presence
// Range: -2.0 to 2.0. Positive values increase likelihood of new topics
// Optional. Default: 0
PresencePenalty *float32 `json:"presence_penalty,omitempty"`

// ResponseFormat specifies the format of the model's response
// Optional. Use for structured outputs
ResponseFormat *openai.ChatCompletionResponseFormat `json:"response_format,omitempty"`

// Seed enables deterministic sampling for consistent outputs
// Optional. Set for reproducible results
Seed *int `json:"seed,omitempty"`

// FrequencyPenalty prevents repetition by penalizing tokens based on frequency
// Range: -2.0 to 2.0. Positive values decrease likelihood of repetition
// Optional. Default: 0
FrequencyPenalty *float32 `json:"frequency_penalty,omitempty"`

// LogitBias modifies likelihood of specific tokens appearing in completion
// Optional. Map token IDs to bias values from -100 to 100
LogitBias map[string]int `json:"logit_bias,omitempty"`

// User unique identifier representing end-user
// Optional. Helps OpenAI monitor and detect abuse
User *string `json:"user,omitempty"`

// EnableThinking enables thinking mode
// https://help.aliyun.com/zh/model-studio/deep-thinking
// Optional. Default: base on the Model
EnableThinking *bool `json:"enable_thinking,omitempty"`
}

示例

文本生成

package main

import (
        "context"
        "fmt"
        "log"
        "os"

        "github.com/cloudwego/eino-ext/components/model/qwen"
        "github.com/cloudwego/eino/schema"
)

func main() {
        ctx := context.Background()
        // get api key: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key?spm=a2c4g.11186623.help-menu-2400256.d_3_0.1ebc47bb0ClCgF
        apiKey := os.Getenv("DASHSCOPE_API_KEY")
        modelName := os.Getenv("MODEL_NAME")
        chatModel, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
                BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
                APIKey:      apiKey,
                Timeout:     0,
                Model:       modelName,
                MaxTokens:   of(2048),
                Temperature: of(float32(0.7)),
                TopP:        of(float32(0.7)),
        })

        if err != nil {
                log.Fatalf("NewChatModel of qwen failed, err=%v", err)
        }

        resp, err := chatModel.Generate(ctx, []*schema.Message{
                schema.UserMessage("as a machine, how do you answer user's question?"),
        })
        if err != nil {
                log.Fatalf("Generate of qwen failed, err=%v", err)
        }

        fmt.Printf("output: \n%v", resp)

}

func of[T any](t T) *T {
        return &t
}

多模态理解(图片理解)

package main

import (
        "context"
        "encoding/base64"
        "fmt"
        "log"
        "os"

        "github.com/cloudwego/eino-ext/components/model/qwen"
        "github.com/cloudwego/eino/schema"
)

func main() {
        ctx := context.Background()
        // get api key: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key?spm=a2c4g.11186623.help-menu-2400256.d_3_0.1ebc47bb0ClCgF
        apiKey := os.Getenv("DASHSCOPE_API_KEY")
        modelName := os.Getenv("MODEL_NAME")
        chatModel, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
                BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
                APIKey:      apiKey,
                Timeout:     0,
                Model:       modelName,
                MaxTokens:   of(2048),
                Temperature: of(float32(0.7)),
                TopP:        of(float32(0.7)),
        })

        if err != nil {
                log.Fatalf("NewChatModel of qwen failed, err=%v", err)
        }

        image, err := os.ReadFile("./examples/generate_with_image/test.jpg")
        if err != nil {
                log.Fatalf("os.ReadFile failed, err=%v\n", err)
        }

        resp, err := chatModel.Generate(ctx, []*schema.Message{
                {
                        Role: schema.User,
                        UserInputMultiContent: []schema.MessageInputPart{
                                {
                                        Type: schema.ChatMessagePartTypeText,
                                        Text: "What do you see in this image?",
                                },
                                {
                                        Type: schema.ChatMessagePartTypeImageURL,
                                        Image: &schema.MessageInputImage{
                                                MessagePartCommon: schema.MessagePartCommon{
                                                        Base64Data: of(base64.StdEncoding.EncodeToString(image)),
                                                        MIMEType:   "image/jpeg",
                                                },
                                                Detail: schema.ImageURLDetailAuto,
                                        },
                                },
                        },
                },
        })
        if err != nil {
                log.Printf("Generate error: %v", err)
                return
        }
        fmt.Printf("Assistant: %s\n", resp.Content)

}

func of[T any](t T) *T {
        return &t
}

流式生成

package main

import (
        "context"
        "fmt"
        "io"
        "log"
        "os"

        "github.com/cloudwego/eino-ext/components/model/qwen"
        "github.com/cloudwego/eino/schema"
)

func main() {
        ctx := context.Background()
        // get api key: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key?spm=a2c4g.11186623.help-menu-2400256.d_3_0.1ebc47bb0ClCgF
        apiKey := os.Getenv("DASHSCOPE_API_KEY")
        modelName := os.Getenv("MODEL_NAME")
        cm, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
                BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
                APIKey:      apiKey,
                Timeout:     0,
                Model:       modelName,
                MaxTokens:   of(2048),
                Temperature: of(float32(0.7)),
                TopP:        of(float32(0.7)),
        })
        if err != nil {
                log.Fatalf("NewChatModel of qwen failed, err=%v", err)
        }

        sr, err := cm.Stream(ctx, []*schema.Message{
                schema.UserMessage("你好"),
        })
        if err != nil {
                log.Fatalf("Stream of qwen failed, err=%v", err)
        }

        var msgs []*schema.Message
        for {
                msg, err := sr.Recv()
                if err != nil {
                        if err == io.EOF {
                                break
                        }

                        log.Fatalf("Stream of qwen failed, err=%v", err)
                }

                fmt.Println(msg)
                // assistant: 你好
                // finish_reason:
                // : !
                // finish_reason:
                // : 有什么
                // finish_reason:
                // : 可以帮助
                // finish_reason:
                // : 你的吗?
                // finish_reason:
                // :
                // finish_reason: stop
                // usage: &{9 7 16}
                msgs = append(msgs, msg)
        }

        msg, err := schema.ConcatMessages(msgs)
        if err != nil {
                log.Fatalf("ConcatMessages failed, err=%v", err)
        }

        fmt.Println(msg)
        // assistant: 你好!有什么可以帮助你的吗?
        // finish_reason: stop
        // usage: &{9 7 16}
}

func of[T any](t T) *T {
        return &t
}

工具调用

package main

import (
        "context"
        "encoding/json"
        "fmt"
        "log"
        "os"

        "github.com/cloudwego/eino-ext/components/model/qwen"
        "github.com/cloudwego/eino/schema"
)

func main() {
        ctx := context.Background()
        // get api key: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key?spm=a2c4g.11186623.help-menu-2400256.d_3_0.1ebc47bb0ClCgF
        apiKey := os.Getenv("DASHSCOPE_API_KEY")
        modelName := os.Getenv("MODEL_NAME")
        cm, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
                BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
                APIKey:      apiKey,
                Timeout:     0,
                Model:       modelName,
                MaxTokens:   of(2048),
                Temperature: of(float32(0.7)),
                TopP:        of(float32(0.7)),
        })
        if err != nil {
                log.Fatalf("NewChatModel of qwen failed, err=%v", err)
        }

        err = cm.BindTools([]*schema.ToolInfo{
                {
                        Name: "user_company",
                        Desc: "根据用户的姓名和邮箱,查询用户的公司和职位信息",
                        ParamsOneOf: schema.NewParamsOneOfByParams(
                                map[string]*schema.ParameterInfo{
                                        "name": {
                                                Type: "string",
                                                Desc: "用户的姓名",
                                        },
                                        "email": {
                                                Type: "string",
                                                Desc: "用户的邮箱",
                                        },
                                }),
                },
                {
                        Name: "user_salary",
                        Desc: "根据用户的姓名和邮箱,查询用户的薪酬信息",
                        ParamsOneOf: schema.NewParamsOneOfByParams(
                                map[string]*schema.ParameterInfo{
                                        "name": {
                                                Type: "string",
                                                Desc: "用户的姓名",
                                        },
                                        "email": {
                                                Type: "string",
                                                Desc: "用户的邮箱",
                                        },
                                }),
                },
        })
        if err != nil {
                log.Fatalf("BindTools of qwen failed, err=%v", err)
        }

        resp, err := cm.Generate(ctx, []*schema.Message{
                {
                        Role:    schema.System,
                        Content: "你是一名房产经纪人,结合用户的薪酬和工作,使用 user_company、user_salary 两个 API,为其提供相关的房产信息。邮箱是必须的",
                },
                {
                        Role:    schema.User,
                        Content: "我的姓名是 zhangsan,我的邮箱是 zhangsan@bytedance.com,请帮我推荐一些适合我的房子。",
                },
        })

        if err != nil {
                log.Fatalf("Generate of qwen failed, err=%v", err)
        }

        fmt.Println(resp)
        // assistant:
        // tool_calls: [{0x14000275930 call_1e25169e05fc4596a55afb function {user_company {"email": "zhangsan@bytedance.com", "name": "zhangsan"}} map[]}]
        // finish_reason: tool_calls
        // usage: &{316 32 348}

        // ==========================
        // using stream
        fmt.Printf("\n\n======== Stream ========\n")
        sr, err := cm.Stream(ctx, []*schema.Message{
                {
                        Role:    schema.System,
                        Content: "你是一名房产经纪人,结合用户的薪酬和工作,使用 user_company、user_salary 两个 API,为其提供相关的房产信息。邮箱是必须的",
                },
                {
                        Role:    schema.User,
                        Content: "我的姓名是 lisi,我的邮箱是 lisi@bytedance.com,请帮我推荐一些适合我的房子。",
                },
        })
        if err != nil {
                log.Fatalf("Stream of qwen failed, err=%v", err)
        }

        msgs := make([]*schema.Message, 0)
        for {
                msg, err := sr.Recv()
                if err != nil {
                        break
                }
                jsonMsg, err := json.Marshal(msg)
                if err != nil {
                        log.Fatalf("json.Marshal failed, err=%v", err)
                }
                fmt.Printf("%s\n", jsonMsg)
                msgs = append(msgs, msg)
        }

        msg, err := schema.ConcatMessages(msgs)
        if err != nil {
                log.Fatalf("ConcatMessages failed, err=%v", err)
        }
        jsonMsg, err := json.Marshal(msg)
        if err != nil {
                log.Fatalf("json.Marshal failed, err=%v", err)
        }
        fmt.Printf("final: %s\n", jsonMsg)
}

func of[T any](t T) *T {
        return &t
}

使用方式

组件初始化

Qwen 模型通过 NewChatModel 函数进行初始化,主要配置参数如下:

import "github.com/cloudwego/eino-ext/components/model/qwen"

apiKey := os.Getenv("DASHSCOPE_API_KEY")
model, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
    BaseURL:    "https://dashscope.aliyuncs.com/compatible-mode/v1",      // URL
    // 基础配置
    APIKey:  "api-key",         // API 密钥
    Timeout: 30 * time.Second,   // 超时时间

    // 模型参数
    Model:            "qwen-ma",   // 模型名称
    MaxTokens:        &maxTokens,// 最大生成长度
    Temperature:      &temp,     // 温度
    TopP:             &topP,     // Top-P 采样
    Stop:             []string{},// 停止词
    PresencePenalty:  &pp,      // 存在惩罚
    FrequencyPenalty: &fp,      // 频率惩罚

    // 高级参数
    ResponseFormat: &format,                  // 响应格式
    Seed:           &seed,                    // 随机种子
    LogitBias:      map[string]int{},         // Token 偏置
    User:           &user,                    // 用户标识
    EnableThinking: of(false),                 // 是否开启思考模式
    Modalities:     make([]qwen.Modality, 0), // 模型回复模态类型: ["text","audio"] 默认 text
    
    Audio: &qwen.Audio{ // 音频输出参数,当模态存在audio时,此字段必填
        Format: qwen._AudioFormatWav_,
        Voice:  qwen._AudioVoiceChelsie_,
    },
    
})

生成对话

对话生成支持普通模式和流式模式:

// invoke模式
response, err := model.Generate(ctx, messages)
    
// 流式模式
stream, err := model.Stream(ctx, messages)

消息格式示例:

import (
    "os"
   "encoding/base64"

    "github.com/cloudwego/eino/schema"
)

// base64 格式的图片数据
image, err := os.ReadFile("./examples/image/eino.png")
    if err != nil {
        log.Fatalf("os.ReadFile failed, err=%v\n", err)
    }

imageStr := base64.StdEncoding.EncodeToString(image)

messages := []*schema.Message{
    // 系统消息
    schema.SystemMessage("你是一个助手"),
    
    // 多模态消息(包含图片)
    {
        Role: schema.User,
        UserInputMultiContent: []schema.MessageInputPart{
            {
                Type: schema.ChatMessagePartTypeImageURL,
                Image: &schema.MessageInputImage{
                    MessagePartCommon: schema.MessagePartCommon{
                        Base64Data: &imageStr,
                        MIMEType:   "image/png",    // required when use Base64Data
                    },
                Detail: schema.ImageURLDetailAuto,
                },
            },
            {
                Type: schema.ChatMessagePartTypeText,
                Text: "这张图片是什么?",
            },
        },
    },
}

工具调用

支持绑定工具和强制工具调用:

import "github.com/cloudwego/eino/schema"

// 定义工具
tools := []*schema.ToolInfo{
    {
       Name: "search",
       Desc: "搜索信息",
       ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
          "query": {
             Type:     schema.String,
             Desc:     "搜索关键词",
             Required: true,
          },
       }),
    },
}
// 绑定可选工具
err := model.BindTools(tools)

// 绑定强制工具
err := model.BindForcedTools(tools)

工具相关信息,可以参考 Eino: ToolsNode 使用说明

完整使用示例

直接对话

package main

import (
    "context"
    "time"
    
    "github.com/cloudwego/eino-ext/components/model/qwen"
    "github.com/cloudwego/eino/schema"
)

func main() {
    ctx := context.Background()
    
    // 初始化模型
    model, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
        APIKey:  "your-api-key", // required
        Timeout: 30 * time.Second,
        Model:   "qwen-max", // required
        
        // 如果模型支持语音生成,并且有需求生成语音时,需要配置如下配置
        // Modalities: []qwen.Modality{qwen.AudioModality, qwen.TextModality},
        //Audio: &qwen.Audio{
        //        Format: qwen.AudioFormatMp3,
        //        Voice:  qwen.AudioVoiceAlloy,
        //},
},
        
    })
    if err != nil {
        panic(err)
    }
    
    // base64 格式的图片数据
    image, err := os.ReadFile("./examples/image/cat.png")
        if err != nil {
            log.Fatalf("os.ReadFile failed, err=%v\n", err)
        }

    imageStr := base64.StdEncoding.EncodeToString(image)

    // 请求消息
    messages := []*schema.Message{
        schema.SystemMessage("你是一个图片生成助手,可以仿照用户给定的图片生成一个风格近似的图片"),
        {
            Role: schema.User,
            UserInputMultiContent: []schema.MessageInputPart{
                {
                    Type: schema.ChatMessagePartTypeImage,
                    Image: &schema.MessageInputImage{
                    MessagePartCommon: schema.MessagePartCommon{
                        Base64Data: &imageStr,
                        MIMEType:   "image/png",    // required when use Base64Data
                    },
                    Detail: schema.ImageURLDetailAuto,
                },
                {
                    Type: schema.ChatMessagePartTypeText,
                    Text: "Generate an image of a cat",
                },
            },
        },
    }
    
    // 生成回复
    response, err := model.Generate(ctx, messages)
    if err != nil {
        panic(err)
    }
          
    // 处理回复
    /*
        生成的多模态内容存储在 response.AssistantGentMultiContent 字段中
        本例中最终生成的 message 形如:
        AssistantMessage = schema.Message{
                        Role: schema.Assistant,
                         AssistantGenMultiContent : []schema.MessageOutputPart{
                             {Type: schema.ChatMessagePartTypeImageURL,
                              Image: &schema.MessageOutputImage{
                                  MessagePartCommon: schema.MessagePartCommon{
                                      Base64Data: &DataStr, 
                                      MIMEType: "image/png",
                                      },
                                  },
                              },
                          },
                      }
    */
    
    fmt.Printf("Assistant: %s\n", resp)
}

流式对话

package main

import (
    "context"
    "time"
    
    "github.com/cloudwego/eino-ext/components/model/qwen"
    "github.com/cloudwego/eino/schema"
)

func main() {
    ctx := context.Background()
    
    // 初始化模型
    model, err := qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
        APIKey:  "your-api-key",
        Timeout: 30 * time.Second,
        Model:   "qwen-max",
    })
    if err != nil {
        panic(err)
    }
    
    // 准备消息
    messages := []*schema.Message{
        schema.SystemMessage("你是一个助手"),
        schema.UserMessage("写一个故事"),
    }
    
    // 获取流式回复
    reader, err := model.Stream(ctx, messages)
    if err != nil {
        panic(err)
    }
    defer reader.Close() // 注意要关闭
    
    // 处理流式内容
    for {
        chunk, err := reader.Recv()
        if err != nil {
            break
        }
        print(chunk.Content)
    }
}

更多示例

相关文档


最后修改 December 12, 2025 : chore: update websocket docs (#1479) (967538e)