using AIStudio.Tools.Databases.IndexStore;
using AIStudio.Tools.Databases.VectorStore;
namespace AIStudio.Tools.Services.Indexing;
///
/// The chunks a document had stored before it is read again, so that a chunk whose text did not
/// change keeps its vector.
///
///
/// A vector depends on nothing but its text and the embedding signature, and all vectors of one
/// store share that signature: another one drops the store. So a chunk whose text is byte for byte
/// the text of a chunk the document had stored may take over that vector, instead of going to the
/// embedding provider again. That spares a file which was only saved again, or changed near its
/// end, almost every request.
/// Only the id and the hash of each stored chunk are held in memory, about a hundred bytes per
/// chunk. The vectors stay in the vector store until the run is done with the document, and each
/// batch reads the few it can use, cf. TakeAsync. The new chunks belong to the next generation and
/// so have ids of their own, cf. NextGeneration: writing them never overwrites a vector a later
/// chunk may still need.
///
internal sealed class ReusableVectors
{
private readonly Dictionary chunkIdsByTextHash = new(StringComparer.Ordinal);
private ReusableVectors(IReadOnlyList storedChunks)
{
foreach (var storedChunk in storedChunks)
this.chunkIdsByTextHash.TryAdd(storedChunk.TextHash, storedChunk.ChunkId);
//
// A document stored for the first time, or again after a failure removed all of it,
// starts at generation 0, which gives the ids documents got before generations existed:
//
this.NextGeneration = storedChunks.Count is 0 ? 0 : storedChunks.Max(storedChunk => storedChunk.Generation) + 1;
this.StoredGenerations = storedChunks.Select(storedChunk => storedChunk.Generation).ToHashSet();
}
///
/// The generation of the chunks about to be written, cf. IndexedDocumentIds.CreateChunkIdSource.
///
public int NextGeneration { get; }
///
/// The generations the document has stored chunks of.
///
public IReadOnlyCollection StoredGenerations { get; }
///
/// Reads which chunks a document has stored now, before anything of it is deleted.
///
/// The index store, which knows the chunks of the document and their texts.
/// The data source the document belongs to.
/// The key of the document.
/// The cancellation token.
/// The chunks of the document, which may be none.
public static async Task ReadAsync(IndexStoreClient indexStore, string dataSourceId, string documentKey, CancellationToken token) =>
new(await indexStore.GetStoredChunksAsync(dataSourceId, documentKey, token));
///
/// Finds the stored vector of every chunk of a batch whose text the document had before.
///
/// The vector store, which still holds the vectors of the stored chunks.
/// The vector store of the data source.
/// The size of the vectors in the store.
/// The text hash of each chunk of the batch.
/// The cancellation token.
/// For each chunk, in the same order, its stored vector, or null when it has to be embedded.
public async Task?[]> TakeAsync(VectorStoreClient vectorStore, string collectionName, int vectorSize, IReadOnlyList textHashes, CancellationToken token)
{
var vectors = new IReadOnlyList?[textHashes.Count];
if (this.chunkIdsByTextHash.Count == 0 || vectorSize <= 0)
return vectors;
var sourcePointIds = textHashes.Select(textHash => this.chunkIdsByTextHash.GetValueOrDefault(textHash)).ToArray();
var pointIdsToRead = sourcePointIds.OfType().Distinct(StringComparer.OrdinalIgnoreCase).ToList();
if (pointIdsToRead.Count == 0)
return vectors;
//
// A vector of another size cannot come from this store as it is now, and the store would
// refuse it next to the others:
//
var storedVectors = (await vectorStore.RetrieveVectorsAsync(collectionName, pointIdsToRead, token))
.Where(storedVector => storedVector.Vector.Count == vectorSize)
.ToDictionary(storedVector => storedVector.PointId, storedVector => storedVector.Vector, StringComparer.OrdinalIgnoreCase);
for (var index = 0; index < textHashes.Count; index++)
if (sourcePointIds[index] is { } pointId && storedVectors.TryGetValue(pointId, out var storedVector))
vectors[index] = storedVector;
return vectors;
}
}