You need to enable JavaScript to run this app.
导航
调用 Doubao-pro-vision API
最近更新时间:2024.11.29 11:21:48首次发布时间:2024.10.24 14:27:13

注意

视觉理解模型使用教程与API说明已上线,请移步:

API SDK

提供统一 SDK 的接入形式(需要用 API key 进行鉴权,获取方式请参考 获取 API key),SDK 中可以通过设置环境变量ARK_API_KEY或者Ark()初始化时引入 API KEY。

现阶段 SDK V3 仅提供 Python 版本,其他语言将陆续发布,敬请期待。

Python

注意

目前 SDK 只支持python版本3.7及以上。

'''
Usage:
pip install volcengine-python-sdk
'''

from volcenginesdkarkruntime import Ark

# gets API Key from environment variable ARK_API_KEY
client = Ark()

if __name__ == "__main__":
    # Non-streaming:
    print("----- standard request -----")
    completion = client.chat.completions.create(
        model="${YOUR_ENDPOINT_ID}",
        messages=[
            {
                "role": "user",
                "content": "Say this is a test",
            },
        ],
        extra_headers={"x-ark-beta-vision": "true"}
    )
    print(completion.choices[0].message.content)

    # Streaming:
    print("----- streaming request -----")
    stream = client.chat.completions.create(
        model="${YOUR_ENDPOINT_ID}",
        messages=[
            {
                "role": "user",
                "content": "How do I output all files in a directory using Python?",
            },
        ],
        stream=True,
        extra_headers={"x-ark-beta-vision": "true"}
    )
    for chunk in stream:
        if not chunk.choices:
            continue

        print(chunk.choices[0].delta.content, end="")
    print()

API Specification

Chat

主要参考 OpenAIHuggingFace ,Parameters 记录可选控制参数,具体哪些参数可用依赖模型服务。

Input

字段

子字段

类型

必填

描述

默认值

model

-

string

以 endpoint_id 索引对应的模型接入点。
创建 endpoint 教程,可以参见创建推理接入点--火山方舟大模型服务平台-火山引擎

-

messages

-

list

本次对话的消息列表,包含用户输入的最后一条消息。

-

role

string

发出该消息的对话参与者的角色,可选 systemuserassistant

-

content (role=user)

string/array

消息的内容。多模态模型的 content 可以为 list,list 的元素可以为 textimage_url。其中,每个轮次的image_url可以有多个,多轮累计不超过 10 个,其中 image_url 是图片链接,需要被大模型读取的图片。image 格式仅支持jpeg(jpg)、png 格式,大小不超过 10M,示例如下。
【单轮单图】

[
  {
    "type": "text",
    "text": "What'\''s in this image?"
  },
  {
    "type": "image_url",
    "image_url": {
      "url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"
    }
  }
]

【单轮多图】

[
  {
    "type": "text",
    "text": "What'\''s in this image?"
  },
  {
    "type": "image_url",
    "image_url": {
      "url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"
    }
  },
  {
    "type": "image_url",
    "image_url": {
      "url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"
    }
  }
]

-

content (role=assistant)

string

消息的内容。

-

content (role=system)

string

消息的内容。

-

frequency_penalty

-

number

-2.0 到 2.0 之间的数字。如果为正,会根据新 token 在文本中的出现频率对其进行惩罚,从而降低模型重复相同内容的可能性。

1

logit_bias

-

map

修改指定 token 在模型输出内容中出现的概率。
接受一个 map,该对象将 token(token id 使用 tokenization 接口获取)映射到从 -100 到 100 的关联偏差值。每个模型的效果有所不同,但 -1 和 1 之间的值会减少或增加选择的可能性;-100 或 100 应该导致禁止或排他选择相关的 token。

null

logprobs

-

boolean

是否返回输出 tokens 的 logprobs。如果为 true,则返回 message (content) 中每个输出 token 的 logprobs。

false

top_logprobs

-

integer

0 到 20 之间的整数,指定每个 token 位置最有可能返回的 token 数量,每个 token 都有关联的对数概率。 如果使用此参数,则 logprobs 必须设置为 true

null

max_tokens

-

integer

模型最大输出 token 数。
输入 token 和输出 token 的总长度还受模型的上下文长度限制。

