feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
const VectorStore = require('./vectorStore');
|
||||
|
||||
class Bot {
|
||||
constructor() {
|
||||
this.vectorStore = new VectorStore();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.vectorStore.connect();
|
||||
}
|
||||
|
||||
async indexFAQs(faqs) {
|
||||
for (const faq of faqs) {
|
||||
const combined = `${faq.question}\n${faq.answer}`;
|
||||
await this.vectorStore.addDocument(faq.id, combined);
|
||||
}
|
||||
}
|
||||
|
||||
async answer(question) {
|
||||
const results = await this.vectorStore.query(question, 3);
|
||||
return results.map(r => r.document).join('\n---\n');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bot;
|
||||
+26
-67
@@ -1,70 +1,29 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const { OpenAI } = require('openai');
|
||||
const { ChromaClient } = require('chromadb');
|
||||
const { moderateInput } = require('./middleware');
|
||||
const Bot = require('./bot');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
const faqs = [
|
||||
{
|
||||
id: '1',
|
||||
question: 'What is ChromaDB?',
|
||||
answer: 'ChromaDB is a vector database designed for storing and querying embeddings efficiently.',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
question: 'How do I use MCP-tool?',
|
||||
answer: 'MCP-tool is a utility that generates embeddings from text using a chosen model.',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
question: 'Can I delete a document from the vector store?',
|
||||
answer: 'Yes, you can delete a document by its ID using the deleteDocument method.',
|
||||
},
|
||||
];
|
||||
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
const chroma = new ChromaClient({ path: 'chromadb' });
|
||||
(async () => {
|
||||
const bot = new Bot();
|
||||
await bot.init();
|
||||
await bot.indexFAQs(faqs);
|
||||
|
||||
const COLLECTION_NAME = 'faq_collection';
|
||||
const TOP_K = 3;
|
||||
|
||||
// Initialize collection
|
||||
let collectionPromise = chroma.getOrCreateCollection({
|
||||
name: COLLECTION_NAME,
|
||||
metadata: { description: 'FAQ embeddings' }
|
||||
});
|
||||
|
||||
app.post('/ask', async (req, res) => {
|
||||
try {
|
||||
const { question } = req.body;
|
||||
if (!question) {
|
||||
return res.status(400).json({ error: 'Question is required' });
|
||||
}
|
||||
|
||||
// Moderate user input
|
||||
const moderationResult = await moderateInput(question);
|
||||
if (!moderationResult.allowed) {
|
||||
return res.status(403).json({
|
||||
error: 'Question contains disallowed content',
|
||||
reasons: moderationResult.reasons
|
||||
});
|
||||
}
|
||||
|
||||
// Embed the question
|
||||
const embeddingResponse = await openai.embeddings.create({
|
||||
model: 'text-embedding-ada-002',
|
||||
input: question
|
||||
});
|
||||
const embedding = embeddingResponse.data[0].embedding;
|
||||
|
||||
// Query ChromaDB
|
||||
const collection = await collectionPromise;
|
||||
const queryResult = await collection.query({
|
||||
queryEmbeddings: [embedding],
|
||||
nResults: TOP_K,
|
||||
includeMetadata: true
|
||||
});
|
||||
|
||||
if (!queryResult.ids || queryResult.ids.length === 0) {
|
||||
return res.json({ answer: "I don't have an answer for that." });
|
||||
}
|
||||
|
||||
// Pick the top result
|
||||
const topAnswer = queryResult.metadatas[0]?.answer || "I don't have an answer for that.";
|
||||
|
||||
res.json({ answer: topAnswer });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`FAQ bot listening on port ${PORT}`);
|
||||
});
|
||||
const userQuestion = 'Explain ChromaDB';
|
||||
const response = await bot.answer(userQuestion);
|
||||
console.log('Answer:\n', response);
|
||||
})();
|
||||
+40
-35
@@ -1,40 +1,45 @@
|
||||
import { ChromaClient } from "chromadb";
|
||||
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
|
||||
import { OpenAI } from "langchain/llms/openai";
|
||||
const { ChromaClient } = require('chromadb');
|
||||
const { MCPTool } = require('mcp-tool');
|
||||
|
||||
const client = new ChromaClient({
|
||||
path: process.env.CHROMA_DB_PATH || "./chromadb",
|
||||
});
|
||||
class VectorStore {
|
||||
constructor() {
|
||||
this.client = new ChromaClient({ path: './chromadb' });
|
||||
this.collection = null;
|
||||
this.mcp = new MCPTool(); // default configuration
|
||||
}
|
||||
|
||||
const embeddings = new OpenAIEmbeddings({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
async connect() {
|
||||
this.collection = await this.client.getOrCreateCollection('faq');
|
||||
}
|
||||
|
||||
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],
|
||||
});
|
||||
async addDocument(id, text) {
|
||||
const embedding = await this.mcp.embed(text);
|
||||
await this.collection.add({
|
||||
ids: [id],
|
||||
embeddings: [embedding],
|
||||
documents: [text],
|
||||
});
|
||||
}
|
||||
|
||||
async query(text, k = 5) {
|
||||
const embedding = await this.mcp.embed(text);
|
||||
const results = await this.collection.query({
|
||||
queryEmbeddings: [embedding],
|
||||
nResults: k,
|
||||
});
|
||||
const ids = results.ids[0];
|
||||
const distances = results.distances[0];
|
||||
const documents = results.documents[0];
|
||||
return ids.map((id, idx) => ({
|
||||
id,
|
||||
score: distances[idx],
|
||||
document: documents[idx],
|
||||
}));
|
||||
}
|
||||
|
||||
async deleteDocument(id) {
|
||||
await this.collection.delete({ ids: [id] });
|
||||
}
|
||||
}
|
||||
|
||||
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],
|
||||
}));
|
||||
}
|
||||
module.exports = VectorStore;
|
||||
Reference in New Issue
Block a user