阶段 5:升级为可讲的 Agent 项目

本阶段目标:把前面学过的 FastAPI、RAG、Tool Calling、LangGraph、状态管理、结构化输出整合成一个完整项目。
这个项目不只是“能跑”,更要能在面试中讲清楚:

  • 业务场景;
  • 系统架构;
  • 关键模块;
  • 技术难点;
  • 优化方案;
  • 评估指标。

1. 阶段 5 的定位

阶段 3 你做的是 RAG 项目:

用户问题 -> 检索知识库 -> 拼接上下文 -> 模型回答

阶段 4 你学的是 Tool Calling 和 Agent:

用户目标 -> 模型选择工具 -> 执行工具 -> 工具结果回传 -> 模型继续决策

阶段 5 要做的是把它们合起来:

用户问题
  -> 问题分类 / 意图识别
  -> 知识类问题走 RAG
  -> 外部任务走工具调用
  -> 多轮上下文保存状态
  -> 最终输出结构化答案
  -> 记录日志与可视化调用过程

你最后要做出的项目可以叫:

Knowledge Agent Assistant:带知识库检索、外部工具调用和多轮状态的 AI Assistant

这个项目适合写进简历,也适合在 AI Agent / 大模型应用 / 后端开发相关岗位面试中讲。


2. 项目一句话介绍

面试时可以先用一句话介绍:

我做了一个面向论文 / 文档知识库的 AI Assistant,它不是简单的 RAG 问答,而是基于 LangGraph 构建了一个可控的 Agent 流程:系统会先判断用户问题类型,知识类问题调用 RAG 检索工具,任务类问题调用外部工具,同时维护多轮对话状态,最后输出带引用来源的结构化答案,并记录工具调用过程和问题日志,方便调试与评估。


3. 业务场景设计

3.1 为什么需要这个项目

很多企业或实验室都有大量文档,例如:

  • 论文 PDF;
  • 项目文档;
  • 技术方案;
  • 接口文档;
  • 业务知识库;
  • FAQ;
  • 操作手册。

普通聊天模型的问题是:

  1. 不知道你的私有文档;
  2. 容易凭空编造;
  3. 不能调用外部工具;
  4. 多轮任务状态容易丢;
  5. 回答过程不可追踪;
  6. 出错后难排查。

所以你做这个项目的业务价值是:

让用户可以用自然语言查询知识库、执行简单外部任务,并且系统能给出可追踪、可解释、可复盘的回答。

3.2 示例业务场景

假设你的知识库里有多篇 RAG、Agent、LLM 应用相关论文和笔记。

用户可能会问:

1. RAG 为什么能减少幻觉?
2. 帮我总结这篇论文的核心方法。
3. 对比一下 RAG 和微调的区别。
4. 帮我列出知识库里关于 Agent Memory 的论文。
5. 根据刚才的回答,生成一个面试回答版本。
6. 查一下当前知识库有多少篇论文。
7. 把刚才的总结保存成学习笔记。

这些问题不是都应该用同一个流程处理。

例如:

用户问题 类型 处理方式
RAG 为什么能减少幻觉? 知识问答 RAG 检索
总结这篇论文 文档总结 RAG + 总结
对比两篇论文 多文档分析 多次检索 + 合成
知识库有多少篇论文 外部工具 调用 metadata / database 工具
保存成学习笔记 外部任务 调用文件保存工具
根据刚才内容继续改写 多轮对话 依赖 state / memory

这就是 Agent 项目的意义:不是所有问题都强行走一次 RAG,而是由系统根据任务类型决定下一步。


4. 系统总体架构

建议系统分为 6 层:

┌─────────────────────────────────────────────┐
│                 前端 / Client                │
│   Chat UI / 调用过程展示 / 引用片段展示       │
└─────────────────────┬───────────────────────┘
                      │
┌─────────────────────▼───────────────────────┐
│               FastAPI 服务层                 │
│   接收请求 / 参数校验 / 返回结构化响应        │
└─────────────────────┬───────────────────────┘
                      │