null

stop

-

string/array

用于指定模型在生成响应时应停止的词语。当模型生成的响应中包含这些词汇时,生成过程将停止。

null

stream

-

boolean

是否流式返回。如果为 true,则按 SSE 协议返回数据。

false

stream_options

-

object

stream=true 时可以设置这个参数。

null

include_usage

bool

如果设置,则在 data: [DONE]消息之前会返回一个额外的块。此块上的 usage 字段显示了整个请求的 token 用量,其 choices 字段是一个空数组。所有其他块也将包含 usage 字段,但值为 null。

false

temperature

-

number

采样温度在 0 到 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使输出更加集中和确定。
通常建议修改 temperature 或 top_p,但不建议两者都修改。

1

top_p

-

number

temperature 抽样的另一种选择,称为核抽样,其中模型考虑具有 top_p 概率质量的 token。所以 0.1 意味着只考虑包含前 10% 概率质量的标记。
一般建议修改 top_p 或 temperature,但不建议两者都修改

1

Output

非流式调用

字段

子字段

类型

描述

id

-

string

一次 chat completion 接口调用的唯一标识。

choices

-

array

本次 chat 结果列表。长度固定为 1。

index

integer

该元素在 choices 列表的索引。

message

object

模型输出的消息内容,示例如下:

{
  "role": "assistant",
  "content": "\n\nHello there, how may I assist you today?",
}

finish_reason

string

模型生成结束原因,stop表示正常生成结束,length 表示已经到了生成的最大 token 数量,content_filter 表示命中审核提前终止。

logprobs

object

该输出结果的概率信息,其只有一个 content 字段,类型为 array,表示 message 列表中每个元素 content token 的概率信息,content 元素子字段说明如下:

  1. token [string]: 对应 token;
  2. logprob [number]:token 的概率;
  3. bytes [array]:表示 token 的 UTF-8 字节表示的整数列表。在字符由多个 token 表示,并且它们的字节表示必须组合以生成正确的文本表示的情况下(表情符号或特殊字符)非常有用。如果 token 没有 byte 表示,则可以为空。
  4. top_logprobs [array]:最可能的 token 列表及其在此 token 位置的对数概率:
    1. token [string]: 对应 token;
    2. logprob [number]:token 的概率;
    3. bytes [array]:表示 token 的 UTF-8 字节表示的整数列表。在字符由多个 token 表示,并且它们的字节表示必须组合以生成正确的文本表示的情况下(表情符号或特殊字符)非常有用。如果 token 没有 byte 表示,则可以为空。

created

-

integer

本次对话生成时间戳(秒)。

model

-

string

实际使用的模型名称和版本。

object

-

string

固定为 chat.completion。

usage

-

object

本次请求的 tokens 用量。

prompt_tokens

integer

本次请求中输入的 token 数量。

completion_tokens

integer

模型生成的 token 数量。

total_tokens

integer

总的 token 数量。

流式调用

字段

子字段

类型

描述

id

-

string

一次 chat completion 接口调用的唯一标识,一次流式调用所有的 chunk 有相同的 id。

choices

-

array

结果列表。长度固定为 1。如果设置了stream_options: {"include_usage": true},则最后一个 chunk 的 choices 也为空列表。

index

integer

该元素在 choices 列表的索引。

delta

object

由流式模型响应的模型输出增量,示例如下。

{
  "role": "assistant",
  "content": " there",
}

finish_reason

string

模型生成结束原因,stop表示正常生成结束,length 表示已经到了生成的最大 token 数量,content_filter 表示命中审核提前终止。

logprobs

object

该输出结果的概率信息,其只有一个 content 字段,类型为 array,表示 message 列表中每个元素 content token 的概率信息,content 元素子字段说明如下:

  1. token [string]: 对应 token;
  2. logprob [number]:token 的概率;
  3. bytes [array]:表示 token 的 UTF-8 字节表示的整数列表。在字符由多个 token 表示,并且它们的字节表示必须组合以生成正确的文本表示的情况下(表情符号或特殊字符)非常有用。如果 token 没有 byte 表示,则可以为空。
  4. top_logprobs [array]:最可能的 token 列表及其在此 token 位置的对数概率:
    1. token [string]: 对应 token;
    2. logprob [number]:token 的概率;
    3. bytes [array]:表示 token 的 UTF-8 字节表示的整数列表。在字符由多个 token 表示,并且它们的字节表示必须组合以生成正确的文本表示的情况下(表情符号或特殊字符)非常有用。如果 token 没有 byte 表示,则可以为空。

