腾讯元器是基于腾讯混元大模型的一站式智能体制作平台,我们可以在这个平台上制作属于自己的智能体,并且可以制作插件,通过智能体插件配置,让智能体拥有这些插件的能力。
在智能体创建完成之后,可以将这些智能体接入到QQ和微信客服。当然,如果我们想要在第三方应用接入元器智能体,可以和chatGPT一样通过api调用。在本篇文章中,会讲述如何创建一个智能体、如何使用插件以及如何在微信小程序中接入智能体。
元器/ChatGPT微信小程序效果图展示:
元器/ChatGPT微信小程序动图展示:
进入腾讯元器首页,我们可以看到有智能体和插件的入口。点击“创建智能体”就可以新建一个智能体。
因为初次使用元器,我们可以参考已经发布的智能体设置,来创建智能体。
我个人是个国漫迷,尤其是文改漫,所以我打算创建一个很中二的智能体:完美世界的男主 -- 荒天帝。我参考孔乙己”智能体设置,来配置荒天帝智能体。
在详细设定中设定智能体身份、个人背景、任务关系、个人技能和人物性格等。
因为详细设定有10000字的限制,如果想要让智能体更好地理解用户问题意图,我们可以通过添加知识库的方式辅助模型回答用户的问题。
我直接下载了完美世界全本,在智能体设置页面通过创建知识库来添加。至此,一个名为荒天帝的智能体创建成功。创建之后可以进行对话测试。
如图,可以与荒天帝实现简单的对话。体验入口:https://yuanqi.tencent.com/agent/v0c4FkvNvtOi?from=share
接下来我们要做的就是添加一些插件,并将荒天帝接入到微信小程序中。
很早之前就想写一个类似于ChatGPT的小程序,后来因为事情太多搁置了,正好借此机会来手写一个微信小程序,并接入腾讯智能体实现对话功能。
技术架构还是uni-app + vue3,前端能力有限,所以开发周期就是边学边开发,我前端开发的思路就是:先有这个东西,然后在需要和其他模块数据联动交互的时候,再进行公共模块的开发,这些小程序的过程中都有体现。
不论是H5还是小程序开发,第一步就是考虑布局layout。我这个小程序布局主要分为四个部分:从从左到右分为菜单aside和会话区。会话区从上到下分为header、对话显示main区域以及输入框区域。布局代码如下:
<view>
<view class="laytout">
<view class="aside">Aside</view>
<view class="right">
<view class="header">Header</view>
<view class="main">Main Content</view>
<view class="footer">footer</view>
</view>
</view>
</view>
Element Plus提供了布局元素,uni-app只能使用原生的view(div)元素,然后通过自定义css来完成布局。
.laytout {
display: flex;
height: 100%;
width: 100%;
}
.right {
display: flex;
flex-direction: column;
}
.header {
height: 5vh;
}
.main {
height: 84vh;
width: 100vw;
margin-top: 10px;
}
.footer {
width: 100vw;
}
为了方便开发和标识各个区域,可以给各个区域添加background和border,在添加css样式之后,布局雏形就出来了。
布局完成,接着就是每个布局区域的功能设计。
main区域是与ChatGPT的会话区域,在这里要实现的功能:
滚动窗口使用scroll-view组件来实现,
<scroll-view class="scroll-Y" scroll-y="true" scroll-with-animation='true'>
</scroll-view>
scroll-y设置为true,表示是在垂直方向上滚动,scroll-with-animation用来开启滚动条动画。滚动窗口的高度设置为84vh,剩下的16vh就分配给header和footer。接下来就是将交互会话在scroll中实现。
会话部分是整个小程序的核心内容。和平时我们使用的微信和QQ聊天一样,ChatGPT/元器消息在左,个人消息在右。在开发这个模块的时候,我从最简单的功能实现开始,在js中定义了一个消息列表messageList。
const messageList = []
messageList.push({ content: '你好', sender: 'right' });
messageList.push({ content: '请问有什么可以帮助您吗', sender: 'left' });
其中content是文本内容,sender用来区别是元器智能体还是用户,这里都使用了固定值,只是为了做一个样式设计,后面会根据元器智能体API请求规范进行填充。然后就是html的设计。
<view class="session" v-for="(message, index) in messageList" :key="index">
<view class="message-line" :class="message.sender === 'left' ? 'message-line-receive' : 'message-line-send'">
<image v-if="message.sender === 'left'" src='../../../static/chatgpt.png'></image>
<view :class="message.sender === 'left' ? 'receive' : 'send'">{{ message.content }}</view>
<image v-if="message.sender === 'right'" src='../../../static/logo.png'></image>
</view>
</view>
通过遍历messageList来渲染消息。每行消息又分为两部分:头像和消息内容,通过v-if对sender的判断,来实现智能体对话部分是头像在消息内容左侧,而用户的头像在消息内容右侧。
在上面我使用了flex弹性布局,并使用flex-end使元素靠右分布。
.session {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.message-line {
display: flex;
justify-content: flex-end;
}
而元器智能体的消息框应该靠左侧分布,所以我将align-self设置成flex-start,来实现这个功能。
.message-line-receive {
align-self: flex-start;
}
除此之外,使用margin设置一下元素间隔,最后的样式:
因为和智能体的对话要靠用户的输入内容驱动,所以接下来就是设计footer区域。
footer区域主要是用户输入问题,除了输入框之外可以增加一些功能按钮,例如语音、文件上传等。
<view class="input">
<uni-icons type="chat" size="30"></uni-icons>
<view class="chat-input uni-column">
<input class="uni-input" v-model="inputMessage" maxlength="200" placeholder="你有什么想知道的?" @confirm="sendMessages" />
</view>
<uni-icons class='mic' type="mic" size="30"></uni-icons>
</view>
我这里的footer区域设置了三个组件:两个uni图标,一个input输入框。
第一个chat图标暂时没什么想法,第二个mic图标我打算接入腾讯云语音识别,这个就留给下一篇文章。从上面的html可以看到,input输入框绑定了一个confirm事件,当输入完成点击回车,这时候需要触发三个动作:
footer和main区域是两个组件实现的,按照我们上面的设计,我们要将输入的消息内容,放到main定义的messageList中才能渲染成功。但是两个组件要想数据交互,就要使用共享状态变量,要不就是vuex、要不就是pinia。uin-app内置了pinia,所以这里还是使用pinia。
export const useChat = defineStore('useChat',
() => {
const state : Chat = reactive({
messages: [],
model_id: 0,
receiver: '',
sender: '',
})
这里一共定义了四个变量,messages用来存放智能体和用户的消息内容,用来构造元器智能体api的请求参数。model_id用来区分使用的模型,例如0是智能体、1是ChatGPT,以此来调用不同的接口。
receiver和sender表示角色,在元器智能体api的请求参数中,必须要有role,用户的role是user,智能体的是assistant,在这里也就对应着这个变量。
在智能体的调用api文档中,可以看到请求参数。
而其中messages参数,就是我们在状态变量中构造的。所以在共享状态变量中,我定义了constructMessage函数,用来处理共享状态变量和构造消息列表。
// role 1表示send, 2 receive
const constructMessage = function (message, role) {
switch (state.model_id) {
// 元器
case 0:
state.sender = 'user'
state.receiver = 'assistant'
const templete = {
"role": "",
"content": [
{
"type": "text",
"text": message
}]
}
if (role == 1) {
templete.role = 'user'
} else {
templete.role = 'assistant'
}
state.messages.push(templete)
break;
case 1:
break;
}
}
然后就是在input的confirm事件绑定的回调函数sendMessages中,调用智能体的api进行交互。
import {useChat} from '../../../stores/chat'
const chat = useChat()
const inputMessage = ref()
const sendMessages = function () {
// 将新输入的问题添加到messages中
chat.constructMessage(inputMessage.value, 1)
inputMessage.value = ''
uni.request({
url: "https://yuanqi.tencent.com/openapi/v1/agent/chat/completions",
method: 'POST',
header: {
'X-Source': 'openapi',
'Content-Type': 'application/json',
'Authorization': 'Bearer pJhqvfJ1xxx',
},
data: {
"assistant_id": "v0c4FkvN",
"user_id": "rodneyxiong",
"stream": false,
"messages": chat.state.messages
},
success: (res) => {
const message = res.data['choices'][0]['message']['content']
// 将智能体返回的消息放到messages中
chat.constructMessage(message, 2)
console.log(res.data);
}
})
}
在上面的代码中,实现了上面说的三个功能。input使用v-model绑定了inputMessage,在点击回车发送消息之后,先调用共享状态的constructMessage方法,将inputMessage放入message,这样输入内容就出现在了main区域,然后将inputMessage置为空,就实现了清空输入框的动作。
然后调用智能体接口,提取返回的消息内容变量,在上面的sendMessages回调函数中,也放入了共享变量的messages中,然后渲染在了main区域。
这里需要按照状态变量messages中的内容结构,替换main中元素使用的变量。
<view class="session" v-for="(message, index) in chat.state.messages" :key="index">
<view class="message-line" :class="message.role === chat.state.receiver ? 'message-line-receive' : 'message-line-send'">
<image v-if="message.role === chat.state.receiver" src="../../../static/chatgpt.jpeg"></image>
<view :class="message.role === chat.state.receiver ? 'receive' : 'send'">{{ message.content[0].text }}</view>
<image v-if="message.role === chat.state.sender" src='../../../static/logo.jpeg'></image>
</view>
</view>
判断角色的变量从之前的sender变成了role,然后就渲染成了下面的样子。
这里就出现问题了,当文字过多的时候,头像就被挤压到变形了,只要将send和receive元素,也就是消息内容元素的width设置为80vw就可以解决了。
最后做一下css细节优化,更换头像、缩放字体以及设置对话框的背景颜色,最后效果如下图。
在很多ChatGPT的web中,header用来实现新建会话、切换ChatGPT版本的功能,在我的设计中,header包含控制aside折叠的按钮和切换模型的下拉框。
这里的header就两个图标和下拉框,第一个图标用来控制aside的展开和缩放,第二个图标用来新建一个会话,用来切断上下文的联系,最右侧是一个下拉框,用来选择会话模型。
<view class="header">
<view class="uni-icons">
<uni-icons type="bars" size="26" @click="collapse"></uni-icons>
<uni-icons type="plusempty" size="26" @click="createSession"></uni-icons>
</view>
<view class='data-select'>
<uni-data-select v-model="value" :localdata="models" :clear="false" @change="change"></uni-data-select>
</view>
</view>
也是定了三个组件,这里需要注意的就是uni-data-select组件,使用localdata绑定数据,v-model用来绑定模型的value值,这里的value值就是之前在共享变量中提到的model_id,这样就能区分出使用了哪个模型。
同时也绑定了change回调函数,当下拉框选项发生变化时,就可以获取切换后模型的value,然后赋值给model_id。
bars图标用来控制aside的折叠。plusempty图标用来新建会话,这个还需要后面完善。
最后就是aside区域,Aside打算做一个会话别表,可以删除会话列表用来清除上下文关联,重新开始一个对话。图标控制展开、隐藏的边栏,我在实现BuildAdmin管理系统的边栏菜单中,详细介绍了其实现原理。这里目前只实现了通过bars图标来控制是否展开。
<view class="aside" v-show="!layout.state.menuCollapse"> </view>
同样,这里的layout.state.menuCollapse也是使用pinia定义的状态变量,header区域的bars图标元素绑定了click回调函数collapse,用来更改menuCollapse。
const collapse = function () {
layout.state.menuCollapse = !layout.state.menuCollapse
}
这里我将aside的width设置为75vw,有一种推拉的感觉。
.aside {
width: 75vw;
transition: width 0.3s ease;
}
最后效果如下:
可以在aside右上角设置一个关闭按钮,或者点击main区域来隐藏aside,而非再点击折叠按钮。
从小程序的整体功能和页面设计上来说,还有很多优化的地方,例如:
总体来说,元器智能体还是比较容易上手的,操作手册中也提供了创建、发布、接入、api调用以及插件使用的详细步骤。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。