mcpField
P
pinecone-full-text-search

pinecone-full-text-search

github.com/pinecone-io数据与数据库16232 次安装6 周前更新
代码仓库

关于 pinecone-full-text-search

通过 Pinecone 文档架构 API 构建和查询全文搜索索引的工作流技能,涵盖架构设计、搜索构造及用于可靠批量导入 JSONL 的辅助脚本。

主要功能

  1. 01文档架构索引设计
  2. 02文本、向量与元数据字段
  3. 03BM25 与向量搜索模式
  4. 04文本和元数据过滤器
  5. 05批量导入 JSONL 并检查错误
  6. 06导入后轮询索引就绪状态

适用场景

  1. 01设计全文搜索索引
  2. 02构造文档架构搜索请求
  3. 03批量导入已准备的 JSONL 文档
  4. 04排查索引与查询行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pinecone-io/skills --skill pinecone-full-text-search

安装后在 Claude Code 中输入/pinecone-full-text-search确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ingestion.md
onboarding-walkthrough.md
querying.md
schema-design.md
scripts/
ingest.py

详细介绍

Pinecone 全文搜索

需要 pinecone Python SDK ≥ 10.0.0(pip install pinecone>=10.0.0)。文档架构 API 已在 10.0.0 版本中从 pinecone.preview 毕业——现在它已成为 SDK 中受 SemVer 覆盖的一等组成部分,可直接通过 pc 访问(pc.indexes、pc.index(...))。如果你沿用旧习惯导入 pinecone.preview 并找到此技能,请停止:该包已在 10.0.0 中彻底删除(会出现 ModuleNotFoundError,没有兼容垫片)。打包的辅助脚本通过 PEP 723 内联元数据固定使用 pinecone==10.0.0;如果你要基于此技能编写自己的代码,请至少固定到该版本。线上的 API 版本为 2026-07。

