From RAG Prototype to Deployment Decision: A CFPB Case Study從 RAG 原型到部署決策:CFPB 案例研究
A practical guide to designing and evaluating RAG through a LangChain complaint-triage case study—and deciding why the prototype is suitable for a controlled analyst-assistance pilot, but not autonomous deployment.本文透過一個以 LangChain 建構的申訴分流案例,說明如何設計與評估 RAG 系統,以及為何這項原型適合進入受控的分析師輔助試點,卻尚不足以支援自主部署。

Couple years ago, if you ask an AI chatbot a question, you may encounter the situation like below:
User: "What is our Q3 revenue?"
LLM: "I don't have access to that information." ← or hallucinates
Although large language models (LLMs) are powerful reasoning engines, they suffer from three fundamental limitations:
- their knowledge is frozen at a past training cutoff,
- they lack access to private enterprise data, and
- they hallucinate plausible-sounding falsehoods when they encounter a knowledge gap.
Retrieval-augmented generation (RAG) directly addresses these flaws. The core insight of RAG is that instead of baking all knowledge into static, expensive model weights, the system should retrieve current, relevant facts at request time and inject them directly into the model's context. A query that once yielded a hallucination now produces a grounded, verifiable answer:
User query → retrieve Q3 financial report chunks → inject into prompt
LLM: "According to the Q3 report, revenue was $42M, up 18% YoY."
However, missing knowledge is only one reason an application might fall short. Ambiguous instructions might just need a clearer prompt, while inconsistent model behavior might require fine-tuning. Choosing among prompting, fine-tuning, and RAG must therefore begin with the business problem, not the architecture.
This article breaks down that decision from first principles and tests it through a case study using the US Consumer Financial Protection Bureau (CFPB) complaint database. The proposed application classifies and summarizes consumer complaints while providing supporting evidence. The goal is not simply to prove that a RAG pipeline can be built, but to determine whether it improves complaint triage enough to justify its cost and risk. The article is structured as:
- Part 1: Prompting, Fine-Tuning, and RAG
- Part 2: How a RAG System Works
- Part 3: RAG Evaluating the RAG Application
- Part 4: CFPB Complaint Data Case Study
Part 1: Prompting, Fine-Tuning, and RAG
All three approaches can improve an LLM application, but they manipulate completely different levers:
- Prompting changes the instruction. It clarifies the task, supplies a small amount of context (e.g. few-shot learning), and defines the expected response.
- Fine-tuning changes model behavior. It updates underlying parameters so that the model reproduces patterns, tones, or structures demonstrated in training examples.
- RAG changes the evidence available at inference time. It retrieves relevant records from an external collection and supplies them to the model for the current request.
This distinction is far more useful than treating them as three competing features.
| Question | Prompting | RAG | Fine-tuning |
|---|---|---|---|
| Best suited to | Clarifying a task and establishing a baseline | Supplying changing, private, or extensive evidence | Teaching repeated behavior, style, format, or task conventions |
| Knowledge source | Context window | Retrieved documents | Model weights |
| Knowledge scale | ~100K tokens | Millions of documents | Unlimited |
| Explainability | Traceable | Citable | Black box |
| Setup cost | Minimal | Medium: ingestion, indexing, retrieval, evaluation | High: data design, training, evaluation, model lifecycle |
| Per-query cost | High | Medium | Low |
| Common failure | Ambiguous instruction or brittle examples | Relevant evidence is missed, diluted, or misused | The model learns noise, memorizes examples, or fails to generalize |
| Example scenario | Formatting text into structured JSON. | Answering questions from a weekly-changing policy library. | Tone and style alignment for customer support chatbot. |
A useful decision sequence is therefore:
Can a clearer prompt solve the problem?
├─ yes → keep the prompt and measure it
└─ no
↓
Is the missing capability knowledge or evidence?
├─ yes → test retrieval (RAG)
└─ no
↓
Is the required behavior stable, repeated, and represented by good examples?
├─ yes → evaluate fine-tuning
└─ no → revisit the task, workflow, or model choice
This is not a forced choice. A production system can use a carefully designed prompt, retrieve current evidence, and run a fine-tuned model that has learned a stable output contract. The components remain valuable for different reasons. The companion article on LLM fine-tuning examines the training side in detail.
Part 2: How a RAG System Works
A production RAG application is not a single script; it is a system with two distinct data paths. The offline pipeline prepares the searchable knowledge base, while the online pipeline handles the live inference request.

The Offline Pipeline: Building the Knowledge Base
1. Loading and Chucking: Define the unit of meaning
The offline path prepares the data. This pipeline must be deterministic: given a source snapshot, it should produce the exact same searchable records every time.
A useful record contains:
- stable source and document identifiers;
- the text presented to retrieval;
- metadata used for filtering or access control;
- source timestamps;
- a content hash for deduplication;
- parser, chunker, and embedding-model versions.
Chunking is arguably the most impactful representation decision in a RAG system. If a chunk is too large (like embedding an entire 200-page PDF), the resulting vector averages out too many concepts, and the relevant paragraph gets buried in noise. If a chunk is too small (like a single sentence), it loses surrounding meaning—"The rate was 5%" is useless if the retriever stripped away what the rate actually applies to.
To balance precision and context, engineering teams typically evaluate four chunking strategies:
- Fixed-Size Chunking (The Baseline): Splitting text by a strict token count with a sliding overlap so that sentences spanning a boundary are not lost. Given the precision-versus-context tradeoff above, the sweet spot is typically 256–512 tokens with a 10–20% overlap, avoiding the pitfalls of extreme chunk sizes.
Document: [----512 tokens----][----512 tokens----][----512 tokens----]
[64 tokens] [64 tokens]
overlap overlap
-
Document-Aware Chunking: Respecting the natural hierarchy of the file. This means chunking PDFs by section headers, code by functions or classes, HTML by
<section>tags, and keeping tabular data structurally intact. Arbitrary token splits can slice a table mid-row, completely breaking the data's relationship. -
Semantic Chunking: Splitting text dynamically at the exact point where the embedding similarity between sentences drops. While more computationally expensive during ingestion, this ensures chunks align with actual topic boundaries rather than arbitrary token counts.
-
Parent-Child Chunking (Small-to-Big): A pattern that decouples the retrieval unit from the generation unit. The system embeds small "child" chunks (e.g., 128 tokens) for hyper-precise vector matching, but when a match is found, it returns the larger "parent" chunk (e.g., 1024 tokens) to the LLM to provide rich context.
Indexing:
Parent chunk (1024 tokens) → store as context
↓ split into
Child chunks (128 tokens each) → embed and index for retrieval
Query time:
1. Retrieve top-k child chunks (precise embedding match)
2. Look up their parent chunks
3. Send parent chunks (full context) to LLM
2. Embedding and Vector Store: Build a Reproducible Corpus
Once chunks are defined, they are passed through an embedding model and loaded into a vector store.
Query: "What is machine learning?" → [0.23, -0.41, ..., 0.12]
"Explain ML to me" → [0.25, -0.39, ..., 0.11] ← similar vector
"Paris is in France"。 → [-0.91, 0.34, ..., 0.55] ← different vector
The most critical rule of RAG is that the exact same embedding model must be used at both indexing and query time. Embedding spaces are model-specific; you cannot compare an OpenAI vector to a Cohere vector. Therefore, changing your model later requires rebuilding the entire index.
Choosing the Embedding Model
When choosing a model for production, match it to your constraints:
- Managed & High Quality: OpenAI
text-embedding-3-large(3072 dimensions) is the industry default for quality, whiletext-embedding-3-smallis optimized for latency and cost. - Self-Hosted & Private: If data cannot leave your VPC, use strong open-source bi-encoders like
BGE-large-enorE5-large-v2. - Multilingual: Cohere
embed-v3ormultilingual-e5are purpose-built for cross-language retrieval. - Long Documents: Models like
nomic-embed-textsupport massive 8,192-token context windows for specialized use cases.
A vector store is a specialized database designed to house these high-dimensional arrays alongside their original text chunks and metadata. Unlike traditional relational databases that retrieve records via exact keyword matches (SQL), vector stores retrieve information by mathematical proximity—finding the stored vectors that are closest in distance to the user's query in semantic space.
Calculating exact cosine similarity across millions of 1536-dimensional vectors per query is too slow for production. Vector databases solve this using Approximate Nearest Neighbor (ANN) algorithms—most commonly HNSW (Hierarchical Navigable Small World).
Choosing the Vector Store
The right vector store depends on your infrastructure strategy:
| Infrastructure Strategy | Recommended Tools | Why it fits |
|---|---|---|
| Fully Managed Cloud | Pinecone | Zero operational overhead; built for pure production scale. |
| Existing Stack | pgvector (PostgreSQL) | "Keeps vector data alongside relational data, avoiding a new infrastructure dependency. |
| High-Performance OSS | Qdrant, Milvus, Weaviate | Self-hosted or managed; robust filtering, Rust-based speed (Qdrant), or massive scale (Milvus). |
| Prototyping & Dev | Chroma, FAISS | SQLite-backed or purely in-memory. Great for local research but less suited for enterprise deployment. |
Storing the vector and the text is not enough. A searchable index is a liability if you cannot debug it. Every record requires metadata (source IDs, timestamps, content hashes, and chunker versions) that makes targeted deletion and incident response possible. Furthermore, the embedding contract is strict: if you upgrade your embedding model, you must rebuild the entire index, because vectors from different models cannot be compared.
The Online Pipeline: Serving the Request
A RAG response is only as good as the evidence retrieved, the context assembled, and the model's ability to use that context. Each layer requires its own test.
While the offline pipeline is measured in indexing throughput and reproducibility, the online pipeline is measured in end-to-end latency (typically 100–500 ms), retrieval precision, and factual grounding.
3. Query Embedding & Retrieval
First-stage retrieval must evaluate millions of candidate passages within tens of milliseconds. Production architectures achieve this by pairing lexical matching with vector approximations, ultimately combining them into a hybrid system.
Sparse Retrieval: BM25 (Lexical Exact Matching)
Sparse retrieval ranks documents using pure lexical evidence. It is exceptionally strong when exact language matters, such as searching for a specific product name, an error code, or a distinctive phrase.
The most common algorithm, BM25, evaluates term frequency and rarity. In simple terms:
- The score increases when the query term appears frequently in a document (high term frequency) or when the term is rare across the entire corpus (high inverse document frequency).
- The score decreases when the document is excessively long, or if the query term is ubiquitous across all documents (e.g., "the", "is", "and").
BM25 details
BM25 is a term-matching algorithm that scores documents without requiring deep learning or vector representations.
Where:
- : Term frequency of query token in document .
- : Inverse document frequency. Discriminates between rare, informative terms (high weight) and ubiquitous words like "the" or "is" (penalized).
- : Normalizes document length against the average corpus length () using hyperparameter (typically ), preventing excessively long documents from dominating results.
- : Calibrates term-frequency saturation (typically ).
Dense Retrieval: Embeddings & Approximate Nearest Neighbors (ANN)

