Fixed 300-token windows shred functions mid-signature. Pure markdown splitters worship READMEs and ignore the call graph. Code RAG on Bedrock Knowledge Bases only works when chunking respects symbols: one coherent unit per function/class (plus a thin caller hint), metadata for repo/language/path, and enough overlap that cross-file types still retrieve. This is the senior playbook for chunking that returns the right function — not a random docstring.
⚡ TL;DR: Prefer AST-aware chunks (tree-sitter / ts-morph) sized to symbols; fall back to semantic splits for prose docs; use fixed-size only for logs. Attach metadata (
repo,path,symbol,language). Keep parent headers in every child chunk. Evaluate with hit-rate@k on real developer questions. Deep dive companions: RAG OpenSearch vs pgvector, Bedrock Agents guardrails.
Fixed-size: when it still wins
Fixed windows are fine for homogeneous prose and terrible for TypeScript services.
# ❌ Fixed-size on source — splits mid-function
def fixed_chunks(text: str, size: int = 800, overlap: int = 100):
out = []
i = 0
while i < len(text):
out.append(text[i : i + size])
i += size - overlap
return out
# ✅ Fixed-size only for runbooks / plain ADRs
def fixed_for_prose(path: str, text: str):
if path.endswith((".md", ".txt")) and "/src/" not in path:
return fixed_chunks(text, size=1200, overlap=150)
raise ValueError("use_ast_chunker_for_code")
Semantic chunking for docs
Embed consecutive sentences; cut when cosine similarity drops. Good for ADRs and wiki pages feeding the same KB.
import numpy as np
def semantic_breaks(sentence_embeddings: list[np.ndarray], threshold: float = 0.55):
breaks = [0]
for i in range(1, len(sentence_embeddings)):
sim = float(np.dot(sentence_embeddings[i - 1], sentence_embeddings[i]))
if sim < threshold:
breaks.append(i)
breaks.append(len(sentence_embeddings))
return breaks # slice sentences[breaks[j]:breaks[j+1]]
AST-aware chunking for code (the default)
Parse with tree-sitter or ts-morph; emit one chunk per exported function/class with signature, body, and a one-line file path header.
// chunk-ts.ts — illustrative ts-morph chunker
import { Project } from "ts-morph";
export function chunkTypeScript(filePath: string, source: string) {
const project = new Project({ useInMemoryFileSystem: true });
const sf = project.createSourceFile(filePath, source);
const chunks: { id: string; text: string; metadata: Record<string, string> }[] = [];
for (const fn of sf.getFunctions()) {
if (!fn.isExported()) continue;
const name = fn.getName() ?? "anonymous";
const start = fn.getStartLineNumber();
const text = `// file: ${filePath}\n// symbol: ${name}\n${fn.getText()}`;
// ✅ Symbol-bounded chunk with path breadcrumb
chunks.push({
id: `${filePath}::${name}`,
text,
metadata: { path: filePath, symbol: name, language: "typescript", start: String(start) },
});
}
// ❌ Don’t embed import-only noise as its own retrieval unit
return chunks.filter((c) => c.text.split("\n").length > 3);
}
Bedrock KB ingestion knobs that matter
When using Bedrock Knowledge Bases, set chunking strategy explicitly for each data source. For code, prefer ingesting pre-chunked objects (one S3 object or one document per symbol) so the service does not re-split your careful AST units. Put metadata attributes Bedrock can filter later (repo, language, team).
{
"chunkingStrategy": "NONE",
"comment": "Pre-chunked AST units already in S3; do not re-window"
}
# ✅ Upload one JSON/text object per symbol with metadata sidecar
aws s3 cp chunks/auth/getSession.json s3://kb-code/auth/getSession.json \
--metadata language=typescript,repo=platform,symbol=getSession
Compare retrieval backends and when pgvector beats OpenSearch in RAG on AWS. Cache hot system prompts with Bedrock Prompt Caching.
Evaluate chunking, don’t argue about it
| Metric | Target (illustrative) |
|---|---|
| Hit-rate@5 on golden Qs | ≥ 0.85 for symbol questions |
| Exact-symbol recall | ≥ 0.9 when query names the function |
| Doc-only questions | Prefer semantic chunks, not AST |
| Avg chunk tokens | 200–800 for code; avoid 50-token crumbs |
def hit_at_k(retrieved_ids: list[str], gold_ids: set[str], k: int = 5) -> float:
return 1.0 if gold_ids.intersection(retrieved_ids[:k]) else 0.0
Closing checklist
✅ Dos
– ✅ AST-chunk code; semantic-chunk prose; fixed-size only for logs
– ✅ Prefix every chunk with file: + symbol:
– ✅ Use chunkingStrategy: NONE when pre-chunked
– ✅ Attach filterable metadata (repo, language, team)
– ✅ Measure hit-rate@k on real engineer questions
❌ Don’ts
– ❌ Don’t fixed-window TypeScript services
– ❌ Don’t index node_modules or generated clients
– ❌ Don’t let KB re-split careful AST units
– ❌ Don’t omit path breadcrumbs
– ❌ Don’t tune chunk size by vibes alone
Related reading
- RAG on AWS: OpenSearch vs Aurora pgvector for Codebase Chat
- Bedrock Prompt Caching and Batch Inference: Cut Latency and Cost
- Amazon Bedrock Agents: Tool Use, Memory, and Production Guardrails
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: OpenSearch Neural Pipelines: Embed API Docs on Write, Hybrid Rank - CheatCoders