┌─────────────────────▼───────────────────────┐
│              LangGraph 编排层                │
│   classify -> route -> retrieve/tool -> answer│
└───────────────┬───────────────┬─────────────┘
                │               │
┌───────────────▼───────┐   ┌───▼────────────────┐
│       RAG 检索层       │   │       Tool 层        │
│ PDF解析/切块/向量库/检索│   │ 外部API/数据库/文件工具 │
└───────────────┬───────┘   └───┬────────────────┘
                │               │
┌───────────────▼───────────────▼─────────────┐
│              状态存储与日志层                │
│ thread state / checkpoints / query logs       │
└─────────────────────────────────────────────┘

4.1 架构层说明

层级 职责 面试说法
FastAPI 服务层 提供 HTTP API,做请求校验和响应封装 对外暴露统一服务入口
LangGraph 编排层 控制 Agent 流程、分支、循环、状态 负责决策与流程编排
RAG 检索层 解析 PDF、切块、embedding、向量检索 提供知识增强能力
Tool 层 封装外部能力,如查数据库、保存文件 让 Agent 能执行动作
状态存储层 保存多轮对话、checkpoint、日志 支持上下文连续与问题复盘
输出结构化层 统一答案格式、引用、错误信息 保证前端和评估可解析

5. 推荐技术栈

模块 推荐技术
Web 服务 FastAPI
参数校验 Pydantic
Agent 编排 LangGraph
LLM 调用 OpenAI API / LangChain ChatModel
RAG 框架 LangChain 或自写检索流程
向量数据库 Chroma / FAISS
文档解析 PyMuPDF / pypdf
状态存储 SQLite / Redis / LangGraph checkpointer
日志 Python logging / SQLite query logs
前端 Streamlit / React / 简单 HTML
部署 Docker / docker-compose

初学阶段建议:

FastAPI + LangGraph + Chroma + SQLite + 简单前端

不要一开始就上太多复杂组件。


6. 项目核心流程

6.1 总流程

用户发送问题
  -> FastAPI 接收请求
  -> Pydantic 校验 request
  -> LangGraph 初始化 state
  -> classify_node 判断问题类型
  -> route_node 决定下一步
      -> 知识问题:retrieve_node
      -> 外部任务:tool_node
      -> 闲聊/改写:direct_answer_node
  -> answer_node 生成最终答案
  -> format_node 输出结构化结果
  -> log_node 记录问题、工具调用、耗时、引用
  -> FastAPI 返回响应

6.2 LangGraph 节点设计

建议先做这些节点:

节点 作用
classify_question 判断问题类型
retrieve_knowledge 调用 RAG 检索
execute_tool 调用外部工具
generate_answer 根据 state 生成最终回答
format_output 转成统一 JSON
handle_error 错误兜底
save_log 保存日志

7. 问题分类设计

问题分类是阶段 5 的关键模块。

不要所有问题都直接 RAG。

7.1 推荐分类

class QuestionType(str, Enum):
    KNOWLEDGE_QA = "knowledge_qa"
    DOCUMENT_SUMMARY = "document_summary"
    COMPARISON = "comparison"
    EXTERNAL_TASK = "external_task"
    FOLLOW_UP = "follow_up"
    CHITCHAT = "chitchat"
    UNKNOWN = "unknown"

7.2 分类结果结构

{
  "question_type": "knowledge_qa",
  "need_retrieval": true,
  "need_tool": false,
  "confidence": 0.86,
  "reason": "用户询问知识库中的概念解释,需要检索文档片段。"
}

7.3 路由规则

分类 下一步
knowledge_qa RAG 检索
document_summary RAG 检索 + 总结
comparison 多次检索 + 对比
external_task 工具调用
follow_up 读取历史 state
chitchat 直接回答
unknown 澄清问题或兜底回答

8. RAG 检索层设计

