August 13, 2026 · 22 min read
Retrieval-Augmented Generation (RAG) has become the dominant architecture for building AI applications that need to reason over private or domain-specific data. This guide shows you how to implement production-grade RAG using Chinese AI models via TokenEase — covering embedding models, vector databases, chunking strategies, and retrieval optimization.
Chinese AI providers now offer embedding and reranking models that match or exceed OpenAI's performance at a fraction of the cost:
| Model | Dimensions | Context | Strength | Price (per 1M tokens) |
|---|---|---|---|---|
| text-embedding-3-large | 3072 | 8192 | General | $0.13 |
| DeepSeek Embedding | 1024 | 4096 | Code+Text | $0.007 |
| BGE-large-zh | 1024 | 512 | Chinese | Free (self-hosted) |
| Doubao Embedding | 1024 | 4096 | Latency | $0.002 |
| GLM-4 Embedding | 1024 | 8192 | Long context | $0.005 |
A production RAG system has four main components:
Chunking is the most impactful decision in RAG. Poor chunks = poor retrieval.
# Optimal chunking for different document types
import tiktoken
def chunk_document(text, chunk_size=512, overlap=50):
"""Semantic chunking with sentence boundaries"""
tokenizer = tiktoken.get_encoding("cl100k_base")
sentences = text.replace(". ", ".
").split("
")
chunks = []
current_chunk = []
current_tokens = 0
for sentence in sentences:
tokens = len(tokenizer.encode(sentence))
if current_tokens + tokens > chunk_size and current_chunk:
chunks.append(" ".join(current_chunk))
# Keep overlap sentences
overlap_text = " ".join(current_chunk[-2:])
current_chunk = [overlap_text, sentence]
current_tokens = len(tokenizer.encode(overlap_text)) + tokens
else:
current_chunk.append(sentence)
current_tokens += tokens
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
# Chunk size recommendations:
# - FAQ / Q&A: 128-256 tokens (precise matches)
# - Technical docs: 512 tokens (standard)
# - Legal contracts: 1024 tokens (context-dependent)
# - Code: 256 tokens (function-level)
parent_document_retrieval — store small chunks for search but retrieve the full parent document for the LLM context window. This gives precise retrieval with rich generation context.
import requests
import json
TOKEN = "your_tokenease_api_key"
BASE_URL = "https://tokenease.io/v1"
def get_embedding(text, model="deepseek-embedding"):
"""Generate embeddings through TokenEase unified API"""
response = requests.post(
f"{BASE_URL}/embeddings",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"model": model,
"input": text,
"encoding_format": "float"
}
)
return response.json()["data"][0]["embedding"]
# Batch embedding for efficiency
def get_embeddings_batch(texts, model="deepseek-embedding"):
response = requests.post(
f"{BASE_URL}/embeddings",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"model": model,
"input": texts,
"encoding_format": "float"
}
)
return [d["embedding"] for d in response.json()["data"]]
# Example: Embed 1000 chunks
chunks = chunk_document(long_document)
embeddings = get_embeddings_batch(chunks[:100]) # Batch in 100s
We recommend Milvus or Chroma for production. Here's a Milvus setup:
# docker-compose.yml for Milvus
version: '3.5'
services:
etcd:
image: quay.io/coreos/etcd:v3.5.5
minio:
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
standalone:
image: milvusdb/milvus:v2.4.0
ports:
- "19530:19530"
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
# Python client for vector operations
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection
connections.connect("default", host="localhost", port="19530")
# Define collection schema for 1024-dim DeepSeek embeddings
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="chunk_text", dtype=DataType.VARCHAR, max_length=65535),
FieldSchema(name="source_doc", dtype=DataType.VARCHAR, max_length=512),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024)
]
schema = CollectionSchema(fields, "RAG Knowledge Base")
collection = Collection("kb_collection", schema)
# Create IVF_FLAT index for fast search
index_params = {
"metric_type": "COSINE",
"index_type": "IVF_FLAT",
"params": {"nlist": 128}
}
collection.create_index("embedding", index_params)
collection.load()
# Insert documents
entities = [
chunks, # chunk_text
["doc1.pdf"] * len(chunks), # source_doc
embeddings # embedding vectors
]
collection.insert(entities)
Two-stage retrieval (vector search + cross-encoder rerank) significantly improves accuracy:
def retrieve_with_rerank(query, collection, top_k=10, final_k=5):
# Stage 1: Vector similarity search
query_emb = get_embedding(query)
search_params = {"metric_type": "COSINE", "params": {"nprobe": 16}}
results = collection.search(
data=[query_emb],
anns_field="embedding",
param=search_params,
limit=top_k,
output_fields=["chunk_text", "source_doc"]
)
candidates = []
for hit in results[0]:
candidates.append({
"text": hit.entity.get("chunk_text"),
"source": hit.entity.get("source_doc"),
"score": hit.score
})
# Stage 2: Cross-encoder reranking (using GLM-4 via TokenEase)
rerank_prompts = [
f"Query: {query}\nDocument: {c['text']}\nRelevance (0-10):"
for c in candidates
]
rerank_scores = batch_score_relevance(rerank_prompts)
for i, score in enumerate(rerank_scores):
candidates[i]["rerank_score"] = score
# Sort by rerank score and return top final_k
candidates.sort(key=lambda x: x["rerank_score"], reverse=True)
return candidates[:final_k]
def batch_score_relevance(prompts):
"""Use GLM-4 to score relevance"""
scores = []
for prompt in prompts:
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"model": "glm-4",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 5,
"temperature": 0.1
}
)
try:
score = float(response.json()["choices"][0]["message"]["content"])
scores.append(min(max(score, 0), 10))
except:
scores.append(5.0)
return scores
def generate_answer(query, retrieved_chunks):
context = "\n\n".join([
f"[Document {i+1}] {chunk['text']}"
for i, chunk in enumerate(retrieved_chunks)
])
system_prompt = """You are a helpful assistant. Answer the user's question based ONLY on the provided documents.
If the answer is not in the documents, say "I don't have enough information to answer that."
Always cite the document number(s) you used."""
user_prompt = f"""Documents:
{context}
Question: {query}
Answer:"""
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"model": "deepseek-v4",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"temperature": 0.3,
"max_tokens": 1024
}
)
return response.json()["choices"][0]["message"]["content"]
# Full RAG pipeline
query = "What are the refund policies for enterprise accounts?"
chunks = retrieve_with_rerank(query, collection)
answer = generate_answer(query, chunks)
print(answer)
Combine vector similarity with keyword matching for better recall:
# BM25 + Vector hybrid search
from rank_bm25 import BM25Okapi
def hybrid_search(query, collection, documents, alpha=0.7):
# Vector scores
vector_results = retrieve_with_rerank(query, collection, top_k=50)
# BM25 scores
tokenized_docs = [doc.split() for doc in documents]
bm25 = BM25Okapi(tokenized_docs)
bm25_scores = bm25.get_scores(query.split())
# Normalize and combine
vector_scores = {r["text"]: r["score"] for r in vector_results}
combined = []
for i, doc in enumerate(documents):
v_score = vector_scores.get(doc, 0)
b_score = bm25_scores[i] / max(bm25_scores) if max(bm25_scores) > 0 else 0
final = alpha * v_score + (1 - alpha) * b_score
combined.append((doc, final))
combined.sort(key=lambda x: x[1], reverse=True)
return combined[:10]
| Technique | Latency Impact | Quality Impact |
|---|---|---|
| IVF index (nlist=128) | -60% | -2% recall |
| HNSW index | -75% | No loss |
| Quantization (FP16) | -40% memory | -1% accuracy |
| Caching frequent queries | -90% (cache hit) | No loss |
| Batch embedding | -70% per doc | No loss |
# Key metrics to track
metrics = {
"retrieval_latency_p99": "< 100ms",
"retrieval_recall@5": "> 85%",
"answer_relevance_score": "> 4.0 / 5.0",
"hallucination_rate": "< 5%",
"cost_per_query": "< $0.001"
}
# Simple evaluation
def evaluate_rag(test_queries, ground_truth, collection):
correct = 0
for query, expected in zip(test_queries, ground_truth):
chunks = retrieve_with_rerank(query, collection, final_k=5)
retrieved_text = " ".join([c["text"] for c in chunks])
if expected.lower() in retrieved_text.lower():
correct += 1
recall = correct / len(test_queries)
print(f"Recall@5: {recall:.2%}")
return recall
Processing 1 million documents through the full RAG pipeline:
Equivalent OpenAI pipeline: ~$15,000 embedding + $0.003/query = 3-5× more expensive.
Ready to build your RAG system? Get your TokenEase API key and start with our migration guide. For production deployments, consider adding query classification (route simple questions to cached answers) and user feedback loops to continuously improve retrieval quality.
Last updated: August 2026. Embedding prices and model capabilities subject to change.