目录

一个专注音视频领域的小圈子

第三篇讲过工具调用和 Agent,第四篇到第七篇讲了 RAG。

这一篇把两条线合在一起:给 Agent 一组博客知识库工具,让它自己决定什么时候检索文章、什么时候读取来源、什么时候直接回答。

本文对应代码位于 code/langchain-demo/chapter08

本篇目标

第八篇要实现的是一个“博客问答 Agent”。

它有三个工具:

  1. search_blog_posts:根据问题检索博客文章。
  2. list_blog_categories:列出博客分类。
  3. read_blog_source:读取某篇博客正文开头。

这样用户问博客内容时,Agent 不需要凭空回答,而是先调用工具检索资料。

代码结构

第八篇代码结构如下:

chapter08/
  __init__.py
  config.py
  llm.py
  rag_utils.py
  blog_tools.py
  blog_qa_agent.py

其中:

  • rag_utils.py:复用前面 RAG 的读取、切分、检索逻辑。
  • blog_tools.py:把检索能力包装成 LangChain 工具。
  • blog_qa_agent.py:创建 Agent 并运行问题。

定义博客工具

工具代码放在 blog_tools.py

最重要的是 search_blog_posts

@tool
def search_blog_posts(question: str) -> str:
    """根据用户问题检索本地博客文章,返回相关片段和来源。"""

    documents = load_documents()
    chunks = split_documents(documents)
    relevant_chunks = retrieve(question, chunks, top_k=4)

    if not relevant_chunks:
        return "没有检索到相关博客片段。"

    return (
        f"命中关键词:{keyword_report(question, relevant_chunks)}\n\n"
        f"{format_context(relevant_chunks)}"
    )

这个工具内部还是做 RAG 检索,但对 Agent 来说,它只是一个可调用工具。

也就是说,模型看到的是:

我有一个 search_blog_posts 工具,可以根据问题检索本地博客文章。

至于工具里面是关键词检索、向量检索,还是调用数据库,Agent 并不关心。

这就是工具抽象的好处。

读取来源工具

除了搜索,还提供了一个读取文章正文的工具:

@tool
def read_blog_source(source: str) -> str:
    """读取某篇博客 Markdown 的正文开头,source 必须来自检索结果。"""

这里加了路径限制:

path = (REPO_ROOT / source).resolve()
blog_root = BLOG_POST_ROOT.resolve()

if not path.is_file() or blog_root not in path.parents:
    return "只能读取 blog/content/post 目录下的文章。"

这个限制很重要。

工具一旦给 Agent 使用,就要考虑边界,不能让它随便读任意路径。

创建 Agent

Agent 创建代码和第三篇类似:

agent = create_agent(
    model=create_chat_model(temperature=0),
    tools=TOOLS,
    system_prompt=(
        "你是一个本地博客问答 Agent。"
        "回答博客内容相关问题时,必须先调用 search_blog_posts。"
        "如果用户询问分类,可以调用 list_blog_categories。"
        "最终答案要用中文,并列出参考来源。"
    ),
)

这里的 system prompt 明确要求:回答博客内容相关问题时,必须先调用 search_blog_posts

否则模型可能会直接根据已有知识回答,这就失去了 RAG 的意义。

运行 demo

code/langchain-demo 目录下运行:

uv run python -m chapter08.blog_qa_agent "Python 函数式编程讲了什么?请给出参考文章。"

输出里会打印消息过程:

Messages:
- human: Python 函数式编程讲了什么?请给出参考文章。
- ai:
  tool_calls: [...]
- tool: 命中关键词:...
- ai: ...

这里可以看到 Agent 的执行过程:

  1. 用户提出问题。
  2. 模型决定调用 search_blog_posts
  3. 工具返回检索片段。
  4. 模型基于工具结果生成最终回答。

这比直接看最终答案更有价值,因为可以确认模型是否真的用了工具。

Agent 和 RAG 的关系

RAG 是一种给模型补充上下文的方法。

Agent 是一种让模型根据目标自主选择工具的执行方式。

两者可以组合:

Agent 负责决策
RAG 工具负责查资料
LLM 负责总结回答

在简单问答里,直接写 RAG chain 就够了。

但如果用户问题类型多了,比如有时候查分类、有时候读文章、有时候做总结,就可以把这些能力做成工具,让 Agent 来调度。

小结

这一篇把第三篇的 Agent 和前几篇的 RAG 合在了一起。

到这里,我们已经不是只写一条固定链路,而是让模型通过工具访问本地博客知识库。

下一篇继续往前补一个常见能力:多轮对话。用户不会每次都把问题说完整,所以需要让系统记住上一轮说了什么。

参考

  • code/langchain-demo/chapter08
  • LangChain @tool
  • LangChain create_agent
  • RAG 工具化

原创文章,转载请注明来源:    LangChain 入门学习第八篇-带工具的博客问答 Agent