TellWorldAI 世界档案
问档案English

首页›实践›实践

实践

用 MCP 做语义搜索:Azure OpenAI 向量加 pgvector

用 Azure OpenAI 向量加 pgvector 余弦检索做商品搜索,再包装成 MCP 工具,供智能体按自然语言查询。本文讲每步的参数和 SQL。

这篇适合谁

适合已经在用 PostgreSQL、想给商品库加自然语言搜索,并把它交给 AI 智能体调用的开发者。读完你应该能顺着链路从生成向量走到向量查询,并知道先检查哪些参数。

为什么要做语义搜索

关键词搜索理解不了意图。比如「雨天穿的舒适跑鞋」,如果商品描述里没有这些字,关键词搜索就找不到。语义搜索把文本转成向量,按相似度给商品排序[1]。

准备什么

  • 一个 Azure OpenAI 的 embedding 部署。示例用的是 text-embedding-3-small,输出 1536 维向量[1]。
  • 带 pgvector 扩展的 PostgreSQL[1][2]。
  • Redis,可选,用来做缓存[1]。
  • 两个环境变量:PROJECT_ENDPOINT 和 EMBEDDING_DEPLOYMENT_NAME,分别对应端点和部署名[1]。

第一步:生成向量

嵌入管理器里放着主要参数。示例使用 1536 维的向量,单次请求最多 8000 个 token,批大小为 100[1]:

        self.embedding_dimension = 1536  # text-embedding-3-small dimension
        self.max_tokens = 8000  # Maximum tokens per request
        self.batch_size = 100  # Batch processing size

没有命中缓存的文本按每批 100 条发送,批与批之间停 0.1 秒。缓存的键是部署名和文本拼接后的 SHA-256 哈希,缓存 24 小时[1]。示例设定的速率限制是每分钟 1000 次请求、15 万个 token[1]。

有两处要先检查。代码里用了 os.getenv 读配置,但没有导入 os,需要自己补上这一行[1]。端点和部署名则从 PROJECT_ENDPOINT 和 EMBEDDING_DEPLOYMENT_NAME 读取[1]。

第二步:为每个商品拼出待嵌入的文本

商品嵌入器用一个模板把字段拼成一段文字,模板是 {product_name} {brand} {description} {category} {tags},然后压缩多余的空白[1]:

        self.text_template = "{product_name} {brand} {description} {category} {tags}"

向量写入 retail.product_embeddings 表,按商品 ID 和嵌入模型做 upsert[1]。默认只为还没有向量、或者向量早于商品 updated_at 的商品重新生成。要全部重建,就把 force_regenerate 设为真[1]。

第三步:按余弦距离查询

查询文本先转成向量。向量以 [x,y,...] 的文本形式传给 PostgreSQL,再用 ::vector 转换类型[1]:

        embedding_vector = f"[{','.join(map(str, query_embedding))}]"

查询的主体部分如下。它按店铺和上架状态过滤,应用相似度阈值,按距离排序并限制行数[1]:

WHERE pe.store_id = $2
  AND p.is_active = TRUE
  AND 1 - (pe.embedding <=> $1::vector) >= $3
ORDER BY pe.embedding <=> $1::vector
LIMIT $4

语义搜索工具要求传入 query 和 store_id。查询文本 1 到 500 个字符。默认返回 20 条,最多 100 条,默认相似度阈值为 0.7[1]。

第四步:混合搜索

混合搜索在语义分数之外加上关键词分数。默认权重是语义 0.7、关键词 0.3[1]:

                "semantic_weight": {
                    "type": "number",
                    "description": "Weight for semantic similarity (0.0 to 1.0)",
                    "minimum": 0.0,
                    "maximum": 1.0,
                    "default": 0.7
                },

关键词部分用 PostgreSQL 的全文检索,即 to_tsvector 和 plainto_tsquery,再通过 FULL OUTER JOIN 与语义结果合并[1]。

推荐怎么做

推荐工具支持四种类型:similar_products、customer_based、trending 和 cross_sell[1]。基于客户的推荐会取该客户最近 days_back 天买过的商品向量并求平均,days_back 默认 90 天。只返回得分不低于 0.6 的相似商品[1]。

性能:索引和缓存

索引示例用的是 HNSW 索引,使用 vector_cosine_ops,参数 m = 16、ef_construction = 200[1]:

CREATE INDEX CONCURRENTLY idx_product_embeddings_vector_optimized 
ON retail.product_embeddings 
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

它还给了一些 PostgreSQL 参数示例,比如 work_mem = '256MB'、shared_buffers = '512MB'[1]。这些是起点,不是针对你机器调好的结果。

Redis 缓存默认连接 redis://localhost:6379,过期时间分别是:向量 7 天,搜索结果 1 小时,推荐结果 4 小时[1]:

        self.embedding_ttl = timedelta(days=7)  # Embeddings cached for 1 week
        self.search_ttl = timedelta(hours=1)    # Search results cached for 1 hour
        self.recommendation_ttl = timedelta(hours=4)  # Recommendations cached for 4 hours

Redis 连不上时,缓存管理器会打印警告,然后不带缓存继续工作[1]。

混合搜索的语义部分有一道门槛

混合查询里,语义部分只保留相似度不低于 0.5 的结果。这个门槛比单独的语义搜索工具默认的 0.7 低[1]。也就是说,混合搜索会召回更多候选,再由关键词分数和语义分数加权合并排序[1]。

缓存怎么分类,怎么清

缓存分三类,过期时间各不相同:向量 7 天、搜索结果 1 小时、推荐结果 4 小时[1]。缓存管理器还提供 invalidate_store_cache 方法,按店铺 ID 组成的模式删除相关的缓存键[1]。Redis 不可用时,缓存管理器只记录警告,并以无缓存的方式继续运行[1]。

输入约束要写进工具 schema

store_id 只能包含字母、数字、下划线和连字符[1]。查询文本限定在 1 到 500 个字符之间,返回条数限定在 1 到 100 之间,相似度阈值是 0.0 到 1.0 之间的数字[1]。把这些限制写进 schema,智能体在调用前就能按规则构造参数,出错也更早暴露。

常见问题

搜索结果为空怎么办? 默认相似度阈值是 0.7,先检查阈值,再对比几条已知相关商品的分数。后续的测试与调试内容会讲向量质量和相关性的验证[1]。

能不能直接复制代码运行? 不能原样运行。补上缺失的 os 导入,并把表名和结构改成你自己的数据库[1]。

pgvector 的文档在哪里? 在 pgvector 的 GitHub 仓库里[2]。

本文改编自 Microsoft 开源课程 *MCP for Beginners* 中的语义搜索实验[1],原文以 MIT 许可证发布。

来源

  1. 1
    Semantic Search IntegrationMicrosoft mcp-for-beginners (MIT)
  2. 2
    pgvectorpgvector project

引用本页: 用 MCP 做语义搜索:Azure OpenAI 向量加 pgvector. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-semantic-search-server. 更新于 2026-11-14

相关动态