8.1 RAG 层职责

RAG 检索层不应该关心 Agent 决策,它只负责:

输入 query -> 返回相关文档片段

也就是说,它应该被封装成一个工具:

def search_knowledge_base(query: str, top_k: int = 5, filters: dict | None = None) -> dict:
    ...

返回:

{
  "ok": true,
  "query": "RAG 如何减少幻觉",
  "chunks": [
    {
      "doc_id": "paper_001",
      "title": "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks",
      "page": 3,
      "score": 0.82,
      "content": "RAG combines parametric memory with non-parametric memory..."
    }
  ]
}

8.2 为什么要把 RAG 封装成 Tool

因为 Agent 的视角里,RAG 只是众多工具之一:

search_knowledge_base
get_document_metadata
list_documents
save_note
query_database

这样系统就从“固定 RAG 问答”升级为“Agentic RAG”。

8.3 检索优化点

优化点 作用
query rewrite 用户问题太口语化时改写检索词
top-k 调整 简单问题少取,复杂问题多取
metadata filter 按文件、年份、章节过滤
hybrid search 语义检索 + 关键词检索
rerank 对初始召回结果重新排序
source citation 回答附来源,降低幻觉
no-answer 判断 检索不到时不要硬答

9. Tool 层设计

Tool 层负责外部能力。

初学项目不用做太多工具,建议先做 3 个:

工具 功能
search_knowledge_base 检索文档片段
get_document_metadata 查询文档标题、作者、页数等
save_learning_note 把回答保存成 Markdown 学习笔记

如果想加分,可以再做:

工具 功能
list_documents 列出知识库文档
query_question_logs 查询历史问题日志
calculator 简单计算
web_search 联网搜索,但要注意安全和引用
create_todo 创建待办事项

9.1 工具 schema 示例

search_knowledge_tool = {
    "type": "function",
    "name": "search_knowledge_base",
    "description": "Search relevant chunks from the local document knowledge base.",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Search query rewritten from the user's question."
            },
            "top_k": {
                "type": "integer",
                "minimum": 1,
                "maximum": 10,
                "description": "Number of chunks to retrieve."
            },
            "doc_type": {
                "type": "string",
                "enum": ["paper", "note", "manual", "all"],
                "description": "Type of document to search."
            }
        },
        "required": ["query", "top_k", "doc_type"],
        "additionalProperties": False
    },
    "strict": True
}

9.2 工具执行器

def execute_tool(tool_name: str, arguments: dict) -> dict:
    if tool_name == "search_knowledge_base":
        return search_knowledge_base(**arguments)

    if tool_name == "get_document_metadata":
        return get_document_metadata(**arguments)

    if tool_name == "save_learning_note":
        return save_learning_note(**arguments)

    return {
        "ok": False,
        "error_code": "UNKNOWN_TOOL",
        "message": f"Tool {tool_name} is not registered.",
        "retryable": False
    }

10. 状态设计

阶段 5 一定要有状态。

因为面试官会问:

多轮对话怎么处理?
上一轮检索结果怎么保存?
用户追问“它”和“刚才那个方法”时怎么办?

10.1 State 字段设计

from typing import TypedDict, List, Optional, Literal, Any

class AgentState(TypedDict):
    thread_id: str
    user_id: str
    question: str

    question_type: Optional[str]
    route: Optional[str]

    messages: List[dict]

    rewritten_query: Optional[str]
    retrieved_chunks: List[dict]
    tool_calls: List[dict]
    tool_results: List[dict]

    answer: Optional[str]
    citations: List[dict]

    error: Optional[dict]
    step_count: int
    final_output: Optional[dict]

10.2 每个字段的作用

字段 作用
thread_id 区分不同对话
user_id 区分用户
question 当前问题
question_type 分类结果
route 当前走哪条路径
messages 多轮上下文
rewritten_query 改写后的检索 query
retrieved_chunks 检索片段
tool_calls 工具调用记录
tool_results 工具返回
answer 最终答案
citations 引用来源
error 错误信息
step_count 防止无限循环
final_output 返回给前端的结构化结果

