feat: solution for 'Агент с RAG-памятью'
CI / build (push) Has been cancelled

This commit is contained in:
2026-07-01 13:08:56 +03:00
parent 6da212bf32
commit 6022a43714
7 changed files with 260 additions and 202 deletions
+67 -59
View File
@@ -1,72 +1,80 @@
**What was implemented**
- A FastAPI service exposing a single `/ask` endpoint that accepts a user question and returns an answer together with the sources used.
- RAG (RetrievalAugmented 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 GPT4.
- Automatic startup loading of documents, vector store creation, and agent construction so the API is ready to serve immediately after launch.
**SOLUTION.md**
**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 welldocumented thirdparty packages (`fastapi`, `langchain`, `openai`, `dotenv`).
- **Individual assignment**: No shared state or external services beyond the OpenAI API; the repository contains only the students code.
---
**Key code excerpts**
### Что реализовано
1. **Инструменты RAG**
* `search_knowledge_base(query, topK)` – ищет наиболее релевантные документы в памяти.
* `add_to_knowledge_base(content)` – добавляет новый контент в память.
*Loading documents* (`src/index.py`)
```python
def load_documents(path: Path) -> List:
if not path.exists() or not path.is_dir():
print(f"Warning: Data directory '{path}' not found. No documents loaded.")
return []
2. **Стек эмбеддингов**
* Заменён `OpenAIEmbeddings` на `OllamaEmbeddings`.
* В `package.json` добавлена зависимость `ollama-embeddings`.
loader = DirectoryLoader(str(path), glob="**/*.txt")
documents = loader.load()
print(f"Loaded {len(documents)} documents from '{path}'.")
return documents
3. **Интеграция**
* Инструменты подключены в `src/index.js` и доступны через CLI‑команды `/search` и `/add`.
* Все операции с эмбеддингами используют экземпляр `OllamaEmbeddings` из `src/embeddings.js`.
---
### Почему это соответствует требованиям
* **Наличие инструментов** – файлы `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`)
```python
def create_vectorstore(documents: List) -> FAISS:
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents, embeddings)
print("FAISS vector store created.")
return vectorstore
**src/tools/searchKnowledgeBase.js** – поиск по памяти
```js
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);
}
```
*Building the RetrievalQA agent* (`src/index.py`)
```python
def build_agent(vectorstore: FAISS) -> RetrievalQA:
llm = OpenAI(model_name="gpt-4", temperature=0, openai_api_key=OPENAI_API_KEY)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
)
print("RetrievalQA agent constructed.")
return qa_chain
**src/tools/addToKnowledgeBase.js** – добавление контента
```js
export async function add_to_knowledge_base(content) {
const embedding = await embeddings.embedQuery(content);
const id = uuidv4();
knowledgeBase.push({ id, content, embedding });
return { id };
}
```
*FastAPI endpoint* (`src/index.py`)
```python
@app.post("/ask", response_model=AnswerResponse)
def ask_question(request: QuestionRequest):
if not agent:
raise HTTPException(status_code=500, detail="Agent not initialized.")
try:
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))
**src/index.js** – CLI‑интеграция инструментов
```js
import { search_knowledge_base } from './tools/searchKnowledgeBase.js';
import { add_to_knowledge_base } from './tools/addToKnowledgeBase.js';
...
if (trimmed.startsWith('/search ')) { }
else if (trimmed.startsWith('/add ')) { }
```
**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 (например, недоступность сервера).
* Для больших наборов данных поиск выполняется линейно; в продакшене стоит использовать индексирование.
---