← 返回博客
python2026-09-16 11:43:432 分钟 · 710 0

LangChain 原理与实战(五):综合实战,搭一个知识库问答 Agent

把前四篇串起来:一个能读你文档、能调接口、还能接着上一句聊的问答 Agent。覆盖文档检索工具化、多轮记忆、以及一条最小可跑的完整代码,并点出上线前要补的工程化细节。

前四篇各讲一块:链、LCEL、RAG、Agent。这一篇把它们揉成一个能用的东西:一个问答 Agent,问它公司的事,它会去你的文档里查;问它实时数据,它会调接口;聊到第三句,它还记得第一句说的啥。

下面是一份最小可跑的完整代码,然后把几个上线前必补的点拆开说。

完整代码

from langchain_openai import ChatOpenAI
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.tools.retriever import create_retriever_tool

# 1) 检索这半边:复用第三篇建的库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(persist_directory="./db", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

retriever_tool = create_retriever_tool(
    retriever,
    "search_docs",
    "搜索公司内部文档,回答产品功能、退款政策等问题时使用。",
)

# 2) 工具这半边:一个实时接口
@tool
def get_weather(city: str) -> str:
    """查询城市当前天气,返回温度和状况。"""
    return f"{city} 22 度,多云,风力 3 级"

# 3) 模型 + 提示词(含多轮占位)
model = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是公司客服助手,只根据工具返回的信息回答,不知道就说不知道。"),
    MessagesPlaceholder("chat_history"),
    ("human", "{input}"),
    MessagesPlaceholder("agent_scratchpad"),
])

agent = create_tool_calling_agent(model, [retriever_tool, get_weather], prompt)
executor = AgentExecutor(agent=agent, tools=[retriever_tool, get_weather], verbose=True)

ans = executor.invoke({"input": "我们的退款政策是怎样的?"})
print(ans["output"])

跑通这条,你就拥有了一个会查文档、会调接口的助手。但它现在每次都是"失忆"的,同一会话多问几句就接不上。下面补记忆。

多轮记忆怎么接

AgentExecutor 默认不存历史。要续上上下文,把历史消息在调用前塞进 chat_history,并在每轮结束后把新消息追回去。LangChain 提供了现成的包装:

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}
def get_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

chain_with_history = RunnableWithMessageHistory(
    executor,
    get_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)

chain_with_history.invoke(
    {"input": "退款政策里写明几天到账?"},
    config={"configurable": {"session_id": "u_001"}},
)
chain_with_history.invoke(
    {"input": "那超时了怎么投诉?"},
    config={"configurable": {"session_id": "u_001"}},
)

session_id 区分不同用户,get_history 按它取出对应会话的消息。第二次问"超时了怎么投诉",模型已经看得到第一轮聊过退款。生产里 store 要换成 Redis 这类外部存储,别放进程内存。

上线前还得补的几件事

  • 检索质量k 取几、块切多大,决定了答案靠不靠谱。先离线拿真实问题测一遍召回,别等用户骂了再调。
  • 工具描述:docstring 写清"什么时候该用",模型才调得准。描述含糊,它要么不调、要么乱调。
  • 超时与降级:工具调外部 API 会挂。AgentExecutormax_iterations 防死循环,单个工具加自己的超时和兜底返回值。
  • 成本控制:Agent 可能连调好几次模型,一次问答的 token 数不好预估。接 with_config 或回调把每次用量记下来,设个上限。
  • 别让它编:system 提示词钉死"不知道就说不知道",检索工具返回空时也别硬答。

这个系列到这就收了

五篇走下来,你手里有了:一套核心抽象的认知(一)、能写顺的 LCEL 链(二)、能查文档的 RAG(三)、会调工具的 Agent(四)、以及把它们拼起来的问答助手(五)。

LangChain 的接口还在变,但"模型只管生成、脏活抽象成层、用管道组合"这条主线很稳。后面你接新向量库、换模型、加工具,都是在替换某个 Runnable 节点,主干不用动。真到要管更复杂的多步状态、人机协作,再去碰 LangGraph,那是 Agent 这条线的下一站。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/214