11. 结构化输出设计

不要只返回一段字符串。

建议 API 返回:

{
  "answer": "RAG 可以通过检索外部知识减少模型凭空生成的概率...",
  "question_type": "knowledge_qa",
  "citations": [
    {
      "title": "RAG Survey",
      "page": 5,
      "chunk_id": "rag_001_p5_c2",
      "snippet": "Retrieval-augmented models condition generation on retrieved documents..."
    }
  ],
  "tool_trace": [
    {
      "tool_name": "search_knowledge_base",
      "arguments": {
        "query": "RAG hallucination reduction",
        "top_k": 5
      },
      "status": "success",
      "latency_ms": 124
    }
  ],
  "confidence": 0.78,
  "error": null
}

11.1 为什么结构化输出重要

面试可以这样说:

我没有直接把模型输出的文本返回给前端,而是定义了统一的响应结构,包括 answer、citations、tool_trace、confidence 和 error。这样前端可以展示引用片段,后端可以记录工具调用,后续评估也可以基于结构化字段做统计。


12. FastAPI 接口设计

12.1 核心接口

接口 方法 作用
/chat POST 用户提问,返回 Agent 回答
/documents/upload POST 上传 PDF
/documents GET 查看知识库文档
/logs/questions GET 查看问题日志
/health GET 健康检查

12.2 请求体

from pydantic import BaseModel, Field
from typing import Optional

class ChatRequest(BaseModel):
    user_id: str = Field(..., description="User ID")
    thread_id: str = Field(..., description="Conversation thread ID")
    question: str = Field(..., min_length=1, max_length=2000)
    top_k: int = Field(default=5, ge=1, le=10)
    enable_trace: bool = True

12.3 响应体

from typing import List, Optional, Any
from pydantic import BaseModel

class Citation(BaseModel):
    title: str
    page: Optional[int] = None
    chunk_id: str
    snippet: str

class ToolTrace(BaseModel):
    tool_name: str
    arguments: dict
    status: str
    latency_ms: int

class ChatResponse(BaseModel):
    answer: str
    question_type: str
    citations: List[Citation]
    tool_trace: List[ToolTrace]
    confidence: Optional[float] = None
    error: Optional[dict] = None

12.4 FastAPI 路由示例

@app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest):
    result = graph.invoke(
        {
            "thread_id": request.thread_id,
            "user_id": request.user_id,
            "question": request.question,
            "messages": [],
            "retrieved_chunks": [],
            "tool_calls": [],
            "tool_results": [],
            "citations": [],
            "step_count": 0,
            "error": None,
            "answer": None,
            "final_output": None,
        },
        config={
            "configurable": {
                "thread_id": request.thread_id
            }
        }
    )

    return result["final_output"]

13. LangGraph 编排设计

13.1 推荐图结构

START
  -> classify_question
  -> route_question
      -> retrieve_knowledge
      -> execute_tool
      -> direct_answer
  -> generate_answer
  -> format_output
  -> save_log
  -> END

如果要支持检索为空后的 query rewrite:

START
  -> classify_question
  -> route_question
      -> retrieve_knowledge
          -> check_retrieval
              -> generate_answer
              -> rewrite_query -> retrieve_knowledge
      -> execute_tool
      -> direct_answer
  -> format_output
  -> save_log
  -> END

13.2 条件分支函数

def route_question(state: AgentState) -> str:
    question_type = state["question_type"]

    if question_type in ["knowledge_qa", "document_summary", "comparison"]:
        return "retrieve_knowledge"

    if question_type == "external_task":
        return "execute_tool"

    if question_type in ["chitchat", "follow_up"]:
        return "direct_answer"

    return "handle_error"

13.3 检索后判断

