This commit is contained in:
@@ -1,166 +1,74 @@
|
|||||||
# RAG Agent with Retrieval-Augmented Generation
|
# Agent with RAG Memory
|
||||||
|
|
||||||
**Version:** 20
|
This project implements a simple command‑line agent that uses **Ollama embeddings** for a Retrieval‑Augmented Generation (RAG) style knowledge base.
|
||||||
**Author:** Artur Kuzakhmetov
|
The agent supports two main tools:
|
||||||
**Course:** Deep Agents Virtual File System
|
|
||||||
**Deadline:** 31.08.2026
|
|
||||||
|
|
||||||
---
|
- **`search_knowledge_base`** – find the most relevant documents for a query.
|
||||||
|
- **`add_to_knowledge_base`** – add new content to the knowledge base.
|
||||||
## Overview
|
|
||||||
|
|
||||||
This repository implements an educational agent that uses Retrieval-Augmented Generation (RAG) to answer user queries.
|
|
||||||
The agent:
|
|
||||||
|
|
||||||
1. **Embeds** a collection of text documents into a FAISS vector store using OpenAI embeddings.
|
|
||||||
2. **Retrieves** the most relevant passages for a user query.
|
|
||||||
3. **Generates** a response with OpenAI GPT‑4, conditioned on the retrieved context.
|
|
||||||
|
|
||||||
The agent is exposed via a FastAPI web service with a single `/ask` endpoint.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── data/ # Place your .txt documents here
|
|
||||||
├── src/
|
|
||||||
│ └── index.py # FastAPI app and RAG logic
|
|
||||||
├── .env # (Optional) Environment variables
|
|
||||||
├── README.md
|
|
||||||
└── requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** The `data/` directory is **not** committed to version control.
|
|
||||||
> Add your own documents there before running the agent.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### 1. Clone the Repository
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Clone the repository
|
||||||
git clone https://git.brojs.ru/kuzakhmetovartur/agent-s-rag-pamyatyu.git
|
git clone https://git.brojs.ru/kuzakhmetovartur/agent-s-rag-pamyatyu.git
|
||||||
cd agent-s-rag-pamyatyu
|
cd agent-s-rag-pamyatyu
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Create a Virtual Environment
|
> **Note**: The project uses the `ollama-embeddings` package.
|
||||||
|
> Make sure you have an Ollama server running locally (default `http://localhost:11434`).
|
||||||
|
> You can change the host or model via environment variables:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv .venv
|
# Example .env file
|
||||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
OLLAMA_HOST=http://localhost:11434
|
||||||
|
OLLAMA_MODEL=all-minilm
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
> `requirements.txt` contains:
|
|
||||||
> ```text
|
|
||||||
> fastapi
|
|
||||||
> uvicorn
|
|
||||||
> langchain
|
|
||||||
> openai
|
|
||||||
> faiss-cpu
|
|
||||||
> python-dotenv
|
|
||||||
> ```
|
|
||||||
|
|
||||||
### 4. Set Up OpenAI API Key
|
|
||||||
|
|
||||||
Create a file named `.env` in the project root:
|
|
||||||
|
|
||||||
```dotenv
|
|
||||||
OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Security:** Do **not** commit the `.env` file to version control.
|
|
||||||
> Add it to `.gitignore` if you have one.
|
|
||||||
|
|
||||||
### 5. Add Documents
|
|
||||||
|
|
||||||
Place any number of `.txt` files in the `data/` directory.
|
|
||||||
Each file will be treated as a separate document.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running the Agent
|
## Running the Agent
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvicorn src.index:app --reload
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The API will be available at `http://127.0.0.1:8000`.
|
You will see a prompt:
|
||||||
|
|
||||||
### Example Request
|
```
|
||||||
|
Agent>
|
||||||
```bash
|
|
||||||
curl -X POST "http://127.0.0.1:8000/ask" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"question":"What is the capital of France?"}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response**
|
### Commands
|
||||||
|
|
||||||
```json
|
- `/search <query>` – Search the knowledge base for the most relevant documents.
|
||||||
{
|
- `/add <content>` – Add new content to the knowledge base.
|
||||||
"answer": "The capital of France is Paris.",
|
- `/exit` – Exit the program.
|
||||||
"sources": ["data/geo_facts.txt"]
|
|
||||||
}
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent> /add The quick brown fox jumps over the lazy dog.
|
||||||
|
Content added with id 3f1c2e4b-...
|
||||||
|
|
||||||
|
Agent> /search fox
|
||||||
|
Searching for "fox"...
|
||||||
|
Top results:
|
||||||
|
1. [3f1c2e4b-...] (0.9123)
|
||||||
|
The quick brown fox jumps over the lazy dog.
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Project Structure
|
||||||
|
|
||||||
## Architecture Details
|
- `src/embeddings.js` – Wrapper around `ollama-embeddings`.
|
||||||
|
- `src/tools/searchKnowledgeBase.js` – Implements the search tool.
|
||||||
|
- `src/tools/addToKnowledgeBase.js` – Implements the add tool.
|
||||||
|
- `src/index.js` – CLI entry point and agent logic.
|
||||||
|
- `package.json` – Dependencies and scripts.
|
||||||
|
|
||||||
| Component | Purpose | Library |
|
## Extending
|
||||||
|-----------|---------|---------|
|
|
||||||
| **Document Loader** | Reads `.txt` files from `data/` | `langchain.document_loaders.DirectoryLoader` |
|
|
||||||
| **Embeddings** | Converts text to vectors | `langchain.embeddings.openai.OpenAIEmbeddings` |
|
|
||||||
| **Vector Store** | Stores and queries vectors | `langchain.vectorstores.FAISS` |
|
|
||||||
| **Retriever** | Finds top‑k relevant documents | FAISS retriever |
|
|
||||||
| **LLM** | Generates answer | `langchain.llms.OpenAI` (GPT‑4) |
|
|
||||||
| **Chain** | Combines retrieval and generation | `langchain.chains.RetrievalQA` |
|
|
||||||
| **API** | Exposes the agent | `FastAPI` |
|
|
||||||
|
|
||||||
---
|
The current implementation uses an in‑memory vector store.
|
||||||
|
To persist data or use a more sophisticated vector database, replace the `knowledgeBase` array in `searchKnowledgeBase.js` with your preferred storage solution.
|
||||||
## Testing
|
|
||||||
|
|
||||||
The repository includes a simple integration test in `tests/test_agent.py` (not shown here).
|
|
||||||
Run tests with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pytest
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Compliance with Course Guidelines
|
|
||||||
|
|
||||||
- **Educational Agent Solution**: The agent follows the structure outlined in the Deep Agents lecture, using a clear separation between data ingestion, retrieval, and generation.
|
|
||||||
- **RAG Memory**: Implemented via FAISS vector store and OpenAI embeddings.
|
|
||||||
- **Python 3.11+**: All code is compatible with Python 3.11 and above.
|
|
||||||
- **Individual Assignment**: All work is authored by a single developer (Artur Kuzakhmetov).
|
|
||||||
- **Versioning**: The repository is tagged as `v20` and the README reflects version 20.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is released under the MIT License.
|
|
||||||
Feel free to adapt and extend it for your own educational projects.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
For questions or feedback, contact:
|
|
||||||
|
|
||||||
- **Email:** artur.kuzakhmetov@example.com
|
|
||||||
- **GitLab:** https://git.brojs.ru/kuzakhmetovartur/agent-s-rag-pamyatyu
|
|
||||||
|
|
||||||
---
|
---
|
||||||
+67
-59
@@ -1,72 +1,80 @@
|
|||||||
**What was implemented**
|
**SOLUTION.md**
|
||||||
- A FastAPI service exposing a single `/ask` endpoint that accepts a user question and returns an answer together with the sources used.
|
|
||||||
- RAG (Retrieval‑Augmented Generation) logic built with LangChain: documents from `data/` are embedded with OpenAI embeddings, stored in a FAISS vector store, and queried by a `RetrievalQA` chain that feeds the retrieved passages to GPT‑4.
|
|
||||||
- Automatic startup loading of documents, vector store creation, and agent construction so the API is ready to serve immediately after launch.
|
|
||||||
|
|
||||||
**Why the main parts satisfy the assignment**
|
---
|
||||||
- **RAG memory**: `create_vectorstore` builds a FAISS index from the loaded documents, and `build_agent` wires this index into a `RetrievalQA` chain that retrieves relevant passages before generation.
|
|
||||||
- **Course guidelines**: The solution follows the Deep Agents Virtual File System pattern – a single `src/index.py` module, clear separation of concerns (loading, vector store, agent, API), and use of environment variables for secrets.
|
|
||||||
- **Python implementation**: All code is pure Python 3.11+, uses only standard libraries and well‑documented third‑party packages (`fastapi`, `langchain`, `openai`, `dotenv`).
|
|
||||||
- **Individual assignment**: No shared state or external services beyond the OpenAI API; the repository contains only the student’s code.
|
|
||||||
|
|
||||||
**Key code excerpts**
|
### Что реализовано
|
||||||
|
1. **Инструменты RAG**
|
||||||
|
* `search_knowledge_base(query, topK)` – ищет наиболее релевантные документы в памяти.
|
||||||
|
* `add_to_knowledge_base(content)` – добавляет новый контент в память.
|
||||||
|
|
||||||
*Loading documents* (`src/index.py`)
|
2. **Стек эмбеддингов**
|
||||||
```python
|
* Заменён `OpenAIEmbeddings` на `OllamaEmbeddings`.
|
||||||
def load_documents(path: Path) -> List:
|
* В `package.json` добавлена зависимость `ollama-embeddings`.
|
||||||
if not path.exists() or not path.is_dir():
|
|
||||||
print(f"Warning: Data directory '{path}' not found. No documents loaded.")
|
|
||||||
return []
|
|
||||||
|
|
||||||
loader = DirectoryLoader(str(path), glob="**/*.txt")
|
3. **Интеграция**
|
||||||
documents = loader.load()
|
* Инструменты подключены в `src/index.js` и доступны через CLI‑команды `/search` и `/add`.
|
||||||
print(f"Loaded {len(documents)} documents from '{path}'.")
|
* Все операции с эмбеддингами используют экземпляр `OllamaEmbeddings` из `src/embeddings.js`.
|
||||||
return documents
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Почему это соответствует требованиям
|
||||||
|
* **Наличие инструментов** – файлы `searchKnowledgeBase.js` и `addToKnowledgeBase.js` экспортируют требуемые функции, которые можно вызывать из любого модуля.
|
||||||
|
* **Использование OllamaEmbeddings** – в `embeddings.js` создаётся единственный экземпляр `OllamaEmbeddings`, а в инструментах вызывается `embeddings.embedQuery`.
|
||||||
|
* **Обновлённые импорты** – все модули импортируют `embeddings` из `src/embeddings.js`, а не из OpenAI.
|
||||||
|
* **Пакетная зависимость** – `ollama-embeddings` присутствует в `package.json`, что позволяет npm установить нужный пакет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ключевые фрагменты кода
|
||||||
|
|
||||||
|
**src/embeddings.js** – инициализация OllamaEmbeddings
|
||||||
|
```js
|
||||||
|
import { OllamaEmbeddings } from 'ollama-embeddings';
|
||||||
|
const modelName = process.env.OLLAMA_MODEL || 'all-minilm';
|
||||||
|
export const embeddings = new OllamaEmbeddings({
|
||||||
|
model: modelName,
|
||||||
|
host: process.env.OLLAMA_HOST || 'http://localhost:11434'
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
*Creating the vector store* (`src/index.py`)
|
**src/tools/searchKnowledgeBase.js** – поиск по памяти
|
||||||
```python
|
```js
|
||||||
def create_vectorstore(documents: List) -> FAISS:
|
export async function search_knowledge_base(query, topK = 3) {
|
||||||
embeddings = OpenAIEmbeddings()
|
const queryEmbedding = await embeddings.embedQuery(query);
|
||||||
vectorstore = FAISS.from_documents(documents, embeddings)
|
const scored = knowledgeBase.map(entry => ({
|
||||||
print("FAISS vector store created.")
|
id: entry.id,
|
||||||
return vectorstore
|
content: entry.content,
|
||||||
|
score: cosineSimilarity(queryEmbedding, entry.embedding)
|
||||||
|
}));
|
||||||
|
scored.sort((a, b) => b.score - a.score);
|
||||||
|
return scored.slice(0, topK);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*Building the RetrievalQA agent* (`src/index.py`)
|
**src/tools/addToKnowledgeBase.js** – добавление контента
|
||||||
```python
|
```js
|
||||||
def build_agent(vectorstore: FAISS) -> RetrievalQA:
|
export async function add_to_knowledge_base(content) {
|
||||||
llm = OpenAI(model_name="gpt-4", temperature=0, openai_api_key=OPENAI_API_KEY)
|
const embedding = await embeddings.embedQuery(content);
|
||||||
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
|
const id = uuidv4();
|
||||||
qa_chain = RetrievalQA.from_chain_type(
|
knowledgeBase.push({ id, content, embedding });
|
||||||
llm=llm,
|
return { id };
|
||||||
chain_type="stuff",
|
}
|
||||||
retriever=retriever,
|
|
||||||
return_source_documents=True,
|
|
||||||
)
|
|
||||||
print("RetrievalQA agent constructed.")
|
|
||||||
return qa_chain
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*FastAPI endpoint* (`src/index.py`)
|
**src/index.js** – CLI‑интеграция инструментов
|
||||||
```python
|
```js
|
||||||
@app.post("/ask", response_model=AnswerResponse)
|
import { search_knowledge_base } from './tools/searchKnowledgeBase.js';
|
||||||
def ask_question(request: QuestionRequest):
|
import { add_to_knowledge_base } from './tools/addToKnowledgeBase.js';
|
||||||
if not agent:
|
...
|
||||||
raise HTTPException(status_code=500, detail="Agent not initialized.")
|
if (trimmed.startsWith('/search ')) { … }
|
||||||
try:
|
else if (trimmed.startsWith('/add ')) { … }
|
||||||
result = agent({"question": request.question})
|
|
||||||
answer = result.get("answer", "")
|
|
||||||
sources = [doc.metadata.get("source", "") for doc in result.get("source_documents", [])]
|
|
||||||
return AnswerResponse(answer=answer, sources=sources)
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Honest limitations**
|
---
|
||||||
- The vector store is rebuilt on every server restart; no persistence across restarts.
|
|
||||||
- No caching of embeddings or query results, which may increase latency for repeated queries.
|
|
||||||
- Error handling is minimal – any exception during a request returns a generic 500 error.
|
|
||||||
- The solution assumes all documents are plain `.txt`; other formats would need additional loaders.
|
|
||||||
|
|
||||||
These points are acceptable for the current assignment scope and can be refined in future iterations.
|
### Ограничения
|
||||||
|
* Память реализована как простая массив‑объект, поэтому данные не сохраняются между перезапусками.
|
||||||
|
* Нет обработки ошибок при работе с Ollama (например, недоступность сервера).
|
||||||
|
* Для больших наборов данных поиск выполняется линейно; в продакшене стоит использовать индексирование.
|
||||||
|
|
||||||
|
---
|
||||||
+5
-5
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-s-rag-pamyatyu",
|
"name": "agent-s-rag-pamyatyu",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "RAG agent using ChromaDB as the vector store",
|
"description": "Agent with RAG memory using Ollama embeddings",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "commonjs",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js"
|
"start": "node src/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chromadb": "^0.3.0",
|
"ollama-embeddings": "^1.0.0",
|
||||||
"openai": "^3.3.0",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv": "^16.0.0"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { OllamaEmbeddings } from 'ollama-embeddings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of OllamaEmbeddings.
|
||||||
|
* The model name can be overridden via the OLLAMA_MODEL environment variable.
|
||||||
|
*/
|
||||||
|
const modelName = process.env.OLLAMA_MODEL || 'all-minilm';
|
||||||
|
export const embeddings = new OllamaEmbeddings({
|
||||||
|
model: modelName,
|
||||||
|
// Optional: specify the Ollama host if not default
|
||||||
|
host: process.env.OLLAMA_HOST || 'http://localhost:11434'
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to embed a single string.
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {Promise<number[]>} embedding vector
|
||||||
|
*/
|
||||||
|
export async function embedText(text) {
|
||||||
|
return await embeddings.embedQuery(text);
|
||||||
|
}
|
||||||
+66
-2
@@ -1,3 +1,67 @@
|
|||||||
const Agent = require('./agent');
|
import dotenv from 'dotenv';
|
||||||
|
import readline from 'readline';
|
||||||
|
import { search_knowledge_base } from './tools/searchKnowledgeBase.js';
|
||||||
|
import { add_to_knowledge_base } from './tools/addToKnowledgeBase.js';
|
||||||
|
|
||||||
module.exports = { Agent };
|
dotenv.config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple command-line agent that supports two commands:
|
||||||
|
* 1. /search <query> - searches the knowledge base
|
||||||
|
* 2. /add <content> - adds content to the knowledge base
|
||||||
|
* Any other input is treated as a normal message and the agent echoes it back.
|
||||||
|
*/
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
prompt: 'Agent> '
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Agent with RAG memory using Ollama embeddings.');
|
||||||
|
console.log('Commands:');
|
||||||
|
console.log(' /search <query> - Search knowledge base');
|
||||||
|
console.log(' /add <content> - Add content to knowledge base');
|
||||||
|
console.log(' /exit - Exit');
|
||||||
|
rl.prompt();
|
||||||
|
|
||||||
|
rl.on('line', async (line) => {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed === '/exit') {
|
||||||
|
rl.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith('/search ')) {
|
||||||
|
const query = trimmed.slice(8).trim();
|
||||||
|
if (!query) {
|
||||||
|
console.log('Please provide a query.');
|
||||||
|
} else {
|
||||||
|
console.log(`Searching for "${query}"...`);
|
||||||
|
const results = await search_knowledge_base(query);
|
||||||
|
if (results.length === 0) {
|
||||||
|
console.log('No relevant documents found.');
|
||||||
|
} else {
|
||||||
|
console.log('Top results:');
|
||||||
|
results.forEach((res, idx) => {
|
||||||
|
console.log(`${idx + 1}. [${res.id}] (${res.score.toFixed(4)})`);
|
||||||
|
console.log(` ${res.content}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (trimmed.startsWith('/add ')) {
|
||||||
|
const content = trimmed.slice(5).trim();
|
||||||
|
if (!content) {
|
||||||
|
console.log('Please provide content to add.');
|
||||||
|
} else {
|
||||||
|
const { id } = await add_to_knowledge_base(content);
|
||||||
|
console.log(`Content added with id ${id}.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Echo back the message (placeholder for more complex agent logic)
|
||||||
|
console.log(`You said: ${trimmed}`);
|
||||||
|
}
|
||||||
|
rl.prompt();
|
||||||
|
}).on('close', () => {
|
||||||
|
console.log('Goodbye!');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { embeddings } from '../embeddings.js';
|
||||||
|
import { knowledgeBase } from './searchKnowledgeBase.js';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new content to the knowledge base.
|
||||||
|
* @param {string} content
|
||||||
|
* @returns {Promise<{id: string}>}
|
||||||
|
*/
|
||||||
|
export async function add_to_knowledge_base(content) {
|
||||||
|
const embedding = await embeddings.embedQuery(content);
|
||||||
|
const id = uuidv4();
|
||||||
|
knowledgeBase.push({ id, content, embedding });
|
||||||
|
return { id };
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { embeddings } from '../embeddings.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory knowledge base.
|
||||||
|
* Each entry: { id, content, embedding }
|
||||||
|
*/
|
||||||
|
const knowledgeBase = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute cosine similarity between two vectors.
|
||||||
|
* @param {number[]} a
|
||||||
|
* @param {number[]} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function cosineSimilarity(a, b) {
|
||||||
|
const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
|
||||||
|
const normA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
|
||||||
|
const normB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
|
||||||
|
return dot / (normA * normB);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the knowledge base for the most relevant documents.
|
||||||
|
* @param {string} query
|
||||||
|
* @param {number} topK
|
||||||
|
* @returns {Promise<Array<{id: string, content: string, score: number}>>}
|
||||||
|
*/
|
||||||
|
export async function search_knowledge_base(query, topK = 3) {
|
||||||
|
const queryEmbedding = await embeddings.embedQuery(query);
|
||||||
|
const scored = knowledgeBase.map(entry => ({
|
||||||
|
id: entry.id,
|
||||||
|
content: entry.content,
|
||||||
|
score: cosineSimilarity(queryEmbedding, entry.embedding)
|
||||||
|
}));
|
||||||
|
scored.sort((a, b) => b.score - a.score);
|
||||||
|
return scored.slice(0, topK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose the knowledge base for other modules (e.g., add tool).
|
||||||
|
*/
|
||||||
|
export { knowledgeBase };
|
||||||
Reference in New Issue
Block a user