From ad0f904f98f13602f29d1877669f914b15fa8bd1 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 23 Jan 2026 07:12:28 -0800 Subject: [PATCH] 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 --- .../entities/knowledge-article.orm.ts | 22 +++++++++++++++++- .../postgres/entities/knowledge-chunk.orm.ts | 22 +++++++++++++++++- .../entities/system-experience.orm.ts | 22 +++++++++++++++++- .../postgres/entities/user-memory.orm.ts | 23 ++++++++++++++++++- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-article.orm.ts b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-article.orm.ts index b530f63..3d00de4 100644 --- a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-article.orm.ts +++ b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-article.orm.ts @@ -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 }) diff --git a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-chunk.orm.ts b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-chunk.orm.ts index 7f87804..d82b70f 100644 --- a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-chunk.orm.ts +++ b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/knowledge-chunk.orm.ts @@ -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: '{}' }) diff --git a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/system-experience.orm.ts b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/system-experience.orm.ts index 60d4a4c..2f138a1 100644 --- a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/system-experience.orm.ts +++ b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/system-experience.orm.ts @@ -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 }) diff --git a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/user-memory.orm.ts b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/user-memory.orm.ts index 3cdce7d..2731487 100644 --- a/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/user-memory.orm.ts +++ b/packages/services/knowledge-service/src/infrastructure/database/postgres/entities/user-memory.orm.ts @@ -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 })