@hazeljs/ai 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +497 -0
- package/dist/ai-enhanced.service.d.ts +108 -0
- package/dist/ai-enhanced.service.d.ts.map +1 -0
- package/dist/ai-enhanced.service.js +345 -0
- package/dist/ai-enhanced.service.test.d.ts +2 -0
- package/dist/ai-enhanced.service.test.d.ts.map +1 -0
- package/dist/ai-enhanced.service.test.js +501 -0
- package/dist/ai-enhanced.test.d.ts +2 -0
- package/dist/ai-enhanced.test.d.ts.map +1 -0
- package/dist/ai-enhanced.test.js +587 -0
- package/dist/ai-enhanced.types.d.ts +277 -0
- package/dist/ai-enhanced.types.d.ts.map +1 -0
- package/dist/ai-enhanced.types.js +2 -0
- package/dist/ai.decorator.d.ts +4 -0
- package/dist/ai.decorator.d.ts.map +1 -0
- package/dist/ai.decorator.js +57 -0
- package/dist/ai.decorator.test.d.ts +2 -0
- package/dist/ai.decorator.test.d.ts.map +1 -0
- package/dist/ai.decorator.test.js +189 -0
- package/dist/ai.module.d.ts +12 -0
- package/dist/ai.module.d.ts.map +1 -0
- package/dist/ai.module.js +44 -0
- package/dist/ai.module.test.d.ts +2 -0
- package/dist/ai.module.test.d.ts.map +1 -0
- package/dist/ai.module.test.js +23 -0
- package/dist/ai.service.d.ts +11 -0
- package/dist/ai.service.d.ts.map +1 -0
- package/dist/ai.service.js +266 -0
- package/dist/ai.service.test.d.ts +2 -0
- package/dist/ai.service.test.d.ts.map +1 -0
- package/dist/ai.service.test.js +222 -0
- package/dist/ai.types.d.ts +30 -0
- package/dist/ai.types.d.ts.map +1 -0
- package/dist/ai.types.js +2 -0
- package/dist/context/context.manager.d.ts +69 -0
- package/dist/context/context.manager.d.ts.map +1 -0
- package/dist/context/context.manager.js +168 -0
- package/dist/context/context.manager.test.d.ts +2 -0
- package/dist/context/context.manager.test.d.ts.map +1 -0
- package/dist/context/context.manager.test.js +180 -0
- package/dist/decorators/ai-function.decorator.d.ts +42 -0
- package/dist/decorators/ai-function.decorator.d.ts.map +1 -0
- package/dist/decorators/ai-function.decorator.js +80 -0
- package/dist/decorators/ai-validate.decorator.d.ts +46 -0
- package/dist/decorators/ai-validate.decorator.d.ts.map +1 -0
- package/dist/decorators/ai-validate.decorator.js +83 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/prompts/task.prompt.d.ts +12 -0
- package/dist/prompts/task.prompt.d.ts.map +1 -0
- package/dist/prompts/task.prompt.js +12 -0
- package/dist/providers/anthropic.provider.d.ts +48 -0
- package/dist/providers/anthropic.provider.d.ts.map +1 -0
- package/dist/providers/anthropic.provider.js +194 -0
- package/dist/providers/anthropic.provider.test.d.ts +2 -0
- package/dist/providers/anthropic.provider.test.d.ts.map +1 -0
- package/dist/providers/anthropic.provider.test.js +222 -0
- package/dist/providers/cohere.provider.d.ts +57 -0
- package/dist/providers/cohere.provider.d.ts.map +1 -0
- package/dist/providers/cohere.provider.js +230 -0
- package/dist/providers/cohere.provider.test.d.ts +2 -0
- package/dist/providers/cohere.provider.test.d.ts.map +1 -0
- package/dist/providers/cohere.provider.test.js +267 -0
- package/dist/providers/gemini.provider.d.ts +45 -0
- package/dist/providers/gemini.provider.d.ts.map +1 -0
- package/dist/providers/gemini.provider.js +180 -0
- package/dist/providers/gemini.provider.test.d.ts +2 -0
- package/dist/providers/gemini.provider.test.d.ts.map +1 -0
- package/dist/providers/gemini.provider.test.js +219 -0
- package/dist/providers/ollama.provider.d.ts +45 -0
- package/dist/providers/ollama.provider.d.ts.map +1 -0
- package/dist/providers/ollama.provider.js +232 -0
- package/dist/providers/ollama.provider.test.d.ts +2 -0
- package/dist/providers/ollama.provider.test.d.ts.map +1 -0
- package/dist/providers/ollama.provider.test.js +267 -0
- package/dist/providers/openai.provider.d.ts +57 -0
- package/dist/providers/openai.provider.d.ts.map +1 -0
- package/dist/providers/openai.provider.js +320 -0
- package/dist/providers/openai.provider.test.d.ts +2 -0
- package/dist/providers/openai.provider.test.d.ts.map +1 -0
- package/dist/providers/openai.provider.test.js +364 -0
- package/dist/tracking/token.tracker.d.ts +72 -0
- package/dist/tracking/token.tracker.d.ts.map +1 -0
- package/dist/tracking/token.tracker.js +222 -0
- package/dist/tracking/token.tracker.test.d.ts +2 -0
- package/dist/tracking/token.tracker.test.d.ts.map +1 -0
- package/dist/tracking/token.tracker.test.js +272 -0
- package/dist/vector/vector.service.d.ts +50 -0
- package/dist/vector/vector.service.d.ts.map +1 -0
- package/dist/vector/vector.service.js +163 -0
- package/package.json +60 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('@hazeljs/core', () => ({
|
|
4
|
+
__esModule: true,
|
|
5
|
+
Service: () => () => undefined,
|
|
6
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
7
|
+
}));
|
|
8
|
+
const token_tracker_1 = require("./token.tracker");
|
|
9
|
+
const NOW = Date.now();
|
|
10
|
+
function makeUsage(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
promptTokens: 100,
|
|
13
|
+
completionTokens: 50,
|
|
14
|
+
totalTokens: 150,
|
|
15
|
+
timestamp: NOW,
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
describe('TokenTracker', () => {
|
|
20
|
+
let tracker;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tracker = new token_tracker_1.TokenTracker();
|
|
23
|
+
});
|
|
24
|
+
describe('constructor', () => {
|
|
25
|
+
it('creates with defaults', () => {
|
|
26
|
+
expect(tracker).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
it('accepts custom config', () => {
|
|
29
|
+
const t = new token_tracker_1.TokenTracker({
|
|
30
|
+
maxTokensPerRequest: 1000,
|
|
31
|
+
maxTokensPerDay: 5000,
|
|
32
|
+
maxTokensPerMonth: 50000,
|
|
33
|
+
});
|
|
34
|
+
expect(t).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
it('uses partial config with defaults for missing fields', () => {
|
|
37
|
+
const t = new token_tracker_1.TokenTracker({ maxTokensPerRequest: 500 });
|
|
38
|
+
expect(t).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('track()', () => {
|
|
42
|
+
it('tracks global usage', () => {
|
|
43
|
+
tracker.track(makeUsage());
|
|
44
|
+
const exported = tracker.exportData();
|
|
45
|
+
expect(exported).toHaveLength(1);
|
|
46
|
+
expect(exported[0].totalTokens).toBe(150);
|
|
47
|
+
});
|
|
48
|
+
it('tracks per-user usage when userId provided', () => {
|
|
49
|
+
tracker.track(makeUsage({ userId: 'user1' }));
|
|
50
|
+
const userData = tracker.exportData('user1');
|
|
51
|
+
expect(userData).toHaveLength(1);
|
|
52
|
+
});
|
|
53
|
+
it('appends to existing user history', () => {
|
|
54
|
+
tracker.track(makeUsage({ userId: 'user1' }));
|
|
55
|
+
tracker.track(makeUsage({ totalTokens: 200, userId: 'user1' }));
|
|
56
|
+
expect(tracker.exportData('user1')).toHaveLength(2);
|
|
57
|
+
});
|
|
58
|
+
it('calculates cost when model is provided and cost is missing', () => {
|
|
59
|
+
tracker.track({ promptTokens: 1000, completionTokens: 500, totalTokens: 1500, timestamp: NOW }, 'gpt-4-turbo-preview');
|
|
60
|
+
const exported = tracker.exportData();
|
|
61
|
+
expect(exported[0].cost).toBeGreaterThan(0);
|
|
62
|
+
});
|
|
63
|
+
it('does not recalculate if cost is already set', () => {
|
|
64
|
+
tracker.track(makeUsage({ cost: 0.999 }), 'gpt-4');
|
|
65
|
+
expect(tracker.exportData()[0].cost).toBe(0.999);
|
|
66
|
+
});
|
|
67
|
+
it('does not set cost when no model provided and cost is missing', () => {
|
|
68
|
+
tracker.track(makeUsage());
|
|
69
|
+
expect(tracker.exportData()[0].cost).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('checkLimits()', () => {
|
|
73
|
+
it('allows request within all limits', async () => {
|
|
74
|
+
const result = await tracker.checkLimits('user1', 100);
|
|
75
|
+
expect(result.allowed).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it('blocks request exceeding per-request limit', async () => {
|
|
78
|
+
const t = new token_tracker_1.TokenTracker({ maxTokensPerRequest: 10 });
|
|
79
|
+
const result = await t.checkLimits(undefined, 100);
|
|
80
|
+
expect(result.allowed).toBe(false);
|
|
81
|
+
expect(result.reason).toContain('Request exceeds token limit');
|
|
82
|
+
});
|
|
83
|
+
it('returns allowed when no userId and requestTokens within limit', async () => {
|
|
84
|
+
const result = await tracker.checkLimits(undefined, 50);
|
|
85
|
+
expect(result.allowed).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it('returns allowed with no arguments', async () => {
|
|
88
|
+
const result = await tracker.checkLimits();
|
|
89
|
+
expect(result.allowed).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('blocks when daily limit exceeded', async () => {
|
|
92
|
+
const t = new token_tracker_1.TokenTracker({ maxTokensPerDay: 100, maxTokensPerMonth: 1000000 });
|
|
93
|
+
t.track({
|
|
94
|
+
promptTokens: 60,
|
|
95
|
+
completionTokens: 50,
|
|
96
|
+
totalTokens: 110,
|
|
97
|
+
timestamp: NOW,
|
|
98
|
+
userId: 'user1',
|
|
99
|
+
});
|
|
100
|
+
const result = await t.checkLimits('user1');
|
|
101
|
+
expect(result.allowed).toBe(false);
|
|
102
|
+
expect(result.reason).toBe('Daily token limit exceeded');
|
|
103
|
+
});
|
|
104
|
+
it('blocks when monthly limit exceeded', async () => {
|
|
105
|
+
const t = new token_tracker_1.TokenTracker({ maxTokensPerDay: 1000000, maxTokensPerMonth: 100 });
|
|
106
|
+
t.track({
|
|
107
|
+
promptTokens: 60,
|
|
108
|
+
completionTokens: 50,
|
|
109
|
+
totalTokens: 110,
|
|
110
|
+
timestamp: NOW,
|
|
111
|
+
userId: 'user1',
|
|
112
|
+
});
|
|
113
|
+
const result = await t.checkLimits('user1');
|
|
114
|
+
expect(result.allowed).toBe(false);
|
|
115
|
+
expect(result.reason).toBe('Monthly token limit exceeded');
|
|
116
|
+
});
|
|
117
|
+
it('returns usage info in response for user with history', async () => {
|
|
118
|
+
tracker.track(makeUsage({ userId: 'user1', totalTokens: 15 }));
|
|
119
|
+
const result = await tracker.checkLimits('user1');
|
|
120
|
+
expect(result.usage).toBeDefined();
|
|
121
|
+
expect(result.usage?.today).toBe(15);
|
|
122
|
+
expect(result.usage?.limit.daily).toBeDefined();
|
|
123
|
+
});
|
|
124
|
+
it('returns allowed true for user with no history', async () => {
|
|
125
|
+
const result = await tracker.checkLimits('newUser');
|
|
126
|
+
expect(result.allowed).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('calculateCost()', () => {
|
|
130
|
+
it('calculates cost for gpt-4', () => {
|
|
131
|
+
const cost = tracker.calculateCost(makeUsage({ promptTokens: 1000, completionTokens: 500 }), 'gpt-4');
|
|
132
|
+
expect(cost).toBeGreaterThan(0);
|
|
133
|
+
});
|
|
134
|
+
it('calculates cost for gpt-4-turbo-preview', () => {
|
|
135
|
+
const cost = tracker.calculateCost(makeUsage({ promptTokens: 1000, completionTokens: 500 }), 'gpt-4-turbo-preview');
|
|
136
|
+
expect(cost).toBeGreaterThan(0);
|
|
137
|
+
});
|
|
138
|
+
it('calculates cost for gpt-3.5-turbo', () => {
|
|
139
|
+
const cost = tracker.calculateCost(makeUsage({ promptTokens: 1000, completionTokens: 500 }), 'gpt-3.5-turbo');
|
|
140
|
+
expect(cost).toBeGreaterThan(0);
|
|
141
|
+
});
|
|
142
|
+
it('calculates cost for claude models', () => {
|
|
143
|
+
const models = ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'];
|
|
144
|
+
models.forEach((model) => {
|
|
145
|
+
const cost = tracker.calculateCost(makeUsage({ promptTokens: 1000, completionTokens: 500 }), model);
|
|
146
|
+
expect(cost).toBeGreaterThan(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it('returns 0 for unknown model', () => {
|
|
150
|
+
const cost = tracker.calculateCost(makeUsage(), 'unknown-model-xyz');
|
|
151
|
+
expect(cost).toBe(0);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('getUserStats()', () => {
|
|
155
|
+
it('returns zero stats for unknown user', () => {
|
|
156
|
+
const stats = tracker.getUserStats('nobody');
|
|
157
|
+
expect(stats.totalTokens).toBe(0);
|
|
158
|
+
expect(stats.requestCount).toBe(0);
|
|
159
|
+
expect(stats.averageTokensPerRequest).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
it('returns correct stats for user with history', () => {
|
|
162
|
+
tracker.track(makeUsage({ totalTokens: 150, cost: 0.01, userId: 'user1' }));
|
|
163
|
+
tracker.track(makeUsage({ totalTokens: 300, cost: 0.02, userId: 'user1' }));
|
|
164
|
+
const stats = tracker.getUserStats('user1');
|
|
165
|
+
expect(stats.totalTokens).toBe(450);
|
|
166
|
+
expect(stats.requestCount).toBe(2);
|
|
167
|
+
expect(stats.totalCost).toBeCloseTo(0.03);
|
|
168
|
+
expect(stats.averageTokensPerRequest).toBe(225);
|
|
169
|
+
});
|
|
170
|
+
it('calculates dailyAverage', () => {
|
|
171
|
+
tracker.track(makeUsage({ totalTokens: 300, userId: 'user1' }));
|
|
172
|
+
const stats = tracker.getUserStats('user1', 30);
|
|
173
|
+
expect(stats.dailyAverage).toBe(10); // 300/30
|
|
174
|
+
});
|
|
175
|
+
it('excludes usage outside the time window', () => {
|
|
176
|
+
const old = NOW - 40 * 24 * 60 * 60 * 1000;
|
|
177
|
+
tracker.track(makeUsage({ totalTokens: 999, timestamp: old, userId: 'user1' }));
|
|
178
|
+
tracker.track(makeUsage({ totalTokens: 10, userId: 'user1' }));
|
|
179
|
+
const stats = tracker.getUserStats('user1', 30);
|
|
180
|
+
expect(stats.totalTokens).toBe(10);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('getGlobalStats()', () => {
|
|
184
|
+
it('returns zero stats when empty', () => {
|
|
185
|
+
const stats = tracker.getGlobalStats();
|
|
186
|
+
expect(stats.totalTokens).toBe(0);
|
|
187
|
+
expect(stats.requestCount).toBe(0);
|
|
188
|
+
expect(stats.uniqueUsers).toBe(0);
|
|
189
|
+
expect(stats.topUsers).toHaveLength(0);
|
|
190
|
+
});
|
|
191
|
+
it('returns correct stats with multiple users', () => {
|
|
192
|
+
tracker.track(makeUsage({ totalTokens: 150, userId: 'u1' }));
|
|
193
|
+
tracker.track(makeUsage({ totalTokens: 300, userId: 'u2' }));
|
|
194
|
+
tracker.track(makeUsage({ totalTokens: 75 })); // no userId
|
|
195
|
+
const stats = tracker.getGlobalStats();
|
|
196
|
+
expect(stats.totalTokens).toBe(525);
|
|
197
|
+
expect(stats.requestCount).toBe(3);
|
|
198
|
+
expect(stats.uniqueUsers).toBe(2);
|
|
199
|
+
});
|
|
200
|
+
it('returns top users sorted by token usage', () => {
|
|
201
|
+
tracker.track(makeUsage({ totalTokens: 100, userId: 'small' }));
|
|
202
|
+
tracker.track(makeUsage({ totalTokens: 500, userId: 'big' }));
|
|
203
|
+
const stats = tracker.getGlobalStats();
|
|
204
|
+
expect(stats.topUsers[0].userId).toBe('big');
|
|
205
|
+
});
|
|
206
|
+
it('limits top users to 10', () => {
|
|
207
|
+
for (let i = 0; i < 15; i++) {
|
|
208
|
+
tracker.track(makeUsage({ totalTokens: i * 10, userId: `user${i}` }));
|
|
209
|
+
}
|
|
210
|
+
const stats = tracker.getGlobalStats();
|
|
211
|
+
expect(stats.topUsers.length).toBeLessThanOrEqual(10);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe('cleanup()', () => {
|
|
215
|
+
it('removes old global usage data', () => {
|
|
216
|
+
const old = NOW - 91 * 24 * 60 * 60 * 1000;
|
|
217
|
+
tracker.track(makeUsage({ totalTokens: 999, timestamp: old }));
|
|
218
|
+
tracker.track(makeUsage({ totalTokens: 15 }));
|
|
219
|
+
tracker.cleanup(90);
|
|
220
|
+
const exported = tracker.exportData();
|
|
221
|
+
expect(exported).toHaveLength(1);
|
|
222
|
+
expect(exported[0].totalTokens).toBe(15);
|
|
223
|
+
});
|
|
224
|
+
it('removes empty user entries after cleanup', () => {
|
|
225
|
+
const old = NOW - 100 * 24 * 60 * 60 * 1000;
|
|
226
|
+
tracker.track(makeUsage({ timestamp: old, userId: 'oldUser' }));
|
|
227
|
+
tracker.cleanup(90);
|
|
228
|
+
expect(tracker.exportData('oldUser')).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
it('keeps recent user entries after cleanup', () => {
|
|
231
|
+
const old = NOW - 100 * 24 * 60 * 60 * 1000;
|
|
232
|
+
tracker.track(makeUsage({ timestamp: old, userId: 'user1' }));
|
|
233
|
+
tracker.track(makeUsage({ userId: 'user1' }));
|
|
234
|
+
tracker.cleanup(90);
|
|
235
|
+
expect(tracker.exportData('user1')).toHaveLength(1);
|
|
236
|
+
});
|
|
237
|
+
it('uses default 90 days when no argument', () => {
|
|
238
|
+
const old = NOW - 95 * 24 * 60 * 60 * 1000;
|
|
239
|
+
tracker.track(makeUsage({ timestamp: old }));
|
|
240
|
+
tracker.cleanup();
|
|
241
|
+
expect(tracker.exportData()).toHaveLength(0);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('exportData()', () => {
|
|
245
|
+
it('exports all global data when no userId', () => {
|
|
246
|
+
tracker.track(makeUsage({ userId: 'u1' }));
|
|
247
|
+
tracker.track(makeUsage());
|
|
248
|
+
const data = tracker.exportData();
|
|
249
|
+
expect(data).toHaveLength(2);
|
|
250
|
+
});
|
|
251
|
+
it('returns empty array for unknown user', () => {
|
|
252
|
+
expect(tracker.exportData('nobody')).toEqual([]);
|
|
253
|
+
});
|
|
254
|
+
it('returns copy of global history', () => {
|
|
255
|
+
tracker.track(makeUsage());
|
|
256
|
+
const data = tracker.exportData();
|
|
257
|
+
expect(data).toHaveLength(1);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('updateConfig()', () => {
|
|
261
|
+
it('updates maxTokensPerRequest', async () => {
|
|
262
|
+
tracker.updateConfig({ maxTokensPerRequest: 2000 });
|
|
263
|
+
const result = await tracker.checkLimits(undefined, 1500);
|
|
264
|
+
expect(result.allowed).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
it('merges config without overwriting unspecified fields', async () => {
|
|
267
|
+
tracker.updateConfig({ maxTokensPerRequest: 50 });
|
|
268
|
+
const result = await tracker.checkLimits(undefined, 100);
|
|
269
|
+
expect(result.allowed).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { VectorStoreConfig, VectorDocument, VectorSearchRequest, VectorSearchResult, VectorDatabase } from '../ai-enhanced.types';
|
|
2
|
+
/**
|
|
3
|
+
* Vector database service
|
|
4
|
+
*
|
|
5
|
+
* Note: This is a mock implementation. In production, you would use actual vector database clients:
|
|
6
|
+
* - Pinecone: npm install @pinecone-database/pinecone
|
|
7
|
+
* - Weaviate: npm install weaviate-ts-client
|
|
8
|
+
* - Qdrant: npm install @qdrant/js-client-rest
|
|
9
|
+
* - Chroma: npm install chromadb
|
|
10
|
+
*/
|
|
11
|
+
export declare class VectorService {
|
|
12
|
+
private config?;
|
|
13
|
+
private documents;
|
|
14
|
+
/**
|
|
15
|
+
* Initialize vector store
|
|
16
|
+
*/
|
|
17
|
+
initialize(config: VectorStoreConfig): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Upsert documents
|
|
20
|
+
*/
|
|
21
|
+
upsert(documents: VectorDocument[]): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Search for similar documents
|
|
24
|
+
*/
|
|
25
|
+
search(request: VectorSearchRequest): Promise<VectorSearchResult[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Delete documents
|
|
28
|
+
*/
|
|
29
|
+
delete(ids: string[]): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get document by ID
|
|
32
|
+
*/
|
|
33
|
+
get(id: string): Promise<VectorDocument | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Clear all documents
|
|
36
|
+
*/
|
|
37
|
+
clear(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Get statistics
|
|
40
|
+
*/
|
|
41
|
+
getStats(): Promise<{
|
|
42
|
+
count: number;
|
|
43
|
+
database: VectorDatabase;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Calculate cosine similarity between two vectors
|
|
47
|
+
*/
|
|
48
|
+
private cosineSimilarity;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=vector.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vector.service.d.ts","sourceRoot":"","sources":["../../src/vector/vector.service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;GAQG;AACH,qBACa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA0C;IAE3D;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1D;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBxD;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAoCzE;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAQrD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,cAAc,CAAA;KAAE,CAAC;IAWtE;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAiBzB"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.VectorService = void 0;
|
|
13
|
+
const core_1 = require("@hazeljs/core");
|
|
14
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
15
|
+
/**
|
|
16
|
+
* Vector database service
|
|
17
|
+
*
|
|
18
|
+
* Note: This is a mock implementation. In production, you would use actual vector database clients:
|
|
19
|
+
* - Pinecone: npm install @pinecone-database/pinecone
|
|
20
|
+
* - Weaviate: npm install weaviate-ts-client
|
|
21
|
+
* - Qdrant: npm install @qdrant/js-client-rest
|
|
22
|
+
* - Chroma: npm install chromadb
|
|
23
|
+
*/
|
|
24
|
+
let VectorService = class VectorService {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.documents = new Map();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize vector store
|
|
30
|
+
*/
|
|
31
|
+
async initialize(config) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
core_2.default.info(`Vector store initialized: ${config.database}`);
|
|
34
|
+
// In production, initialize the actual vector database client
|
|
35
|
+
// switch (config.database) {
|
|
36
|
+
// case 'pinecone':
|
|
37
|
+
// this.client = new PineconeClient();
|
|
38
|
+
// await this.client.init({ apiKey: config.apiKey });
|
|
39
|
+
// break;
|
|
40
|
+
// case 'weaviate':
|
|
41
|
+
// this.client = weaviate.client({ scheme: 'https', host: config.endpoint });
|
|
42
|
+
// break;
|
|
43
|
+
// // ... other databases
|
|
44
|
+
// }
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Upsert documents
|
|
48
|
+
*/
|
|
49
|
+
async upsert(documents) {
|
|
50
|
+
core_2.default.debug(`Upserting ${documents.length} documents`);
|
|
51
|
+
for (const doc of documents) {
|
|
52
|
+
// Generate mock embedding if not provided
|
|
53
|
+
if (!doc.embedding) {
|
|
54
|
+
doc.embedding = Array.from({ length: 1536 }, () => Math.random() * 2 - 1);
|
|
55
|
+
}
|
|
56
|
+
this.documents.set(doc.id, doc);
|
|
57
|
+
}
|
|
58
|
+
// In production:
|
|
59
|
+
// await this.client.upsert({
|
|
60
|
+
// vectors: documents.map(doc => ({
|
|
61
|
+
// id: doc.id,
|
|
62
|
+
// values: doc.embedding,
|
|
63
|
+
// metadata: { content: doc.content, ...doc.metadata },
|
|
64
|
+
// })),
|
|
65
|
+
// });
|
|
66
|
+
core_2.default.info(`Upserted ${documents.length} documents`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Search for similar documents
|
|
70
|
+
*/
|
|
71
|
+
async search(request) {
|
|
72
|
+
core_2.default.debug(`Searching for: ${request.query}`);
|
|
73
|
+
// Mock implementation - generate random query embedding
|
|
74
|
+
const queryEmbedding = Array.from({ length: 1536 }, () => Math.random() * 2 - 1);
|
|
75
|
+
// Calculate cosine similarity for all documents
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const [id, doc] of this.documents) {
|
|
78
|
+
if (doc.embedding) {
|
|
79
|
+
const score = this.cosineSimilarity(queryEmbedding, doc.embedding);
|
|
80
|
+
results.push({
|
|
81
|
+
id,
|
|
82
|
+
content: doc.content,
|
|
83
|
+
score,
|
|
84
|
+
metadata: doc.metadata,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Sort by score and return top K
|
|
89
|
+
results.sort((a, b) => b.score - a.score);
|
|
90
|
+
const topK = request.topK || 10;
|
|
91
|
+
// In production:
|
|
92
|
+
// const response = await this.client.query({
|
|
93
|
+
// vector: queryEmbedding,
|
|
94
|
+
// topK: request.topK || 10,
|
|
95
|
+
// filter: request.filter,
|
|
96
|
+
// });
|
|
97
|
+
core_2.default.info(`Found ${results.length} results`);
|
|
98
|
+
return results.slice(0, topK);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Delete documents
|
|
102
|
+
*/
|
|
103
|
+
async delete(ids) {
|
|
104
|
+
core_2.default.debug(`Deleting ${ids.length} documents`);
|
|
105
|
+
for (const id of ids) {
|
|
106
|
+
this.documents.delete(id);
|
|
107
|
+
}
|
|
108
|
+
// In production:
|
|
109
|
+
// await this.client.delete({ ids });
|
|
110
|
+
core_2.default.info(`Deleted ${ids.length} documents`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get document by ID
|
|
114
|
+
*/
|
|
115
|
+
async get(id) {
|
|
116
|
+
return this.documents.get(id) || null;
|
|
117
|
+
// In production:
|
|
118
|
+
// const response = await this.client.fetch({ ids: [id] });
|
|
119
|
+
// return response.vectors[id];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clear all documents
|
|
123
|
+
*/
|
|
124
|
+
async clear() {
|
|
125
|
+
this.documents.clear();
|
|
126
|
+
core_2.default.info('All documents cleared');
|
|
127
|
+
// In production:
|
|
128
|
+
// await this.client.deleteAll();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get statistics
|
|
132
|
+
*/
|
|
133
|
+
async getStats() {
|
|
134
|
+
return {
|
|
135
|
+
count: this.documents.size,
|
|
136
|
+
database: this.config?.database || 'pinecone',
|
|
137
|
+
};
|
|
138
|
+
// In production:
|
|
139
|
+
// const stats = await this.client.describeIndexStats();
|
|
140
|
+
// return { count: stats.totalVectorCount, database: this.config.database };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Calculate cosine similarity between two vectors
|
|
144
|
+
*/
|
|
145
|
+
cosineSimilarity(a, b) {
|
|
146
|
+
if (a.length !== b.length) {
|
|
147
|
+
throw new Error('Vectors must have the same length');
|
|
148
|
+
}
|
|
149
|
+
let dotProduct = 0;
|
|
150
|
+
let normA = 0;
|
|
151
|
+
let normB = 0;
|
|
152
|
+
for (let i = 0; i < a.length; i++) {
|
|
153
|
+
dotProduct += a[i] * b[i];
|
|
154
|
+
normA += a[i] * a[i];
|
|
155
|
+
normB += b[i] * b[i];
|
|
156
|
+
}
|
|
157
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
exports.VectorService = VectorService;
|
|
161
|
+
exports.VectorService = VectorService = __decorate([
|
|
162
|
+
(0, core_1.Service)()
|
|
163
|
+
], VectorService);
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hazeljs/ai",
|
|
3
|
+
"version": "0.2.0-alpha.1",
|
|
4
|
+
"description": "AI integration module for HazelJS framework",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc --skipLibCheck",
|
|
12
|
+
"test": "jest --coverage --maxWorkers=1",
|
|
13
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
14
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
19
|
+
"@google/generative-ai": "^0.24.1",
|
|
20
|
+
"@hazeljs/prompts": "^0.2.0-alpha.1",
|
|
21
|
+
"cohere-ai": "^7.14.0",
|
|
22
|
+
"openai": "^6.15.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20.17.50",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
27
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
28
|
+
"eslint": "^8.56.0",
|
|
29
|
+
"jest": "^29.7.0",
|
|
30
|
+
"ts-jest": "^29.1.2",
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/hazel-js/hazeljs.git",
|
|
39
|
+
"directory": "packages/ai"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"hazeljs",
|
|
43
|
+
"ai",
|
|
44
|
+
"openai",
|
|
45
|
+
"anthropic",
|
|
46
|
+
"gemini",
|
|
47
|
+
"llm"
|
|
48
|
+
],
|
|
49
|
+
"author": "Muhammad Arslan <muhammad.arslan@hazeljs.com>",
|
|
50
|
+
"license": "Apache-2.0",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/hazeljs/hazel-js/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://hazeljs.com",
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@hazeljs/cache": ">=0.2.0-beta.0",
|
|
57
|
+
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "cbc5ee2c12ced28fd0576faf13c5f078c1e8421e"
|
|
60
|
+
}
|