@dastbal/nestjs-ai-agent 1.0.0
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/README.md +116 -0
- package/dist/ai-agent.module.d.ts +2 -0
- package/dist/ai-agent.module.js +30 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +81 -0
- package/dist/core/agent/factory.d.ts +24 -0
- package/dist/core/agent/factory.js +155 -0
- package/dist/core/agent/safe-backend.d.ts +46 -0
- package/dist/core/agent/safe-backend.js +113 -0
- package/dist/core/llm/provider.d.ts +8 -0
- package/dist/core/llm/provider.js +84 -0
- package/dist/core/rag/indexer.d.ts +37 -0
- package/dist/core/rag/indexer.js +215 -0
- package/dist/core/rag/math.d.ts +11 -0
- package/dist/core/rag/math.js +29 -0
- package/dist/core/rag/retriever.d.ts +28 -0
- package/dist/core/rag/retriever.js +156 -0
- package/dist/core/state/db.d.ts +23 -0
- package/dist/core/state/db.js +118 -0
- package/dist/core/state/file-registry.d.ts +38 -0
- package/dist/core/state/file-registry.js +112 -0
- package/dist/core/tools/ast/chunker.d.ts +58 -0
- package/dist/core/tools/ast/chunker.js +297 -0
- package/dist/core/tools/ast/definitions.d.ts +0 -0
- package/dist/core/tools/ast/definitions.js +29 -0
- package/dist/core/tools/tools.d.ts +42 -0
- package/dist/core/tools/tools.js +196 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/package.json +52 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LLMProvider = void 0;
|
|
37
|
+
const google_vertexai_1 = require("@langchain/google-vertexai");
|
|
38
|
+
const dotenv = __importStar(require("dotenv"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
// 1. Cargar variables de entorno desde la RAÍZ del proyecto
|
|
42
|
+
// process.cwd() obtiene la carpeta desde donde ejecutas "npm run agent"
|
|
43
|
+
const rootDir = process.cwd();
|
|
44
|
+
dotenv.config({ path: path.join(rootDir, '.env.development') });
|
|
45
|
+
class LLMProvider {
|
|
46
|
+
constructor() { }
|
|
47
|
+
static getModel() {
|
|
48
|
+
if (!this.instance) {
|
|
49
|
+
// 2. Validación de variables críticas
|
|
50
|
+
const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
51
|
+
if (!credentialsPath) {
|
|
52
|
+
throw new Error('❌ GOOGLE_APPLICATION_CREDENTIALS no está definido en el .env');
|
|
53
|
+
}
|
|
54
|
+
// 3. Convertir ruta relativa a ABSOLUTA (Truco para Windows)
|
|
55
|
+
// Si dice "./credentials.json", lo convierte a "C:\Users\...\credentials.json"
|
|
56
|
+
const absoluteCredentialsPath = path.resolve(rootDir, credentialsPath);
|
|
57
|
+
// Verificamos que el archivo JSON realmente exista antes de intentar conectar
|
|
58
|
+
if (!fs.existsSync(absoluteCredentialsPath)) {
|
|
59
|
+
throw new Error(`❌ No se encuentra el archivo de credenciales en: ${absoluteCredentialsPath}`);
|
|
60
|
+
}
|
|
61
|
+
// Sobrescribimos la variable de entorno con la ruta absoluta para que la librería de Google la lea bien
|
|
62
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = absoluteCredentialsPath;
|
|
63
|
+
console.log(`📄 Usando credenciales: ${absoluteCredentialsPath}`);
|
|
64
|
+
this.instance = new google_vertexai_1.ChatVertexAI({
|
|
65
|
+
// Usamos tus variables específicas
|
|
66
|
+
model: process.env.GOOGLE_CLOUD_MODEL_NAME || 'gemini-2.0-flash-lite-001',
|
|
67
|
+
temperature: 0,
|
|
68
|
+
// maxOutputTokens: 8192,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return this.instance;
|
|
72
|
+
}
|
|
73
|
+
static getEmbeddingsModel() {
|
|
74
|
+
if (!this.embeddingsInstance) {
|
|
75
|
+
// Validar credentials igual que antes...
|
|
76
|
+
this.embeddingsInstance = new google_vertexai_1.VertexAIEmbeddings({
|
|
77
|
+
model: 'text-embedding-004', // El modelo más eficiente de Google actualmente
|
|
78
|
+
// Los mismos parámetros de location y projectID que ya configuramos
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return this.embeddingsInstance;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.LLMProvider = LLMProvider;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Indexer Service (The Orchestrator) 🎼
|
|
3
|
+
* Responsible for keeping the AI memory in sync with the codebase.
|
|
4
|
+
* It coordinates the FileRegistry, AST Parser, and Vector Store.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IndexerService {
|
|
7
|
+
private registry;
|
|
8
|
+
private chunker;
|
|
9
|
+
private db;
|
|
10
|
+
private BATCH_SIZE;
|
|
11
|
+
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Main Entry Point: Scans the project and updates the brain.
|
|
14
|
+
* Scans files, checks hashes, generates embeddings, and saves the knowledge graph.
|
|
15
|
+
* * @param sourceDir - Relative path to source code (usually 'src').
|
|
16
|
+
*/
|
|
17
|
+
indexProject(sourceDir?: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Processes a single file: Reads content, Calculates Hash, Parses AST,
|
|
20
|
+
* Updates Registry, and Accumulates Chunks.
|
|
21
|
+
*/
|
|
22
|
+
private processSingleFile;
|
|
23
|
+
/**
|
|
24
|
+
* Generates embeddings using Vertex AI and saves them to SQLite in transactions.
|
|
25
|
+
*/
|
|
26
|
+
private embedAndSaveBatches;
|
|
27
|
+
/**
|
|
28
|
+
* Persists dependency relationships into the graph table.
|
|
29
|
+
* Uses 'INSERT OR IGNORE' to prevent duplicates without errors.
|
|
30
|
+
*/
|
|
31
|
+
private saveGraph;
|
|
32
|
+
/**
|
|
33
|
+
* Recursively gets all .ts files in a directory.
|
|
34
|
+
* Returns RELATIVE paths (e.g., 'src/users/users.service.ts') to ensure consistency in DB.
|
|
35
|
+
*/
|
|
36
|
+
private getAllFiles;
|
|
37
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IndexerService = void 0;
|
|
37
|
+
const file_registry_1 = require("../state/file-registry");
|
|
38
|
+
const chunker_1 = require("../tools/ast/chunker");
|
|
39
|
+
const db_1 = require("../state/db");
|
|
40
|
+
const provider_1 = require("../llm/provider");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
/**
|
|
44
|
+
* The Indexer Service (The Orchestrator) 🎼
|
|
45
|
+
* Responsible for keeping the AI memory in sync with the codebase.
|
|
46
|
+
* It coordinates the FileRegistry, AST Parser, and Vector Store.
|
|
47
|
+
*/
|
|
48
|
+
class IndexerService {
|
|
49
|
+
constructor() {
|
|
50
|
+
// Optimization: Send chunks to Vertex AI in groups to respect rate limits and improve speed.
|
|
51
|
+
this.BATCH_SIZE = 10;
|
|
52
|
+
this.registry = new file_registry_1.FileRegistry();
|
|
53
|
+
this.chunker = new chunker_1.NestChunker();
|
|
54
|
+
this.db = db_1.AgentDB.getInstance();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Main Entry Point: Scans the project and updates the brain.
|
|
58
|
+
* Scans files, checks hashes, generates embeddings, and saves the knowledge graph.
|
|
59
|
+
* * @param sourceDir - Relative path to source code (usually 'src').
|
|
60
|
+
*/
|
|
61
|
+
async indexProject(sourceDir = 'src') {
|
|
62
|
+
const rootDir = process.cwd();
|
|
63
|
+
const fullSourceDir = path.join(rootDir, sourceDir);
|
|
64
|
+
console.log(`🚀 Starting Indexing Process on: ${sourceDir}`);
|
|
65
|
+
const files = this.getAllFiles(fullSourceDir);
|
|
66
|
+
const filesToProcess = [];
|
|
67
|
+
// Check changes
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (this.registry.isFileChanged(file)) {
|
|
70
|
+
filesToProcess.push(file);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (filesToProcess.length === 0) {
|
|
74
|
+
console.log('✨ Project is up to date.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log(`📦 Found ${filesToProcess.length} files to process.`);
|
|
78
|
+
// --- CAMBIO IMPORTANTE ---
|
|
79
|
+
// Acumuladores separados
|
|
80
|
+
const pendingChunks = [];
|
|
81
|
+
const pendingEdges = []; // <--- Acumulamos el grafo aquí
|
|
82
|
+
// 1. PRIMERA PASADA: Registrar archivos y generar datos
|
|
83
|
+
for (const filePath of filesToProcess) {
|
|
84
|
+
await this.processSingleFile(filePath, pendingChunks, pendingEdges);
|
|
85
|
+
}
|
|
86
|
+
// 2. SEGUNDA PASADA: Guardar Grafo (Ahora que todos los archivos existen en registry)
|
|
87
|
+
if (pendingEdges.length > 0) {
|
|
88
|
+
console.log(`🕸️ Saving ${pendingEdges.length} dependency relations...`);
|
|
89
|
+
this.saveGraph(pendingEdges);
|
|
90
|
+
}
|
|
91
|
+
// 3. TERCERA PASADA: Guardar Vectores
|
|
92
|
+
if (pendingChunks.length > 0) {
|
|
93
|
+
await this.embedAndSaveBatches(pendingChunks);
|
|
94
|
+
}
|
|
95
|
+
console.log('✅ Indexing Complete.');
|
|
96
|
+
}
|
|
97
|
+
// ==========================================
|
|
98
|
+
// ⚙️ INTERNAL LOGIC
|
|
99
|
+
// ==========================================
|
|
100
|
+
/**
|
|
101
|
+
* Processes a single file: Reads content, Calculates Hash, Parses AST,
|
|
102
|
+
* Updates Registry, and Accumulates Chunks.
|
|
103
|
+
*/
|
|
104
|
+
processSingleFile(filePath, chunkAccumulator, edgeAccumulator) {
|
|
105
|
+
try {
|
|
106
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
107
|
+
// A. Calculate Hash
|
|
108
|
+
const hash = require('crypto')
|
|
109
|
+
.createHash('md5')
|
|
110
|
+
.update(content)
|
|
111
|
+
.digest('hex');
|
|
112
|
+
// B. Analyze
|
|
113
|
+
const analysis = this.chunker.analyze(filePath, content, hash);
|
|
114
|
+
// --- CAMBIO CLAVE: ORDEN DE OPERACIONES ---
|
|
115
|
+
// 1. PRIMERO: Registrar el archivo en DB.
|
|
116
|
+
// Si no hacemos esto, el foreign key de 'source' fallará si intentáramos guardar algo.
|
|
117
|
+
this.registry.updateFile(filePath, analysis.skeleton);
|
|
118
|
+
// 2. SEGUNDO: Acumular relaciones para guardarlas DESPUÉS
|
|
119
|
+
// No llamamos a this.saveGraph() aquí.
|
|
120
|
+
edgeAccumulator.push(...analysis.dependencies);
|
|
121
|
+
// 3. TERCERO: Acumular Chunks
|
|
122
|
+
const chunksWithFile = analysis.chunks.map((c) => ({
|
|
123
|
+
...c,
|
|
124
|
+
filePath: filePath,
|
|
125
|
+
}));
|
|
126
|
+
chunkAccumulator.push(...chunksWithFile);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error(`❌ Error processing file ${filePath}:`, error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generates embeddings using Vertex AI and saves them to SQLite in transactions.
|
|
134
|
+
*/
|
|
135
|
+
async embedAndSaveBatches(allChunks) {
|
|
136
|
+
console.log(`🧠 Generating Embeddings for ${allChunks.length} chunks...`);
|
|
137
|
+
for (let i = 0; i < allChunks.length; i += this.BATCH_SIZE) {
|
|
138
|
+
const batch = allChunks.slice(i, i + this.BATCH_SIZE);
|
|
139
|
+
// 1. Prepare Text for Embedding
|
|
140
|
+
// CRITICAL: We embed "metadata + content" for better semantic search results.
|
|
141
|
+
// This allows the LLM to find "UsersService method" even if the code doesn't say "User".
|
|
142
|
+
const textsToEmbed = batch.map((c) => {
|
|
143
|
+
const metaStr = c.metadata.methodName
|
|
144
|
+
? `Method: ${c.metadata.methodName}`
|
|
145
|
+
: `Class: ${c.metadata.className}`;
|
|
146
|
+
return `${metaStr}\n${c.content}`;
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
// 2. Call Vertex AI (Embeddings API)
|
|
150
|
+
const embeddingsModel = provider_1.LLMProvider.getEmbeddingsModel();
|
|
151
|
+
const vectors = await embeddingsModel.embedDocuments(textsToEmbed);
|
|
152
|
+
// 3. Save to DB (Transaction for performance)
|
|
153
|
+
const insertChunk = this.db.prepare(`
|
|
154
|
+
INSERT OR REPLACE INTO code_chunks (id, file_path, chunk_type, content, vector_json, metadata)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
156
|
+
`);
|
|
157
|
+
// Explicitly typed transaction callback to fix TS7006
|
|
158
|
+
const insertMany = this.db.transaction((chunks, vectors) => {
|
|
159
|
+
chunks.forEach((chunk, idx) => {
|
|
160
|
+
insertChunk.run(chunk.id, chunk.filePath, // filePath added in processSingleFile
|
|
161
|
+
chunk.type, chunk.content, JSON.stringify(vectors[idx]), // Serialize vector to string for storage
|
|
162
|
+
JSON.stringify(chunk.metadata));
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
insertMany(batch, vectors);
|
|
166
|
+
process.stdout.write('.'); // Visual feedback
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.error('❌ Embedding Error:', err);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
console.log('\n💾 Vectors Saved.');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Persists dependency relationships into the graph table.
|
|
176
|
+
* Uses 'INSERT OR IGNORE' to prevent duplicates without errors.
|
|
177
|
+
*/
|
|
178
|
+
saveGraph(edges) {
|
|
179
|
+
if (!edges || edges.length === 0)
|
|
180
|
+
return;
|
|
181
|
+
const insertEdge = this.db.prepare(`
|
|
182
|
+
INSERT OR IGNORE INTO dependency_graph (source, target, relation)
|
|
183
|
+
VALUES (?, ?, ?)
|
|
184
|
+
`);
|
|
185
|
+
// Explicitly typed transaction callback to fix TS7006
|
|
186
|
+
const runMany = this.db.transaction((edges) => {
|
|
187
|
+
edges.forEach((edge) => insertEdge.run(edge.sourcePath, edge.targetPath, edge.relation));
|
|
188
|
+
});
|
|
189
|
+
runMany(edges);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Recursively gets all .ts files in a directory.
|
|
193
|
+
* Returns RELATIVE paths (e.g., 'src/users/users.service.ts') to ensure consistency in DB.
|
|
194
|
+
*/
|
|
195
|
+
getAllFiles(dir, fileList = []) {
|
|
196
|
+
const files = fs.readdirSync(dir);
|
|
197
|
+
files.forEach((file) => {
|
|
198
|
+
const absolutePath = path.join(dir, file);
|
|
199
|
+
const stat = fs.statSync(absolutePath);
|
|
200
|
+
if (stat.isDirectory()) {
|
|
201
|
+
this.getAllFiles(absolutePath, fileList);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Filter: Only TS files, ignore tests (.spec.ts)
|
|
205
|
+
if (file.endsWith('.ts') && !file.endsWith('.spec.ts')) {
|
|
206
|
+
// KEY FIX: Normalize to relative path before adding to list
|
|
207
|
+
const relativePath = path.relative(process.cwd(), absolutePath);
|
|
208
|
+
fileList.push(relativePath);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return fileList;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.IndexerService = IndexerService;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mathematical utilities for Vector Search.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculates the Cosine Similarity between two vectors.
|
|
6
|
+
* A score of 1.0 means identical direction (perfect match).
|
|
7
|
+
* A score of 0.0 means orthogonal (no relation).
|
|
8
|
+
* * @param vecA - The query vector
|
|
9
|
+
* @param vecB - The database vector
|
|
10
|
+
*/
|
|
11
|
+
export declare function cosineSimilarity(vecA: number[], vecB: number[]): number;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mathematical utilities for Vector Search.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cosineSimilarity = cosineSimilarity;
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the Cosine Similarity between two vectors.
|
|
9
|
+
* A score of 1.0 means identical direction (perfect match).
|
|
10
|
+
* A score of 0.0 means orthogonal (no relation).
|
|
11
|
+
* * @param vecA - The query vector
|
|
12
|
+
* @param vecB - The database vector
|
|
13
|
+
*/
|
|
14
|
+
function cosineSimilarity(vecA, vecB) {
|
|
15
|
+
if (vecA.length !== vecB.length) {
|
|
16
|
+
throw new Error('Vectors must have the same dimensionality');
|
|
17
|
+
}
|
|
18
|
+
let dotProduct = 0;
|
|
19
|
+
let normA = 0;
|
|
20
|
+
let normB = 0;
|
|
21
|
+
for (let i = 0; i < vecA.length; i++) {
|
|
22
|
+
dotProduct += vecA[i] * vecB[i];
|
|
23
|
+
normA += vecA[i] * vecA[i];
|
|
24
|
+
normB += vecB[i] * vecB[i];
|
|
25
|
+
}
|
|
26
|
+
if (normA === 0 || normB === 0)
|
|
27
|
+
return 0;
|
|
28
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ProcessedChunk } from '../types';
|
|
2
|
+
interface SearchResult {
|
|
3
|
+
chunk: ProcessedChunk;
|
|
4
|
+
score: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class RetrieverService {
|
|
7
|
+
private db;
|
|
8
|
+
/**
|
|
9
|
+
* Searches the codebase using Vector Embeddings (Cosine Similarity).
|
|
10
|
+
* @param query - The natural language query.
|
|
11
|
+
* @param limit - Max chunks to retrieve.
|
|
12
|
+
*/
|
|
13
|
+
query(query: string, limit?: number): Promise<SearchResult[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves the 'Graph Dependencies' for a specific file from the DB.
|
|
16
|
+
* This allows the Agent to know what other files are related (DTOs, Interfaces).
|
|
17
|
+
*/
|
|
18
|
+
private getDependencies;
|
|
19
|
+
/**
|
|
20
|
+
* Generates a Rich Context Report for the LLM.
|
|
21
|
+
* It combines:
|
|
22
|
+
* 1. The matched code snippets (Vector Search).
|
|
23
|
+
* 2. The file's dependencies (Graph Search).
|
|
24
|
+
* 3. Explicit File Paths to encourage using 'read_file'.
|
|
25
|
+
*/
|
|
26
|
+
getContextForLLM(query: string): Promise<string>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RetrieverService = void 0;
|
|
37
|
+
const db_1 = require("../state/db");
|
|
38
|
+
const provider_1 = require("../llm/provider");
|
|
39
|
+
const math_1 = require("./math");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
class RetrieverService {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.db = db_1.AgentDB.getInstance();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Searches the codebase using Vector Embeddings (Cosine Similarity).
|
|
47
|
+
* @param query - The natural language query.
|
|
48
|
+
* @param limit - Max chunks to retrieve.
|
|
49
|
+
*/
|
|
50
|
+
async query(query, limit = 5) {
|
|
51
|
+
console.log(`🔍 [RAG] Embedding Query: "${query}"...`);
|
|
52
|
+
const embeddingModel = provider_1.LLMProvider.getEmbeddingsModel();
|
|
53
|
+
const queryVector = await embeddingModel.embedQuery(query);
|
|
54
|
+
const stmt = this.db.prepare('SELECT * FROM code_chunks');
|
|
55
|
+
const rows = stmt.all();
|
|
56
|
+
const scoredChunks = rows.map((row) => {
|
|
57
|
+
const vector = JSON.parse(row.vector_json);
|
|
58
|
+
const score = (0, math_1.cosineSimilarity)(queryVector, vector);
|
|
59
|
+
const metadata = JSON.parse(row.metadata);
|
|
60
|
+
return {
|
|
61
|
+
score,
|
|
62
|
+
chunk: {
|
|
63
|
+
id: row.id,
|
|
64
|
+
type: row.chunk_type,
|
|
65
|
+
content: row.content,
|
|
66
|
+
metadata: metadata,
|
|
67
|
+
// Ensure filePath is recovered from the DB row or metadata
|
|
68
|
+
filePath: row.file_path || metadata.filePath,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
return scoredChunks.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves the 'Graph Dependencies' for a specific file from the DB.
|
|
76
|
+
* This allows the Agent to know what other files are related (DTOs, Interfaces).
|
|
77
|
+
*/
|
|
78
|
+
getDependencies(sourcePath) {
|
|
79
|
+
// 1. IMPORTANTE: Normalizar la ruta para que coincida con lo guardado en DB
|
|
80
|
+
// Esto convierte "src\module\..." a "src/module/..."
|
|
81
|
+
const normalizedPath = sourcePath.split(path.sep).join('/');
|
|
82
|
+
try {
|
|
83
|
+
// 2. Consultar la tabla dependency_graph que definiste en AgentDB
|
|
84
|
+
// Buscamos todo lo que este archivo (source) importa (target)
|
|
85
|
+
const stmt = this.db.prepare(`
|
|
86
|
+
SELECT target
|
|
87
|
+
FROM dependency_graph
|
|
88
|
+
WHERE source = ? OR source = ?
|
|
89
|
+
`);
|
|
90
|
+
// Probamos con la ruta normalizada y la original por si acaso
|
|
91
|
+
const results = stmt.all(normalizedPath, sourcePath);
|
|
92
|
+
// 3. Devolver solo los strings de los targets
|
|
93
|
+
return results.map((row) => row.target);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error(`Error fetching dependencies for ${sourcePath}:`, error);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generates a Rich Context Report for the LLM.
|
|
102
|
+
* It combines:
|
|
103
|
+
* 1. The matched code snippets (Vector Search).
|
|
104
|
+
* 2. The file's dependencies (Graph Search).
|
|
105
|
+
* 3. Explicit File Paths to encourage using 'read_file'.
|
|
106
|
+
*/
|
|
107
|
+
async getContextForLLM(query) {
|
|
108
|
+
const results = await this.query(query, 4);
|
|
109
|
+
// Group chunks by File to provide a structured view
|
|
110
|
+
const filesMap = new Map();
|
|
111
|
+
for (const res of results) {
|
|
112
|
+
const path = res.chunk.filePath || 'unknown';
|
|
113
|
+
// console.log(res);
|
|
114
|
+
// console.log(path);
|
|
115
|
+
// console.log(this.getDependencies(path));
|
|
116
|
+
if (!filesMap.has(path)) {
|
|
117
|
+
filesMap.set(path, {
|
|
118
|
+
filePath: path,
|
|
119
|
+
relevance: res.score,
|
|
120
|
+
chunks: [],
|
|
121
|
+
imports: this.getDependencies(path), // <--- GRAPH MAGIC 🕸️
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
filesMap.get(path)?.chunks.push(res.chunk);
|
|
125
|
+
}
|
|
126
|
+
// Build the formatted string
|
|
127
|
+
let output = `🔎 **RAG ANALYSIS REPORT**\n`;
|
|
128
|
+
output += `Query: "${query}"\n`;
|
|
129
|
+
output += `Found ${filesMap.size} relevant files.\n\n`;
|
|
130
|
+
filesMap.forEach((fileCtx) => {
|
|
131
|
+
const relevancePct = (fileCtx.relevance * 100).toFixed(1);
|
|
132
|
+
output += `=================================================================\n`;
|
|
133
|
+
output += `📂 **FILE:** ${fileCtx.filePath}\n`;
|
|
134
|
+
output += `📊 **RELEVANCE:** ${relevancePct}%\n`;
|
|
135
|
+
if (fileCtx.imports.length > 0) {
|
|
136
|
+
output += `🔗 **DEPENDENCIES (Imports):**\n`;
|
|
137
|
+
// Show top 5 imports to give context on DTOs/Entities used
|
|
138
|
+
fileCtx.imports
|
|
139
|
+
.slice(0, 5)
|
|
140
|
+
.forEach((imp) => (output += ` - ${imp}\n`));
|
|
141
|
+
if (fileCtx.imports.length > 5)
|
|
142
|
+
output += ` - (...and ${fileCtx.imports.length - 5} more)\n`;
|
|
143
|
+
}
|
|
144
|
+
output += `\n📝 **CODE SNIPPETS:**\n`;
|
|
145
|
+
fileCtx.chunks.forEach((chunk) => {
|
|
146
|
+
output += ` --- [${chunk.metadata.methodName || 'Class Structure'}] ---\n`;
|
|
147
|
+
output += `${chunk.content.trim()}\n\n`;
|
|
148
|
+
});
|
|
149
|
+
output += `💡 **AGENT HINT:** To edit this file or see full imports, run: read_file("${fileCtx.filePath}")\n`;
|
|
150
|
+
output += `=================================================================\n\n`;
|
|
151
|
+
});
|
|
152
|
+
// console.log(output);
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.RetrieverService = RetrieverService;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Singleton Database Manager.
|
|
4
|
+
* Handles the connection to the local SQLite instance used for caching and state management.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AgentDB {
|
|
7
|
+
private static instance;
|
|
8
|
+
/**
|
|
9
|
+
* Private constructor to enforce Singleton pattern.
|
|
10
|
+
*/
|
|
11
|
+
private constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves the active database connection.
|
|
14
|
+
* If it doesn't exist, it initializes the DB file and the schema.
|
|
15
|
+
* * @returns {Database.Database} The SQLite connection instance.
|
|
16
|
+
*/
|
|
17
|
+
static getInstance(): Database.Database;
|
|
18
|
+
/**
|
|
19
|
+
* Initializes the database tables based on our architectural plan.
|
|
20
|
+
* 1. file_registry: Tracks file hashes and cached skeletons.
|
|
21
|
+
*/
|
|
22
|
+
private static initSchema;
|
|
23
|
+
}
|