当前位置: 首页 > news >正文

使用 TypeScript 实现基于 pgvector 的 LLM 自动化测试用例

使用 TypeScript 实现基于 pgvector 的 LLM 自动化测试用例,核心是通过 TypeScript 连接 PostgreSQL(含 pgvector 扩展),结合向量生成库和 LLM 客户端,完成“预期向量存储→实际输出生成→相似度验证”的全流程。以下是具体实现方案,包含完整代码示例和关键步骤说明。

一、环境准备

  1. 依赖安装

    # 核心依赖
    npm install pg @types/pg  # PostgreSQL 客户端及类型
    npm install @xenova/transformers  # 向量生成(基于 Sentence-BERT)
    npm install openai  # 若使用 OpenAI 模型(可选)
    npm install jest @types/jest ts-jest  # 测试框架(可选,也可用 Mocha)# 类型定义及工具
    npm install -D typescript ts-node @types/node
    
  2. 数据库配置

    • 确保 PostgreSQL 已安装 pgvector 扩展(CREATE EXTENSION vector;
    • 新建数据库(如 llm_test_db),并创建测试用例表和结果表(SQL 见下文)

二、核心实现代码

1. 数据库初始化(创建表结构)

// src/db/schema.ts
import { Pool } from 'pg';// 数据库连接配置
export const pool = new Pool({user: 'postgres',host: 'localhost',database: 'llm_test_db',password: 'your_password',port: 5432,
});// 初始化表结构(首次运行时执行)
export async function initDatabase() {const client = await pool.connect();try {// 创建测试用例表(存储 prompt、预期向量等)await client.query(`CREATE TABLE IF NOT EXISTS llm_test_cases (id SERIAL PRIMARY KEY,test_case_id VARCHAR(50) UNIQUE NOT NULL,prompt TEXT NOT NULL,expected_vector vector(384) NOT NULL,  -- 若用 all-MiniLM-L6-v2 模型,向量维度为 384similarity_threshold FLOAT NOT NULL,scenario VARCHAR(50) NOT NULL);`);// 创建测试结果表await client.query(`CREATE TABLE IF NOT EXISTS llm_test_results (id SERIAL PRIMARY KEY,test_case_id VARCHAR(50) REFERENCES llm_test_cases(test_case_id),actual_output TEXT NOT NULL,actual_vector vector(384) NOT NULL,similarity FLOAT NOT NULL,passed BOOLEAN NOT NULL,test_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`);// 为向量创建索引(加速相似度查询)await client.query(`CREATE INDEX IF NOT EXISTS idx_test_cases_vector ON llm_test_cases USING ivfflat (expected_vector vector_cosine_ops) WITH (lists = 100);`);console.log('数据库表结构初始化完成');} catch (err) {console.error('数据库初始化失败:', err);} finally {client.release();}
}

2. 向量生成工具(基于 Sentence-BERT)

使用 @xenova/transformers 库生成文本向量(无需依赖外部 API,适合本地测试):

// src/embeddings/vectorGenerator.ts
import { pipeline } from '@xenova/transformers';// 初始化 Sentence-BERT 模型(生成文本向量)
let embedder: any;
async function initEmbedder() {if (!embedder) {embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');}return embedder;
}/*** 将文本转换为向量* @param text 输入文本* @returns 384 维向量数组*/
export async function textToVector(text: string): Promise<number[]> {const model = await initEmbedder();const result = await model(text, { pooling: 'mean', normalize: true });// 提取向量数据(flatten 为一维数组)return Array.from(result.data) as number[];
}

3. LLM 调用客户端(示例:调用 OpenAI API)

// src/llm/llmClient.ts
import { OpenAI } from 'openai';const openai = new OpenAI({apiKey: 'your-openai-api-key', // 从环境变量读取更安全
});/*** 调用 LLM 生成输出* @param prompt 输入提示词* @returns LLM 生成的文本*/
export async function callLLM(prompt: string): Promise<string> {const response = await openai.chat.completions.create({model: 'gpt-3.5-turbo',messages: [{ role: 'user', content: prompt }],temperature: 0.7, // 控制输出随机性(测试时可适当降低)});return response.choices[0].message.content || '';
}

4. 测试用例管理(初始化测试数据)

// src/testCases/testCaseManager.ts
import { pool } from '../db/schema';
import { textToVector } from '../embeddings/vectorGenerator';// 测试用例类型定义
export interface TestCase {test_case_id: string;prompt: string;expected_output: string; // 预期输出的核心语义similarity_threshold: number;scenario: string;
}/*** 初始化测试用例(将预期输出转为向量并存入数据库)*/
export async function initTestCases() {// 定义测试用例(可根据实际场景扩展)const testCases: TestCase[] = [{test_case_id: 'FACT-001',prompt: '地球的平均半径约为多少公里?',expected_output: '地球的平均半径约为6371公里',similarity_threshold: 0.85, // 事实类场景:高阈值scenario: '事实准确性',},{test_case_id: 'RELEV-001',prompt: '电脑频繁蓝屏的可能原因是什么?',expected_output: '蓝屏可能由驱动错误、硬件故障或系统损坏导致',similarity_threshold: 0.75, // 相关性场景:中阈值scenario: '核心相关性',},];// 将预期输出转为向量并入库for (const caseItem of testCases) {const expectedVector = await textToVector(caseItem.expected_output);const client = await pool.connect();try {await client.query(`INSERT INTO llm_test_cases (test_case_id, prompt, expected_vector, similarity_threshold, scenario)VALUES ($1, $2, $3, $4, $5)ON CONFLICT (test_case_id) DO NOTHING`, [caseItem.test_case_id,caseItem.prompt,`[${expectedVector.join(',')}]`, // pgvector 向量格式:[x1,x2,...,xn]caseItem.similarity_threshold,caseItem.scenario,]);console.log(`测试用例 ${caseItem.test_case_id} 已初始化`);} catch (err) {console.error(`初始化测试用例 ${caseItem.test_case_id} 失败:`, err);} finally {client.release();}}
}

5. 自动化测试逻辑(基于 Jest)

// src/tests/llmE2ETest.test.ts
import { pool } from '../db/schema';
import { textToVector } from '../embeddings/vectorGenerator';
import { callLLM } from '../llm/llmClient';// 从数据库获取所有测试用例
async function getTestCases() {const result = await pool.query(`SELECT test_case_id, prompt, expected_vector, similarity_threshold FROM llm_test_cases`);return result.rows;
}// 执行单个测试用例
async function runTestCase(testCase: any) {const { test_case_id, prompt, expected_vector, similarity_threshold } = testCase;// 1. 调用 LLM 生成实际输出const actualOutput = await callLLM(prompt);console.log(`\n测试用例 ${test_case_id} 实际输出:`, actualOutput);// 2. 将实际输出转为向量const actualVector = await textToVector(actualOutput);// 3. 计算与预期向量的余弦相似度(pgvector 中 <-> 为欧氏距离,余弦相似度 = 1 - 距离)const similarityResult = await pool.query(`SELECT 1 - (expected_vector <-> $1) AS cosine_similarityFROM llm_test_casesWHERE test_case_id = $2`, [`[${actualVector.join(',')}]`, // 实际向量test_case_id,]);const similarity = similarityResult.rows[0].cosine_similarity;console.log(`相似度:${similarity.toFixed(4)}(阈值:${similarity_threshold}`);// 4. 判断是否通过const passed = similarity >= similarity_threshold;// 5. 记录测试结果到数据库await pool.query(`INSERT INTO llm_test_results (test_case_id, actual_output, actual_vector, similarity, passed)VALUES ($1, $2, $3, $4, $5)`, [test_case_id,actualOutput,`[${actualVector.join(',')}]`,similarity,passed,]);// 6. 断言结果(Jest 会捕获失败)expect(passed).toBe(true);
}// 批量执行所有测试用例
describe('LLM E2E 测试', () => {let testCases: any[];// 测试前获取所有用例beforeAll(async () => {testCases = await getTestCases();});// 逐个执行测试用例testCases.forEach((testCase) => {it(`测试用例 ${testCase.test_case_id}`, async () => {await runTestCase(testCase);});});
});

三、执行流程

  1. 初始化数据库

    // src/index.ts
    import { initDatabase } from './db/schema';
    import { initTestCases } from './testCases/testCaseManager';async function main() {await initDatabase(); // 创建表结构await initTestCases(); // 初始化测试用例
    }main().catch(console.error);
    

    执行:npx ts-node src/index.ts

  2. 运行测试
    配置 Jest 后(npx jest --init),执行:

    npx jest src/tests/llmE2ETest.test.ts
    

四、关键技术点说明

  1. 向量格式处理

    • pgvector 要求向量以 [x1,x2,...,xn] 字符串格式存储,因此需将 TypeScript 数组转换为该格式。
    • 余弦相似度计算:pgvector 中 vector <-> vector 返回欧氏距离,余弦相似度 = 1 - 欧氏距离(需确保向量已归一化)。
  2. 类型安全

    • 通过 TypeScript 接口(如 TestCase)定义测试用例结构,避免类型错误。
    • 数据库查询结果建议用类型断言进一步约束(如 result.rows as TestCaseRow[])。
  3. 性能优化

    • 向量索引:对 expected_vector 建立 ivfflat 索引,加速相似度查询(尤其测试用例较多时)。
    • 模型复用:textToVector 中复用 embedder 实例,避免重复加载模型。
  4. 扩展场景

    • 多预期向量:若一个测试用例有多个合理输出,可扩展表结构存储多个 expected_vector,验证时取最大相似度。
    • 阈值动态调整:根据 scenario 字段自动适配阈值(如 scenario === '合规性' 时阈值设为 0.9)。

五、总结

该方案通过 TypeScript 整合了 pgvector 向量存储、Sentence-BERT 向量生成和 LLM 调用,实现了对 LLM 输出的“语义级自动化测试”。相比传统关键词匹配,其优势在于:

  • 容忍 LLM 输出的表述多样性(如同义词、句式变化);
  • 通过向量相似度量化输出质量,而非刚性文本比对;
  • 测试结果可持久化存储,便于后续分析 LLM 性能波动。

实际使用时,可根据测试场景调整向量模型(如用更大的 BERT 模型提升精度)和相似度阈值,平衡测试的严格性与灵活性。

http://www.dtcms.com/a/414217.html

相关文章:

  • LeetCode-hot100——验证二叉搜索树
  • CentOS7安装部署K8s
  • 【无标题】使用 Playwright 实现跨 Chromium、Firefox、WebKit 浏览器自动化操作
  • 做网站能用思源黑体吗国内外网站网站
  • 基于Qt的跨平台Word文档导出器WordEx的设计与实现
  • LeetCode 0812.最大三角形面积:三角形面积公式考察
  • LeetCode热题100--994. 腐烂的橘子--中等
  • 杭州做网站优化wordpress整站加密
  • 用Spark+Django打造食物营养数据可视化分析系统
  • 个人用云计算学习笔记 --17(DNS 服务器)
  • 泛微 Ecology10 OA 系统介绍
  • 哪个网站可以直接做ppt上海网站免费制作
  • STM32CubeMX安装教程
  • 游戏引擎以及游戏开发
  • 使用Python实现自动编写Word全自动化系统
  • 用flash做的经典网站微信公众号制作平台
  • OpenAI正式推出GPT-5-Codex增强型AI编程助手,该工具专为软件工程师设计
  • SpringMVC 进阶:核心组件详解与参数绑定全攻略
  • SpringMVC 实战:整合 MyBatis 实现完整 CRUD
  • Interleaving-Reasoning-Generation - 交错推理生成技术
  • 【C++】:继承全面解析
  • 大良营销网站建设机构设计专业所需网站
  • Spark专题-第二部分:Spark SQL 入门(8)-算子介绍-sort
  • 知识体系_分布式内存计算框架_spark
  • 银行 网站开发 干什么wordpress路径错误
  • QML 语法基础详解
  • ExcelVBA一键生成智能散点趋势图
  • ✨WPF编程基础【1.4】:类型转换器(含示例及源码)
  • 公链分析报告 - 模块化区块链2
  • 数图实战项目(十五-2:第一阶段:从RAW数据到ISP管道,听不懂在说啥?---> 那就盘它):从奥运大屏,到手机小屏,快来挖一挖里面都有什么