前面介绍了通过 update API 根据 ID 更新数据,如果 ID 不存在,不会报错,也不会做任何更新动作。虽然没有报错,但是在真实业务中,这比报错还难受,不好怕查问题。
为此,引入 upsert API,如果数据不存在,就插入新数据,如果存在,则更新数据。这是我平常用的最多的写入方法,因为灌数据经常要重跑,用 add 的话第二次就全被忽略了。
方法签名如下:
collection.upsert(
ids,
embeddings=None,
metadatas=None,
documents=None,
images=None,
uris=None,
)参数说明:
ids:必填,文档唯一标识,类型为 list[str]
embeddings:向量数组,类型为 list[list[float]]。如果传 None,自动调用 collection 的 embedding 函数,从 documents/images 生成向量。
metadatas:元数据,类型为 list[dict],每条记录附加自定义键值对,用于过滤检索。例如:[{"source":"pdf", "page":1}]
documents:原始文本,类型为 list[str]。文本向量库常用,可以不填(只用 embedding)
images:图像数据,多模态专用,图像数组,类型为 list[np.ndarray]。注意,只有多模态集合才会用到,普通文本向量库填 None
uris:资源路径,类型为 list[str],如文件 / 图片 url、本地路径,多模态使用。
下面通过一个简单示例验证 upsert 方法的用法和效果:
import chromadb
client = chromadb.EphemeralClient()
collection = client.get_or_create_collection("demo_collection", embedding_function=None)
# 新增文档
collection.add(
ids=["q1", "q2"],
documents=[
"订单付款后多久发货?一般 48 小时内出库",
"如何申请退款?在订单详情页点击申请退款",
],
metadatas=[
{"category": "物流"},
{"category": "售后"},
],
)
# 更新文档
collection.upsert(
ids=["q1", "q2","q3"],
embeddings=None,
metadatas=None,
documents=[
"订单付款后多久发货? 48小时",
"如何申请退款? 订单页",
"快递丢了怎么办?联系客服补发或全额退款",
],
images=None,
uris=None,
)
print(collection.get(ids=["q1", "q2", "q3"]))运行示例,输出如下:
{'ids': ['q1', 'q2', 'q3'], 'embeddings': None,
'documents': ['订单付款后多久发货? 48小时', '如何申请退款? 订单页', '快递丢了怎么办?联系客服补发或全额退款'],
'uris': None, 'included': ['metadatas', 'documents'], 'data': None,
'metadatas': [{'category': '物流'}, {'category': '售后'}, None]}从输出得知,更新时 q3 是不存在的,upsert 自动帮我们新增了,而且 metadatas 也被清空了,是因为我们调用 upsert 时没有传递 metadatas。
主要区别如下:
| 行为 | add | update | upsert |
| id 不存在 | 插入 | 静默跳过 | 插入 |
| id 已存在 | 静默忽略 | 局部覆盖 | 整体覆盖 |
| 只传部分字段 | — | 保留其他字段 | 未传字段会被清空 |
特别注意:upsert 是“整条替换”语义,你 upsert(ids=["x"], metadatas=[{"a":1}]) 而不带 documents,那这条记录的 document 会被设成空。
所以,用 upsert 就把所有字段都带上,别偷懒。