@ruvector/postgres-cli
Version:
Advanced AI vector database CLI for PostgreSQL - pgvector drop-in replacement with 53+ SQL functions, 39 attention mechanisms, GNN layers, hyperbolic embeddings, and self-learning capabilities
189 lines (188 loc) • 7.94 kB
JavaScript
/**
* Vector Commands
* CLI commands for vector operations
*/
import chalk from 'chalk';
import ora from 'ora';
import Table from 'cli-table3';
import { readFileSync } from 'fs';
export class VectorCommands {
static async distance(client, options) {
const spinner = ora('Computing vector distance...').start();
try {
await client.connect();
const a = JSON.parse(options.a);
const b = JSON.parse(options.b);
let distance;
let metricName;
switch (options.metric) {
case 'l2':
distance = await client.l2DistanceArr(a, b);
metricName = 'L2 (Euclidean)';
break;
case 'ip':
distance = await client.innerProductArr(a, b);
metricName = 'Inner Product';
break;
case 'cosine':
default:
distance = await client.cosineDistanceArr(a, b);
metricName = 'Cosine';
break;
}
spinner.succeed(chalk.green('Distance computed'));
console.log(chalk.bold.blue('\nVector Distance:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Metric:')} ${metricName}`);
console.log(` ${chalk.green('Distance:')} ${distance.toFixed(6)}`);
console.log(` ${chalk.green('Dimension:')} ${a.length}`);
// Additional context for cosine distance
if (options.metric === 'cosine') {
const similarity = 1 - distance;
console.log(` ${chalk.green('Similarity:')} ${similarity.toFixed(6)} (1 - distance)`);
}
}
catch (err) {
spinner.fail(chalk.red('Distance computation failed'));
console.error(chalk.red(err.message));
}
finally {
await client.disconnect();
}
}
static async normalize(client, options) {
const spinner = ora('Normalizing vector...').start();
try {
await client.connect();
const vector = JSON.parse(options.vector);
const normalized = await client.vectorNormalize(vector);
spinner.succeed(chalk.green('Vector normalized'));
console.log(chalk.bold.blue('\nNormalized Vector:'));
console.log(chalk.gray('-'.repeat(40)));
console.log(` ${chalk.green('Original Dimension:')} ${vector.length}`);
// Compute original norm for reference
const originalNorm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
console.log(` ${chalk.green('Original Norm:')} ${originalNorm.toFixed(6)}`);
// Verify normalized norm is ~1
const normalizedNorm = Math.sqrt(normalized.reduce((sum, v) => sum + v * v, 0));
console.log(` ${chalk.green('Normalized Norm:')} ${normalizedNorm.toFixed(6)}`);
// Display vector (truncated if too long)
if (normalized.length <= 10) {
console.log(` ${chalk.green('Result:')} [${normalized.map((v) => v.toFixed(4)).join(', ')}]`);
}
else {
const first5 = normalized.slice(0, 5).map((v) => v.toFixed(4)).join(', ');
const last3 = normalized.slice(-3).map((v) => v.toFixed(4)).join(', ');
console.log(` ${chalk.green('Result:')} [${first5}, ..., ${last3}]`);
}
}
catch (err) {
spinner.fail(chalk.red('Normalization failed'));
console.error(chalk.red(err.message));
}
finally {
await client.disconnect();
}
}
static async create(client, name, options) {
const spinner = ora(`Creating vector table '${name}'...`).start();
try {
await client.connect();
await client.createVectorTable(name, parseInt(options.dim), options.index);
spinner.succeed(chalk.green(`Vector table '${name}' created successfully`));
console.log(` ${chalk.gray('Dimensions:')} ${options.dim}`);
console.log(` ${chalk.gray('Index Type:')} ${options.index.toUpperCase()}`);
}
catch (err) {
spinner.fail(chalk.red('Failed to create vector table'));
console.error(chalk.red(err.message));
}
finally {
await client.disconnect();
}
}
static async insert(client, table, options) {
const spinner = ora(`Inserting vectors into '${table}'...`).start();
try {
await client.connect();
let vectors = [];
if (options.file) {
const content = readFileSync(options.file, 'utf-8');
const data = JSON.parse(content);
vectors = Array.isArray(data) ? data : [data];
}
else if (options.text) {
// For text, we'd need an embedding model
// For now, just show a placeholder
console.log(chalk.yellow('Note: Text embedding requires an embedding model'));
console.log(chalk.gray('Using placeholder embedding...'));
vectors = [{
vector: Array(384).fill(0).map(() => Math.random()),
metadata: { text: options.text }
}];
}
let inserted = 0;
for (const item of vectors) {
await client.insertVector(table, item.vector, item.metadata);
inserted++;
}
spinner.succeed(chalk.green(`Inserted ${inserted} vector(s) into '${table}'`));
}
catch (err) {
spinner.fail(chalk.red('Failed to insert vectors'));
console.error(chalk.red(err.message));
}
finally {
await client.disconnect();
}
}
static async search(client, table, options) {
const spinner = ora(`Searching vectors in '${table}'...`).start();
try {
await client.connect();
let queryVector;
if (options.query) {
queryVector = JSON.parse(options.query);
}
else if (options.text) {
console.log(chalk.yellow('Note: Text embedding requires an embedding model'));
console.log(chalk.gray('Using placeholder embedding...'));
queryVector = Array(384).fill(0).map(() => Math.random());
}
else {
throw new Error('Either --query or --text is required');
}
const results = await client.searchVectors(table, queryVector, parseInt(options.topK), options.metric);
spinner.stop();
if (results.length === 0) {
console.log(chalk.yellow('No results found'));
return;
}
const resultTable = new Table({
head: [
chalk.cyan('ID'),
chalk.cyan('Distance'),
chalk.cyan('Metadata')
],
colWidths: [10, 15, 50]
});
for (const result of results) {
resultTable.push([
String(result.id),
result.distance.toFixed(6),
result.metadata ? JSON.stringify(result.metadata).slice(0, 45) + '...' : '-'
]);
}
console.log(chalk.bold.blue(`\nSearch Results (${results.length} matches)`));
console.log(resultTable.toString());
}
catch (err) {
spinner.fail(chalk.red('Search failed'));
console.error(chalk.red(err.message));
}
finally {
await client.disconnect();
}
}
}
export default VectorCommands;