created

-

integer

本次对话生成时间戳(秒)。

model

-

string

实际使用的模型名称和版本。

object

-

string

固定为 chat.completion.chunk。

usage

-

object

本次请求的 tokens 用量。
一个可选字段,仅当在请求中设置stream_options: {"include_usage": true}时才会出现。如果设置了它,除了最后一个 chunk 包含整个请求的 token 使用量外,其它 chunk 的 usage 都是 null。

prompt_tokens

integer

本次请求中输入的 token 数量。

completion_tokens

integer

模型生成的 token 数量。

total_tokens

integer

总的 token 数量。

在 stream 模式下,基于 SSE (Server-Sent Events) 协议返回生成内容,每次返回结果为生成的部分内容片段:

  • 内容片段按照生成的先后顺序返回,完整的结果需要调用者拼接才能得到;
  • 如果流式请求开始时就出现错误(如参数错误),HTTP 返回非 200,方法调用也会直接返回错误;
  • 如果流式过程中出现错误,HTTP 依然会返回 200, 错误信息会在一个片段返回。

示例代码

下面代码实现了“图片压缩 > 上传 TO S> 调用VLM Chat ”的流程,供参考。
示例图片
Image

Python

  1. 安装依赖
  • requirements.txt
Pillow
volcengine-python-sdk[ark]
tos
pip install -r requirements.txt
  1. 运行
import os
import tos
from PIL import Image
from tos import HttpMethodType
from volcenginesdkarkruntime import Ark

# 从环境变量获取 AK/SK/APIKEY信息
ak = os.getenv('VOLC_ACCESSKEY')
sk = os.getenv('VOLC_SECRETKEY')
api_key = os.getenv('ARK_API_KEY')

# 压缩前图片
original_file = "original_image.jpeg"
# 压缩后图片存放路径
compressed_file = "comressed_image.jpeg"
# 压缩的目标图片大小,300KB
target_size = 300 * 1024

# endpoint 和 region 填写Bucket 所在区域对应的Endpoint。
# 以华北2(北京)为例,region 填写 cn-beijing。
# 公网域名endpoint 填写 tos-cn-beijing.volces.com
endpoint, region = "tos-cn-beijing.volces.com", "cn-beijing"
# 对象桶名称
bucket_name = "demo-bucket-test"
# 对象名称,例如 images 下的 compressed_image.jpeg 文件,则填写为 images/compressed_image.jpeg
object_key = "images/compressed_image.jpeg"


def compress_image(input_path, output_path):
    img = Image.open(input_path)
    current_size = os.path.getsize(input_path)

    # 粗略的估计压缩质量,也可以从常量开始,逐步减小压缩质量,直到文件大小小于目标大小
    image_quality = int(float(target_size / current_size) * 100)
    img.save(output_path, optimize=True, quality=int(float(target_size / current_size) * 100))

    # 如果压缩后文件大小仍然大于目标大小,则继续压缩
    # 压缩质量递减,直到文件大小小于目标大小
    while os.path.getsize(output_path) > target_size:
        img = Image.open(output_path)
        image_quality -= 10
        if image_quality <= 0:
            break
        img.save(output_path, optimize=True, quality=image_quality)
    return image_quality


