程序员盒子接入百度文心大模型文生图能力

原文:https://www.coderutil.com/article?id=203

image.png

一、前言

AI时代是一个快速发展的时代,它带来了很多机会和挑战。对于普通人来说,AI技术的应用可以帮助我们更高效地完成工作,提高生产力和工作质量。同时,AI技术也可以帮助我们更好地理解和解决问题,提高我们的智能水平。

然而,AI技术的应用也可能会给我们的生活和工作带来一些挑战。例如,AI技术可能会取代一些人类的工作,导致失业率的上升。此外,AI技术的发展也需要我们不断学习和适应,以保持其竞争力和适应性。

总之,AI时代对于普通人来说是一个充满机遇和挑战的时代。我们需要不断学习和适应,以保持我们的竞争力和适应性。

————以上内容由「文新一言」生成

思考:盒子能够做点什么?要不先接入个文生图玩一玩,刚好前两天百度智能云文心模型开放了文生图的API,那整一下呗……

二、画聊 - ShowCase

2.1 服务名称

画聊:依附于C圈交流社区,社区文生图服务。

2.1 服务入口

file_ec7acb8cd7794431ae7de4d98d6dd64d.png

2.2 绘画编辑器

file_e4b639219b9149bbb555d3f7a74acdb3.png

2.3 一键分享到C圈

file_5e77cf8685d1405692dfa42e3a3e7c0a.png

这是效果演示,有兴趣的大家自己去体验,因为文生图的API服务现在费用还是很高的,所以我们目前每天限量前500名用户可以体验,每天每天限量免费3次,望理解。

三、申请流程

3.1 申请页面-百度文新一言大模型主页: https://cloud.baidu.com/wenxin.html

file_2e45e902f90947439c6ff6b3d14882d7.png

3.2 底部 AI开放能力/ 多模态内容生成/文心AI作画:

file_c0d45295e7df4ac79c62bd79c138be66.png

3.3 购买用量

按需购买就可以,一开始不用买太多,这里地方是文心开放的基础版能力(支持个人用户接入),高级版(作画效果更好)目前只支持企业介入(需要联系客服申请),我们就以个人为例,可以先购买600张图:

file_0b2f90d5320d4391aba484874765dfef.png

3.4点击立即使用:

file_6fbeb1532686404fa63ecef41c87448b.png

需要先创建自己的应用(按照要求填写应用信息即可,比较简单),获取AK、SK

file_88634de391154c8f8affd89f09d92bc6.png

现在文新AI作画服务我们已经申请购买好,做好前期准备工作后,接下来就是API对接啦(作为多年的API调用工程师,这个不是什么难事)

四、接入核心流程分析

4.1 API接口文档了解接入流程

接口文档: https://cloud.baidu.com/doc/NLP/s/Ml9i5amtk

4.2 接入流程梳理

接入流程主要有几下四个核心步骤:

1、请求百度智能云Oauth2.0结果获取token,token默认有效期30天;

2、业务册自己缓存token到redis;

3、携带token+body请求文新文生图模型api,提交生图任务,文心api返回本次提交的任务id;

4、业务测根据taskId轮训请求文心文生图结果api,查询生成结果。

file_8aaa58c571824ef1b92ae298b1e1410b.png

4.3 模型设计

关于模型设计,有以下几个问题需要考虑到:

file_80709d3b0c734258a249dce1c67e52ec.png

问题一:我现在选用的相对低价格的套餐,但是限流1qps,如果只是我个人使用的话这个并发支持量肯定没有任何问题的,但是如果要在盒子开放能力,那用户量大一点,并发高一点的时候1qps扛不住,会出现因为限流导致很多请求失败的问题,怎么解决?

方案:消息队列提交任务,所有请求排队处理,可以慢一点,但是要保障可用性。

问题二:毕竟是付费产品包,处于安全性、调用量可控的考虑,业务侧也需要做限流处理,不然一下子上百万的调用量要了命

方案:强制登录后使用,然后BY账号每次限流3到5次。

问题三:轮训还是socket?在4.2的接入流程中大家也发现了结果生成文新模型api是异步执行的,那就涉及到结果查询怎么相应给客户端?

方案:简单点吧,轮训请求吧,控制好轮训的频次(降低对服务端的压力)

提交任务模型设计:

file_d91de19772a642cf889c988da9a0c379.png

客户端轮训请求任务id(轮训1):

file_4829cdd24c48459c9351ef19b24cb397.png

客户端轮训请求任务结果(轮训2,结束轮训1):

上一步的轮询中我们已经拿到了任务id,现在可以拿着任务id轮训请求任务结果了(补充下,为什么这里要轮训,因为任务生成是有进度的,不是一下子就完成的,或许你的这次请求,任务只完成啦50%,还需要一到两次才能拿到完成的结果)

file_75771a2969e148e2b89cba3adba64699.png

好啦,整个接入流程非常简单,弄清楚接入流程后就开发代码编码了。

五、技术实现

5.1接口设计