def check_retrieval(state: AgentState) -> str:
    chunks = state["retrieved_chunks"]

    if len(chunks) > 0:
        return "generate_answer"

    if state["step_count"] >= 2:
        return "generate_answer"

    return "rewrite_query"

14. 错误兜底设计

Agent 项目必须能讲错误兜底。

14.1 常见错误与处理

错误 场景 处理
参数错误 用户问题为空、top_k 超范围 Pydantic 校验
检索为空 向量库没有相关内容 query rewrite,仍无结果则说明未找到
工具失败 外部 API 超时 retry + timeout + fallback
模型输出不合法 JSON 解析失败 重新生成或 fallback
循环失控 Agent 一直调用工具 最大步数限制
权限问题 查询无权限文档 返回权限错误
敏感操作 保存、删除、发送请求 二次确认

14.2 兜底回答模板

我在当前知识库中没有检索到足够可靠的依据,因此不能直接给出确定结论。
你可以尝试:
1. 换一种问法;
2. 指定具体文档;
3. 上传相关资料;
4. 降低筛选条件。

14.3 工具错误结构

{
  "ok": false,
  "error_code": "TOOL_TIMEOUT",
  "message": "search_knowledge_base timed out after 5 seconds",
  "retryable": true
}

15. 日志记录设计

日志是加分项,但非常适合面试。

15.1 为什么要记录日志

因为你需要知道:

  • 用户都问了什么;
  • 哪些问题经常检索不到;
  • 哪些工具经常失败;
  • 平均响应时间是多少;
  • 哪些回答没有引用;
  • 哪些问题成本高;
  • 哪些分类不准确。

15.2 日志字段

CREATE TABLE question_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id TEXT,
    thread_id TEXT,
    question TEXT,
    question_type TEXT,
    answer TEXT,
    citations_count INTEGER,
    tool_calls_count INTEGER,
    latency_ms INTEGER,
    success BOOLEAN,
    error_code TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

15.3 工具调用日志

CREATE TABLE tool_call_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    thread_id TEXT,
    tool_name TEXT,
    arguments_json TEXT,
    result_status TEXT,
    latency_ms INTEGER,
    error_code TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

16. 工具调用过程可视化

前端可以展示:

用户问题:RAG 为什么能减少幻觉?

执行过程:
1. classify_question -> knowledge_qa
2. search_knowledge_base(query="RAG hallucination", top_k=5) -> success
3. generate_answer -> success
4. format_output -> success

引用来源:
- RAG Survey, page 5
- Lewis et al., 2020, page 3

16.1 可视化价值

面试可以这样说:

我把工具调用过程作为 tool_trace 返回给前端,这样用户能看到系统为什么这么回答,开发者也能快速定位是分类错了、检索错了,还是生成阶段出错。


17. 简单前端建议

不建议一开始做复杂 React。

优先级:

第一版:FastAPI Swagger / Postman 能调通
第二版:Streamlit 简单聊天界面
第三版:展示引用片段和工具调用 trace
第四版:再考虑 React

17.1 Streamlit 页面功能

功能 说明
聊天输入框 输入问题
答案展示 展示最终 answer
引用展示 展示引用片段
工具 trace 展示每一步工具调用
文档上传 上传 PDF
日志查看 查看最近问题

18. 项目目录结构

建议仓库结构:

knowledge-agent-assistant/
  README.md
  requirements.txt
  .env.example
  docker-compose.yml
  Dockerfile

  app/
    main.py

    api/
      routes_chat.py
      routes_documents.py
      routes_logs.py

    schemas/
      chat.py
      document.py
      common.py

    agent/
      state.py
      graph.py
      nodes.py
      router.py
      prompts.py

    rag/
      loader.py
      splitter.py
      embeddings.py
      vectorstore.py
      retriever.py
      citation.py

    tools/
      registry.py
      knowledge_tools.py
      document_tools.py
      note_tools.py

    storage/
      db.py
      models.py
      repositories.py
      checkpointer.py

    observability/
      logger.py
      trace.py
      metrics.py

    config.py

  data/
    raw_pdfs/
    chroma_db/
    notes/

  tests/
    test_rag.py
    test_tools.py
    test_graph.py
    test_api.py

  docs/
    architecture.md
    api.md
    eval.md
    interview_script.md

