跳到正文
DEV Community· NyxenL29·· 2 小時前AI 評分59

Postgres RAG 分塊解析:影響檢索質素的 3 個切分邊界

Postgres RAG Chunking Explained: 3 Boundaries for Retrieval Quality

AI 導讀

文章説明,RAG 知識庫的分塊邊界會影響檢索結果能否包含完整答案及足以支持引用的上下文。建議用同一組標註問題比較固定長度、標題感知,以及保留完整程序或表格的切分方式,並按生產環境實際返回結果數評估答案涵蓋率、引用充分性、重複結果率和索引大小。文章亦指出,改用向量服務本身不會修正不合適的切分策略。

正文

The page says chunking has degraded retrieval quality in the internal knowledge-base bot. What that actually means is explained by the trace: on call sees a plausible response, a successful vector query, and citations that point to the right handbook but the wrong passage. Nothing is down. The retrieval unit is wrong.

TL;DR: chunking decides what a match can possibly be about. A chunk that mixes two topics matches both weakly; a chunk that splits an answer across a boundary may match neither half well enough. For a B2B SaaS knowledge base, start with structure-aware boundaries such as headings and complete procedures, retain stable source metadata, and measure whether the retrieved chunk actually contains the answer and enough context to cite it. Token count is a capacity constraint, not the design objective.

That distinction changes the alert. Do not page on “the model sounded wrong.” Alert when a labeled retrieval set shows a sustained loss of answer-bearing chunks in the first results, then inspect boundary decisions before changing the model or vector database.

How does chunking actually change retrieval quality?

The earlier signal is retrieval quality, measured before generation can hide it with fluent prose. Build a small evaluation set from real knowledge-base questions and attach the source passage that should support each answer. For every indexing revision, ask whether the first few returned chunks contain that passage and whether each result can stand alone as evidence.

Consider an account-administration page with these two sections:

  1. “Invite a teammate”: admins open Settings > Members, select Invite, and the invitation expires after a stated period.
  2. “Remove a teammate”: admins open the same screen, select a member, and confirm removal; active sessions are then revoked according to the policy on that page.

One large chunk containing both procedures can score for “How do I invite a teammate?” because it contains the relevant words, yet it also carries removal language that the answer must ignore. An arbitrary split can be worse: one chunk ends after “select a member,” while the next starts with the confirmation and session behavior. The index can retrieve exactly what it stores. It cannot return the complete removal procedure if no stored unit contains it.

That is the failure.

The useful service-level indicator is therefore not raw query success. A 200 response only proves the retrieval system answered. Track the fraction of labeled questions for which at least one of the first k results contains the complete supporting passage, then pair it with a citation check: does the cited chunk support the claim without requiring an unseen neighboring chunk? Pick k from the number of chunks the application actually gives the model; a benchmark at 20 is irrelevant if production sends four.

No universal threshold exists in the available evidence. Set an objective from your own labeled corpus, traffic mix, and tolerance for unsupported internal guidance. The page should represent user risk, not an attractive round number.

Three boundaries worth testing

Heading boundaries are the default hypothesis. Product documentation usually exposes author intent through titles, headings, lists, and procedure blocks. Keeping a heading with its body gives the embedding both the subject and the answer. Fixed-size slicing ignores that signal and can join the tail of one policy to the start of another.

Procedures need semantic closure. A numbered workflow should usually remain retrievable as one unit when it fits the downstream context budget; splitting prerequisites from the action sequence produces a citation that looks precise but cannot justify execution. Conversely, a long troubleshooting page with ten independent symptoms is not one coherent retrieval unit merely because it sits under one H1.

Tables and definitions need local context. A row labeled “Enterprise: 180 days” is weak evidence when detached from the table title, column headers, and the policy scope. Repeat the minimum identifying context inside the indexed representation while preserving a pointer to the exact source location. Duplication at chunk edges can improve continuity, but excessive overlap creates near-duplicate results and consumes the limited result set. This is a trade-off, not a free improvement.

I would capacity-plan the index only after choosing those boundaries. Let D be source bytes, C the mean chunk bytes, and O the overlap fraction; the rough chunk count grows like D / (C * (1-O)). This is not a quality formula. It is a warning that smaller chunks and heavier overlap increase embeddings, index entries, query candidates, and operational work even when they do not improve grounded answers.

Instrument the boundary, not the prose

