@yesvara/svara 0.1.0 → 0.1.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/CONTRIBUTING.md +233 -0
- package/README.md +172 -22
- package/dist/{chunk-FEA5KIJN.mjs → chunk-WA3BIA3S.mjs} +356 -27
- package/dist/index.d.mts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +454 -269
- package/dist/index.mjs +81 -239
- package/dist/{retriever-4QY667XF.mjs → retriever-OTPBECGO.mjs} +1 -1
- package/package.json +27 -15
- package/src/core/agent.ts +89 -8
- package/src/core/types.ts +14 -1
- package/src/database/schema.ts +31 -6
- package/src/rag/chunker.ts +1 -0
- package/src/rag/retriever.ts +146 -33
- package/svara@1.0.0 +0 -0
- package/test-rag.ts +20 -0
- package/tsx +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Chunker,
|
|
3
3
|
DocumentLoader,
|
|
4
|
+
SvaraDB,
|
|
4
5
|
VectorRetriever
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WA3BIA3S.mjs";
|
|
6
7
|
import {
|
|
7
8
|
__esm,
|
|
8
9
|
__export,
|
|
@@ -339,8 +340,8 @@ var SvaraApp = class {
|
|
|
339
340
|
* .route('/chat', supportAgent.handler())
|
|
340
341
|
* .route('/sales', salesAgent.handler());
|
|
341
342
|
*/
|
|
342
|
-
route(
|
|
343
|
-
this.express.post(
|
|
343
|
+
route(path, handler) {
|
|
344
|
+
this.express.post(path, handler);
|
|
344
345
|
return this;
|
|
345
346
|
}
|
|
346
347
|
/**
|
|
@@ -884,13 +885,17 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
884
885
|
verbose;
|
|
885
886
|
channels = /* @__PURE__ */ new Map();
|
|
886
887
|
knowledgeBase = null;
|
|
888
|
+
retriever = null;
|
|
889
|
+
// Store VectorRetriever for retrieveChunks access
|
|
887
890
|
knowledgePaths = [];
|
|
888
891
|
isStarted = false;
|
|
892
|
+
db;
|
|
889
893
|
constructor(config) {
|
|
890
894
|
super();
|
|
891
895
|
this.name = config.name;
|
|
892
896
|
this.maxIterations = config.maxIterations ?? 10;
|
|
893
897
|
this.verbose = config.verbose ?? false;
|
|
898
|
+
this.db = new SvaraDB("./data/svara.db");
|
|
894
899
|
this.systemPrompt = config.systemPrompt ?? `You are ${config.name}, a helpful and friendly AI assistant. Be concise and accurate.`;
|
|
895
900
|
this.llmConfig = resolveConfig(config.model, {
|
|
896
901
|
temperature: config.temperature,
|
|
@@ -1009,7 +1014,8 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1009
1014
|
response: result.response,
|
|
1010
1015
|
sessionId: result.sessionId,
|
|
1011
1016
|
usage: result.usage,
|
|
1012
|
-
toolsUsed: result.toolsUsed
|
|
1017
|
+
toolsUsed: result.toolsUsed,
|
|
1018
|
+
retrievedDocuments: result.retrievedDocuments || []
|
|
1013
1019
|
});
|
|
1014
1020
|
} catch (err) {
|
|
1015
1021
|
const error = err;
|
|
@@ -1080,6 +1086,46 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1080
1086
|
await this.knowledgeBase.load(arr);
|
|
1081
1087
|
}
|
|
1082
1088
|
}
|
|
1089
|
+
// ─── Internal: User & Session Tracking ───────────────────────────────────────
|
|
1090
|
+
async trackUserAndSession(userId, sessionId, channel = "api") {
|
|
1091
|
+
try {
|
|
1092
|
+
const existingUser = this.db.query(
|
|
1093
|
+
"SELECT id FROM svara_users WHERE id = ?",
|
|
1094
|
+
[userId]
|
|
1095
|
+
);
|
|
1096
|
+
if (existingUser.length === 0) {
|
|
1097
|
+
this.db.run(
|
|
1098
|
+
`INSERT INTO svara_users (id, display_name, first_seen, last_seen)
|
|
1099
|
+
VALUES (?, ?, unixepoch(), unixepoch())`,
|
|
1100
|
+
[userId, userId]
|
|
1101
|
+
);
|
|
1102
|
+
} else {
|
|
1103
|
+
this.db.run(
|
|
1104
|
+
"UPDATE svara_users SET last_seen = unixepoch() WHERE id = ?",
|
|
1105
|
+
[userId]
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
const existingSession = this.db.query(
|
|
1109
|
+
"SELECT id FROM svara_sessions WHERE id = ?",
|
|
1110
|
+
[sessionId]
|
|
1111
|
+
);
|
|
1112
|
+
if (existingSession.length === 0) {
|
|
1113
|
+
this.db.run(
|
|
1114
|
+
`INSERT INTO svara_sessions (id, user_id, channel, created_at, updated_at)
|
|
1115
|
+
VALUES (?, ?, ?, unixepoch(), unixepoch())`,
|
|
1116
|
+
[sessionId, userId, channel]
|
|
1117
|
+
);
|
|
1118
|
+
} else {
|
|
1119
|
+
this.db.run(
|
|
1120
|
+
"UPDATE svara_sessions SET updated_at = unixepoch() WHERE id = ?",
|
|
1121
|
+
[sessionId]
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
this.log("debug", `Tracked user ${userId} with session ${sessionId}`);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
this.log("error", `Failed to track user: ${error.message}`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1083
1129
|
// ─── Internal: Agentic Loop ───────────────────────────────────────────────
|
|
1084
1130
|
/**
|
|
1085
1131
|
* Receives a raw incoming message from a channel and processes it.
|
|
@@ -1092,13 +1138,33 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1092
1138
|
});
|
|
1093
1139
|
}
|
|
1094
1140
|
async run(message, options) {
|
|
1141
|
+
console.log(`
|
|
1142
|
+
[RUN START] kb=${!!this.knowledgeBase} ret=${!!this.retriever}`);
|
|
1095
1143
|
const startTime = Date.now();
|
|
1096
1144
|
const sessionId = options.sessionId ?? crypto.randomUUID();
|
|
1097
|
-
|
|
1145
|
+
const userId = options.userId ?? "unknown";
|
|
1146
|
+
await this.trackUserAndSession(userId, sessionId);
|
|
1147
|
+
this.emit("message:received", { message, sessionId, userId });
|
|
1098
1148
|
const history = await this.memory.getHistory(sessionId);
|
|
1099
1149
|
let ragContext = "";
|
|
1100
|
-
|
|
1150
|
+
let retrievedDocuments = [];
|
|
1151
|
+
if (this.knowledgeBase && this.retriever) {
|
|
1101
1152
|
ragContext = await this.knowledgeBase.retrieve(message);
|
|
1153
|
+
try {
|
|
1154
|
+
console.log(`[DEBUG] Calling retrieveChunks for query: "${message}"`);
|
|
1155
|
+
const context = await this.retriever.retrieveChunks(message, 3);
|
|
1156
|
+
console.log(`[DEBUG] Retrieved ${context.chunks.length} chunks`);
|
|
1157
|
+
retrievedDocuments = context.chunks.map((item) => ({
|
|
1158
|
+
source: item.chunk?.source || "unknown",
|
|
1159
|
+
score: Math.round(item.score * 100) / 100,
|
|
1160
|
+
excerpt: item.chunk?.content?.substring(0, 150) || ""
|
|
1161
|
+
}));
|
|
1162
|
+
console.log(`[DEBUG] Mapped ${retrievedDocuments.length} documents`);
|
|
1163
|
+
} catch (e) {
|
|
1164
|
+
console.error(`[ERROR] RAG retrieval failed:`, e);
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
console.log(`[DEBUG] No knowledgeBase (${!!this.knowledgeBase}) or retriever (${!!this.retriever})`);
|
|
1102
1168
|
}
|
|
1103
1169
|
const messages = this.context.buildMessages(
|
|
1104
1170
|
this.systemPrompt,
|
|
@@ -1108,7 +1174,7 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1108
1174
|
);
|
|
1109
1175
|
const internalCtx = {
|
|
1110
1176
|
sessionId,
|
|
1111
|
-
userId
|
|
1177
|
+
userId,
|
|
1112
1178
|
agentName: this.name,
|
|
1113
1179
|
history,
|
|
1114
1180
|
metadata: options.metadata ?? {}
|
|
@@ -1165,7 +1231,8 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1165
1231
|
toolsUsed: [...new Set(toolsUsed)],
|
|
1166
1232
|
iterations,
|
|
1167
1233
|
usage: totalUsage,
|
|
1168
|
-
duration: Date.now() - startTime
|
|
1234
|
+
duration: Date.now() - startTime,
|
|
1235
|
+
retrievedDocuments: retrievedDocuments.length > 0 ? retrievedDocuments : void 0
|
|
1169
1236
|
};
|
|
1170
1237
|
this.emit("message:sent", { response: finalResponse, sessionId });
|
|
1171
1238
|
return result;
|
|
@@ -1174,9 +1241,9 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1174
1241
|
async initKnowledge(paths) {
|
|
1175
1242
|
try {
|
|
1176
1243
|
const { glob } = await import("glob");
|
|
1177
|
-
const { VectorRetriever: VectorRetriever2 } = await import("./retriever-
|
|
1178
|
-
|
|
1179
|
-
await retriever.init({ embeddings: { provider: "openai" } });
|
|
1244
|
+
const { VectorRetriever: VectorRetriever2 } = await import("./retriever-OTPBECGO.mjs");
|
|
1245
|
+
this.retriever = new VectorRetriever2(this.name, this.db);
|
|
1246
|
+
await this.retriever.init({ embeddings: { provider: "openai" } });
|
|
1180
1247
|
const files = [];
|
|
1181
1248
|
for (const pattern of paths) {
|
|
1182
1249
|
const matches = await glob(pattern);
|
|
@@ -1186,16 +1253,16 @@ var SvaraAgent = class extends EventEmitter {
|
|
|
1186
1253
|
console.warn(`[@yesvara/svara] No files found matching: ${paths.join(", ")}`);
|
|
1187
1254
|
return;
|
|
1188
1255
|
}
|
|
1189
|
-
await retriever.addDocuments(files);
|
|
1256
|
+
await this.retriever.addDocuments(files);
|
|
1190
1257
|
this.knowledgeBase = {
|
|
1191
1258
|
load: async (p) => {
|
|
1192
1259
|
const newFiles = [];
|
|
1193
1260
|
for (const pattern of Array.isArray(p) ? p : [p]) {
|
|
1194
1261
|
newFiles.push(...await glob(pattern));
|
|
1195
1262
|
}
|
|
1196
|
-
await retriever.addDocuments(newFiles);
|
|
1263
|
+
await this.retriever.addDocuments(newFiles);
|
|
1197
1264
|
},
|
|
1198
|
-
retrieve: (query, topK) => retriever.retrieve(query, topK)
|
|
1265
|
+
retrieve: (query, topK) => this.retriever.retrieve(query, topK)
|
|
1199
1266
|
};
|
|
1200
1267
|
this.log("info", `Knowledge base loaded: ${files.length} file(s).`);
|
|
1201
1268
|
} catch (err) {
|
|
@@ -1257,231 +1324,6 @@ function createTool(definition) {
|
|
|
1257
1324
|
};
|
|
1258
1325
|
}
|
|
1259
1326
|
|
|
1260
|
-
// src/database/sqlite.ts
|
|
1261
|
-
import path from "path";
|
|
1262
|
-
import fs from "fs";
|
|
1263
|
-
|
|
1264
|
-
// src/database/schema.ts
|
|
1265
|
-
var SCHEMA_VERSION = 1;
|
|
1266
|
-
var CREATE_TABLES_SQL = `
|
|
1267
|
-
-- Schema version tracking
|
|
1268
|
-
CREATE TABLE IF NOT EXISTS svara_meta (
|
|
1269
|
-
key TEXT PRIMARY KEY,
|
|
1270
|
-
value TEXT NOT NULL
|
|
1271
|
-
);
|
|
1272
|
-
|
|
1273
|
-
-- Conversation history persistence
|
|
1274
|
-
CREATE TABLE IF NOT EXISTS svara_messages (
|
|
1275
|
-
id TEXT PRIMARY KEY,
|
|
1276
|
-
session_id TEXT NOT NULL,
|
|
1277
|
-
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
|
|
1278
|
-
content TEXT NOT NULL,
|
|
1279
|
-
tool_call_id TEXT,
|
|
1280
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1281
|
-
);
|
|
1282
|
-
|
|
1283
|
-
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
1284
|
-
ON svara_messages (session_id, created_at);
|
|
1285
|
-
|
|
1286
|
-
-- Session metadata
|
|
1287
|
-
CREATE TABLE IF NOT EXISTS svara_sessions (
|
|
1288
|
-
id TEXT PRIMARY KEY,
|
|
1289
|
-
user_id TEXT,
|
|
1290
|
-
channel TEXT NOT NULL,
|
|
1291
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1292
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1293
|
-
metadata TEXT DEFAULT '{}'
|
|
1294
|
-
);
|
|
1295
|
-
|
|
1296
|
-
-- Vector store chunks for RAG
|
|
1297
|
-
CREATE TABLE IF NOT EXISTS svara_chunks (
|
|
1298
|
-
id TEXT PRIMARY KEY,
|
|
1299
|
-
document_id TEXT NOT NULL,
|
|
1300
|
-
content TEXT NOT NULL,
|
|
1301
|
-
chunk_index INTEGER NOT NULL,
|
|
1302
|
-
embedding BLOB, -- stored as binary float32 array
|
|
1303
|
-
source TEXT NOT NULL,
|
|
1304
|
-
metadata TEXT DEFAULT '{}',
|
|
1305
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1306
|
-
);
|
|
1307
|
-
|
|
1308
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_document
|
|
1309
|
-
ON svara_chunks (document_id);
|
|
1310
|
-
|
|
1311
|
-
-- Document registry
|
|
1312
|
-
CREATE TABLE IF NOT EXISTS svara_documents (
|
|
1313
|
-
id TEXT PRIMARY KEY,
|
|
1314
|
-
source TEXT NOT NULL UNIQUE,
|
|
1315
|
-
type TEXT NOT NULL,
|
|
1316
|
-
size INTEGER,
|
|
1317
|
-
hash TEXT,
|
|
1318
|
-
indexed_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1319
|
-
metadata TEXT DEFAULT '{}'
|
|
1320
|
-
);
|
|
1321
|
-
|
|
1322
|
-
-- Key-value store for arbitrary agent state
|
|
1323
|
-
CREATE TABLE IF NOT EXISTS svara_kv (
|
|
1324
|
-
key TEXT PRIMARY KEY,
|
|
1325
|
-
value TEXT NOT NULL,
|
|
1326
|
-
expires_at INTEGER, -- unix timestamp, NULL = no expiry
|
|
1327
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1328
|
-
);
|
|
1329
|
-
`;
|
|
1330
|
-
var INSERT_META_SQL = `
|
|
1331
|
-
INSERT OR REPLACE INTO svara_meta (key, value)
|
|
1332
|
-
VALUES ('schema_version', ?), ('created_at', ?);
|
|
1333
|
-
`;
|
|
1334
|
-
|
|
1335
|
-
// src/database/sqlite.ts
|
|
1336
|
-
var KVStore = class {
|
|
1337
|
-
constructor(db) {
|
|
1338
|
-
this.db = db;
|
|
1339
|
-
}
|
|
1340
|
-
db;
|
|
1341
|
-
/** Set a key-value pair, with optional TTL in seconds. */
|
|
1342
|
-
set(key, value, ttlSeconds) {
|
|
1343
|
-
const expiresAt = ttlSeconds ? Math.floor(Date.now() / 1e3) + ttlSeconds : null;
|
|
1344
|
-
this.db.prepare(`
|
|
1345
|
-
INSERT OR REPLACE INTO svara_kv (key, value, expires_at, updated_at)
|
|
1346
|
-
VALUES (?, ?, ?, unixepoch())
|
|
1347
|
-
`).run(key, JSON.stringify(value), expiresAt);
|
|
1348
|
-
}
|
|
1349
|
-
/** Get a value by key. Returns undefined if not found or expired. */
|
|
1350
|
-
get(key) {
|
|
1351
|
-
const row = this.db.prepare(`
|
|
1352
|
-
SELECT value, expires_at FROM svara_kv
|
|
1353
|
-
WHERE key = ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
1354
|
-
`).get(key);
|
|
1355
|
-
if (!row) return void 0;
|
|
1356
|
-
return JSON.parse(row.value);
|
|
1357
|
-
}
|
|
1358
|
-
/** Delete a key. */
|
|
1359
|
-
delete(key) {
|
|
1360
|
-
this.db.prepare("DELETE FROM svara_kv WHERE key = ?").run(key);
|
|
1361
|
-
}
|
|
1362
|
-
/** Check if a key exists and is not expired. */
|
|
1363
|
-
has(key) {
|
|
1364
|
-
return this.get(key) !== void 0;
|
|
1365
|
-
}
|
|
1366
|
-
/** Get all keys matching a prefix. */
|
|
1367
|
-
keys(prefix = "") {
|
|
1368
|
-
const rows = this.db.prepare(`
|
|
1369
|
-
SELECT key FROM svara_kv
|
|
1370
|
-
WHERE key LIKE ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
1371
|
-
`).all(`${prefix}%`);
|
|
1372
|
-
return rows.map((r) => r.key);
|
|
1373
|
-
}
|
|
1374
|
-
};
|
|
1375
|
-
var SvaraDB = class {
|
|
1376
|
-
db;
|
|
1377
|
-
kv;
|
|
1378
|
-
constructor(dbPath = ":memory:") {
|
|
1379
|
-
if (dbPath !== ":memory:") {
|
|
1380
|
-
fs.mkdirSync(path.dirname(path.resolve(dbPath)), { recursive: true });
|
|
1381
|
-
}
|
|
1382
|
-
this.db = this.openDatabase(dbPath);
|
|
1383
|
-
this.configure();
|
|
1384
|
-
this.migrate();
|
|
1385
|
-
this.kv = new KVStore(this.db);
|
|
1386
|
-
}
|
|
1387
|
-
// ─── Query Helpers ────────────────────────────────────────────────────────
|
|
1388
|
-
/**
|
|
1389
|
-
* Run a SELECT and return all matching rows.
|
|
1390
|
-
*/
|
|
1391
|
-
query(sql, params = []) {
|
|
1392
|
-
return this.db.prepare(sql).all(...params);
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Run a SELECT and return the first matching row.
|
|
1396
|
-
*/
|
|
1397
|
-
queryOne(sql, params = []) {
|
|
1398
|
-
return this.db.prepare(sql).get(...params);
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Run an INSERT/UPDATE/DELETE. Returns affected row count.
|
|
1402
|
-
*/
|
|
1403
|
-
run(sql, params = []) {
|
|
1404
|
-
return this.db.prepare(sql).run(...params).changes;
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* Execute raw SQL (for DDL, migrations, etc.).
|
|
1408
|
-
*/
|
|
1409
|
-
exec(sql) {
|
|
1410
|
-
this.db.exec(sql);
|
|
1411
|
-
}
|
|
1412
|
-
/**
|
|
1413
|
-
* Run multiple operations in a single transaction.
|
|
1414
|
-
*
|
|
1415
|
-
* @example
|
|
1416
|
-
* db.transaction(() => {
|
|
1417
|
-
* db.run('INSERT INTO orders ...', [...]);
|
|
1418
|
-
* db.run('UPDATE inventory ...', [...]);
|
|
1419
|
-
* });
|
|
1420
|
-
*/
|
|
1421
|
-
transaction(fn) {
|
|
1422
|
-
return this.db.transaction(fn)();
|
|
1423
|
-
}
|
|
1424
|
-
/**
|
|
1425
|
-
* Close the database connection.
|
|
1426
|
-
*/
|
|
1427
|
-
close() {
|
|
1428
|
-
this.db.close();
|
|
1429
|
-
}
|
|
1430
|
-
// ─── Internal Message Storage ─────────────────────────────────────────────
|
|
1431
|
-
saveMessage(params) {
|
|
1432
|
-
this.db.prepare(`
|
|
1433
|
-
INSERT OR REPLACE INTO svara_messages (id, session_id, role, content, tool_call_id)
|
|
1434
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1435
|
-
`).run(
|
|
1436
|
-
params.id,
|
|
1437
|
-
params.sessionId,
|
|
1438
|
-
params.role,
|
|
1439
|
-
params.content,
|
|
1440
|
-
params.toolCallId ?? null
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
getMessages(sessionId, limit = 50) {
|
|
1444
|
-
return this.db.prepare(`
|
|
1445
|
-
SELECT id, role, content, tool_call_id, created_at
|
|
1446
|
-
FROM svara_messages
|
|
1447
|
-
WHERE session_id = ?
|
|
1448
|
-
ORDER BY created_at ASC
|
|
1449
|
-
LIMIT ?
|
|
1450
|
-
`).all(sessionId, limit);
|
|
1451
|
-
}
|
|
1452
|
-
clearSession(sessionId) {
|
|
1453
|
-
this.db.prepare("DELETE FROM svara_messages WHERE session_id = ?").run(sessionId);
|
|
1454
|
-
}
|
|
1455
|
-
// ─── Private Setup ────────────────────────────────────────────────────────
|
|
1456
|
-
openDatabase(dbPath) {
|
|
1457
|
-
try {
|
|
1458
|
-
const Database = __require("better-sqlite3");
|
|
1459
|
-
return new Database(dbPath);
|
|
1460
|
-
} catch {
|
|
1461
|
-
throw new Error(
|
|
1462
|
-
'[SvaraJS] Database requires the "better-sqlite3" package.\nRun: npm install better-sqlite3'
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
configure() {
|
|
1467
|
-
this.db.pragma("journal_mode = WAL");
|
|
1468
|
-
this.db.pragma("synchronous = NORMAL");
|
|
1469
|
-
this.db.pragma("foreign_keys = ON");
|
|
1470
|
-
}
|
|
1471
|
-
migrate() {
|
|
1472
|
-
this.db.exec(CREATE_TABLES_SQL);
|
|
1473
|
-
const meta = this.db.prepare(
|
|
1474
|
-
"SELECT value FROM svara_meta WHERE key = 'schema_version'"
|
|
1475
|
-
).get();
|
|
1476
|
-
if (!meta) {
|
|
1477
|
-
this.db.prepare(INSERT_META_SQL).run(
|
|
1478
|
-
String(SCHEMA_VERSION),
|
|
1479
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
1327
|
// src/index.ts
|
|
1486
1328
|
init_web();
|
|
1487
1329
|
init_telegram();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yesvara/svara",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Build AI agents in 15 lines of code. Multi-channel, RAG-ready, production-grade.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -23,37 +23,46 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
|
-
"ai",
|
|
27
|
-
"
|
|
26
|
+
"ai",
|
|
27
|
+
"agent",
|
|
28
|
+
"llm",
|
|
29
|
+
"openai",
|
|
30
|
+
"anthropic",
|
|
31
|
+
"rag",
|
|
32
|
+
"chatbot",
|
|
33
|
+
"whatsapp",
|
|
34
|
+
"telegram",
|
|
35
|
+
"framework",
|
|
36
|
+
"agentic"
|
|
28
37
|
],
|
|
29
38
|
"author": "Yesvara Contributors",
|
|
30
39
|
"license": "MIT",
|
|
31
40
|
"repository": {
|
|
32
41
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/yogiswara92/
|
|
42
|
+
"url": "https://github.com/yogiswara92/svarajs"
|
|
34
43
|
},
|
|
35
44
|
"homepage": "https://svarajs.yesvara.com",
|
|
36
45
|
"publishConfig": {
|
|
37
46
|
"access": "public"
|
|
38
47
|
},
|
|
39
48
|
"dependencies": {
|
|
40
|
-
"
|
|
41
|
-
"better-sqlite3": "^9.
|
|
42
|
-
"openai": "^4.47.1",
|
|
43
|
-
"@anthropic-ai/sdk": "^0.24.3",
|
|
44
|
-
"zod": "^3.23.8",
|
|
49
|
+
"@anthropic-ai/sdk": ">=0.20.0",
|
|
50
|
+
"better-sqlite3": "^9.6.0",
|
|
45
51
|
"chalk": "^5.3.0",
|
|
46
52
|
"commander": "^12.1.0",
|
|
47
53
|
"dotenv": "^16.4.5",
|
|
54
|
+
"express": "^4.18.2",
|
|
48
55
|
"glob": "^10.4.1",
|
|
49
|
-
"
|
|
56
|
+
"inquirer": "^9.3.2",
|
|
50
57
|
"mammoth": "^1.7.2",
|
|
58
|
+
"openai": "^6.34.0",
|
|
51
59
|
"ora": "^8.0.1",
|
|
52
|
-
"
|
|
60
|
+
"pdf-parse": "^1.1.1",
|
|
61
|
+
"zod": "^3.23.8"
|
|
53
62
|
},
|
|
54
63
|
"devDependencies": {
|
|
55
|
-
"@types/express": "^4.17.21",
|
|
56
64
|
"@types/better-sqlite3": "^7.6.10",
|
|
65
|
+
"@types/express": "^4.17.21",
|
|
57
66
|
"@types/node": "^20.14.2",
|
|
58
67
|
"@types/pdf-parse": "^1.1.4",
|
|
59
68
|
"tsup": "^8.1.0",
|
|
@@ -61,12 +70,15 @@
|
|
|
61
70
|
"vitest": "^1.6.0"
|
|
62
71
|
},
|
|
63
72
|
"peerDependencies": {
|
|
64
|
-
"openai": ">=4.0.0",
|
|
65
73
|
"@anthropic-ai/sdk": ">=0.20.0"
|
|
66
74
|
},
|
|
67
75
|
"peerDependenciesMeta": {
|
|
68
|
-
"openai": {
|
|
69
|
-
|
|
76
|
+
"openai": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@anthropic-ai/sdk": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
70
82
|
},
|
|
71
83
|
"engines": {
|
|
72
84
|
"node": ">=18.0.0"
|
package/src/core/agent.ts
CHANGED
|
@@ -53,6 +53,7 @@ import type {
|
|
|
53
53
|
import { ConversationMemory } from '../memory/conversation.js';
|
|
54
54
|
import { ContextBuilder } from '../memory/context.js';
|
|
55
55
|
import { ToolRegistry } from '../tools/registry.js';
|
|
56
|
+
import { SvaraDB } from '../database/sqlite.js';
|
|
56
57
|
import { ToolExecutor } from '../tools/executor.js';
|
|
57
58
|
import type { Tool } from '../types.js';
|
|
58
59
|
|
|
@@ -169,8 +170,10 @@ export class SvaraAgent extends EventEmitter {
|
|
|
169
170
|
|
|
170
171
|
private channels: Map<ChannelName, SvaraChannel> = new Map();
|
|
171
172
|
private knowledgeBase: KnowledgeBase | null = null;
|
|
173
|
+
private retriever: any = null; // Store VectorRetriever for retrieveChunks access
|
|
172
174
|
private knowledgePaths: string[] = [];
|
|
173
175
|
private isStarted = false;
|
|
176
|
+
private db: SvaraDB;
|
|
174
177
|
|
|
175
178
|
constructor(config: AgentConfig) {
|
|
176
179
|
super();
|
|
@@ -178,6 +181,7 @@ export class SvaraAgent extends EventEmitter {
|
|
|
178
181
|
this.name = config.name;
|
|
179
182
|
this.maxIterations = config.maxIterations ?? 10;
|
|
180
183
|
this.verbose = config.verbose ?? false;
|
|
184
|
+
this.db = new SvaraDB('./data/svara.db');
|
|
181
185
|
|
|
182
186
|
this.systemPrompt = config.systemPrompt
|
|
183
187
|
?? `You are ${config.name}, a helpful and friendly AI assistant. Be concise and accurate.`;
|
|
@@ -324,6 +328,7 @@ export class SvaraAgent extends EventEmitter {
|
|
|
324
328
|
sessionId: result.sessionId,
|
|
325
329
|
usage: result.usage,
|
|
326
330
|
toolsUsed: result.toolsUsed,
|
|
331
|
+
retrievedDocuments: result.retrievedDocuments || [],
|
|
327
332
|
});
|
|
328
333
|
} catch (err) {
|
|
329
334
|
const error = err as Error;
|
|
@@ -405,6 +410,58 @@ export class SvaraAgent extends EventEmitter {
|
|
|
405
410
|
}
|
|
406
411
|
}
|
|
407
412
|
|
|
413
|
+
// ─── Internal: User & Session Tracking ───────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
private async trackUserAndSession(userId: string, sessionId: string, channel = 'api'): Promise<void> {
|
|
416
|
+
try {
|
|
417
|
+
// Track user
|
|
418
|
+
const existingUser = this.db.query(
|
|
419
|
+
'SELECT id FROM svara_users WHERE id = ?',
|
|
420
|
+
[userId]
|
|
421
|
+
) as Array<{ id: string }>;
|
|
422
|
+
|
|
423
|
+
if (existingUser.length === 0) {
|
|
424
|
+
// New user
|
|
425
|
+
this.db.run(
|
|
426
|
+
`INSERT INTO svara_users (id, display_name, first_seen, last_seen)
|
|
427
|
+
VALUES (?, ?, unixepoch(), unixepoch())`,
|
|
428
|
+
[userId, userId]
|
|
429
|
+
);
|
|
430
|
+
} else {
|
|
431
|
+
// Update last_seen
|
|
432
|
+
this.db.run(
|
|
433
|
+
'UPDATE svara_users SET last_seen = unixepoch() WHERE id = ?',
|
|
434
|
+
[userId]
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Track session
|
|
439
|
+
const existingSession = this.db.query(
|
|
440
|
+
'SELECT id FROM svara_sessions WHERE id = ?',
|
|
441
|
+
[sessionId]
|
|
442
|
+
) as Array<{ id: string }>;
|
|
443
|
+
|
|
444
|
+
if (existingSession.length === 0) {
|
|
445
|
+
// New session
|
|
446
|
+
this.db.run(
|
|
447
|
+
`INSERT INTO svara_sessions (id, user_id, channel, created_at, updated_at)
|
|
448
|
+
VALUES (?, ?, ?, unixepoch(), unixepoch())`,
|
|
449
|
+
[sessionId, userId, channel]
|
|
450
|
+
);
|
|
451
|
+
} else {
|
|
452
|
+
// Update updated_at
|
|
453
|
+
this.db.run(
|
|
454
|
+
'UPDATE svara_sessions SET updated_at = unixepoch() WHERE id = ?',
|
|
455
|
+
[sessionId]
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this.log('debug', `Tracked user ${userId} with session ${sessionId}`);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
this.log('error', `Failed to track user: ${(error as Error).message}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
408
465
|
// ─── Internal: Agentic Loop ───────────────────────────────────────────────
|
|
409
466
|
|
|
410
467
|
/**
|
|
@@ -419,18 +476,40 @@ export class SvaraAgent extends EventEmitter {
|
|
|
419
476
|
}
|
|
420
477
|
|
|
421
478
|
private async run(message: string, options: AgentRunOptions): Promise<AgentRunResult> {
|
|
479
|
+
console.log(`\n[RUN START] kb=${!!this.knowledgeBase} ret=${!!this.retriever}`);
|
|
422
480
|
const startTime = Date.now();
|
|
423
481
|
const sessionId = options.sessionId ?? crypto.randomUUID();
|
|
482
|
+
const userId = options.userId ?? 'unknown';
|
|
483
|
+
|
|
484
|
+
// Track user and session
|
|
485
|
+
await this.trackUserAndSession(userId, sessionId);
|
|
424
486
|
|
|
425
|
-
this.emit('message:received', { message, sessionId, userId
|
|
487
|
+
this.emit('message:received', { message, sessionId, userId });
|
|
426
488
|
|
|
427
489
|
// Build LLM message history
|
|
428
490
|
const history = await this.memory.getHistory(sessionId);
|
|
429
491
|
|
|
430
492
|
// RAG retrieval
|
|
431
493
|
let ragContext = '';
|
|
432
|
-
|
|
494
|
+
let retrievedDocuments: Array<{ source: string; score: number; excerpt: string }> = [];
|
|
495
|
+
if (this.knowledgeBase && this.retriever) {
|
|
433
496
|
ragContext = await this.knowledgeBase.retrieve(message);
|
|
497
|
+
// Also retrieve chunks to get document metadata and scores
|
|
498
|
+
try {
|
|
499
|
+
console.log(`[DEBUG] Calling retrieveChunks for query: "${message}"`);
|
|
500
|
+
const context = await this.retriever.retrieveChunks(message, 3);
|
|
501
|
+
console.log(`[DEBUG] Retrieved ${context.chunks.length} chunks`);
|
|
502
|
+
retrievedDocuments = context.chunks.map((item: any) => ({
|
|
503
|
+
source: item.chunk?.source || 'unknown',
|
|
504
|
+
score: Math.round(item.score * 100) / 100,
|
|
505
|
+
excerpt: item.chunk?.content?.substring(0, 150) || '',
|
|
506
|
+
}));
|
|
507
|
+
console.log(`[DEBUG] Mapped ${retrievedDocuments.length} documents`);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
console.error(`[ERROR] RAG retrieval failed:`, e);
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
console.log(`[DEBUG] No knowledgeBase (${!!this.knowledgeBase}) or retriever (${!!this.retriever})`);
|
|
434
513
|
}
|
|
435
514
|
|
|
436
515
|
const messages = this.context.buildMessages(
|
|
@@ -442,7 +521,7 @@ export class SvaraAgent extends EventEmitter {
|
|
|
442
521
|
|
|
443
522
|
const internalCtx: InternalAgentContext = {
|
|
444
523
|
sessionId,
|
|
445
|
-
userId
|
|
524
|
+
userId,
|
|
446
525
|
agentName: this.name,
|
|
447
526
|
history,
|
|
448
527
|
metadata: options.metadata ?? {},
|
|
@@ -521,6 +600,7 @@ export class SvaraAgent extends EventEmitter {
|
|
|
521
600
|
iterations,
|
|
522
601
|
usage: totalUsage,
|
|
523
602
|
duration: Date.now() - startTime,
|
|
603
|
+
retrievedDocuments: retrievedDocuments.length > 0 ? retrievedDocuments : undefined,
|
|
524
604
|
};
|
|
525
605
|
|
|
526
606
|
this.emit('message:sent', { response: finalResponse, sessionId });
|
|
@@ -534,8 +614,9 @@ export class SvaraAgent extends EventEmitter {
|
|
|
534
614
|
const { glob } = await import('glob');
|
|
535
615
|
const { VectorRetriever } = await import('../rag/retriever.js');
|
|
536
616
|
|
|
537
|
-
|
|
538
|
-
|
|
617
|
+
// Create retriever with agent name for isolated RAG per agent
|
|
618
|
+
this.retriever = new VectorRetriever(this.name, this.db);
|
|
619
|
+
await this.retriever.init({ embeddings: { provider: 'openai' } });
|
|
539
620
|
|
|
540
621
|
const files: string[] = [];
|
|
541
622
|
for (const pattern of paths) {
|
|
@@ -548,16 +629,16 @@ export class SvaraAgent extends EventEmitter {
|
|
|
548
629
|
return;
|
|
549
630
|
}
|
|
550
631
|
|
|
551
|
-
await retriever.addDocuments(files);
|
|
632
|
+
await this.retriever.addDocuments(files);
|
|
552
633
|
this.knowledgeBase = {
|
|
553
634
|
load: async (p) => {
|
|
554
635
|
const newFiles: string[] = [];
|
|
555
636
|
for (const pattern of (Array.isArray(p) ? p : [p])) {
|
|
556
637
|
newFiles.push(...await glob(pattern));
|
|
557
638
|
}
|
|
558
|
-
await retriever.addDocuments(newFiles);
|
|
639
|
+
await this.retriever.addDocuments(newFiles);
|
|
559
640
|
},
|
|
560
|
-
retrieve: (query, topK) => retriever.retrieve(query, topK),
|
|
641
|
+
retrieve: (query, topK) => this.retriever.retrieve(query, topK),
|
|
561
642
|
};
|
|
562
643
|
|
|
563
644
|
this.log('info', `Knowledge base loaded: ${files.length} file(s).`);
|