19. README 应该怎么写

README 不要只写“怎么运行”,还要体现项目价值。

建议结构:

# Knowledge Agent Assistant

## 1. 项目背景
## 2. 核心功能
## 3. 系统架构
## 4. 技术栈
## 5. 快速启动
## 6. API 示例
## 7. Agent 工作流
## 8. RAG 检索设计
## 9. 工具调用设计
## 10. 状态管理
## 11. 错误处理
## 12. 评估指标
## 13. 后续优化

20. 本周开发计划

Day 1:确定项目骨架

产出:

  • 创建 Git 仓库;
  • 搭 FastAPI;
  • /health
  • 写基础目录结构;
  • 写 ChatRequest / ChatResponse。

验收:

POST /chat 可以返回 mock 答案
GET /health 返回 ok

Day 2:接入 RAG 检索层

产出:

  • PDF 上传;
  • 文档解析;
  • chunk 切分;
  • embedding;
  • Chroma 入库;
  • 检索 top-k;
  • 返回引用片段。

验收:

输入问题 -> 能检索出相关 chunk -> 返回 title/page/snippet

Day 3:封装 Tool 层

产出:

  • search_knowledge_base
  • get_document_metadata
  • list_documents
  • 工具 registry;
  • 工具错误结构。

验收:

工具可以被统一 execute_tool 调用
工具成功和失败都返回结构化结果

Day 4:接入 LangGraph

产出:

  • AgentState;
  • classify 节点;
  • retrieve 节点;
  • tool 节点;
  • answer 节点;
  • 条件边;
  • 最大 step 控制。

验收:

知识问题自动走 RAG
工具问题自动走 Tool
普通问题可以直接回答

Day 5:加入状态与日志

产出:

  • thread_id;
  • SQLite 日志表;
  • question_logs;
  • tool_call_logs;
  • 保存 tool_trace;
  • 支持多轮上下文。

验收:

同一个 thread_id 下可以追问
日志里能看到问题、分类、工具调用、耗时

Day 6:结构化输出与前端

产出:

  • 统一 ChatResponse;
  • citations;
  • tool_trace;
  • error;
  • Streamlit 简单界面;
  • 引用展示;
  • 工具过程展示。

验收:

前端能显示答案、引用和调用过程

Day 7:文档与面试材料

产出:

  • README;
  • 架构图;
  • 2 分钟项目介绍稿;
  • 难点总结;
  • 评估指标;
  • 后续优化计划。

验收:

可以完整演示项目
可以用 2 分钟讲清楚项目
可以回答面试追问

21. 架构图 Mermaid 版本

可以放到 README 或 docs/architecture.md 中。

Knowledge QA

External Task

Follow-up / Chat

User / Frontend

FastAPI Service Layer

Pydantic Validation

LangGraph Orchestration Layer

Question Classifier

Route

RAG Retrieval Tool

External Tools

Direct Answer Node

Vector Store: Chroma / FAISS

PDF Chunks + Metadata

SQLite / Business DB

File / Note Storage

Answer Generation Node

Structured Output Formatter

Logs and Trace Storage


22. 2 分钟项目介绍稿

下面这版可以直接背。

我做的是一个带知识库检索和工具调用能力的 AI Assistant,主要面向论文和技术文档问答场景。

项目最开始是一个普通 RAG 系统,用户提问后,系统从 PDF 知识库中检索相关 chunk,再把检索结果拼到 prompt 里让模型回答。但我后来发现,真实场景里的问题不都是知识问答,有些问题需要查文档元信息,有些需要列出已有文件,有些需要根据上一轮对话继续追问,所以我把它升级成了一个 Agent 系统。

