cc_rag_tutorial

第 9 章:与 CC 的集成实践——把外挂 RAG 变成 CC“可用的能力”

1. 开篇:从“外部搜索”到“原生器官”

在前几章,我们已经打造了一个强大的 RAG 引擎(Ingest -> Index -> Retrieve)。但如果它只是一个独立的 Python 脚本,那它对 Claude Code (CC) 来说依然是“外人”。

本章的核心任务是“器官移植”:我们要定义一套标准化的输入/输出契约(I/O Contract),让 CC 感觉这个 RAG 不是一个外部工具,而是它与生俱来的“海马体”(长期记忆)或“图书馆”。

当集成本章内容后,你将看到如下的魔法时刻:

本章学习目标

  1. 协议设计:掌握 Tool(工具)、Payload(载荷)与 Schema(模式)的定义标准。
  2. Prompt 协同:如何编写 System Prompt,让 CC 学会“什么时候搜”以及“怎么用搜到的东西”。
  3. 数据编排:将非结构化的 Chunk 转换为 CC 偏好的 XML/JSON 混合格式。
  4. 防御性设计:防止 CC 陷入死循环、幻觉引用或上下文溢出。

2. 集成架构:三种“挂载”形态

CC 本质上是一个运行在终端里的 ReAct (Reasoning + Acting) Agent。根据你的介入程度,RAG 可以通过三种方式挂载。

2.1 形态对比表

特性 A. 工具化 (Tool/Function Call) B. 命令化 (Slash Command) C. 隐式钩子 (Implicit Hook)
触发机制 CC 自主决定。基于意图识别。 人类手动触发。如 /rag query 事件触发。如“启动时”、“报错时”。
交互心流 流畅,无感。Agent 自行“思考->检索->回答”。 打断。人类介入控制检索词。 预加载。将知识“推”给 CC。
实现难度 高。需要精心设计 Description 和 Schema。 中。只需暴露简单的 CLI 接口。 低。简单的脚本注入。
适用场景 复杂问题解决、代码理解、未知领域探索。 强制纠偏、精准定位特定文档。 项目初始化(加载规范)、错误自动诊断。
推荐指数 ⭐⭐⭐⭐⭐ (最符合 Agent 原生体验) ⭐⭐⭐ (作为兜底手段) ⭐⭐ (仅用于特定场景)

2.2 工具化模式的数据流 (The Anatomy of a RAG Call)

这是最复杂的模式,也是效果最好的。我们需理解其中的四次握手

User Input: "怎么修复 UserTable 的死锁问题?"
      |
      v
+------------+
| Claude Code|  <-- 1. 思考 (Reasoning):
| (Brain)    |      "我不清楚 UserTable 的锁机制,
+------------+       我需要调用 'search_codebase' 工具。"
      |
      | 2. 工具调用请求 (Tool Call Request)
      |    { "tool": "search", "args": { "query": "UserTable deadlock locking mechanism", "file_pattern": "*.sql" } }
      v
+------------+
| Integration|  <-- 3. 拦截与转发 (Adapter Layer)
| Layer      |      解析 JSON -> 调用你的 RAG API -> 获取 Top-K Chunks
+------------+
      |
      | 4. 结果返回 (Tool Output)
      |    <search_results>...</search_results> (XML 格式文本)
      v
+------------+
| Claude Code|  <-- 5. 综合生成 (Generation)
| (Brain)    |      阅读 XML -> 结合上下文 -> 生成带引用的回答
+------------+

3. 接口契约:设计“检索工具”的 Schema

让 CC 用好工具的关,在于让它准确理解工具的能力边界。这完全依赖于你提供的 namedescriptioninput_schema

3.1 工具描述 (The “Instruction Manual”)

不要只写“搜索代码”。要写得像给新员工的操作指南。

差的描述

“Search the codebase.”

好的描述 (推荐)

“Semantic search engine for the current project. Use this tool when you need to understand specific implementation details, find function definitions, look up error codes, or understand architectural patterns that are not in the current context. Preferred over generic knowledge for project-specific questions.” (语义搜索引擎。当你需要理解具体实现、查找函数定义、错误码或架构模式时必须使用。对于项目特定问题,优先使用此工具而非通用知识。)

3.2 参数设计 (JSON Schema)

一个成熟的 RAG 工具接口设计如下:

{
  "name": "search_codebase",
  "description": "Search for code snippets, documentation, and issues within the project repository.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Natural language query focusing on the 'what' and 'why'. E.g., 'How is authentication handled?' or 'User class definition'."
      },
      "keywords": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Specific symbols, class names, or error codes to boost ranking (e.g., ['AuthService', 'ERROR_503'])."
      },
      "file_pattern": {
        "type": "string",
        "description": "Glob pattern to filter files. Critical for reducing noise. E.g., 'src/**/*.ts' for TypeScript code, or 'docs/*.md' for documentation."
      },
      "requires_exact_match": {
        "type": "boolean",
        "description": "If true, strictly filters results containing the keywords. Use for looking up specific variable/function names."
      }
    },
    "required": ["query"]
  }
}