def upload_tos(filename, tos_endpoint, tos_region, tos_bucket_name, tos_object_key):
    # 创建 TosClientV2 对象,对桶和对象的操作都通过 TosClientV2 实现
    tos_client, inner_tos_client = tos.TosClientV2(ak, sk, tos_endpoint, tos_region), tos.TosClientV2(ak, sk,
                                                                                                      tos_endpoint,
                                                                                                      tos_region)
    try:
        # 将本地文件上传到目标桶中, filename为本地压缩后图片的完整路径
        tos_client.put_object_from_file(tos_bucket_name, tos_object_key, filename)
        # 获取上传后预签名的 url
        return inner_tos_client.pre_signed_url(HttpMethodType.Http_Method_Get, tos_bucket_name, tos_object_key)
    except Exception as e:
        if isinstance(e, tos.exceptions.TosClientError):
            # 操作失败,捕获客户端异常,一般情况为非法请求参数或网络异常
            print('fail with client error, message:{}, cause: {}'.format(e.message, e.cause))
        elif isinstance(e, tos.exceptions.TosServerError):
            # 操作失败,捕获服务端异常,可从返回信息中获取详细错误信息
            print('fail with server error, code: {}'.format(e.code))
            # request id 可定位具体问题,强烈建议日志中保存
            print('error with request id: {}'.format(e.request_id))
            print('error with message: {}'.format(e.message))
            print('error with http code: {}'.format(e.status_code))
        else:
            print('fail with unknown error: {}'.format(e))
        raise e


if __name__ == "__main__":
    print("----- Compress Image -----")
    quality = compress_image(original_file, compressed_file)
    print("Compressed Image Quality: {}".format(quality))

    print("----- Upload to Tos -----")
    pre_signed_url_output = upload_tos(compressed_file, endpoint, region, bucket_name, object_key)
    print("Pre-signed TOS URL: {}".format(pre_signed_url_output.signed_url))

    print("----- Image Input Chat -----")
    client = Ark(api_key=api_key)

    # Image input:
    response = client.chat.completions.create(
        model="${YOUR_ENDPOINT_ID}",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Which is the most secure payment app according to Americans?"},
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": pre_signed_url_output.signed_url
                        }
                    },
                ],
            }
        ],
        extra_headers={"x-ark-beta-vision": "true"}
    )

    print(response.choices[0])

Golang

package main

import (
    "context"
    "fmt"
    "image"
    "image/jpeg"
    "os"

    "github.com/volcengine/ve-tos-golang-sdk/v2/tos"
    "github.com/volcengine/ve-tos-golang-sdk/v2/tos/enum"
    "github.com/volcengine/volcengine-go-sdk/service/arkruntime"
    "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
)

const (
    targetSize          = int64(300 * 1024)       // 300KB
    originalImageFile   = "original_image.jpeg"   // 压缩前图片路径
    compressedImageFile = "compressed_image.jpeg" // 压缩后图片存放路径

    // Bucket 对应的 Endpoint,以华北2(北京)为例
    // 公网域名地址:https://tos-cn-beijing.volces.com
    endpoint, region = "https://tos-cn-beijing.volces.com", "cn-beijing"
    // 填写 对象存储BucketName
    bucketName = "*** Provide your bucket name ***"
    // 将文件上传到 images 目录下的 compressed_image.jpeg 文件
    objectKey = "images/compressed_image.jpeg"
)

var (
    accessKey = os.Getenv("VOLC_ACCESSKEY")
    secretKey = os.Getenv("VOLC_SECRETKEY")
    apiKey    = os.Getenv("ARK_API_KEY")
)

func compressImage(inputPath, outputPath string, targetSize int64) (int, error) {
    file, err := os.Open(inputPath)
    if err != nil {
       return -1, err
    }
    defer file.Close()

    img, _, err := image.Decode(file)
    if err != nil {
       return -1, err
    }

    outputFile, err := os.Create(outputPath)
    if err != nil {
       return -1, err
    }
    defer outputFile.Close()

    originalImageInfo, err := file.Stat()
    if err != nil {
       return -1, err
    }
    quality := int(float64(targetSize) / float64(originalImageInfo.Size()) * 100)

    for {
       err = jpeg.Encode(outputFile, img, &jpeg.Options{Quality: quality})
       if err != nil {
          return -1, err
       }

       info, err := outputFile.Stat()
       if err != nil {
          return -1, err
       }

       if info.Size() <= targetSize {
          break
       }

       quality -= 10
       if quality <= 0 {
          return -1, fmt.Errorf("无法压缩到目标大小")
       }

       outputFile.Close()
       outputFile, err = os.Create(outputPath)
       if err != nil {
          return -1, err
       }
    }

    return quality, nil
}
func uploadTos(ctx context.Context, endpoint, region, bucketName, objectKey, filePath string) (*tos.PreSignedURLOutput, error) {
    // 初始化客户端
    client, err := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(tos.NewStaticCredentials(accessKey, secretKey)))
    if err != nil {
       return nil, err
    }

    // 将压缩后的图片上传到tos
    output, err := client.PutObjectFromFile(ctx, &tos.PutObjectFromFileInput{
       PutObjectBasicInput: tos.PutObjectBasicInput{
          Bucket: bucketName,
          Key:    objectKey,
       },
       FilePath: filePath,
    })
    if err != nil {
       return nil, err
    }
    fmt.Println("PutObjectV2 Request ID:", output.RequestID)

    // 获取预签名下载链接
    innerClient, err := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(tos.NewStaticCredentials(accessKey, secretKey)))
    if err != nil {
       return nil, err
    }
    return innerClient.PreSignedURL(&tos.PreSignedURLInput{
       HTTPMethod: enum.HttpMethodGet,
       Bucket:     bucketName,
       Key:        objectKey,
    })
}

