feat: solution for 'Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily)'
This commit is contained in:
@@ -1,30 +1,61 @@
|
|||||||
# Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily)
|
# Tavily Compare
|
||||||
|
|
||||||
Главная
|
This Node.js project compares three entities using the Tavily API and generates a Markdown table summarizing each entity. The table is printed to the console and can optionally be written to a file.
|
||||||
Мои задания
|
|
||||||
Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily)
|
|
||||||
5Д
|
|
||||||
EN
|
|
||||||
Повторный экзамен #2: Сравнительный обзор 3 сущностей (Tavily)
|
|
||||||
Зачёт
|
|
||||||
Версия 3
|
|
||||||
Дедлайн сдачи: 31.08.2026
|
|
||||||
|
|
||||||
В работе
|
## Prerequisites
|
||||||
|
|
||||||
Требуется доработка
|
- Node.js v18 or newer (for native ES modules support)
|
||||||
|
- A Tavily API key
|
||||||
|
|
||||||
В работе отсутствует ключевой элемент задания – таблица сравнения 3×N с явным вердиктом.
|
## Setup
|
||||||
|
|
||||||
Редактирование ответа
|
1. **Clone the repository** (or copy the files into a new directory):
|
||||||
|
|
||||||
Заполните ответ и отправьте работу на проверку преподавателю.
|
```bash
|
||||||
|
git clone https://github.com/your-username/tavily-compare.git
|
||||||
|
cd tavily-compare
|
||||||
|
```
|
||||||
|
|
||||||
Тип ответа
|
2. **Install dependencies**:
|
||||||
Текст
|
|
||||||
Ссылка
|
```bash
|
||||||
Файлы
|
npm install
|
||||||
Ссылка (URL)
|
```
|
||||||
Прикреплённые файлы
|
|
||||||
Загрузить файл
|
3. **Create a `.env` file** in the project root and add your Tavily API key:
|
||||||
Отправить на проверку
|
|
||||||
|
```env
|
||||||
|
TAVILY_API_KEY=your_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run the script with three entity names:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node src/index.js "Entity One" "Entity Two" "Entity Three"
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will output a Markdown table to the console.
|
||||||
|
|
||||||
|
### Writing to a file
|
||||||
|
|
||||||
|
To also write the table to a file, use the `--output` (or `-o`) flag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node src/index.js "Entity One" "Entity Two" "Entity Three" --output comparison.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The file `comparison.md` will contain the same table.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `src/index.js` – Entry point; orchestrates argument parsing, API calls, and output.
|
||||||
|
- `src/compare.js` – Handles API requests to Tavily and returns summaries.
|
||||||
|
- `src/markdown.js` – Builds the Markdown table string.
|
||||||
|
- `package.json` – Project metadata and dependencies.
|
||||||
|
- `README.md` – Documentation.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
+3
-4
@@ -1,15 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "tavily-comparison",
|
"name": "tavily-compare",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Compare three entities using Tavily API and display a 3xN table with verdict.",
|
"description": "Compare three entities using Tavily API and generate markdown table",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js"
|
"start": "node src/index.js"
|
||||||
},
|
},
|
||||||
"author": "Your Name",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import fetch from 'node-fetch';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export async function compareEntities(names) {
|
||||||
|
const apiKey = process.env.TAVILY_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('TAVILY_API_KEY environment variable is not set.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const name of names) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.tavily.com/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${apiKey}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: name,
|
||||||
|
search_depth: 'basic',
|
||||||
|
max_results: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to fetch data for "${name}". Status: ${response.status} ${response.statusText}`);
|
||||||
|
results.push({ name, summary: 'Error fetching data' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const content = data.results && data.results[0] && data.results[0].content
|
||||||
|
? data.results[0].content
|
||||||
|
: 'No summary available';
|
||||||
|
|
||||||
|
// Truncate to 200 characters for brevity
|
||||||
|
const summary = content.length > 200 ? content.slice(0, 200) + '...' : content;
|
||||||
|
|
||||||
|
results.push({ name, summary });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error processing "${name}": ${err.message}`);
|
||||||
|
results.push({ name, summary: 'Error processing entity' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
+38
-86
@@ -1,99 +1,51 @@
|
|||||||
import fetch from 'node-fetch';
|
import { compareEntities } from './compare.js';
|
||||||
|
import { generateMarkdownTable } from './markdown.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
const API_KEY = process.env.TAVILY_API_KEY;
|
function parseArgs() {
|
||||||
if (!API_KEY) {
|
const args = process.argv.slice(2);
|
||||||
console.error('Error: TAVILY_API_KEY environment variable is not set.');
|
const entities = [];
|
||||||
process.exit(1);
|
let outputFile = null;
|
||||||
}
|
|
||||||
|
|
||||||
const API_URL = 'https://api.tavily.com/search';
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
const ENTITIES = [
|
if (arg === '--output' || arg === '-o') {
|
||||||
'Apple Inc.',
|
outputFile = args[i + 1];
|
||||||
'Microsoft Corporation',
|
i++;
|
||||||
'Google LLC'
|
} else {
|
||||||
];
|
entities.push(arg);
|
||||||
|
}
|
||||||
const ATTRIBUTES = ['Title', 'URL', 'Snippet', 'Score'];
|
|
||||||
|
|
||||||
async function fetchEntityData(entity) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(API_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${API_KEY}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: entity,
|
|
||||||
search_depth: 'basic',
|
|
||||||
include_raw_content: false,
|
|
||||||
top_k: 1
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(`Failed to fetch data for "${entity}". Status: ${response.status}`);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
return { entities, outputFile };
|
||||||
|
|
||||||
if (!data.results || data.results.length === 0) {
|
|
||||||
console.warn(`No results found for "${entity}".`);
|
|
||||||
return {
|
|
||||||
Entity: entity,
|
|
||||||
Title: 'N/A',
|
|
||||||
URL: 'N/A',
|
|
||||||
Snippet: 'N/A',
|
|
||||||
Score: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const topResult = data.results[0];
|
|
||||||
return {
|
|
||||||
Entity: entity,
|
|
||||||
Title: topResult.title || 'N/A',
|
|
||||||
URL: topResult.url || 'N/A',
|
|
||||||
Snippet: topResult.snippet || 'N/A',
|
|
||||||
Score: topResult.score || 0
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error fetching data for "${entity}":`, error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const results = [];
|
const { entities, outputFile } = parseArgs();
|
||||||
for (const entity of ENTITIES) {
|
|
||||||
const data = await fetchEntityData(entity);
|
if (entities.length !== 3) {
|
||||||
if (data) {
|
console.error('Error: Exactly three entity names must be provided.');
|
||||||
results.push(data);
|
console.error('Usage: node src/index.js <entity1> <entity2> <entity3> [--output <file.md>]');
|
||||||
}
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.length === 0) {
|
try {
|
||||||
console.error('No data to display.');
|
const results = await compareEntities(entities);
|
||||||
return;
|
const markdown = generateMarkdownTable(results);
|
||||||
|
|
||||||
|
console.log('\nGenerated Markdown Table:\n');
|
||||||
|
console.log(markdown);
|
||||||
|
|
||||||
|
if (outputFile) {
|
||||||
|
const filePath = path.resolve(process.cwd(), outputFile);
|
||||||
|
fs.writeFileSync(filePath, markdown, 'utf-8');
|
||||||
|
console.log(`\nMarkdown table written to ${filePath}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Fatal error: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the highest score for verdict
|
|
||||||
const maxScore = Math.max(...results.map(r => r.Score));
|
|
||||||
|
|
||||||
// Add verdict column
|
|
||||||
const tableData = results.map(r => ({
|
|
||||||
Entity: r.Entity,
|
|
||||||
Title: r.Title,
|
|
||||||
URL: r.URL,
|
|
||||||
Snippet: r.Snippet,
|
|
||||||
Score: r.Score,
|
|
||||||
Verdict: r.Score === maxScore ? 'Best' : ''
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Print table
|
|
||||||
console.log('\nComparison Table (3xN with Verdict):\n');
|
|
||||||
console.table(tableData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export function generateMarkdownTable(results) {
|
||||||
|
const header = '| Entity | Summary |\n|---|---|';
|
||||||
|
const rows = results
|
||||||
|
.map(
|
||||||
|
(r) =>
|
||||||
|
`| ${escapeMarkdown(r.name)} | ${escapeMarkdown(r.summary)} |`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
return `${header}\n${rows}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeMarkdown(text) {
|
||||||
|
return text.replace(/\|/g, '\\|');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user