Rule of Thumb (经验法则)


4. 响应结构:设计“证据”的 Payload

当你的 RAG 检索到 5 个 Chunk 后,不能把它们简单拼成字符串扔回去。CC 对 XML 结构有着极强的解析能力。

4.1 推荐的 XML 返回格式

这种格式既能被机器解析,又具有人类可读性(方便调试)。

<search_results>
  <!-- 元数据摘要,帮助 CC 快速判断结果质量 -->
  <meta>
    <total_hits>12</total_hits>
    <returned_hits>3</returned_hits>
    <latency_ms>45</latency_ms>
    <search_strategy>hybrid (semantic + keyword)</search_strategy>
  </meta>

  <!-- 结果 1:高置信度代码 -->
  <result id="doc_8823" score="0.92">
    <source_file>src/backend/auth_service.py</source_file>
    <lines>45-68</lines>
    <language>python</language>
    <last_modified>2023-10-25</last_modified>
    <snippet>
class AuthService:
    def validate_token(self, token):
        # TODO: Fix the hardcoded timeout
        timeout = 3600 
        if time.now() - token.timestamp > timeout:
            raise TokenExpiredError("Token is too old")
        ...
    </snippet>
  </result>

  <!-- 结果 2:相关文档 -->
  <result id="doc_102" score="0.85">
    <source_file>docs/api_spec.md</source_file>
    <lines>12-15</lines>
    <language>markdown</language>
    <snippet>
## Authentication
All endpoints require a Bearer token. Tokens are valid for 1 hour by default.
    </snippet>
  </result>
  
  <!-- 兜底提示:如果没找到 -->
  <!-- <message>No high-confidence results found for '...'. Try broadening your search.</message> -->
</search_results>

4.2 关键字段设计哲学

  1. <lines> (行号)至关重要
    • 没有行号,CC 只能“读”代码,不能“改”代码。
    • 有了行号,CC 以生成 sed 命令或补丁。
  2. <source_file> (路径)
    • 必须是相对于项目根目录的相对路径。绝对路径会浪费 token 且造成混淆。
  3. <last_modified> (时效性)
    • 告诉 CC 这是两年前的代码还是昨天的提交。这有助于解决“旧代码与新文档冲突”的幻觉问题。
  4. <snippet> (内容)
    • 截断策略:如果 Chunk 过长,不要硬切。尝试保留完整的函数体或段落。如果在中间截断,务必添加 // ... (content truncated) 标记。

5. 集成 Prompt:给 CC 的“紧箍咒”

仅仅定义工具是不够的,你需要在 CC 的 System Prompt(或自定义指令配置)中注入 RAG 使用规范。

5.1 通用 RAG 增强 Prompt 模板

## Tool Use Guidelines: RAG & Search

You have access to a tool named `search_codebase`. You MUST use this tool in the following scenarios:
1. When asked about specific files, classes, or functions not visible in the current conversation history.
2. When planning complex refactoring tasks to understand dependencies.
3. When debugging errors to find where specific error messages are generated.

## Citation & Evidence Rules (STRICT)

When answering based on `search_codebase` results:
1. **Evidence First**: Do not make up code or logic. If the search results are empty or irrelevant, state: "I could not find relevant information in the codebase."
2. **Inline Citation**: When you reference a specific logic, append the source like this: 
   "The timeout is set to 3600s [[src/auth.py:48]]."
3. **Context Check**: Before suggesting a file edit, ensure you have the correct file path from the search result.

## Handling "No Results"
If a search yields no results, do NOT halllucinate. Instead:
- Ask the user for clarification.
- Suggest searching for a different keyword.
- Or propose listing the directory structure to get oriented.

6. 高级实践:从“能用”到“好用”

6.1 查询重写 (Query Rewriting)

用户通常比较懒,他们会说:“为什么在这个文件里报错了?” 如果直接把这句话传给 RAG,效果会很差。

策略: 在中间层(Adapter)或 Prompt 中,要求 CC 将查询重写为“独立、完整”的句子。

6.2 结果去重与合并 (Deduplication)

向量检索可能会返回 3 个 chunk,它们来自同一个文件的相邻段落(Chunk 1: lines 1-20, Chunk 2: lines 15-35)。

策略: 在集成层做一步后处理: 如果多个结果来自同一文件且行号重叠或相邻,合并它们为一个大的 Block。这能显著提升 CC 阅读代码的连续性。


7. 常见陷阱与调试技巧 (Gotchas)

陷阱 1:JSON 转义地狱

陷阱 2:上下文“被撑爆”

陷阱 3:CC 偷懒不搜索


8. 本章小结

