之前写过一篇《如何优雅的在页面上嵌入AI-Agent人工智能》,本篇文章很多基础架构都是集成于上篇文章,也是对上篇文章继续深化挖掘,从更基础的开始搭建,做到从设计到实现再到代码层业务层的实践运用。本篇文章集成演示系统设置为网页端,对于交互界面本篇文章UI前端也有设计,很多灵活设计的内容不必严格按照本篇文章代码效果编写,按照生产规则设计即可。如果本篇文章有帮助请不吝支持!
我将项目模块设计为四块,涉及了AI通信、用户请求的全流程和系统反馈模块的功能。旨在通过自然语言处理与外部数据集成,自动高效地回答用户的查询,同时根据用户反馈不断优化自身,以提供准确、智能和个性化的客户服务体验。
前端我们采用VUE3来搭建对话界面,快捷方便。后端采用Spring Boot:作为项目的核心框架,MyBatis-Plus用于数据库持久化操作,简化了 MyBatis 的使用,并提供了基本的 CRUD 方法。MySQL: 作为数据库,用于存储 AI 会话数据。我这里调用的是阿里通义千问的模型,当然每个厂商的AI SDK都大差不差,调用规则基本都是一致的,通过查阅SDK的返回参数就能很轻松的调用。
客户点击智能窗口开始会话,前端记录窗口会话ID,与客户提问Prompt一并返回后端,后端请求阿里通义接口获取回答和问答ID,后端再将获取到的text和本次会话request_id一并返回给前端,若用户对给出的回答点赞,则返回单次回答的request_id和evaluate状态,如果满意则evaluate返回1,不满意evaluate返回2。
前端UI可借鉴的模板有很多,比如ChatGPT、文心一言等,或者走比较简约的客服弹窗:
比如类似微信界面的对话框,是最容易让客户明白使用的。
实现一个简单的对话页面:
<template>
<div class="chat-window">
<div class="chat-header">Fanstuck</div>
<div class="messages">
<div v-if="isLoading" class="load">
<hr/><hr/><hr/><hr/>
</div>
<div v-for="(message, index) in messages" :key="index" class="message" :class="{ 'user': message.sender === 'user', 'bot': message.sender === 'bot' }">
<div class="avatar">
<img v-if="message.sender === 'user'" :src="userAvatar" alt="User">
<img v-else :src="botAvatar" alt="Bot">
</div>
<div v-if="message.sender === 'bot' && message.isTyping" class="typing" :ref="'typeitContainer' + index"></div>
<div v-else class="message-content">{{ message.content }}</div>
</div>
</div>
<div class="input-area">
<input v-model="newMessage" @keyup.enter="sendMessage" placeholder="Type a message..." class="input-field" :disabled="isSending"/>
<button @click="sendMessage" class="send-button" :disabled="isSending || !newMessage.trim()">发送</button>
</div>
</div>
</template>
聊天窗口结构:
isLoading
为true
时,显示加载动画。message.sender
区分用户和机器人消息,应用不同的样式。userAvatar
或botAvatar
)。isTyping
为true
),显示打字动画容器,否则显示消息内容。newMessage
,在按下回车键时调用sendMessage
方法,禁用状态取决于isSending
。sendMessage
方法,禁用条件为isSending
或newMessage
为空。我们需要和后端进行通信还需要实现打字机效果展示,可以通过引入的库:
TypeIt
:用于实现打字机效果。axios
:用于处理HTTP请求。方法:
startChat()
:向后端发送请求,启动新的聊天会话。
chatCode
。messages
添加一条错误信息。sendMessage()
:处理发送消息的逻辑。
newMessage
是否为空。isSending
和isLoading
状态。messages
。chatCode
为空,调用startChat()
获取。messages
,并设置isTyping
为true
。TypeIt
在指定的容器中显示打字机效果。methods: {
async startChat(){
try{
const response = await axios.post('http://localhost:8080/api/chats/start');
this.chatCode = response.data; // 将后端返回的 chatCode 保存在前端
console.log('Chat started with code:', this.chatCode);
} catch (error) {
console.error('Failed to start chat:', error);
this.messages.push({ sender: 'bot', content: '无法启动对话。请稍后再试。' });
}
},
async sendMessage() {
if (!this.newMessage.trim()) return; // 确保不发送空消息
this.isSending = true; // 开始发送,禁用发送按钮
this.isLoading = true;
// 添加用户消息到对话
this.messages.push({ sender: 'user', content: this.newMessage, isTyping: false });
// 如果 chatCode 为空,先调用 startChat 获取
if (!this.chatCode) {
await this.startChat();
if (!this.chatCode) {
this.isSending = false;
this.isLoading = false;
return; // 如果获取 chatCode 失败,停止发送
}
}
try {
// 模拟异步获取机器人回复
setTimeout(async () => {
// 向Spring Boot后端发送POST请求
const response = await axios.post(`http://localhost:8080/api/chats/${this.chatCode}/ask`, {
prompt: this.newMessage // 使用POST请求并传递JSON格式的数据
});
const botMessageContent = response.data.reply; // 根据后端响应的结构获取消息内容
this.messages.push({ sender: 'bot', content: botMessageContent, isTyping: true });
setTimeout(() => {
const botMessageIndex = this.messages.length -1 ;
const container = this.$refs[`typeitContainer${botMessageIndex}`];
if (container) {
new TypeIt(container[0], {
strings: [botMessageContent],
speed: 50,
afterComplete: (instance) => {
this.messages[botMessageIndex].isTyping = false;
instance.destroy(); // 强制更新以显示完整消息
}
}).go();
}
},0);
this.isLoading = false;
// 清空输入框
this.newMessage = '';
this.isSending = false; // 结束发送,允许再次发送
}, 1000); // 假设1秒后收到回复
} catch (error) {
console.error('获取回复失败:', error);
// 处理错误情况,例如添加一条错误消息到对话
this.messages.push({ sender: 'bot', content: '抱歉,无法获取回复。' });
}
后端在本人的上一篇博文有比较详细的模块划分和讲解,系统的后端设计可以划分为两个主要模块,分别处理Web端数据和AI交互:
Web数据处理模块:
AI数据交互模块:
这里我们用混元大模型设计API接口,首先打开腾讯云混元创建对应应用:
发布之后我们可以得到对应的APIKey
访问密钥管理创建密钥,和API组合一起访问就可以进行后端AI通信了。
API实现通信调用模版可以参考:
Slf4j
@Service
public class TencentyiServicelmpl implements TencentiService{
@Resource
private TencentProperties aliyunProperties;
@Autowired
private AiChatTencentRespRepository aiChatTencentRespRepository;
@Override
public TencentChatRespDto chat(TencentChatReqDto dto) {
try {
// 构建API调用参数
ApplicationParam param = ApplicationParam.builder()
.apiKey(TencentProperties.getTongyi().getApiKey())
.appId(TencentProperties.getTongyi().getChatAppId())
.prompt(dto.getPrompt())
.build();
Application application = new Application();
ApplicationResult result = application.call(param);
// 创建返回对象
AiResponse response = new AiResponse(result.getRequestId(), dto.getPrompt(), result.getOutput().getText(), result.getOutput().getFinishReason(), result.getUsage());
// 将 AiResponse 转换为实体类 AiChatTongyiRespPo
AiChatTencentRespPo po = AiResponseToPoConverter.convert(response);
aiChatTencentRespRepository.saveAiResponse(po);
return new TencentChatRespDto().setReply(result.getOutput().getText());
}catch (Exception e){
log.error("调用大模型未知错误", e);
throw new RuntimeException("调用失败", e);
}
}
}
同时返回的数据信息需要我们建表保存,用于后续的大模型召回统计测试:
其中request_id对应的是一条回复通信的唯一识别,tokens对应的是输入和输出的字节数,帮助我们优化模型和prompt节省经费。后端的RESTful API可以设计三个:
开始对话 (/start
):
POST /start
路由用于开始一个新的聊天会话,调用 askService.startChat()
启动一个新的对话并返回一个聊天编号,方便后续的提问和互动。提问 (/{chatCode}/ask
):
POST /{chatCode}/ask
路由允许用户在特定的聊天会话中向AI提问。AiChatForm
),调用 askService.ask(chatCode, form.getPrompt())
生成AI的回答,并通过 assemble()
方法将 AiChatAsk
对象转换为返回给用户的 AskReplyVo
视图对象。评价 (/{chatCode}/asks/{askCode}/evaluate
):
PUT /{chatCode}/asks/{askCode}/evaluate
路由用于对AI的回答进行评价。askService.evaluate()
方法将用户的评价(如满意度)记录下来。ServiceException("问答不存在")
。同时新建一张表保留全部对话数据,用于后续业务模型的调优:
需要注意设计的点是chat_code记录的是同一对话框编号,可以标记为同一客户标识,就可以追溯上下文问题关联。后续客户对评论的点赞是后触发操作,通过前端返回ask_code和evaluate,更新这张mysql表,起到对评论的点赞记录。
public class AiChatAskServiceImpl implements AiChatAskService {
@Resource
private AiChatAskRepository askRepository;
@Resource
private AiyunTongyiService tongyiService;
@Override
public String startChat() {
return IdUtil.fastSimpleUUID();
}
@Override
public AiChatAsk ask(String chatCode, String prompt) {
AiChatAsk ask = new AiChatAsk(new AiChatAskInfo(chatCode, prompt));
ask.start();
TongyiChatRespDto resp = tongyiService.chat(new TongyiChatReqDto()
.setAskCode(ask.getAskInfo().getAskCode())
.setPrompt(prompt));
ask.reply(resp.getReply());
askRepository.save(ask);
return ask;
}
@Override
public void evaluate(AiChatAsk ask, Integer evaluate) {
ask.evaluate(evaluate);
askRepository.saveOnEvaluate(ask);
}
}
参照以上功能设计实现即可,接口涵盖了AI聊天会话的基本功能:启动会话、向AI提问、以及对AI回答的满意度评价,同时设计了一个测试接口用于验证系统是否正常运行。源代码将上传至Github有需要的同学可以直接看源码浏览,代码逻辑简单基本上没有什么很复杂的设计,后续可以集成多模态多功能作为基底使用。
通过本篇文章,我们从基础架构的设计到前后端的具体实现,系统性地展示了如何集成一个AI问答客服系统。从最初的项目顶层架构和技术选型,到详细的设计时序图,再到具体的前端UI设计和实现,以及后端的逻辑和代码演示,逐步完成了一个功能齐全、模块清晰的AI问答客服系统。
在项目构建的过程中,强调了系统的低耦合性和模块化设计,以便于后续的维护和扩展。前端和后端各自承担相应的职责,前端注重用户体验和交互设计,后端则注重业务逻辑的处理和与AI的高效交互,确保系统既能为用户提供流畅的使用体验,也能快速处理用户的请求并返回有价值的回答。希望这篇文章能够帮助大家了解AI问答客服系统的构建流程,并为实际开发提供有效的参考。感谢阅读和支持,期待你的反馈和进一步的交流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。