資料表支援使用字串(String)和二進位(Binary)格式寫入向量資料。使用字串格式時可讀性更好且問題調查更方便,使用二進位格式時成本更低。
前提條件
已將圖片、視頻和文本等內容經過大模型轉換成向量資料。更多資訊,請參見產生向量。
二進位格式
使用二進位格式儲存向量資料佔用的磁碟空間更小,向量儲存成本更低。在成本敏感且向量維度較大的情境下,推薦通過二進位格式寫入向量資料。
通過Binary格式寫入向量資料時,需要通過Table StoreSDK或者工具將向量轉換為位元據。
重要
二進位格式寫入的向量仍是Float32類型。
向量以二進位格式儲存到資料表中,讀取時也是位元據。如需提升可讀性,建議使用工具類將其轉換為字串格式。
通過Table StoreSDK轉為二進位
說明
自 Java SDK 5.17.6 版本和 Python SDK 6.2.1 版本開始,Table Store支援通過 VectorUtils 工具類進行向量資料的二進位轉換。
import com.alicloud.openservices.tablestore.SyncClient;
import com.alicloud.openservices.tablestore.model.*;
import com.alicloud.openservices.tablestore.model.search.vector.VectorUtils;
import java.util.Random;
import java.util.UUID;
// 產生隨機向量的輔助方法。
private static float[] generateRandomFloats(int length, Random random) {
float[] result = new float[length];
for (int i = 0; i < length; i++) {
result[i] = random.nextFloat();
}
return result;
}
// 批量寫入資料。
private static void batchWriteRow(SyncClient tableStoreClient) throws Exception {
Random random = new Random();
// 寫入 1 千行資料,每 100 行一個批次。
for (int i = 0; i < 10; i++) {
BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
for (int j = 0; j < 100; j++) {
// 使用者的業務資料。
String text = "一段字串,可使用者全文檢索索引。同時該欄位產生 Embedding 向量,寫入到下方 field_vector 欄位中進行向量語義相似性查詢";
// 轉化好的向量,需使用者進行轉換。
float[] vector = generateRandomFloats(1024,random);
RowPutChange rowPutChange = new RowPutChange("TABLE_NAME");
// 設定主鍵。
rowPutChange.setPrimaryKey(PrimaryKeyBuilder.createPrimaryKeyBuilder().addPrimaryKeyColumn("PK_1", PrimaryKeyValue.fromString(UUID.randomUUID().toString())).build());
// 設定屬性列。
rowPutChange.addColumn("field_string", ColumnValue.fromLong(i));
rowPutChange.addColumn("field_long", ColumnValue.fromLong(i * 100 + j));
rowPutChange.addColumn("field_text", ColumnValue.fromString(text));
// 通過binary格式寫入向量資料
rowPutChange.addColumn("field_vector", ColumnValue.fromBinary(VectorUtils.toBytes(vector)));
batchWriteRowRequest.addRowChange(rowPutChange);
}
BatchWriteRowResponse batchWriteRowResponse = tableStoreClient.batchWriteRow(batchWriteRowRequest);
System.out.println("批量寫入是否全部成功:" + batchWriteRowResponse.isAllSucceed());
if (!batchWriteRowResponse.isAllSucceed()) {
for (BatchWriteRowResponse.RowResult rowResult : batchWriteRowResponse.getFailedRows()) {
System.out.println("失敗的行:" + batchWriteRowRequest.getRowChange(rowResult.getTableName(), rowResult.getIndex()).getPrimaryKey());
System.out.println("失敗原因:" + rowResult.getError());
}
}
}
}import time
import tablestore.utils
from tablestore import *
def batch_write_vector(rows_count):
print('Begin prepare data: %d' % rows_count)
batch_write_row_reqs = BatchWriteRowRequest()
put_row_items = []
for i in range(rows_count):
pk = [('PK1', i)]
cols = [('field_string', 'key%03d' % i),
('field_long', i),
('field_text', '一些文本'),
('field_vector', tablestore.utils.VectorUtils.floats_to_bytes([0.1, 0.2, 0.3, 0.4]))]
put_row_item = PutRowItem(Row(pk,cols),Condition(RowExistenceExpectation.IGNORE))
put_row_items.append(put_row_item)
batch_write_row_reqs.add(TableInBatchWriteRowItem(table_name, put_row_items))
client.batch_write_row(batch_write_row_reqs)
print('End prepare data.')
print('Wait for data sync to search index.')
time.sleep(60)通過工具轉為二進位
public class VectorUtils {
private static final ByteOrder order = ByteOrder.LITTLE_ENDIAN;
/**
* 將float[]轉換為二進位
* @param vector 需要轉換的向量
* @return byte 二進位化後的資料
*/
public static byte[] toBytes(float[] vector) {
if (vector == null || vector.length == 0) {
throw new ClientException("vector is null or empty");
}
ByteBuffer buffer = ByteBuffer.allocate(vector.length * 4);
buffer.order(order);
for (float value : vector) {
buffer.putFloat(value);
}
return buffer.array();
}
/**
* 將二進位化後的資料轉換回float[]
* @param bytes 二進位化後的資料
* @return Float 原向量
*/
public static float[] toFloats(byte[] bytes) {
int length = bytes.length / 4;
if (bytes.length % 4 != 0 || length == 0) {
throw new ClientException("bytes length is not multiple of 4(SIZE_OF_FLOAT32) or length is 0");
}
ByteBuffer buffer = ByteBuffer.wrap(bytes);
buffer.order(order);
float[] vector = new float[length];
buffer.asFloatBuffer().get(vector);
return vector;
}
}// Float32ToBytes 將[]float32 轉換為位元組
func Float32ToBytes(vector []float32) ([]byte, error) {
if len(vector) == 0 {
return nil, errors.New("vector is null or empty")
}
data := make([]byte, 4*len(vector))
for i, v := range vector {
binary.LittleEndian.PutUint32(data[i*4:(i+1)*4], math.Float32bits(v))
}
return data, nil
}
// ToFloat32 將位元組轉回[]float32
func ToFloat32(data []byte) ([]float32, error) {
if data == nil {
return nil, errors.New("bytes is null")
}
if len(data)%4 != 0 || len(data) == 0 {
return nil, errors.New("bytes length is not multiple of 4(SIZE_OF_FLOAT32) or length is 0")
}
floats := make([]float32, len(data)/4)
buf := bytes.NewReader(data)
for i := range floats {
if err := binary.Read(buf, binary.LittleEndian, &floats[i]); err != nil {
return nil, err
}
}
return floats, nil
}class VectorUtils:
# 將floats轉換為bytearray
@staticmethod
def floats_to_bytes(floats):
if not isinstance(floats, (list, tuple)) or not all(isinstance(f, float) for f in floats):
raise TypeError("Input must be a list/tuple of floats")
if len(floats) == 0:
raise ValueError("vector is empty")
return bytearray(struct.pack('<' + 'f' * len(floats), *floats))
# 將bytearray轉換回floats
@staticmethod
def bytes_to_floats(byte_data):
if not isinstance(byte_data, bytearray):
raise TypeError("Input must be a bytearray object")
num_floats = len(byte_data) // 4
if len(byte_data) % 4 != 0 or num_floats == 0:
raise ValueError("bytes length is not multiple of 4(SIZE_OF_FLOAT32) or length is 0")
floats = struct.unpack('<' + 'f' * num_floats, byte_data)
return list(floats)驗證轉換的正確性
此處以Java SDK為例驗證向量資料與二進位之間的轉換是否正確。當轉換後的浮點數數組與初始的浮點數數組相同時表示轉換正確。
public class VectorUtilsTest {
public static void main(String[] args) {
float[] vector = new float[] { 1, 2, 3, 4 };
byte[] bytes = VectorUtils.toBytes(vector);
System.out.println("轉換後的位元據:" + Arrays.toString(bytes));
float[] newVector = VectorUtils.toFloats(bytes);
System.out.println("轉換後的浮點數數組:" + Arrays.toString(newVector));
}
}字串格式
使用字串格式儲存向量通常會消耗較多的磁碟空間,但可讀性更高。通過字串格式寫入向量資料時,需要將Float32數組(例如[0.1,0.2,0.3,0.4])轉換為JSON字串進行寫入。
此處以Java SDK為例介紹字串格式的向量資料寫入操作。
// 批量寫入資料。
private static void batchWriteRow(SyncClient tableStoreClient) throws Exception {
// 寫入 1 千行資料,每 100 行一個批次。
for (int i = 0; i < 10; i++) {
BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
for (int j = 0; j < 100; j++) {
// 使用者的業務資料。
String text = "一段字串,可使用者全文檢索索引。同時該欄位產生 Embedding 向量,寫入到下方 field_vector 欄位中進行向量語義相似性查詢";
// 文本轉為向量。
String vector = "[1, 2, 3, 4]";
RowPutChange rowPutChange = new RowPutChange("TABLE_NAME");
// 設定主鍵。
rowPutChange.setPrimaryKey(PrimaryKeyBuilder.createPrimaryKeyBuilder().addPrimaryKeyColumn("PK_1", PrimaryKeyValue.fromString(UUID.randomUUID().toString())).build());
// 設定屬性列。
rowPutChange.addColumn("field_string", ColumnValue.fromLong(i));
rowPutChange.addColumn("field_long", ColumnValue.fromLong(i * 100 + j));
rowPutChange.addColumn("field_text", ColumnValue.fromString(text));
// 向量格式為 float32 數組字串,例如 [1, 5.1, 4.7, 0.08 ]。
rowPutChange.addColumn("field_vector", ColumnValue.fromString(vector));
batchWriteRowRequest.addRowChange(rowPutChange);
}
BatchWriteRowResponse batchWriteRowResponse = tableStoreClient.batchWriteRow(batchWriteRowRequest);
System.out.println("批量寫入是否全部成功:" + batchWriteRowResponse.isAllSucceed());
if (!batchWriteRowResponse.isAllSucceed()) {
for (BatchWriteRowResponse.RowResult rowResult : batchWriteRowResponse.getFailedRows()) {
System.out.println("失敗的行:" + batchWriteRowRequest.getRowChange(rowResult.getTableName(), rowResult.getIndex()).getPrimaryKey());
System.out.println("失敗原因:" + rowResult.getError());
}
}
}
}