[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fKb0GCgvXuwsx-eoMOdWFQeNipWQfiVZh5lUMwTwd-7w":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":16,"$fR6XOgtWntStRgNTLwbMU4FencKTKB7CHS3vTGNSFwXw":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":15},"rag-system-design","RAG 系统设计：从 naive 到 production-ready","Retrieval-Augmented Generation 不只是「向量数据库 + LLM」，分块策略、召回质量、重排序、缓存才是工程核心。","# RAG 系统设计：从 naive 到 production-ready\n\n> RAG 的入门门槛很低，但要做好很难。大多数教程止步于\"向量数据库 + LLM\"，而生产级 RAG 的核心是分块策略、召回质量和重排序。\n\n## 为什么需要 RAG\n\nLLM 存在三个核心局限：\n\n1. **知识截止**：训练数据有时间限制，无法获取最新信息\n2. **私有数据**：企业内部文档、代码库、知识库 LLM 没见过\n3. **幻觉问题**：无相关知识时模型会\"编\"答案\n\nRAG（Retrieval-Augmented Generation）通过在推理时动态检索相关文档，将这些内容注入到上下文中，让模型基于事实回答。\n\n## RAG 全链路\n\n```\n文档 -> [处理] -> [分块] -> [Embedding] -> [向量数据库]\n                                              |\n用户问题 -> [查询改写] -> [检索] -> [重排序] -> [LLM生成] -> 回答\n```\n\n### 阶段一：文档处理\n\n```python\nfrom langchain_community.document_loaders import PyPDFLoader\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\nimport re\n\ndef clean_document(text: str) -> str:\n    text = re.sub(r'\\n{3,}', '\\n\\n', text)\n    text = re.sub(r'- \\d+ -', '', text)\n    return text.strip()\n```\n\n### 阶段二：分块策略\n\n分块是 RAG 质量的关键决策，没有银弹。\n\n**固定大小分块（Fixed-size）：**\n```python\nsplitter = RecursiveCharacterTextSplitter(\n    chunk_size=512,       # token 数\n    chunk_overlap=64,     # 重叠区域\n    separators=[\"\\n\\n\", \"\\n\", \"。\", \".\", \" \", \"\"]\n)\nchunks = splitter.split_documents(docs)\n```\n\n**语义分块（Semantic Chunking）：**\n```python\nfrom langchain_experimental.text_splitter import SemanticChunker\nfrom langchain_openai import OpenAIEmbeddings\n\nsplitter = SemanticChunker(\n    OpenAIEmbeddings(),\n    breakpoint_threshold_type=\"percentile\",\n    breakpoint_threshold_amount=95\n)\n```\n\n**层级分块（Hierarchical）：**\n```\n文档\n├── 章节摘要（大块，用于召回范围定位）\n│   ├── 段落（中块，主要检索单元）\n│   │   └── 句子（小块，精确引用）\n```\n\n**chunk_overlap 的作用：**\n- 防止关键信息被切割在两个 chunk 的边界\n- 通常设为 chunk_size 的 10-20%\n- 代价是存储增加和潜在重复检索\n\n**选择建议：**\n| 场景 | 推荐方案 |\n|------|---------|\n| 通用文档 | RecursiveCharacterTextSplitter，512 tokens，overlap 64 |\n| 代码 | 按函数\u002F类分割，保留完整语义单元 |\n| 结构化文档（FAQ）| 按问答对分割 |\n| 多语言 | 字符级分割而非 token 级 |\n\n### 阶段三：Embedding 模型选择\n\n```python\n# OpenAI text-embedding-3-small（性价比高）\nfrom openai import OpenAI\nclient = OpenAI()\nresponse = client.embeddings.create(\n    model=\"text-embedding-3-small\",  # 1536 维，$0.02\u002F1M tokens\n    input=[\"your text here\"]\n)\nembedding = response.data[0].embedding\n\n# BGE（BAAI，开源，中文效果好）\nfrom sentence_transformers import SentenceTransformer\nmodel = SentenceTransformer('BAAI\u002Fbge-large-zh-v1.5')\nembedding = model.encode(\"你的文本\", normalize_embeddings=True)\n```\n\n| 模型 | 维度 | 特点 |\n|------|------|------|\n| text-embedding-3-small | 1536 | 便宜，效果好 |\n| text-embedding-3-large | 3072 | 更准确，贵 3x |\n| BGE-large-zh | 1024 | 中文最强开源 |\n| E5-mistral-7b | 4096 | 英文 SOTA，本地运行 |\n\n### 阶段四：向量数据库\n\n```python\n# Qdrant（推荐，功能强，开源可自部署）\nfrom qdrant_client import QdrantClient\nfrom qdrant_client.models import Distance, VectorParams, PointStruct\n\nclient = QdrantClient(url=\"http:\u002F\u002Flocalhost:6333\")\nclient.create_collection(\n    collection_name=\"docs\",\n    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)\n)\n\npoints = [\n    PointStruct(id=i, vector=emb, payload={\"text\": chunk, \"source\": \"doc.pdf\"})\n    for i, (emb, chunk) in enumerate(zip(embeddings, chunks))\n]\nclient.upsert(collection_name=\"docs\", points=points)\n```\n\n```sql\n-- pgvector（已有 PostgreSQL 的首选）\nCREATE EXTENSION vector;\nCREATE TABLE embeddings (\n    id SERIAL PRIMARY KEY,\n    content TEXT,\n    embedding vector(1536),\n    metadata JSONB\n);\nCREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops);\n```\n\n| 数据库 | 适合场景 |\n|--------|---------|\n| Qdrant | 生产部署，功能丰富 |\n| pgvector | 已用 PostgreSQL，简化架构 |\n| Chroma | 本地开发，简单易用 |\n| Pinecone | 全托管，无运维 |\n| Weaviate | 需要混合检索（向量+BM25） |\n\n### 阶段五：检索策略\n\n**稠密检索（Dense Retrieval）：**\n```python\nquery_embedding = embed(user_query)\nresults = qdrant_client.search(\n    collection_name=\"docs\",\n    query_vector=query_embedding,\n    limit=10\n)\n```\n\n**稀疏检索（BM25）：**\n```python\nfrom rank_bm25 import BM25Okapi\n\ncorpus = [chunk.split() for chunk in all_chunks]\nbm25 = BM25Okapi(corpus)\nscores = bm25.get_scores(query.split())\n```\n\n**混合检索（Hybrid Search）：**\n```python\ndef hybrid_search(query: str, alpha: float = 0.5):\n    \"\"\"alpha=1: 纯向量, alpha=0: 纯BM25\"\"\"\n    dense_results = dense_search(query, top_k=20)\n    sparse_results = bm25_search(query, top_k=20)\n    \n    # RRF (Reciprocal Rank Fusion) 融合\n    scores = {}\n    k = 60\n    for rank, doc in enumerate(dense_results):\n        scores[doc.id] = scores.get(doc.id, 0) + alpha \u002F (rank + k)\n    for rank, doc in enumerate(sparse_results):\n        scores[doc.id] = scores.get(doc.id, 0) + (1-alpha) \u002F (rank + k)\n    \n    return sorted(scores.items(), key=lambda x: x[1], reverse=True)\n```\n\n### 阶段六：重排序（Reranker）\n\n```python\nfrom sentence_transformers import CrossEncoder\n\nreranker = CrossEncoder('BAAI\u002Fbge-reranker-large')\n\ndef rerank(query: str, candidates: list, top_k: int = 3):\n    pairs = [(query, doc) for doc in candidates]\n    scores = reranker.predict(pairs)\n    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)\n    return [doc for doc, _ in ranked[:top_k]]\n```\n\n**为什么需要重排序：**\n- 向量检索：快，但精度受限于 embedding 质量\n- Cross-encoder：慢（O(n)），但精度高\n- 典型做法：向量检索 top-50，cross-encoder 重排取 top-3\n\n### 阶段七：LLM 生成\n\n```python\ndef generate_answer(query: str, contexts: list) -> str:\n    context_str = \"\\n\\n---\\n\\n\".join(contexts)\n    \n    prompt = f\"\"\"基于以下参考资料回答问题。如果参考资料中没有相关信息，\n请直接说明，不要编造内容。\n\n参考资料：\n{context_str}\n\n问题：{query}\n\n回答：\"\"\"\n    \n    response = openai_client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        temperature=0.1\n    )\n    return response.choices[0].message.content\n```\n\n## 评估指标\n\n```python\n# RAGAS 框架（专为 RAG 评估设计）\nfrom ragas import evaluate\nfrom ragas.metrics import faithfulness, answer_relevancy, context_recall, context_precision\nfrom datasets import Dataset\n\ndata = {\n    \"question\": [\"什么是 RAG？\"],\n    \"answer\": [\"RAG 是检索增强生成...\"],\n    \"contexts\": [[\"RAG 全称 Retrieval-Augmented Generation...\"]],\n    \"ground_truth\": [\"RAG 是一种结合检索和生成的 AI 技术\"]\n}\n\nresult = evaluate(Dataset.from_dict(data), metrics=[\n    faithfulness, answer_relevancy, context_recall, context_precision\n])\n```\n\n**关键指标：**\n- **Recall@K**：Ground truth 文档在 top-K 检索结果中出现的比例\n- **MRR（Mean Reciprocal Rank）**：首个相关文档排名的倒数均值\n- **Faithfulness**：答案与检索到的上下文的一致性\n\n## 常见工程坑\n\n**1. 噪声文档污染：**\n```python\ndef quality_filter(doc: str) -> bool:\n    if len(doc) \u003C 100:\n        return False\n    if doc.count('\\n') \u002F len(doc) > 0.3:\n        return False\n    return True\n```\n\n**2. 召回与上下文窗口冲突：**\n```python\nimport tiktoken\nenc = tiktoken.get_encoding(\"cl100k_base\")\n\ndef fit_to_context(docs: list, max_tokens: int = 3000) -> list:\n    result, total = [], 0\n    for doc in docs:\n        tokens = len(enc.encode(doc))\n        if total + tokens > max_tokens:\n            break\n        result.append(doc)\n        total += tokens\n    return result\n```\n\n**3. Embedding 缓存：**\n```python\nimport hashlib, redis, json\n\ncache = redis.Redis()\n\ndef cached_embed(text: str) -> list:\n    key = f\"embed:{hashlib.md5(text.encode()).hexdigest()}\"\n    cached = cache.get(key)\n    if cached:\n        return json.loads(cached)\n    embedding = embed_api(text)\n    cache.setex(key, 86400, json.dumps(embedding))  # 24h TTL\n    return embedding\n```\n\n## 生产级优化\n\n**查询改写（Query Rewriting）：**\n```python\ndef rewrite_query(original_query: str) -> list:\n    prompt = f\"\"\"为以下问题生成 3 个不同的搜索查询，覆盖不同表述方式：\n问题：{original_query}\n输出 JSON 数组，每个元素是一个查询字符串。\"\"\"\n    variants = llm.json_complete(prompt)\n    return [original_query] + variants\n```\n\n**HyDE（Hypothetical Document Embedding）：**\n```python\ndef hyde_search(query: str) -> list:\n    hypothetical = llm.complete(\n        f\"写一段简短的文字来回答：{query}（即使你不确定，也要给出合理的内容）\"\n    )\n    hyde_embedding = embed(hypothetical)\n    return vector_search(hyde_embedding, top_k=10)\n```\n\n**多路召回合并：**\n```python\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\ndef multi_recall(query: str) -> list:\n    results = []\n    with ThreadPoolExecutor() as executor:\n        futures = {\n            executor.submit(dense_search, query): \"dense\",\n            executor.submit(bm25_search, query): \"bm25\",\n            executor.submit(hyde_search, query): \"hyde\",\n        }\n        for future in as_completed(futures):\n            results.append((futures[future], future.result()))\n    return rrf_fusion(results)\n```\n\n## 架构总结\n\n```\n                    +-----------------+\n用户问题 ---------->|   查询改写       |\n                    +--------+--------+\n                             |  多个变体\n              +--------------+--------------+\n              v              v              v\n         稠密检索          BM25           HyDE\n              +--------------+--------------+\n                             | RRF 融合 top-50\n                             v\n                    +-----------------+\n                    | Cross-encoder   | 重排序 top-3\n                    |   Reranker      |\n                    +--------+--------+\n                             |\n                             v\n                    +-----------------+\n                    |   LLM 生成       | temperature=0.1\n                    +--------+--------+\n                             |\n                          最终答案\n```\n\nRAG 的核心不是技术选型，而是**数据质量 + 评估体系**。没有好的评估，你不知道改动是否真的有效。\n","\u003Ch1>RAG 系统设计：从 naive 到 production-ready\u003C\u002Fh1>\n\u003Cblockquote>\n\u003Cp>RAG 的入门门槛很低，但要做好很难。大多数教程止步于&quot;向量数据库 + LLM&quot;，而生产级 RAG 的核心是分块策略、召回质量和重排序。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2 id=\"为什么需要-rag\">为什么需要 RAG\u003C\u002Fh2>\n\u003Cp>LLM 存在三个核心局限：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>知识截止\u003C\u002Fstrong>：训练数据有时间限制，无法获取最新信息\u003C\u002Fli>\n\u003Cli>\u003Cstrong>私有数据\u003C\u002Fstrong>：企业内部文档、代码库、知识库 LLM 没见过\u003C\u002Fli>\n\u003Cli>\u003Cstrong>幻觉问题\u003C\u002Fstrong>：无相关知识时模型会&quot;编&quot;答案\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>RAG（Retrieval-Augmented Generation）通过在推理时动态检索相关文档，将这些内容注入到上下文中，让模型基于事实回答。\u003C\u002Fp>\n\u003Ch2 id=\"rag-全链路\">RAG 全链路\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>文档 -&gt; [处理] -&gt; [分块] -&gt; [Embedding] -&gt; [向量数据库]\n                                              |\n用户问题 -&gt; [查询改写] -&gt; [检索] -&gt; [重排序] -&gt; [LLM生成] -&gt; 回答\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"阶段一-文档处理\">阶段一：文档处理\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\">from langchain_community.document_loaders import PyPDFLoader\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\nimport re\n\ndef clean_document(text: str) -&gt; str:\n    text = re.sub(r'\\n{3,}', '\\n\\n', text)\n    text = re.sub(r'- \\d+ -', '', text)\n    return text.strip()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"阶段二-分块策略\">阶段二：分块策略\u003C\u002Fh3>\n\u003Cp>分块是 RAG 质量的关键决策，没有银弹。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>固定大小分块（Fixed-size）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">splitter = RecursiveCharacterTextSplitter(\n    chunk_size=512,       # token 数\n    chunk_overlap=64,     # 重叠区域\n    separators=[&quot;\\n\\n&quot;, &quot;\\n&quot;, &quot;。&quot;, &quot;.&quot;, &quot; &quot;, &quot;&quot;]\n)\nchunks = splitter.split_documents(docs)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>语义分块（Semantic Chunking）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">from langchain_experimental.text_splitter import SemanticChunker\nfrom langchain_openai import OpenAIEmbeddings\n\nsplitter = SemanticChunker(\n    OpenAIEmbeddings(),\n    breakpoint_threshold_type=&quot;percentile&quot;,\n    breakpoint_threshold_amount=95\n)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>层级分块（Hierarchical）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>文档\n├── 章节摘要（大块，用于召回范围定位）\n│   ├── 段落（中块，主要检索单元）\n│   │   └── 句子（小块，精确引用）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>chunk_overlap 的作用：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>防止关键信息被切割在两个 chunk 的边界\u003C\u002Fli>\n\u003Cli>通常设为 chunk_size 的 10-20%\u003C\u002Fli>\n\u003Cli>代价是存储增加和潜在重复检索\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>选择建议：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>场景\u003C\u002Fth>\n\u003Cth>推荐方案\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>通用文档\u003C\u002Ftd>\n\u003Ctd>RecursiveCharacterTextSplitter，512 tokens，overlap 64\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>代码\u003C\u002Ftd>\n\u003Ctd>按函数\u002F类分割，保留完整语义单元\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>结构化文档（FAQ）\u003C\u002Ftd>\n\u003Ctd>按问答对分割\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>多语言\u003C\u002Ftd>\n\u003Ctd>字符级分割而非 token 级\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 id=\"阶段三-embedding-模型选择\">阶段三：Embedding 模型选择\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\"># OpenAI text-embedding-3-small（性价比高）\nfrom openai import OpenAI\nclient = OpenAI()\nresponse = client.embeddings.create(\n    model=&quot;text-embedding-3-small&quot;,  # 1536 维，$0.02\u002F1M tokens\n    input=[&quot;your text here&quot;]\n)\nembedding = response.data[0].embedding\n\n# BGE（BAAI，开源，中文效果好）\nfrom sentence_transformers import SentenceTransformer\nmodel = SentenceTransformer('BAAI\u002Fbge-large-zh-v1.5')\nembedding = model.encode(&quot;你的文本&quot;, normalize_embeddings=True)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>模型\u003C\u002Fth>\n\u003Cth>维度\u003C\u002Fth>\n\u003Cth>特点\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>text-embedding-3-small\u003C\u002Ftd>\n\u003Ctd>1536\u003C\u002Ftd>\n\u003Ctd>便宜，效果好\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>text-embedding-3-large\u003C\u002Ftd>\n\u003Ctd>3072\u003C\u002Ftd>\n\u003Ctd>更准确，贵 3x\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>BGE-large-zh\u003C\u002Ftd>\n\u003Ctd>1024\u003C\u002Ftd>\n\u003Ctd>中文最强开源\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>E5-mistral-7b\u003C\u002Ftd>\n\u003Ctd>4096\u003C\u002Ftd>\n\u003Ctd>英文 SOTA，本地运行\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 id=\"阶段四-向量数据库\">阶段四：向量数据库\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\"># Qdrant（推荐，功能强，开源可自部署）\nfrom qdrant_client import QdrantClient\nfrom qdrant_client.models import Distance, VectorParams, PointStruct\n\nclient = QdrantClient(url=&quot;http:\u002F\u002Flocalhost:6333&quot;)\nclient.create_collection(\n    collection_name=&quot;docs&quot;,\n    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)\n)\n\npoints = [\n    PointStruct(id=i, vector=emb, payload={&quot;text&quot;: chunk, &quot;source&quot;: &quot;doc.pdf&quot;})\n    for i, (emb, chunk) in enumerate(zip(embeddings, chunks))\n]\nclient.upsert(collection_name=&quot;docs&quot;, points=points)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- pgvector（已有 PostgreSQL 的首选）\nCREATE EXTENSION vector;\nCREATE TABLE embeddings (\n    id SERIAL PRIMARY KEY,\n    content TEXT,\n    embedding vector(1536),\n    metadata JSONB\n);\nCREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>数据库\u003C\u002Fth>\n\u003Cth>适合场景\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Qdrant\u003C\u002Ftd>\n\u003Ctd>生产部署，功能丰富\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>pgvector\u003C\u002Ftd>\n\u003Ctd>已用 PostgreSQL，简化架构\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Chroma\u003C\u002Ftd>\n\u003Ctd>本地开发，简单易用\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Pinecone\u003C\u002Ftd>\n\u003Ctd>全托管，无运维\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Weaviate\u003C\u002Ftd>\n\u003Ctd>需要混合检索（向量+BM25）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 id=\"阶段五-检索策略\">阶段五：检索策略\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>稠密检索（Dense Retrieval）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">query_embedding = embed(user_query)\nresults = qdrant_client.search(\n    collection_name=&quot;docs&quot;,\n    query_vector=query_embedding,\n    limit=10\n)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>稀疏检索（BM25）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">from rank_bm25 import BM25Okapi\n\ncorpus = [chunk.split() for chunk in all_chunks]\nbm25 = BM25Okapi(corpus)\nscores = bm25.get_scores(query.split())\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>混合检索（Hybrid Search）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">def hybrid_search(query: str, alpha: float = 0.5):\n    &quot;&quot;&quot;alpha=1: 纯向量, alpha=0: 纯BM25&quot;&quot;&quot;\n    dense_results = dense_search(query, top_k=20)\n    sparse_results = bm25_search(query, top_k=20)\n    \n    # RRF (Reciprocal Rank Fusion) 融合\n    scores = {}\n    k = 60\n    for rank, doc in enumerate(dense_results):\n        scores[doc.id] = scores.get(doc.id, 0) + alpha \u002F (rank + k)\n    for rank, doc in enumerate(sparse_results):\n        scores[doc.id] = scores.get(doc.id, 0) + (1-alpha) \u002F (rank + k)\n    \n    return sorted(scores.items(), key=lambda x: x[1], reverse=True)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"阶段六-重排序-reranker\">阶段六：重排序（Reranker）\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\">from sentence_transformers import CrossEncoder\n\nreranker = CrossEncoder('BAAI\u002Fbge-reranker-large')\n\ndef rerank(query: str, candidates: list, top_k: int = 3):\n    pairs = [(query, doc) for doc in candidates]\n    scores = reranker.predict(pairs)\n    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)\n    return [doc for doc, _ in ranked[:top_k]]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>为什么需要重排序：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>向量检索：快，但精度受限于 embedding 质量\u003C\u002Fli>\n\u003Cli>Cross-encoder：慢（O(n)），但精度高\u003C\u002Fli>\n\u003Cli>典型做法：向量检索 top-50，cross-encoder 重排取 top-3\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"阶段七-llm-生成\">阶段七：LLM 生成\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\">def generate_answer(query: str, contexts: list) -&gt; str:\n    context_str = &quot;\\n\\n---\\n\\n&quot;.join(contexts)\n    \n    prompt = f&quot;&quot;&quot;基于以下参考资料回答问题。如果参考资料中没有相关信息，\n请直接说明，不要编造内容。\n\n参考资料：\n{context_str}\n\n问题：{query}\n\n回答：&quot;&quot;&quot;\n    \n    response = openai_client.chat.completions.create(\n        model=&quot;gpt-4o&quot;,\n        messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}],\n        temperature=0.1\n    )\n    return response.choices[0].message.content\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"评估指标\">评估指标\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-python\"># RAGAS 框架（专为 RAG 评估设计）\nfrom ragas import evaluate\nfrom ragas.metrics import faithfulness, answer_relevancy, context_recall, context_precision\nfrom datasets import Dataset\n\ndata = {\n    &quot;question&quot;: [&quot;什么是 RAG？&quot;],\n    &quot;answer&quot;: [&quot;RAG 是检索增强生成...&quot;],\n    &quot;contexts&quot;: [[&quot;RAG 全称 Retrieval-Augmented Generation...&quot;]],\n    &quot;ground_truth&quot;: [&quot;RAG 是一种结合检索和生成的 AI 技术&quot;]\n}\n\nresult = evaluate(Dataset.from_dict(data), metrics=[\n    faithfulness, answer_relevancy, context_recall, context_precision\n])\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>关键指标：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Recall@K\u003C\u002Fstrong>：Ground truth 文档在 top-K 检索结果中出现的比例\u003C\u002Fli>\n\u003Cli>\u003Cstrong>MRR（Mean Reciprocal Rank）\u003C\u002Fstrong>：首个相关文档排名的倒数均值\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Faithfulness\u003C\u002Fstrong>：答案与检索到的上下文的一致性\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"常见工程坑\">常见工程坑\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>1. 噪声文档污染：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">def quality_filter(doc: str) -&gt; bool:\n    if len(doc) &lt; 100:\n        return False\n    if doc.count('\\n') \u002F len(doc) &gt; 0.3:\n        return False\n    return True\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>2. 召回与上下文窗口冲突：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">import tiktoken\nenc = tiktoken.get_encoding(&quot;cl100k_base&quot;)\n\ndef fit_to_context(docs: list, max_tokens: int = 3000) -&gt; list:\n    result, total = [], 0\n    for doc in docs:\n        tokens = len(enc.encode(doc))\n        if total + tokens &gt; max_tokens:\n            break\n        result.append(doc)\n        total += tokens\n    return result\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>3. Embedding 缓存：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">import hashlib, redis, json\n\ncache = redis.Redis()\n\ndef cached_embed(text: str) -&gt; list:\n    key = f&quot;embed:{hashlib.md5(text.encode()).hexdigest()}&quot;\n    cached = cache.get(key)\n    if cached:\n        return json.loads(cached)\n    embedding = embed_api(text)\n    cache.setex(key, 86400, json.dumps(embedding))  # 24h TTL\n    return embedding\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"生产级优化\">生产级优化\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>查询改写（Query Rewriting）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">def rewrite_query(original_query: str) -&gt; list:\n    prompt = f&quot;&quot;&quot;为以下问题生成 3 个不同的搜索查询，覆盖不同表述方式：\n问题：{original_query}\n输出 JSON 数组，每个元素是一个查询字符串。&quot;&quot;&quot;\n    variants = llm.json_complete(prompt)\n    return [original_query] + variants\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>HyDE（Hypothetical Document Embedding）：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">def hyde_search(query: str) -&gt; list:\n    hypothetical = llm.complete(\n        f&quot;写一段简短的文字来回答：{query}（即使你不确定，也要给出合理的内容）&quot;\n    )\n    hyde_embedding = embed(hypothetical)\n    return vector_search(hyde_embedding, top_k=10)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>多路召回合并：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">from concurrent.futures import ThreadPoolExecutor, as_completed\n\ndef multi_recall(query: str) -&gt; list:\n    results = []\n    with ThreadPoolExecutor() as executor:\n        futures = {\n            executor.submit(dense_search, query): &quot;dense&quot;,\n            executor.submit(bm25_search, query): &quot;bm25&quot;,\n            executor.submit(hyde_search, query): &quot;hyde&quot;,\n        }\n        for future in as_completed(futures):\n            results.append((futures[future], future.result()))\n    return rrf_fusion(results)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"架构总结\">架构总结\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>                    +-----------------+\n用户问题 ----------&gt;|   查询改写       |\n                    +--------+--------+\n                             |  多个变体\n              +--------------+--------------+\n              v              v              v\n         稠密检索          BM25           HyDE\n              +--------------+--------------+\n                             | RRF 融合 top-50\n                             v\n                    +-----------------+\n                    | Cross-encoder   | 重排序 top-3\n                    |   Reranker      |\n                    +--------+--------+\n                             |\n                             v\n                    +-----------------+\n                    |   LLM 生成       | temperature=0.1\n                    +--------+--------+\n                             |\n                          最终答案\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>RAG 的核心不是技术选型，而是\u003Cstrong>数据质量 + 评估体系\u003C\u002Fstrong>。没有好的评估，你不知道改动是否真的有效。\u003C\u002Fp>\n","2026-05-03",[11,12,13,14],"ai","rag","llm","工程实践",false,[17,30,41,53,59,62,69,76,83,90,100,109,119,128,136,144,153,162,171,181,188,198,204,211,217,226,233,240,248,258,267,276,286,296,306,314,324,335,345,354,362,368,376,384,392,400,408,415],{"slug":18,"title":19,"description":20,"pub_date":21,"tags":22,"draft":15,"word_count":29},"ide-skills-guide","Agent Skills 完全指南：21 款第三方 Skill 深度评测与使用心得","全面评测 21 款第三方 Agent Skills，涵盖 Vue 生态、前端设计、构建工具、实用工具四大分类。从安装配置到实际使用场景，带你了解每个 Skill 的功能特点、最佳实践与使用心得。","2026-06-15",[23,24,25,26,27,28],"agent","skills","AI","效率工具","前端","Vue",4169,{"slug":31,"title":32,"description":33,"pub_date":34,"tags":35,"draft":15,"word_count":40},"linux-kernel-skeleton-struct-funcptr-container_of","Linux 内核骨架：struct、函数指针与 container_of","读懂 Linux 内核源码的三件套：巨大的 struct 组合代替继承、函数指针表实现虚派发、container_of 宏从嵌入成员找回完整对象。","2026-05-09",[36,37,38,39],"linux","kernel","C","container_of",1369,{"slug":42,"title":43,"description":44,"pub_date":45,"tags":46,"draft":15,"word_count":52},"astro-complete-guide-2025","Astro 5 深度剖析：Islands 架构原理、构建优化与 Cloudflare Workers 边缘部署","从编译器视角解析 Astro 5 的 Islands 架构实现原理，Content Layer API 的 Vite 插件机制，Server Islands 的流式渲染，以及如何在 Cloudflare Workers + D1 边缘环境下榨干性能。","2026-05-08",[47,48,49,50,51],"astro","frontend","cloudflare","performance","architecture",3663,{"slug":54,"title":55,"description":56,"pub_date":9,"tags":57,"draft":15,"word_count":58},"llm-prompt-engineering","Prompt Engineering 实战：让 LLM 真正听话的技巧","System prompt 怎么写、Few-shot 怎么设计、Chain-of-Thought 原理，以及常见失败模式和调试方法。",[11,13,14],1723,{"slug":4,"title":5,"description":6,"pub_date":9,"tags":60,"draft":15,"word_count":61},[11,12,13,14],1613,{"slug":63,"title":64,"description":65,"pub_date":9,"tags":66,"draft":15,"word_count":68},"git-advanced-workflow","Git 进阶工作流：rebase、cherry-pick、bisect 的正确使用","merge 会了，但 rebase 总搞错？bisect 找 bug 提交？interactive rebase 整理历史？这篇一次说清楚。",[67,14],"git",1396,{"slug":70,"title":71,"description":72,"pub_date":9,"tags":73,"draft":15,"word_count":75},"docker-practical-guide","Docker 实战：从会用到用好","会 docker run 不够，Dockerfile 最佳实践、多阶段构建、Compose 编排、镜像瘦身才是日常真正需要的。",[74,36,14],"docker",1268,{"slug":77,"title":78,"description":79,"pub_date":9,"tags":80,"draft":15,"word_count":82},"anthropics-skills-guide","anthropics\u002Fskills：Anthropic 官方 Agent Skills 仓库解析","Anthropic 官方开源的 Agent Skills 标准仓库，127k stars，解析 SKILL.md 规范、17 个示例 skill 的设计模式，以及如何在 Claude Code \u002F Claude.ai \u002F API 中使用",[11,81,23,24],"Claude",2090,{"slug":84,"title":85,"description":86,"pub_date":9,"tags":87,"draft":15,"word_count":89},"karpathy-claude-code-guidelines","Karpathy 的 LLM 编码批评与 CLAUDE.md 最佳实践","基于 Andrej Karpathy 对 LLM 编程助手的观察，forrestchang 提炼出一个 CLAUDE.md 文件，4 条原则解决 AI 编码的典型失控问题：乱猜假设、过度设计、乱改代码、目标不清",[11,81,88,14],"Claude Code",2699,{"slug":91,"title":92,"description":93,"pub_date":9,"tags":94,"draft":15,"word_count":99},"typescript-advanced-patterns","TypeScript 高级模式：让类型系统为你工作","基础 TS 会了但类型总是 any？条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。",[95,96,97,98],"typescript","类型系统","前端工程","高级模式",1419,{"slug":101,"title":102,"description":103,"pub_date":9,"tags":104,"draft":15,"word_count":108},"linux-performance-tuning","Linux 性能调优实战：从 top 到 perf 的完整工具链","遇到性能问题不知道从哪下手？这篇建立系统化的排查思路，从 CPU\u002F内存\u002FIO\u002F网络逐层分析。",[36,105,106,107],"性能","运维","系统编程",1524,{"slug":110,"title":111,"description":112,"pub_date":9,"tags":113,"draft":15,"word_count":118},"python-functional-programming","Python 函数式编程：map\u002Ffilter\u002Freduce 之外","Python 不是纯函数式语言，但 functools、itertools、偏函数、闭包这些工具用好了能让代码简洁一个量级。",[114,115,116,117],"python","函数式","闭包","装饰器",1867,{"slug":120,"title":121,"description":122,"pub_date":9,"tags":123,"draft":15,"word_count":127},"python-oop-guide","Python 面向对象：__init__ 之外你需要知道的","Python OOP 不只是 class + __init__，魔术方法、描述符、元类才是真正的武器。",[114,124,125,126],"OOP","面向对象","魔术方法",1792,{"slug":129,"title":130,"description":131,"pub_date":9,"tags":132,"draft":15,"word_count":135},"python-data-structures","Python 内置数据结构深度解析","list、dict、set、tuple 不只是数据容器，搞懂它们的底层实现和时间复杂度，才能写出高性能 Python。",[114,133,105,134],"数据结构","算法",1517,{"slug":137,"title":138,"description":139,"pub_date":9,"tags":140,"draft":15,"word_count":143},"python-basics-quick-start","Python 快速上手：写给有编程基础的人","已经会其他语言，想快速掌握 Python 的语法特性和思维方式，这篇是捷径。",[114,141,142],"入门","基础",1607,{"slug":145,"title":146,"description":147,"pub_date":9,"tags":148,"draft":15,"word_count":152},"python-dataclass-pydantic","Python dataclass vs Pydantic：数据类选型指南","dataclass 是标准库的轻量选择，Pydantic v2 是带验证的重武器，什么时候用哪个，这篇说清楚。",[114,149,150,151],"dataclass","pydantic","数据验证",1323,{"slug":154,"title":155,"description":156,"pub_date":9,"tags":157,"draft":15,"word_count":161},"python-asyncio-practical","Python asyncio 实战：从回调地狱到协程优雅","asyncio 是 Python 异步编程的核心，搞懂 event loop、Task、gather 这些概念才能写出真正高效的异步代码。",[114,158,159,160],"asyncio","并发","网络编程",1258,{"slug":163,"title":164,"description":165,"pub_date":9,"tags":166,"draft":15,"word_count":170},"python-type-hints-guide","Python 类型注解完全指南：从入门到实践","Python 3.5+ 引入类型注解，配合 mypy\u002Fpyright 让 Python 也能享受静态类型检查的好处。",[114,167,168,169],"typescript-style","type-hints","工具链",1102,{"slug":172,"title":173,"description":174,"pub_date":175,"tags":176,"draft":15,"word_count":180},"pwa-install-update-button","PWA 踩坑：为什么安装按钮从来不出现","从 beforeinstallprompt 到 Service Worker waiting，把 PWA 的安装与更新提示真正做对","2026-05-02",[177,178,179],"pwa","javascript","web",1683,{"slug":182,"title":183,"description":184,"pub_date":185,"tags":186,"draft":15,"word_count":187},"openclaw-vs-hermes-agent","OpenClaw vs Hermes Agent：两个本地优先 Agent 的设计差异","OpenClaw（Novita AI）和 Hermes Agent（Nous Research）都是本地运行的个人 AI Agent，但在记忆系统、技能学习、运行环境和模型生态上走了不同的路。深入对比两种架构的核心差异。","2026-05-01",[11,23,13],1679,{"slug":189,"title":190,"description":191,"pub_date":185,"tags":192,"draft":15,"word_count":197},"cpp-random-design-patterns","C++ 设计模式实战：RAII、观察者、工厂","用现代 C++（C++17\u002F20）实现三种高频设计模式：RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。",[193,194,195,196],"cpp","设计模式","c++17","工程",2613,{"slug":199,"title":200,"description":201,"pub_date":185,"tags":202,"draft":15,"word_count":203},"data-structures-fundamentals","数据结构基础：从数组到红黑树","系统梳理常用数据结构的核心原理、时间复杂度和适用场景。数组、链表、栈、队列、哈希表、二叉树、堆、图，每种结构附实现要点和 C++ 代码片段。",[133,134,193,142],3004,{"slug":205,"title":206,"description":207,"pub_date":208,"tags":209,"draft":15,"word_count":210},"ai-agent-what-is","什么是 AI Agent？从 LLM 到自主执行","LLM 本身是无状态问答机，Agent 是什么让它’动’起来的？本文深入解析 Agent 的四个核心能力、ReAct 框架、工具调用原理，以及主流框架横向对比。","2026-04-30",[11,23,13],2116,{"slug":212,"title":213,"description":214,"pub_date":208,"tags":215,"draft":15,"word_count":216},"ai-agent-memory","AI Agent 的记忆系统：从上下文窗口到长期记忆","深入拆解 AI Agent 的四种记忆类型、上下文窗口压缩策略、RAG 向量检索原理，以及三种典型失败模式和工程选型建议。",[11,23,12],2052,{"slug":218,"title":219,"description":220,"pub_date":208,"tags":221,"draft":15,"word_count":225},"network-proxy-vpn-guide","代理与翻墙技术原理：从 HTTP 代理到现代协议","深入解析代理与 VPN 的本质区别，梳理从 SOCKS5 到 Shadowsocks、V2Ray\u002FXray、Hysteria2 的协议演进，以及机场订阅的技术本质。",[222,223,224],"网络","代理","协议",2148,{"slug":227,"title":228,"description":229,"pub_date":208,"tags":230,"draft":15,"word_count":143},"algorithm-binary-search","二分查找：永远写不对？记住这个模板","彻底搞清楚二分查找的边界问题：闭区间和左闭右开两套模板、三道经典 LeetCode 题目完整 C++ 实现，以及二分答案的进阶思路。",[134,231,232,193],"二分查找","leetcode",{"slug":234,"title":235,"description":236,"pub_date":208,"tags":237,"draft":15,"word_count":239},"algorithm-sliding-window","滑动窗口算法：从暴力到 O(n) 的思维跃迁","系统讲解滑动窗口算法的核心模板、适用题型，配合三道经典 LeetCode 题目的完整 C++ 实现，彻底理解双指针收缩思路。",[134,238,232,193],"滑动窗口",1943,{"slug":241,"title":242,"description":243,"pub_date":208,"tags":244,"draft":15,"word_count":247},"network-clash-config","Clash \u002F Mihomo 配置详解：规则、策略组与分流","深入解析 Clash\u002FMihomo 的核心配置结构，包括代理节点、策略组类型、规则优先级、DNS fake-ip 模式，以及一份实用的完整配置模板。",[222,245,223,246],"clash","配置",1292,{"slug":249,"title":250,"description":251,"pub_date":252,"tags":253,"draft":15,"word_count":257},"hid-hotplug","HID 设备热插拔检测：从 udev 到 node-hid","在 Linux 上用 node-hid + usb 库实现可靠的 USB HID 设备热插拔检测，踩坑记录","2026-04-28",[193,254,36,255,256],"hid","nodejs","electron",2039,{"slug":259,"title":260,"description":261,"pub_date":262,"tags":263,"draft":15,"word_count":266},"electron-ipc-types","Electron IPC 类型安全：从 any 到完全类型化","用 TypeScript 泛型封装 Electron IPC，彻底消灭 any，preload 契约集中管理","2026-04-25",[256,95,264,265],"ipc","vue",1446,{"slug":268,"title":269,"description":270,"pub_date":271,"tags":272,"draft":15,"word_count":275},"element-plus-popover-hide","手动关闭多个 el-popover（不用 v-model:visible）","通过 ref + Reflect.get 调用 hide() 方法手动关闭 Element Plus Popover，解释 Vue3 Proxy 导致无法直接调用实例方法的原因。","2024-10-25",[265,273,274],"element-plus","vue3",1321,{"slug":277,"title":278,"description":279,"pub_date":280,"tags":281,"draft":15,"word_count":285},"vite-vue3-ts-elementplus-pinia","用 Vite+（vp）从零搭建 Vue3 + TypeScript + Element Plus + Pinia + Vue Router","使用 Vite+ 统一工具链（vp）一条命令搭建 Vue3 全家桶，涵盖按需导入、Pinia store、路由配置，以及常见坑的解决方案。","2024-08-27",[265,282,95,273,283,284],"vite","pinia","vite-plus",1960,{"slug":287,"title":288,"description":289,"pub_date":290,"tags":291,"draft":15,"word_count":295},"cef-lnk2038-iterator-debug-level","CEF LNK2038：解决 _ITERATOR_DEBUG_LEVEL 不匹配错误","分析 CEF（Chromium Embedded Framework）集成时出现的 LNK2038 _ITERATOR_DEBUG_LEVEL 链接错误，从根本原因到解决方案的完整指南。","2024-05-07",[193,292,293,294],"CEF","Visual Studio","链接错误",1509,{"slug":297,"title":298,"description":299,"pub_date":300,"tags":301,"draft":15,"word_count":305},"npm-electron-install-fix","彻底解决 npm 安装 Electron 失败的问题","分析 npm install electron 失败的根本原因（下载二进制超时\u002F被墙），通过国内镜像（npmmirror）彻底解决，并介绍多种备选方案和常见错误排查。","2024-03-01",[256,302,303,304],"npm","前端工具链","国内镜像",1494,{"slug":307,"title":308,"description":309,"pub_date":310,"tags":311,"draft":15,"word_count":313},"git-out-of-memory","解决 git 报错：Fatal: Out of memory, malloc failed","分析 git 大仓库操作时出现 Out of memory malloc failed 的根本原因，通过调整 pack.windowMemory、http.postBuffer 和 git repack 彻底解决。","2024-01-31",[67,36,312],"工具",2244,{"slug":315,"title":316,"description":317,"pub_date":318,"tags":319,"draft":15,"word_count":323},"vmware-tools-install","在 VMware 虚拟机中安装 open-vm-tools 完整指南","详解 VMware Tools 的作用、open-vm-tools 与官方 VMware Tools 的区别，以及在 Ubuntu 虚拟机中安装并生效的完整步骤和常见问题排查。","2023-11-21",[320,36,321,322],"VMware","Ubuntu","虚拟机",2523,{"slug":325,"title":326,"description":327,"pub_date":328,"tags":329,"draft":15,"word_count":334},"load-balancing-algorithms","负载均衡算法完全指南：从轮询到一致性哈希","系统梳理静态与动态负载均衡算法，涵盖轮询、随机、权重、IP Hash、一致性 Hash、最少连接、最快响应等，并对比 Nginx、Dubbo、Spring Cloud LoadBalancer 的实现差异。","2023-11-15",[330,331,332,333],"分布式","负载均衡","Nginx","微服务",1764,{"slug":336,"title":337,"description":338,"pub_date":339,"tags":340,"draft":15,"word_count":344},"win-cw2a-ca2w","ATL 字符串转换：CW2A 与 CA2W 完全指南","详解 ATL 宏 CW2A\u002FCA2W 在 Unicode 与 ANSI 之间的字符串转换用法、头文件依赖、USES_CONVERSION 宏的作用与常见陷阱。","2023-06-09",[193,341,342,343],"windows","ATL","字符串",1665,{"slug":346,"title":347,"description":348,"pub_date":339,"tags":349,"draft":15,"word_count":353},"csharp-sendmessage-cpp","C# 通过 SendMessage 向 C++ 窗口发送消息与字符串","使用 P\u002FInvoke 调用 user32.dll 的 SendMessage，从 C# 发送自定义 WM_USER 消息及字符串指针给 C++ 原生窗口，并在 C++ 侧正确接收和转换。",[350,193,341,351,352],"C#","互操作","PInvoke",1554,{"slug":355,"title":356,"description":357,"pub_date":358,"tags":359,"draft":15,"word_count":361},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","2023-05-26",[193,341,360],"WinAPI",1823,{"slug":363,"title":364,"description":365,"pub_date":358,"tags":366,"draft":15,"word_count":367},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[341,193,312],1619,{"slug":369,"title":370,"description":371,"pub_date":358,"tags":372,"draft":15,"word_count":375},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[193,373,374],"c++11","random",1549,{"slug":377,"title":378,"description":379,"pub_date":380,"tags":381,"draft":15,"word_count":383},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[341,193,382],"registry",1201,{"slug":385,"title":386,"description":387,"pub_date":388,"tags":389,"draft":15,"word_count":391},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[390,193,341],"mfc",1546,{"slug":393,"title":394,"description":395,"pub_date":396,"tags":397,"draft":15,"word_count":399},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[193,398,341,390],"duilib",2639,{"slug":401,"title":402,"description":403,"pub_date":404,"tags":405,"draft":15,"word_count":407},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","2022-08-17",[390,193,341,406],"dpi",1414,{"slug":409,"title":410,"description":411,"pub_date":412,"tags":413,"draft":15,"word_count":414},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","2022-08-16",[390,193,341],1633,{"slug":416,"title":417,"description":418,"pub_date":419,"tags":420,"draft":15,"word_count":422},"algorithm-number-complement","整数的补数：位运算掩码解法","LeetCode 476 题，用掩码 XOR 实现整数补数，附 C++\u002FPython\u002FJava 三种实现及补数与补码的区别","2021-03-08",[134,421,232],"位运算",1374,[]]