Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test the Embeddings Constants the embedding model 'models/gemini-embedding-2' should match the snapshot 1`] = `
{
"enabled": false,
"id": "77bb8ff3-a6b0-460b-bcaa-00631a907852",
"modelName": "models/gemini-embedding-2",
"modelProvider": "gemini",
"numberOfDimensions": 768,
}
`;
71 changes: 71 additions & 0 deletions api-specifications/src/embeddings/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test the embeddings module The embeddings module matches the snapshot 1`] = `
{
"Constants": {
"EEmbeddingModelProvider": {
"GEMINI": "gemini",
},
"EmbeddingServiceIds": [
"77bb8ff3-a6b0-460b-bcaa-00631a907852",
],
"EmbeddingServices": [
{
"enabled": false,
"id": "77bb8ff3-a6b0-460b-bcaa-00631a907852",
"modelName": "models/gemini-embedding-2",
"modelProvider": "gemini",
"numberOfDimensions": 768,
},
],
"MAX_EMBEDDING_MODEL_ID_LENGTH": 36,
"MAX_NUMBER_OF_DIMENSIONS": 100000,
"MIN_NUMBER_OF_DIMENSIONS": 1,
"MODEL_NAME_MAX_LENGTH": 256,
},
"Schemas": {
"EmbeddingModel": {
"$id": "/components/schemas/EmbeddingModelSchema",
"additionalProperties": false,
"properties": {
"enabled": {
"description": "Whether the embedding model is enabled and can be used to trigger an embedding process.",
"type": "boolean",
},
"id": {
"description": "The UUID of the embedding model.",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"type": "string",
},
"modelName": {
"description": "The provider specific name of the embedding model.",
"maxLength": 256,
"pattern": "\\S",
"type": "string",
},
"modelProvider": {
"description": "The provider of the embedding model.",
"enum": [
"gemini",
],
"type": "string",
},
"numberOfDimensions": {
"description": "The number of dimensions of the vectors produced by the embedding model.",
"maximum": 100000,
"minimum": 1,
"type": "integer",
},
},
"required": [
"id",
"modelProvider",
"modelName",
"numberOfDimensions",
"enabled",
],
"type": "object",
},
},
}
`;
34 changes: 34 additions & 0 deletions api-specifications/src/embeddings/constants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import EmbeddingsConstants from "./constants";

describe("Test the Embeddings Constants", () => {
describe("Test the EmbeddingModels registry", () => {
test("every embedding model should have a unique id in the entire registry", () => {
// GIVEN the registry of embedding models
const givenEmbeddingModels = EmbeddingsConstants.EmbeddingServices;

// WHEN the unique ids are extracted from the registry
const actualUniqueIds = new Set(givenEmbeddingModels.map((model) => model.id));

// THEN expect as many unique ids as there are registered embedding models
expect(actualUniqueIds.size).toBe(givenEmbeddingModels.length);
});

test("the EmbeddingServiceIds should have one id for every registered embedding model", () => {
// GIVEN the registry of embedding models
// WHEN the supported embedding model ids are derived from the registry
// THEN expect the number of ids to be the same as the number of registered embedding models
expect(EmbeddingsConstants.EmbeddingServiceIds).toHaveLength(EmbeddingsConstants.EmbeddingServices.length);
});
});

test.each(EmbeddingsConstants.EmbeddingServices.map((model) => [model.modelName, model] as const))(
"the embedding model '%s' should match the snapshot",
(_modelName, givenEmbeddingModel) => {
// GIVEN an embedding model from the registry

// WHEN the embedding model is inspected
// THEN expect the embedding model to match the snapshot
expect(givenEmbeddingModel).toMatchSnapshot();
}
);
});
41 changes: 41 additions & 0 deletions api-specifications/src/embeddings/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace EmbeddingsConstants {
export enum EEmbeddingModelProvider {
GEMINI = "gemini",
}

export interface IEmbeddingService {
id: string;
modelProvider: EEmbeddingModelProvider;
modelName: string;
numberOfDimensions: number;
enabled: boolean;
}

export const EmbeddingServices: IEmbeddingService[] = [
{
id: `77bb8ff3-a6b0-460b-bcaa-00631a907852`,
modelProvider: EEmbeddingModelProvider.GEMINI,
modelName: "models/gemini-embedding-2",
numberOfDimensions: 768,
enabled: false,
},
];

/**
* The identifiers of the embedding models that are supported.
* These are the only values accepted as the `embeddingServiceId` when triggering an embedding process.
*/
export const EmbeddingServiceIds = Array.from(new Set(EmbeddingServices.map((model) => model.id)));

// Since the embedding model id is a UUID, it is 36 characters long.
export const MAX_EMBEDDING_MODEL_ID_LENGTH = 36;

// The maximum length of the human/provider readable name of an embedding model (e.g. "models/gemini-embedding-2").
export const MODEL_NAME_MAX_LENGTH = 256;

// The bounds for the number of dimensions produced by an embedding model.
export const MIN_NUMBER_OF_DIMENSIONS = 1;
export const MAX_NUMBER_OF_DIMENSIONS = 100000;
}

