Skip to content

Commit 0218f08

Browse files
authored
Merge pull request #26 from appwrite/feat-embeddings
2 parents 1097a72 + bfbf377 commit 0218f08

File tree

10 files changed

+1966
-10
lines changed

10 files changed

+1966
-10
lines changed

package-lock.json

Lines changed: 933 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
},
2222
"license": "MIT",
2323
"dependencies": {
24+
"@huggingface/transformers": "^3.5.2",
2425
"archiver": "^7.0.1",
2526
"chokidar": "^4.0.3",
2627
"eslint": "^9.27.0",
2728
"ignore": "^7.0.4",
2829
"mime-types": "^3.0.1",
30+
"node-appwrite": "17.0.0",
2931
"node-pty": "^1.0.0",
3032
"prettier": "^3.5.3",
31-
"ws": "^8.18.2",
32-
"node-appwrite": "17.0.0"
33+
"ws": "^8.18.2"
3334
},
3435
"devDependencies": {
3536
"@types/archiver": "^6.0.3",

src/adapters/embeddings.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export interface EmbeddingConfig {
2+
[key: string]: any;
3+
}
4+
5+
export abstract class EmbeddingAdapter {
6+
protected config: EmbeddingConfig;
7+
public isInitializing: boolean = false;
8+
9+
constructor(config: EmbeddingConfig = {}) {
10+
this.config = config;
11+
}
12+
13+
/**
14+
* Initialize the embedding model/service
15+
*/
16+
abstract initialize(): Promise<void>;
17+
18+
/**
19+
* Generate embeddings for the given text
20+
* @param text The text to generate embeddings for
21+
* @returns Promise<number[]> The embedding vector
22+
*/
23+
abstract generateEmbedding(text: string): Promise<number[]>;
24+
25+
/**
26+
* Get the name/identifier of this adapter
27+
*/
28+
abstract getName(): string;
29+
30+
/**
31+
* Check if the adapter is initialized and ready to use
32+
*/
33+
abstract isInitialized(): boolean;
34+
35+
/**
36+
* Clean up resources when done
37+
*/
38+
async cleanup(): Promise<void> {
39+
// Default implementation - can be overridden by specific adapters
40+
}
41+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
pipeline,
3+
type FeatureExtractionPipeline,
4+
} from "@huggingface/transformers";
5+
import { EmbeddingAdapter, EmbeddingConfig } from "../embeddings";
6+
7+
export interface HuggingFaceConfig extends EmbeddingConfig {
8+
modelName?: string;
9+
pooling?: "mean" | "none" | "cls" | undefined;
10+
normalize?: boolean;
11+
}
12+
13+
export class HuggingFaceEmbeddingAdapter extends EmbeddingAdapter {
14+
private pipeline: FeatureExtractionPipeline | null = null;
15+
private modelName: string;
16+
private pooling: "mean" | "none" | "cls" | undefined;
17+
private normalize: boolean;
18+
19+
constructor(config: HuggingFaceConfig = {}) {
20+
super(config);
21+
this.modelName = config.modelName || "jinaai/jina-embeddings-v2-base-code";
22+
this.pooling = config.pooling || "mean";
23+
this.normalize = config.normalize !== false; // default to true
24+
}
25+
26+
async initialize(): Promise<void> {
27+
if (!this.pipeline && !this.isInitializing) {
28+
this.isInitializing = true;
29+
console.log(`[HuggingFace] Initializing model: ${this.modelName}...`);
30+
try {
31+
this.pipeline = await pipeline("feature-extraction", this.modelName);
32+
console.log("[HuggingFace] Model initialized successfully");
33+
} catch (error) {
34+
console.error(`[HuggingFace] Error initializing model: ${error}`);
35+
throw error;
36+
}
37+
}
38+
this.isInitializing = false;
39+
}
40+
41+
async generateEmbedding(text: string): Promise<number[]> {
42+
if (!this.pipeline) {
43+
throw new Error(
44+
"HuggingFace adapter not initialized. Call initialize() first.",
45+
);
46+
}
47+
48+
try {
49+
const output = await this.pipeline(text, {
50+
pooling: this.pooling,
51+
normalize: this.normalize,
52+
});
53+
54+
// Convert tensor to array if needed
55+
return Array.from(output.data);
56+
} catch (error) {
57+
console.error(`[HuggingFace] Error generating embedding: ${error}`);
58+
throw error;
59+
}
60+
}
61+
62+
getName(): string {
63+
return `HuggingFace (${this.modelName})`;
64+
}
65+
66+
isInitialized(): boolean {
67+
return this.pipeline !== null;
68+
}
69+
70+
async cleanup(): Promise<void> {
71+
this.pipeline = null;
72+
console.log("[HuggingFace] Adapter cleaned up");
73+
}
74+
}

src/adapters/embeddings/openai.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { EmbeddingAdapter, EmbeddingConfig } from "../embeddings";
2+
3+
export interface OpenAIConfig extends EmbeddingConfig {
4+
apiKey: string;
5+
model?: string;
6+
baseURL?: string;
7+
}
8+
9+
export class OpenAIEmbeddingAdapter extends EmbeddingAdapter {
10+
private apiKey: string;
11+
private model: string;
12+
private baseURL: string;
13+
private initialized: boolean = false;
14+
15+
constructor(config: OpenAIConfig) {
16+
super(config);
17+
if (!config.apiKey) {
18+
throw new Error("OpenAI API key is required");
19+
}
20+
this.apiKey = config.apiKey;
21+
this.model = config.model || "text-embedding-3-small";
22+
this.baseURL = config.baseURL || "https://api.openai.com/v1";
23+
}
24+
25+
async initialize(): Promise<void> {
26+
console.log(`[OpenAI] Initializing with model: ${this.model}...`);
27+
this.isInitializing = true;
28+
if (!this.apiKey.startsWith("sk-")) {
29+
console.warn(
30+
"[OpenAI] API key doesn't start with 'sk-', this might cause issues",
31+
);
32+
}
33+
this.initialized = true;
34+
this.isInitializing = false;
35+
console.log("[OpenAI] Adapter initialized successfully");
36+
}
37+
38+
async generateEmbedding(text: string): Promise<number[]> {
39+
if (!this.initialized) {
40+
throw new Error(
41+
"OpenAI adapter not initialized. Call initialize() first.",
42+
);
43+
}
44+
45+
if (this.isInitializing) {
46+
throw new Error(
47+
"OpenAI adapter is initializing. Please wait a moment and try again.",
48+
);
49+
}
50+
51+
try {
52+
const response = await fetch(`${this.baseURL}/embeddings`, {
53+
method: "POST",
54+
headers: {
55+
Authorization: `Bearer ${this.apiKey}`,
56+
"Content-Type": "application/json",
57+
},
58+
body: JSON.stringify({
59+
input: text,
60+
model: this.model,
61+
}),
62+
});
63+
64+
if (!response.ok) {
65+
throw new Error(
66+
`OpenAI API error: ${response.status} ${response.statusText}`,
67+
);
68+
}
69+
70+
const data = (await response.json()) as any;
71+
72+
if (!data.data || !data.data[0] || !data.data[0].embedding) {
73+
throw new Error("Invalid response format from OpenAI API");
74+
}
75+
76+
return data.data[0].embedding;
77+
} catch (error) {
78+
console.error(`[OpenAI] Error generating embedding: ${error}`);
79+
throw error;
80+
}
81+
}
82+
83+
getName(): string {
84+
return `OpenAI (${this.model})`;
85+
}
86+
87+
isInitialized(): boolean {
88+
return this.initialized;
89+
}
90+
91+
async cleanup(): Promise<void> {
92+
this.initialized = false;
93+
console.log("[OpenAI] Adapter cleaned up");
94+
}
95+
}

src/adapters/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export { EmbeddingAdapter } from "./embeddings";
2+
export type { EmbeddingConfig } from "./embeddings";
3+
export { HuggingFaceEmbeddingAdapter } from "./embeddings/huggingface";
4+
export type { HuggingFaceConfig } from "./embeddings/huggingface";
5+
export { OpenAIEmbeddingAdapter } from "./embeddings/openai";
6+
export type { OpenAIConfig } from "./embeddings/openai";

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
export { Appwrite } from "./services/appwrite";
12
export { Code } from "./services/code";
3+
export { Embeddings } from "./services/embeddings";
24
export { Filesystem } from "./services/filesystem";
35
export { Git } from "./services/git";
46
export { System } from "./services/system";
57
export { Terminal } from "./services/terminal";
68
export { Synapse } from "./synapse";
7-
export { Appwrite } from "./services/appwrite";

0 commit comments

Comments
 (0)