目录
一、认识阿里云百炼
模型广场
创建自己的模型
二、AI扩图示例
1、开头服务、设置秘钥
2、选择HTTP方式调用流程
3、创建任务请求示例
4、发送http请求提交任务
5、查看任务进度的流程设计
6、后端查看任务进度代码
三、总结
大家好,我是jstart千语。今天来给大家如何给系统接入AI大模型,让我们的系统可以增加AI功能。本篇主要是依赖阿里云百炼来讲解,因为它有丰富的模型和应用,也可以自定义一个应用发布,然后调用API使用自己的模型。本篇以AI扩图为例给大家讲解。
一、认识阿里云百炼
模型广场
官网地址:阿里云百炼https://bailian.console.aliyun.com/#/home
模型广场地址:阿里云百炼https://bailian.console.aliyun.com/?tab=model#/model-market
可以看到有非常多现成的模型可以使用,可以根据自己的业务功能选一个来实现
创建自己的模型
(1)登录后还可以创建自己的应用:
(2)创建好应用后,要选择一个模型:
(3)模型示例,点击右上发布
(4)这里通过官方文档学习调用,这里就不展开讲了
二、AI扩图示例
官方文档:阿里云百炼https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2796845.html
1、开头服务、设置秘钥
可自行查看官方文档,申请一个秘钥非常简单,但记得秘钥不要暴露。之后还要放在配置文件里,然后读取。
秘钥官方文档:阿里云百炼https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2712195.html
2、选择HTTP方式调用流程
调用大模型可以使用sdk调用和使用http调用,这里我们选择的是发送http请求的方式调用
1. 根据官方文档得知,只需要向官方指定的路径,携带图片的地址、以及个人秘钥和其他相关请求头即可发送http请求,创建一个扩图任务,任务创建成功后,会返回一个任务id
2. 任务完成后,不会主动向我们响应,我们需要携带用户id反复地询问官方文档指定的地址,看任务有没有完成,如果任务完成了,就会返回图片地址
3、创建任务请求示例
请求参数具体表示什么请看官方文档:阿里云百炼
部分请求参数解释:
注意,虽然这个http请求只支持异步,但还是要显示地设置。这样做的好处是便于后期扩展,如果后面又支持同步调用了,那么只需要改一下参数即可。而且,这样也可以让开发者清楚这是一个异步请求,更好理解。
请求实体示例:
@Data
public class CreateOutPaintingTaskRequest implements Serializable {
/**
* 模型,例如 "image-out-painting"
*/
private String model = "image-out-painting";
/**
* 输入图像信息
*/
private Input input;
/**
* 图像处理参数
*/
private Parameters parameters;
@Data
public static class Input {
/**
* 必选,图像 URL
*/
@Alias("image_url")
private String imageUrl;
}
@Data
public static class Parameters implements Serializable {
/**
* 可选,逆时针旋转角度,默认值 0,取值范围 [0, 359]
*/
private Integer angle;
/**
* 可选,输出图像的宽高比,默认空字符串,不设置宽高比
* 可选值:["", "1:1", "3:4", "4:3", "9:16", "16:9"]
*/
@Alias("output_ratio")
private String outputRatio;
/**
* 可选,图像居中,在水平方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
*/
@Alias("x_scale")
@JsonProperty("xScale")
private Float xScale;
/**
* 可选,图像居中,在垂直方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
*/
@Alias("y_scale")
@JsonProperty("yScale")
private Float yScale;
/**
* 可选,在图像上方添加像素,默认值 0
*/
@Alias("top_offset")
private Integer topOffset;
/**
* 可选,在图像下方添加像素,默认值 0
*/
@Alias("bottom_offset")
private Integer bottomOffset;
/**
* 可选,在图像左侧添加像素,默认值 0
*/
@Alias("left_offset")
private Integer leftOffset;
/**
* 可选,在图像右侧添加像素,默认值 0
*/
@Alias("right_offset")
private Integer rightOffset;
/**
* 可选,开启图像最佳质量模式,默认值 false
* 若为 true,耗时会成倍增加
*/
@Alias("best_quality")
private Boolean bestQuality;
/**
* 可选,限制模型生成的图像文件大小,默认值 true
* - 单边长度 <= 10000:输出图像文件大小限制为 5MB 以下
* - 单边长度 > 10000:输出图像文件大小限制为 10MB 以下
*/
@Alias("limit_image_size")
private Boolean limitImageSize;
/**
* 可选,添加 "Generated by AI" 水印,默认值 true
*/
@Alias("add_watermark")
private Boolean addWatermark = false;
}
}
解释:
- 添加了@Alias注解的属性表示进行json转换时转换成的名字,但这个注解只对使用hutool工具包时生效,springMVC转换不受影响。
- xScale和yScale属性上面加上了类似@JsonProperty("yScale")的注解,是因为这个属性的名字比较特殊:第二个字母是大写开头的属性名。这样的属性springMVC不会映射得到,也就是在携带的请求参数中,即使给yScale赋值了,yScale实际上也是没有值的,需要添加@JsonProperty("yScale")注解来解决。
4、发送http请求提交任务
具体代码:
@Slf4j @Component public class AliYunAiApi { // 读取配置文件(事先在配置文件中,按这个路径配置好) @Value("${aliYunAi.apiKey}") private String apiKey; // 创建任务地址 public static final String CREATE_PICTURE_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting"; //创建扩图任务 public CreateOutPaintingTaskResponse createOutPaintingTask(CreateOutPaintingTaskRequest createOutPaintingTaskRequest) { //...一下校验逻辑 // 发送请求 HttpRequest httpRequest = HttpRequest.post(CREATE_OUT_PAINTING_TASK_URL) .header(Header.AUTHORIZATION, "Bearer " + apiKey) // 必须开启异步处理,设置为enable。 .header("X-DashScope-Async", "enable") .header(Header.CONTENT_TYPE, ContentType.JSON.getValue()) .body(JSONUtil.toJsonStr(createOutPaintingTaskRequest)); try (HttpResponse httpResponse = httpRequest.execute()) { if (!httpResponse.isOk()) { log.error("请求异常:{}", httpResponse.body()); throw new BusinessException(ErrorCode.OPERATION_ERROR, "AI 扩图失败"); } CreateOutPaintingTaskResponse response = JSONUtil.toBean(httpResponse.body(), CreateOutPaintingTaskResponse.class); String errorCode = response.getCode(); if (StrUtil.isNotBlank(errorCode)) { String errorMessage = response.getMessage(); log.error("扩图失败,errorCode:{}, errorMessage:{}", errorCode, errorMessage); throw new BusinessException(ErrorCode.OPERATION_ERROR, "AI 扩图接口响应异常"); } return response; } } }
这里使用hutool 发送了http请求,并且根据官方文档的响应示例,编写响应实体来接受http请求发送后获取的响应体。
注意:创建任务的响应体根据创建成功或异常,返回的响应体是不同,那我们后端直接把这两个不同的响应体的字段合并成一个响应实体即可。返回响应体时,直接判断code字段有没有值,如果有值,说明出现异常了。如果响应成功,会返回一个任务id,这个任务id非常重要,后面要根据这个任务id,来查询这个任务是否已经完成
创建任务时的响应实体:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateOutPaintingTaskResponse {
private Output output;
/**
* 表示任务的输出信息
*/
@Data
public static class Output {
/**
* 任务 ID
*/
private String taskId;
/**
* 任务状态
* PENDING:排队中
* RUNNING:处理中
* SUSPENDED:挂起
* SUCCEEDED:执行成功
* FAILED:执行失败
* UNKNOWN:任务不存在或状态未知
*/
private String taskStatus;
}
/**
* 接口错误码。
* 接口成功请求不会返回该参数。
*/
private String code;
/**
* 接口错误信息。
*接口成功请求不会返回该参数。
*/
private String message;
/**
* 请求唯一标识。
* 可用于请求明细溯源和问题排查。
*/
private String requestId;
}
5、查看任务进度的流程设计
如果提交了很多任务,任务可能会阻塞,同一时间只有一个任务正在被执行,在查看任务进度时,这个任务未必就已经完成能拿到新的图片url。所以我们要不断地发送请求,知道能拿到新图片的url为止,即轮询。
业务有关的问题:前端向AI服务器发送请求询问,还是后端发送请求?
是前端不停地请求AI服务器还是后端不停地请求AI服务器?从这个业务来讲,一定是后端发送请求的,因为要涉及到一个API的key,总不能把key保存到前端吧。即使是先请求后端获取到key再通过前端请求AI服务器也是不妥的,因为前端不能实时保存任务,如果用户刷新了界面,那任务就可能丢失了。所以使用后端来调用AI服务器更加合适一些。
关于轮询:在开发时是前端轮询还是后端轮询呢?
- 前端轮询:前端每隔一定时间,向后端发送请求询问,然后后端向AI服务器发送请求。
- 后端轮询:前端只需要发送一次请求给后端,后端通过定时给AI服务器发送请求询问。
使用后端轮询的话比较占用线程,容易阻塞,比如前端发送了四五十个请求,那么这四五十个线程都要阻塞等待AI服务器返回结果才能响应给前端,资源容易耗尽,性能不好。所以我们使用的是前端轮询。
6、后端查看任务进度代码
查看任务进度的响应实体:阿里云百炼https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2796845.html
@Data @NoArgsConstructor @AllArgsConstructor public class GetOutPaintingTaskResponse { /** * 请求唯一标识 */ private String requestId; /** * 输出信息 */ private Output output; /** * 表示任务的输出信息 */ @Data public static class Output { /** * 任务 ID */ private String taskId; /** * 任务状态 * PENDING:排队中 * RUNNING:处理中 * SUSPENDED:挂起 * SUCCEEDED:执行成功 * FAILED:执行失败 * UNKNOWN:任务不存在或状态未知 */ private String taskStatus; /** * 提交时间 * 格式:YYYY-MM-DD HH:mm:ss.SSS */ private String submitTime; /** * 调度时间 * 格式:YYYY-MM-DD HH:mm:ss.SSS */ private String scheduledTime; /** * 结束时间 * 格式:YYYY-MM-DD HH:mm:ss.SSS */ private String endTime; /** * 输出图像的 URL */ private String outputImageUrl; /** * 接口错误码 * <p>接口成功请求不会返回该参数</p> */ private String code; /** * 接口错误信息 * <p>接口成功请求不会返回该参数</p> */ private String message; /** * 任务指标信息 */ private TaskMetrics taskMetrics; } /** * 表示任务的统计信息 */ @Data public static class TaskMetrics { /** * 总任务数 */ private Integer total; /** * 成功任务数 */ private Integer succeeded; /** * 失败任务数 */ private Integer failed; } }
后端发送查询任务进度的http请求:
@Slf4j @Component public class AliYunAiApi { // 读取配置文件 @Value("${aliYunAi.apiKey}") private String apiKey; // 查询任务状态 public static final String GET_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/tasks/%s"; /** * 查询创建的任务 */ public GetOutPaintingTaskResponse getOutPaintingTask(String taskId) { if (StrUtil.isBlank(taskId)) { throw new BusinessException(ErrorCode.OPERATION_ERROR, "任务 id 不能为空"); } try (HttpResponse httpResponse = HttpRequest.get(String.format(GET_TASK_URL, taskId)) .header(Header.AUTHORIZATION, "Bearer " + apiKey) .execute()) { if (!httpResponse.isOk()) { throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取任务失败"); } return JSONUtil.toBean(httpResponse.body(), GetOutPaintingTaskResponse.class); } } }
三、总结
至此,我们就完成了调用AI大模型的工作了 ,接下来就只剩测试。其实也就是在后端发送http请求的过程,主要的设计是在轮询这方面。总体来看还是非常简单的 。