权威参考(最后手段)。 如果此技能及其 references/*.md 文件都没有解答你的问题,可查阅 Pinecone 官方 FTS 文档:https://docs.pinecone.io/guides/search/full-text-search。对于此处涵盖的内容,请优先参考此技能——文档可能描述了不适用的接口(例如经典向量 API 或旧版 pinecone.preview 形式),而这些接口不适用于已毕业的文档架构路径。只有确实遇到难题时才查阅此链接。

请一开始就告知用户:“此技能附带一个位于 scripts/ingest.py 的辅助工具,可安全处理批量摄取(分批 upsert、错误检查、就绪状态轮询)。进行到摄取步骤时,我会使用它。”请在对话开始时说明这一点,让用户知道该辅助工具的存在。查询构造需按照下方 查询 部分手动编写 documents.search(...)——没有查询辅助工具。

用于构建 Pinecone 全文搜索索引的工作流技能,采用已毕业的文档架构 API(pc.indexes、pc.index(name)、API 版本 2026-07)。涵盖架构设计(文本、稠密向量、稀疏向量、可筛选元数据)、数据摄取(包括异步索引和轮询)以及查询构造(text / query_string / dense_vector / sparse_vector 评分;$match_phrase / $match_all / $match_any 文本匹配筛选器;$eq / $in / $gte / $exists / $and / $or / $not 元数据筛选器)。

范围 — 此技能仅适用于文档架构 FTS API

此技能涵盖 pc.indexes.create(..., schema=...)、pc.index(name)、idx.documents.upsert(...) / idx.documents.batch_upsert(...) / idx.documents.search(...)。如果你发现自己要使用以下任意一种 API,请停止——它们属于不同的 Pinecone API,此技能的指导和辅助工具不适用:

  • 经典向量 / 记录 API:pc.Index(name)、index.upsert(vectors=[...])、index.query(vector=..., sparse_vector=...)、pc.create_index(dimension=..., metric=..., spec=ServerlessSpec(...))。这是 10.0.0 中的已弃用语法糖路径——它仍可运行,但会创建由向量数据平面提供服务的无架构索引,并通过预留的 _values 字段访问向量。它无法存储 full_text_search 字段。
  • 集成嵌入 / 记录索引:使用 embed={...} 调用 pc.create_index_for_model(...) / pc.indexes.create_for_model(...)。Pinecone 会在服务端对文本进行向量化,生成的 semantic_text 字段由记录 API 提供服务(upsert_records / search_records),而非文档 API。它们的 upsert / 搜索参数形式不同。semantic_text 字段不能与 full_text_search 字段在同一索引中组合使用。

如果用户已有非文档架构索引,可以在旁边另建一个文档架构索引——两者互不依赖——但不能事后向经典索引或集成嵌入索引添加 FTS 字段;文档架构索引的读写始终只能通过 index.documents.* 执行,不能使用 index.upsert / index.query / index.upsert_records(这些调用会被拒绝,并提示“This index has a document schema, so writes must go through the documents API”)。

查询 — 构造 documents.search(...) 调用

对于任何要求查询 FTS 索引的任务,请直接编写 documents.search(...) 调用。Schema 是权威依据——在构造调用前实时查看索引描述,以确认哪些字段启用了 FTS、哪些字段可筛选,以及哪些字段是向量。

工作流:

  1. 发现 Schema。 调用 pc.indexes.describe(<index>) 并读取 schema.fields 字典。每个字段的类都表示其类型(StringField、FloatField、DenseVectorField 等);属性则说明该字段是否启用了 FTS(full_text_search)、是否可筛选,或者是否带有 dimension。只有在本次对话中已经查看过 Schema 时,才可跳过此步骤。
  2. 构造调用,遵循下方规则——每个请求只使用一种评分类型,将硬性要求放入 filter,将排序信号放入 score_by,并在每次调用中显式指定 include_fields。
  3. 执行:使用 idx = pc.index(name=<index>); resp = idx.documents.search(...),然后读取 resp.matches。

规范形式:

# Pure BM25 keyword search
resp = idx.documents.search(
    namespace="__default__",
    top_k=10,
    score_by=[{"type": "text", "field": "body", "query": "machine learning"}],
    filter={"year": {"$gt": 2024}, "category": {"$eq": "ai"}},  # optional
    include_fields=["*"],   # always pass explicitly
)

# Hybrid: dense ranking with a lexical filter (one type in score_by + filter narrows)
resp = idx.documents.search(
    namespace="__default__",
    top_k=10,
    score_by=[{"type": "dense_vector", "field": "embedding", "values": query_embedding}],
    filter={"body": {"$match_all": "TensorFlow"}, "year": {"$gt": 2024}},
    include_fields=["*"],
)

关键规则(服务端会强制执行;在本地遵循这些规则可让 Agent 循环更紧凑):

  • score_by 是一个子句列表,但每个请求只能使用一种评分类型(服务端会拒绝混合类型)。多字段 BM25 是唯一例外:可以使用多个 text 子句,或使用一个带有 fields: [...] 的 query_string。若要结合 BM25 和稠密向量信号,请使用文本匹配筛选条件($match_all / $match_phrase / $match_any)限制稠密向量搜索;不要在 score_by 中混合评分类型。
  • filter 的键是字段名(必须存在于 Schema 中,或是已上传文档中的自动索引元数据字段——请参阅下文的可筛选元数据未在 Schema 中声明)或逻辑运算符($and、$or、$not)。字段值必须是运算符字典({"$gt": 5},而不是裸值)。
  • 每次调用都必须指定 include_fields。传入 ["*"] 可获取所有已存储字段,传入 [] 仅获取 ID 和分数,或传入字段名列表。在某些 SDK/后端版本中,省略该参数会导致 400 错误。

子句形式(用于 score_by):

type必填键适用场景
textfield(启用 FTS 的字符串字段)、query开放式关键词搜索;对单个字段进行 BM25 排序
query_stringquery(Lucene)、fields 可选Lucene 加权(^N)、邻近查询(~N)、跨字段布尔查询、短语前缀
dense_vectorfield(dense_vector)、values(浮点数列表)语义/情绪/主题排序
sparse_vectorfield(sparse_vector)、sparse_values({indices, values})自定义稀疏编码器排序

text / dense_vector / sparse_vector 使用单数形式的 field。只有 query_string 接受 fields 数组(也接受单数形式的 field 作为别名)。sparse_vector 使用 sparse_values(不是 values),与稠密向量不同。

按字段类型划分的筛选运算符:

字段类型合法运算符
启用 FTS 的 string$match_phrase、$match_all、$match_any
可筛选元数据(string/自动索引)$eq、$ne、$in、$nin、$exists
可筛选的 string_list(自动索引,未在 Schema 中声明)$in、$nin、$exists
可筛选的 float(自动索引,未在 Schema 中声明)$eq、$ne、$gt、$gte、$lt、$lte、$exists
可筛选的 boolean(自动索引,未在 Schema 中声明)$eq、$exists
逻辑包装器$and: [filters]、$or: [filters]、$not: filter

响应中的匹配项形式:

for m in resp.matches:
    m._id        # document id
    m._score     # match score (NOT `score`)
    m.to_dict()  # full doc payload (when include_fields includes the field)

如需了解更多内容——包括多字段 BM25、Lucene 模式、混合组合、RRF 合并和常见错误症状——请参阅 references/querying.md。如需了解 Schema 字段类型及其在查询侧支持的功能,请参阅 references/schema-design.md。

摄取 — 使用打包的辅助脚本

对于要求将 JSONL 文件批量摄取到现有 FTS 索引的任何任务,标准做法是调用随附的辅助脚本,而不是手写 Python 脚本。不要读取脚本源码——本节已包含所需的一切信息。

该脚本会完成三件裸 LLM 摄取代码通常会漏掉的事,每件都对应一种悄无声息的生产故障:

  1. 分批批量 upsert。 不要逐个文档循环调用 upsert。
  2. 检查每批结果。 即使个别文档失败,batch_upsert 也会返回 202;失败信息保存在 result.errors / result.has_errors 中。如果不检查,“已摄取 100 个文档”就会悄无声息地变成“已摄取 73 个文档,另有 27 个丢失”。
  3. 轮询,直到文档可搜索。 upsert 后,Pinecone 仍在构建倒排索引。在这段时间内调用 documents.search 会返回空结果。如果不进行轮询,用户可能会调试自己的查询代码一小时,却始终找不到索引竞态问题。

你只需提供准备好的、符合架构的 JSONL 文件和索引名称;其余工作由脚本完成。架构验证属于上游环节(你的准备流水线,或待推出的 prepare_documents.py)——ingest.py 会信任你交给它的内容。

调用方式:

uv run --script scripts/ingest.py \
  --data processed.jsonl \
  --index <index_name> \
  --sentinel-field <fts_field>

标志:

标志短标志必需用途
--data-d是包含准备好的文档的 JSONL 文件路径(每行一个)
--index-i是Pinecone 索引名称(必须已存在)
--sentinel-field-f是索引中启用了 FTS 的字段,用于就绪状态轮询查询。请选择架构中最长的自由文本字段。
--namespace-n否默认为 __default__
--batch-size-b否默认为 50(与 SDK 自身的 batch_upsert 默认值一致)。对于大型稠密向量,请调小此值。 包含 50 个文档、每个向量为 3072 维浮点数的批次约为 5-10 MB,可能会被拒绝;维度较高时,请调至 --batch-size 25(或更低)。
--max-concurrency—否默认为 4。用于上传批次的并行 HTTP 连接数。
--poll-deadline—否默认为 300(秒)。等待文档变为可搜索状态的最长时间,超时后放弃。
--sentinel-s否用于就绪状态轮询查询的令牌。默认值:doc[0][sentinel-field] 中按空白字符分隔的第一个词。

脚本会打印的内容:

Loading processed.jsonl ...
Loaded 5000 document(s).
Sentinel: body='The'

Upserting in batches of 50 ...
  batch @     0:   50 docs in  0.31s  (total: 50/5000)
  batch @    50:   50 docs in  0.29s  (total: 100/5000)
  ...

Upsert complete: 5000 doc(s) in 21.4s.

Polling for searchability (deadline 300s) ...
Searchable after 12.3s (3 probe(s)).

Done — total 33.7s.

如果某个批次失败,脚本会打印每条错误消息并以非零状态退出。如果轮询超时,脚本会提示可能的原因(sentinel 字段未启用 FTS、截止时间过短、文档已在结构上完成 upsert,但被倒排索引构建器拒绝),并以非零状态退出。不要屏蔽这些错误——它们揭示了数据或索引的真实问题。

以下情况不应使用此脚本:

  • 用户正在进行逐文档补丁更新。部分字段更新请使用 documents.update(...)(参见 references/ingestion.md 中的更新文档)——此脚本用于批量加载,不适用于逐条记录操作。
  • 用户要从非 JSONL 数据源摄取数据(CSV、Parquet、Postgres 转储)。请先转换为 JSONL;此脚本不会解析其他格式。
  • 用户明确要求你从头编写摄取代码(教学场景)。请按要求执行,并遵循标准模式:documents.batch_upsert + 检查 result.has_errors + 使用 sentinel 和截止时间轮询 documents.search。

脚本位于相对于此技能目录的 scripts/ingest.py。这是采用 PEP 723 内联元数据的脚本——首次调用时,uv run --script 会自动安装 typer 和 pinecone。无需设置。

使用场景

以下是三种具体模式,可作为你处理任务的参考。根据用户的请求选择最接近的模式并遵循其步骤;如果任务确实是混合场景,则灵活处理。

UC-1:端到端索引新语料库

触发条件。“为这个 CSV / JSONL / 文件夹建立搜索索引”、“基于[我的文章 / 产品 / 工单 / 转录文本]构建搜索后端”、“让我的[数据集]可搜索”。

**对于尚未处理或杂乱的数据,请先加载入门指南。**如果用户提供的是原始数据(字段类型不明确、可能包含超出 FTS 限制的长文本字段、逗号分隔的标签字符串、字符串格式的日期、可能重复的 ID 等),且没有提供明确的架构,**请阅读 references/onboarding-walkthrough.md 并逐步遵循其中的流程。**这是一份对话式指南:先了解数据,向用户说明处理决策,提出架构,创建前先确认,然后共同处理、摄取并验证。之所以提供这份指南,是因为架构不可变,而“为新语料库建立索引”属于高风险流程,在每个决策点明确征得用户同意会更稳妥。

如果用户已经提供了格式规范的 JSONL 和架构规范,请按以下精简步骤操作。

步骤(数据已准备好且架构已确定时):

  1. 检查语料库的结构——文本字段、结构化元数据,以及是否还需要向量。将其与 references/schema-design.md 中的规范结构(文章、产品、工单、图像库、代码)匹配。
  2. 为每个文本字段选择分析器设置——language、stemming、stop_words。长篇文本启用词干提取,专有名词 / 标识符则关闭。确定哪些字段用于 FTS,哪些仅作为可筛选元数据——请参见下文的可筛选元数据不在架构中声明;仅用于筛选的元数据属于文档侧的决策,而不是架构字段。
  3. 使用 SchemaBuilder 组装架构,并在调用 indexes.create 之前与用户确认——架构在 2026-07 中不可变,因此错误的调用会导致必须重新摄取。
  4. 创建索引。默认情况下,pc.indexes.create(...) 会轮询,直到索引就绪——除非你传入了 timeout=-1,否则无需单独的等待循环。
  5. 运行 scripts/ingest.py --data <jsonl> --index <name> --sentinel-field <fts_field>——参见上文的摄取——使用打包的辅助工具部分。该脚本会在一次调用中处理 batch_upsert、逐批检查错误以及更新后的就绪状态轮询。除非用户明确要求,否则不要手写循环。 6.(脚本会自动轮询——正常退出时,索引已经可以搜索。如果跳过脚本并自行实现,则必须使用哨兵查询和截止时间轮询 documents.search;batch_upsert 返回 ≠ 已可搜索——这是文档索引的等待,与步骤 4 中的索引创建等待相互独立,且需要额外等待。)
  6. 使用一到两个探测查询验证已知包含哨兵内容的字段。

**结果。**针对用户数据的可用 documents.search 调用,并返回按相关性排序的匹配结果。

UC-2:向纯文本语料添加稠密(或稀疏)信号

触发条件。“添加语义搜索”“添加嵌入”“让它支持混合搜索”,或任何描述了仅凭文本无法满足的查询模式(视觉相似度、情绪、跨模态“看起来像什么”)的提示。

步骤。

  1. 确认新信号代表文本无法表达的模态或信号——图像 / 音频 / 外部评分,或与现有 FTS 字段不同的语料。将相同文本重新编码到稠密字段中属于反模式(references/schema-design.md →“何时添加稠密字段”)。
  2. 由于架构不可变,规划创建新索引,而不是迁移。重新创建前先征得用户确认。混合索引必须在创建时显式声明其 sparse_vector 字段——之后无法再添加。
  3. 选择嵌入提供方,并在架构阶段固定其输出维度。注意原生维度可能引发的载荷大小问题——Gemini-3072 等需要截断(references/ingestion.md →“稠密向量载荷大小”)。
  4. 架构 → 创建(默认会阻塞,直到就绪)→ 内联提供嵌入或使用预缓存嵌入进行摄取。
  5. 使用混合查询进行验证:dense_vector score_by + 文本匹配筛选器($match_phrase / $match_all)。这是受支持的单次调用跨模态查询形式。

结果。 一个索引,两种检索形式——纯文本和稠密向量 + 筛选器混合检索——无需进一步设置即可运行。

UC-3:根据自然语言用户提示构建 documents.search 调用(Agent 模式)

触发条件。 Agent 收到用户提示,例如“查找有关机器学习、提到了 TensorFlow 且发表于 2024 年之后的文章”,或“按与这段文字的相似度排列气候政策相关文档”。索引已存在。

步骤。

  1. (可选)发现架构:调用 pc.indexes.describe(<NAME>) 并读取 schema.fields。如果你已在此前的对话中了解字段类型,则跳过。
  2. 使用下方的 Agent 模式分解表,将用户提示拆解为 score_by / filter 形式。(硬性要求 → filter。排序信号 → score_by。始终显式设置 include_fields。)
  3. 按照上方“查询”部分的规则构建 documents.search(...) 调用——每个请求只使用一种评分类型,运算符与字段类型匹配,并始终设置 include_fields。
  4. 执行调用。响应包含 resp.matches;遍历结果可通过 m.to_dict() 获取 m._id、m._score 和字段值。按用户要求的形式使用这些匹配结果。
  5. 如果结果为空或不正确,请按《常见问题》的故障排查树逐步检查。

结果。 与用户意图相符的实时搜索结果。

应主动避免的四种常见 UC-3 错误:

  • 在 score_by 中混用评分类型(服务端会拒绝)。将硬性要求放入 filter;在 score_by 中按一种信号排序。
  • 将硬性要求作为 BM25 词项放入 score_by,而不是作为 $match_all / $match_phrase 放入 filter(会返回排序后的结果,但不能保证包含该词项)。
  • 运算符与字段类型不匹配(例如,在浮点数字段上使用 $match_all,或在字符串字段上使用 $gt)。请参阅“查询”部分的运算符表。
  • 忽略 include_fields(某些 SDK / 后端构建版本会返回 400)。务必显式传入。

Agent 模式下的查询分解

将用户提示词线索映射到 API 形式。按从上到下的顺序阅读——识别线索,然后复制对应的形式。

用户提示词线索API 形式
开放式关键词(“关于机器学习的文章”、搜索栏查询)score_by=[{"type": "text", "field": "<field>", "query": "<terms>"}] — BM25 词元 OR
精确短语,用于驱动排名(“按‘文笔优美’进行排名”)score_by=[{"type": "query_string", "query": '<field>:("phrase here")'}]
精确短语,硬性要求(“必须包含‘machine learning’”)filter={"<field>": {"$match_phrase": "machine learning"}}
必须包含的词元,顺序不限(“必须提到 TensorFlow”、“必须与 Illinois 有关”)filter={"<field>": {"$match_all": "tokens space-separated"}} — 优先于 query_string 中的 +token,因为它是真正的硬性筛选条件,不会影响得分
至少包含其中一个词元(“包含 AI 或 ML 或 robotics”)filter={"<field>": {"$match_any": "AI ML robotics"}}
排除词元(“与 deprecated 无关”、“不要评论文章”)filter={"$not": {"<field>": {"$match_any": "deprecated opinion"}}} — 或在 query_string 中使用 -token
布尔条件 / 加权 / 邻近度 / 短语前缀(“将‘eagle’的权重设为 3 倍”、“在 N 个词以内”)score_by=[{"type": "query_string", "query": '<expr with ^N / ~N / "…"*>'}] — 只有 Lucene 支持这些功能
跨字段布尔条件(“标题或正文包含 X”)score_by=[{"type": "query_string", "query": 'title:(X) OR body:(X)'}]
数值 / 日期 / 范围 / 布尔元数据(“2024 年之后”、“评分 > 4”、“有库存”)filter={"<field>": {"$gt": ..., "$gte": ..., "$eq": ..., "$exists": true}}
类别 / 标签 / 列表成员关系(“category = fiction”、“标记为 X”)filter={"<field>": {"$in": [...]}}(适用于普通可筛选元数据和可筛选的 string_list 字段)
语义相似度 / 情绪 / 主题(“关于 ML 的文章”、“感觉忧郁的文档”)score_by=[{"type": "dense_vector", "field": "<embedding_field>", "values": embed(<text>)}] — 需要 dense_vector 字段
视觉外观 / 针对图像语料的跨模态文本查询使用相同的 dense_vector 形式,并采用生成已存储图像向量的嵌入模型。多模态嵌入器(如 Gemini-2)会将文本查询映射到图像空间。
混合:词法要求 + 语义排名(“关于 ML 且提到 TensorFlow 的文章”)词法 → filter($match_all / $match_phrase);语义 → score_by(dense_vector)。单次调用。

Agent 必须强制遵守的两条结构规则,不得例外:

  • 每个请求只能使用一种评分类型。 score_by 接受 text / query_string / dense_vector / sparse_vector,但每个请求只能按一种类型排名。不要在 score_by 中混用 dense 和 text——服务端会拒绝。多字段 BM25 是唯一允许的“列表”模式(多个 text 子句,或一个跨字段 query_string)。
  • 混合 = filter + score_by,而不是两个 score_by 子句。 当提示词同时包含词法要求和语义排名信号时,词法部分放入 filter(使用 $match_* 运算符),语义部分放入 score_by。如果确实需要让两种信号都影响排名,则运行两次搜索,并在客户端合并 ID。

可筛选元数据根本不在架构中声明

这是与旧版 pinecone.preview API 相比最大的结构变化,也很容易只理解一半。

对于 托管型索引(本技能中的所有示例都使用这种部署类型——{"deployment_type": "managed", "cloud": ..., "region": ...};省略 deployment= 时也默认使用这种类型),架构中只能声明参与搜索的字段:dense_vector、sparse_vector,以及启用了 full_text_search 的 string 字段。**所有其他字段类型——string(可筛选、不启用 FTS)、string_list、float、boolean——只要出现在架构中,创建时就会被拒绝并返回 400。**这已通过实际验证,而不只是文档说明:服务端的错误信息明确列出了这四种类型——“架构只接受用于搜索的字段(字段类型为 dense_vector、sparse_vector,以及配置了 full_text_search 的 string)。若要将字段 '<name>' 用于筛选(字段类型为 boolean、float、string 或 string_list),请将其从架构中省略,并将其包含在文档中。”(此限制仅适用于托管型/BYOC 部署——只有 pod 部署才允许在架构中声明可筛选元数据,本技能不涉及这种部署。)SchemaBuilder 的 add_float_field、add_boolean_field 和 add_string_list_field 方法仍然存在,并且在 pod 部署中仍可正常使用;但对于本技能始终使用的托管型部署,不要调用这些方法。

正确做法是:**不要在架构中声明任何仅用于筛选的字段,无论其类型是什么。**只需将字段包含在要执行 upsert 的文档中——无论该字段是否出现在架构中,Pinecone 都会自动为 upsert 文档中的字段建立索引以供筛选(字符串和数字/布尔值支持精确匹配,列表支持成员匹配),无需任何配置。

# WRONG on a managed index — the server 400s on EVERY one of these, not just category:
schema = (
    SchemaBuilder()
    .add_string_field("body", full_text_search={"language": "en"})
    .add_string_field("category", filterable=True)      # <-- rejected
    .add_float_field("year", filterable=True)            # <-- also rejected
    .add_string_list_field("tags", filterable=True)      # <-- also rejected
    .build()
)

# RIGHT — the schema declares only search fields. category/year/tags are
# simply included on upserted documents and auto-index for filtering.
schema = (
    SchemaBuilder()
    .add_string_field("body", full_text_search={"language": "en"})
    .build()
)

idx.documents.upsert(namespace=NAMESPACE, documents=[{
    "_id": "doc-1",
    "body": "...",
    "year": 2025.0,          # not in the schema — still filterable via $gt / $gte / $eq
    "tags": ["classic"],     # not in the schema — still filterable via $in / $nin
    "category": "fiction",   # not in the schema — still filterable via $eq / $in / $exists
}])

旧版预览文档中 references/schema-design.md 的前瞻性说明——“现在就声明元数据字段,为未来做好准备”——已不再适用;现在声明任何仅用于筛选的字段都是错误做法,而非仅仅没有必要。

工作流概览

分为三个阶段。每个阶段都有自己的参考文件——编写该阶段的代码前请先查阅。

  1. 设计架构。确定哪些字符串字段需要全文搜索(在架构中声明)、是否需要 dense_vector 字段(以及它是否值得占用位置),以及是否还需要 sparse_vector 字段——这些是唯一应该出现在架构中的内容。任何可筛选元数据字段——无论是 string、string_list、float 还是 boolean——都不声明;只需将其包含在 upsert 文档中。在 2026-07 中,索引创建后架构固定不变——请谨慎规划。→ references/schema-design.md
  2. **摄取文档。**如果要从已准备好的 JSONL 批量加载,请运行随附的 scripts/ingest.py 辅助脚本(它会按正确方式执行 batch_upsert、检查错误并轮询就绪状态——详见上文的 摄取——使用打包的辅助脚本部分)。如果要逐文档修补更新,请使用 documents.update(...)。无论哪种方式,HTTP 调用返回后,文档都会异步建立索引;batch_upsert 返回 202 ≠ 文档已可搜索。→ 详见 references/ingestion.md 中的规范模式。
  3. 查询索引。单个搜索请求只能按一种评分类型排序——在 score_by 中准确传入 text、query_string、dense_vector 或 sparse_vector 中的一种(支持通过多个 text 子句或跨字段 query_string 实现多字段 BM25)。添加 filter={...},用于文本匹配($match_phrase / $match_all / $match_any)和元数据筛选($eq / $in / $gte / $exists / $and / $or / $not)。使用 include_fields 控制响应内容。→ references/querying.md

快速模板

用于最小文本索引和可筛选元数据索引的端到端骨架。复制后,编辑所有标记为 # TODO: 的位置。此模板有意省略外部嵌入调用,以保持通用性;有关稠密/稀疏字段模式和嵌入服务提供方集成,请参阅 references/ingestion.md;有关四种评分形式以及文本匹配和元数据筛选器,请参阅 references/querying.md。

import time
from pinecone import Pinecone, SchemaBuilder

INDEX_NAME = "my-fts-index"        # TODO: name your index (lowercase alphanumeric + hyphens, 1-45 chars)
NAMESPACE = "__default__"          # TODO: pick a namespace; auto-created on first upsert

pc = Pinecone()                    # reads PINECONE_API_KEY
# TODO: preprod backends require an x-environment header on the client:
#   pc = Pinecone(additional_headers={"x-environment": "preprod-aws-0"})

# 1. Schema — one FTS string field. That's the only kind of field that goes
#    here: `category` and `year` below are deliberately NOT in the schema —
#    see "Filterable metadata isn't declared in the schema at all" in
#    SKILL.md. Field names must NOT start with `_` (reserved for `_id` /
#    `_score`) or `$` (reserved for filter operators), and are limited to 64
#    bytes.
schema = (
    SchemaBuilder()
    .add_string_field("body", full_text_search={"language": "en"})  # TODO: rename for your content
    .build()
)

# 2. Create the index. Polls until ready by default (pass timeout=-1 to
#    return immediately instead). Deployment defaults to managed/aws/us-east-1
#    when omitted; pass `deployment=` explicitly to pick a different region.
#    read_capacity defaults to {"mode": "OnDemand"}; pass
#    {"mode": "Dedicated", ...} only if you specifically want provisioned reads.
if not pc.indexes.exists(INDEX_NAME):
    pc.indexes.create(
        name=INDEX_NAME,
        schema=schema,
        deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
    )

idx = pc.index(name=INDEX_NAME)

# 3. Upsert a single document. `_id` is required, every other field is optional.
#    `category` and `year` aren't in the schema but are still filterable —
#    see above.
#    upsert REPLACES the document on conflict; use documents.update(...) for
#    per-field patches (references/ingestion.md).
idx.documents.upsert(
    namespace=NAMESPACE,
    documents=[{
        "_id": "doc-1",
        "body": "Full-text search is great for keyword queries.",
        "category": "intro",
        "year": 2025.0,
    }],
)

# 4. Poll until the FTS side is searchable (upsert returns BEFORE docs are indexed).
deadline = time.time() + 300
while time.time() < deadline:
    resp = idx.documents.search(
        namespace=NAMESPACE, top_k=1,
        score_by=[{"type": "text", "field": "body", "query": "search"}],  # TODO: sentinel query likely to hit
        include_fields=[],          # required on every search; [] = lightest payload (ids + _score only)
    )
    if resp.matches:
        break
    time.sleep(5)

# 5. Search — text scoring composed with metadata filter.
resp = idx.documents.search(
    namespace=NAMESPACE,
    top_k=5,
    score_by=[{"type": "text", "field": "body", "query": "keyword queries"}],
    filter={"year": {"$gte": 2024}},        # TODO: adjust filter or drop it
    include_fields=["*"],                    # "*" = all stored fields; [] = `_id` + `_score` only
)
for m in resp.matches:
    print(m._id, m._score, m.to_dict())

常见注意事项

  • 在托管索引的架构中,不应声明任何可筛选的元数据字段,无论是 string、string_list、float 还是 boolean。 schema= 中只允许使用 dense_vector、sparse_vector 和启用了 FTS 的 string 字段;其他所有字段类型都会被拒绝,并返回 400。这已通过真实 API 在线确认,并非仅见于文档。省略这些字段,让系统根据已 upsert 的文档自动建立索引。请参阅上文 完全不在架构中声明可筛选元数据——这最可能导致从旧版 pinecone.preview API 迁移而来的代码出问题,因为旧 API 允许声明这类字段。
  • 每个搜索请求只能使用一种评分类型。 score_by 接受 text、query_string、dense_vector 或 sparse_vector,但请求只能按一种类型排序。多字段 BM25 没有问题(传入多个 text 子句,或一个跨字段的 query_string)。若要结合 BM25 排序和 dense_vector(或 sparse_vector)信号,请在词法字段上使用文本匹配 filter 运算符($match_phrase / $match_all / $match_any)限制稠密搜索,而不是在 score_by 中混用类型。服务器会拒绝“在 score_by 中混合稠密向量和文本子句”的模式。
  • 文本匹配筛选运算符是跨模态的关键。 $match_phrase(精确短语)、$match_all(匹配所有词元,顺序不限)、$match_any(至少匹配一个词元)是作用于 full_text_search 字段的筛选运算符。每个运算符接受一个字符串(最多 128 个词元)。它们会复用字段的分词器/词干提取器,可组合使用 $and / $or / $not,也是将词法预筛选与稠密或稀疏排序组合起来的受支持方式。短语邻近度("…"~N)、词项加权(^N)和短语前缀("… word"*)只能用于评分——它们属于 query_string,不能用于 filter。
  • 预生产环境的后端需要在 Pinecone() 客户端中设置 additional_headers={"x-environment": "..."}。 缺少此标头会让请求进入生产环境,导致出现“找不到索引”/结果为空等症状,看起来像代码错误,但实际上不是。
  • 每次调用 documents.search(...) 都必须传入 include_fields。 传入 ["*"] 可包含所有已存储字段;也可以传入字段名称列表以选择要返回的字段。在某些 SDK/后端构建版本中,省略该参数会返回 400,而非采用合理的默认值;请始终显式传入,避免意外情况。
  • 匹配分数是 _score;文档 ID 是 _id。 系统匹配分数始终位于 _score 字段,因此用户元数据中名为 score 的字段可以与之共存。请始终读取 m._score,不要读取 m.score。
  • 保留字段名称:不能以下划线 _ 或美元符号 $ 开头,且最长为 64 字节。 _ 用于系统字段(_id、_score);$ 用于筛选运算符。架构验证会拒绝违反任一规则的名称。长度上限按字节计算,而不是按字符计算——使用非 ASCII 名称时请格外注意。
  • 向量字段基数:在 2026-07 中,每个索引最多只能有一个 dense_vector 和一个 sparse_vector 字段。 文本字段可以有多个。
  • 混合索引必须在创建时声明其 sparse_vector 字段——之后无法再添加。 在 2026-07 中,仅在稠密字段上设置 metric="dotproduct" 并不代表创建混合索引(旧版预览 API 中则是如此)。无论是否声明该字段,创建调用都会成功;如果缺少 sparse_vector 字段,之后只有稀疏写入会被拒绝,而且通常发生在代码库的其他部分。如果你要迁移旧版预览架构,请在重新创建索引前检查每个设置了 metric="dotproduct" 的稠密字段,确认是否缺少稀疏字段。
  • 默认情况下,batch_upsert 失败时不会显式报错。 返回值包含 has_errors、failed_batch_count,以及带有 error_message 的 BatchError 对象列表。如果不检查这些内容,就会看到“已上传 0 / N”和无限期的“尚未建立索引”轮询;真正的原因(负载过大、架构不匹配、保留字段名称)会被隐藏。请务必在后续步骤前打印 result.errors[*].error_message。
  • 批次中的稠密向量负载大小很重要。 一个含 50 个文档、向量维度为 3072 的批次,大小约为 5–10 MB,可能会被拒绝。如果每个批次都失败,可先尝试通过服务提供商的截断参数降低嵌入维度(例如 Gemini 的 output_dimensionality=768),再排查架构问题。
  • 异步建立索引:batch_upsert 返回不等于可以搜索。 HTTP 调用返回后,服务器仍会在后台构建倒排索引。如果立即查询,结果集会为空。请始终使用哨兵查询轮询 documents.search,并设置截止时间(参见 references/ingestion.md 中的模式)。这与 pc.indexes.create() 自带的默认轮询(等待索引就绪)是两回事,且需要额外执行。
  • 字符串 FTS 字段的形状是 full_text_search={...}(字典),也可以使用 True 采用服务器默认值。用户可设置的子字段: language、stemming、stop_words、ngram。由服务器设置(可在 describe() 响应中看到,但创建索引时无法设置):lowercase(默认为 true)和 max_term_len。词干提取默认关闭(false),若设置 stop_words=True 则必须启用。托管索引中的字符串字段只能启用 FTS 或可筛选,不能同时启用——在 full_text_search 旁传入 filterable=True 会导致服务器静默保留筛选配置并丢弃搜索配置。
  • 在 2026-07 中,索引创建后架构固定不变。 不支持创建后添加、移除或更改字段类型。要更改现有向量字段的维度或度量标准,必须创建新索引。请一次性规划好架构。
  • 支持按字段更新:documents.update(...)。 可传入形如 documents=[{"_id": ..., "set_fields": {...}}] 的记录,也可以使用 filter= + set_fields=/remove_fields=,通过元数据匹配一次性修改多份文档。如果你想完全替换文档,documents.upsert 在 _id 冲突时仍会执行完整替换。请参阅 references/ingestion.md →“更新文档”。
  • 文档操作:搜索、获取和删除现在都支持 filter。 documents.fetch 和 documents.delete 均新增了 filter 参数——请传入 ids、filter 中的一个,或者(仅限删除)传入 delete_all。按筛选条件获取的结果采用分页方式(通过 pagination_token 每页最多 10,000 份文档);按 ID 获取的结果永不分页。documents.delete 会返回一个响应对象,其中包含 matched_records(按筛选条件删除时为某一时点的匹配数量;按 ID 列表或使用 delete_all 删除时为 None——删除操作本身会异步执行)。
  • documents.list(...) 可枚举某个命名空间中的文档 ID;旧版预览 API 没有等效功能。 结果采用惰性分页,并按 ID 排序,还可选择使用 prefix 筛选。请参阅 references/querying.md →“documents.list — 枚举文档 ID”。
  • 首次 upsert 时会自动创建命名空间。 将任意命名空间字符串传给 documents.upsert / batch_upsert,即可按需创建命名空间;不同命名空间中的文档彼此完全隔离。如果不需要分区,请使用 "__default__"。
  • 命名空间管理和 describe_index_stats 现在可用于文档架构索引。 idx.create_namespace(name=...)、idx.list_namespaces()、idx.describe_namespace(name=...)、idx.delete_namespace(name=...) 和 idx.describe_index_stats() 均可使用,并已确认正常工作——签名和示例请参阅 references/ingestion.md →“命名空间管理”。
  • 文档和请求大小限制:每份文档最大 2 MB;每个请求最大 2 MB 和 1000 份文档;每个启用 FTS 的 string 字段最大 100 KB 和 10,000 个词元(超过 256 字节的词元会被分析器截断);每份文档的可筛选元数据(即 FTS 字段以外的所有内容)最大 40 KB。架构最多可声明 100 个 FTS 字符串字段。对于长篇文本语料,请在导入前分块——参阅 references/ingestion.md。
  • score_by 子句格式——对于 text/dense_vector/sparse_vector,规范写法使用单数 field;只有 query_string 使用 fields 数组。
    • text:{"type":"text", "field":"<fts_field>", "query":"<terms>"}。
    • query_string:{"type":"query_string", "query":"<lucene>", "fields":["<a>","<b>"]}(可选的 fields 数组;query_string 也接受裸字符串 "fields":"body",以及作为别名的旧式 "field":"body")。
    • dense_vector:{"type":"dense_vector", "field":"<dense_field>", "values":[/*floats*/]}。
    • sparse_vector:{"type":"sparse_vector", "field":"<sparse_field>", "sparse_values":{"indices":[...],"values":[...]}}——注意,稀疏子句使用 sparse_values(不是 values)。
  • 不支持单词项前缀通配符。 auto* 在 query_string 中不起作用;请使用短语前缀("machine lea"*——短语必须至少包含两个词项,最后一个词项会按前缀匹配)。
  • 在启用了 CMEK 的项目中,只要索引包含任意 full_text_search 字段,就无法创建索引;2026-07 中,文档型索引也不支持备份/恢复。 如果这两项要求中的任意一项不可或缺,那么文档架构 FTS 功能目前还不能满足需求。
  • 支持模糊搜索(term~N)和正则表达式搜索(field:/pattern/), 但仅限 type: "query_string"——请参阅 查询语法 和 references/querying.md。这两种语法都不能用于 type: "text"。
  • Pinecone 支持从对象存储批量导入文档型索引(参见 导入数据)——只是此 skill 尚未实现该功能。此处请使用 documents.upsert / documents.batch_upsert(通过 scripts/ingest.py,参见上文)导入数据;也可以在有专用导入 skill 后使用它。

扩展点

目前已随项目发布在 scripts/ 下:

  • scripts/ingest.py — 将准备好的 JSONL 批量写入现有 FTS 索引。会以安全的批次大小处理 batch_upsert,检查每个批次的 result.errors,并在失败时明确中止;随后使用哨兵值和截止时间轮询 documents.search,直到文档可搜索。与架构无关:只接受 --data、--index 和 --sentinel-field。用法请参阅上文 导入 — 使用打包的辅助工具 一节。

查询构造没有打包的辅助工具——请按照上文 查询 一节,直接编写 documents.search(...) 调用。

评论

支持 Markdown