export default EmbeddingsConstants;
25 changes: 25 additions & 0 deletions api-specifications/src/embeddings/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
describe("Test the embeddings module", () => {
test("The embeddings module can be required via the index", async () => {
// GIVEN the module
// WHEN the module is required via the index
expect(async () => await import("./")).not.toThrow(); // We check that it doesn't throw an error instead of simply letting it fail on import because we want an easier error message
const embeddingsModule = await import("./");

// THEN Check if the Constants are defined in it
expect(embeddingsModule.default.Constants.EmbeddingServices).toBeDefined();
expect(embeddingsModule.default.Constants.EmbeddingServiceIds).toBeDefined();
expect(embeddingsModule.default.Constants.MAX_EMBEDDING_MODEL_ID_LENGTH).toBeDefined();
});

test("The embeddings module matches the snapshot", () => {
// GIVEN the module
// WHEN the module is required via the index
expect(async () => {
// THEN Check if the module can be required without error
const embeddingsModule = await import("./");
expect(embeddingsModule.default).toMatchSnapshot();
}).not.toThrowError();
});
});

export {};
28 changes: 28 additions & 0 deletions api-specifications/src/embeddings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// we need to disable the eslint rule here because this is a top level export
/* eslint-disable @typescript-eslint/no-unused-vars */

import EmbeddingsConstants from "./constants";
import EmbeddingModelSchema from "./schema";

/**
* This file should be imported in the following way

import EmbeddingsAPISpecs from "api-specifications/embeddings";

* And the general pattern is EmbeddingsAPISpecs.{Constants/Schemas}.{...}
*
* NOTE: The API contract for /models/{modelId}/embedding-process-states (schemas, types, enums)
* lives under modelInfo — see ModelInfoAPISpecs.ModelInfo.EmbeddingProcessStates.
* Only the embedding-model constants remain here.
*/

namespace EmbeddingsSchemas {
export const EmbeddingModel = EmbeddingModelSchema;
}

namespace EmbeddingsAPISpecs {
export import Constants = EmbeddingsConstants;
export import Schemas = EmbeddingsSchemas;
}

export default EmbeddingsAPISpecs;
3 changes: 3 additions & 0 deletions api-specifications/src/embeddings/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"main": "index.js"
}
145 changes: 145 additions & 0 deletions api-specifications/src/embeddings/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { randomUUID } from "crypto";
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { getTestString } from "_test_utilities/specialCharacters";
import {
testBooleanField,
testEnumField,
testNonEmptyStringField,
testSchemaWithAdditionalProperties,
testSchemaWithValidObject,
testUUIDField,
testValidSchema,
} from "_test_utilities/stdSchemaTests";
import { assertCaseForProperty, CaseType, constructSchemaError } from "_test_utilities/assertCaseForProperty";
import EmbeddingsAPISpecs from "./index";
import EmbeddingsConstants from "./constants";

