大家好,我是鱼皮。
几个月前,我自己开发过一个 AI 文档总结助手应用。给大家简单演示一下,首先我上传了一个文档,定义1 1 等于 3
:
然后把文档喂给 AI 文档总结助手,再向它提问,然后 AI 就回答出了我们文档中的内容,如下图:
是不是很有趣哈哈~ 所以 AI 并不是完全可信的哦,要看原始数据是否可信!
当时参考网上的教程,做这个花了挺长一段时间,成就感满满。
但没想到,这段时间,AI 以一日千里的速度发展,现在开发一个同样的 AI 文档总结助手,大家猜猜要多久?
答案是:只要 5 分钟!!!
没错,使用腾讯云新出的向量数据库产品能力,哪怕没有 AI 知识,也能够轻轻松松开发出 AI 应用。
下面就给大家分享一下 AI 总结助手开发教程。
AI 总结助手开发教程
实现原理
动手写代码前,我们要先了解整个 AI 总结助手的实现原理,为什么 AI 能够回答出我们指定的文档内容呢?
那肯定要把文档数据先 “喂” 给 AI 呀,可是怎么 “喂” 呢?
因为 AI 的 “脑容量” 很小,接受的输入有限,所以我们要对文档进行拆分,比如将一篇万字长文拆为 20 个 500 字的小段落。
然后,我们要将这些小段落存储到数据库中,当用户向 AI 提问时,AI 要先从数据库中查询出和用户问题相似度最高的小段落,然后对这些小段路进行总结,再给用户回答。
为什么要给 AI 提供一个数据库呢?我举个通俗易懂的比喻:我们考试时如果脑袋记不住所有考点,是不是带本书进考场,然后根据考题从书中查出答案,再整理一下写到考卷上就行了呢?
那么问题就来了,怎么根据用户的问题从数据库中查出最相似的段落呢?文本段落应该以什么格式存储到数据库中呢?
这就需要用到一种特殊的数据库技术 —— 向量数据库。
什么是向量数据库?
向量数据库就是一个专门存储和处理向量数据的数据库,它内置了相似内容检索功能,可以找到和某个向量最相似的数据。
相比于传统关系型数据库(比如 MySQL)的模糊查询(like)而言,向量检索会更灵活。如今,得益于 AI 的发展,向量数据库作为 AI 的 “小抄”,也变得越来越流行。
那什么是向量数据呢?
其实就是用一些算法将文本、图片、音视频等内容统一转换成数值向量。
比如:“中午吃饺子”,经过转换后得到的向量数据可能是:[0.8, 0.6, 0.9, 0.4, ...];而 “晚上写代码”,经过转换后得到的向量数据可能是:[0.1, 0.2, 0.3, 0.4, ...]
如果用户要从向量数据库搜索内容,那么也可以把搜索关键字转换为类似的向量数据,然后计算两个向量之间的距离来判断相似度即可。
比如用户问:“中午吃什么?”,经过转换后得到的向量数据可能是:[0.8, 0.6, 0.7, 0.3, ...]。
显然,这个向量数据会和 “中午吃饺子” 的向量数据更接近,所以会优先搜出 “中午吃饺子”。
采用不同的向量转换算法、或者不同的相似度计算方法,得到的向量值和计算结果可能也是不同的。
具体实现流程
了解向量数据库后,我们可以整理出 AI 应用的具体实现流程:
1)将自己已有的知识库文档进行段落拆分;
2)利用算法(Embedding)将文档数据转换为向量
3)将向量存储到向量数据库中
4)将用户发送的问题通过算法(Embedding)转换为向量
5)根据用户问题向量,在向量数据库进行相似性查询
6)将检索到的最相似结果作为背景知识(上下文),转换为 prompt 并发送给 AI 大模型,从而获得响应结果
流程图如下:
此前,鱼皮就是按照这个流程自己开发实现的 AI 总结助手。但是要自己对文档进行拆分、还要通过某种算法转换成向量数据,想想都麻烦!
有没有更简单的实现方式呢?
流程简化
还真有!
最近腾讯云发布了最新版本的向量数据库,不仅提供了数据写入和检索的自动向量化功能(embedding),还支持文本自动拆分和一键上传,可以直接将文章转为拆分好的向量写入到向量数据库,大大简化了开发流程。而且简单易用,新人也能快速上手。
如果用腾讯云的向量数据库,上面的实现流程就简化为 3 个核心步骤:
1)将文档上传到腾讯云向量数据库(自动拆分并转为向量存储)
2)将用户发送的问题传入到向量数据库进行相似性查询
3)将检索到的最相似结果作为背景知识(上下文),转换为 prompt 并发送给 AI 大模型,从而获得响应结果
那么流程图就简化为下面这样了:
流程确定后,就可以开始写代码了。
AI 总结助手开发
从上述流程中我们会发现,想要实现 AI 总结助手,向量数据库和 AI 大模型是两大不可或缺的角色。
此处,我们选用上面介绍的腾讯云向量数据库,并且搭配与腾讯云联合的百川 AI 大模型,能最大程度地节约开发成本。
1、免费领取资源
首先要免费领取腾讯云向量数据库 百川 AI 大模型的使用权。
进入向量数据库产品主页,点击免费领取:
地址:https://cloud.tencent.com/act/pro/agi
2)在弹框中填入自己的手机号即可领取成功,等待初始化就好了
3)等初始化成功后,进入腾讯云向量数据库的实例列表,当状态显示为运行中时,开启外网访问:
4)开启外网时,需要填写允许访问的白名单,那由于此处仅为测试,我就直接设置为全网可访问了:
5)访问百川 AI 大模型,点击领取百川的免费调用次数(几百万 tokens 呢)
地址:https://platform.baichuan-ai.com/console/recharge
6)进入 API Key 管理页面,新建一个属于自己的 API Key,后面就可以调用百川大模型的 AI 能力了。
资源领取好了,我们就可以愉快地使用资源啦。
正式开发前,我们要先阅读腾讯云向量数据库官方的 API 开发文档,以最新的文档为准去写代码。
地址:https://cloud.tencent.com/document/product/1709/97768
2、引入依赖
我们以 Java Maven 项目开发为例,先引入程序所需的依赖,比如腾讯云向量数据库、HTTP 调用客户端等。
代码如下:
代码语言:javascript复制<dependencies>
<dependency>
<groupId>com.tencent.tcvectordb</groupId>
<artifactId>vectordatabase-sdk-java</artifactId>
<version>1.0.4-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/libs/vectordatabase-sdk-java-1.0.4.jar</systemPath>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.8</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
</dependencies>
3、连接向量数据库
参考腾讯云官方提供的 Java SDK Demo 代码,首先和向量数据库建立连接:
代码语言:javascript复制import com.tencent.tcvectordb.client.VectorDBClient;
import com.tencent.tcvectordb.model.param.database.ConnectParam;
import com.tencent.tcvectordb.model.param.enums.ReadConsistencyEnum;
public class VDBClientFactory {
public static VectorDBClient createClient() {
ConnectParam param = getConnectParam();
return new VectorDBClient(param, ReadConsistencyEnum.EVENTUAL_CONSISTENCY);
}
private static ConnectParam getConnectParam() {
return ConnectParam.newBuilder()
.withUrl("url")
.withUsername("username")
.withKey("key")
.withTimeout(30)
.build();
}
}
上述代码中的 url 可以直接在腾讯云向量数据库的实例列表中看到,直接选中复制即可:
对于 username 和 key 参数,则需要点进实例,选择密钥管理来获取:
4、上传文档到向量数据库
上传文档到数据库前,肯定要先初始化数据库表。
让我们新建一个AISearchExample
类,在这个类中编写调用向量数据库的方法,创建数据库和数据表,代码如下:
public class AISearchExample {
private static final String DB_NAME = "ai_test_db";
private static final String COLLECTION_NAME = "ai_test_collection";
private static void initDatabase(VectorDBClient client) {
System.out.println("init database..");
try {
client.dropAIDatabase(DB_NAME);
} catch (VectorDBException e) {
// ignore
}
client.createAIDatabase(DB_NAME);
}
private static void initCollection(VectorDBClient client) {
System.out.println("init collection..");
Database database = client.database(DB_NAME);
CreateAICollectionParam param = CreateAICollectionParam.newBuilder().withName(COLLECTION_NAME).build();
database.createAICollection(param);
}
}
然后编写一个writeKnowledgeByFile
方法,把本地的文档上传到向量数据库里:
代码语言:javascript复制可以直接上传文档,不需要再操心文档段落的拆分、如何转换为数值向量等复杂的问题,大幅节约时间
public class AISearchExample {
...
private static void writeKnowledgeByFile(VectorDBClient client) throws Exception {
AICollection collection = client.database(DB_NAME).describeAICollection(COLLECTION_NAME);
for (String f : Objects.requireNonNull(new File("doc").list())) {
String filePath = "doc/" f;
System.out.println("upload file " filePath);
collection.upload(filePath, Collections.emptyMap());
}
System.out.println("all file uploaded.");
System.out.println("文件上传后,向量数据库会进行解析和Embedding,请耐心等待10-20秒后可以开始进行知识检索。");
}
}
这里我把自己写的学习路线文章都上传到向量数据库:
编写好上述的初始化方法后,依次调用即可:
代码语言:javascript复制public class AISearchExample {
...
private static void initKnowledge(VectorDBClient client) throws Exception {
initDatabase(client);
initCollection(client);
writeKnowledgeByFile(client);
}
}
5、搜索文档
将文档都上传到向量数据库后,就可以实现数据的检索了。
在AISearchExample
类中,再添加一个搜索方法searchKnowledge
,代码如下:
public class AISearchExample {
...
private static String searchKnowledge(String question, VectorDBClient client) {
// 访问指定的表
AICollection collection = client.database(DB_NAME).describeAICollection(COLLECTION_NAME);
// 构造搜索条件
SearchByContentsParam param = SearchByContentsParam.newBuilder().withContent(question).build();
StringBuilder allKnowledge = new StringBuilder();
List<Document> results = collection.search(param);
int index = 1;
// 获取搜索结果
for (Document document : results) {
ChunkInfo chunk = (ChunkInfo)document.getObject("chunk");
allKnowledge.append(chunk.getText()).append(" ");
}
return allKnowledge.toString();
}
}
运行代码,测试下效果,成功检索出了指定回答:
效果不错,我再试试,问问 “中午吃什么”:
What?这什么啊,你不要睁着眼睛乱说好不好!
这里我们发现了一个关键问题:当我搜索一个完全不存在的问题时,向量数据库仍然会给出结果,然而这并不是我想要的。如果没有相关的内容,直接不返回结果好像更符合预期。
好在腾讯云向量数据库返回了检索相似度,可以根据这个值设定一个阈值,从而进行过滤。
修改一下代码,过滤相似度低于 0.8 的文档:
代码语言:javascript复制public class AISearchExample {
...
/**
* 文档相关性的阈值
*/
private static final Double THRESHOLD = 0.8;
private static String searchKnowledge(String question, VectorDBClient client) {
AICollection collection = client.database(DB_NAME).describeAICollection(COLLECTION_NAME);
SearchByContentsParam param = SearchByContentsParam.newBuilder().withContent(question).build();
StringBuilder allKnowledge = new StringBuilder();
List<Document> results = collection.search(param);
int index = 1;
for (Document document : results) {
Double score = document.getScore();
if (ObjectUtils.isEmpty(score) || score < THRESHOLD) {
continue;
}
ChunkInfo chunk = (ChunkInfo)document.getObject("chunk");
allKnowledge.append(chunk.getText()).append(" ");
}
return allKnowledge.toString();
}
}
再测试下效果,这次正常了:
至此,我们使用向量数据库实现了文档数据的存储和查询。“小抄” 已经准备好,接下来就把它给 AI 吧!
6、使用百川大模型
可以通过 OKHttp 库向百川大模型发送请求,实现 AI 的问答能力。
代码看起来比较长,但其实只需要按照百川要求的参数格式来设置请求头、封装 prompt,最后发起调用并获取返回结果就好了,代码如下:
代码语言:javascript复制public class BaiChuanLLM {
private static final String URL = "https://api.baichuan-ai.com/v1/chat";
/**
* 这里的ak和sk可以从百川官网获取,文章中已经演示过了,直接替换掉即可
*/
private static final String API_KEY = "ak";
private static final String SECRET_KEY = "sk";
private static final ObjectMapper MAPPER = new ObjectMapper();
private static volatile OkHttpClient HTTP_CLIENT;
public static String ask(String question, String knowledge) {
try {
String prompt = getPrompt(question, knowledge);
return llmRequest(prompt);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static String llmRequest(String prompt) throws IOException {
String requestData = getBaiChuanRequest(prompt);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = calculateMd5(SECRET_KEY requestData timestamp);
Headers headers = getHeaders(timestamp, signature);
RequestBody body = RequestBody.create(requestData, MediaType.parse("application/json; charset=utf-8"));
Request request = (new Request.Builder()).url(URL).headers(headers).post(body).build();
try (Response response = getHttpClient().newCall(request).execute()) {
JsonNode node = null;
if (response.body() != null) {
node = MAPPER.readTree(response.body().string());
}
if (node != null) {
return node.get("data").get("messages").get(0).get("content").asText();
}
return null;
}
}
private static Headers getHeaders(String timestamp, String signature) {
return (new Headers.Builder())
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " API_KEY)
.add("X-BC-Request-Id", "RequestId-1001")
.add("X-BC-Timestamp", timestamp)
.add("X-BC-Signature", signature)
.add("X-BC-Sign-Algo", "MD5")
.build();
}
public static String calculateMd5(String inputString) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(inputString.getBytes());
byte[] digest = md.digest();
StringBuilder buffer = new StringBuilder();
for (byte b : digest) {
buffer.append(String.format("x", b & 0xff));
}
return buffer.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
private static String getBaiChuanRequest(String prompt) throws JsonProcessingException {
ObjectNode data = JsonNodeFactory.instance.objectNode();
data.put("model", "Baichuan2-53B");
ObjectNode node = JsonNodeFactory.instance.objectNode();
node.put("role", "user");
node.put("content", prompt);
data.put("messages", JsonNodeFactory.instance.arrayNode().add(node));
return new ObjectMapper().writeValueAsString(data);
}
private static String getPrompt(String question, String knowledge) throws JsonProcessingException {
JsonNodeFactory factory = JsonNodeFactory.instance;
ObjectNode obj = factory.objectNode();
obj.put("请回答问题", question);
obj.put("背景知识如下", knowledge);
return new ObjectMapper().writeValueAsString(obj);
}
synchronized private static OkHttpClient getHttpClient() {
if (HTTP_CLIENT == null) {
HTTP_CLIENT = (new OkHttpClient.Builder())
.connectTimeout(2L, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES))
.build();
}
return HTTP_CLIENT;
}
}
上面的代码大家也不用记,直接复制到自己的程序中就行。
最后,我们在刚刚创建的AISearchExample
类中编写一个 main 方法,以实现调用。
示例代码如下:
代码语言:javascript复制public static void main(String[] args) throws Exception {
VectorDBClient client = createClient();
initKnowledge(client);
Scanner scanner = new Scanner(System.in);
System.out.print("请输入您的问题(exit退出):");
String inputString = scanner.nextLine();
while (!"exit".equalsIgnoreCase(inputString)) {
if (!inputString.trim().isEmpty()) {
String result = searchKnowledge(inputString, client);
if (StringUtils.isBlank(result)) {
System.out.println("未找到相关内容");
}else {
System.out.println(result);
}
String llmResult = BaiChuanLLM.ask(inputString, result);
System.out.println("---->LLM回答结果:");
System.out.println(llmResult);
}
System.out.println("nn");
System.out.print("请输入您的问题(exit退出):");
inputString = scanner.nextLine();
}
}
注意:
1)由于版本持续更新迭代,请以官方最新的 SDK Demo 为准
地址:https://cloud.tencent.com/document/product/1709/97768
2)相比于 Java,Python 调用会更加简单,只需要不到 100 行代码就能搞定
最终效果
查询向量数据库中已有的信息时,向量数据库成功查询到了文档段落:
百川 AI 大模型基于上面的文档段落,给出了更清晰的回答:
很好,一个 AI 总结助手就开发完成啦!
总结
通过开发上述应用,大家应该也能发现,腾讯云向量数据库确实是立了大功,它不仅帮我完成了文件分片上传,还通过内置的 Embedding 功能帮我完成了文档到数值向量的转换,并内置了搜索功能,大幅降低了开发成本!
不过对于我来说,最重要的还是它送了 400 万 token 的百川大模型以及免费一个月的腾讯云向量数据库。
点击下面的链接,或者文章下方的阅读原文,即可免费领取腾讯云向量数据库 百川大模型 400 万 tokens 调用额度。推荐大家用它来快速制作自己的学习笔记助手、八股文背诵助手等,提高学习工作效率~
领取地址:https://cloud.tencent.com/act/pro/agi 也可以点击下方阅读原文领取~