feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
+21
-23
@@ -1,25 +1,23 @@
|
||||
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');
|
||||
}
|
||||
/**
|
||||
* Minimal Context‑Aware Prompt (MCP) tool.
|
||||
* Generates a prompt that can be used for vector search.
|
||||
*/
|
||||
export function generatePrompt(question) {
|
||||
return `Answer the following question based on the knowledge base: "${question}"`;
|
||||
}
|
||||
|
||||
module.exports = Bot;
|
||||
/**
|
||||
* Handles a user query by generating a prompt, searching the vector store,
|
||||
* and returning the best answer.
|
||||
* @param {string} question
|
||||
* @param {ChromaVectorStore} vectorStore
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function answerQuestion(question, vectorStore) {
|
||||
const prompt = generatePrompt(question);
|
||||
const results = await vectorStore.similaritySearch(prompt, 1);
|
||||
if (results.length === 0) {
|
||||
return "I couldn't find an answer to that question.";
|
||||
}
|
||||
return results[0];
|
||||
}
|
||||
+39
-25
@@ -1,29 +1,43 @@
|
||||
const Bot = require('./bot');
|
||||
import readlineSync from 'readline-sync';
|
||||
import ChromaVectorStore from './vectorStore.js';
|
||||
import { answerQuestion } from './bot.js';
|
||||
|
||||
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.',
|
||||
},
|
||||
/**
|
||||
* Sample FAQ dataset.
|
||||
* In a real application this would be loaded from a file or database.
|
||||
*/
|
||||
const faqData = [
|
||||
{ id: '1', text: 'What is ChromaDB?', metadata: { category: 'database' } },
|
||||
{ id: '2', text: 'How do I install ChromaDB?', metadata: { category: 'installation' } },
|
||||
{ id: '3', text: 'What is an MCP-tool?', metadata: { category: 'concept' } },
|
||||
{ id: '4', text: 'How to use the FAQ bot?', metadata: { category: 'usage' } },
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const bot = new Bot();
|
||||
await bot.init();
|
||||
await bot.indexFAQs(faqs);
|
||||
/**
|
||||
* Main entry point.
|
||||
*/
|
||||
async function main() {
|
||||
const vectorStore = new ChromaVectorStore();
|
||||
await vectorStore.init('faq');
|
||||
|
||||
const userQuestion = 'Explain ChromaDB';
|
||||
const response = await bot.answer(userQuestion);
|
||||
console.log('Answer:\n', response);
|
||||
})();
|
||||
// Load data into the collection if it is empty.
|
||||
// For simplicity we always add the data; in production you would check existence.
|
||||
await vectorStore.addDocuments(faqData);
|
||||
|
||||
console.log('FAQ bot is ready. Type your question (or "exit" to quit).');
|
||||
|
||||
while (true) {
|
||||
const question = readlineSync.question('> ');
|
||||
if (question.trim().toLowerCase() === 'exit') {
|
||||
console.log('Goodbye!');
|
||||
break;
|
||||
}
|
||||
const answer = await answerQuestion(question, vectorStore);
|
||||
console.log(`Answer: ${answer}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
+57
-31
@@ -1,45 +1,71 @@
|
||||
const { ChromaClient } = require('chromadb');
|
||||
const { MCPTool } = require('mcp-tool');
|
||||
import { ChromaClient } from 'chromadb';
|
||||
|
||||
class VectorStore {
|
||||
/**
|
||||
* Simple embedding utility.
|
||||
* Produces a 768‑dimensional vector where each dimension is a count of
|
||||
* the number of words that hash to that index.
|
||||
*/
|
||||
function embed(text) {
|
||||
const vector = new Array(768).fill(0);
|
||||
const words = text.toLowerCase().split(/\s+/);
|
||||
for (const word of words) {
|
||||
const hash = [...word].reduce((acc, ch) => acc + ch.charCodeAt(0), 0);
|
||||
const idx = hash % 768;
|
||||
vector[idx] += 1;
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around ChromaDB providing a minimal API for the bot.
|
||||
*/
|
||||
class ChromaVectorStore {
|
||||
constructor() {
|
||||
this.client = new ChromaClient({ path: './chromadb' });
|
||||
this.client = new ChromaClient();
|
||||
this.collection = null;
|
||||
this.mcp = new MCPTool(); // default configuration
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.collection = await this.client.getOrCreateCollection('faq');
|
||||
}
|
||||
|
||||
async addDocument(id, text) {
|
||||
const embedding = await this.mcp.embed(text);
|
||||
await this.collection.add({
|
||||
ids: [id],
|
||||
embeddings: [embedding],
|
||||
documents: [text],
|
||||
/**
|
||||
* Initializes the collection. Creates it if it does not exist.
|
||||
* @param {string} name - Collection name.
|
||||
*/
|
||||
async init(name = 'faq') {
|
||||
this.collection = await this.client.getOrCreateCollection({
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
async query(text, k = 5) {
|
||||
const embedding = await this.mcp.embed(text);
|
||||
/**
|
||||
* Adds documents to the collection.
|
||||
* @param {Array<{id?: string, text: string, metadata?: object}>} docs
|
||||
*/
|
||||
async addDocuments(docs) {
|
||||
const ids = docs.map((d, idx) => d.id ?? `doc-${idx}`);
|
||||
const metadatas = docs.map(d => d.metadata ?? {});
|
||||
const embeddings = docs.map(d => embed(d.text));
|
||||
await this.collection.add({
|
||||
ids,
|
||||
documents: docs.map(d => d.text),
|
||||
metadatas,
|
||||
embeddings,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a similarity search.
|
||||
* @param {string} queryText
|
||||
* @param {number} k
|
||||
* @returns {Promise<Array<string>>} Top k documents.
|
||||
*/
|
||||
async similaritySearch(queryText, k = 3) {
|
||||
const queryEmbedding = embed(queryText);
|
||||
const results = await this.collection.query({
|
||||
queryEmbeddings: [embedding],
|
||||
queryEmbeddings: [queryEmbedding],
|
||||
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] });
|
||||
return results[0].documents;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorStore;
|
||||
export default ChromaVectorStore;
|
||||
export { embed };
|
||||
Reference in New Issue
Block a user