describe("Test the EmbeddingModel Schema", () => {
// GIVEN the EmbeddingsAPISpecs.Schemas.EmbeddingModel schema
// WHEN the schema is validated
// THEN expect the schema to be valid
testValidSchema("EmbeddingsAPISpecs.Schemas.EmbeddingModel", EmbeddingsAPISpecs.Schemas.EmbeddingModel);

describe("Validate JSON against the EmbeddingModel Schema", () => {
// GIVEN a valid EmbeddingModel object
const givenValidEmbeddingModel: EmbeddingsConstants.IEmbeddingService = {
id: randomUUID(),
modelProvider: EmbeddingsConstants.EEmbeddingModelProvider.GEMINI,
modelName: getTestString(EmbeddingsConstants.MODEL_NAME_MAX_LENGTH),
numberOfDimensions: 768,
enabled: true,
};

// WHEN the object is validated
// THEN expect the object to validate successfully
testSchemaWithValidObject(
"EmbeddingsAPISpecs.Schemas.EmbeddingModel",
EmbeddingsAPISpecs.Schemas.EmbeddingModel,
givenValidEmbeddingModel
);

// AND WHEN the object has additional properties
// THEN expect the object to not validate
testSchemaWithAdditionalProperties(
"EmbeddingsAPISpecs.Schemas.EmbeddingModel",
EmbeddingsAPISpecs.Schemas.EmbeddingModel,
givenValidEmbeddingModel
);
});

describe("validate EmbeddingsAPISpecs.Schemas.EmbeddingModel fields", () => {
describe("Test validation of 'id'", () => {
testUUIDField("id", EmbeddingsAPISpecs.Schemas.EmbeddingModel);
});

describe("Test validation of 'modelProvider'", () => {
testEnumField(
"modelProvider",
EmbeddingsAPISpecs.Schemas.EmbeddingModel,
Object.values(EmbeddingsConstants.EEmbeddingModelProvider)
);
});

describe("Test validation of 'modelName'", () => {
testNonEmptyStringField(
"modelName",
EmbeddingsConstants.MODEL_NAME_MAX_LENGTH,
EmbeddingsAPISpecs.Schemas.EmbeddingModel
);
});

describe("Test validation of 'numberOfDimensions'", () => {
test.each([
[
CaseType.Failure,
"undefined",
undefined,
constructSchemaError("", "required", "must have required property 'numberOfDimensions'"),
],
[CaseType.Failure, "null", null, constructSchemaError("/numberOfDimensions", "type", "must be integer")],
[CaseType.Failure, "a string", "768", constructSchemaError("/numberOfDimensions", "type", "must be integer")],
[CaseType.Failure, "a float", 768.5, constructSchemaError("/numberOfDimensions", "type", "must be integer")],
[
CaseType.Failure,
"less than the minimum",
EmbeddingsConstants.MIN_NUMBER_OF_DIMENSIONS - 1,
constructSchemaError(
"/numberOfDimensions",
"minimum",
`must be >= ${EmbeddingsConstants.MIN_NUMBER_OF_DIMENSIONS}`
),
],
[
CaseType.Failure,
"greater than the maximum",
EmbeddingsConstants.MAX_NUMBER_OF_DIMENSIONS + 1,
constructSchemaError(
"/numberOfDimensions",
"maximum",
`must be <= ${EmbeddingsConstants.MAX_NUMBER_OF_DIMENSIONS}`
),
],
[CaseType.Success, "the minimum", EmbeddingsConstants.MIN_NUMBER_OF_DIMENSIONS, undefined],
[CaseType.Success, "the maximum", EmbeddingsConstants.MAX_NUMBER_OF_DIMENSIONS, undefined],
[CaseType.Success, "a valid value", 768, undefined],
])("(%s) Validate 'numberOfDimensions' when it is %s", (caseType, _description, givenValue, failureMessages) => {
// GIVEN an object with the given value
const givenObject = { numberOfDimensions: givenValue };
// THEN expect the object to validate accordingly
assertCaseForProperty(
"numberOfDimensions",
givenObject,
EmbeddingsAPISpecs.Schemas.EmbeddingModel,
caseType,
failureMessages
);
});
});

describe("Test validation of 'enabled'", () => {
testBooleanField("enabled", EmbeddingsAPISpecs.Schemas.EmbeddingModel);
});
});

describe("Validate the registered EmbeddingModels against the EmbeddingModel Schema", () => {
// GIVEN an AJV instance compiled with the EmbeddingModel schema
const givenAjv = new Ajv({ validateSchema: true, allErrors: true, strict: true });
addFormats(givenAjv);
const givenValidateFunction = givenAjv.compile(EmbeddingsAPISpecs.Schemas.EmbeddingModel);

// AND every embedding model registered in the constants registry
test.each(EmbeddingsConstants.EmbeddingServices.map((model) => [model.modelName, model] as const))(
"the registered embedding model '%s' validates against the EmbeddingModel schema",
(_modelName, givenEmbeddingModel) => {
// WHEN the registered embedding model is validated against the schema
const actualIsValid = givenValidateFunction(givenEmbeddingModel);

// THEN expect the embedding model to validate successfully
expect(actualIsValid).toBe(true);
// AND no errors to be present
expect(givenValidateFunction.errors).toBeNull();
}
);
});
});
Loading
Loading