如果你是用 Make 或其他低代碼工具打 Qdrant,最容易把人逼瘋的,通常不是向量檢索本身,而是那種看起來完全不合理的 JSON 400。
你會看到這些錯:
Format error in JSON body: key must be a stringexpected value at line 1 column 1expected value at line 2 column 32
最氣的是,你把 body 貼去 JSON validator,居然還過。
然後你就開始懷疑人生,懷疑 Qdrant,懷疑 HTTP module,最後懷疑自己是不是該先離開電腦去買杯咖啡。
這篇想講的核心很簡單:
Qdrant 的 JSON 400,很多時候不是 Qdrant 壞掉,而是 server 實際收到的 body,根本不是你以為你送出去的那個東西。
這件事一旦想通,排查路線就會突然很乾淨。你不再把錯誤想成「Qdrant 有沒有接受我的 request」,而是先回頭問:
我真的送出了一個它期望的 JSON object 嗎?還是我只是送出了一個看起來很像 JSON 的字串?
先講核心主張
如果要把這篇收成一句話,我會這樣講:
JSON 400 不是 Qdrant 的問題類型,而是客戶端真相的問題類型。
這裡所謂的客戶端真相,指的是 server 真正收到的 bytes。
不是你在 Make 裡看到的模板,不是你貼到 validator 的內容,也不是你腦中想像的 request body。
Qdrant 其實很誠實。它收到什麼,就照什麼解析。你覺得自己送的是 object,但它如果實際收到的是 string,它就會把你打回來。這沒什麼玄學。
先把兩種最常用的 endpoint 釘死
很多 JSON 400 看起來像 parse error,實際上是 body shape 根本跟 endpoint 不對齊。
Qdrant points 相關常見的幾條路徑,大概是:
POST /collections/{collection}/points
用來 upsert pointsPOST /collections/{collection}/points/query
單筆 queryPOST /collections/{collection}/points/query/batch
批次 queryciteturn851418search2turn851418search4
這裡有一個很容易踩的坑:
單筆 endpoint 跟 batch endpoint 期待的 body 長得不一樣。
單筆 query
Qdrant 要的是類似這種 object:
{
"query": [0.1, 0.2, 0.3],
"limit": 3,
"with_payload": true
}
batch query
Qdrant 要的是:
{
"searches": [
{
"query": [0.1, 0.2, 0.3],
"limit": 2
},
{
"query": [0.1, 0.2, 0.3],
"limit": 1
}
]
}
如果你把 batch body 打到單筆 endpoint,錯誤訊息未必會很貼心地告訴你「你用錯 endpoint」,它可能只回你一個很討厭的 400。
三種最常見的「看起來像 JSON,但其實不是」
這類問題我現在幾乎都先從三個方向查。
1. 你以為送的是 JSON object,其實送的是 JSON string
這是最常見的。
你在工具裡看到的內容可能長這樣:
{
"query": [0.1, 0.2],
"limit": 3
}
但真正送出去的其實是:
"{"query":[0.1,0.2],"limit":3}"
這時候 validator 還是會說合法,因為它確實是一個合法的 JSON string。
問題是 Qdrant 要的是 object,不是外面又多包一層引號的字串。
這種情況最常見的根源就是 double-stringify。
你先把 object stringify 一次,再讓 HTTP module 又把整個東西當字串送出去。看起來像 JSON,其實只是一顆 JSON 風味糖果。
2. 你以為送的是 array,實際上 array 被展平成裸數字串
你本來想送的是:
{
"query": [-0.02, 0.03, 0.04]
}
但有些低代碼工具在 raw 模板裡插值時,會把 array 展成:
{
"query": -0.02, 0.03, 0.04
}
這就已經不是合法 JSON 了。
server 當然會在某個 column 爆掉。這類 line 2 column 32,很多時候就是在跟你說:你的 vector 根本沒有被包成 JSON array。
3. endpoint 跟 body shape 根本不匹配
這個也很常見。
你送的是:
{
"searches": [...]
}
但 URL 寫的是 /points/query。
或者你明明打 /points/query/batch,卻只送了一個單筆的 query object。
這種錯很討厭,因為它看起來像 JSON parse error,實際上是 request shape 與 endpoint contract 不相符。
最穩的排查順序:先把最小可用跑通
我現在很少一開始就上 batch query、filter、payload、rerank 全家餐。
JSON 問題最有效的排查法,其實很樸素:
Step 1:先跑單筆 query,不加 filter
{
"query": [/* embedding */],
"limit": 1,
"with_payload": true
}
Step 2:確認 vector 真的是 array,不是字串
這一步超無聊,但很重要。
如果 HTTP 模組有 request preview 或 raw log,一定要看那個,不要只看你自己組的模板。
Step 3:再加 filter
等單筆 query 穩了,再把 filter.must 加回來。
Step 4:最後才升級到 batch
這時候才把 URL 換成 /points/query/batch,body 也改成 searches: [...]
這個順序的好處是,你永遠知道自己是在哪一層把事情弄壞的。
為什麼 JSON validator 會過,但 server 還是報錯
這件事真的很容易把人搞混。
因為 validator 只能檢查你貼進去的內容是不是一份合法 JSON。它不會幫你檢查:
- 你是不是把 object 包成 string
- 你的 endpoint 跟 body shape 是否對齊
- 你的 array 在實際插值時有沒有被展平
- 你的 HTTP client 最後是不是根本送了別的東西
也就是說,validator 驗的是你手上的文字,不是server 端實際收到的 request body。
這兩者常常不是同一件事。
在 Make 或低代碼工具裡,我比較信的做法
如果你是在 Make 這類環境裡工作,我會比較建議這幾條:
1. 優先用 object builder,不要狂拼 raw text
如果平台有「Create JSON」或類似模組,通常比你在 raw body 裡徒手插值更穩。
2. embedding 要當資料,不要當文字
一旦你把 embedding 先變成字串,再塞回 JSON,後面很容易出現裸數字、CSV 風味、或 escape 一團亂的問題。
3. 避免 double-stringify
這件事值得講三次。
如果整個 body 是 object,就讓 HTTP module 直接送 object。不要自己先 stringify,再交給它幫你包第二次。
這類錯誤跟 Qdrant 其實沒那麼有關
這也是我後來比較確定的一件事。
很多人會把這種問題歸類成「Qdrant 不好用」。但說穿了,這比較像是:
- 低代碼工具的插值真相
- HTTP client 的 body 序列化真相
- endpoint contract 與 payload shape 的真相
Qdrant 在這裡只是負責很誠實地告訴你:「我收到的不是我要的東西。」
什麼時候這篇的判準不成立
這裡也要補個邊界。
不是所有 400 都是 JSON 組裝問題。
有些 400 確實可能是:
- filter schema 不對
- payload index 沒建
- query 結構用的是舊版 SDK / wrapper 期待的格式
- endpoint 路徑本身打錯
所以這篇的核心判準不是「看到 400 就一定是 double-stringify」,而是:
先確認 server 收到的是不是一份 shape 正確的 request,再去懷疑更高層的 retrieval 設計。
我現在比較相信的 debug 心法
如果你要我把這類問題收成一組我真的會拿來用的判準,大概是這樣:
- 先確認 endpoint
- 再確認 body 最外層 shape
- 再確認 vector 是不是 array
- 再確認是不是被 double-stringify
- 單筆跑通後,才升級到 batch
- filter 跟 payload index 問題,留到 JSON 真的穩之後再查
這樣做不華麗,但很有效。
下一篇接什麼
這篇是在講 client 真相。
下一篇會往另一個更大的主題走:RAG 怎麼從能回答,走到真的能上線。
也就是說,retrieval、citation、evaluation、ACL、versioning、observability 這些原本看起來像附加題的東西,為什麼其實才是 production RAG 的主考卷。