96SEO 2026-08-03 11:16 15
说起来,
做 Agent 项目。向量检索几乎是绕不开的一环。
RAG 知识库、对话记忆、工具调用前的上下文召回——流程都差不多:文本转成 Embedding,存起来查询时按相似度找最相关的内容。

PostgreSQL + pgvector
MySQL + Milvus
本地采用 Docker Compose 启动 PostgreSQL和 pgAdmin,并通过初始化脚本自动建表。
services:
# PostgreSQL with pgvector
postgres:
# 官方 pgvector 镜像,基于 PostgreSQL,内置向量
说到image,pgvector/pgvector:pg16
container_name: pg_vector_db
restart: always
# 环境变量
environment:
POSTGRES_USER: copyer
POSTGRES_PASSWORD:
POSTGRES_DB: copyer-test
从ports来看,- "5432:5432"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/postgres:/var/lib/postgresql/data
# 初始化脚本
- ./init-scripts:/docker-entrypoint-initdb.d
healthcheck:
说到test。interval: 5s
timeout: 5s
retries: 5
# PostgreSQL GUI
pgAdmin:
container_name: pgAdmin
image这方面,dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL:
PGADMIN_DEFAULT_PASSWORD: copyer
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/pgadmin:/var/lib/pgadmin
healthcheck:
test这方面,interval: 30s
timeout: 20s
retries: 5
ports的观点是,- "8080:80"
depends_on:
- postgres
networks:
default:
再看name,common-network
从关键配置说明来看,
在 /init-scripts/create-story.sql 中添加下面内容:
-- 启用 pgvector
CREATE EXTENSION IF NOT EXISTS vector;老实说,-- 小说 chunk 向量表
CREATE TABLE IF NOT EXISTS story (
id VARCHAR PRIMARY KEY,vector VECTOR NOT NULL,book_id VARCHAR NOT NULL。book_name VARCHAR NOT NULL,chapter_num INTEGER NOT NULL,"index" INTEGER NOT NULL,content TEXT NOT NULL
);-- 按书籍过滤加速
CREATE INDEX IF NOT EXISTS idx_story_book_id ON story;其实,-- 向量相似度检索
CREATE INDEX IF NOT EXISTS idx_story_vector_cosine
ON story USING hnsw;
启动容器这方面,
docker-compose up -d
PgAdmin 登录后即可看到 copyer-test 数据库和 story` 表已创建完毕。
A) 安装依赖:
pnpm add pg dotenv @langchain/openai # pg 用于连接 PostgreSQL,dotenv 加载环境变量。@langchain/openai 调用 embedding 接口
// src/insert.mjs
import { config } from "dotenv";import pg from "pg";import { OpenAIEmbeddings } from "@langchain/openai";config,const pool = new pg.Pool({
说到host,process.env.PG_HOST?,"localhost",port: Number。user: process.env.PG_USER?,"copyer",password: process.env.PG_PASSWORD?,"",database: process.env.PG_DATABASE?,"copyer-test",});const embeddings = new OpenAIEmbeddings({
再看model,process.env.AI_EMBEDDING_MODEL?,"text-embedding-v3",dimensions: Number,apiKey: process.env.AI_EMBEDDING_KEY。configuration: { baseURL : process.env.AI_EMBEDDING_BASE_URL },});function toVectorLiteral {
return ``;}
// 模拟文本样例
const samples =;for {
const vector = await embeddings.embedQuery;老实说,await pool.query(
`INSERT INTO story
VALUES
ON CONFLICT DO UPDATE SET vector = EXCLUDED.vector。content = EXCLUDED.content`,);}
await pool.end;
// src/query.mjs
import { config } from "dotenv";import pg from "pg";import { OpenAIEmbeddings } from "@langchain/openai";config,const pool = new pg.Pool;const embeddings = new OpenAIEmbeddings;function toVectorLiteral {
return ``;}
const question = "PostgreSQL 怎么做向量检索?",const queryVector = await embeddings.embedQuery;const { rows } = await pool.query(
`SELECT id,content。- AS score
FROM story
WHERE book_id = $2
ORDER BY vector <=> $1::vector
LIMIT 5`,);
rows.forEach=> console.log.toFixed} → ${r.content}`));await pool.end;
This demonstrates core of **single‑library** approach:
INSERT …ON CONFLICT …SQ L ORDER BY vector <=>,no extra service layer.User‑centric conversation memory 常见痛点包括:
PostgreSQL + pgvector 把「消息内容」和「向量」放同一张表,实现“一条 SQL 完成过滤 + 相似度排序”。下面展示基于 NestJS + Prisma 的完整实现思路。
nest new nestjs-pg --package-manager pnpm --skip-git
pnpm add @nestjs/config @prisma/client class-validator class-transformer
pnpm add -D prisma
pnpm prisma init
.env 示例:
DATABASE_URL=postgresql://copyer:@localhost/:5432/copyer-test?schema=public
generator client {
provider = "prisma-client-js"
previewFeatures =
}
datasource db {
provider = "postgresql"
url = env
extensions =
}
model User {
id Int @id @default)
name String
createdAt DateTime @default) @map
conversations Conversation
@@map
}
model Conversation {
id Int @id @default)
userId Int @map
title String?createdAt DateTime @default) @map
user User @relation
messages Message
@@map
}
model Message {
id Int @id @default)
conversationId Int @map
role String // user / assistant / system
content String
vector Unsupported")?// 存储 embedding
createdAt DateTime @default) @map
conversation Conversation @relation
@@map
}
Migrate 并同步到数据库:
pnpm prisma migrate dev --name init
// src/prisma/prisma.service.ts
import { Injectable。OnModuleInit,OnModuleDestroy } from "@nestjs/common";import { PrismaClient } from "@prisma/client";@Injectable
export class PrismaService extends PrismaClient implements OnModuleInit,OnModuleDestroy {
async onModuleInit { await this.$connect;}
async onModuleDestroy { await this.$disconnect;}
}
// src/prisma/prisma.module.ts
import { Global,Module } from "@nestjs/common";import { PrismaService } from "./prisma.service";@Global
@Module({
providers:,exports:。})
export class PrismaModule {}
// src/app.module.ts
import { Module } from "@nestjs/common";import { ConfigModule } from "@nestjs/config";import { PrismaModule } from "./prisma/prisma.module";@Module({
imports:,})
export class AppModule {}
// src/messages/messages.service.ts
import { Injectable } from "@nestjs/common";import { PrismaService } from "../prisma/prisma.service";import { EmbeddingService } from "../embedding/embedding.service";// 自封装的 embedding 调用
@Injectable
export class MessagesService {
constructor(
private readonly prisma: PrismaService。private readonly embeddingSrv: EmbeddingService,) {}
/** 插入一条消息并写入对应的向量 */
async createWithVector{
const normalized = content.trim;不过,const vec = await this.embeddingSrv.embed;const vecLit = this.embeddingSrv.toPgVectorLiteral;return this.prisma.$transaction(async tx=>{
const msg = await tx.message.create({
data的观点是。{ conversationId,role,content:normalized },select:{ id:true }
});await tx.$executeRaw`UPDATE messages SET vector=${vecLit}::vector WHERE id=${msg.id}`;
不过,return msg;
}),}
}
// src/messages/messages.service.ts
async searchSimilarInConversation(
conversationId:number。query:string,topK:number=5,excludeMessageId?:number){
const qVec = await this.embeddingSrv.embed;const qLit = this.embeddingSrv.toPgVectorLiteral;const rows:any = await this.prisma.$queryRaw`
SELECT id,role,content。- AS score
FROM messages
WHERE conversation_id=${conversationId}
AND vector IS NOT NULL
${excludeMessageId?this.prisma.sql`AND id <> ${excludeMessageId}` : this.prisma.empty}
ORDER BY vector <=> ${qLit}::vector
LIMIT ${topK}
`;return rows.map}));}
The above query combines:
`WHERE conversation_id=…`,ensuring we only search inside current chat.`<=>`。which computes cosine distance when index was created with `VECTOR_COSINE_OPS`.Demos of stored messages :
从user来看,我们团队在选型 Node 后端,NestJS 的 Module 和依赖注入适合拆微服务吗?assistant:NestJS 基于 TypeScript,内置 Module、Controller、Provider 分层…从user来看,另一条线想走 Python:FastAPI 写 AI 接口…assistant:Vue 组合式 API 上手快…说起来,...
If a user later asks “NestJS 和 Python 后端怎么配合”。following call will surface two most relevant prior messages:
typescript
// 假设 conversationId 已知为42:
const results = await messagesService.searchSimilarInConversation(
42,'NestJS 和 Python 怎么一起工作',10);console.log,/*
*/
"
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback