phase 4: RAG管道

phase 4: RAG管道

Phase 4:RAG 管道

本阶段目标:把「检索 + 提示 + 生成」串成完整 RAG 链,掌握上下文格式化、链的组装,并可选接触 RAGAS 做质量评估。


1. 设计指导

1.1 RAG 数据流

用户问题 → Retriever → 若干 Document
                    → 格式化为一段 context 字符串
                    → Prompt(context=..., question=...) → LLM → 答案
  • 检索:Phase 3 的 Retriever,invoke(question) 得到 List[Document]
  • 格式化:把 Document 列表转成「带来源的」字符串,便于 LLM 引用且便于你溯源(file_path、行号等)。
  • 生成:用 LCEL 组合 RunnableParallel({"context": retriever | format, "question": passthrough}) | prompt | llm | parser

1.2 设计原则

  • context 长度可控:对 Document 内容做截断或限制条数,避免超出模型 context window。
  • 答案可溯源:在 prompt 中要求「引用时注明来源」,并在 metadata 中保留 file_path。

1.3 与系统功能文档的对应

  • 系统文档中的「三阶段检索」(多路召回、元数据过滤、Rerank)可在后续迭代中加;本阶段先做单路向量检索 + 格式化 + 生成,把主链路跑通。

2. 需要实现的功能

  • [ ] 实现 format_docs(docs: List[Document]) -> str,每条带 sourcefile_path
  • [ ] 用 LCEL 组装 RAG 链:RunnableParallel(context=retriever | format_docs, question=RunnablePassthrough.pick("question")) | prompt | llm | StrOutputParser
  • [ ] 对 3~5 个问题做端到端调用,检查答案是否用到检索到的内容。
  • [ ] (可选)跑 RAGAS 的 faithfulness / answer_relevancy(需准备少量 question + ground_truth)。

3. 示例代码

3.1 上下文格式化

# chains/rag_utils.py
from langchain_core.documents import Document

def format_docs(docs: list[Document]) -> str:
    """把 Document 列表格式化为 RAG 的 context 字符串"""
    parts = []
    for i, doc in enumerate(docs):
        path = doc.metadata.get("file_path", doc.metadata.get("source", "unknown"))
        parts.append(f"[{i+1}] 来源: {path}\n{doc.page_content}")
    return "\n\n---\n\n".join(parts)

3.2 RAG 链

# chains/rag.py
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai import ChatOpenAI
from .rag_utils import format_docs

SYSTEM_RAG = """你是一个代码库助手。请仅根据下面「上下文」中的内容回答用户问题。
如果上下文中没有相关信息,请明确说「在提供的上下文中未找到相关信息」。
回答时请注明引用自哪个来源(如 [1] 来源: path/to/file.py)。
上下文:
{context}
"""

def build_rag_chain(retriever, llm=None):
    if llm is None:
        import os
        from dotenv import load_dotenv
        load_dotenv()
        llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    prompt = ChatPromptTemplate.from_messages([
        ("system", SYSTEM_RAG),
        ("human", "{question}"),
    ])
    return (
        RunnableParallel({
            "context": retriever | format_docs,
            "question": RunnablePassthrough.pick("question"),
        })
        | prompt
        | llm
        | StrOutputParser()
    )

3.3 使用示例

# 在脚本或 main 中
from retrieval.vectorstore import build_vectorstore, get_retriever
from retrieval.ingestion import ingest_directory
from chains.rag import build_rag_chain

docs = ingest_directory(".")
vs = build_vectorstore(docs)
retriever = get_retriever(vs, k=5)
rag = build_rag_chain(retriever)

answer = rag.invoke({"question": "本项目的主入口在哪里?"})
print(answer)

3.4 流式 RAG

# 同一链支持 stream
for chunk in rag.stream({"question": "什么是 RAG?"}):
    print(chunk, end="", flush=True)

3.5 RAGAS 初体验(可选)

uv add ragas
# evaluation/ragas_simple.py
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings

# 需要准备 dataset: list of {"question", "answer", "contexts", "ground_truth"}
dataset = [
    {
        "question": "主入口在哪里?",
        "answer": "...",      # 模型生成的答案
        "contexts": ["..."],  # 检索到的 context 列表
        "ground_truth": "主入口在 main.py",
    },
]
result = evaluate(
    dataset=dataset,
    metrics=[faithfulness, answer_relevancy],
    llm=ChatOpenAI(model="gpt-4o-mini"),
    embeddings=OpenAIEmbeddings(model="text-embedding-3-small"),
)
print(result)

4. 需要导入的包和环境

4.1 本阶段依赖

与 Phase 1 + Phase 3 一致;若用 RAGAS 则增加:

uv add ragas

4.2 环境

  • 已有向量库(Phase 3 产出)或现场 ingest_directory + build_vectorstore
  • OPENAI_API_KEY 可用。

5. 本阶段小结

  • RAG 链RunnableParallel(context=retriever | format_docs, question=...) | prompt | llm | parser
  • format_docs:统一把 Document 列表变成带来源的 context 字符串,便于引用与溯源。
  • RAGAS:可对已有 question/answer/contexts/ground_truth 做 faithfulness、answer_relevancy 等评估,为后续优化检索/分块提供依据。

下一步:Phase 5:工具与单 Agent——@tool、bind_tools、ReAct 单 Agent。