目录
前面九篇已经把第一阶段的内容基本走完了:
- 环境准备和模型调用。
- PromptTemplate 和结构化输出。
- 工具调用和 Agent。
- 本地 Markdown RAG。
- 向量检索 RAG。
- 持久化索引。
- 检索质量调优。
- 带工具的博客问答 Agent。
- 多轮对话和上下文记忆。
这一篇做一个收束:把本地博客问答封装成一个简单 CLI。
本文对应代码位于 code/langchain-demo/chapter10。
本篇目标
第十篇不再引入新概念,而是做一个能直接使用的命令行入口:
uv run python -m chapter10.app search "FFmpeg 硬解码" --category ffmpeg
uv run python -m chapter10.app ask "Python 函数式编程讲了什么?"
其中:
search:只检索文章,不调用模型。ask:先检索文章,再调用模型回答。
这样平时调试时,可以先用 search 看检索结果,再用 ask 看最终回答。
代码结构
第十篇代码结构如下:
chapter10/
__init__.py
config.py
llm.py
rag_utils.py
app.py
其中:
rag_utils.py:读取、切分、检索、格式化上下文。app.py:命令行入口。
这一章的目标是把前面几篇的能力整理成一个更像工具的入口。
argparse 子命令
CLI 使用 Python 标准库 argparse。
核心代码如下:
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Local blog QA CLI")
subparsers = parser.add_subparsers(dest="command")
ask_parser = subparsers.add_parser("ask", help="retrieve context and ask LLM")
ask_parser.add_argument("question", nargs="?", default=DEFAULT_QUESTION)
ask_parser.add_argument("--top-k", type=int, default=4)
ask_parser.add_argument("--category")
search_parser = subparsers.add_parser("search", help="only show retrieved posts")
search_parser.add_argument("question", nargs="?", default=DEFAULT_QUESTION)
search_parser.add_argument("--top-k", type=int, default=4)
search_parser.add_argument("--category")
这里把 ask 和 search 分成两个子命令。
它们共享几个参数:
question:用户问题。--top-k:返回多少个结果。--category:限定博客分类。
如果不传子命令,默认执行 ask。
search 命令
search 不调用模型,只展示检索结果:
if args.command == "search":
results = retrieve(args.question, chunks, args.top_k, args.category)
print_search_results(results)
return
运行:
uv run python -m chapter10.app search "FFmpeg 硬解码" --category ffmpeg
输出类似:
Sources:
1. FFmpeg 调用 MediaCodec 硬解码到 Surface 上
blog/content/post/ffmpeg/FFmpeg 调用 MediaCodec 硬解码到 Surface 上/index.md
...
2. FFmpeg 调用 Android MediaCodec 进行硬解码(附源码)
...
这个命令很适合调试。
如果 search 的结果都不相关,那就不用急着看 ask 的回答,应该先调整检索参数。
ask 命令
ask 会先检索,再调用模型:
results = retrieve(args.question, chunks, args.top_k, args.category)
answer = answer_question(args.question, results)
模型调用逻辑还是熟悉的 LCEL:
chain = prompt | create_chat_model(temperature=0.2) | StrOutputParser()
return chain.invoke(
{
"question": question,
"context": format_context(chunks),
}
)
运行:
uv run python -m chapter10.app ask "Python 函数式编程讲了什么?"
输出会先列出检索来源,再给出回答。
这种输出顺序很实用,因为可以先判断模型回答是不是基于正确资料。
为什么要先做 CLI
很多 AI 应用一开始就想做 Web 页面。
但在学习阶段,我更建议先做 CLI。
原因很简单:
- CLI 更容易调试。
- 不需要处理前端状态。
- 输入输出清楚。
- 很适合验证 RAG、Agent、Memory 这些核心链路。
等 CLI 跑顺了,再把它封装成 Web API 或页面,会更稳。
第一阶段小结
到第十篇为止,第一阶段已经完成。
这一阶段的目标不是做一个大而全的应用,而是把 LangChain 常用的基础能力都实践一遍:
LLM 调用
PromptTemplate
结构化输出
Tool Calling
Agent
Document
Text Splitter
Retriever
RAG
Memory
CLI 封装
后面如果继续写第二阶段,就可以往更接近真实项目的方向走:
- 接入真正的 embedding 模型。
- 使用 Chroma、FAISS 或 PGVector。
- 做增量索引。
- 做 Web API。
- 加入评测集和自动化评估。
- 接入 LangSmith 或其他 tracing 工具。
小结
这一篇完成了第一阶段最后一个 demo:本地知识库问答 CLI。
它不是一个复杂应用,但已经能把前面几篇的内容串起来。
从这里开始,继续扩展就很自然了:先把 CLI 打磨稳定,再接向量数据库、Web 服务、评估和部署。
参考
code/langchain-demo/chapter10- Python
argparse - LangChain
ChatPromptTemplate - LangChain
StrOutputParser
原创文章,转载请注明来源: LangChain 入门学习第十篇-封装一个本地知识库问答 CLI
相关文章
- LangChain 入门学习第九篇-多轮对话和上下文记忆
- LangChain 入门学习第八篇-带工具的博客问答 Agent
- LangChain 入门学习第七篇-RAG 检索质量优化
- LangChain 入门学习第六篇-持久化索引和本地知识库问答
- LangChain 入门学习第五篇-使用本地 Embeddings 实现向量检索 RAG
- LangChain 入门学习第四篇-基于本地 Markdown 的 RAG 问答
- LangChain 入门学习第三篇-工具调用和 Agent
- LangChain 入门学习第二篇-PromptTemplate 和结构化输出
- LangChain 入门学习第一篇-环境准备和模型调用
- 使用 Cursor 进行 AI 编程的年度总结