Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
ogzhanolguncu committed Apr 30, 2024
1 parent 5be5ffc commit ddfa01f
Show file tree
Hide file tree
Showing 14 changed files with 705 additions and 1 deletion.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
"vitest": "latest"
},
"dependencies": {
"@upstash/vector": "^1.0.7"
"@langchain/community": "^0.0.50",
"@langchain/core": "^0.1.58",
"@langchain/openai": "^0.0.28",
"@upstash/sdk": "0.0.19-alpha",
"ai": "^3.0.35"
}
}
42 changes: 42 additions & 0 deletions src/clients/ratelimiter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Ratelimit } from "@upstash/sdk";

import type { Redis, Upstash } from "@upstash/sdk";
import { InternalUpstashError } from "../../error/internal";

const DEFAULT_RATELIMITER_NAME = "@upstash-rag-chat-ratelimit";
const MAX_ALLOWED_CHAT_REQUEST = 10;

export class RatelimiterClientConstructor {
private redisClient?: Redis;
private ratelimiterClient?: Ratelimit;
private sdkClient: Upstash;

constructor(sdkClient: Upstash, redisClient?: Redis) {
this.redisClient = redisClient;
this.sdkClient = sdkClient;
}

public async getRatelimiterClient(): Promise<Ratelimit | undefined> {
if (!this.ratelimiterClient) {
try {
await this.initializeRatelimiterClient();
} catch (error) {
console.error("Failed to initialize Ratelimiter client:", error);
return undefined;
}
}
return this.ratelimiterClient;
}

private initializeRatelimiterClient = async () => {
if (!this.redisClient)
throw new InternalUpstashError("Redis client is in missing in initializeRatelimiterClient!");

const ratelimiter = await this.sdkClient.newRatelimitClient(this.redisClient, {
limiter: Ratelimit.tokenBucket(MAX_ALLOWED_CHAT_REQUEST, "1d", MAX_ALLOWED_CHAT_REQUEST),
prefix: DEFAULT_RATELIMITER_NAME,
});

this.ratelimiterClient = ratelimiter;
};
}
65 changes: 65 additions & 0 deletions src/clients/redis/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Upstash } from "@upstash/sdk";
import { describe, expect, test } from "bun:test";
import { DEFAULT_REDIS_CONFIG, DEFAULT_REDIS_DB_NAME, RedisClientConstructor } from ".";

const upstashSDK = new Upstash({
email: process.env.UPSTASH_EMAIL!,
token: process.env.UPSTASH_TOKEN!,
});

describe("Redis Client", () => {
test(
"Initialize client without db name",
async () => {
const constructor = new RedisClientConstructor({
sdkClient: upstashSDK,
});
const redisClient = await constructor.getRedisClient();

expect(redisClient).toBeTruthy();

await upstashSDK.deleteRedisDatabase(DEFAULT_REDIS_DB_NAME);
},
{ timeout: 10_000 }
);

test(
"Initialize client with db name",
async () => {
const constructor = new RedisClientConstructor({
sdkClient: upstashSDK,
redisDbNameOrInstance: "test-name",
});
const redisClient = await constructor.getRedisClient();

expect(redisClient).toBeTruthy();

await upstashSDK.deleteRedisDatabase("test-name");
},
{ timeout: 10_000 }
);

test(
"Initialize client with existing instance",
async () => {
const dbName = DEFAULT_REDIS_CONFIG.name + "suffix";
const redisInstance = await upstashSDK.createRedisDatabase({
...DEFAULT_REDIS_CONFIG,
name: dbName,
});
const existingRedisClient = await upstashSDK.newRedisClient(redisInstance.database_name);

const constructor = new RedisClientConstructor({
sdkClient: upstashSDK,
redisDbNameOrInstance: existingRedisClient,
});
const redisClient = await constructor.getRedisClient();

expect(redisClient).toBeTruthy();

await upstashSDK.deleteRedisDatabase(dbName);
},
{ timeout: 10_000 }
);
});
84 changes: 84 additions & 0 deletions src/clients/redis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { CreateCommandPayload, Upstash } from "@upstash/sdk";