file_433d9367fdbd4dcb92d54469ac11e4d5.png

接口定义如下:

/**
 * @Author 程序员七七
 * @webSite https://www.coderutil.com
 * @Date 2023/4/19 23:16
 * @description
 */
@Slf4j
@Api(tags = "【AIG Open】文生图服务")
@RestController
@RequestMapping("/api/aig/image")
public class AIGImageController {

    /**
     * 获取绘画风格
     *
     * @return
     */
    @ValidateLogin
    @GetMapping("/list/style")
    public APIResponseBean<List<BaiduAiImageStyleResponseVO>> styleList() {
        // TODO
        return APIResponseBeanUtil.success();
    }

    /**
     * 获取绘画尺寸
     *
     * @return
     */
    @ValidateLogin
    @GetMapping("/list/resolution")
    public APIResponseBean<List<BaiduAiImageResolutionResponseVO>> resolutionList() {
        // TODO
        return APIResponseBeanUtil.success();
    }

    /**
     * 获取请求token
     *
     * @return
     */
    @ValidateLogin
    @RequestPermission
    @GetMapping("/****/***")
    public APIResponseBean<String> getAccessToken() {
        // TODO
        return APIResponseBeanUtil.success();
    }

    /**
     * 提交绘画任务
     *
     * @return
     */
    @ValidateLogin
    @SafeClick(event = ClickEvent.SUBMIT, time = 10000, message = "10秒内只能提交一次")
    @PostMapping("/task/submit")
    public APIResponseBean submitTask(@RequestBody AIGImageCreateRequestVO aigImageCreateRequest) {
        // TODO
        // 限流1qps,这里需要消息队列提交任务
        return APIResponseBeanUtil.success("提交成功,结果生成中...");
    }

    /**
     * 查询任务id
     *
     * @return
     */
    @ValidateLogin
    @GetMapping("/getTaskId")
    public APIResponseBean<Long> getTaskId() {
        // TODO
        return APIResponseBeanUtil.success();
    }

    /**
     * 查询任务结果
     *
     * @return
     */
    @ValidateLogin
    @GetMapping("/result/{taskId}")
    public APIResponseBean<String> getTaskResult(@PathVariable Long taskId) {
        // TODO
        return APIResponseBeanUtil.success();
    }

    /**
     * 获取今日使用人数
     *
     * @return
     */
    @GetMapping("/useUser/count")
    public APIResponseBean<Long> getUseUserCount() {
        // TODO
        return APIResponseBeanUtil.success();
    }
}

5.2 Token获取

应用信息配置:

# AIG properties config
aig.config.baidu.host=https://aip.baidubce.com
aig.config.baidu-text2image-token-url=${aig.config.baidu.host}/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s
aig.config.baidu-text2image-submit-url=${aig.config.baidu.host}/rpc/2.0/ernievilg/v1/txt2img?access_token=%s
aig.config.baidu-text2image-data-url=${aig.config.baidu.host}/rpc/2.0/ernievilg/v1/getImg?access_token=%s
aig.config.baidu-ak=应用AppKey
aig.config.baidu-sk=应用SecretKey

维护配置类:

/**
 * @Author 程序员七七
 * @webSite https://www.coderutil.com
 * @Date 2023/4/19 22:50
 * @description
 */
@Data
@Component
@ConfigurationProperties(prefix = "aig.config")
public class AIGPropertiesConfig {

    /**
     * 百度智能云ak
     */
    private String baiduAk;

    /**
     * sk
     */
    private String baiduSk;

    /**
     * 获取智能创作token
     */
    private String baiduText2imageTokenUrl;

    /**
     * 提交生成图片人物
     */
    private String baiduText2imageSubmitUrl;

    /**
     * 查询生成结果
     */
    private String baiduText2imageDataUrl;
}

token请求与缓存:

    @Autowired
    private AIGPropertiesConfig aigPropertiesConfig;
    @Autowired
    private RedisService redisService;
   
   /**
     * 获取请求token - 从缓存中获取
     *
     * @return
     */
    public String getRequestTokenFromCache() {
        String key = RedisKeyEnum.BAIDU_AIG_IMAGE_TOKEN_CACHE.getKey();
        String cache = redisService.get(key);
        if (StringUtils.isNotBlank(cache)) {
            return cache;
        }
        String token = this.getRequestTokenFromApi();
        if (StringUtils.isNotBlank(token)) {
            redisService.set(key, token, RedisKeyEnum.BAIDU_AIG_IMAGE_TOKEN_CACHE.getExpireTime());
        }
        return token;
    }

   /**
     * 获取请求token - API获取
     *
     * @return
     */
    private synchronized String getRequestTokenFromApi() {
        String tokenUrl = aigPropertiesConfig.getBaiduText2imageTokenUrl();
        String requestTokenApi = String.format(tokenUrl, aigPropertiesConfig.getBaiduAk(), aigPropertiesConfig.getBaiduSk());
        Map responseMap = HttpClientUtil.postFormForUrl(requestTokenApi, new HashMap<>(), Map.class);
        return String.valueOf(responseMap.get("access_token"));
    }