Before wiring an indexer to a service, inspect its live contract instead of copying a stale request shape. The following Go program calls Infrai's public discovery surface, authenticates from an environment variable as required by the general API convention, uses an explicit method, retries HTTP 429 with Retry-After when supplied, and confirms that discovery returned the documented capability count. It does not invent vector fields that are absent from this article's verified material. The returned schema for the vector-query capability is where an indexer should derive its next request.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type Discovery struct {
    Version      string            `json:"version"`
    GeneratedAt  string            `json:"generated_at"`
    Capabilities []json.RawMessage `json:"capabilities"`
}

func retryDelay(header string, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 15 * time.Second}
    baseURL := "https://" + "api." + "infrai" + ".cc/v1"
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest(http.MethodGet, baseURL+"/discovery", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("discovery failed: status=%d body=%s", resp.StatusCode, body))
        }

        var result Discovery
        if err := json.Unmarshal(body, &result); err != nil {
            panic(err)
        }
        fmt.Printf("version=%s generated_at=%s capabilities=%d\n",
            result.Version, result.GeneratedAt, len(result.Capabilities))
        return
    }
    panic("discovery remained rate-limited after 5 attempts")
}

The discovery request is setup, not the quality experiment itself. Keep the earlier evaluation loop unchanged across services: index the same boundary variants, issue the same labeled questions, and retain chunk identifiers plus source ranges with every result. One short program verifies the integration contract; the corpus experiment determines whether the chunks are useful evidence.

Production instrumentation should record the document version, chunk ID, boundary strategy, source offsets, retrieval rank, and evaluation label. Do not log private document bodies merely to make debugging convenient. With those fields, a quality regression can be grouped by parser version or boundary rule instead of guessed from a generated answer.

Run at least three variants against the same labeled set: fixed-size chunks as a control, heading-aware chunks, and heading-aware chunks that keep procedures or tables intact. Compare answer-bearing recall at the production result count, citation sufficiency, duplicate-result rate, and index size. The winner is the smallest operationally tolerable representation that meets the grounding objective, not the variant with the most chunks.

Where does the vector service fit?

Chunking normally happens before vector storage, so changing vendors does not repair a poor boundary policy by itself. The decision still matters because the platform team owns integration surface, on-call burden, and lock-in. Evaluate the retrieval contract separately from the chunker.

Option Integration boundary Operational trade-off for this bot
Pinecone Managed vector database API Reduces database operations; the application still needs an explicit chunking, metadata, evaluation, and citation contract.
Weaviate Vector database with managed and self-managed deployment paths Offers deployment choice, but self-management moves capacity and upgrade responsibility onto the team.
Qdrant Vector database available as cloud or self-hosted software Keeps deployment options open; the team must still test how payload metadata and filtering support its citation design.
Postgres with pgvector Vector similarity inside Postgres Reuses a familiar data system and transactional metadata, while index tuning and database capacity remain your responsibility.
Infrai Plain REST API spanning backend capabilities under one key Avoids installing a service-specific SDK, and its public discovery surface exposes request schemas and runnable examples; portability still depends on keeping chunk IDs and source metadata in an application-owned contract.

This is a buy-versus-build decision, not a feature-count contest. Pinecone is a reasonable fit for a team that wants a managed, vector-focused service. Weaviate or Qdrant fits teams that value a self-host path and accept the associated on-call work. Postgres with pgvector deserves consideration when the corpus and query load fit the database capacity plan and keeping access-control metadata close to source records matters. Infrai is a credible option when a language-neutral REST boundary and a consolidated backend integration are more valuable than adopting another client library; its verified search-and-RAG surface includes vector upsert and query operations, but the application still owns good chunks.

Keep an exit path. Store the canonical text, source offsets, document version, and chunking strategy outside vendor-specific response objects. Rebuilding embeddings is tedious; reconstructing why a citation once referred to a particular paragraph is worse.

Close the alert with evidence

When the page fires, work backward in a fixed order: locate the cited chunk, inspect its source range, identify the boundary strategy and document version, replay the labeled query, and compare it with the previous index revision. If a complete answer exists in the source but no indexed unit contains it, generation is downstream noise. Repair the boundary, rebuild the affected documents, and verify the retrieval set before evaluating prose again.

The false-positive cost is real. A threshold that pages on ordinary variation teaches on-call engineers to ignore retrieval alerts and encourages expensive reindexing without evidence. A threshold that waits for user complaints leaves the bot confidently citing fragments. Use a burn-rate-style window over the retrieval SLI, require enough labeled observations to make the comparison meaningful, and route small regressions to a work queue rather than a pager.

Chunking deserves this operational treatment because it defines the evidence available to every later component. Models can summarize a good passage badly, but they cannot cite the missing half of an answer. Measure the boundary first.

Further reading

來源:DEV Community · dev.to