架构上,我把系统分成六层:最外层是 FastAPI 服务层,负责请求校验和响应返回;中间用 LangGraph 做编排层,负责问题分类、路由、工具调用和状态流转;底层有 RAG 检索层,用来完成 PDF 解析、切块、向量化和 top-k 检索;同时还有 Tool 层,把知识库检索、文档元信息查询、笔记保存等能力封装成工具;状态存储层负责保存 thread_id、对话状态、工具调用日志;最后用结构化输出层统一返回 answer、citations、tool_trace 和 error。

这个项目的关键点是,我没有让所有问题都强行走 RAG,而是先做问题分类。知识类问题走 RAG,外部任务走工具调用,多轮追问会读取当前 thread 的状态。这样系统比普通 RAG 更灵活,也更接近真实 Agent 应用。

技术难点主要有三个:第一是检索结果不稳定,所以我加了 query rewrite、top-k 控制和引用片段;第二是 Agent 可能循环失控,所以我设置了最大步数、工具错误结构和兜底回答;第三是回答不可追踪,所以我记录了 tool_trace 和问题日志,方便定位是分类、检索还是生成环节出错。

最后我用检索命中率、回答引用覆盖率、工具调用成功率、平均延迟和错误率来评估系统效果。后续如果继续优化,我会加入 hybrid search、rerank、权限控制和更完善的前端可视化。


23. 面试讲解框架

面试官让你介绍项目时,不要上来讲代码细节。

按照这个顺序讲:

1. 业务场景
2. 系统架构
3. 核心流程
4. 关键模块
5. 技术难点
6. 优化与评估

23.1 业务场景

这个项目解决的是私有文档知识库问答和任务执行的问题。用户可以用自然语言询问 PDF 论文、技术文档,也可以让系统执行一些外部任务,比如查询文档元信息、保存学习笔记。相比普通聊天模型,它能基于私有知识回答,并给出引用来源。

23.2 系统架构

系统分为 FastAPI 服务层、LangGraph 编排层、RAG 检索层、Tool 层、状态存储层和结构化输出层。FastAPI 负责接口,LangGraph 负责任务编排,RAG 负责知识检索,Tool 层封装外部能力,状态层保存多轮上下文和日志,输出层保证回答结构统一。

23.3 关键模块

关键模块包括问题分类模块、RAG 检索模块、工具调用模块、状态管理模块和结构化输出模块。问题分类决定走 RAG 还是工具;RAG 模块返回 chunk 和引用;工具模块统一注册和执行工具;状态模块保存 thread_id 下的上下文;输出模块返回 answer、citations 和 tool_trace。

23.4 技术难点

难点主要是三类:第一,检索质量不稳定,需要做 query rewrite、chunk 设计和引用控制;第二,Agent 流程不可控,需要设置最大步数、工具白名单和错误兜底;第三,多轮上下文容易混乱,所以用 thread_id 和 state 管理当前会话状态。

23.5 优化方案

优化方面,我从检索、流程和工程三个方向做。检索上可以加入 hybrid search 和 rerank;流程上可以把固定流程用 workflow,开放任务用 Agent;工程上记录 tool_trace、latency 和 error code,方便调试和评估。

23.6 评估指标

评估指标包括检索命中率、答案引用覆盖率、工具调用成功率、平均延迟、p95 延迟、错误率和用户反馈。对于 RAG,我重点看问题是否检索到正确 chunk;对于 Agent,我重点看工具是否选对、调用是否成功、是否能在有限步骤内完成任务。


24. 面试高频追问

Q1:你这个项目和普通 RAG 有什么区别?

普通 RAG 是固定流程:

问题 -> 检索 -> 回答

我的项目是 Agentic RAG:

问题 -> 分类 -> 路由 -> 检索 / 工具 / 直接回答 -> 状态更新 -> 结构化输出

