开发一个 AI 分身应用
收藏
我的收藏

教程目标

通过本教程,可以快速了解开发一个 AI 分身应用的全流程。

前置准备

参考 AI 分身应用接入指南中指引。
    创建一个抖音开放平台账号。
    创建一个 AI 分身应用。
    了解 IDE 开发和抖音云基础功能。

整体流程

相关代码 DEMO,可以在 IDE 中「创建 AI 分身工程」功能中进行下载创建,参考 IDE 功能介绍

一个简单的应用开发

AI 分身应用是基于大语言模型和知识库等 AI 能力为某一场景提供拟人化的解决方案。

服务端接口

在项目开发中,我们需要实现以下几个接口。
    开场白(必须):
    path:/avatar/serv/onboarding
    Http method:post
    流式对话(必须)
    path:/avatar/serv/chatstream
    Http method:post
    调试健康检查接口
    path:/ping(调试期必须)
    Http method:get
    resp:实现即可

开场白接口

功能:用户首次或者一定时间后与 AI 分身聊天时,会触发给用户介绍一下本 AI 分身擅长的场景和功能,并以流式传递返回。
// OnBoarding . // @router /onboarding [POST] @PostMapping(value = "/avatar/serv/onboarding") public void OnBoarding(@RequestBody OnBoardingRequestVo onBoardingRequestVo, HttpServletResponse resp) { // 验证参数,可根据业务自行判断 // 将用户入参数复制,复制DTO OnBoardingDTO onBoardingDTO = new OnBoardingDTO(); BeanUtils.copyProperties(onBoardingRequestVo, onBoardingDTO); // 调用服务 onBoardingService.OnBoarding(onBoardingDTO, resp); }
具体实现可参考:
public class OnBoardingServiceImpl implements IOnBoardingService { private static final Logger logger = LoggerFactory.getLogger(OnBoardingServiceImpl.class); @Override public void OnBoarding(OnBoardingDTO onBoardingRequestDTO, HttpServletResponse resp) { // 开场白 demo, 可根据业务自行调整。 List<String> respString = new ArrayList<>(); respString.add("你好,"); respString.add("我是赵小小,"); respString.add("两性情感"); respString.add("专家,"); respString.add("您有什么"); respString.add("想咨询"); respString.add("的问题"); // 初始化一个chunk发送器 ChunkSendWriter<HttpResponse> chunkSendWriter = new ChunkSendWriter<>(); // 循环这个demo数据向调用方发送数据 for (int idx = 0; idx < respString.size(); idx++) { try { // 组装返回数据 OnBoardingResponseDTO onBoardingResponseDTO = new OnBoardingResponseDTO(); onBoardingResponseDTO.setStreamFinish(false); CopilotContent copilotContent = new CopilotContent(); copilotContent.setContentType(ContentTypeEnum.TEXT); copilotContent.setContent(respString.get(idx)); copilotContent.setRoleEnum(RoleEnum.Assistant); copilotContent.setSegFinish(false); if (idx == respString.size() - 1) { copilotContent.setSegFinish(true); } copilotContent.setSegType(SegTypeEnum.ANSWER); onBoardingResponseDTO.setCopilotContent(copilotContent); onBoardingResponseDTO.setBaseResp(new BaseResp()); // 发送 chunkSendWriter.Send(resp, HttpResponse.SuccessResponse(onBoardingResponseDTO)); // 模拟休眠 Thread.sleep(200); } catch (Exception e) { logger.error("OnBoarding.send error={}", e.getMessage(), e); } } // 定义一些sug类型的数据demo List<String> sugList = new ArrayList<>(); sugList.add("异地恋该如何长期维护稳定?"); sugList.add("女朋友长期不回答消息,表明了什么?"); sugList.add("如何给暗恋多年的女神表白?"); // 发送 for (int idx = 0; idx < sugList.size(); idx++) { try { // 组装返回数据 OnBoardingResponseDTO onBoardingResponseDTO = new OnBoardingResponseDTO(); onBoardingResponseDTO.setStreamFinish(false); CopilotContent copilotContent = new CopilotContent(); copilotContent.setContentType(ContentTypeEnum.TEXT); copilotContent.setContent(sugList.get(idx)); copilotContent.setRoleEnum(RoleEnum.Assistant); copilotContent.setSegFinish(false); if (idx == sugList.size() - 1) { copilotContent.setSegFinish(true); onBoardingResponseDTO.setStreamFinish(true); } copilotContent.setSegType(SegTypeEnum.FOLLOWUP); onBoardingResponseDTO.setCopilotContent(copilotContent); onBoardingResponseDTO.setBaseResp(new BaseResp()); // 发送 chunkSendWriter.Send(resp, HttpResponse.SuccessResponse(onBoardingResponseDTO)); Thread.sleep(200); } catch (Exception e) { logger.error("OnBoarding.send error={}", e.getMessage(), e); } } try { chunkSendWriter.EndWriteChunked(resp); } catch (Exception e) { logger.error("OnBoarding.send error={}", e.getMessage(), e); } } }
在 IDE 启动调试的时候,右侧对话便能出现开场白

流式对话

功能:用户与 AI 分身聊天时,用户的消息会流入该接口,并以使用该接口的返回值作为输出。在本接口中,开发者可以调用大模型,记忆等能力来丰富输出给到用户。在本 demo 中仅调用了大模型相关。其余能力详细查看下面的章节。
@PostMapping(value = "/avatar/serv/chatstream") public void ChatStream(@RequestBody ChatStreamRequestVo chatStreamRequestVo, HttpServletResponse response) { // 验证参数, 根据业务自行判断 // 参数复制 ChatStreamDTO chatStreamDTO = new ChatStreamDTO(); BeanUtils.copyProperties(chatStreamRequestVo, chatStreamDTO); // 调用服务 chatStreamService.ChatStream(chatStreamDTO, response); }
大模型配置:
在IDE开发中,IDE会将大模型环境变量以及抖音云服务的环境变量自动注入到IDE的运行环境中,开发者可以在代码中通过获取环境变量的方式获取 大模型的接入点。其他大模型接入点环境变量的名称,可以参考抖音云大模型接入点列表界面的相关说明。在抖音云开通大模型后,将接入点ID通过环境变量的方式获取后填入到Model_EP中,此时即可开始访问,否则会导致调用大模型失败。
public static final String MODEL_EP = "";
具体能力实现:调用记忆能力和豆包大模型的流式接口进行问答。
// ChatStream [POST] 流式对话接口 @Service public class ChatStreamServiceImpl implements IChatStreamService { private static final Logger logger = LoggerFactory.getLogger(ChatStreamServiceImpl.class); @Autowired private AIModelApiRestTemplate aiModelApiRestTemplate; @Autowired private MemoryApiRestTemplate memoryApiRestTemplate; @Override public void ChatStream(ChatStreamDTO chatStreamDTO, HttpServletResponse resp) { // 1. 调用一下长期记忆能力, demo List<String> longHistory = memoryApiRestTemplate.CallMemoryApiRestMethod(chatStreamDTO); // 2. 获取用户的输入 ChatCompletionRequest streamChatCompletionRequest = ChatCompletionRequest.builder() .model(GlobalPromptConfig.MODEL_EP) .messages(ChatContentUtil.FromMessages(chatStreamDTO.getMessage(), chatStreamDTO.getChatContext(), longHistory)) .stream(true) .build(); // 初始化回调处理器 ChatStreamChunkHandle chatStreamChunkHandle = new ChatStreamChunkHandle(new ChunkSendWriter<>()); AIModelConsumer aiModelConsumer = new AIModelConsumer(chatStreamDTO, resp, chatStreamChunkHandle); // 调用大模型, 流式 aiModelApiRestTemplate.CallAIModelRestStream(streamChatCompletionRequest, aiModelConsumer); } }

能力使用

外域接口调用

功能说明:外域接口是开发者在开发 AI 分身应用的时候,有从外部服务器获取用户数据的诉求,就可以实现该功能调用获取外域数据。
// 调用请求函数,这个函数是专门请求外域的统一入口, 可根据自己的要求调整 ExtendApiResponse extendApiResponse = extendApiRestTemplate.CallExtendApiRestMethod(apiID, requestBody); // 调用外部的api接口 public ExtendApiResponse CallExtendApiRestMethod(String apiUrl, String reqContent) { // 调用Post方法 try { Map<String, String> headMap = new HashMap<>(); headMap.put("Content-Type", "application/json"); // 拼接请求链接 ExtendApiRequest extendApiRequest = new ExtendApiRequest(); extendApiRequest.setProxyType(1); extendApiRequest.setApiID(apiUrl); extendApiRequest.setReqBody(reqContent); String responseBody = this.PostCallMethod(EXTEND_API_URL, JSON.toJSONString(extendApiRequest), headMap); return JSON.parseObject(responseBody, ExtendApiResponse.class); } catch (Exception e) { logger.error("CallExtendApiRestMethod 调用异常, error={}", e.getMessage(), e); return null; } }

通过 session 查会话记录

功能说明:抖音 AI 分身原子能力-记忆召回接口,可通过一定的筛选条件(包括模糊匹配,记忆 ID 筛选等)获取用户的历史记忆信息。参考记忆召回
private static final String MEMORY_API_URL = "http://open-ai-byted-org.dyc.ivolces.com/dy_open_api/avatar/atomic/api/recall_memory/"; // 调用记忆召回接口, demo List<String> longHistory = memoryApiRestTemplate.CallMemoryApiRestMethod(chatStreamDTO); // 调用长期记忆的api接口 public List<String> CallMemoryApiRestMethod(ChatStreamDTO chatStreamDTO) { List<String> resultList = new ArrayList<>(); MemoryRecallRequest memoryRecallRequest = new MemoryRecallRequest(); try { // 1. 封装请求参数 MemoryRecallParams memoryRecallParams = new MemoryRecallParams(); memoryRecallParams.setDsl(null); memoryRecallParams.setQuery(chatStreamDTO.getMessage().getMessageContent().getContent()); memoryRecallParams.setLimit(10); // 根据自己的业务代码去组装请求下游获取不同会话的历史数据 AvatarRequestInfo avatarRequestInfo = new AvatarRequestInfo(); avatarRequestInfo.setBizID(chatStreamDTO.getBizContext().getBizID()); avatarRequestInfo.setTrafficSource(chatStreamDTO.getCommonContext().getTrafficSource()); avatarRequestInfo.setOpenID(chatStreamDTO.getCommonContext().getUserInfo().getOpenID()); avatarRequestInfo.setAvatarAppID(chatStreamDTO.getCommonContext().getAvatarInfo().getAvatarAppID()); avatarRequestInfo.setTenantID("ebtest_1"); avatarRequestInfo.setProviderID("ebtest_1"); memoryRecallRequest.setParams(memoryRecallParams); memoryRecallRequest.setRequestInfo(avatarRequestInfo); // 2. 发起api请求逻辑 Map<String, String> headMap = new HashMap<>(); headMap.put("Content-Type", "application/json"); String responseBody = this.PostCallMethod(MEMORY_API_URL, memoryRecallRequest, headMap); MemoryRecallHttpResponse memoryRecallHttpResponse = JSON.parseObject(responseBody, MemoryRecallHttpResponse.class); logger.info("CallExtendApiRestMethod memoryRecallHttpResponse: {}", JSON.toJSONString(memoryRecallHttpResponse)); if (memoryRecallHttpResponse == null) { return resultList; } if (memoryRecallHttpResponse.getData() == null || memoryRecallHttpResponse.getData().getMemories() == null) { return resultList; } for (Memory data : memoryRecallHttpResponse.getData().getMemories()) { resultList.add(data.getContent()); } logger.info("CallExtendApiRestMethod resultList: {}", resultList); return resultList; } catch (Exception e) { logger.error("CallExtendApiRestMethod 调用异常, error={}", e.getMessage(), e); return resultList; } }

常见问题

启动调试失败,如何解决?

[抖音云] 本地代理容器启动失败,失败原因: 本地代理容器健康检查失败

原因:代理容器是根据调试流量动态唤起的,如果长时间未进行测试调用,代理容器资源会被回收。此时再次启动调试时,会触发实例开始启动,提示:"本地代理容器健康检查失败。"
解决办法:稍等1min左右,重新启动调试。

当前小程序下未启动本地调试功能,请前往[抖音云控制台] -> [服务列表] -> [本地调试] 打开本地调试开关

原因:使用调试功能时需要在抖音云控制台打开本地调试按钮。
解决办法:前往抖音云控制台,在「服务列表」-> 「本地调试」打开本地调试开关。

其余启动失败问题排查

    1.正常情况下启动成功后,打开Docker Desktop软件,会发现有如下两个容器正在运行(状态为Running),分别为dycloud-local-proxyai-app-local-debug
    2.如果启动失败,上述两个容器状态应该是非 Running,此时可以将这两个容器勾选,然后删除掉。重新启动调试;如果仍然失败,点击容器 Name 一栏,获取容器日志,截图提交技术工单解决。
    3.如需访问Mysql,Redis,MongoDB请在Dockerfile.debug文件内增加,代码中通过环境变量的方式来访问,在抖音云Dev和Prod部署时会自动注入这几个环境变量
ENV REDIS_PASSWORD=请自行输入对应密码信息 ENV REDIS_ADDRESS=dycloud-local-proxy:6379 ENV MYSQL_PASSWORD=请自行输入对应密码信息 ENV MYSQL_ADDRESS=dycloud-local-proxy:3306 ENV MYSQL_USERNAME= ENV REDIS_PASSWORD=请自行输入对应密码信息 ENV MONGO_ADDRESS=dycloud-local-proxy:3717 ENV MONGO_PASSWORD=请自行输入对应密码信息

对话失败

修改 endpointid,默认模板中没有 endpointid。