Skip to content

Commit 15a52c6

Browse files
feat(core): add SimpleVectorStoreService for management (#1127)
* feat: Add SimpleVectorStoreService for managing vector data and schema initialization * format --------- Co-authored-by: shown <yuluo08290126@gmail.com>
1 parent 694bf36 commit 15a52c6

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.cloud.ai.service;
17+
18+
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
19+
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
20+
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
21+
import com.alibaba.cloud.ai.dbconnector.DbAccessor;
22+
import com.alibaba.cloud.ai.dbconnector.DbConfig;
23+
import com.alibaba.cloud.ai.dbconnector.bo.ColumnInfoBO;
24+
import com.alibaba.cloud.ai.dbconnector.bo.DbQueryParameter;
25+
import com.alibaba.cloud.ai.dbconnector.bo.ForeignKeyInfoBO;
26+
import com.alibaba.cloud.ai.dbconnector.bo.TableInfoBO;
27+
import com.alibaba.cloud.ai.request.DeleteRequest;
28+
import com.alibaba.cloud.ai.request.SchemaInitRequest;
29+
import com.google.gson.Gson;
30+
import org.springframework.ai.document.Document;
31+
import org.springframework.ai.document.MetadataMode;
32+
import org.springframework.ai.vectorstore.SearchRequest;
33+
import org.springframework.ai.vectorstore.SimpleVectorStore;
34+
import org.springframework.ai.vectorstore.filter.Filter;
35+
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
36+
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.beans.factory.annotation.Value;
38+
import org.springframework.stereotype.Service;
39+
40+
import java.util.*;
41+
import java.util.stream.Collectors;
42+
43+
@Service
44+
public class SimpleVectorStoreService {
45+
46+
private final SimpleVectorStore vectorStore;
47+
48+
private final Gson gson;
49+
50+
private final DbAccessor dbAccessor;
51+
52+
private final DbConfig dbConfig;
53+
54+
@Autowired
55+
public SimpleVectorStoreService(@Value("${spring.ai.dashscope.api-key:default_api_key}") String apiKey, Gson gson,
56+
DbAccessor dbAccessor, DbConfig dbConfig) {
57+
this.gson = gson;
58+
this.dbAccessor = dbAccessor;
59+
this.dbConfig = dbConfig;
60+
61+
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
62+
DashScopeEmbeddingModel dashScopeEmbeddingModel = new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED,
63+
DashScopeEmbeddingOptions.builder().withModel("text-embedding-v2").build());
64+
this.vectorStore = SimpleVectorStore.builder(dashScopeEmbeddingModel).build();
65+
}
66+
67+
/**
68+
* 初始化数据库 schema 到向量库
69+
* @param schemaInitRequest schema 初始化请求
70+
* @throws Exception 如果发生错误
71+
*/
72+
public Boolean schema(SchemaInitRequest schemaInitRequest) throws Exception {
73+
DbConfig dbConfig = schemaInitRequest.getDbConfig();
74+
DbQueryParameter dqp = DbQueryParameter.from(dbConfig)
75+
.setSchema(dbConfig.getSchema())
76+
.setTables(schemaInitRequest.getTables());
77+
78+
DeleteRequest deleteRequest = new DeleteRequest();
79+
deleteRequest.setVectorType("column");
80+
deleteDocuments(deleteRequest);
81+
deleteRequest.setVectorType("table");
82+
deleteDocuments(deleteRequest);
83+
84+
List<ForeignKeyInfoBO> foreignKeyInfoBOS = dbAccessor.showForeignKeys(dbConfig, dqp);
85+
Map<String, List<String>> foreignKeyMap = buildForeignKeyMap(foreignKeyInfoBOS);
86+
87+
List<TableInfoBO> tableInfoBOS = dbAccessor.fetchTables(dbConfig, dqp);
88+
for (TableInfoBO tableInfoBO : tableInfoBOS) {
89+
processTable(tableInfoBO, dqp, dbConfig);
90+
}
91+
92+
List<Document> columnDocuments = tableInfoBOS.stream().flatMap(table -> {
93+
try {
94+
return dbAccessor.showColumns(dbConfig, dqp).stream().map(column -> convertToDocument(table, column));
95+
}
96+
catch (Exception e) {
97+
throw new RuntimeException(e);
98+
}
99+
}).collect(Collectors.toList());
100+
101+
vectorStore.add(columnDocuments);
102+
103+
List<Document> tableDocuments = tableInfoBOS.stream()
104+
.map(this::convertTableToDocument)
105+
.collect(Collectors.toList());
106+
107+
vectorStore.add(tableDocuments);
108+
109+
return true;
110+
}
111+
112+
private void processTable(TableInfoBO tableInfoBO, DbQueryParameter dqp, DbConfig dbConfig) throws Exception {
113+
dqp.setTable(tableInfoBO.getName());
114+
List<ColumnInfoBO> columnInfoBOS = dbAccessor.showColumns(dbConfig, dqp);
115+
for (ColumnInfoBO columnInfoBO : columnInfoBOS) {
116+
dqp.setColumn(columnInfoBO.getName());
117+
List<String> sampleColumn = dbAccessor.sampleColumn(dbConfig, dqp);
118+
sampleColumn = Optional.ofNullable(sampleColumn)
119+
.orElse(new ArrayList<>())
120+
.stream()
121+
.filter(Objects::nonNull)
122+
.distinct()
123+
.limit(3)
124+
.filter(s -> s.length() <= 100)
125+
.toList();
126+
127+
columnInfoBO.setTableName(tableInfoBO.getName());
128+
columnInfoBO.setSamples(gson.toJson(sampleColumn));
129+
}
130+
131+
ColumnInfoBO primaryColumnDO = columnInfoBOS.stream()
132+
.filter(ColumnInfoBO::isPrimary)
133+
.findFirst()
134+
.orElse(new ColumnInfoBO());
135+
136+
tableInfoBO.setPrimaryKey(primaryColumnDO.getName());
137+
tableInfoBO.setForeignKey(String.join("、", buildForeignKeyList(tableInfoBO.getName())));
138+
}
139+
140+
public Document convertToDocument(TableInfoBO tableInfoBO, ColumnInfoBO columnInfoBO) {
141+
String text = Optional.ofNullable(columnInfoBO.getDescription()).orElse(columnInfoBO.getName());
142+
Map<String, Object> metadata = Map.of("name", columnInfoBO.getName(), "tableName", tableInfoBO.getName(),
143+
"description", Optional.ofNullable(columnInfoBO.getDescription()).orElse(""), "type",
144+
columnInfoBO.getType(), "primary", columnInfoBO.isPrimary(), "notnull", columnInfoBO.isNotnull(),
145+
"vectorType", "column");
146+
if (columnInfoBO.getSamples() != null) {
147+
metadata.put("samples", columnInfoBO.getSamples());
148+
}
149+
return new Document(columnInfoBO.getName(), text, metadata);
150+
}
151+
152+
private List<String> buildForeignKeyList(String tableName) {
153+
return new ArrayList<>();
154+
}
155+
156+
public Document convertTableToDocument(TableInfoBO tableInfoBO) {
157+
String text = Optional.ofNullable(tableInfoBO.getDescription()).orElse(tableInfoBO.getName());
158+
Map<String, Object> metadata = Map.of("schema", Optional.ofNullable(tableInfoBO.getSchema()).orElse(""), "name",
159+
tableInfoBO.getName(), "description", Optional.ofNullable(tableInfoBO.getDescription()).orElse(""),
160+
"foreignKey", tableInfoBO.getForeignKey(), "primaryKey", tableInfoBO.getPrimaryKey(), "vectorType",
161+
"table");
162+
return new Document(tableInfoBO.getName(), text, metadata);
163+
}
164+
165+
private Map<String, List<String>> buildForeignKeyMap(List<ForeignKeyInfoBO> foreignKeyInfoBOS) {
166+
Map<String, List<String>> foreignKeyMap = new HashMap<>();
167+
for (ForeignKeyInfoBO fk : foreignKeyInfoBOS) {
168+
String key = fk.getTable() + "." + fk.getColumn() + "=" + fk.getReferencedTable() + "."
169+
+ fk.getReferencedColumn();
170+
171+
foreignKeyMap.computeIfAbsent(fk.getTable(), k -> new ArrayList<>()).add(key);
172+
foreignKeyMap.computeIfAbsent(fk.getReferencedTable(), k -> new ArrayList<>()).add(key);
173+
}
174+
return foreignKeyMap;
175+
}
176+
177+
/**
178+
* 删除指定条件的向量数据
179+
* @param deleteRequest 删除请求
180+
* @return 是否删除成功
181+
*/
182+
public Boolean deleteDocuments(DeleteRequest deleteRequest) throws Exception {
183+
try {
184+
if (deleteRequest.getId() != null && !deleteRequest.getId().isEmpty()) {
185+
vectorStore.delete(Arrays.asList("comment_count"));
186+
}
187+
else if (deleteRequest.getVectorType() != null && !deleteRequest.getVectorType().isEmpty()) {
188+
FilterExpressionBuilder b = new FilterExpressionBuilder();
189+
Filter.Expression expression = b.eq("vectorType", "column").build();
190+
List<Document> documents = vectorStore.similaritySearch(
191+
SearchRequest.builder().topK(Integer.MAX_VALUE).filterExpression(expression).build());
192+
vectorStore.delete(documents.stream().map(Document::getId).toList());
193+
}
194+
else {
195+
throw new IllegalArgumentException("Either id or vectorType must be specified.");
196+
}
197+
return true;
198+
}
199+
catch (Exception e) {
200+
throw new Exception("Failed to delete collection data by filterExpression: " + e.getMessage(), e);
201+
}
202+
}
203+
204+
}

0 commit comments

Comments
 (0)