import { Redis } from "@upstash/sdk";
import type { PreferredRegions } from "../../types";

export const DEFAULT_REDIS_DB_NAME = "upstash-rag-chat-redis";

export const DEFAULT_REDIS_CONFIG: CreateCommandPayload = {
name: DEFAULT_REDIS_DB_NAME,
tls: true,
region: "us-east-1",
eviction: false,
};

type Config = {
sdkClient: Upstash;
redisDbNameOrInstance?: string | Redis;
preferredRegion?: PreferredRegions;
};

export class RedisClientConstructor {
private redisDbNameOrInstance?: string | Redis;
private preferredRegion?: PreferredRegions;
private sdkClient: Upstash;
private redisClient?: Redis;

constructor({ sdkClient, preferredRegion, redisDbNameOrInstance }: Config) {
this.redisDbNameOrInstance = redisDbNameOrInstance;
this.sdkClient = sdkClient;
this.preferredRegion = preferredRegion ?? "us-east-1";
}

public async getRedisClient(): Promise<Redis | undefined> {
if (!this.redisClient) {
try {
await this.initializeRedisClient();
} catch (error) {
console.error("Failed to initialize Redis client:", error);
return undefined;
}
}
return this.redisClient;
}

private initializeRedisClient = async () => {
const { redisDbNameOrInstance } = this;

// Direct Redis instance provided
if (redisDbNameOrInstance instanceof Redis) {
this.redisClient = redisDbNameOrInstance;
return;
}

// Redis name provided
if (typeof redisDbNameOrInstance === "string") {
await this.createRedisClientByName(redisDbNameOrInstance);
return;
}

// No specific Redis information provided, using default configuration
await this.createRedisClientByDefaultConfig();
};

private createRedisClientByName = async (redisDbName: string) => {
try {
const redis = await this.sdkClient.getRedisDatabase(redisDbName);
this.redisClient = await this.sdkClient.newRedisClient(redis.database_name);
} catch {
await this.createRedisClientByDefaultConfig(redisDbName);
}
};

private createRedisClientByDefaultConfig = async (redisDbName?: string) => {
const redisDatabase = await this.sdkClient.getOrCreateRedisDatabase({
...DEFAULT_REDIS_CONFIG,
name: redisDbName ?? DEFAULT_REDIS_CONFIG.name,
region: this.preferredRegion ?? DEFAULT_REDIS_CONFIG.region,
});

if (redisDatabase?.database_name) {
this.redisClient = await this.sdkClient.newRedisClient(redisDatabase.database_name);
}
};
}
65 changes: 65 additions & 0 deletions src/clients/vector/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Upstash } from "@upstash/sdk";
import { describe, expect, test } from "bun:test";
import { DEFAULT_VECTOR_DB_NAME, VectorClientConstructor, DEFAULT_VECTOR_CONFIG } from ".";

const upstashSDK = new Upstash({
email: process.env.UPSTASH_EMAIL!,
token: process.env.UPSTASH_TOKEN!,
});