Dense retrieval translates text into continuous geometric vectors. This is achieved using a bi-encoder embedding method, which encodes the query and the candidate document independently into a shared high-dimensional vector space. Because the document vectors are precomputed offline and saved in the vector store, only the user's query needs to be embedded at runtime.
Bi-encoder embedding
A bi-encoder embedding method encodes query and candidate document separately independently into a shared high-dimensional vector space.
- Mechanism:
- Why it scales: Document embeddings () are computed once during offline indexing. At request time, the system performs a single forward pass to embed the user query () and executes sub-millisecond similarity lookups across millions of vectors.
- Limitation: Because the document and query are encoded in isolation, the model cannot capture fine-grained token-level cross-interactions.
Once the query is embedded, the system must search the vector store. Computing exact cosine similarity across millions of high-dimensional vectors at request time is computationally prohibitive ( per query). Production vector databases solve this using Approximate Nearest Neighbor (ANN) indexing algorithms like HNSW (Hierarchical Navigable Small World):
- Build Phase: Constructs a multi-layer graph where upper layers contain sparse, long-range connections (coarse search) and bottom layers contain dense, localized clusters (fine search).
- Search Phase: Traverses the graph from the top down to rapidly home in on candidate neighborhoods, trading a tiny fraction of recall for logarithmic search speed ().
Dense vs. Sparse: The Fundamental Tradeoff
| Dimension | Sparse Retrieval (BM25) | Dense Retrieval (Vector Search) |
|---|---|---|
| Matching Paradigm | Exact keyword / lexical matching | Latent semantic matching ("car" = "automobile" = "vehicle") |
| Core Strength | Handles rare/specific terms: precise strings, error codes, SKUs, model names | Captures meaning, paraphrase, intent |
| Failure Mode | Vocabulary mismatch ("automobile" != "car"), no semantic understanding ("joyful" != "happy") | Struggles with exact rare terms (e.g. alphanumeric identifiers "SK-1234") |
| Compute Overhead | Fast, CPU-only, minimal memory footprint | GPU acceleration preferred; memory-intensive vector index |
| Interpretability | Can explain why a doc scored high | High-dimensional geometric representation |
Neither method dominates every query. Hybrid retrieval executes both BM25 and dense retrieval in parallel, leveraging the exact-match precision of sparse search and the semantic reach of dense search, and then mathematically merges their ranked lists.
4. Re-Ranking & Fusion
Information retrieval in modern RAG systems hinges on a fundamental engineering tradeoff between computational efficiency and semantic expressiveness. Managing this tradeoff requires pairing two distinct neural architectures into a two-stage funnel:
| Stage | Model Architecture | Scope | Primary Objective | Latency Target |
|---|---|---|---|---|
| Stage 1: Retrieval | Bi-Encoder + ANN | Entire corpus (105–107 chunks) | High Recall (cast a wide net) | ∼10–20 ms |
| Stage 2: Re-ranking | Cross-Encoder | Top-K candidates (20–50 chunks) | High Precision (rank exact matches) | ∼50–100 ms |
A bi-encoder searches an entire corpus efficiently because document vectors are precomputed offline. However, it evaluates queries and documents in isolation.
BI-ENCODER (First-Stage Retrieval)
Online Query → [ Encoder ] → Vector (q) ──┐
├→ Cosine Similarity → Rank
Offline Document → [ Encoder ] → Vector (d) ──┘
A cross-encoder processes the query and document jointly through full self-attention layers. This allows every query token to interact directly with every document token.
CROSS-ENCODER (Second-Stage Re-ranking)
[ Query + Document ] → [ Joint Encoder with Cross-Attention ] → Relevance Score
Running a cross-encoder over 1,000,000 documents is computationally impossible (1M forward passes per query ), but running it over the top 50 candidates takes .
Cross-encoder details
A cross-encoder processes the query and document jointly through full self-attention layers ().
- Mechanism:
- Why it is precise: Every token in the query attends directly to every token in the candidate document through full self-attention layers. This joint representation captures rich nuances, negations, and complex lexical interactions that bi-encoders miss.
- Limitation: Running full self-attention across a million query-document pairs per request is computationally prohibitive ().
Fusing Hybrid Results
When combining sparse (BM25) and dense (vector) retrieval, systems employ one of two strategies to merge their candidate lists:
- Reciprocal Rank Fusion (RRF): RRF merges ranked lists without requiring score normalization. It uses relative rank positions rather than raw scores:
Where is the set of retrievers and is a smoothing constant (typically ) that dampens the disproportionate advantage of top-ranked items. RRF rewards documents that perform consistently well across both retrieval paradigms.
Example ():
- Document A: Rank 1 in BM25, Rank 5 in Dense
- Document B: Rank 3 in BM25, Rank 1 in Dense
Document B wins because it maintained a high rank across both retrievers.
- Linear Score Fusion: Alternatively, raw scores can be normalized to via min-max scaling and combined using a tuned weight factor :
Note: Linear fusion requires careful calibration because BM25 scores are unbounded, while cosine similarities are bounded between -1 and 1.
Production Reranking Options
- Open-Source Cross-Encoders: Self-hosted models like
cross-encoder/ms-marco-MiniLM-L-6-v2orbge-reranker-largeprovide high accuracy with direct infrastructure control.
from sentence_transformers import CrossEncoder
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
# Score each candidate against the query
pairs = [(query, doc) for doc in candidates]
- Managed APIs: Specialized endpoints like Cohere Rerank (
rerank-v3.5) handle tokenization and scoring via managed APIs.
import cohere
co = cohere.Client("api-key")
results = co.rerank(
query=query,
documents=candidates,
model="rerank-english-v3.0",
top_n=5
)
- LLM-Based Reranking: Prompting a frontier LLM to score relevance on a 1–10 scale yields high nuance, but is typically reserved for small candidate sets () due to latency and token cost.
Prompt: "On a scale of 1-10, how relevant is this passage to the query?
Query: {query}
Passage: {passage}
Score:"
Run for each candidate, sort by score.
Only feasible for small candidate sets (<20) due to cost.
5. Context Assembly & Generation
Retrieving the right passages is only half the battle; those records must be assembled into the model's input window and governed by strict behavioral constraints.
The Information Budget
Context assembly is fundamentally an information-budget problem. The system must decide:
- How many records to include within token limits.
- Which metadata fields the generator is allowed to inspect.
- How passages are formatted, separated, and deduplicated.
- Which text represents trusted instructions versus untrusted user data.
More context is not necessarily safer. Extraneous noise distracts the model, redundant passages inflate confidence artificially, and crucial facts can easily be buried.
Lost in the Middle
LLMs attend better to information at the beginning and end of the context window. Information in the middle is often missed (Liu et al., 2024):
PROMPT ATTENTION CURVE ("Lost in the Middle")
[ System Instructions & Schema ] ← High Attention / Primacy Effect
[ Chunk #1 (Highest Relevance) ] ← Strong Attention
[ Chunk #2 (Moderate Relevance) ] ← Weakest Attention ("Lost in the Middle")
[ Chunk #3 (Moderate Relevance) ] ← Weakest Attention ("Lost in the Middle")
[ Chunk #4 (High Relevance) ] ← Strong Attention
[ User Query & Citation Rule ] ← High Attention / Recency Effect
To counter the "Lost in the Middle" phenomenon:
- Truncate aggressively: Keep only the top 3–5 candidate chunks rather than filling the entire context window.
- Re-order strategically: Place the best evidence at the very beginning and very end of the context block.
Grounding with Citations
Formatting retrieved chunks with clear identifiers allows the model to attribute claims directly to source passages.
Generation Contracts
An instruction like "use only the supplied sources" is a soft suggestion, not a strict safeguard. Production generation requires mechanical controls:
- Typed Output Schema: Enforce structured outputs (e.g., JSON Schema or Pydantic) to prevent downstream parsing errors.
- Deterministic Decoding: Set the temperature to 0.0 for reproducible inference and reliable evaluation.
- Data Separation: Maintain a strict boundary between evidence and references.
- Evidence: Verifiable, exact quotes extracted directly from the user's current submission.
- Retrieved References: Historical records or precedents retrieved to explain classification categories.
- Validation & Fallback: Programmatically validate citations after generation. If retrieved evidence scores fall below a minimum threshold, route the request to a human-in-the-loop triage queue or an abstention path rather than risking a hallucinated answer.
6. Advanced RAG Patterns: Dynamic and Agentic Workflows
The linear pipeline—embed, retrieve, insert, generate—is sufficient for basic document lookup. However, enterprise systems often require more dynamic intervention to handle ambiguous user behavior and complex analytical tasks. When standard RAG fails, engineering teams introduce advanced routing, compression, and agentic patterns.
-
Query Routing & Expansion (Pre-Retrieval): Users rarely write perfectly optimized semantic queries. Rather than passing a raw, ambiguous prompt (e.g., "error connecting") directly to the vector database, an LLM intercepts it first. an LLM first intercepts it.
- Routing classifies the intent and directs it to the appropriate system (e.g., routing a statistical query to a SQL database versus routing a policy question to the vector index).
- Expansion rewrites the query into an optimized format ("database connection error troubleshooting steps") or generates multiple variations to maximize recall. A specialized variant called HyDE (Hypothetical Document Embeddings) prompts the LLM to write a fake, hypothetical answer and embeds that text, which often matches the structural style of the target documents better than the original question.
-
Contextual Compression (Post-Retrieval): A standard 512-token chunk might contain only one sentence relevant to the user's prompt. Passing the entire chunk wastes the model's context budget. Contextual compression uses a fast, lightweight model to filter out irrelevant sentences from the retrieved chunks before they reach the final generator, maximizing the information density of the prompt.
-
Agentic and Self-RAG (Execution): Linear pipelines fail on complex comparative questions (e.g., "Compare the revenue growth of our top 3 products in Q3 vs Q2").
- Agentic RAG turns the LLM into an orchestrator that executes multiple iterative searches—first retrieving Q3 data, then Q2 data, then cross-referencing—before generating a final response.
- Self-RAG gives the model the autonomy to decide if it even needs to retrieve information, evaluate whether the fetched chunks are actually relevant, and iteratively regenerate its answer if it detects that its claims lack grounding in the provided text.
Part 3: RAG Evaluating the RAG Application
An end-to-end score can tell us that a system became worse. It rarely tells us why. RAG evaluation should preserve the boundaries between retrieval, generation, operations, maintenance, and business outcomes.
1. Retrieval Evaluation: Did the system surface useful evidence?
A retrieval test set consists of historical queries mapped to human-verified relevance judgments. Depending on the task, relevance may be binary (relevant/not relevant), graded, or tied to a known source document. Common metrics include:
- Hit Rate@k: A binary measure of whether at least one relevant document appears in the top results.
- Recall@k: Of all the relevant documents that exist in the corpus, what fraction successfully appeared in the top results?
- Precision@k: Of the top documents retrieved, what fraction is actually relevant?
- Mean Reciprocal Rank (MRR): Evaluates how early the first relevant result appears in the ranked list.
- nDCG (Normalized Discounted Cumulative Gain): Measures overall ranking quality when relevance has multiple weighted grades (e.g., "perfect match" vs. "partial match").
Retrieval should also be sliced by rare labels, complaint length, time period, and lexical difficulty. An average dominated by common checking-account cases can conceal a retriever that fails on small categories.
2. Generation Evaluation: Did the model use the context correctly?
Retrieving the right document means nothing if the LLM hallucinates or ignores it. Generation evaluation has several independent dimensions:
- Task correctness: Are the predicted product and issue labels accurate? (Best measured using exact match and Macro-F1, which gives equal weight to all labels, preventing common categories from overwhelming the score).
- Schema validity: Can the JSON/structured response be parsed and consumed safely by downstream APIs?
- Faithfulness: Are all generated claims explicitly supported by the retrieved context?
- Evidence validity: Are the evidence quotations exact, verbatim spans of the submitted complaint? (This can be evaluated via deterministic string-matching).
- Summary coverage: Does the generated summary preserve the material facts of the complaint without inventing new details?
- Abstention quality: Does the system gracefully defer to a human when the retrieved evidence is insufficient?
Product and issue prediction will use exact match plus macro-F1. Macro-F1 gives each label equal weight and therefore prevents common categories from overwhelming the score. Evidence validity can begin with deterministic checks: every quote must occur in the input. Human review or a separately evaluated judge is still needed to determine whether the quote truly supports the claim.
Frameworks like RAGAS formalize dimensions such as faithfulness and context relevance using "LLM-as-a-judge" techniques. However, reference-free automated judgments are directional estimates, not ground truth. They are highly useful for rapid iteration only when calibrated against human review Es et al., 2024.
3. Operational Evaluation: Can the system meet its service objective?
Algorithmic quality is necessary but insufficient. A production service must be evaluated on its infrastructure realities:
- Latency: p50 and p95 response times separated strictly by stage (retrieval, reranking, and generation).
- Throughput: Requests per second under expected concurrent loads.
- Cost: Token counts (input, context budget, and output) translated into cost per accepted response.
- Reliability: Timeout rates, schema parsing failures, and dependency-error rates.
Stage-level measurement matters. If generation consumes 95% of response time, optimizing vector search will not materially improve the user experience.
4. Maintenance Evaluation: Can quality survive change?
A RAG system is a living database. The maintenance scorecard must monitor the health of the underlying data pipeline:
- Data Health: Ingestion delays, failed parsing records, duplicate rates, and source freshness.
- Semantic Drift: Shifts in user query distributions, introduction of new taxonomy values, or retirement of old product labels.
- Operational Agility: The exact time and compute cost required to rebuild, validate, and roll back an index.
Index versions should be tested with the same rigor as model weights. A candidate index must pass the golden retrieval set before promotion, and the previous version must remain instantly recoverable.
5. Business Evaluation: Did the intervention improve the workflow?
Ultimately, the decisive measures live entirely outside the model's architecture. Success is defined by:
- Efficiency: Average analyst handling time and backlog age.
- Quality: Inter-reviewer agreement and the severity/expected cost of incorrect automated routing.
- Adoption: Analyst acceptance rates, correction rates, and rework frequency.
- ROI: The fully loaded cost per completed complaint triage.
The evaluation ladder should progress sequentially: from offline static tests, to shadow operation (running silently alongside humans), to a limited analyst-assistance pilot. Full automation is a separate business decision with a significantly higher evidence threshold.
Part 4: CFPB Complaint Data Case Study
1. Business problem
The CFPB Consumer Complaint Database publishes information about complaints concerning consumer financial products and services. When a consumer opts to publish their narrative, the CFPB takes steps to remove personal information before publication. The Bureau also warns that the database is not a statistical sample and should not be treated as representative of every consumer's experience. The publication process is described in the CFPB's guidance on how complaint data is shared.
The proposed application addresses a narrow workflow:
Input: an unstructured consumer complaint narrative
Output: suggested product
suggested issue
concise summary
exact supporting quotations
retrieved precedent complaints
confidence and abstention status
The application could support several internal users. Quality and operations analysts could use it to classify, summarize, and review individual complaints; risk teams could use aggregated patterns to identify emerging control issues; and communications or public-relations teams could monitor recurring themes that may require a response. The system remains a decision-support tool: it does not determine whether a complaint is valid, decide consumer relief, rank companies, or provide legal advice. For individual cases, the original narrative and supporting evidence remain visible for human review.
The current-workflow hypothesis
Manual triage creates systemic inefficiency. Without an assistance tool, an analyst must read the narrative, navigate a product-and-issue taxonomy, write a summary, and preserve enough rationale for review. Long or ambiguous complaints take more time. Similar cases can be classified differently, creating rerouting and downstream rework.

The RAG value hypothesis
The RAG value hypothesis is that dynamically retrieved, historically labelled complaints provide more useful precedents than a fixed few-shot prompt. If the system also extracts traceable evidence, an analyst can review the suggestion instead of starting from a blank form.

That hypothesis implies measurable outcomes:
- Lower handling time without a higher correction rate;
- Greater agreement on product and issue labels;
- Fewer rerouted complaints;
- Useful summaries and evidence that analysts accept;
- Acceptable latency and cost at the expected volume.
It also establishes a strong baseline requirement. Classification is a supervised-learning problem with existing labels, so RAG must be compared with a simpler classifier rather than only with a weaker prompt. If a classifier routes complaints more accurately and cheaply, retrieval must justify itself through evidence, precedent discovery, adaptability, or an improved workflow.
2. Data Analysis and Offline Pipeline
Our dataset contains 98,185 complaint records received between January 2025 and June 2026. However, just 36.7% (36,063 records) contain a published consumer narrative. The service can learn and be evaluated only on that narrative-bearing subset.
This non-random missingness creates substantial selection bias. Narratives are published only when consumers consent, and recent complaints can appear before the de-identification "scrubbing" process is complete. A model evaluated strictly on published narratives is not validated for the entire intake stream entering the CFPB workflow.
| Data property | Observed in the local snapshot | Design consequence |
|---|---|---|
| Total complaints | 98,185 | Useful for describing the source, not all usable for narrative modelling |
| Published narratives | 36,063 | Defines the modelling population |
| Products | 11 | Product becomes the first level of a hierarchical prediction |
| Issues | 82 | Accuracy alone will overstate performance on the long tail |
| Product–issue pairs | 118 | Issue predictions must be valid for the selected product |
| Median narrative length | 178 words | One complaint normally fits one retrieval record |
| 99th-percentile length | 1,099 words | A small long-document policy is still required |
| Maximum length | 5,205 words | Blind truncation would discard material text |
Deduplication Prevents Memory-Leakage
Standard text normalization (whitespace, Unicode, case folding) reveals 1,068 repeated narrative rows within the published subset. If a record enters the retrieval index (train) and its duplicate enters the test set, nearest-neighbor retrieval performance is artificially inflated: the system retrieves the ground-truth answer from effectively identical text rather than generalizing from similar examples.
SHA-256 hashing for deduplication
The implementation deduplicates by grouping records using a canonical SHA-256 narrative hash, retaining only the chronologically earliest occurrence.
import hashlib
def narrative_hash(text: str) -> str:
"""Computes SHA-256 hash of normalized text for exact deduplication."""
canonical = normalize_narrative(text).casefold()
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
After removing duplicates, the dataset is split chronologically. This is stricter than a random shuffle split and simulates the real-world operational requirement: can an index of past complaints help classify new complaints arriving tomorrow?
| Data Split | Date Range (First Seen) | Records | Operational Purpose |
|---|---|---|---|
| Training/Index | Before 1 March 2026 | 28,174 | Retrieval corpus and supervised classifier baseline. |
| Development | 1 March–30 April 2026 | 3,966 | Tune retrieval strategy, re-ranker, and abstention thresholds. |
| Test (Golden Set) | From 1 May 2026 | 2,855 | Frozen final comparison set. |
Imbalanced Label Distribution (The Long Tail)
Product labels are concentrated: the three largest categories account for 74.5% of the training index. The largest product category, "Checking or savings accounts," contributes 36.0% of the training index. Of the 77 unique issue labels observed in training, 37 have fewer than 50 examples, and 19 have fewer than ten.
The evaluation period (Test set) included five complaints whose product–issue pairs never appeared in the historical index; four of their issue labels were entirely new to the taxonomy. These "cold start" examples demonstrate that continuous index updates are not merely a freshness optimization: they are how the application acquires new operational categories.
This severe imbalance has three evaluation and design consequences:
- Macro-F1 is critical. Unlike overall accuracy, Macro-F1 gives rare labels equal weight, preventing dominant categories from masking failure on niche labels.
- Abstention is required. The model must be capable of recognizing when its taxonomy is incomplete or lacks historical precedents for a specific complaint, deferring to a human.
- Index refresh is dynamic. Index updates are functional requirements for acquiring new categories, not just optimization steps.
Labels are Measurable Targets, Not Unquestionable Truth
Product and issue fields make this dataset valuable for evaluation, but they should be understood as operational, non-objective labels. A single narrative may detail multiple events, fit several plausible issue codes, or omit critical details available elsewhere in the investigative process. The evaluation benchmark measures agreement with the published label, not a final, objective determination of the consumer's experience.
Because the ground truth is imperfect, the proposed human-in-the-loop workflow keeps the original narrative visible, emphasizes traceable evidence quotations, and presents the classification as a suggestion rather than a final verdict. Human review is a permanent requirement, not a temporary inconvenience.
Indexing Strategy: Parent-Child Representation
Each complaint remains the definitive parent record, holding the authoritative metadata (product, issue, complaint date, and complaint ID).
In the LangChain implementation, this becomes a parent Document whose metadata carries these operational and audit trail fields.
Because some narratives exceed standard embedding context windows, the dense retrieval pipeline employs a "small-to-big" design using Parent/Child relationships:
- Child Splitting: Long narratives are segmented into overlapping child chunks of at most 1,200 words (with a 100-word overlap).
- Passage Indexing: The 28,174 training complaints were processed into 28,444 child passages. Only 215 complaints required splitting into more than one passage. Chroma vector stores index these high-density child passages, maximizing semantic matching precision without naive truncation.
- Parent Retrieval: At retrieval time, the system performs a search across child passages, and the results are programmatically deduplicated back to their parent complaint IDs before the results fusion and reranking stages.
In contrast, BM25 indexes the complete parent narrative text, leveraging its strength with specific keywords across long contexts.
This data architecture enables a relevant passage inside a 5,000-word complaint to match a user query directly (maximizing retrieval precision) while keeping the labels, citations, and complete audit trail anchored strictly at the complaint level for generation and human review.
3. Building the application
The proposed implementation is intentionally lightweight, reproducible, and inspectable. It utilizes:
- LangChain as the core orchestration framework for standardizing Document, Retriever, Prompt, and structured-generation interfaces;
- Chroma as the persistent vector store for dense retrieval;
rank-bm25for sparse, lexical retrieval;- Ollama to serve local quantization of embedding and generator models.
By utilizing local hosting via Ollama, all raw narratives, dense vector embeddings, and generated triage outputs remain entirely on the physical machine running the experiment, eliminating regulatory concerns regarding public cloud API usage.
OFFLINE PIPELINE
CFPB CSV → clean → hash/deduplicate → chronological split
↓
parent Documents
├─ full narrative → BM25 retriever
└─ child windows → Ollama embeddings → Chroma
ONLINE PIPELINE
Narrative → BM25 parent top-50 ───────────────┐
Narrative → Chroma child search → parents ──────┴─ weighted RRF
↓
top-5 precedents
↓
typed generation + validation
↓
response or safe fallback
Hybrid Retrieval, Unified Identity
The sparse retrieval path subclasses LangChain's BaseRetriever and executes rank-bm25 directly over the complete parent narratives.
Its custom tokenizer removes common function words (stop words), standalone numbers, and standard CFPB redaction placeholders (e.g. XXXX) before scoring.
Filtering this noise ensures the lexical retriever focuses purely on unique, discriminative terms.
The dense path utilizes LangChain's OllamaEmbeddings integration, running the locally available nomic-embed-text model.
It indexes the 28,444 child embeddings and their parent metadata in a persistent Chroma collection.
At query time, Chroma returns semantic child passage candidates. The application programmatically aggregates these results up to
their definitive parent IDs, ensuring that only unique parent complaints populate the generator’s context window, even if a single,
long complaint produces multiple high-scoring child fragments.
Both sparse and dense searches return 50 initial candidates. The ranked lists are combined using weighted Reciprocal Rank Fusion (RRF):
This heavy bias toward dense retrieval (0.9 weight) is not a theoretical assumption; it was selected through rigorous testing against the chronological Development split. Naive 50:50 hybrid fusion diluted stronger dense results in early smoke tests. The Development split was used for this final architectural optimization, keeping the Test set completely frozen.
A cross-encoder reranker is excluded. Reranking remains a candidate future experiment; in this design, every component must demonstrate enough incremental accuracy to justify its latency and maintainability costs. It is not considered a mandatory box in a production RAG diagram.
Generator as Judge: Validation and Fallbacks
The hybrid retrieval process supplies dynamic, labelled few-shot examples to the generator. Each precedent carries its historical, human-verified product and issue label.
A deterministic diagnostic baseline is calculated by performing a rank-weighted vote over the labels of the top-five retrieved precedents. This isolates the distinct accuracy contribution of retrieval itself and provides a robust, safe fallback when the LLM’s generative output fails validation.
The generative path utilizes a LangChain ChatPromptTemplate to keep system policy (grounding instructions), the user's submitted narrative, and the historical examples distinctly segmented.
The prompt, populated with the five retrieved precedents, is sent to a local llama3.1 model through ChatOllama.
Structured output is bound via with_structured_output() to a Pydantic schema.
The generation model is required to:
- Extract: A concise summary and one-to-two supporting evidence quotes.
- Analyze: Either choose the most supported product-issue pair from the provided dynamic precedents or abstain if grounding is insufficient.
The strict response contract is defined as follows:
{
"product": "Checking or savings account",
"issue": "Closing an account",
"summary": "The consumer says the bank closed an account without notice.",
"evidence": [
{
"quote": "The bank closed my account without warning.",
"reason": "Directly supports the account-closure classification."
}
],
"retrieved_references": [
{
"complaint_id": "...",
"product": "Checking or savings account",
"issue": "Closing an account",
"score": 0.0159
}
],
"confidence": 0.72,
"abstained": false
}
The generation pipeline enforces immediate programmatic validation upon response. The generated product and issue must form a valid, observed operational pair. Critically, every evidence quote must pass a strict substring check against the original submitted narrative. The retrieved precedent text is treated strictly as untrusted context examples, never as trusted input data or instructions.
If structured generation fails Pydantic parsing, uses an invalid label, or fabrication fails the substring evidence check, the specific reason is recorded, and the system gracefully falls back to the deterministic retrieval-only diagnostic prediction.
Observability and Service Boundary
The application is structured as a FastAPI service exposing three endpoints:
GET /healthzverifies process responsiveness.GET /readyzconfirms a retrieval index is loaded and reports its version.POST /v1/analyzeexecutes the hybrid retrieval and validated structured generation pipeline.
All analytic responses include exhaustive diagnostic metadata: embedding and generator model details, index version, total latency, and a granular breakdown of retrieval and generation latency. For example:
{
"trace": {
"index_version": "20260825T195722Z",
"embedding_model": "nomic-embed-text:latest",
"generation_model": "llama3.1:latest",
"retrieval_method": "langchain_chroma_bm25_weighted_rrf",
"retrieval_ms": 684.3,
"generation_ms": 2511.7,
"total_ms": 3197.4,
"fallback_reason": "generation did not return a valid exact evidence span"
}
}
For a successfully validated generation, fallback_reason is null. Index updates are managed via an atomic promotion pattern:
construction writes a complete staging directory with Chroma data and metadata, promotes it only upon success, and
refuses to overwrite an active index version.
While this case study is designed with "production-shaped" rigor, it is not a production deployment. Real-world activation would require authentication, RBAC, encrypted storage, concurrency load testing, data retention policies, active monitoring, and an approved human-in-the-loop review workflow before processing sensitive complaint data.
4. Evaluation results
The initial evaluation isolates retrieval quality and label prediction accuracy across the entire 2,855-record Test set. All retrieval methods utilize the same frozen corpus, test records, top-five weighted vote logic, and product–issue hierarchical constraints. Quality metrics cover the complete frozen Test set.
Latency analysis is provided as a separate local-machine diagnostic. It is measured on the first 50 test complaints using the actual application pipeline—including query embedding time, parent aggregation, and RRF calculation. This diagnostic measures the current in-process implementation bottlenecks rather than serving as a general benchmark for the underlying Chroma vector store.
| System | Product Accuracy | Product Macro-F1 | Issue Accuracy | Issue Macro-F1 |
|---|---|---|---|---|
| Majority Label | 43.5% | 0.055 | 24.7% | 0.007 |
| Supervised Naive Bayes (Baseline) | 73.7% | 0.386 | 45.4% | 0.187 |
| BM25 Retrieval Vote | 73.3% | 0.379 | 41.9% | 0.172 |
| Chroma Dense Retrieval Vote | 75.6% | 0.412 | 43.8% | 0.207 |
| Hybrid Retrieval Vote | 75.1% | 0.407 | 44.2% | 0.202 |
Dense retrieval performs strongly against the standard supervised baseline, outperforming it on product accuracy and both Macro-F1 measures. However, the simpler Naive Bayes classifier still achieves the highest issue accuracy (45.4%) while predicting labels in approximately 0.45 ms per record. For a production operations team prioritizing only overall routing accuracy, Naive Bayes remains a compelling, highly efficient candidate.
The most encouraging signal for RAG is the improvement in the long tail. Chroma dense retrieval increases Issue Macro-F1 from 0.187 to 0.207. This lift confirms the core hypothesis: dynamically selected precedents provide high-quality context for rare and niche operational categories that fixed prompts struggle to represent. In this experiment, hybrid fusion marginally raised overall issue accuracy over dense retrieval but lowered its Macro-F1, indicating it is not universally superior.
Absolute scores remain modest. Neither the dense (0.207) nor hybrid (0.202) Issue Macro-F1 is sufficient for autonomous routing; both require a robust human-in-the-loop review workflow.
Retrieval Recall Defines the Generation Ceiling
The retrieval evaluation uses historical operational labels as weak labels to determine relevance. "Relevant" here means sharing the same product and issue; it is a repeatable benchmark, not a final semantic judgment.
| Retriever | Label Hit@1 | Label Hit@5 | Label MRR | Search p50 | Search p95 |
|---|---|---|---|---|---|
| BM25 | 37.4% | 71.0% | 0.517 | 665 ms | 1,884 ms |
| Chroma dense | 39.8% | 74.0% | 0.542 | 59 ms | 77 ms |
| Hybrid | 40.2% | 74.4% | 0.546 | 706 ms | 1,870 ms |
The hybrid retrieval process successfully places a complaint sharing the published label in the first position (Hit@1) 40.2% of the time, and within the top five (Hit@5) 74.4% of the time.
This defines the hard performance ceiling for the generative model. Because the generator is constrained to choose only from the retrieved product–issue pairs, it is physically impossible for it to return the published operational label in the remaining 25.6% of test cases. This "Selection Constraint" is a critical architectural trade-off: forcing the model to select from dynamic examples eliminates taxonomy invention (hallucinated categories), but it transforms retrieval recall from an optimization step into a hard dependency.
Although hybrid retrieval provides the highest quality results, its median search time (p50) jumps significantly from 59 ms to 706 ms. This is a consequence of the current local implementation bottleneck: the in-process BM25 scorer scans all parent documents sequentially. Performance at the tail (p95: 1,870 ms) also varies directly with complaint narrative length. This bottleneck is not an inherent flaw in sparse search. A production deployment test should replace the in-process scorer with a scalable inverted index before judging hybrid latency. In the context of this local-machine evaluation code, dense-only retrieval remains the more credible default choice.
Structured Generation is an Operational Integration Test
The LangChain generative pipeline was exercised via an end-to-end integration test using a live complaint. The user stated that a bank closed a checking account without warning or notice. Retrieval successfully supplied relevant checking-account and account-closing precedents.
ChatOllama returned a structured object that satisfied the JSON schema requirements. However, the generated output failed the extensive post-generation validation checks:
the identified evidence quote was not an exact substring of the original narrative. The validator successfully rejected the output, recorded the hallucination failure reason,
and automatically reverted to returning the deterministic retrieval baseline with extractive evidence instead.
This integration test demonstrates an essential distinction in production RAG design: typed generation validates the shape of a response; it does not validate the truth of its content. The additional substring and label consistency checks described in Section 3 successfully caught a critical failure that schema validation alone would have masked.
A planned 11-case qualitative comparison was aborted because a local Ollama service call failed to terminate. This is framed as a critical operational test failure. This case study makes no claim about generation accuracy, summary quality, or generation latency. The evaluation runner is now identified as requiring standard operational resilience patterns, which must be implemented before rerunning the comprehensive generation comparison:
- Per-request timeouts;
- Active query cancellation;
- Incremental result persistence (streaming to disk);
- A defined idempotent retry policy.
Finally, human evaluation remains non-negotiable. Programmatic substring validation proves only that a quote was not fabricated; it does not prove that the quotation semantic supports the selected issue or that the summary provides actionable decision support for an analyst.
5. Should this system be deployed?
Not as an autonomous complaint classifier. The current evidence does support a controlled analyst-assistance experiment, but not a production rollout.
The engineering and operational case against immediate automation is concrete:
- Insufficient Accuracy: Overall issue accuracy remains below 50% across all tested systems.
- Weak Tail Performance: The best Issue Macro-F1 is only 0.207, exposing unacceptable performance on rare compliance categories.
- The Retrieval Ceiling: The correct product–issue pair is entirely absent from the top-five retrieved context window for 25.6% of test complaints.
- Taxonomy Drift: Entirely new taxonomy pairs appeared during the test period, proving the system encounters out-of-distribution data.
- Unproven Generation: Generation quality has not yet completed a reliable multi-case evaluation, and the runner lacks proven timeout and recovery paths.
- Unmeasured ROI: Crucial business metrics—summary usefulness, evidence support, handling-time reduction, and correction costs—have not yet been measured with actual analysts.
- Selection Bias: The modelling population strictly excludes complaints without published narratives, meaning the system is untested against the true intake stream.
Deploying despite these gaps would dangerously confuse an inspectable prototype with a validated, bank-grade operating process.
6. Strategic Recommendations and Next Steps
Architectural Pivot: A Composite Pattern
The experiment suggests a more pragmatic architecture than simply "forcing RAG everywhere." A supervised classifier is exceptionally cheap and achieves the highest overall issue accuracy. Dense retrieval improves product accuracy and surfaces excellent precedents. Structured generation excels at producing readable summaries and extracting verbatim evidence.
The next iteration should abandon the pure RAG routing paradigm and test a composite pipeline that assigns each component the specific job it demonstrated best:
- Supervised Classifier: Calculates broad product and issue probabilities at high speed.
- Dense Retrieval: Surfaces historical precedents, optionally filtered or boosted by the classifier's probabilities.
- LLM Generation: Constrained strictly to generating a summary and extracting exact evidence, not predicting an unconstrained operational label.
- Human Analyst: Reviews the composite dossier to accept, correct, or reject the suggested triage.
The sparse (BM25) branch should remain only if analysts ultimately find its specific precedents materially more useful than dense results. In the current local implementation, its marginal MRR gain penalizes median retrieval latency by roughly 647 ms. An optimized inverted index could change that comparison, so the branch must be optimized and remeasured before final promotion or deprecation.
The Path Forward: Shadow Piloting and Business Validation
A business case must be measured, not manufactured. The next phase must run silently (a shadow pilot) beside the existing manual workflow to establish baseline handling times, label agreement rates, rerouting frequencies, and exact correction costs. Following shadow validation, a limited visible pilot will randomize eligible complaints between the legacy interface and the AI-assisted interface, strictly preserving final human control.
Full promotion requires hard evidence across four operational gates:
- Quality: Acceptable error severity by product and issue, reliably calibrated abstention rates, and zero unacceptable regressions on tail categories.
- Human utility: Analysts actively accept or only lightly edit generated summaries and evidence, resulting in a measurable drop in handling time and downstream rerouting.
- Operations: Latency, throughput, failure recovery, index refresh pipelines, access controls, and audit logging meet all agreed-upon Service Level Objectives (SLOs).
- Economics: Measured operational savings conclusively exceed the combined costs of compute, infrastructure, human review, maintenance, and expected errors.
The final financial viability will be judged against a strict annual value model:
The inputs for this equation must be derived exclusively from the pilot. Inserting guessed analyst salaries, hypothesized volumes, or assumed time savings would fabricate a business case rather than validate an investment.
幾年前,當你向 AI 聊天機器人提出問題時,很可能會遇到以下情況:
User: "What is our Q3 revenue?"
LLM: "I don't have access to that information." ← 或是直接產生幻覺
大型語言模型(LLM)具備強大的推理與生成能力,但仍有三項根本限制:
- 知識停留在訓練資料的截止時間;
- 無法直接存取企業內部的私有資料;
- 面對知識缺口時,可能產生看似合理、實際上卻無根據的內容,也就是所謂的「幻覺」。
檢索增強生成(Retrieval-Augmented Generation,RAG)正是用來補足這類知識缺口。其核心概念是:與其把所有知識固化在昂貴且靜態的模型權重中,不如在收到請求時,即時檢索最新且相關的資訊,並將其納入模型的上下文。如此一來,模型便能依據可查證的來源作答,而不是在資訊不足時猜測:
User query → retrieve Q3 financial report chunks → inject into prompt
LLM: "According to the Q3 report, revenue was $42M, up 18% YoY."
然而,知識缺口只是應用程式表現不佳的可能原因之一。若問題出在指令含糊,也許只需要改善提示詞;若模型行為不一致,則可能需要微調(fine-tuning)。因此,在提示工程、微調與 RAG 之間做選擇時,應先釐清商業問題,再決定技術架構。
本文將從第一原理拆解這項決策,並以美國消費者金融保護局(Consumer Financial Protection Bureau,CFPB)的消費者申訴資料庫進行案例驗證。這項應用會分類並摘要申訴內容,同時提供可追溯的佐證。重點不只是證明 RAG 流程能夠運作,而是判斷它能否有效改善申訴分流(complaint triage),並合理化相應的成本與風險。全文分為四個部分:
Part 1:提示工程、微調與 RAG
這三種方法都能改善 LLM 應用程式,但作用的層面並不相同:
- 提示工程改變的是「指令」。 它釐清任務、提供少量上下文(例如 few-shot 範例),並定義預期的回應格式。
- 微調改變的是「模型行為」。 它更新模型參數,使模型穩定重現訓練範例所示範的模式、語氣或結構。
- RAG 改變的是「推論時可取得的證據」。 它從外部資料來源檢索相關紀錄,並在當次請求中提供給模型。
與其把三者視為互相競爭的功能,不如理解它們分別解決不同類型的問題。
| 問題 | 提示工程 | RAG | 微調 |
|---|---|---|---|
| 最適合的情境 | 釐清任務、建立基準表現 | 提供會變動、私有或大量的證據 | 教會模型重複出現的行為、語氣、格式或任務慣例 |
| 知識來源 | 上下文視窗(context window) | 被檢索出的文件 | 模型權重 |
| 知識規模 | 約 10 萬個 token | 數百萬份文件 | 無上限 |
| 可解釋性 | 可追溯 | 可引用來源 | 黑盒 |
| 建置成本 | 極低 | 中等:需要資料匯入、建立索引、檢索、評估 | 高:需要資料設計、訓練、評估、模型生命週期管理 |
| 單次查詢成本 | 高 | 中 | 低 |
| 常見失敗模式 | 指令模糊或範例不夠穩健 | 相關證據被遺漏、被稀釋,或被誤用 | 模型學到雜訊、死記範例,或無法泛化 |
| 範例情境 | 把文字整理成結構化 JSON | 回答內容每週都在變動的政策問答 | 讓客服聊天機器人的語氣與風格保持一致 |
因此,一個實用的決策流程如下:
更清楚的提示詞能否解決問題?
├─ 可以 → 保留提示詞並量測成效
└─ 不行
↓
缺少的是知識或證據嗎?
├─ 是 → 測試檢索(RAG)
└─ 否
↓
所需行為是否穩定、重複出現,且有高品質範例?
├─ 是 → 評估微調
└─ 否 → 重新檢視任務、工作流程或模型選擇
這並不是一道三選一的單選題。一套正式系統可以同時採用精心設計的提示詞、即時檢索最新證據,以及能穩定輸出指定格式的微調模型。三者各自承擔不同職責。若想進一步了解模型訓練,可以參考延伸文章 LLM 微調。
Part 2:RAG 系統如何運作
一套可投入正式環境的 RAG 應用,並不是單一支腳本,而是由兩條資料路徑組成的系統:離線(offline)流程負責建立可檢索的知識庫,線上(online)流程則處理即時推論請求。

離線流程:建置知識庫
1. 載入與分塊(Chunking):定義「意義的最小單位」
離線流程負責準備資料,而且必須具備可重現性:給定相同的來源快照與設定,每次都應產生相同的可檢索紀錄。
一筆好的紀錄應該包含:
- 穩定的來源與文件識別碼;
- 提供給檢索使用的文字內容;
- 用於過濾或存取控制的中繼資料(metadata);
- 來源時間戳記;
- 用於去重複的內容雜湊值(content hash);
- 解析器(parser)、分塊器(chunker)與嵌入模型(embedding model)的版本編號。
在 RAG 系統中,分塊(chunking)是影響檢索品質最深的表示法決策之一。若 chunk 過大,例如直接對整份 200 頁的 PDF 建立 embedding,向量可能混合過多概念,使真正相關的段落被雜訊稀釋;若 chunk 過小,例如只保留單一句子,又可能失去必要語境。像「利率是 5%」這句話,若沒有說明利率適用的產品與條件,便難以獨立使用。
為了在精確度與上下文之間取得平衡,工程團隊通常會評估四種分塊策略:
- 固定大小分塊(Fixed-Size Chunking,基準做法):以固定 token 數切分文字,並保留滑動重疊(overlap),降低跨越邊界的內容遭截斷的風險。256–512 token、重疊 10–20% 可作為常見的起始設定,但仍應依文件結構與評估結果調整。
Document: [----512 tokens----][----512 tokens----][----512 tokens----]
[64 tokens] [64 tokens]
overlap overlap
-
文件感知分塊(Document-Aware Chunking):尊重檔案本身的自然結構層級。也就是說,PDF 依照章節標題切分、程式碼依照函式或類別切分、HTML 依照
<section>標籤切分,並讓表格資料維持結構完整。任意的 token 切分可能會把一個表格從某一列的中間切開,徹底破壞資料之間的對應關係。 -
語意分塊(Semantic Chunking):在句子之間 embedding 相似度明顯下降的那個位置動態切分文字。雖然在資料匯入(ingestion)階段的運算成本較高,但能確保 chunk 對齊實際的主題邊界,而不是任意的 token 數量。
-
父子分塊(Parent-Child Chunking,又稱 Small-to-Big):一種將「檢索單位」與「生成單位」解耦的模式。系統會對較小的「子」chunk(例如 128 token)建立 embedding,以提高向量比對的精確度;找到匹配後,再將對應的較大「父」chunk(例如 1,024 token)提供給 LLM,補回完整語境。
Indexing:
Parent chunk (1024 tokens) → store as context
↓ split into
Child chunks (128 tokens each) → embed and index for retrieval
Query time:
1. Retrieve top-k child chunks (precise embedding match)
2. Look up their parent chunks
3. Send parent chunks (full context) to LLM
(建立索引時:父 chunk(1024 token)先被保存下來作為上下文;再切分成子 chunk(每個 128 token),對子 chunk 做 embedding 並建立索引供檢索使用。查詢時:先取出 top-k 個最相關的子 chunk(精確的 embedding 比對)→ 找出它們對應的父 chunk → 把父 chunk(完整上下文)送給 LLM。)
2. Embedding 與向量資料庫:建立可重現的語料庫
定義好 chunk 之後,接著會送進 embedding 模型,再載入向量資料庫。
Query: "What is machine learning?" → [0.23, -0.41, ..., 0.12]
"Explain ML to me" → [0.25, -0.39, ..., 0.11] ← similar vector
"Paris is in France"。 → [-0.91, 0.34, ..., 0.55] ← different vector
RAG 有一項關鍵的技術契約:建立索引與查詢時,必須使用相同且相容的 embedding 模型與設定。不同模型的向量空間彼此不相容,因此更換 embedding 模型後,通常必須重建整個索引。
如何選擇 Embedding 模型
為正式環境選擇模型時,應先對照資料治理、語言、品質、延遲與成本等限制:
- 託管服務、重視品質:OpenAI 的
text-embedding-3-large(3,072 維)適合品質優先的情境;text-embedding-3-small則偏向延遲與成本效益。 - 自架、追求隱私:如果資料不能離開你的 VPC,可以使用強大的開源 bi-encoder,例如
BGE-large-en或E5-large-v2。 - 多語言需求:Cohere 的
embed-v3或multilingual-e5是專為跨語言檢索設計的模型。 - 長文件:像
nomic-embed-text這類模型支援高達 8,192 token 的上下文視窗,適合特殊用途。
向量資料庫用來儲存高維向量,並保留其對應的原始文字 chunk 與中繼資料。相較於透過欄位、條件或關鍵字查詢紀錄的關聯式資料庫,向量資料庫會依據向量距離,在語意空間中找出與查詢最接近的內容。
若每次查詢都對數百萬個 1,536 維向量計算精確的餘弦相似度,延遲往往難以符合正式服務需求。向量資料庫通常以近似最近鄰(Approximate Nearest Neighbor,ANN)演算法降低搜尋成本,其中常見的方法之一是 HNSW(Hierarchical Navigable Small World)。
如何選擇向量資料庫
適合的向量資料庫取決於你的基礎架構策略:
| 基礎架構策略 | 建議工具 | 適合的原因 |
|---|---|---|
| 全託管雲端服務 | Pinecone | 幾乎零維運負擔;專為純生產規模而生。 |
| 既有技術棧 | pgvector(PostgreSQL) | 讓向量資料與關聯式資料放在一起,不必再引入新的基礎架構依賴。 |
| 高效能開源方案 | Qdrant、Milvus、Weaviate | 可自架或託管;具備強大的過濾能力(filtering)、以 Rust 打造的高速表現(Qdrant),或是超大規模的擴充能力(Milvus)。 |
| 原型開發與研究 | Chroma、FAISS | 以 SQLite 為底層,或純記憶體運作。很適合本地研究,但較不適合企業級部署。 |
只儲存向量與文字並不足夠。若索引缺乏可追溯性,發生品質問題時便難以定位原因。每筆紀錄都應保留來源 ID、時間戳記、內容雜湊值與分塊器版本等中繼資料,才能支援精準刪除、版本追蹤與事件應變。此外,一旦更換 embedding 模型或其關鍵設定,就必須重建索引,因為不同向量空間無法直接比較。
線上流程:服務即時請求
RAG 回應的品質,取決於檢索到的證據、組裝出的上下文,以及模型運用該上下文的能力——三者環環相扣。 每一層都需要各自獨立的測試。
離線流程著重索引處理量與可重現性;線上流程則關注端到端延遲、檢索品質,以及生成內容是否有充分依據(grounding)。實際延遲目標應依工作負載與服務水準目標(SLO)制定,而不能只套用通用數字。
3. 查詢 Embedding 與檢索
第一階段檢索的任務,是在嚴格的延遲預算內,從大量候選段落中找出一小組高召回率結果。正式環境常將詞彙比對(lexical matching)與向量搜尋搭配使用,形成混合檢索系統。
稀疏檢索:BM25(詞彙精確比對)
稀疏檢索純粹依靠詞彙證據為文件排序。當語言的精確性很重要時——例如搜尋特定的產品名稱、錯誤代碼,或是某個獨特的詞語——稀疏檢索表現特別出色。
最常見的演算法 BM25,評估的是詞頻與稀有程度。簡單來說:
- 當查詢詞在某份文件中出現頻率高(高詞頻),或該詞在整個語料庫中很罕見(高逆向文件頻率)時,分數會上升。
- 當文件過長,或查詢詞在所有文件中都很常見(例如「the」「is」「and」)時,分數會下降。
BM25 詳細說明
BM25 是一種詞彙比對演算法,為文件評分時不需要深度學習或向量表示法。
其中:
- :查詢詞 在文件 中的詞頻。
- :逆向文件頻率。用來區分「罕見、資訊量高的詞」(權重高)與「像 the、is 這類到處都是的詞」(被懲罰、權重低)。
- :以語料庫平均長度()為基準,搭配超參數 (通常 )對文件長度做正規化,避免過長的文件主導結果。
- :校準詞頻的飽和程度(通常 )。
密集檢索:Embedding 與近似最近鄰(ANN)

密集檢索把文字轉換成連續的幾何向量,做法是使用 bi-encoder 這種 embedding 方式,將查詢與候選文件各自獨立編碼進同一個高維向量空間。由於文件向量早在離線階段就已預先算好並存進向量資料庫,執行期只需要對使用者的查詢做 embedding 即可。
Bi-encoder embedding
bi-encoder 這種 embedding 方式,會把查詢與候選文件分別、獨立地編碼進同一個高維向量空間。
- 運作機制:
- 為什麼能夠擴展規模:文件 embedding()只需在離線建索引時計算一次。收到請求後,系統只需對查詢執行一次前向運算(forward pass)取得 ,再交由向量索引搜尋大量候選。實際延遲取決於索引類型、資料規模與基礎架構。
- 限制:由於文件與查詢是各自獨立編碼的,模型無法捕捉到細緻的 token 層級交叉互動。
查詢完成 embedding 後,系統便會搜尋向量資料庫。若在請求當下對數百萬個高維向量逐一計算精確餘弦相似度,每次查詢的成本為 ,通常不具實務可行性。正式環境的向量資料庫會使用 HNSW 等近似最近鄰索引降低搜尋成本:
- 建構階段:建立一個多層圖結構,上層是稀疏、長距離的連接(用於粗略搜尋),底層則是密集、局部化的群集(用於精細搜尋)。
- 搜尋階段:由上而下走訪這個圖結構,快速鎖定候選鄰域,用一點點召回率(recall)換取對數等級的搜尋速度()。
密集檢索 vs. 稀疏檢索:根本上的取捨
| 面向 | 稀疏檢索(BM25) | 密集檢索(向量搜尋) |
|---|---|---|
| 比對邏輯 | 精確的關鍵字/詞彙比對 | 潛在語意比對(「car」=「automobile」=「vehicle」) |
| 核心優勢 | 擅長處理罕見/特定詞彙:精確字串、錯誤代碼、SKU、型號名稱 | 能捕捉意義、改寫說法、使用者意圖 |
| 失效模式 | 詞彙不一致就抓不到(「automobile」≠「car」),沒有語意理解能力(「joyful」≠「happy」) | 難以處理精確的罕見詞(例如英數混合的識別碼「SK-1234」) |
| 運算開銷 | 快、只需 CPU、記憶體佔用極小 | 建議使用 GPU 加速;向量索引較耗記憶體 |
| 可解釋性 | 能解釋為何某份文件得分高 | 高維度幾何表示法,較難解釋 |
沒有任何一種方法能夠通吃所有查詢。混合檢索會同時平行執行 BM25 與密集檢索,同時發揮稀疏搜尋的精確比對能力與密集搜尋的語意涵蓋範圍,再以數學方式合併兩者的排名結果。
4. 重新排序(Re-Ranking)與融合(Fusion)
現代 RAG 系統中的資訊檢索,本質上要面對「運算效率」與「語意表達能力」之間的根本工程取捨。要處理好這個取捨,需要把兩種不同的神經網路架構,串接成一個兩階段的漏斗:
| 階段 | 模型架構 | 涵蓋範圍 | 主要目標 | 延遲目標 |
|---|---|---|---|---|
| 第一階段:檢索 | Bi-Encoder + ANN | 整個語料庫(10⁵–10⁷ 個 chunk) | 高召回率(廣撒網) | ∼10–20 毫秒 |
| 第二階段:重新排序 | Cross-Encoder | Top-K 候選(20–50 個 chunk) | 高精確度(精準排序) | ∼50–100 毫秒 |
bi-encoder 能夠有效率地搜尋整個語料庫,因為文件向量早已在離線階段預先算好。不過,它是各自獨立評估查詢與文件。
BI-ENCODER (First-Stage Retrieval)
Online Query → [ Encoder ] → Vector (q) ──┐
├→ Cosine Similarity → Rank
Offline Document → [ Encoder ] → Vector (d) ──┘
cross-encoder 則是透過完整的自注意力(self-attention)層,把查詢與文件一起聯合處理,讓查詢中的每一個 token 都能直接與文件中的每一個 token 互動。
CROSS-ENCODER (Second-Stage Re-ranking)
[ Query + Document ] → [ Joint Encoder with Cross-Attention ] → Relevance Score
對 1,000,000 份文件逐一執行 cross-encoder,通常不具成本與延遲上的可行性;但若只重新排序前 50 筆候選,便可能落在可接受的服務預算內。實際時間仍取決於模型、硬體與批次設定。
Cross-encoder 詳細說明
cross-encoder 會透過完整的自注意力層,把查詢與文件一起聯合處理()。
- 運作機制:
- 為什麼比較精確:查詢中的每一個 token,都能透過完整的自注意力層,直接對候選文件中的每一個 token 進行關注。這種聯合表示法能捕捉到 bi-encoder 會遺漏的細膩語意、否定語氣,以及複雜的詞彙互動。
- 限制:若要在每次請求中對上百萬組查詢—文件配對執行完整的自注意力運算,運算成本高到不切實際( 組配對 1 毫秒 16.6 分鐘)。
融合混合檢索的結果
在結合稀疏(BM25)與密集(向量)檢索時,系統通常會採用以下兩種策略之一,來合併兩份候選清單:
- 倒數排名融合(Reciprocal Rank Fusion, RRF):RRF 在合併排名清單時,不需要對分數做正規化,而是使用相對的排名位置,而非原始分數:
其中 是檢索器(retriever)的集合, 是一個平滑常數(通常 ),用來降低排名最前面的項目所享有的不成比例優勢。RRF 獎勵的是「在兩種檢索典範中都能穩定表現良好」的文件。
範例():
- 文件 A:在 BM25 中排名第 1,在密集檢索中排名第 5
- 文件 B:在 BM25 中排名第 3,在密集檢索中排名第 1
文件 B 勝出,因為它在兩種檢索器中都維持了較高的排名。
- 線性分數融合(Linear Score Fusion):另一種做法,是先用 min-max 縮放法把原始分數正規化到 區間,再用一個調校過的權重因子 加以組合:
註:線性融合需要謹慎校準,因為 BM25 的分數沒有上下界,而餘弦相似度則是被限制在 -1 到 1 之間。
正式環境常見的重新排序選項
- 開源 Cross-Encoder:自架
cross-encoder/ms-marco-MiniLM-L-6-v2或bge-reranker-large等模型,可自行掌控資料流向、部署方式與效能調校。
from sentence_transformers import CrossEncoder
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
# Score each candidate against the query
pairs = [(query, doc) for doc in candidates]
- 託管 API:像 Cohere Rerank(
rerank-v3.5)這類專門的服務端點,會透過託管 API 處理斷詞與評分。
import cohere
co = cohere.Client("api-key")
results = co.rerank(
query=query,
documents=candidates,
model="rerank-english-v3.0",
top_n=5
)
- 以 LLM 為基礎的重新排序:讓一個前沿 LLM 以 1–10 分為候選段落的相關性評分,能得到相當細膩的判斷,但由於延遲與 token 成本的關係,通常只適用於較小的候選集合(少於 20 個)。
Prompt: "On a scale of 1-10, how relevant is this passage to the query?
Query: {query}
Passage: {passage}
Score:"
Run for each candidate, sort by score.
Only feasible for small candidate sets (<20) due to cost.
5. 上下文組裝與生成
找到正確段落只是完成了一半;系統還必須在有限的上下文預算內組裝內容,並以明確的生成契約約束模型行為。
資訊預算
上下文組裝,本質上是一個「資訊預算」問題。系統必須決定:
- 在 token 上限之內,要納入多少筆紀錄。
- 生成模型可以檢視哪些 metadata 欄位。
- 段落要如何格式化、分隔,以及去重複。
- 哪些文字代表可信任的指令,哪些又是不可信任的使用者資料。
上下文並不是越多越安全。多餘的雜訊會讓模型分心,重複的段落會人為地灌高模型的自信程度,而真正關鍵的事實反而容易被淹沒。
「Lost in the Middle」(迷失在中間)現象
LLM 對上下文視窗開頭與結尾的資訊,注意力表現較好;位於中間的資訊,則經常被忽略(Liu et al., 2024):
PROMPT ATTENTION CURVE ("Lost in the Middle")
[ System Instructions & Schema ] ← High Attention / Primacy Effect
[ Chunk #1 (Highest Relevance) ] ← Strong Attention
[ Chunk #2 (Moderate Relevance) ] ← Weakest Attention ("Lost in the Middle")
[ Chunk #3 (Moderate Relevance) ] ← Weakest Attention ("Lost in the Middle")
[ Chunk #4 (High Relevance) ] ← Strong Attention
[ User Query & Citation Rule ] ← High Attention / Recency Effect
(系統指令與 schema:高注意力/初始效應;chunk #1,相關性最高:強注意力;chunk #2、#3,相關性中等:注意力最弱,也就是「迷失在中間」;chunk #4,相關性高:強注意力;使用者查詢與引用規則:高注意力/時近效應。)
要對抗「Lost in the Middle」現象,可以:
- 積極截斷:只保留 top 3–5 個候選 chunk,而不是把整個上下文視窗塞滿。
- 策略性重新排序:把最好的證據放在上下文區塊的最前面與最後面。
以引用建立依據(Grounding)
替檢索出的 chunk 加上清楚的識別碼並格式化,能讓模型把每一項主張直接歸因到對應的來源段落。
生成的行為契約
「只使用提供的來源」仍屬提示層級的軟性約束,不能取代程式化防護。正式環境的生成流程至少需要以下控管:
- 型別化的輸出 Schema:強制使用結構化輸出(例如 JSON Schema 或 Pydantic),避免下游解析出錯。
- 確定性解碼:把 temperature 設為 0.0,確保推論結果可重現、評估結果可信。
- 資料區隔:在「證據」與「參考資料」之間維持嚴格的界線。
- 證據(Evidence):可驗證、直接從使用者當次提交內容中擷取出來的精確引句。
- 檢索到的參考資料(Retrieved References):為了說明分類類別而檢索出的歷史紀錄或先例。
- 驗證與備援:生成完成後,以程式方式驗證引用內容。如果檢索到的證據分數低於最低門檻,就把請求導向人工審核佇列,或改採拒答(abstention)路徑,而不是冒著產生幻覺答案的風險。
6. 進階 RAG 模式:動態與 Agentic 工作流程
「embedding → 檢索 → 插入 → 生成」這條線性流程,足以應付基本的文件查找。然而,企業系統經常需要更動態的介入手段,來處理模糊的使用者行為與複雜的分析任務。當標準 RAG 力有未逮時,工程團隊會導入更進階的路由、壓縮與 agentic 模式。
-
查詢路由與擴展(檢索前):使用者的原始查詢通常不是最適合檢索的形式。與其把「連線錯誤」這類模糊提示詞直接送進向量資料庫,不如先由 LLM 判斷意圖或改寫查詢。
- 路由(Routing) 會判斷使用者的意圖,並導向適合的系統(例如:把統計性的查詢導向 SQL 資料庫,把政策性的問題導向向量索引)。
- 擴展(Expansion) 會把查詢改寫成最佳化的形式(例如改寫成「資料庫連線錯誤 疑難排解步驟」),或產生多個不同版本以最大化召回率。其中有一種特殊變體叫做 HyDE(Hypothetical Document Embeddings,假設文件嵌入),做法是請 LLM 先寫出一個虛構的假設性答案,再對這段文字做 embedding——這段文字的風格結構,通常比原始問題更貼近目標文件的樣貌。
-
上下文壓縮(檢索後):一個 512-token chunk 可能只有一句話與查詢真正相關。上下文壓縮會在送入最終生成模型前,先以較快、較輕量的模型移除不相關句子,提高提示詞中的資訊密度。
-
Agentic 與 Self-RAG(執行):線性流程在面對複雜的比較性問題時(例如「比較我們前三大產品在 Q3 與 Q2 的營收成長」)會失靈。
- Agentic RAG 把 LLM 變成一個協調者(orchestrator),讓它執行多次反覆的搜尋——先檢索 Q3 資料、再檢索 Q2 資料、然後交叉比對——最後才產生答案。
- Self-RAG 則賦予模型自主權,讓它自己判斷究竟需不需要檢索資訊、評估抓回來的 chunk 是否真的相關,並且在偵測到自己的主張缺乏文本依據時,反覆重新生成答案。
Part 3:評估 RAG 應用程式
單一端到端分數只能告訴我們系統表現變好或變差,卻很難解釋原因。RAG 評估應將檢索、生成、維運、維護與商業成效分開衡量,才能定位真正的瓶頸。
1. 檢索評估:系統有找到有用的證據嗎?
檢索測試集通常由歷史查詢與人工確認的相關性標記組成。依任務需求,相關性可以是二元判斷(相關/不相關)、分級評分,或直接對應特定來源文件。常見指標包括:
- Hit Rate@k:一個二元指標,衡量 top 個結果中,是否至少出現一份相關文件。
- Recall@k(召回率):在語料庫中所有相關文件裡,有多少比例成功出現在 top 個結果之中?
- Precision@k(精確率):在檢索出的 top 份文件中,有多少比例真的相關?
- 平均倒數排名(Mean Reciprocal Rank, MRR):評估排名清單中,第一個相關結果出現得有多早。
- nDCG(Normalized Discounted Cumulative Gain):當相關性有多個加權等級時(例如「完全符合」對比「部分符合」),用來衡量整體排名品質。
檢索結果也應依罕見標籤、申訴長度、時間區間與詞彙難度分層檢視。若平均值主要由常見的支票/儲蓄帳戶案例構成,便可能掩蓋檢索器在少數類別上的失敗。
2. 生成評估:模型有正確運用上下文嗎?
即使檢索器找到了正確文件,若 LLM 忽略內容或產生幻覺,整體回應仍然不可用。因此,生成評估需要拆成數個獨立面向:
- 任務正確性:預測出的 product 與 issue 標籤準不準確?(最好用 exact match 搭配 Macro-F1 來衡量,Macro-F1 會讓每個標籤獲得同等權重,避免常見類別主導整體分數)。
- Schema 有效性:JSON/結構化回應能不能被下游 API 安全地解析與使用?
- 忠實度(Faithfulness):所有生成出的主張,是否都明確有檢索到的上下文支持?
- 證據有效性:作為證據的引句,是否為申訴原文中逐字、精確的片段?(這一點可以透過確定性的字串比對來評估)。
- 摘要涵蓋度:生成的摘要有沒有保留申訴的實質事實,而沒有捏造新的細節?
- 拒答品質(Abstention Quality):當檢索到的證據不足時,系統能不能妥善地把案件交還給人工處理?
product 與 issue 預測可使用 exact match 與 Macro-F1 評估。Macro-F1 對各標籤賦予相同權重,能降低常見類別主導整體分數的問題。證據有效性則可從決定性的字串檢查開始:每一句引言都必須逐字出現在輸入內容中。至於引言在語意上是否支持該項主張,仍需人工審核,或由另行驗證的判斷模型(judge)評估。
RAGAS 等框架會運用「LLM 作為評審(LLM-as-a-judge)」的方法,將忠實度與上下文相關性等面向形式化。不過,這類不依賴參考答案的自動判斷仍是方向性估計,不應視為 ground truth。經過人工樣本校準後,它們才適合用於快速迭代 Es et al., 2024。
3. 維運評估:系統能達成服務目標嗎?
演算法品質是必要條件,卻不是充分條件。一項正式服務還必須接受基礎架構與操作面的評估:
- 延遲:嚴格依照階段(檢索、重新排序、生成)分別量測的 p50 與 p95 回應時間。
- 吞吐量:在預期的並發負載下,每秒能處理的請求數。
- 成本:把 token 用量(輸入、上下文預算、輸出)換算成每一個被接受回應的成本。
- 可靠性:逾時比例、schema 解析失敗次數,以及依賴項目的錯誤率。
分階段量測非常重要。如果生成階段就佔掉了 95% 的回應時間,那麼再怎麼優化向量搜尋,也不會實質改善使用者體驗。
4. 維護評估:品質能撐過變動嗎?
RAG 是一套持續變動的資料系統。維護指標必須持續監控底層資料流程的健康狀況:
- 資料健康度:資料匯入延遲、解析失敗的紀錄數、重複率,以及來源資料的新鮮度。
- 語意漂移(Semantic Drift):使用者查詢分布的變化、新分類值的出現,或舊產品標籤的淘汰。
- 維運敏捷度:重建、驗證並回滾一個索引,實際需要花費多少時間與運算成本。
索引版本應採用與模型版本同等嚴謹的測試與發布流程。候選索引必須先通過黃金檢索測試集(golden retrieval set)才能上線,前一版索引也應保留可立即回滾的能力。
5. 商業評估:這項介入措施改善了工作流程嗎?
最終具有決策意義的指標,往往位於模型架構之外。這項應用是否成功,取決於:
- 效率:分析人員的平均處理時間,以及待辦案件的積壓天數。
- 品質:審核人員之間的一致性,以及錯誤自動分流所造成的嚴重程度/預期成本。
- 採用度:分析人員的接受率、修正率,以及重工頻率。
- 投資報酬率(ROI):每完成一件申訴分流,所需的全成本(fully loaded cost)。
評估應循序推進:先進行離線靜態測試,再進入影子運作(shadow operation,在既有人工作業旁靜默執行),最後才展開小規模的分析師輔助試點。全面自動化則是另一項獨立的商業決策,需要更高的證據門檻。
Part 4:CFPB 申訴資料案例研究
1. 商業問題
CFPB 消費者申訴資料庫公開消費者對金融產品與服務提出的申訴資訊。若消費者同意公開申訴敘述(narrative),CFPB 會在發布前採取措施移除個人資訊。CFPB 也明確提醒,這份資料庫不是具代表性的統計樣本,不應用來推論所有消費者的經驗。詳細發布流程可參考 CFPB 的申訴資料使用說明。
本文將應用範圍限定在一項明確的工作流程:
Input: an unstructured consumer complaint narrative
Output: suggested product
suggested issue
concise summary
exact supporting quotations
retrieved precedent complaints
confidence and abstention status
(輸入:一段非結構化的消費者申訴敘述;輸出:建議的 product、建議的 issue、簡潔摘要、精確的佐證引句、檢索到的先例申訴,以及信心分數與拒答狀態。)
這項應用可支援多種內部角色。品質與營運分析人員可用它協助分類、摘要與審核個別申訴;風險管理團隊可從彙整後的模式中辨識新興控管問題;公關或企業溝通團隊則可追蹤反覆出現、可能需要回應的議題。這套系統的定位始終是決策輔助:它不判定申訴是否成立、不決定消費者救濟、不替公司排名,也不提供法律意見。處理個案時,原始申訴敘述與佐證內容都必須保留,供人工審核。
現行工作流程的假設
人工分流可能造成持續性的效率損失。在沒有輔助工具的情況下,分析人員必須閱讀申訴敘述、從 product 與 issue 分類體系中選擇對應項目、撰寫摘要,並留下足以供後續覆核的理由。內容冗長或語意模糊的申訴需要更多處理時間;相似案件也可能因人而異地被歸入不同類別,造成重新分派與下游重工。

RAG 價值假設
RAG 的價值假設是:相較於固定的 few-shot 提示詞,動態檢索且附有歷史標籤的申訴案例,能提供更貼近當前案件的先例。若系統同時擷取可追溯的證據,分析人員便可從審核系統建議開始,而不必每次都從空白表單起步。

這項假設隱含了幾個可被量測的結果:
- 處理時間下降,且修正率不會上升;
- product 與 issue 標籤的一致性提高;
- 需要重新分派的申訴變少;
- 分析人員認為生成的摘要與證據有用,並願意採用;
- 在預期的申訴量下,延遲與成本都在可接受範圍內。
這項假設也設定了必要的比較基準。分類本身是已有標籤的監督式學習問題,因此 RAG 不能只與較弱的提示詞比較,還必須面對更簡單的分類器。若分類器能以更低成本提供更高的分流準確度,檢索機制就必須透過證據呈現、先例探索、適應新類別或改善工作流程,證明其額外價值。
2. 資料分析與離線 Pipeline
本地資料快照包含 98,185 筆於 2025 年 1 月至 2026 年 6 月間收到的申訴紀錄,其中只有 36.7%(36,063 筆)附有公開的消費者申訴敘述。這項服務只能使用這個「具敘述文字」的子集合進行建模與評估。
這種缺失並非隨機,因而可能造成選樣偏誤(selection bias)。敘述文字只有在消費者同意後才會公開;較新的申訴也可能先出現在資料庫中,之後才完成去識別化處理。換言之,只在公開敘述上通過評估的模型,並不代表已涵蓋所有進入 CFPB 流程的申訴。
| 資料特性 | 在本地快照中觀察到的數值 | 對設計上的影響 |
|---|---|---|
| 申訴總數 | 98,185 | 適合用來描述資料來源整體樣貌,但無法全部用於敘述文字建模 |
| 已公開的敘述文字 | 36,063 | 定義了實際可建模的母體 |
| Product 類別數 | 11 | Product 成為階層式預測的第一層 |
| Issue 類別數 | 82 | 若只看整體準確率,會高估模型在長尾類別上的表現 |
| Product–Issue 配對數 | 118 | Issue 的預測結果,必須對應到所選的 Product 才算有效 |
| 敘述文字長度中位數 | 178 詞 | 一則申訴通常剛好對應一筆檢索紀錄 |
| 第 99 百分位長度 | 1,099 詞 | 仍然需要一套針對長文件的處理方式 |
| 最大長度 | 5,205 詞 | 若直接盲目截斷,會遺失實質內容 |
去重複可避免「記憶洩漏」
經過空白字元、Unicode 與大小寫正規化後,公開敘述子集中共有 1,068 筆重複紀錄。若其中一筆進入檢索索引,而重複版本落入測試集,最近鄰檢索的分數便會被高估:系統實際上是在取回幾乎相同的文字,而不是從相似案例中展現泛化能力。
使用 SHA-256 雜湊進行去重複
實作方式是先為每筆敘述文字計算一個標準化的 SHA-256 雜湊值,把紀錄分組,並且只保留時間序上最早出現的那一筆。
import hashlib
def narrative_hash(text: str) -> str:
"""Computes SHA-256 hash of normalized text for exact deduplication."""
canonical = normalize_narrative(text).casefold()
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
移除重複紀錄後,資料集再依時間順序切分。這比隨機切分更接近實際維運問題:使用歷史申訴建立的索引,能否協助分類之後才收到的新案件?
| 資料切分 | 日期區間(首次出現時間) | 筆數 | 維運用途 |
|---|---|---|---|
| 訓練/索引集 | 2026 年 3 月 1 日之前 | 28,174 | 作為檢索語料庫,以及監督式分類器的基準 |
| 開發集 | 2026 年 3 月 1 日至 4 月 30 日 | 3,966 | 用於調校檢索策略、重新排序器與拒答門檻 |
| 測試集(黃金測試集) | 2026 年 5 月 1 日起 | 2,855 | 凍結、作為最終比較用的資料集 |
標籤分布不均(長尾問題)
product 標籤分布高度集中:前三大類別合計占訓練索引的 74.5%,其中「支票或儲蓄帳戶(Checking or savings account)」單一類別便占 36.0%。訓練集中共有 77 個 issue 標籤,其中 37 個少於 50 筆,19 個甚至少於 10 筆。
測試集中有五筆申訴的 product–issue 配對從未出現在歷史索引,其中四筆的 issue 標籤在訓練資料中完全不存在。這些冷啟動(cold start)案例顯示,更新索引不只是提升資料新鮮度,也關係到系統能否取得新的營運類別。
這種分布不均對評估與設計帶來三項影響:
- Macro-F1 至關重要。 與整體準確率不同,Macro-F1 讓罕見標籤享有同等權重,避免主流類別掩蓋掉模型在小眾標籤上的失敗。
- 必須具備拒答能力。 模型必須有能力辨識出「自己的分類體系不完整」或「某筆申訴缺乏歷史先例」的情況,並把案件交還給人工處理。
- 索引必須定期更新。 更新索引是取得新類別的功能需求,而不只是效能最佳化。
標籤是可量測的目標,而非不容質疑的真相
Product 與 issue 欄位使這份資料集適合量化評估,但它們應被視為營運標籤,而非不容質疑的客觀事實。一則敘述可能同時涵蓋多個事件、合理對應到不同 issue,也可能缺少後續調查才取得的資訊。這套基準衡量的是模型與已發布標籤的一致程度,而不是對消費者實際遭遇做出最終判定。
正因為 ground truth 並不完美,本文的人機協作(human-in-the-loop)流程會保留原始申訴敘述、呈現可追溯的證據引句,並將分類結果標示為建議,而非最終判定。人工審核是系統設計的一部分,不是暫時性的補救措施。
索引策略:父子(Parent-Child)表示法
每則申訴都以「父」紀錄保存完整版本,包括 product、issue、申訴日期與 complaint ID。在 LangChain 實作中,它會成為父層級 Document,中繼資料則保留營運標籤與稽核追蹤欄位。
為避免長篇敘述遭截斷,密集檢索流程採用 small-to-big 的父子設計:
- 子層級切分:較長的敘述文字,會被切分成多個有重疊的子 chunk,每個最多 1,200 詞(重疊 100 詞)。
- 子段落索引:28,174 筆訓練申訴共產生 28,444 個子段落,其中 215 筆申訴需要切成一個以上的段落。Chroma 會索引這些子段落,以避免直接截斷長文,並提高局部語意的可檢索性。
- 還原父層級:查詢時先搜尋子段落,再於融合前依 complaint ID 去重複並還原為父層級紀錄。
BM25 則直接處理完整的父層級敘述,保留其在特定關鍵字與罕見詞彙上的比對能力。
這項設計讓長達 5,000 詞的申訴仍可由相關子段落直接匹配查詢,同時把標籤、引用與稽核軌跡維持在申訴層級,供生成流程與人工審核使用。
3. 建置應用程式
這項實作刻意保持輕量、可重現且容易檢視,主要元件包括:
- LangChain 統一 Document、Retriever、Prompt 與結構化生成介面;
- Chroma 作為密集檢索用的持久化向量資料庫;
rank-bm25負責稀疏、詞彙層級的檢索;- Ollama 在本機提供 embedding 與生成模型服務。
透過 Ollama 在本機執行模型,原始申訴敘述、向量 embedding 與生成結果都留在實驗機器上,可降低資料傳送至公有雲 API 所衍生的外洩與法遵風險;但本機儲存、存取控制與保留政策仍需另外治理。
OFFLINE PIPELINE
CFPB CSV → clean → hash/deduplicate → chronological split
↓
parent Documents
├─ full narrative → BM25 retriever
└─ child windows → Ollama embeddings → Chroma
ONLINE PIPELINE
Narrative → BM25 parent top-50 ───────────────┐
Narrative → Chroma child search → parents ────┴─ weighted RRF
↓
top-5 precedents
↓
typed generation + validation
↓
response or safe fallback
(離線流程:CFPB CSV → 清理 → 雜湊去重 → 依時間切分 → 父層級 Document,再分為「完整敘述 → BM25 retriever」與「子層級窗口 → Ollama embeddings → Chroma」兩條路徑。線上流程:申訴敘述分別經過 BM25 父層級 top-50 與 Chroma 子層級搜尋,再還原父層級、以加權 RRF 融合、取得 top-5 先例,最後執行型別化生成與驗證;若驗證失敗,則安全地採用備援結果。)
混合檢索,統一身分
稀疏檢索路徑繼承 LangChain 的 BaseRetriever,並以 rank-bm25 對完整的父層級敘述評分。自訂斷詞器會先移除常見停用詞(stop words)、獨立數字與 CFPB 遮蔽佔位符(例如 XXXX),降低低資訊量詞彙對排序的干擾。
密集檢索路徑使用 LangChain 的 OllamaEmbeddings 整合,在本機執行 nomic-embed-text。系統將 28,444 個子段落 embedding 及其父層級中繼資料寫入持久化 Chroma collection。查詢時,Chroma 先回傳語意相關的子段落,再由應用程式依父層級 ID 彙整,避免同一則長申訴的多個片段占滿候選清單。
稀疏與密集兩條搜尋路徑,各自都回傳 50 筆初始候選。兩份排名清單,會用加權過的倒數排名融合(RRF)加以合併:
密集檢索權重設為 0.9,是根據依時間切分的開發集結果選定。早期測試顯示,50:50 融合反而會稀釋較強的密集檢索結果。所有融合權重調整都限制在開發集內,最終測試集則維持凍結。
目前版本未加入 cross-encoder 重新排序器。重新排序仍值得後續測試,但每個新增元件都必須證明其品質增益足以抵銷延遲與維護成本,而不是因為它常出現在 RAG 架構圖中就直接導入。
結構化生成:驗證與備援
混合檢索流程,會提供動態、附帶標籤的 few-shot 範例給生成模型。每一個先例,都附有 CFPB 歷史資料中已發布的 product 與 issue 標籤。
系統會先計算一個決定性的診斷基準:對前五個檢索先例的標籤進行排名加權投票。這既能隔離檢索本身對分類準確度的貢獻,也能在 LLM 輸出未通過驗證時提供安全備援。
生成路徑使用 LangChain 的 ChatPromptTemplate,將系統政策、使用者提交的申訴與歷史範例清楚分隔。填入五個檢索先例後,提示詞透過 ChatOllama 傳送至本機的 llama3.1 模型;with_structured_output() 則將回應綁定至 Pydantic schema。
生成模型被要求要:
- 擷取:一段簡潔的摘要,以及一到兩句佐證用的證據引句。
- 分析:從提供的動態先例中,選出支持度最高的 product–issue 配對;若依據不足,則選擇拒答。
回應契約如下:
{
"product": "Checking or savings account",
"issue": "Closing an account",
"summary": "The consumer says the bank closed an account without notice.",
"evidence": [
{
"quote": "The bank closed my account without warning.",
"reason": "Directly supports the account-closure classification."
}
],
"retrieved_references": [
{
"complaint_id": "...",
"product": "Checking or savings account",
"issue": "Closing an account",
"score": 0.0159
}
],
"confidence": 0.72,
"abstained": false
}
生成流程收到回應後,會立即執行程式化驗證:product 與 issue 必須構成歷史資料中曾觀察到的有效配對;每一句證據引句也必須逐字存在於原始申訴敘述中。檢索到的先例文字一律視為不可信任的上下文資料,不得被當成系統指令執行。
若結構化生成無法通過 Pydantic 解析、使用無效標籤,或證據未通過子字串檢查,系統會記錄失敗原因,並改用純檢索的決定性預測作為備援。
可觀測性與服務邊界
這個應用程式被設計成一個 FastAPI 服務,對外暴露三個端點:
GET /healthz:確認程序仍在正常回應。GET /readyz:確認檢索索引已經載入,並回報其版本。POST /v1/analyze:執行混合檢索、結構化生成、驗證與備援流程。
每一筆分析結果,都會附上結構化的診斷 metadata,包括 embedding 與生成模型、索引版本、檢索方法、各階段延遲,以及備援狀態。舉例來說:
{
"trace": {
"index_version": "20260825T195722Z",
"embedding_model": "nomic-embed-text:latest",
"generation_model": "llama3.1:latest",
"retrieval_method": "langchain_chroma_bm25_weighted_rrf",
"retrieval_ms": 684.3,
"generation_ms": 2511.7,
"total_ms": 3197.4,
"fallback_reason": "generation did not return a valid exact evidence span"
}
}
當生成結果成功通過驗證時,fallback_reason 會是 null。索引更新採用原子化的「升級(promotion)」模式來管理:建構流程會先把完整的 Chroma 資料與 metadata 寫進一個暫存(staging)目錄,只有在成功之後才會正式升級為現行版本,並且拒絕覆寫任何已存在的版本目錄。
這個案例研究採用接近正式系統的設計方式,但本身並不是正式部署。若要處理非公開的敏感申訴資料,仍需補上身分驗證、角色權限控管(RBAC)、傳輸與儲存加密、並發負載測試、資料保留政策、主動監控、稽核紀錄,以及經核准的人機協作審核流程。
4. 評估結果
第一階段評估在完整的 2,855 筆測試集上,分別檢視檢索品質與標籤預測準確度。所有方法均使用相同的凍結語料庫、測試紀錄、top-5 加權投票邏輯與 product–issue 階層限制。
延遲則使用本機診斷結果,針對前 50 筆測試申訴執行實際應用流程,包含查詢 embedding、父層級彙整與 RRF 計算。這組數據反映的是目前單一行程實作的效能,不能視為 Chroma 的通用效能基準。
| 系統 | Product 準確率 | Product Macro-F1 | Issue 準確率 | Issue Macro-F1 |
|---|---|---|---|---|
| 多數標籤(Majority Label) | 43.5% | 0.055 | 24.7% | 0.007 |
| 監督式 Naive Bayes(基準) | 73.7% | 0.386 | 45.4% | 0.187 |
| BM25 檢索投票 | 73.3% | 0.379 | 41.9% | 0.172 |
| Chroma 密集檢索投票 | 75.6% | 0.412 | 43.8% | 0.207 |
| 混合檢索投票 | 75.1% | 0.407 | 44.2% | 0.202 |
密集檢索在 product 準確率與兩項 Macro-F1 指標上優於監督式基準模型;不過,較簡單的 Naive Bayes 分類器仍取得最高的 issue 準確率(45.4%),而且每筆預測平均只需約 0.45 毫秒。若團隊只關心整體分流準確率與成本,Naive Bayes 仍是值得優先考慮的方案。
對 RAG 較有利的訊號,是長尾指標有所改善:Chroma 密集檢索將 issue Macro-F1 從 0.187 提升至 0.207,顯示動態先例可能對部分少數類別有幫助。然而,混合融合雖略微提高整體 issue 準確率,卻降低 Macro-F1,說明它並未在所有面向都優於單純密集檢索。
即使如此,絕對分數仍然偏低。無論密集檢索(0.207)或混合檢索(0.202)的 issue Macro-F1,都不足以支持自動化路由,仍需搭配人機協作審核。
檢索召回率決定了生成表現的上限
檢索評估使用歷史維運標籤作為弱標籤(weak label),藉此判斷相關性。這裡所謂的「相關」,指的是「擁有相同的 product 與 issue」,這是一種可重複執行的基準測試,而不是最終的語意判斷。
| 檢索器 | 標籤 Hit@1 | 標籤 Hit@5 | 標籤 MRR | 搜尋 p50 | 搜尋 p95 |
|---|---|---|---|---|---|
| BM25 | 37.4% | 71.0% | 0.517 | 665 毫秒 | 1,884 毫秒 |
| Chroma 密集檢索 | 39.8% | 74.0% | 0.542 | 59 毫秒 | 77 毫秒 |
| 混合檢索 | 40.2% | 74.4% | 0.546 | 706 毫秒 | 1,870 毫秒 |
混合檢索流程,有 40.2% 的機率能把「與已公開標籤相符」的申訴,排在第一位(Hit@1);有 74.4% 的機率,能讓它出現在前五名之內(Hit@5)。
這個數字構成生成模型的硬上限。由於生成模型只能從檢索到的 product–issue 配對中選擇,在其餘 25.6% 的測試案例中,它不可能回傳與已發布標籤一致的結果。這項選擇限制(selection constraint)是一個明確的架構取捨:它能防止模型捏造分類值,卻也使檢索召回率成為生成正確性的前置條件。
混合檢索雖取得略高的 Hit@1、Hit@5 與 MRR,搜尋時間中位數(p50)卻由 59 毫秒升至 706 毫秒。主要瓶頸來自單一行程內的 BM25 評分器:它會依查詢詞逐一掃描父層級文件,p95 也會隨申訴長度增加。這不是稀疏檢索本身的固有限制;在正式評估混合檢索延遲前,應先改用可擴展的倒排索引(inverted index)並重新量測。以目前程式碼而言,單純密集檢索是較合理的預設值。
結構化生成是一項維運整合測試
本研究以一則申訴對 LangChain 生成流程進行端到端整合測試。申訴內容指出,銀行在未事先通知的情況下關閉支票帳戶;檢索流程成功找到相關的支票帳戶與帳戶關閉先例。
ChatOllama 回傳的物件符合 JSON schema,但其中的證據引句並非原始敘述中的精確子字串,因此未通過後續驗證。系統攔截這項輸出、記錄失敗原因,並改為回傳純檢索預測與抽取式證據。
這次測試凸顯一項關鍵差異:型別化生成(typed generation)只能驗證回應結構,無法保證內容正確。額外的子字串與標籤一致性檢查,攔截了單靠 schema 驗證無法發現的錯誤。
原訂的 11 案例比較,因本機 Ollama 呼叫未能在合理時間內結束而中止。這是一項維運測試失敗,因此本文不對生成準確度、摘要品質或生成延遲提出結論。重新執行完整比較前,評估執行器(evaluation runner)至少需要補上:
- 每次請求的逾時(timeout)機制;
- 主動的查詢取消功能;
- 漸進式的結果持久化(邊執行邊寫入磁碟);
- 一套明確定義、具備冪等性(idempotent)的重試策略。
人工評估仍不可省略。子字串檢查只能證明引言確實出現在原文中,無法判斷它是否在語意上支持所選 issue,也不能證明摘要能為分析人員提供可採取行動的決策支援。
5. 這套系統該上線嗎?
不應作為自主申訴分類器上線。 現有證據足以支持受控的分析師輔助試點,但不足以支持正式部署或自動化決策。
不立即自動化的理由很明確:
- 準確度不足:所有受測系統的整體 issue 準確率,都低於 50%。
- 長尾表現偏弱:最佳 issue Macro-F1 僅 0.207,少數類別的表現仍不足以支援自主路由。
- 檢索天花板:25.6% 的測試申訴,其已發布的 product–issue 配對未出現在前五筆檢索結果中。
- 分類體系漂移:測試期間出現訓練索引中未曾觀察到的分類配對,顯示系統會面對分布外(out-of-distribution)資料。
- 生成品質尚未驗證:生成品質尚未完成一次可靠的多案例評估,評估執行器也還沒有經過驗證的逾時與復原機制。
- ROI 尚未量測:摘要有用程度、證據支持力、處理時間下降幅度、修正成本等關鍵商業指標,都還沒有在真實分析人員身上量測過。
- 選樣偏誤:建模母體不包含未公開敘述文字的申訴,因此尚未涵蓋完整的申訴進件流程。
若忽略這些差距便直接上線,就會把可供檢視的原型,誤當成已完成驗證的正式作業流程。
6. 策略建議與下一步
架構轉向:組合式模式
這次實驗指向一套比全面採用 RAG 更務實的架構:監督式分類器成本低,並取得最高的整體 issue 準確率;密集檢索改善 product 準確率並提供歷史先例;結構化生成則負責摘要與逐字證據。
下一輪迭代應測試組合式流程,讓各元件承擔它最擅長的工作:
- 監督式分類器:高速計算出大致的 product 與 issue 機率分布。
- 密集檢索:找出歷史先例,並可選擇性地依分類器的機率結果做過濾或加權。
- LLM 生成:限定只負責摘要與精確證據擷取,而不直接預測不受限制的營運標籤。
- 人工分析人員:審核這份組合式報告,決定接受、修正,或駁回系統建議的分流結果。
只有當分析人員確認 BM25 找到的特定先例比密集檢索更有實務價值時,這條分支才值得保留。目前它僅帶來小幅 MRR 增益,卻使檢索延遲中位數增加約 647 毫秒。最佳化的倒排索引可能改變這項比較,因此應先完成最佳化與重新量測,再決定是否保留。
接下來的路:影子試點與商業驗證
商業價值必須透過量測建立,而不能依賴假設。下一階段應先讓系統在既有人工流程旁靜默運作,也就是進行影子試點,以建立處理時間、標籤一致率、重新分派頻率與修正成本的基準。完成影子驗證後,再進入範圍有限的可見試點,將符合條件的申訴分配至傳統介面或 AI 輔助介面,並保留人工的最終決定權。
要全面推廣上線,必須在四道維運關卡上,都拿出扎實的證據:
- 品質:依 product 與 issue 分別衡量的錯誤嚴重程度可被接受、拒答率經過可靠校準,而且在長尾類別上,沒有任何無法接受的效能倒退。
- 人員效用:分析人員會主動接受生成出的摘要與證據,或者只需做輕微修改,並因此帶來可量測的處理時間下降,以及下游重新分派案件的減少。
- 維運:延遲、吞吐量、故障復原、索引更新流程、存取控管與稽核紀錄,均符合事先議定的服務水準目標(SLO)。
- 經濟效益:實際量測到的維運節省,確實且明顯超過運算、基礎架構、人工審核、維護,以及預期錯誤成本的總和。
最終財務可行性可透過以下年度價值模型評估:
公式中的輸入值都應來自試點結果。若直接代入推測的薪資、申訴量或時間節省幅度,得到的只是未經驗證的商業假設,而不是可信的投資評估。