|
| 1 | +import OpenAI from 'openai'; |
| 2 | +import type { VectorStore } from 'openai/resources/beta/vector-stores/vector-stores.mjs'; |
| 3 | +import type { Uploadable } from 'openai/uploads.mjs'; |
| 4 | + |
| 5 | +export class ContextService { |
| 6 | + private readonly storeIdMap = new Map<string, string>(); |
| 7 | + private readonly storeMap = new Map<string, Context>(); |
| 8 | + |
| 9 | + constructor(private readonly client: OpenAI) {} |
| 10 | + |
| 11 | + private saveContext(store: VectorStore) { |
| 12 | + const context = new Context(this.client, store); |
| 13 | + this.storeIdMap.set(context.name, context.id); |
| 14 | + this.storeMap.set(context.name, context); |
| 15 | + return context; |
| 16 | + } |
| 17 | + |
| 18 | + async getOrCreate(name: string, id?: string): Promise<Context> { |
| 19 | + const storeId = id || this.storeIdMap.get(name); |
| 20 | + if (storeId) { |
| 21 | + const context = this.storeMap.get(name); |
| 22 | + if (context) return context; |
| 23 | + |
| 24 | + try { |
| 25 | + const store = await this.client.beta.vectorStores.retrieve(storeId); |
| 26 | + if (store.name === name) { |
| 27 | + return this.saveContext(store); |
| 28 | + } |
| 29 | + } catch {} |
| 30 | + } |
| 31 | + const store = await this.client.beta.vectorStores.create({ |
| 32 | + name, |
| 33 | + expires_after: { anchor: 'last_active_at', days: 30 }, |
| 34 | + }); |
| 35 | + return this.saveContext(store); |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +export class Context { |
| 40 | + constructor( |
| 41 | + private readonly client: OpenAI, |
| 42 | + public readonly store: VectorStore |
| 43 | + ) {} |
| 44 | + |
| 45 | + get id() { |
| 46 | + return this.store.id; |
| 47 | + } |
| 48 | + |
| 49 | + get name() { |
| 50 | + return this.store.name; |
| 51 | + } |
| 52 | + |
| 53 | + private get files() { |
| 54 | + return this.client.beta.vectorStores.files; |
| 55 | + } |
| 56 | + |
| 57 | + async list() { |
| 58 | + const lists = await this.files.list(this.id); |
| 59 | + const list = await Array.fromAsync(lists.iterPages()); |
| 60 | + return list.flatMap(f => f.data); |
| 61 | + } |
| 62 | + |
| 63 | + async add(content: Uploadable, signal?: AbortSignal) { |
| 64 | + const file = await this.files.uploadAndPoll(this.id, content, { |
| 65 | + signal, |
| 66 | + }); |
| 67 | + return file.id; |
| 68 | + } |
| 69 | + |
| 70 | + async remove(fileId: string) { |
| 71 | + const ret = await this.files.del(this.id, fileId); |
| 72 | + return ret.deleted; |
| 73 | + } |
| 74 | +} |
0 commit comments