本章完成了 RAG 系统的“最后一公里”。我们没有写任何检索算法而是专注于通信

下一章,我们将汇总所有知识,通过一个Reference Implementation(参考实现),从零搭建一个最小可用的外挂 RAG 系统。


9. 练习题

基础题 (50%)

习题 1:工具选择逻辑 **问题**:你正在设计一个集成 Prompt。用户问:“帮我给 README.md 增加一段安装说明”。此时,CC 应该调用 `search_codebase` 吗?为什么? **提示**:思考 RAG 的目的(获取未知信息)与编辑任务的区别。
**参考答案**: **不一定需要,或者只需轻量调用。** * **情况 A**:如果 CC 已经读取了 `README.md`(在当前上下文中),它不需要搜索,直接编辑即可。 * **情况 B**:如果 CC 还没读取文件,它应该先调用文件读取工具(如 `cat README.md`),而不是语义搜索。 * **RAG 的作用**:如果用户问“安装依赖需要哪些步骤?”,而这些步骤分散在 `package.json` 和 `Dockerfile` 中,此时 CC 才**应该**调用 RAG 来汇总信息。 * **关键点**:区分“定位/读取文件”(精确操作)与“查找知识”(模糊操作)。
习题 2:Schema 设计 **问题**:如果在工具参数中增加一个 `language` 字段(枚举值:python, javascript, etc.),会有什么优缺点? **提示**:从准确率和 Agent 负担两个角度考虑。
**参考答案**: * **优点**:能显著提高过滤准确率。例如搜 "interface" 时,指定 `language="typescript"` 可以排除 Java 代码的干扰。 * **缺点**:增加了 Agent 的认知负担。CC 需要准确判断用户想搜哪种语言。如果项目是多语言混合(如 `.vue` 件包含 JS 和 HTML),强制指定语言反而可能导致漏搜。 * **结论**:除非是单一语言的大型项目,否则建议用 `file_pattern` 代替显式的 `language` 字段,更灵活。
习题 3:XML vs JSON **问题**:为什么我们在返回结果 Payload 时推荐 XML 结构,而不是纯 JSON 列表? **提示**:Claude 系列模型的训练特性。
**参考答案**: 1. **模型偏好**:Claude 模型在微调阶段大量接触 XML 标签数据,对 `content` 的边界识别能力强于 JSON 的括号嵌套。 2. **容错性**:如果代码片段(Chunk)本身包含大量引号、转义符或不规范的 JSON 字符,嵌入 JSON 字符串极易导致解析错误。XML 的 CDATA 或简单的标签包裹对特殊字符容忍度更高。 3. **Token 效率**:虽然 XML 标签也有开销,但在处理多行文本块(Code Block)时,比 JSON 的转义序列(`\n\t`)更节省且易读。

挑战题 (50%)

习题 4:防御性 Prompt 设计 **问题**:设计一段 System Prompt,专门用于应对以下恶意外挂 RAG 场景: 检索出的文档包含过时的、误导性的代码(比如 v1 版本的 API),而当前项目使用的是 v2。如何让 CC 识别并警惕? **提示**:利用元数据(Metadata)和冲突解决策略。
**参考答案**: **Prompt 片段建议**: > "Critical Conflict Resolution Rule: > You will receive search results with a `` timestamp. > 1. Always prioritize chunks from files that appear to be **more recent**. > 2. If you find conflicting logic (e.g., two different implementations of `login`), explicitly point out the conflict to the user: 'I found two versions of login logic, one in `v1/` and one in `v2/`. I will assume `v2/` is correct based on the path.' > 3. If a retrieved code snippet contradicts the code explicitly provided by the user in the chat, **trust the user's code** as the ground truth." </details>
习题 5:多轮对话中的 RAG 状态 **问题**:用户问了一个问题,CC 搜索了,回答了。用户接着问:“那 `Payment` 模块呢?”(省略了上下文)。 如果不做特殊处理,直接把 "那 Payment 模块呢?" 发给 RAG 会发生什么?你应该如何在集成层解决这个问题? **提示**:Query Expansion(查询扩展)或 Conversational History Aware Retrieval。
**参考答案**: **后果**:直接搜索 "那 Payment 模块呢?" 几乎搜不到任何有用的代码,因为关键词极其模糊。 **解决方案**: 1. **CC 侧处理(推荐)**:依靠 CC 的强大理解力。在 Prompt 中要求 CC:“在调用搜索工具前,必须将用户的指代性问题(如‘它’、‘那个’)改写为完整的独立查询。” * CC 内部思考:用户指的是“`Payment` 模块是否有同样的死锁问题(于上一轮对话)”。 * 生成的 Query:`"Payment module deadlock locking mechanism"`。 2. **集成层处理(备选)**:使用一个轻量级 LLM 在 RAG 入口处做 "Query Rewriting",将 `History + Current Query` 重写为 `Standalone Query`。