func main() {
    ctx := context.Background()

    fmt.Println("----- Compress Image -----")
    quality, err := compressImage(originalImageFile, compressedImageFile, targetSize)
    if err != nil {
       panic(err)
    }

    fmt.Println("Compressed Image Quality:", quality)

    fmt.Println("----- Upload Image To TOS -----")
    preSignedUrl, err := uploadTos(ctx, endpoint, region, bucketName, objectKey, compressedImageFile)
    if err != nil {
       return
    }
    fmt.Println("PreSignedUrl:", preSignedUrl.SignedUrl)

    fmt.Println("----- Image Input Chat -----")
    client := arkruntime.NewClientWithApiKey(apiKey)
    req := model.ChatCompletionRequest{
       Model: "${YOUR_ENDPOINT_ID}",
       Messages: []*model.ChatCompletionMessage{
          {
             Role: model.ChatMessageRoleUser,
             Content: &model.ChatCompletionMessageContent{
                ListValue: []*model.ChatCompletionMessageContentPart{
                   {
                      Type: model.ChatCompletionMessageContentPartTypeText,
                      Text: "Which is the most secure payment app according to Americans?",
                   },
                   {
                      Type: model.ChatCompletionMessageContentPartTypeImageURL,
                      ImageURL: &model.ChatMessageImageURL{
                         URL: preSignedUrl.SignedUrl,
                      },
                   },
                },
             },
          },
       },
    }

    resp, err := client.CreateChatCompletion(ctx, req, arkruntime.WithCustomHeaders(map[string]string{
       "x-ark-beta-vision": "true",
    }))
    if err != nil {
       fmt.Printf("standard chat error: %v\n", err)
       return
    }
    fmt.Println(*resp.Choices[0].Message.Content.StringValue)
}

Java

package com.volcengine.ark.runtime;

