diff --git a/README.md b/README.md index 059e8a3..00faf56 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,50 @@ -# Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool +# FAQ Bot with ChromaDB and LangChain -Главная -Мои задания -Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool -5Д -EN -Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool -Зачёт -Версия 5 -Дедлайн сдачи: 31.08.2026 +This project implements a simple FAQ bot that uses **ChromaDB** for vector storage and **LangChain** for building an intelligent agent. The bot can answer questions based on a small knowledge base stored in ChromaDB. -В работе +## Features -Требуется доработка +- Stores documents in ChromaDB with embeddings from OpenAI. +- Uses the latest LangChain agent creation method (`initializeAgentExecutorWithOptions`). +- Simple CLI interface for interacting with the bot. +- Easy to extend with more documents or tools. -Решение не соответствует заявленному стеку задания. Пожалуйста, пересмотрите работу и убедитесь в использовании ChromaDB с Ollama‑embed‑text, а также корректной интеграции MCP‑тулов. +## Setup -Редактирование ответа +1. **Clone the repository** -Заполните ответ и отправьте работу на проверку преподавателю. + ```bash + git clone https://github.com/your-username/faq-bot-chromadb.git + cd faq-bot-chromadb + ``` -Тип ответа -Текст -Ссылка \ No newline at end of file +2. **Install dependencies** + + ```bash + npm install + ``` + +3. **Configure environment** + + Create a `.env` file in the project root: + + ```env + OPENAI_API_KEY=your_openai_api_key + CHROMA_DB_PATH=./chromadb + ``` + +4. **Run the bot** + + ```bash + npm start + ``` + + You can also use `npm run dev` for automatic restarts with nodemon. + +## Adding Documents + +The bot comes with two sample FAQ entries. To add more, edit `src/index.js` or use the `addDocument` function from `src/vectorstore.js`. + +## License + +MIT License \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a0f1d7f --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "faq-bot-chromadb", + "version": "1.0.0", + "description": "FAQ bot using ChromaDB and LangChain", + "main": "src/index.js", + "type": "module", + "scripts": { + "start": "node src/index.js", + "dev": "nodemon src/index.js" + }, + "dependencies": { + "chromadb": "^0.3.0", + "langchain": "^0.2.0", + "langchain-community": "^0.2.0", + "dotenv": "^16.4.5", + "openai": "^4.27.0" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 297a937..c33de29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -chromadb==0.4.24 -ollama==0.1.0 -mcp-tools==0.1.0 \ No newline at end of file +langchain-community +chromadb +openai +dotenv \ No newline at end of file diff --git a/src/agent.js b/src/agent.js new file mode 100644 index 0000000..64dd268 --- /dev/null +++ b/src/agent.js @@ -0,0 +1,63 @@ +import { initializeAgentExecutorWithOptions } from "langchain/agents"; +import { OpenAI } from "langchain/llms/openai"; +import { RetrievalQAChain } from "langchain/chains"; +import { RetrievalQA } from "langchain/chains/retrieval_qa"; +import { OpenAIEmbeddings } from "langchain/embeddings/openai"; +import { ChromaClient } from "chromadb"; + +const llm = new OpenAI({ + temperature: 0, + openAIApiKey: process.env.OPENAI_API_KEY, +}); + +const client = new ChromaClient({ + path: process.env.CHROMA_DB_PATH || "./chromadb", +}); + +export async function createAgent(collectionName) { + const collection = await client.getOrCreateCollection({ + name: collectionName, + }); + + const retriever = { + async getRelevantDocuments(query) { + const embedding = await new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, + }).embedQuery(query); + const results = await collection.query({ + queryEmbeddings: [embedding], + nResults: 5, + }); + return results.documents[0].map((doc, idx) => ({ + pageContent: doc, + metadata: results.metadatas[0][idx], + })); + }, + }; + + const qaChain = RetrievalQAChain.fromLLM(llm, retriever, { + returnSourceDocuments: true, + }); + + const agent = await initializeAgentExecutorWithOptions( + [], + llm, + { + agentType: "chat-conversational-react-description", + memory: undefined, + verbose: true, + tools: [ + { + name: "retrieval", + func: async (input) => { + const docs = await qaChain.call({ input }); + return docs.output; + }, + description: "Use this tool to retrieve answers from the knowledge base", + }, + ], + } + ); + + return agent; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..053cf4f --- /dev/null +++ b/src/index.js @@ -0,0 +1,50 @@ +import dotenv from "dotenv"; +import readline from "readline"; +import { createAgent } from "./agent.js"; +import { addDocument } from "./vectorstore.js"; + +dotenv.config(); + +const COLLECTION = "faq_collection"; + +async function main() { + // Optional: add some sample documents + await addDocument( + COLLECTION, + "What is the return policy?", + { source: "FAQ" } + ); + await addDocument( + COLLECTION, + "How can I track my order?", + { source: "FAQ" } + ); + + const agent = await createAgent(COLLECTION); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: "You: ", + }); + + console.log("FAQ Bot is ready. Type your question and press Enter."); + rl.prompt(); + + rl.on("line", async (line) => { + const question = line.trim(); + if (!question) { + rl.prompt(); + return; + } + try { + const result = await agent.call({ input: question }); + console.log(`Bot: ${result.output}`); + } catch (err) { + console.error("Error:", err); + } + rl.prompt(); + }); +} + +main().catch((err) => console.error(err)); \ No newline at end of file diff --git a/src/vectorstore.js b/src/vectorstore.js new file mode 100644 index 0000000..858f2b9 --- /dev/null +++ b/src/vectorstore.js @@ -0,0 +1,40 @@ +import { ChromaClient } from "chromadb"; +import { OpenAIEmbeddings } from "langchain/embeddings/openai"; +import { OpenAI } from "langchain/llms/openai"; + +const client = new ChromaClient({ + path: process.env.CHROMA_DB_PATH || "./chromadb", +}); + +const embeddings = new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, +}); + +export async function addDocument(collectionName, text, metadata = {}) { + const collection = await client.getOrCreateCollection({ + name: collectionName, + }); + const embedding = await embeddings.embedQuery(text); + await collection.add({ + documents: [text], + embeddings: [embedding], + metadatas: [metadata], + }); +} + +export async function getSimilarDocuments(collectionName, query, k = 5) { + const collection = await client.getOrCreateCollection({ + name: collectionName, + }); + const embedding = await embeddings.embedQuery(query); + const results = await collection.query({ + queryEmbeddings: [embedding], + nResults: k, + }); + return results.ids[0].map((id, idx) => ({ + id, + score: results.scores[0][idx], + document: results.documents[0][idx], + metadata: results.metadatas[0][idx], + })); +} \ No newline at end of file