feat: solution for 'Повторный экзамен: FAQ-бот — ChromaDB + один MCP-tool'
This commit is contained in:
+62
-42
@@ -1,50 +1,70 @@
|
||||
import dotenv from "dotenv";
|
||||
import readline from "readline";
|
||||
import { createAgent } from "./agent.js";
|
||||
import { addDocument } from "./vectorstore.js";
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const { OpenAI } = require('openai');
|
||||
const { ChromaClient } = require('chromadb');
|
||||
const { moderateInput } = require('./middleware');
|
||||
|
||||
dotenv.config();
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const COLLECTION = "faq_collection";
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
const chroma = new ChromaClient({ path: 'chromadb' });
|
||||
|
||||
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 COLLECTION_NAME = 'faq_collection';
|
||||
const TOP_K = 3;
|
||||
|
||||
const agent = await createAgent(COLLECTION);
|
||||
// Initialize collection
|
||||
let collectionPromise = chroma.getOrCreateCollection({
|
||||
name: COLLECTION_NAME,
|
||||
metadata: { description: 'FAQ embeddings' }
|
||||
});
|
||||
|
||||
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();
|
||||
app.post('/ask', async (req, res) => {
|
||||
try {
|
||||
const { question } = req.body;
|
||||
if (!question) {
|
||||
rl.prompt();
|
||||
return;
|
||||
return res.status(400).json({ error: 'Question is required' });
|
||||
}
|
||||
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));
|
||||
// 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}`);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { OpenAI } = require('openai');
|
||||
const { ChromaClient } = require('chromadb');
|
||||
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
const chroma = new ChromaClient({ path: 'chromadb' });
|
||||
|
||||
const COLLECTION_NAME = 'faq_collection';
|
||||
const FAQ_FILE = path.join(__dirname, '..', 'faq.json');
|
||||
|
||||
async function ingest() {
|
||||
try {
|
||||
const rawData = fs.readFileSync(FAQ_FILE, 'utf-8');
|
||||
const faqEntries = JSON.parse(rawData);
|
||||
|
||||
const collection = await chroma.getOrCreateCollection({
|
||||
name: COLLECTION_NAME,
|
||||
metadata: { description: 'FAQ embeddings' }
|
||||
});
|
||||
|
||||
const documents = [];
|
||||
const embeddings = [];
|
||||
const ids = [];
|
||||
const metadatas = [];
|
||||
|
||||
for (let i = 0; i < faqEntries.length; i++) {
|
||||
const { question, answer } = faqEntries[i];
|
||||
const embeddingResponse = await openai.embeddings.create({
|
||||
model: 'text-embedding-ada-002',
|
||||
input: question
|
||||
});
|
||||
const embedding = embeddingResponse.data[0].embedding;
|
||||
|
||||
documents.push(question);
|
||||
embeddings.push(embedding);
|
||||
ids.push(`faq-${i}`);
|
||||
metadatas.push({ answer });
|
||||
}
|
||||
|
||||
await collection.add({
|
||||
documents,
|
||||
embeddings,
|
||||
ids,
|
||||
metadatas
|
||||
});
|
||||
|
||||
console.log(`Ingested ${faqEntries.length} FAQ entries into collection '${COLLECTION_NAME}'.`);
|
||||
} catch (err) {
|
||||
console.error('Error during ingestion:', err);
|
||||
}
|
||||
}
|
||||
|
||||
ingest();
|
||||
@@ -0,0 +1,23 @@
|
||||
const moderate = require('moderate-censor');
|
||||
|
||||
/**
|
||||
* Moderates user input using moderate-censor.
|
||||
* @param {string} text
|
||||
* @returns {Promise<{allowed: boolean, reasons: string[]}>}
|
||||
*/
|
||||
async function moderateInput(text) {
|
||||
try {
|
||||
const result = await moderate.moderate(text);
|
||||
if (result.isAllowed) {
|
||||
return { allowed: true, reasons: [] };
|
||||
} else {
|
||||
return { allowed: false, reasons: result.reasons || [] };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Moderation error:', err);
|
||||
// If moderation fails, default to allowing to avoid blocking legitimate queries
|
||||
return { allowed: true, reasons: [] };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { moderateInput };
|
||||
Reference in New Issue
Block a user