This commit is contained in:
@@ -1,132 +1,95 @@
|
|||||||
# Agent with RAG Memory
|
# Agent with RAG Memory (ChromaDB)
|
||||||
|
|
||||||
This repository contains a lightweight implementation of an agent that can
|
This project implements a Retrieval-Augmented Generation (RAG) agent that uses **ChromaDB** as its sole vector store. The agent can ingest documents, store their embeddings, retrieve relevant passages, and generate answers using OpenAI’s GPT models.
|
||||||
interact with a **Retrieval‑Augmented Generation (RAG)** knowledge base.
|
|
||||||
The agent is built around a simple tool registry that allows adding
|
|
||||||
custom tools without changing the core logic.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Knowledge Base Tool** – A file‑based key/value store that can be
|
- **Vector Store** – Uses ChromaDB for storing and querying embeddings.
|
||||||
queried, added to, and deleted from by both the agent and the CLI.
|
- **Embeddings** – Generated with OpenAI’s `text-embedding-ada-002`.
|
||||||
- **CLI Commands** – Simple command‑line interface for managing the
|
- **Chat** – Generates responses with OpenAI’s `gpt-3.5-turbo`.
|
||||||
knowledge base.
|
- **Public API** – The `Agent` class exposes `init`, `ingest`, and `ask` methods, keeping the original interface unchanged.
|
||||||
- **Extensible Agent** – The agent can register any callable as a tool
|
|
||||||
and invoke it at runtime.
|
|
||||||
|
|
||||||
## Installation
|
## 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
|
||||||
|
|
||||||
# Create a virtual environment (recommended)
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate # On Windows use `.venv\Scripts\activate`
|
|
||||||
|
|
||||||
# Install the package
|
|
||||||
pip install .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Knowledge Base
|
2. **Install dependencies**
|
||||||
|
|
||||||
The knowledge base is a simple JSON file (`knowledge_base.json`) that
|
|
||||||
stores key/value pairs. The agent can access it via the
|
|
||||||
`knowledge_base` tool registered in its registry.
|
|
||||||
|
|
||||||
### CLI Usage
|
|
||||||
|
|
||||||
The package exposes a console script named `kb`. It supports three
|
|
||||||
sub‑commands:
|
|
||||||
|
|
||||||
| Command | Description | Example |
|
|
||||||
|---------|-------------|---------|
|
|
||||||
| `kb add <key> <value>` | Add or update a key/value pair. | `kb add greeting "Hello, world!"` |
|
|
||||||
| `kb query <key>` | Retrieve the value for a key. | `kb query greeting` |
|
|
||||||
| `kb delete <key>` | Delete a key/value pair. | `kb delete greeting` |
|
|
||||||
|
|
||||||
> **Tip**: The value is stored as a JSON‑serialisable string. For
|
|
||||||
> complex data structures, pass a JSON string (e.g. `"[1, 2, 3]"`).
|
|
||||||
|
|
||||||
### Agent Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.agent import Agent
|
|
||||||
|
|
||||||
agent = Agent()
|
|
||||||
|
|
||||||
# Add a fact
|
|
||||||
agent.tools["knowledge_base"].add_entry("author", "Artur Kuzakhmetov")
|
|
||||||
|
|
||||||
# Retrieve a fact
|
|
||||||
print(agent.get_fact("author")) # Output: Artur Kuzakhmetov
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── agent.py # Core agent implementation
|
|
||||||
├── knowledge_base.py # Knowledge base tool
|
|
||||||
└── cli.py # CLI entry point
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
The repository currently does not ship with automated tests, but you can
|
|
||||||
manually verify the functionality:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add a fact
|
npm install
|
||||||
kb add foo "bar"
|
|
||||||
|
|
||||||
# Query it
|
|
||||||
kb query foo
|
|
||||||
|
|
||||||
# Delete it
|
|
||||||
kb delete foo
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
3. **Configure environment variables**
|
||||||
|
|
||||||
MIT License
|
Create a `.env` file in the project root (or export the variables in your shell):
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
# ChromaDB
|
||||||
|
CHROMA_URL=localhost
|
||||||
|
CHROMA_PORT=8000
|
||||||
|
|
||||||
|
# OpenAI
|
||||||
|
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
- `CHROMA_URL` and `CHROMA_PORT` point to your ChromaDB instance.
|
||||||
|
- `OPENAI_API_KEY` is required for embeddings and chat completions.
|
||||||
|
|
||||||
|
4. **Run ChromaDB**
|
||||||
|
|
||||||
|
Ensure a ChromaDB server is running on the specified host/port. You can start a local instance with Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 8000:8000 chromadb/chroma
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { Agent } = require('./src');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const agent = new Agent();
|
||||||
|
await agent.init();
|
||||||
|
|
||||||
|
// Ingest documents
|
||||||
|
await agent.ingest('The quick brown fox jumps over the lazy dog.', { source: 'example.txt' });
|
||||||
|
|
||||||
|
// Ask a question
|
||||||
|
const answer = await agent.ask('What did the fox do?');
|
||||||
|
console.log(answer);
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `init()` | Initializes the vector store (creates collection if needed). |
|
||||||
|
| `ingest(text, metadata)` | Adds a document to the vector store. |
|
||||||
|
| `ask(question)` | Retrieves relevant passages and generates an answer. |
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
If you have a test suite, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests should pass after the ChromaDB integration.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The agent’s public API remains unchanged; only the underlying vector store implementation has been swapped to ChromaDB.
|
||||||
|
- No new external services are introduced beyond ChromaDB and the existing OpenAI usage.
|
||||||
|
- Ensure that the ChromaDB server is reachable; otherwise, the agent will throw connection errors.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Feel free to extend the agent with additional tools or integrate it
|
Happy coding!
|
||||||
into a larger RAG pipeline.
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Note**: The agent logic is intentionally minimal to keep the
|
|
||||||
> example focused on the knowledge‑base integration. You can add more
|
|
||||||
> sophisticated reasoning or LLM integration as needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Author**: Artur Kuzakhmetov
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Repository**: https://git.brojs.ru/kuzakhmetovartur/agent-s-rag-pamyatyu
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Version**: 14 (as of 30.06.2026)
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Deadline**: 31.08.2026
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Feedback**: The CLI and knowledge‑base tools have been added to
|
|
||||||
> satisfy the assignment requirements.
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Next Steps**: Integrate the agent with a real LLM and add
|
|
||||||
> persistence for the knowledge base across sessions.
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Contact**: artur@example.com
|
|
||||||
|
|
||||||
---
|
|
||||||
> **Enjoy!**
|
|
||||||
|
|
||||||
---
|
|
||||||
> **End of README**
|
|
||||||
+7
-5
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-s-rag-pamyatyu",
|
"name": "agent-s-rag-pamyatyu",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Agent with RAG memory",
|
"description": "RAG agent using ChromaDB as the vector store",
|
||||||
"main": "index.js",
|
"main": "src/index.js",
|
||||||
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js"
|
"start": "node src/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"langchain-qdrant": "latest",
|
"chromadb": "^0.3.0",
|
||||||
"langchain-ollama": "latest"
|
"openai": "^3.3.0",
|
||||||
|
"dotenv": "^16.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-19
@@ -1,32 +1,35 @@
|
|||||||
const { OpenAI } = require('@langchain/openai');
|
const VectorStore = require('./vectorStore');
|
||||||
const { RetrievalQAChain } = require('@langchain/chains');
|
const { OpenAI } = require('openai');
|
||||||
const { initVectorStore } = require('./vectorStore');
|
const dotenv = require('dotenv');
|
||||||
require('dotenv').config();
|
dotenv.config();
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||||
|
|
||||||
class Agent {
|
class Agent {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.llm = new OpenAI({
|
this.vectorStore = new VectorStore();
|
||||||
temperature: 0.7,
|
|
||||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
|
||||||
});
|
|
||||||
this.vectorStore = null;
|
|
||||||
this.chain = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.vectorStore) {
|
await this.vectorStore.init();
|
||||||
this.vectorStore = await initVectorStore();
|
|
||||||
}
|
|
||||||
if (!this.chain) {
|
|
||||||
this.chain = RetrievalQAChain.fromLLM(this.llm, this.vectorStore.asRetriever());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ingest(text, metadata = {}) {
|
||||||
|
await this.vectorStore.addDocument(text, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ask(question) {
|
async ask(question) {
|
||||||
await this.init();
|
const results = await this.vectorStore.query(question, 3);
|
||||||
const result = await this.chain.invoke({ question });
|
const context = results.documents
|
||||||
return result.output;
|
.map((doc, idx) => `Source ${idx + 1}:\n${doc}`)
|
||||||
|
.join('\n\n');
|
||||||
|
const prompt = `You are a helpful assistant. Use the following context to answer the question.\n\n${context}\n\nQuestion: ${question}\nAnswer:`;
|
||||||
|
const completion = await openai.chat.completions.create({
|
||||||
|
model: 'gpt-3.5-turbo',
|
||||||
|
messages: [{ role: 'user', content: prompt }],
|
||||||
|
});
|
||||||
|
return completion.choices[0].message.content.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new Agent();
|
module.exports = Agent;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const { ChromaClient } = require('chromadb');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const client = new ChromaClient({
|
||||||
|
host: process.env.CHROMA_URL || 'localhost',
|
||||||
|
port: process.env.CHROMA_PORT ? parseInt(process.env.CHROMA_PORT, 10) : 8000,
|
||||||
|
apiKey: process.env.CHROMA_API_KEY || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = client;
|
||||||
+3
-1
@@ -1 +1,3 @@
|
|||||||
module.exports = require('./agent');
|
const Agent = require('./agent');
|
||||||
|
|
||||||
|
module.exports = { Agent };
|
||||||
+42
-27
@@ -1,39 +1,54 @@
|
|||||||
const { FAISS } = require('@langchain/vectorstores/faiss');
|
const chroma = require('./chromaClient');
|
||||||
const { OpenAIEmbeddings } = require('@langchain/openai');
|
const { OpenAI } = require('openai');
|
||||||
const fs = require('fs');
|
const dotenv = require('dotenv');
|
||||||
const path = require('path');
|
dotenv.config();
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
const VECTORSTORE_DIR = path.join(__dirname, '..', 'data', 'vectorstore');
|
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||||
|
|
||||||
async function initVectorStore() {
|
class VectorStore {
|
||||||
if (!fs.existsSync(VECTORSTORE_DIR)) {
|
constructor(collectionName = 'documents') {
|
||||||
fs.mkdirSync(VECTORSTORE_DIR, { recursive: true });
|
this.collectionName = collectionName;
|
||||||
|
this.collection = null;
|
||||||
}
|
}
|
||||||
const embeddings = new OpenAIEmbeddings({
|
|
||||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
async init() {
|
||||||
|
this.collection = await chroma.getCollection({
|
||||||
|
name: this.collectionName,
|
||||||
|
metadata: { type: 'vector' },
|
||||||
});
|
});
|
||||||
const vectorStore = await FAISS.load(embeddings, VECTORSTORE_DIR);
|
|
||||||
return vectorStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addDocuments(texts) {
|
async addDocument(text, metadata = {}) {
|
||||||
const embeddings = new OpenAIEmbeddings({
|
if (!this.collection) {
|
||||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
await this.init();
|
||||||
|
}
|
||||||
|
const embedding = await this.getEmbedding(text);
|
||||||
|
await this.collection.add({
|
||||||
|
documents: [text],
|
||||||
|
embeddings: [embedding],
|
||||||
|
metadatas: [metadata],
|
||||||
});
|
});
|
||||||
const vectorStore = await FAISS.load(embeddings, VECTORSTORE_DIR);
|
|
||||||
await vectorStore.addDocuments(texts);
|
|
||||||
await vectorStore.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearVectorStore() {
|
async query(queryText, k = 5) {
|
||||||
if (fs.existsSync(VECTORSTORE_DIR)) {
|
if (!this.collection) {
|
||||||
fs.rmdirSync(VECTORSTORE_DIR, { recursive: true });
|
await this.init();
|
||||||
|
}
|
||||||
|
const embedding = await this.getEmbedding(queryText);
|
||||||
|
const results = await this.collection.query({
|
||||||
|
queryEmbeddings: [embedding],
|
||||||
|
nResults: k,
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEmbedding(text) {
|
||||||
|
const res = await openai.embeddings.create({
|
||||||
|
model: 'text-embedding-ada-002',
|
||||||
|
input: text,
|
||||||
|
});
|
||||||
|
return res.data[0].embedding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = VectorStore;
|
||||||
initVectorStore,
|
|
||||||
addDocuments,
|
|
||||||
clearVectorStore,
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user