5.3 提交任务

消息队列设计:

这里我是给予redis自己实现了一个清亮的消息队列,你可以使用其他的MQ。

提交任务:

@Autowired
private AIGImageSubmitQueue aigImageSubmitQueue;

/**
 * 提交文生图任务到队列
 */
AIGImageMessageDTO aigImageMessage = new AIGImageMessageDTO(req);
aigImageSubmitQueue.submit(aigImageMessage);

file_1dd42c06d7564242935148ca305e974e.png

任务处理:

/**
     * 提交任务
     * @param aigImageMessage
     */
    public void submit(AIGImageMessageDTO aigImageMessage) {
        // 请求参数
        BaiduAIImageCreateRequestVO request = aigImageMessage.getRequest();
        // 获取token
        String token = this.getRequestTokenFromCache();
        String submitApi = aigPropertiesConfig.getBaiduText2imageSubmitUrl();
        submitApi = String.format(submitApi, token);
        try {
            BaiduAiImageResponseTaskVO response = HttpClientUtil.postObjectForUrl(submitApi, request, BaiduAiImageResponseTaskVO.class);
            this.doHandleTaskSubmitResult(aigImageMessage, response);
        } catch (Exception e) {
            log.error("AI作图任务提交异常, 参数:{}", JsonUtil.toJsonString(aigImageMessage), e);
        }
    }
    
    /**
     * 处理任务提交结果
     *
     * @param aigImageMessage
     * @param response
     */
    private void doHandleTaskSubmitResult(AIGImageMessageDTO aigImageMessage, BaiduAiImageResponseTaskVO response) {
        String userId = aigImageMessage.getUserId();
        Long taskId = response.getData().getTaskId();
        this.refreshUserTaskIdCache(userId, taskId);
    }

    private void refreshUserTaskIdCache(String userId, Long taskId) {
        // 缓存任务ID
        String key = RedisKeyEnum.BAIDU_AIG_IMAGE_RESULT_TASK_ID_CACHE.getKey(userId);
        redisService.set(key, String.valueOf(taskId), RedisKeyEnum.BAIDU_AIG_IMAGE_RESULT_TASK_ID_CACHE.getExpireTime());
    }

5.4 请求结果

   /**
     * 查询任务结果
     *
     * @param userId
     * @param taskId
     * @return
     */
    public BaiduAiImageResponseVO getAIGImageResult(String userId, Long taskId) {
        Assert.isTrue(taskId != null, "任务ID参数缺失!");
        // 安全考虑,登录用户只能查看自己的的任务id对应的结果(从缓存获取最近的taskid)
        Long taskIdCache = getUserTaskIdFromCache(userId);
        Assert.isTrue(taskId.equals(taskIdCache), "任务ID失效或者无权限查看!");
        /**
         * 优先从缓存获取 - 也算是对下游服务的一种保护,同时降低自己服务资源的
 损耗        */
        BaiduAiImageResponseVO response = this.getTaskResultFromCache(taskId);
        if (response != null) {
            return response;
        }
        // 缓存中没有,从api获取
        response = this.getAIGImageResultFromAPI(userId, taskId);
        if (response != null) {
            // 
            this.refreshTaskResult(taskId, response);
        }
        return response;
    }

    /**
    * 请求api查询生成结果
    *
    **/
    public BaiduAiImageResponseVO getAIGImageResultFromAPI(String userId, Long taskId) {
        // 获取token
        String token = this.getRequestTokenFromCache();
        String dataApi = aigPropertiesConfig.getBaiduText2imageDataUrl();
        dataApi = String.format(dataApi, token);
        Map<String, String> body = new HashMap<>();
        body.put("taskId", String.valueOf(taskId));
        try {
            BaiduAiImageResponseVO response = HttpClientUtil.postObjectForUrl(dataApi, body, BaiduAiImageResponseVO.class);
            if (response != null && response.getData() != null && response.getData().getStatus().equals(1)) {
                // 图片已生成
                response.getData().setImgUrls(new ArrayList<>());
                // 图片结果上传到自己的存储服务(不使用原图,避免原图失效过期)
                String img = response.getData().getImg();
                // 下载百度云的网络图片到本地,并上传到盒子自己的OSS存储
                UploadResultVO ossResult = fileUploadService.downloadNetworkImageAndUploadOSS(img, OSS_AIG_IMAGE_PATH, "png");
                response.getData().setImg(ossResult.getUrl());
                return response;
            }
        } catch (Exception e) {
            log.error("AI作图任务结果查询异常, userId:{}, taskId:{}", userId, taskId, e);
        }
        return null;
    }

5.5 前端实现

前端实现没什么好分享的(前端太差了我),就分享下我用的轮训方法:

需要注意加锁,拿到结果后就不要一直发起无效的请求啦

file_6966a4b3c86346b882895070c9598ab2.png

原文:https://www.coderutil.com/article?id=203

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员盒子应用作者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值