Skip to content

Commit

Permalink
Implement Vectorize GA binding changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ndisidore committed Jul 25, 2024
1 parent b5b2329 commit 0f0cdfb
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const unitTests :Workerd.Config = (
bindings = [
( name = "vectorSearch",
wrapped = (
moduleName = "cloudflare-internal:vectorize-api",
moduleName = "cloudflare-internal:vectorize-v2-api",
innerBindings = [(
name = "fetcher",
service = "vector-search-mock"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const test_vector_search_vector_query = {
returnMetadata: "indexed",
});
assert.equal(true, results.count > 0);
/** @type {VectorizeQueryMatches} */
/** @type {VectorizeMatches} */
const expected = {
matches: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const unitTests :Workerd.Config = (
bindings = [
( name = "vector-search",
wrapped = (
moduleName = "cloudflare-internal:vectorize-api",
moduleName = "cloudflare-internal:vectorize-v2-api",
innerBindings = [(
name = "fetcher",
service = "vector-search-mock"
Expand Down
2 changes: 1 addition & 1 deletion src/cloudflare/internal/test/vectorize/vectorize-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

/** @type {Array<VectorizeQueryMatch>} */
/** @type {Array<VectorizeMatch>} */
const exampleVectorMatches = [
{
id: "b0daca4a-ffd8-4865-926b-e24800af2a2d",
Expand Down
187 changes: 187 additions & 0 deletions src/cloudflare/internal/vectorize-v2-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) 2023 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
interface Fetcher {
fetch: typeof fetch;
}

enum Operation {
INDEX_GET = 0,
VECTOR_QUERY = 1,
VECTOR_INSERT = 2,
VECTOR_UPSERT = 3,
VECTOR_GET = 4,
VECTOR_DELETE = 5,
}

class VectorizeImpl implements Vectorize {
public constructor(private readonly fetcher: Fetcher) {}

public async describe(): Promise<VectorizeIndexInfo> {
const res = await this._send(Operation.INDEX_GET, `/info`, {
method: "GET",
});

return await toJson<VectorizeIndexInfo>(res);
}

public async query(
vector: VectorFloatArray | number[],
options: VectorizeQueryOptions<VectorizeMetadataRetrievalLevel>
): Promise<VectorizeMatches> {
const res = await this._send(Operation.VECTOR_QUERY, `/query`, {
method: "POST",
body: JSON.stringify({
...options,
vector: Array.isArray(vector) ? vector : Array.from(vector),
}),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});

return await toJson<VectorizeMatches>(res);
}

public async insert(
vectors: VectorizeVector[]
): Promise<VectorizeAsyncMutation> {
const res = await this._send(Operation.VECTOR_INSERT, `/insert`, {
method: "POST",
body: JSON.stringify({
vectors: vectors.map((vec) => ({
...vec,
values: Array.isArray(vec.values)
? vec.values
: Array.from(vec.values),
})),
}),
headers: {
"content-type": "application/json",
"cf-vector-search-dim-width": String(
vectors.length ? vectors[0]?.values?.length : 0
),
"cf-vector-search-dim-height": String(vectors.length),
accept: "application/json",
},
});

return await toJson<VectorizeAsyncMutation>(res);
}

public async upsert(
vectors: VectorizeVector[]
): Promise<VectorizeAsyncMutation> {
const res = await this._send(Operation.VECTOR_UPSERT, `/upsert`, {
method: "POST",
body: JSON.stringify({
vectors: vectors.map((vec) => ({
...vec,
values: Array.isArray(vec.values)
? vec.values
: Array.from(vec.values),
})),
}),
headers: {
"content-type": "application/json",
"cf-vector-search-dim-width": String(
vectors.length ? vectors[0]?.values?.length : 0
),
"cf-vector-search-dim-height": String(vectors.length),
accept: "application/json",
},
});

return await toJson<VectorizeAsyncMutation>(res);
}

public async getByIds(ids: string[]): Promise<VectorizeVector[]> {
const res = await this._send(Operation.VECTOR_GET, `/getByIds`, {
method: "POST",
body: JSON.stringify({ ids }),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});

return await toJson<VectorizeVector[]>(res);
}

public async deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation> {
const res = await this._send(Operation.VECTOR_DELETE, `/deleteByIds`, {
method: "POST",
body: JSON.stringify({ ids }),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});

return await toJson<VectorizeAsyncMutation>(res);
}

private async _send(
operation: Operation,
endpoint: string,
init: RequestInit
): Promise<Response> {
const res = await this.fetcher.fetch(
`http://vector-search/${endpoint}`, // `http://vector-search` is just a dummy host, the attached fetcher will receive the request
init
);
if (res.status !== 200) {
let err: Error | null = null;

try {
const errResponse = (await res.json()) as VectorizeError;
err = new Error(
`${Operation[operation]}_ERROR${
typeof errResponse.code === "number"
? ` (code = ${errResponse.code})`
: ""
}: ${errResponse.error}`,
{
cause: new Error(errResponse.error),
}
);
} catch {}

if (err) {
throw err;
} else {
throw new Error(
`${Operation[operation]}_ERROR: Status + ${res.status}`,
{
cause: new Error(`Status ${res.status}`),
}
);
}
}

return res;
}
}

const maxBodyLogChars = 1_000;
async function toJson<T = unknown>(response: Response): Promise<T> {
const body = await response.text();
try {
return JSON.parse(body) as T;
} catch {
throw new Error(
`Failed to parse body as JSON, got: ${
body.length > maxBodyLogChars
? `${body.slice(0, maxBodyLogChars)}…`
: body
}`
);
}
}

export function makeBinding(env: { fetcher: Fetcher }): Vectorize {
return new VectorizeImpl(env.fetcher);
}

export default makeBinding;
19 changes: 18 additions & 1 deletion src/cloudflare/internal/vectorize.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ type VectorizeIndexConfig =

/**
* Metadata about an existing index.
*
* This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.
* See {@link VectorizeIndexInfo} for its post-beta equivalent.
*/
interface VectorizeIndexDetails {
/** The unique ID of the index */
Expand All @@ -105,6 +108,20 @@ interface VectorizeIndexDetails {
vectorsCount: number;
}

/**
* Metadata about an existing index.
*/
interface VectorizeIndexInfo {
/** The number of records containing vectors within the index. */
vectorsCount: number;
/** Number of dimensions the index has been configured for. */
dimensions: number;
/** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */
processedUpToDatetime: number;
/** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */
processedUpToMutation: number;
}

/**
* Represents a single vector value set along with its associated metadata.
*/
Expand Down Expand Up @@ -217,7 +234,7 @@ declare abstract class Vectorize {
* Get information about the currently bound index.
* @returns A promise that resolves with information about the current index.
*/
public describe(): Promise<VectorizeIndexDetails>;
public describe(): Promise<VectorizeIndexInfo>;
/**
* Use the provided vector to perform a similarity search across the index.
* @param vector Input vector that will be used to drive the similarity search.
Expand Down
17 changes: 17 additions & 0 deletions types/defines/vectorize.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type VectorizeIndexConfig =

/**
* Metadata about an existing index.
*
* This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.
* See {@link VectorizeIndexInfo} for its post-beta equivalent.
*/
interface VectorizeIndexDetails {
/** The unique ID of the index */
Expand All @@ -97,6 +100,20 @@ interface VectorizeIndexDetails {
vectorsCount: number;
}

/**
* Metadata about an existing index.
*/
interface VectorizeIndexInfo {
/** The number of records containing vectors within the index. */
vectorsCount: number;
/** Number of dimensions the index has been configured for. */
dimensions: number;
/** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */
processedUpToDatetime: number;
/** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */
processedUpToMutation: number;
}

/**
* Represents a single vector value set along with its associated metadata.
*/
Expand Down

0 comments on commit 0f0cdfb

Please sign in to comment.