From 68540b0d67d33eff573e28ce3ff14d8c50bfd98b Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 13:28:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M2-05=20=E2=80=94=20Vector=20integratio?= =?UTF-8?q?n=20contracts=20+=20citation=20context=20assembler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - integration-types.ts: IndexableChunk, CitationContext, RetrievalRequest/Response - VectorService.buildCitationContexts() for RAG citation assembly - Defines Ingestion↔Vector↔RAG interface contracts Co-Authored-By: Claude Opus 4.7 --- src/modules/vector/integration-types.ts | 54 +++++++++++++++++++++++++ src/modules/vector/vector.service.ts | 14 +++++++ test/m2.e2e-spec.ts | 26 ++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/modules/vector/integration-types.ts diff --git a/src/modules/vector/integration-types.ts b/src/modules/vector/integration-types.ts new file mode 100644 index 0000000..989182b --- /dev/null +++ b/src/modules/vector/integration-types.ts @@ -0,0 +1,54 @@ +// Shared types for Ingestion ↔ Vector ↔ RAG integration + +/** Ingestion → Vector: chunk with embedding ready for upsert */ +export interface IndexableChunk { + id: string; + sourceId: string; + knowledgeBaseId: string; + userId: string; + content: string; + chunkIndex: number; + pageNumber?: number; + sectionTitle?: string; + tokenCount: number; + embedding: number[]; +} + +/** Vector → RAG: search result with source info for citation */ +export interface CitationContext { + chunkId: string; + sourceId: string; + sourceTitle: string; + content: string; + pageNumber?: number; + sectionTitle?: string; + score: number; +} + +/** RAG → Vector: search request parameters */ +export interface RetrievalRequest { + queryEmbedding: number[]; + knowledgeBaseId?: string; + userId?: string; + topK?: number; + enableRerank?: boolean; +} + +/** RAG → Vector: complete retrieval response */ +export interface RetrievalResponse { + results: CitationContext[]; + queryTimeMs: number; +} + +/** Batch upsert result */ +export interface BatchUpsertResult { + upserted: number; + failed: number; + errors?: string[]; +} + +/** Cleanup trigger when import fails or source is deleted */ +export interface VectorCleanupRequest { + sourceId: string; + reason: 'import_failed' | 'source_deleted' | 'reparse'; +} diff --git a/src/modules/vector/vector.service.ts b/src/modules/vector/vector.service.ts index 1d22da8..d9ca674 100644 --- a/src/modules/vector/vector.service.ts +++ b/src/modules/vector/vector.service.ts @@ -2,6 +2,7 @@ import { Injectable, Logger, OnModuleInit, Optional } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { QdrantClient } from '@qdrant/js-client-rest'; import { AiGatewayService } from '../ai/gateway/ai-gateway.service'; +import type { CitationContext } from './integration-types'; export interface VectorPoint { id: string; @@ -191,4 +192,17 @@ export class VectorService implements OnModuleInit { const result = await this.client.count(COLLECTION_NAME); return result.count; } + + /** Build citation context from search results */ + async buildCitationContexts(results: SearchResult[]): Promise { + return results.map(r => ({ + chunkId: r.id, + sourceId: r.payload?.sourceId || 'unknown', + sourceTitle: r.payload?.sourceTitle || 'Untitled', + content: r.payload?.text || '', + pageNumber: r.payload?.pageNumber, + sectionTitle: r.payload?.sectionTitle, + score: r.score, + })); + } } diff --git a/test/m2.e2e-spec.ts b/test/m2.e2e-spec.ts index 269cdc3..6f0c53b 100644 --- a/test/m2.e2e-spec.ts +++ b/test/m2.e2e-spec.ts @@ -233,4 +233,30 @@ describe('M2 E2E Tests', () => { expect(res.body.success).toBe(true); }); }); + + // ══════════════════════════════════════════════ + // M2-05: Vector & Retrieval 对接 + // ══════════════════════════════════════════════ + describe('M2-05 Vector Integration', () => { + let token: string; + beforeAll(async () => { token = await loginAdmin(); }); + + it('VectorService collection accessible', async () => { + if (!token) return; + const res = await request(app.getHttpServer()) + .get('/admin-api/vector/collection') + .set('Authorization', `Bearer ${token}`) + .expect(200); + expect(res.body.data).toHaveProperty('name'); + }); + + it('VectorService count accessible', async () => { + if (!token) return; + const res = await request(app.getHttpServer()) + .get('/admin-api/vector/count') + .set('Authorization', `Bearer ${token}`) + .expect(200); + expect(res.body.data).toHaveProperty('count'); + }); + }); });