区别在于:

  1. 不是所有问题都走检索;
  2. RAG 被封装成工具;
  3. 支持外部工具调用;
  4. 支持多轮状态;
  5. 有工具调用 trace;
  6. 有错误兜底和日志。

Q2:为什么要用 LangGraph,不直接 while loop?

简单 Agent 可以用 while loop,但复杂后会出现分支混乱、状态散落、流程不可观察的问题。LangGraph 把流程显式建模成 State、Node、Edge,可以清楚表达分类、路由、检索、工具调用、错误处理等节点,也方便加入 checkpoint、状态恢复和调试。


Q3:如何判断一个问题走 RAG 还是 Tool?

我会先做问题分类,输出结构化分类结果,例如 knowledge_qaexternal_taskfollow_up。知识解释、论文总结、文档对比走 RAG;查询文档数量、保存笔记、查询日志这类动作走 Tool;如果是追问,则结合 thread state 判断是否需要复用上一轮结果。


Q4:怎么处理检索不到结果?

我不会让模型硬答。流程是:

第一次检索为空
  -> query rewrite
  -> 再检索
  -> 仍然为空
  -> 返回未找到足够依据的兜底回答

同时会在日志里记录这类问题,后续分析是不是 chunk 切分、embedding、query 改写或知识库覆盖有问题。


Q5:怎么防止 Agent 无限调用工具?

我设置了:

  1. 最大 step 数;
  2. 最大工具调用次数;
  3. 工具白名单;
  4. 工具超时;
  5. 连续失败停止;
  6. 敏感操作需要确认;
  7. 所有工具错误都结构化返回。

Q6:多轮对话怎么做?

每个会话有 thread_id。同一个 thread 下会保存 messages、检索结果、工具调用结果、引用信息和中间状态。用户追问“它”“刚才那个方法”时,系统可以从当前 state 中恢复上下文。


Q7:引用片段怎么保证可靠?

每次 RAG 检索都会返回 doc_idtitlepagechunk_idsnippet 和相似度分数。生成答案时要求模型只基于这些片段回答,最终 response 中会把 citations 返回给前端。如果没有足够片段,就返回不确定,而不是强行回答。


Q8:如何评估这个系统?

我会分 RAG 和 Agent 两部分评估。

RAG 侧:

  • 检索命中率;
  • top-k recall;
  • 引用覆盖率;
  • 答案是否基于引用。

Agent 侧:

  • 问题分类准确率;
  • 工具选择准确率;
  • 工具调用成功率;
  • 平均工具调用次数;
  • 最大步数内完成率;
  • 平均延迟和 p95 延迟;
  • 错误率。

25. 简历写法建议

可以写成这样:

Knowledge Agent Assistant|基于 RAG + LangGraph 的知识库智能助手

- 基于 FastAPI + LangGraph 构建多工具 Agent 系统,将 PDF 知识库检索、文档元信息查询、学习笔记保存等能力封装为 Tool,并通过状态图实现问题分类、路由、检索、工具调用和结构化输出。
- 设计 RAG 检索层,支持 PDF 解析、chunk 切分、embedding 入库、top-k 检索和引用片段返回,回答中附带 doc_id、page、chunk_id 等来源信息,降低模型幻觉。
- 引入 thread_id 级别状态管理,保存多轮对话上下文、工具调用结果和引用信息,支持用户基于上一轮结果继续追问。
- 设计工具调用 trace、问题日志和错误兜底机制,记录问题类型、工具调用、耗时、错误码等信息,用于调试、评估和优化。

如果简历空间少,可以压缩成两条:

- 基于 FastAPI + LangGraph + Chroma 实现知识库 Agent,将 RAG 检索、文档元信息查询、笔记保存等能力封装为 Tool,支持问题分类、动态路由、多轮状态和结构化输出。
- 设计引用片段、tool trace、错误兜底和问题日志机制,提升回答可追踪性与系统稳定性,并使用检索命中率、工具成功率、延迟等指标评估效果。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