import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionContentPart;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
import com.volcengine.ark.runtime.service.ArkService;
import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.comm.HttpMethod;
import com.volcengine.tos.model.object.PreSignedURLInput;
import com.volcengine.tos.model.object.PreSignedURLOutput;
import com.volcengine.tos.model.object.PutObjectInput;
import com.volcengine.tos.model.object.PutObjectOutput;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class VLMChatExample {

    private static final String TOS_ENDPOINT = "https://tos-cn-beijing.volces.com";
    private static final String REGION = "cn-beijing";
    private static final String ARK_API_KEY = System.getenv("ARK_API_KEY");
    private static final String VOLC_ACCESSKEY = System.getenv("VOLC_ACCESSKEY");
    private static final String VOLC_SECRETKEY = System.getenv("VOLC_SECRETKEY");
    private static final long TOS_EXPIRE_TIME = 3600;

    public static void compressImage(String inputImagePath, String outputImagePath, long targetSizeInBytes) throws IOException {
        File inputFile = new File(inputImagePath);
        BufferedImage img = ImageIO.read(inputFile);
        float quality = targetSizeInBytes * 1.0f / inputFile.length();
        try (ImageOutputStream ios = ImageIO.createImageOutputStream(new File(outputImagePath))) {
            ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
            ImageWriteParam param = writer.getDefaultWriteParam();
            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param.setCompressionQuality(quality);
            writer.setOutput(ios);
            writer.write(null, new javax.imageio.IIOImage(img, null, null), param);
            writer.dispose();
        }
    }

    public static void uploadImageToTOS(String filePath, String bucketName, String objectKey) throws Throwable {

        TOSV2 tos = new TOSV2ClientBuilder().build(REGION, TOS_ENDPOINT, VOLC_ACCESSKEY, VOLC_SECRETKEY);

        // step 1. 上传本地图片到tos
        File file = new File(filePath);
        try (FileInputStream inputStream = new FileInputStream(file)) {
            PutObjectInput putObjectInput = new PutObjectInput().setBucket(bucketName)
                    .setKey(objectKey).setContent(inputStream).setContentLength(file.length());
            PutObjectOutput output = tos.putObject(putObjectInput);
            System.out.println("putObject succeed, object's etag is " + output.getEtag());
            System.out.println("putObject succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (IOException e) {
            System.out.println("putObject read file failed");
            e.printStackTrace();
            throw e;
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("putObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
            throw e;
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("putObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
            throw e;
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("putObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
            throw t;
        }
    }

    public static String getPreSignedURL(String bucketName, String objectKey) throws Throwable {

        TOSV2 tos = new TOSV2ClientBuilder().build(REGION, TOS_ENDPOINT, VOLC_ACCESSKEY, VOLC_SECRETKEY);

        // 生成预签名链接
        try {
            PreSignedURLInput input = new PreSignedURLInput().setBucket(bucketName).setKey(objectKey)
                    .setHttpMethod(HttpMethod.GET).setExpires(TOS_EXPIRE_TIME);
            PreSignedURLOutput output = tos.preSignedURL(input);
            System.out.println("preSignedURL succeed, the signed url is " + output.getSignedUrl());
            System.out.println("preSignedURL succeed, the signed header is " + output.getSignedHeader());
            return output.getSignedUrl();
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("preSignedURL failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
            throw e;
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("preSignedURL failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
            throw t;
        }
    }

    public static void main(String[] args) throws Throwable {
        String filePath = "/your/path/to/your/image.jpeg";
        String compressedPath = "/your/path/to/compress/image.jpeg";
        String tosBucket = "{your bucket name}";
        String objectKey = "{your object key}";
        // 1. 压缩图片
        try {
            compressImage(filePath, compressedPath, 300 * 1024);
        } catch (IOException e) {
            System.out.println("compressImage failed");
            e.printStackTrace();
            throw e;
        }
        // 2. 上传TOS
        try {
            uploadImageToTOS(compressedPath, tosBucket, objectKey);
        } catch (Throwable t) {
            System.out.println("uploadImageToTOS failed");
            t.printStackTrace();
            throw t;
        }
        // 3. 生成预签名链接
        String preSignedURL = "";
        try {
            preSignedURL = getPreSignedURL(tosBucket, objectKey);
        } catch (Throwable t) {
            System.out.println("getPreSignedURL failed");
            t.printStackTrace();
            throw t;
        }

        // 4. 调用大模型Chat
        ArkService service = new ArkService(ARK_API_KEY);

        System.out.println("----- image input -----");
        final List<ChatMessage> messages = new ArrayList<>();
        final List<ChatCompletionContentPart> multiParts = new ArrayList<>();
        multiParts.add(ChatCompletionContentPart.builder().type("text").text(
                "Which is the most secure payment app according to Americans?"
        ).build());
        multiParts.add(ChatCompletionContentPart.builder().type("image_url").imageUrl(
                new ChatCompletionContentPart.ChatCompletionContentPartImageURL(preSignedURL)
        ).build());
        final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER)
                .multiContent(multiParts).build();
        messages.add(userMessage);

        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model("{your ark model endpoint id}")
                .messages(messages)
                .build();

        service.createChatCompletion(chatCompletionRequest, new HashMap<String, String>() {{
            put("x-ark-beta-vision", "true");
        }}).getChoices().forEach(choice -> System.out.println(choice.getMessage().getContent()));

        // shutdown service
        service.shutdownExecutor();
    }
}