diff --git a/README.md b/README.md index a532d7a..fb8d173 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,19 @@ -# Compare Three Entities +# Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily) -A small utility library that compares three JavaScript objects and reports the differences between them. -The comparison is deep, meaning nested objects are compared recursively. The result is an array of difference objects, each containing: +Главная +Мои задания +Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily) +5Д +EN +Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily) +Зачёт +Версия 6 +Дедлайн сдачи: 31.08.2026 -- `key`: The dot‑separated path to the differing property. -- `values`: An array of the values from the three objects in the order `[a, b, c]`. +В работе -## Installation +Требуется доработка -```bash -npm install compare-three-entities -``` +В вашем решении отсутствует упоминание и использование Qdrant, хотя это требование явно указано в задании. Пожалуйста, добавьте интеграцию с Qdrant или замените его на другой поддерживаемый вами векторный хранилище, чтобы решение соответствовало публичному стеку. -## Usage - -```js -const compare = require('compare-three-entities'); - -const a = { name: 'Alice', age: 30, address: { city: 'NY' } }; -const b = { name: 'Alice', age: 31, address: { city: 'NY' } }; -const c = { name: 'Alice', age: 30, address: { city: 'LA' } }; - -const differences = compare(a, b, c); -console.log(differences); -// [ -// { key: 'age', values: [30, 31, 30] }, -// { key: 'address.city', values: ['NY', 'NY', 'LA'] } -// ] -``` - -## API - -### `compare(a, b, c)` - -- **Parameters**: - - `a` – First object. - - `b` – Second object. - - `c` – Third object. -- **Returns**: `Array` – Sorted array of difference objects. - -## Testing - -Run the test suite with: - -```bash -npm test -``` - -The tests cover basic equality, top‑level differences, nested differences, and missing keys. - -## License - -MIT \ No newline at end of file +Редактиров \ No newline at end of file diff --git a/package.json b/package.json index df5c4c8..515857d 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,24 @@ { - "name": "compare-three-entities", + "name": "tavily-qdrant-demo", "version": "1.0.0", - "description": "A utility to compare three entities and report differences.", + "description": "Demo project integrating Tavily API with Qdrant vector store", "main": "src/index.js", "scripts": { - "test": "jest" + "start": "node src/index.js" }, "keywords": [ - "compare", - "entities", - "deep-equal", - "difference" + "tavily", + "qdrant", + "vector", + "search", + "express" ], - "author": "Auto-generated", + "author": "Your Name", "license": "MIT", - "devDependencies": { - "jest": "^29.7.0" + "dependencies": { + "@qdrant/js-client-rest": "^1.0.0", + "axios": "^1.7.2", + "dotenv": "^16.4.5", + "express": "^4.18.2" } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 5f4175e..8125998 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,61 @@ -/** - * Entry point for the comparison library. - * Exports the compare function as both named and default export. - */ -const compare = require('./compare'); +const express = require('express'); +const { fetchEntityData } = require('./tavily'); +const { + initClient, + createCollection, + upsertEmbeddings, + searchEmbeddings +} = require('./qdrant'); +require('dotenv').config(); -module.exports = compare; -module.exports.default = compare; -module.exports.compare = compare; \ No newline at end of file +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware to parse JSON +app.use(express.json()); + +// Initialize Qdrant client and collection on startup +(async () => { + try { + initClient(); + await createCollection(); + } catch (err) { + console.error('Failed to initialize Qdrant:', err.message); + process.exit(1); + } +})(); + +// Route to fetch data for an entity and store embeddings +app.get('/fetch/:entity', async (req, res) => { + const entity = req.params.entity; + try { + const text = await fetchEntityData(entity); + await upsertEmbeddings(entity, text); + res.json({ status: 'success', entity, textLength: text.length }); + } catch (err) { + res.status(500).json({ status: 'error', message: err.message }); + } +}); + +// Route to search embeddings +app.get('/search', async (req, res) => { + const query = req.query.q; + if (!query) { + return res.status(400).json({ status: 'error', message: 'Missing query parameter q' }); + } + try { + const results = await searchEmbeddings(query); + res.json({ status: 'success', query, results }); + } catch (err) { + res.status(500).json({ status: 'error', message: err.message }); + } +}); + +// Health check +app.get('/', (req, res) => { + res.send('Tavily-Qdrant Demo Server'); +}); + +app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); +}); \ No newline at end of file diff --git a/src/qdrant.js b/src/qdrant.js new file mode 100644 index 0000000..68d5fc7 --- /dev/null +++ b/src/qdrant.js @@ -0,0 +1,116 @@ +const { QdrantClient } = require('@qdrant/js-client-rest'); +const axios = require('axios'); +require('dotenv').config(); + +const QDRANT_URL = process.env.QDRANT_URL; +const QDRANT_API_KEY = process.env.QDRANT_API_KEY; +const OPENAI_API_KEY = process.env.OPENAI_API_KEY; +const VECTOR_SIZE = 1536; // OpenAI Ada embeddings size + +const COLLECTION_NAME = 'entities'; + +let client = null; + +// Initialize Qdrant client +function initClient() { + client = new QdrantClient({ + url: QDRANT_URL, + apiKey: QDRANT_API_KEY + }); +} + +// Create or recreate collection +async function createCollection() { + if (!client) initClient(); + try { + await client.recreateCollection(COLLECTION_NAME, { + vectors: { + size: VECTOR_SIZE, + distance: 'Cosine' + } + }); + console.log(`Collection '${COLLECTION_NAME}' created/recreated.`); + } catch (err) { + console.error('Error creating collection:', err.message); + throw err; + } +} + +// Get embedding from OpenAI +async function getEmbedding(text) { + try { + const response = await axios.post( + 'https://api.openai.com/v1/embeddings', + { + input: text, + model: 'text-embedding-ada-002' + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${OPENAI_API_KEY}` + } + } + ); + if (response.data && response.data.data && response.data.data[0]) { + return response.data.data[0].embedding; + } else { + throw new Error('No embedding returned'); + } + } catch (err) { + console.error('Error getting embedding:', err.message); + throw err; + } +} + +// Upsert embeddings for an entity +async function upsertEmbeddings(entity, text) { + if (!client) initClient(); + try { + const vector = await getEmbedding(text); + const point = { + id: entity, + vector, + payload: { + entity, + text + } + }; + await client.upsertPoints(COLLECTION_NAME, { + points: [point] + }); + console.log(`Upserted embeddings for entity '${entity}'.`); + } catch (err) { + console.error('Error upserting embeddings:', err.message); + throw err; + } +} + +// Search embeddings +async function searchEmbeddings(query, limit = 3) { + if (!client) initClient(); + try { + const queryVector = await getEmbedding(query); + const result = await client.search(COLLECTION_NAME, { + vector: queryVector, + limit, + with_payload: true, + with_vector: false + }); + return result.hits.map((hit) => ({ + id: hit.id, + score: hit.score, + payload: hit.payload + })); + } catch (err) { + console.error('Error searching embeddings:', err.message); + throw err; + } +} + +module.exports = { + initClient, + createCollection, + upsertEmbeddings, + searchEmbeddings +}; \ No newline at end of file diff --git a/src/tavily.js b/src/tavily.js new file mode 100644 index 0000000..34ea229 --- /dev/null +++ b/src/tavily.js @@ -0,0 +1,41 @@ +const axios = require('axios'); +require('dotenv').config(); + +const TAVILY_API_KEY = process.env.TAVILY_API_KEY; +const TAVILY_ENDPOINT = 'https://api.tavily.com/search'; + +async function fetchEntityData(entity) { + try { + const response = await axios.post( + TAVILY_ENDPOINT, + { + query: entity, + search_depth: 2, + include_raw_content: true, + max_results: 5 + }, + { + headers: { + 'Content-Type': 'application/json', + 'accept': 'application/json', + 'Authorization': `Bearer ${TAVILY_API_KEY}` + } + } + ); + + if (response.data && response.data.results) { + // Concatenate all raw content into a single string + const texts = response.data.results + .map((r) => r.raw_content || '') + .filter(Boolean); + return texts.join('\n\n'); + } else { + throw new Error('No results returned from Tavily'); + } + } catch (err) { + console.error('Error fetching data from Tavily:', err.message); + throw err; + } +} + +module.exports = { fetchEntityData }; \ No newline at end of file