describe("Redis Client", () => {
test(
"Initialize client without index name",
async () => {
const constructor = new VectorClientConstructor({
sdkClient: upstashSDK,
});
const vectorClient = await constructor.getVectorClient();

expect(vectorClient).toBeTruthy();

await upstashSDK.deleteVectorIndex(DEFAULT_VECTOR_DB_NAME);
},
{ timeout: 10_000 }
);

test(
"Initialize client with db name",
async () => {
const constructor = new VectorClientConstructor({
sdkClient: upstashSDK,
indexNameOrInstance: "test-name",
});
const redisClient = await constructor.getVectorClient();

expect(redisClient).toBeTruthy();

await upstashSDK.deleteVectorIndex("test-name");
},
{ timeout: 10_000 }
);

test(
"Initialize client with existing instance",
async () => {
const indexName = DEFAULT_VECTOR_CONFIG.name + "suffix";
const vectorInstance = await upstashSDK.createVectorIndex({
...DEFAULT_VECTOR_CONFIG,
name: indexName,
});
const existingVectorClient = await upstashSDK.newVectorClient(vectorInstance.name);

const constructor = new VectorClientConstructor({
sdkClient: upstashSDK,
indexNameOrInstance: existingVectorClient,
});
const vectorClient = await constructor.getVectorClient();

expect(vectorClient).toBeTruthy();

await upstashSDK.deleteVectorIndex(indexName);
},
{ timeout: 10_000 }
);
});
89 changes: 89 additions & 0 deletions src/clients/vector/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { CreateIndexPayload, Upstash } from "@upstash/sdk";
import { Index } from "@upstash/sdk";

import type { PreferredRegions } from "../../types";
import { InternalUpstashError } from "../../error/internal";

export const DEFAULT_VECTOR_DB_NAME = "upstash-rag-chat-vector";

export const DEFAULT_VECTOR_CONFIG: CreateIndexPayload = {
name: DEFAULT_VECTOR_DB_NAME,
similarity_function: "EUCLIDEAN",
embedding_model: "MXBAI_EMBED_LARGE_V1",
region: "us-east-1",
type: "payg",
};

type Config = {
sdkClient: Upstash;
indexNameOrInstance?: string | Index;
preferredRegion?: PreferredRegions;
};

export class VectorClientConstructor {
private indexNameOrInstance?: string | Index;
private preferredRegion?: PreferredRegions;
private sdkClient: Upstash;
private vectorClient?: Index;

constructor({ sdkClient, preferredRegion, indexNameOrInstance: indexNameOrInstance }: Config) {
this.indexNameOrInstance = indexNameOrInstance;
this.sdkClient = sdkClient;
this.preferredRegion = preferredRegion ?? "us-east-1";
}

public async getVectorClient(): Promise<Index | undefined> {
if (!this.vectorClient) {
try {
await this.initializeVectorClient();
} catch (error) {
console.error("Failed to initialize Vector client:", error);
return undefined;
}
}
return this.vectorClient;
}

private initializeVectorClient = async () => {
const { indexNameOrInstance } = this;

// Direct Vector instance provided
if (indexNameOrInstance instanceof Index) {
this.vectorClient = indexNameOrInstance;
return;
}

// Vector name provided
if (typeof indexNameOrInstance === "string") {
await this.createVectorClientByName(indexNameOrInstance);
return;
}

// No specific Vector information provided, using default configuration
await this.createVectorClientByDefaultConfig();
};

private createVectorClientByName = async (indexName: string) => {
try {
const index = await this.sdkClient.getVectorIndexByName(indexName);
if (!index) throw new InternalUpstashError("Index is missing!");

this.vectorClient = await this.sdkClient.newVectorClient(index.name);
} catch {
await this.createVectorClientByDefaultConfig(indexName);
}
};

private createVectorClientByDefaultConfig = async (indexName?: string) => {
const index = await this.sdkClient.getOrCreateIndex({
...DEFAULT_VECTOR_CONFIG,
name: indexName ?? DEFAULT_VECTOR_CONFIG.name,
region: this.preferredRegion ?? DEFAULT_VECTOR_CONFIG.region,
});

if (index?.name) {
const client = await this.sdkClient.newVectorClient(index.name);
this.vectorClient = client;
}
};
}
6 changes: 6 additions & 0 deletions src/error/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class InternalUpstashError extends Error {
constructor(message: string) {
super(message);
this.name = "InternalUpstashError";
}
}
6 changes: 6 additions & 0 deletions src/error/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class UpstashModelError extends Error {
constructor(message: string) {
super(message);
this.name = "UpstashModelError";
}
}
Loading

0 comments on commit ddfa01f

Please sign in to comment.