fix(knowledge): add pgvector transformer for TypeORM embedding columns

TypeORM doesn't natively support pgvector type. Add custom transformer
to convert between JavaScript arrays and pgvector string format [1,2,3].

Fixes: invalid input syntax for type vector errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-23 07:12:28 -08:00
parent 71b98c2d07
commit ad0f904f98
4 changed files with 85 additions and 4 deletions

View File

@ -7,6 +7,22 @@ import {
Index,
} from 'typeorm';
/**
* pgvector
*/
const vectorTransformer = {
to: (value: number[] | undefined): string | null => {
if (!value || value.length === 0) return null;
return `[${value.join(',')}]`;
},
from: (value: string | null): number[] | undefined => {
if (!value) return undefined;
const str = value.replace(/^\[|\]$/g, '');
if (!str) return undefined;
return str.split(',').map(Number);
},
};
@Entity('knowledge_articles')
@Index('idx_knowledge_articles_category', ['category'])
@Index('idx_knowledge_articles_published', ['isPublished'])
@ -35,7 +51,11 @@ export class KnowledgeArticleORM {
@Column({ name: 'source_url', length: 1000, nullable: true })
sourceUrl?: string;
@Column('float', { array: true, nullable: true })
@Column({
type: 'text',
nullable: true,
transformer: vectorTransformer,
})
embedding?: number[];
@Column({ name: 'is_published', default: false })

View File

@ -6,6 +6,22 @@ import {
Index,
} from 'typeorm';
/**
* pgvector
*/
const vectorTransformer = {
to: (value: number[] | undefined): string | null => {
if (!value || value.length === 0) return null;
return `[${value.join(',')}]`;
},
from: (value: string | null): number[] | undefined => {
if (!value) return undefined;
const str = value.replace(/^\[|\]$/g, '');
if (!str) return undefined;
return str.split(',').map(Number);
},
};
@Entity('knowledge_chunks')
@Index('idx_knowledge_chunks_article', ['articleId'])
export class KnowledgeChunkORM {
@ -24,7 +40,11 @@ export class KnowledgeChunkORM {
@Column({ name: 'chunk_type', length: 20 })
chunkType: string;
@Column('float', { array: true, nullable: true })
@Column({
type: 'text',
nullable: true,
transformer: vectorTransformer,
})
embedding?: number[];
@Column('jsonb', { default: '{}' })

View File

@ -7,6 +7,22 @@ import {
Index,
} from 'typeorm';
/**
* pgvector
*/
const vectorTransformer = {
to: (value: number[] | undefined): string | null => {
if (!value || value.length === 0) return null;
return `[${value.join(',')}]`;
},
from: (value: string | null): number[] | undefined => {
if (!value) return undefined;
const str = value.replace(/^\[|\]$/g, '');
if (!str) return undefined;
return str.split(',').map(Number);
},
};
@Entity('system_experiences')
@Index('idx_system_experiences_type', ['experienceType'])
@Index('idx_system_experiences_status', ['verificationStatus'])
@ -51,7 +67,11 @@ export class SystemExperienceORM {
@Column({ name: 'negative_count', default: 0 })
negativeCount: number;
@Column('float', { array: true, nullable: true })
@Column({
type: 'text',
nullable: true,
transformer: vectorTransformer,
})
embedding?: number[];
@Column({ name: 'is_active', default: false })

View File

@ -7,6 +7,23 @@ import {
Index,
} from 'typeorm';
/**
* pgvector
* TypeORM pgvector
*/
const vectorTransformer = {
to: (value: number[] | undefined): string | null => {
if (!value || value.length === 0) return null;
return `[${value.join(',')}]`;
},
from: (value: string | null): number[] | undefined => {
if (!value) return undefined;
const str = value.replace(/^\[|\]$/g, '');
if (!str) return undefined;
return str.split(',').map(Number);
},
};
@Entity('user_memories')
@Index('idx_user_memories_user', ['userId'])
@Index('idx_user_memories_type', ['memoryType'])
@ -32,7 +49,11 @@ export class UserMemoryORM {
@Column({ name: 'related_category', length: 50, nullable: true })
relatedCategory?: string;
@Column('float', { array: true, nullable: true })
@Column({
type: 'text',
nullable: true,
transformer: vectorTransformer,
})
embedding?: number[];
@Column({ name: 'access_count', default: 0 })