-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5be5ffc
commit ddfa01f
Showing
14 changed files
with
705 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
Oops, something went wrong.