搜尋完整文件

輸入關鍵字,例如 idempotency、proposal lifecycle 或權限。

選擇 開啟
English
瀏覽文件

Relay · Search

搜尋 channel

Relay 在單一 channel 內提供 ranked full-text search。Search 是 derived read model。Message store 仍是 source of truth,post-commit worker 會 asynchronous update search index。

目前的 public SDK 尚未提供 client.search(query)。請從 host client 呼叫 HTTP endpoint,或包在 host-owned API layer 後方。

Request

Caller 必須是目前的 channel member:

GET /api/channels/{channel_id}/messages/search?q=refund&limit=20
Authorization: Bearer <jwt>
Parameter條件
qRequired,trim 後為 1 到 200 個 Unicode character
limitOptional,default 20,maximum 100
cursorOptional,使用前一頁回傳的 opaque cursor

limit 應使用 1 到 100 的正整數。目前 endpoint 的 lower-bound validation 不完整,所以 0 或負數不屬於 public contract。

Default Postgres search path 使用 web-search syntax,支援 term、quoted phrase、OR,以及以 leading minus 排除內容:

"payment failed" OR refund -sandbox

目前沒有 sender、date range、mention、attachment type 或 minimum rank filter。

Response

{
  "data": {
    "hits": [
      {
        "message": {
          "id": "message-uuid",
          "channel_id": "channel-uuid",
          "sender_id": "user-uuid",
          "body": "The refund is ready",
          "seq": 42,
          "attachments": []
        },
        "rank": 0.18,
        "snippet": "The <mark>refund</mark> is ready"
      }
    ],
    "next_cursor": null,
    "degraded": false
  }
}

每個 hit 包含一般 Relay message payload,以及 adapter 產生的 ranksnippet。沒有下一頁時,next_cursornull

安全顯示 snippet

snippet 是 untrusted user content,其中混有 adapter 插入的 <mark> tag。它不是 safe HTML fragment。不要直接指定給 innerHTML 或 Svelte {@html}

目前最安全的方式是把 snippet 當成 text render。需要 highlight 時,請使用只允許 mark 的 strict allowlist sanitizer,或由 client 從 message body 重新產生 highlight。

Structured highlight-range response 與 server-escaped snippet contract 尚未實作。

Indexed content

目前的 Postgres index 會 match:

  • Message body
  • Bound attachment filename

Attachment file byte 不會進入 index。Sender ID 與 mention token 存在於 derived document model,但沒有加入目前的 Postgres search vector。

Filename-only match 可能回傳沒有顯示該 filename 的 snippet,因為 snippet 只從 message body 產生。請使用 returned message 的 attachments metadata 顯示 filename。

Ordering 與 cursor

Primary search result 使用以下順序:

rank DESC, inserted_at DESC, message_id DESC

Cursor 必須保持 opaque,且只能在相同 tenant、channel、query 與 search mode 下重用。只有在相關 index row 沒有變化時,pagination 才保持穩定。

目前的 cursor 有兩項已知限制:

  • 無法 decode 的 cursor 會被視為沒有 cursor,並重複第一頁,不會回傳 422
  • Cursor 沒有簽章,也沒有綁定 original query 與 channel。

Query 改變時,client 應丟棄 stored cursor。Pagination 期間 index 發生變化時,應以 message.id deduplicate hit。

Index consistency

Message create、edit、retract 與 delete operation 會寫入 durable post-commit handoff。Search indexing 發生在 conversation transaction commit 之後。

因此目前的行為是:

  • New 或 edited message 可能不會立即出現。
  • Retracted message 的 indexed body 為 empty,但 attachment filename 仍可能 match。
  • Deleted message 會 asynchronous remove from index。Relay 在回傳 hit 前也會 filter deleted source message,所以 stale index row 不會成為 visible message。
  • Pin 與 unpin 不會改變 search document。

Relay 目前沒有公開 maximum index-lag objective。需要 read-after-write consistency 的 workflow,應透過 conversation API 讀取 message,不要使用 search。

中文搜尋模式

Relay 在 production 會要求使用 zhparser 執行中文斷詞。Postgres extension 不可用時,startup 會 fallback 到 simple_bigram,不會停用 search。

CJK query 使用 fallback path 時,response 會標記:

{
  "degraded": true
}

Degraded path 使用 substring bigram,並把 query 限制在前 20 個 unique bigram。Web-search operator 在這條 fallback path 不保留一般語意。

Degraded pagination 尚未完成:fallback 不會套用 incoming cursor,可能重複第一頁。degradedtrue 時,請把 response 視為單頁 fallback,並忽略 next_cursor

Error 與 limit

Status意義
401JWT 缺少或無效
403Caller 不是 channel member
422q 缺少、為 empty,或超過 200 個 character
429User 或 tenant search budget 已耗盡

目前 default 是每個 user 每分鐘 60 次,每個 tenant 每分鐘 600 次。429 response 的 Retry-After 使用秒數。

Operator backfill

Operator 可以 enqueue tenant search backfill,並查看 state:

POST /api/admin/tenants/{tenant_id}/search/reindex
GET /api/admin/tenants/{tenant_id}/search/reindex
X-Admin-Token: <admin-token>

POST 回傳 202 Accepted。State endpoint 會回報 never_runrunningcompletefailed,以及 indexed-row count 與可用的 failure detail。

雖然 route 名稱是 reindex,目前的 operation 是 resumable backfill,不是 clean rebuild。Completed cursor 不會在後續 run 前 reset,stale index row 不會先清除,而且 backfill document 不包含 attachment filename。Operator 不能以這個 endpoint 證明所有 derived search row 都已精確重建。

Product boundary

Channel membership、tenant-scoped index、ranked body search、attachment filename matching、cursor pagination、asynchronous update、中文 fallback、rate limit 與 operator backfill 已實作。

Safe structured highlight、strict cursor validation、cursor binding、reliable degraded pagination、positive limit validation、index-lag objective、exact rebuild semantics、attachment-aware backfill、advanced filter 與 SDK search helper 仍屬於 product work。Meilisearch adapter 目前只是 skeleton,不能作為 production backend 啟用。

下一步:接收已簽章的 webhook event