📋 摘要

LangChain 的核心抽象有哪些?解决了什么问题?

核心抽象

LangChain 把「用 LLM 构建应用」拆成一组可组合的抽象单元:

抽象作用
Model(LLM / ChatModel)统一封装各类模型调用,屏蔽厂商 API 差异
Prompt Template结构化地构造 prompt,变量插值、少样本、部分填充
Output Parser把模型输出解析成结构化对象(JSON/Pydantic)
Retriever统一「按 query 取相关文档」的接口
Vector Store向量存储与相似度检索
Embedding文本 → 向量的统一接口
Tool让模型调用的函数/API 封装
Memory跨轮次状态(对话历史)管理
Runnable / Chain把上述单元组合成可执行流水线

解决了什么问题

  1. 统一接口:换模型(OpenAI → Claude → 本地)只需改配置,业务代码不变。
  2. 组合编排:把 prompt、模型、解析器串成一条链,减少样板代码。
  3. 生态复用:loader/splitter/retriever 等常用组件开箱即用。
  4. 可观测:统一 callback/tracing,方便调试与监控。

核心思想

  • 「一切皆 Runnable」:每个组件实现统一的 invoke/stream/batch 接口,可用管道符自由组合。
💡 面试加分项

把抽象按「模型 / prompt / parser / retriever / tool / memory」分类列出,并点出「统一接口 + 可组合(Runnable)」是 LangChain 的两大设计内核。


Prompt Template 是什么?有哪些类型?

定义

Prompt Template:把 prompt 中变化的部分用占位变量表示,运行时填充,从而复用 prompt 结构。它是「提示工程」的工程化封装。

Python
from langchain_core.prompts import PromptTemplate
t = PromptTemplate.from_template("把下面内容翻译成{target_lang}:{text}")
t.format(target_lang="英文", text="你好")
# -> "把下面内容翻译成英文:你好"

类型

类型适用说明
StringPromptTemplateLLM(纯文本模型)输出字符串 prompt
ChatPromptTemplateChatModel构造 messages 列表,区分 system/human/ai 角色
MessagesPlaceholder动态消息占位一段消息列表(如历史对话)
FewShotPromptTemplate少样本自动拼入 example 列表
PipelinePromptTemplate多步组合把一个 prompt 的结果喂给下一个

关键特性

  • partial(部分填充):先填一部分变量,剩余运行时再填。
  • 变量校验:缺变量时报错,避免拼出残缺 prompt。
  • 生产中用 ChatPromptTemplate.from_messages([("system", ...), ("human", ...)]) 最普遍。
💡 面试加分项

说出「占位变量 + 运行时填充」,并能区分 String vs Chat 模板与 MessagesPlaceholder、FewShot 的用途。


LLM 和 ChatModel 有什么区别?

区别

维度LLMChatModel
输入输出纯字符串消息列表(system/human/ai/tool)
底层模型文本补全(如 text-davinci)对话模型(GPT/Claude 等)
Prompt 构造字符串模板ChatPromptTemplate(角色区分)
适用补全、老模型现代对话/Agent(主流)

现状

  • 现代模型基本都是 ChatModel(messages 接口);LLM 是兼容旧「纯补全」模型的抽象。
  • 二者都实现了统一的 Runnable 接口(invoke/stream/batch),可互换进 chain。

示例

Python
# ChatModel:输入输出都是消息
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
resp = model.invoke([("human", "你好")])   # AIMessage
Text
# LLM:纯字符串
prompt = "给这家公司起个名字"
resp = llm.invoke(prompt)   # str
💡 面试加分项

一句话「LLM 用字符串、ChatModel 用消息列表」,并点出「现代模型都是 ChatModel,二者同为实现 Runnable 的可互换接口」。


output parser 是什么?如何做结构化输出?

定义

Output Parser:把 LLM 的文本输出解析成结构化对象(dict/Pydantic 模型),是「非结构化 → 结构化」的桥梁。

如何做结构化输出

  1. 提示约束:在 prompt 中给出期望格式(如「只输出 JSON」)。
  2. 模型生成:LLM 输出一段文本。
  3. 解析器解析:parser 把文本转成目标结构,失败则抛错或重试。
Python
from langchain_core.output_parsers import JsonOutputParser
parser = JsonOutputParser()
parser.parse('{"name": "张三", "age": 30}')  # -> {"name": "张三", "age": 30}

常见 parser

Parser作用
StrOutputParser直接取字符串(最常用)
JsonOutputParser解析 JSON
PydanticOutputParser解析成 Pydantic 对象(带 schema 校验)
CommaSeparatedListOutputParser解析成列表
StructuredOutputParser通用结构化

进阶:结构化输出(function calling)

  • 现代更可靠做法:用工具的 JSON Schema 约束输出(with_structured_output),让模型直接产出合法结构,而不是「生成文本再解析」。
💡 面试加分项

答出「prompt 约束 + parser 解析」两段,并点出「PydanticOutputParser 带 schema 校验」与「with_structured_output 用 function calling 更可靠」的演进。


chain 是如何组合的?有哪些组合方式?

组合方式

方式说明
顺序链 SequentialChainA → B → C 依次执行,上一输出是下一输入
路由链 RouterChain按条件把输入路由到不同子链
MapReduce / MapRerank多路并行处理后汇总
条件分支依中间结果决定走哪条路径

传统 Chain 与 LCEL

  • 旧版用 SequentialChain、LLMChain 等类组合;新版推荐用 LCEL 管道符 | 组合 Runnable,更简洁。
Python
# LCEL 组合 prompt -> model -> parser
chain = prompt | model | StrOutputParser()
result = chain.invoke({"question": "..."})

原理

  • chain 本质是一个有向图(DAG):节点是 Runnable,边是数据流。
  • 组合后得到的仍是 Runnable,可继续嵌套、复用。
💡 面试加分项

报出「顺序 / 路由 / MapReduce / 条件」四种组合,并点出「组合结果仍是 Runnable、可无限嵌套」这一设计要点。


LCEL(LangChain Expression Language)的引入动机是什么?

动机

早期 LangChain 用 LLMChain、SequentialChain 等类继承体系组合组件,带来问题:

  1. 组合僵硬:每个组合都要新建类,嵌套复杂。
  2. 接口不统一:不同组件调用方法五花八门(predict、run、generate)。
  3. 难以复用:子链难以抽出来独立复用。
  4. 难做异步/流式:流式、批量、回退等横切能力难以统一实现。

LCEL 是什么

  • 声明式组合语法:用 |(管道符)把 Runnable 串起来,类似 Unix 管道 / RxJS。
  • 统一接口:invoke / stream / batch / ainvoke / astream / abatch。
  • 自动获得:流式、异步、批处理、重试、回退、并行等能力。
Python
chain = prompt | model | parser     # 声明式,可读性高

结果

  • 组合从「继承 + 配置对象」变成「函数式组合」,更简单、更可测试、更易扩展。
💡 面试加分项

答出「旧类继承组合僵硬、接口不统一」的痛点,并点出 LCEL 用 | 声明式组合 + 统一 Runnable 接口,自动获得流式/异步/批处理等能力。


管道符 | 和 Runnable 是什么关系?

关系

  • | 是 LCEL 的组合操作符,Runnable 是被组合的单元。
  • 所有组件(prompt/model/parser/retriever…)都实现 Runnable 协议,因此都能用 | 串起来。
Python
chain = prompt | model | parser
# 等价于把三个 Runnable 依次连接,前一个的输出作为后一个的输入

Runnable 协议

Text
invoke(input) -> output          # 同步执行
ainvoke(input) -> output         # 异步执行
stream(input) -> Iterator        # 流式
batch(inputs) -> list            # 批量

原理

  • a | b 内部生成一个 RunnableSequence,执行时把 a 的输出传给 b。
  • 每个 Runnable 自带配置传递(config 含 callbacks、metadata、tags),沿链自动传播。

好处

  • 任意两个满足协议的组件都能自由组合;组合结果仍是 Runnable,可继续 |。
💡 面试加分项

答「| 是组合操作符、Runnable 是可组合单元」,并背出 Runnable 的 invoke/stream/batch 接口与「输出接输入、结果仍是 Runnable」的要点。


RunnableParallel、RunnableLambda、RunnablePassthrough 分别是什么?

RunnableParallel

  • 并行执行多个 Runnable,输入分发给每个分支,结果合并成 dict。
Python
chain = RunnableParallel(joke=prompt | model, poem=prompt2 | model)
result = chain.invoke({"topic": "猫"})   # {"joke": ..., "poem": ...}
  • 用途:一次请求同时产出多路结果,降低多轮串行延迟。

RunnableLambda

  • 把任意 Python 函数包装成 Runnable,使其能进 chain。
Python
from langchain_core.runnables import RunnableLambda
upper = RunnableLambda(lambda x: x.upper())
chain = prompt | model | upper
  • 用途:在链中插入自定义处理逻辑(清洗、转换、后处理)。

RunnablePassthrough

  • 原样透传输入,或从输入中抽取字段,常用于「保留原始输入供后续使用」。
Python
chain = RunnablePassthrough.assign(answer=prompt | model)
# 输入 {"question": ...} -> 输出 {"question": ..., "answer": ...}
  • assign:在透传的同时追加新字段。
💡 面试加分项

分别一句话:Parallel=并行分支合并、Lambda=把函数包成 Runnable、Passthrough=透传/抽取字段,并举例 assign 保留原始输入。


with_fallbacks 是什么?如何做容错?

定义

with_fallbacks:给一个 Runnable 配置多个备用实现,主实现失败(异常/超时)时自动降级到下一个,是链级容错机制。

Python
robust = primary_chain.with_fallbacks([backup_chain, last_resort_chain])

工作机制

  1. 按顺序尝试主链 → 备用链1 → 备用链2。
  2. 触发条件:抛出指定的异常类型(如 RateLimitError、TimeoutError)。
  3. 某个成功即返回,全部失败才抛错。
Python
from langchain_core.runnables import RunnableLambda
chain = main.with_fallbacks(
    [backup],
    exceptions_to_handle=(TimeoutError,),
)

容错场景

场景降级策略
主模型限流/超时切到备用模型/供应商
强模型太贵失败后用小模型兜底
某个下游不可用返回缓存/默认答案

注意

  • 备用链要保证输入输出接口一致;fallback 是「整链替换」,不是「单节点重试」(重试用 with_retry)。
💡 面试加分项

答「主链失败自动降级到备用链」,并说清「按指定异常类型触发、按顺序尝试」,区分 with_fallbacks(降级)与 with_retry(重试)。


LangChain 0.1、0.2、1.0 版本有哪些变化?

版本演进

版本时间关键变化
0.12024.1首个稳定版,包拆分(langchain-core、langchain-community、langchain-openai…)
0.22024.5继续模块化,移除弃用 API,LCEL 成为组合主范式
0.32024.9进一步清理,langgraph 独立成核心编排层
1.02025.10首个大版本:聚焦稳定性与长期维护,langchain 定位为「编排上层」,Agent 与状态编排转向 LangGraph

关键趋势

  1. 单体包 → 模块化包:核心抽象在 langchain-core,各供应商集成在 langchain-<vendor>。
  2. LCEL / Runnable 成为统一接口:淘汰旧的 LLMChain 类组合。
  3. LangGraph 接管 Agent/状态编排:复杂流程、Agent 用 LangGraph,LangChain 提供组件与轻量链。
  4. 1.0 语义:明确「不追求功能堆叠,追求 API 稳定、可长期依赖」。

意义

  • 理解版本演进能体现「对生态架构有整体把握」,面试常考「LangChain 与 LangGraph 的关系」。
💡 面试加分项

答出「0.1 模块化拆分、0.2 LCEL 成主流、1.0 稳定化并让 Agent 编排转向 LangGraph」这条主线即可。


memory(BufferMemory)是如何工作的?有哪些类型?

工作原理

  • Memory 负责把历史对话状态存入/取出,让链在多次调用间「记住」上下文。
  • 调用前:把历史消息注入 prompt;调用后:把本轮交互写回 memory。
Text
用户: 我叫小明
模型: 你好小明
用户: 我叫什么?   <- memory 里有「我叫小明」,能答出

类型

类型说明
ConversationBufferMemory完整保存所有历史消息(最直白,会爆窗口)
ConversationBufferWindowMemory只保留最近 K 轮(滑动窗口)
ConversationSummaryMemory用 LLM 把历史摘要压缩
ConversationSummaryBufferMemory超出窗口的部分摘要、近端保留原文
ConversationTokenBufferMemory按 token 上限截断
VectorStoreRetrieverMemory历史存向量库,按相关性检索(长期记忆)

注意

  • 传统 ConversationBufferMemory 已弃用,新版推荐用 RunnableWithMessageHistory + BaseChatMessageHistory 管理对话历史。
💡 面试加分项

报出「Buffer / BufferWindow / Summary / Token / VectorStore」几类,并点出「旧 memory 弃用、新范式用 RunnableWithMessageHistory」的演进。


bind_tools 是什么?如何绑定工具?

定义

bind_tools:把工具 schema(名称、描述、参数)绑定到 ChatModel 上,使模型在下一次调用时能返回 tool call(结构化调用指令)。

Python
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气"""
    return "北京今天 18°C"

model_with_tools = model.bind_tools([get_weather])
ai_msg = model_with_tools.invoke("北京天气怎么样")
# ai_msg.tool_calls -> [{"name": "get_weather", "args": {"city": "北京"}}]

原理

  1. 工具被序列化成 JSON Schema 传给模型。
  2. 模型判断需要工具时,返回 tool_calls(含函数名 + 参数)。
  3. 宿主执行工具,结果以 tool role 回传,模型继续生成。

关键点

  • 绑定后模型并不会真正执行工具,只是输出调用指令。
  • 工具的 description(含 docstring)质量决定模型选工具的准确率。
  • 多个工具一次绑定,模型可做「选择 + 参数抽取」,支持并行调用。
💡 面试加分项

答「把工具 JSON Schema 绑到模型上,让模型返回 tool_call,真正执行仍在宿主」,并强调「工具描述质量决定调用准确率」。


retriever 抽象是什么?如何自定义?

定义

Retriever:统一的「输入 query,输出相关文档列表」的接口,是 RAG 中「检索」环节的抽象。

Text
Retriever.get_relevant_documents(query) -> List[Document]
  • 屏蔽底层实现(向量检索 / BM25 / 图检索),让上层 chain 无感切换。

如何自定义

实现 BaseRetriever,重写 _get_relevant_documents:

Python
from langchain_core.retrievers import BaseRetriever
from langchain_core.documents import Document

class MyRetriever(BaseRetriever):
    def _get_relevant_documents(self, query: str, **kwargs):
        # 自定义检索逻辑:向量库 / 数据库 / 远程 API
        return [Document(page_content="命中内容", metadata={"source": "..."})]

常见 retriever

Retriever说明
VectorStoreRetriever从向量库按相似度检索
MultiQueryRetriever用 LLM 生成多个 query 分别检索再合并
ParentDocumentRetriever检索小块、返回父块大上下文
SelfQueryRetriever从 query 解析元数据过滤条件
EnsembleRetriever多路检索结果融合(RRF)
💡 面试加分项

答出「query → List[Document] 的统一接口」,并能说「继承 BaseRetriever 重写 _get_relevant_documents 即可自定义」,再报几个内置 retriever。


vector store 抽象是什么?如何接入?

定义

Vector Store:负责向量存储与相似度检索的抽象,RAG 的「知识库底座」。

Text
add_documents(docs) / add_texts(texts)     # 写入(先 embedding 再入库)
similarity_search(query, k)                # 按相似度检索 top-k
similarity_search_with_score(query, k)     # 带分数检索
as_retriever()                             # 转成 Retriever 接进 chain

如何接入

Python
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
chain = retriever | prompt | model | parser

常见实现

Vector Store特点
Chroma轻量,适合原型/本地
FAISSMeta 开源,高性能库
Milvus / Qdrant / Weaviate生产级分布式
pgvectorPostgreSQL 扩展,与关系库同源

关键点

  • 写入时自动调 embedding 向量化;检索时对 query 向量化后做 ANN 检索。
  • 切换 vector store 只需换实现类,as_retriever() 后上层代码不变。
💡 面试加分项

答「统一了 写入 + 相似度检索 接口,as_retriever() 无缝接 chain」,并报 Chroma/FAISS/Milvus/pgvector,点出「换实现不改上层」。


text splitter 有哪些类型?如何选择?

类型

Splitter做法特点
CharacterTextSplitter按单个字符(如 \n)切最基础
RecursiveCharacterTextSplitter按 \n\n → \n → 空格 → 字符 层级递归切默认首选,保持结构
TokenTextSplitter按 token 数切精确控制 token
MarkdownHeaderTextSplitter按 Markdown 标题结构切保留文档层级
CodeSplitter按语言语法切(函数/类)代码场景
SemanticChunker按 embedding 相似度找语义边界质量高、成本高

如何选择

  • 通用文本 → RecursiveCharacterTextSplitter。
  • Markdown/文档 → MarkdownHeaderTextSplitter(按标题)。
  • 代码 → 对应语言的 CodeSplitter。
  • 要精确 token 预算 → TokenTextSplitter。
  • 追求语义完整 → SemanticChunker(慢)。

关键参数

  • chunk_size:块大小(常见 256~1024 token)。
  • chunk_overlap:块间重叠(10%~20%),避免语义被切断。
  • 原则:块过大检索噪声多、块过小语义破碎。
💡 面试加分项

报出「递归字符切分是默认首选、按标题/代码结构切分保语义」,并说出 chunk_size 与 overlap 的权衡。


document loader 有哪些类型?

定义

Document Loader:把各类数据源加载为 Document 对象(page_content + metadata),是 RAG 的数据入口。

类型

类别示例 loader
文件TextLoader、CSVLoader、PyPDFLoader、UnstructuredMarkdownLoader、DocxLoader
网页WebBaseLoader、RecursiveUrlLoader、SitemapLoader
数据库SQLDatabaseLoader、各类数据库 loader
云/企业NotionLoader、GoogleDriveLoader、ConfluenceLoader、GitHubLoader
富媒体UnstructuredImageLoader、UnstructuredPDFLoader(含表格/OCR)

通用加载方式

Python
from langchain_community.document_loaders import DirectoryLoader, TextLoader

loader = DirectoryLoader("./docs", glob="**/*.md", loader_cls=TextLoader)
docs = loader.load()   # -> List[Document]

关键点

  • 核心产物是 Document 列表(正文 + 元数据),后续接 splitter → embedding → vector store。
  • 复杂格式(PDF 表格、扫描件)常用 Unstructured 生态(OCR/版面解析)。
💡 面试加分项

答「把任意数据源加载成 Document」,按「文件/网页/数据库/企业/富媒体」分类报例,并点出「加载后接 splitter → embedding 的固定链路」。


embedding 抽象是什么?如何接入不同的 embedding?

定义

Embedding 抽象:统一的「文本 → 向量」接口,屏蔽不同 embedding 模型/服务的差异。

Text
embed_documents(texts) -> List[vector]   # 文档侧(离线建库)
embed_query(text)      -> vector         # 查询侧(在线检索)

如何接入不同 embedding

Python
from langchain_openai import OpenAIEmbeddings   # OpenAI
# from langchain_huggingface import HuggingFaceEmbeddings  # 本地 BGE 等
# from langchain_google_genai import GoogleGenerativeAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vecs = embeddings.embed_documents(["你好", "世界"])
  • 换服务只改实现类,vectorstore 与上层代码不用改。

关键点

  • 文档与查询分两接口:不对称检索(query/doc 用不同 prompt 或模型)得以支持。
  • 选择看:语言能力(中文选 BGE)、维度、检索专用性、成本/延迟(API vs 本地)。
  • embedding 维度必须与向量库建索引时一致。
💡 面试加分项

答「embed_documents / embed_query 两接口统一不同服务」,并点出「换实现不改上层、query 与 doc 可不对称」两个细节。


callbacks 和 tracing 如何工作?

callbacks

  • 回调钩子:在链/模型执行的生命周期事件上挂自定义函数,实现日志、监控、计费等横切逻辑。
Text
on_llm_start / on_llm_end        # 模型调用前后
on_chain_start / on_chain_end    # 链执行前后
on_tool_start / on_tool_end      # 工具调用前后
on_retriever_start / on_retriever_end
Python
from langchain_core.callbacks import BaseCallbackHandler

class MyHandler(BaseCallbackHandler):
    def on_llm_end(self, response, **kwargs):
        print("token 用量:", response.llm_output)

tracing

  • tracing 是 callbacks 的高级应用:把整条执行轨迹(trace)与子步骤(span)记录下来,形成调用树。
  • 通过 config={"callbacks": [...]} 或环境变量(LANGCHAIN_TRACING_V2=true)把轨迹上报到 LangSmith / Langfuse。

用途

  • 调试(看每步输入输出)、评估(轨迹打分)、成本分析、回归测试。
💡 面试加分项

答「在 on_llm_start/on_chain_end 等生命周期挂钩子」,并点出「tracing = 把 trace/span 调用树上报 LangSmith/Langfuse,用于调试与评估」。


streaming(astream)如何实现?

概念

  • streaming:不等待完整结果,而是逐 token/逐块返回,让前端实时显示,降低感知延迟。
  • astream 是异步流式版本,astream_events 提供更细粒度的事件流。

实现

Python
chain = prompt | model | StrOutputParser()

# 同步流式
for chunk in chain.stream({"topic": "AI"}):
    print(chunk, end="")     # 逐 token 打印

# 异步流式
async for chunk in chain.astream({"topic": "AI"}):
    print(chunk, end="")

关键点

  • LCEL 组合的链自动支持流式:上游 Runnable 的流式会「穿透」整条链(只要下游也支持)。
  • token 流(模型)与工具调用流(增量解析 JSON)是两层。
  • 复杂场景用 astream_events 获取 on_llm_start/on_token/on_tool_start/... 事件序列。

注意

  • StrOutputParser 会把 token 逐块透出;某些 parser(如 JSON)需等完整输出才能解析,会破坏纯 token 流。
💡 面试加分项

答「用 stream/astream 逐块产出、LCEL 自动透传流式」,并点出「JSON 类 parser 需完整输出才能解析」的限制与 astream_events 事件流。


batch 和 abatch 有什么区别?

区别

维度batchabatch
执行方式同步异步
并发内部可并发处理多个输入异步并发,适合高吞吐
返回同步结果列表需 await

用法

Python
inputs = [{"question": "1+1"}, {"question": "2+2"}]

results = chain.batch(inputs)           # 同步,返回 [r1, r2]
results = await chain.abatch(inputs)    # 异步并发

关键点

  • batch 会把多个输入并发执行(受 max_concurrency 限制),比循环 invoke 更快。
  • abatch 是 batch 的异步版本,适合在 asyncio 服务里高并发调用。
  • 三者都是 Runnable 统一接口的一部分:invoke(单条)、batch/abatch(多条)、stream/astream(流式)。
💡 面试加分项

答「batch 同步、abatch 异步并发」,并点出「batch 内部并发处理多个输入、受 max_concurrency 限制,比循环 invoke 快」。


RunnableWithMessageHistory 是什么?

定义

  • 在链上自动管理对话历史:调用前从存储加载历史消息,调用后把本轮交互写回,实现多轮会话。
Python
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

chain = prompt | model
with_history = RunnableWithMessageHistory(
    chain,
    get_session_history=lambda session_id: ChatMessageHistory(),
    input_messages_key="input",
    history_messages_key="history",
)
resp = with_history.invoke(
    {"input": "我叫小明"},
    config={"configurable": {"session_id": "user-1"}},
)

工作原理

  1. 用 session_id 定位某个会话的历史存储(get_session_history 返回)。
  2. 取出历史,作为 history 变量注入 prompt(配合 MessagesPlaceholder)。
  3. 执行链,然后把本轮输入 + 输出写回历史。

关键点

  • session_id 是会话隔离的键,多用户靠它区分。
  • 底层 BaseChatMessageHistory 可接内存 / Redis / 数据库。
  • 这是新版推荐的多轮记忆方案(替代旧 ConversationBufferMemory)。
💡 面试加分项

答「用 session_id 加载/写回历史,替代旧 memory」,并点出「历史通过 MessagesPlaceholder 注入、存储可换内存/Redis/DB」的要点。


LangChain 的 caching 如何实现?

为什么缓存

  • 相同请求避免重复调 LLM,降成本、降延迟。

实现方式

  1. 内存缓存(开发用):
Python
from langchain_core.globals import set_llm_cache
from langchain_core.caches import InMemoryCache
set_llm_cache(InMemoryCache())
  1. 持久化缓存(生产用):
Python
from langchain_community.cache import SQLiteCache, RedisCache
set_llm_cache(SQLiteCache(database_path=".langchain.db"))
  1. 语义缓存(GPTCache / SemanticCache):按语义相似度命中,而非逐字相等——问法不同但意思相同也能命中。

缓存键

  • 默认以「模型 + prompt」为键;相同 prompt 命中缓存直接返回,不调模型。

注意

  • 缓存适合确定性、重复性高的查询(如 FAQ);对需要实时/随机性的场景要谨慎。
  • 语义缓存命中率更高,但可能带来语义偏差,需阈值控制。
💡 面试加分项

答「用 set_llm_cache 配置内存/SQLite/Redis 缓存」,并点出「语义缓存按相似度命中、命中率更高但要控制阈值」的进阶点。


LangChain 有哪些优缺点?

优点

  1. 抽象统一:模型/prompt/retriever 等统一接口,快速搭建原型。
  2. 生态丰富:loader/splitter/tool/integration 众多,开箱即用。
  3. 可组合(LCEL):管道符声明式组合,自动获得流式/异步/批处理。
  4. 可观测:内置 callbacks,对接 LangSmith 等 tracing。
  5. 社区大:资料、集成、教程多,招聘市场认可度高。

缺点

  1. 抽象过度:层层封装,隐藏细节,排查问题难、学习曲线陡。
  2. API 变动大:0.1/0.2/1.0 频繁破坏性变更,升级成本高。
  3. 复杂流程力不从心:链式 DAG 难以表达循环、条件分支、动态路由。
  4. 性能与体积:抽象层带来一定开销,依赖重。
  5. 调试难:报错栈深、黑盒,难以定位问题根源。

一句话总结

  • 「原型快、生产要谨慎」:快速验证很强,深度定制/复杂 Agent 时反而要绕开抽象。
💡 面试加分项

平衡作答:「优点=抽象统一/生态丰富/LCEL 可组合,缺点=抽象过度/API 变动大/难表达复杂流程」,落到「原型快、生产谨慎」的判断。


为什么 LangChain 被批评「抽象过度」?

原因

  1. 封装层次多:一个「调模型」可能穿过 prompt→model→parser→callback 多层,报错栈极深,难以定位。
  2. 隐藏关键细节:开发者看不到底层 prompt 拼装、消息构造、重试逻辑,出了问题「黑盒」。
  3. 魔法配置:大量隐式默认(自动工具选择、自动解析),行为不可预期,难以精确控制。
  4. API 频繁变动:为了兼容旧抽象不断叠新抽象,概念膨胀(Chain/Agent/AgentExecutor…)。
  5. 简单任务被复杂化:只是「一个 prompt + 一个模型」也要引入一堆抽象,收益低、心智负担高。

反方观点(LangChain 的回应)

  • 抽象带来可移植性(换模型不改代码)与可组合性(LCEL 复用)。
  • 1.0 后收敛 API、LangGraph 专注编排,试图「该简单的地方简单、该复杂的地方可控」。

结论

  • 批评的实质是「抽象收益与成本不匹配」:对简单场景过度设计,对复杂场景又不够灵活(才催生 LangGraph)。
💡 面试加分项

归纳成「封装深、黑盒、魔法配置、API 变动、简单任务复杂化」五点,并补一句「实质是抽象收益与成本不匹配」。


LangChain 适用和不适用于哪些场景?

适用场景

  1. 快速原型 / PoC:想几天内跑通 RAG、聊天机器人。
  2. 标准 RAG 流水线:loader → splitter → embedding → vector store → retriever。
  3. 多模型切换需求:需要屏蔽 OpenAI/Claude/本地模型差异。
  4. 简单链式应用:prompt → model → parser 的直线流程。
  5. 需要现成组件:大量 loader/tool/integration 直接用。

不适用场景

  1. 复杂状态机 / 多步 Agent:循环、分支、回溯、人工介入 → 用 LangGraph。
  2. 极致性能/延迟敏感:抽象开销、依赖体积是负担。
  3. 需要完全可控的底层细节:不想被黑盒封装挡住。
  4. 高度定制的工作流:自己写编排更直接。
  5. 长期稳定 API 要求:版本变动大(1.0 后改善)。

结论

  • 经验法则:「原型/标准 RAG 用 LangChain,复杂 Agent/状态编排用 LangGraph」。
💡 面试加分项

明确「适用=原型/标准 RAG/多模型切换,不适用=复杂状态机/性能敏感/需可控」,并落到「复杂编排上 LangGraph」的选型结论。


LangGraph 解决什么问题?和 LangChain 的 chain 有什么区别?

解决什么问题

  • LangChain 的 chain 是静态 DAG:数据单向流动,难以表达循环、条件分支、动态路由、人工介入、持久化状态。
  • LangGraph 用「图 + 状态」建模,天然支持循环、分支、并行、回溯、checkpoint,是 Agent 与复杂工作流的编排层。

区别

维度LangChain chainLangGraph
结构静态 DAG(单向)有环图(可循环)
状态沿链传递,无显式状态显式 State,节点读写
循环不支持(或很别扭)原生支持
人工介入难interrupt/resume 原生支持
持久化弱checkpoint 原生支持
定位组件库 + 轻量链状态机编排层

关系

  • 互补而非替代:LangGraph 内部节点常用 LangChain 组件(模型、工具、retriever);LangChain 提供积木,LangGraph 负责搭复杂结构。
💡 面试加分项

一句话「chain 是静态 DAG、LangGraph 是有环状态机」,并点出「循环/状态/checkpoint/人工介入是 LangGraph 补上的关键能力」,强调互补关系。


StateGraph 和 State 是什么?

State

  • State:图执行过程中共享的状态对象,通常是 TypedDict / Pydantic,节点通过它读写数据。
Python
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]   # 用 reducer 合并
    count: int

StateGraph

  • StateGraph:以 State 为中心的图构建器,定义「节点 + 边 + 状态如何更新」。
Python
from langgraph.graph import StateGraph

builder = StateGraph(State)
builder.add_node("node_a", fn_a)
builder.add_node("node_b", fn_b)
builder.add_edge("node_a", "node_b")
builder.set_entry_point("node_a")
graph = builder.compile()

状态更新(reducer)

  • 每个节点返回一个状态片段,按 channel 的 reducer 合并进整体 State。
  • 默认「后写覆盖前写」;add_messages 这类 reducer 则追加(消息累加)。

关键点

  • State 是 LangGraph 的「显式共享内存」,比 chain 的「隐式数据流」更可控、可持久化、可检查。
💡 面试加分项

答「State 是共享状态对象(TypedDict/Pydantic),StateGraph 用它定义节点+边」,并点出「reducer 控制状态如何合并(覆盖/累加)」。


node 和 edge 是什么?如何定义?

node(节点)

  • 图中的一个执行单元,本质是函数:接收当前 State,返回状态更新片段。
Python
def node_a(state: State):
    # 处理逻辑
    return {"count": state["count"] + 1}   # 返回要更新的字段
  • 节点可以是普通函数,也可以是 LangChain Runnable / 模型调用 / 工具执行。

edge(边)

  • 定义节点间的执行顺序,分两类:
边类型说明
普通边(edge)固定从 A 到 B,无条件
条件边(conditional edge)依状态/输出决定去哪个节点
Python
builder.add_edge("node_a", "node_b")      # 普通边
builder.add_conditional_edges("node_a", route_fn, {"x": "node_b", "y": "node_c"})
builder.set_entry_point("node_a")          # 入口
builder.set_finish_point("node_b")         # 出口(可多个)

关键点

  • compile() 后得到可 invoke/stream 的图;图 = 节点(做什么)+ 边(何时做/按什么顺序)。
  • 条件边用 route_fn(state) -> "下一节点名" 决定路由。
💡 面试加分项

答「node=接收 State 返回更新的函数,edge=执行顺序」,并区分「普通边固定、条件边按状态路由」两种边。


条件边(conditional edge)是什么?

定义

  • 条件边:从一个节点出发,根据当前状态/输出动态决定下一个节点,是图「智能路由」的核心。

实现

Python
def route(state) -> str:
    if "需要工具" in state["messages"][-1].content:
        return "tools"
    return "end"

builder.add_conditional_edges(
    "agent",
    route,                                # 路由函数
    {"tools": "tools", "end": END},       # 返回值 -> 目标节点映射
)

工作流程

  1. 源节点执行完,调 route(state) 返回一个字符串。
  2. 用映射表把该字符串解析成目标节点名。
  3. 跳转到对应节点继续执行。

用途

  • ReAct Agent 的「继续调工具 vs 结束」判断。
  • Supervisor 多 Agent 的「该派给哪个 worker」路由。
  • 异常/兜底分支:依状态走正常流程或降级流程。
💡 面试加分项

答「路由函数根据状态返回字符串,映射到目标节点」,并举例「ReAct 判断继续调工具还是结束、supervisor 判断派给谁」。


LangGraph 如何支持循环?

核心机制

  • LangGraph 的图允许有环:节点 A → B → A 形成循环,靠条件边 + 终止条件控制退出。
Text
agent --条件边--> tools --边--> agent   # 循环
agent --条件边--> END                    # 终止

ReAct 循环示例

Python
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": END})
builder.add_edge("tools", "agent")       # 工具执行完回到 agent,形成循环

def should_continue(state):
    last = state["messages"][-1]
    return "tools" if last.tool_calls else "end"

终止保障

  • 循环必须有退出条件,否则死循环:
    1. 条件边返回 END。
    2. 设置 recursion_limit(最大步数)强制终止。
    3. 节点内检测重复/超限主动结束。
Python
graph = builder.compile(checkpointer=..., interrupt_before=[...])
result = graph.invoke(input, config={"recursion_limit": 25})
💡 面试加分项

答「有环图 + 条件边控制退出」,给出 ReAct 的「agent ⇄ tools + 条件边判 END」结构,并强调「recursion_limit 兜底防死循环」。


checkpoint 持久化是什么?如何实现?

定义

  • checkpoint:图执行到每个节点后保存的 State 快照,用于恢复、续跑、人工介入、时间旅行。

如何实现

  1. 编译时挂 checkpointer:
Python
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
  1. 调用时给 thread_id 标识会话:
Python
config = {"configurable": {"thread_id": "user-123"}}
graph.invoke(input, config)   # 每次调用自动存 checkpoint

作用

能力说明
崩溃恢复从最近 checkpoint 继续,不从头跑
多轮会话同 thread_id 复用状态(记忆)
人工介入interrupt 暂停后,resume 从暂停点继续
时间旅行回到任意历史 checkpoint 重试/分支

生产 checkpointer

  • 内存 MemorySaver(开发)、SqliteSaver、PostgresSaver(生产持久化)。
💡 面试加分项

答「每个节点后存 State 快照」,并说清「checkpointer + thread_id 实现恢复/多轮/人工介入/时间旅行」,报出 Memory/Sqlite/Postgres 三种实现。


human-in-the-loop(interrupt)如何实现?

定义

  • human-in-the-loop(HITL):在关键节点暂停执行,等待人工批准/补充输入后,再 resume 继续。

实现

Python
graph = builder.compile(checkpointer=MemorySaver())

# 在 "tools" 节点执行前暂停
graph = builder.compile(checkpointer=MemorySaver(), interrupt_before=["tools"])

result = graph.invoke(input, config)   # 执行到 tools 前暂停
# ... 人工审查 ...
graph.invoke(None, config)             # 传 None 表示 resume,继续执行

交互方式

  1. interrupt_before / interrupt_after:在指定节点前后暂停。
  2. 动态 interrupt:节点内用 interrupt() 函数,把值抛给外部并等待。
  3. 人工可修改 State(如改工具参数)后再 resume,也可拒绝操作。

场景

  • 高危操作(支付/删除/发消息)前审批;需要用户补充信息;人工接管关键决策。
💡 面试加分项

答「interrupt_before/after 或节点内 interrupt() 暂停 + checkpointer 恢复」,并强调「人工可改 State 后 resume」,落到「高危操作必经人」。


LangGraph 有哪些 streaming modes?

模式

模式粒度说明
values状态值每步完整 State 变化流
updates状态更新每步增量更新片段流
messages消息流式输出 LLM 消息(含 token 流)
custom自定义节点内 StreamWriter 写自定义事件
debug调试尽可能多的执行细节(调试用)
Python
for chunk in graph.stream(input, stream_mode="updates"):
    print(chunk)   # 每个节点更新

for msg, metadata in graph.stream(input, stream_mode="messages"):
    print(msg.content, end="")   # token 级

选择

  • 只想显示最终回答 token → messages。
  • 想看每个节点产出/进度 → updates。
  • 需要精确到节点内部自定义事件 → custom。

关键点

  • 一个 stream 可同时传多个模式 stream_mode=["updates", "messages"]。
  • 流式 + checkpoint 结合,可实现「边执行边持久化」。
💡 面试加分项

报出 values/updates/messages/custom/debug 五种模式,并说出「messages 给 token 流、updates 看节点进度」的选择依据。


subgraph 如何复用?

定义

  • subgraph:把一张图封装成节点嵌入另一张图,实现图级别的模块化与复用。
Python
subgraph_builder = StateGraph(SubState)
# ... 定义子图
subgraph = subgraph_builder.compile()

main_builder = StateGraph(MainState)
main_builder.add_node("sub", subgraph)   # 子图作为节点
main_builder.add_edge(START, "sub")

复用方式

  1. 直接作为节点:把编译好的图传给 add_node,主图节点执行时跑完整子图。
  2. 状态映射:子图 State 与主图 State 字段不同时,用 add_node(..., input=..., output=...) 做字段映射/转换。
  3. 父图访问子图状态:通过 get_state 等 API 查看子图内部状态。

场景

  • 多 Agent 中把每个 agent 逻辑封装成子图;复杂流程按模块拆分、独立测试、多处复用。

注意

  • 子图需要自己的 State 定义;checkpoint 可下钻到子图(namespace 嵌套)。
💡 面试加分项

答「把编译好的图当作节点嵌入主图」,并点出「State 字段不同用 input/output 映射、checkpoint 用 namespace 下钻」两个细节。


LangGraph 如何实现多 Agent(supervisor/swarm)?

supervisor 模式

  • 中央 supervisor agent 用条件边把任务路由给多个 worker 节点,worker 完成后回到 supervisor 汇总。
Python
builder.add_node("supervisor", supervisor_fn)
builder.add_node("researcher", researcher_fn)
builder.add_node("coder", coder_fn)
builder.add_conditional_edges("supervisor", route, {
    "researcher": "researcher", "coder": "coder", "FINISH": END})
builder.add_edge("researcher", "supervisor")   # 完成后回到 supervisor
builder.add_edge("coder", "supervisor")
  • supervisor 输出「next 节点」的指令(通常用 tool call 或结构化输出解析)。

swarm 模式

  • 去中心化:多个 agent 直接 handoff(交接),无固定 supervisor;谁适合谁接手。
  • 实现:每个 agent 可输出「转交给谁」的指令,条件边据此跳转。

对比

维度supervisorswarm
控制集中(supervisor 分发)去中心(agent 间交接)
结构星形网形
适用需集中调度简单交接、客服转接
💡 面试加分项

答「supervisor 用中央节点 + 条件边分发、worker 完成后回 supervisor」,并说「swarm 是去中心 handoff 交接」,点出两者控制方式差异。


LangGraph 的 memory(checkpointer + Store)如何设计?

两类记忆

类型机制内容
短期记忆checkpointer线程内对话状态(thread_id 隔离)
长期记忆Store跨线程共享、可检索的持久知识

checkpointer(短期)

  • 每个 thread_id 保存一份 State 快照,多轮调用复用,实现会话记忆。
Python
graph.compile(checkpointer=PostgresSaver(...))
config = {"configurable": {"thread_id": "user-1"}}

Store(长期)

  • BaseStore:跨会话的键值存储,支持命名空间与语义检索,用于长期记忆/跨用户知识。
Python
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
store.put(("memories",), "u1", {"data": "用户偏好 JSON"})
  • 节点内通过 store 读写;生产可接 Postgres、Redis 等。

分工

  • 短期:同一 thread_id 的上下文连续性 → checkpointer。
  • 长期:跨会话、可被检索复用的知识 → Store(可做语义搜索)。
💡 面试加分项

答「checkpointer 管线程内短期状态、Store 管跨线程长期记忆」,并点出「checkpointer 按 thread_id 隔离、Store 支持命名空间 + 语义检索」。


LangGraph 的 pregel 执行模型是什么?

定义

  • Pregel:源自 Google 的图计算模型(BSP 批量同步并行),LangGraph 借鉴它做状态图的步进执行。

核心思想

Text
每轮 superstep:
  1. 找到所有「待执行」的节点(有输入就绪)
  2. 并行执行这些节点
  3. 收集它们的 State 更新,合并回整体 State
  4. 进入下一轮,直到无节点可执行

特点

特点说明
消息驱动节点靠「收到输入」被激活
批量同步一轮内并行节点同时执行,轮与轮之间有同步点
状态聚合reducer 合并并行节点对同一 channel 的写入
可持久化每轮结束是天然的 checkpoint 点

意义

  • 让 LangGraph 能表达循环、并行、动态路由,同时保持「每步状态可检查、可持久化」。
💡 面试加分项

答「借鉴 Google Pregel 的 BSP 模型:每轮找就绪节点→并行执行→合并状态→下一轮」,并点出「批量同步 + 每轮 checkpoint」带来的可控性。


time-travel 是什么?如何实现?

定义

  • time-travel:回到某个历史 checkpoint,查看或从那里重新执行/分支,用于调试与「如果当初…会怎样」。

实现

  1. 拿到历史状态快照列表:
Python
states = graph.get_state_history(config)   # 按时间倒序
  1. 查看某个历史状态:
Python
snapshot = states[3]
print(snapshot.values)   # 那个时刻的 State
  1. 从历史状态恢复重跑:
Python
graph.invoke(None, {"configurable": {"thread_id": "t1", "checkpoint_id": snapshot.config["configurable"]["checkpoint_id"]}})
  1. 或 fork 新分支:换一个 checkpoint_ns / thread_id 从该点分叉,保留原轨迹。

用途

  • 调试「哪一步开始跑偏」;回放复现问题;A/B 不同后续策略。
💡 面试加分项

答「用 get_state_history 拿历史快照,再指定 checkpoint_id 重新 invoke」,并点出「可 fork 新分支保留原轨迹」的调试价值。


LangGraph 和旧的 AgentExecutor 有什么区别?

AgentExecutor(旧 LangChain)

  • 封装好的一体化 Agent 运行时:内部硬编码了「模型 → 工具 → 循环」的逻辑。
  • 问题:不透明、难定制——想改循环逻辑、加人工介入、做持久化都要 hack。

LangGraph(新)

  • 把 Agent 拆成显式的节点 + 边,循环、路由、工具执行都可见可控。
维度AgentExecutorLangGraph
控制流黑盒(内置循环)显式图(节点+边)
定制难(靠继承/hack)易(加节点/改边)
持久化弱checkpoint 原生
人工介入难interrupt/resume
可观测一般每步可追踪/回放

演进

  • create_react_agent(LangGraph 版)取代了旧 create_react_agent/AgentExecutor,底层是显式 ReAct 图,可自由改造。
💡 面试加分项

答「AgentExecutor 黑盒内置循环、难定制;LangGraph 显式图可控」,并点出「checkpoint/人工介入/可观测是 LangGraph 补上的能力」。


send 和 MapReduce 是什么?

send

  • 动态并行分叉:在条件边/节点里用 Send,把一个节点的多个输出动态派发给多个目标节点,实现类似「扇出」。
Python
from langgraph.types import Send

def continue_to_jokes(state):
    return [Send("generate_joke", {"subject": s}) for s in state["subjects"]]
  • 用途:一个节点产出 N 个任务,分别并行执行 N 个下游节点。

MapReduce

  • Map(映射):把输入拆成多份,并行处理(每个子任务跑一个子图/节点)。
  • Reduce(归约):把所有子结果合并成最终结果。
Text
问题 -> 拆成子问题 -> [并行处理子问题] -> 汇总 -> 最终答案

关系

  • send 是实现 MapReduce「并行 Map + 动态扇出」的机制;MapReduce 是更高层的模式。
💡 面试加分项

答「send 用 Send 动态扇出并行任务、MapReduce 是拆分-并行-归约模式」,并点出「send 是 Map 扇出的实现机制」。


@tool 装饰器是什么?如何自定义工具?

定义

  • @tool 装饰器把普通 Python 函数转成 LangChain 工具,自动生成 JSON Schema(函数名 + docstring 作为描述 + 参数类型)。
Python
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市的实时天气,返回温度与天气状况。"""
    # 真实实现:调 API
    return f"{city} 今天 18°C"

工作原理

  1. 装饰器读取函数签名(参数名/类型/默认值)与 docstring。
  2. 生成工具 schema:name、description、parameters。
  3. 工具可 bind_tools 绑定到模型,或直接 .invoke(args) 调用。

自定义要点

  • docstring 是给模型的「说明」:写清何时用、返回什么,直接影响调用准确率。
  • 参数用类型注解(可加 Annotated 描述、enum 限制取值)。
  • 工具内部可调外部 API/数据库;需处理异常并返回结构化结果。
Python
@tool
def delete_file(path: str) -> str:
    """删除指定文件。高危操作,需谨慎调用。"""
    ...
💡 面试加分项

答「@tool 把函数 + 类型注解 + docstring 自动转成带 schema 的工具」,并强调「docstring 即工具描述,决定模型选工具准确率」。


structured tool(Pydantic)是什么?如何定义?

定义

  • structured tool:用 Pydantic 模型精确定义工具参数 schema,实现强类型、带校验的工具,模型返回的 args 会被解析并校验成 Pydantic 对象。
Python
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool

class WeatherInput(BaseModel):
    city: str = Field(description="城市名,如 '北京'")
    date: str = Field(description="日期,YYYY-MM-DD")

def get_weather(city: str, date: str) -> str:
    return f"{city} {date} 天气晴"

tool = StructuredTool.from_function(
    func=get_weather,
    name="get_weather",
    description="查询指定城市指定日期的天气",
    args_schema=WeatherInput,
)

优势

  1. 参数校验:类型错误/缺字段会被 Pydantic 校验拦截。
  2. schema 精确:字段级 description、enum、约束都进 JSON Schema,模型调用更准。
  3. 结构化回传:解析后的 args 是类型化对象,宿主执行更安全。

与 @tool 关系

  • @tool 用类型注解自动生成 schema(简单快捷);structured tool 用显式 Pydantic(更精细,可加复杂校验)。
💡 面试加分项

答「用 Pydantic 模型定义 args_schema,参数被校验成类型化对象」,并点出「字段级 description/约束进 schema,让模型调用更准、宿主更安全」。


BaseChatModel 抽象是什么?

定义

  • BaseChatModel:LangChain 中「对话模型」的统一抽象基类,屏蔽各家 API 差异,统一暴露 invoke/stream/batch 接口。

核心接口

Text
invoke(messages) -> AIMessage
stream(messages)  -> Iterator[AIMessageChunk]
batch(messages_list) -> list[AIMessage]
bind_tools(tools) -> 绑定工具的模型
with_structured_output(schema) -> 结构化输出模型

输入输出消息类型

消息角色
SystemMessage系统指令
HumanMessage用户
AIMessage模型(可含 tool_calls)
ToolMessage工具结果

意义

  • 实现 BaseChatModel 即可接入一个新模型;上层 chain/agent 无需感知具体厂商。
  • 所有 ChatModel 都是 Runnable,可直接 | 组合。

自定义

  • 继承 BaseChatModel,实现 _generate / _stream(或 _agenerate / _astream),即可包装任意本地/私有模型。
💡 面试加分项

答「统一 invoke/stream/batch/bind_tools 接口 + 消息列表输入输出」,并点出「实现 BaseChatModel 就能接任意模型、上层无感」的扩展机制。


bind 和 config 是什么?如何传递配置?

bind

  • bind:在 Runnable 上绑定固定参数,生成一个新的 Runnable;常用于给模型预设 temperature、stop、tools 等。
Python
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)   # 构造时传参
strict_model = model.bind(temperature=0, response_format="json")  # 运行时再绑定

config

  • config:调用时传入的运行上下文(RunnableConfig),沿整条链自动传播,包含:
字段作用
callbacks回调处理器
metadata / tags元信息(追踪/统计)
configurable自定义参数(session_id、thread_id 等)
recursion_limit图最大步数
Python
result = chain.invoke(input, config={
    "configurable": {"session_id": "s1"},
    "callbacks": [handler],
    "tags": ["prod"],
})

传递机制

  • 每个 Runnable 执行时把 config 逐层透传给下游;子组件可通过 RunnableConfig 读取 configurable 里的自定义值。
💡 面试加分项

答「bind 运行时绑定固定参数、config 是调用时传播的上下文」,并点出「config 含 callbacks/metadata/configurable,沿链自动透传」。


LangSmith 是什么?有什么用?

定义

  • LangSmith:LangChain 官方推出的 LLM 应用可观测性与评估平台,负责 trace、调试、评估、监控。

用途

能力说明
tracing记录完整执行轨迹(trace/span),看每步输入输出、token、延迟
调试回放、对比不同 prompt/模型的效果
评估数据集 + evaluator 自动评分(准确性、忠实度等)
监控线上延迟、成本、错误率看板
Prompt 管理版本化 prompt,线上切换回滚
人工标注对结果打标,沉淀评估数据集

接入

  • 设环境变量即自动上报:
Bash
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__xxx
  • 也可用 SDK 手动记录任意 LLM 调用(不限于 LangChain)。

同类

  • 开源替代:Langfuse、Phoenix(Arize)、OpenLLMetry(OpenTelemetry)。
💡 面试加分项

答「官方可观测 + 评估平台」,报出「tracing/调试/评估/监控/Prompt 管理」能力,并提 Langfuse/Phoenix 作为开源替代。


LangServe 是什么?如何部署?

定义

  • LangServe:把 LangChain/LangGraph 的 chain/agent 一键部署为 REST API(基于 FastAPI),自带 playground、流式、追踪。

如何部署

  1. 定义 chain,用 add_routes 暴露:
Python
from langserve import add_routes
from fastapi import FastAPI

app = FastAPI()
add_routes(app, chain, path="/my_chain")
  1. 启动:
Bash
langchain app new my-app     # 或手动
uvicorn app:app --host 0.0.0.0 --port 8000

提供的端点

  • /my_chain/invoke、/my_chain/stream、/my_chain/batch(对应 Runnable 三接口)。
  • 自动生成 OpenAPI 文档与 playground(网页调试 UI)。
  • 内置流式(SSE)、token 计数、LangSmith 追踪、configurable 透传。

生产注意

  • 用 RunnableConfigurableFields 暴露可配置参数;前面接网关/鉴权/限流;容器化 + 水平扩容。
💡 面试加分项

答「基于 FastAPI,add_routes 把 chain 暴露成 invoke/stream/batch 端点」,并点出「自带 playground + OpenAPI + SSE 流式 + 追踪」。


LangChain 和 LangGraph 如何选型?

选型原则

场景选择
快速原型 / PoCLangChain
标准 RAG 流水线LangChain
直线链式(prompt→model→parser)LangChain(LCEL)
多模型/多供应商切换LangChain
多步 Agent / 循环 / 分支LangGraph
人工介入、checkpoint、时间旅行LangGraph
多 Agent 协作LangGraph
生产级可控状态机LangGraph

组合使用

  • 多数项目是两者并用:LangChain 提供组件(模型/工具/retriever/prompt),LangGraph 提供编排(状态图、循环、持久化)。

判断依据

  • 看是否需要循环/状态/人工介入:需要 → LangGraph;只是直线数据流 → LangChain 足够。
💡 面试加分项

用一句话「直线用 LangChain、带循环/状态/人工介入用 LangGraph」,并强调「两者常组合:LangChain 供积木、LangGraph 做编排」。


如何用 LangGraph 实现一个 ReAct agent?

结构

Text
START -> agent -> (条件边) -> tools -> agent -> ... -> END

实现

Python
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from typing import Annotated, TypedDict

class State(TypedDict):
    messages: Annotated[list, add_messages]

def agent(state):
    return {"messages": [model.bind_tools(tools).invoke(state["messages"])]}

def tool_node(state):
    last = state["messages"][-1]
    results = [tool_map[tc["name"]].invoke(tc) for tc in last.tool_calls]
    return {"messages": results}

def should_continue(state):
    return "tools" if state["messages"][-1].tool_calls else END

builder = StateGraph(State)
builder.add_node("agent", agent)
builder.add_node("tools", tool_node)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent")
graph = builder.compile()

关键点

  • agent 节点:bind_tools 的模型产出含 tool_calls 的 AIMessage。
  • tools 节点:执行 tool_calls,把 ToolMessage 结果写回。
  • 条件边:判断「继续调工具 or 结束」。
  • LangGraph 提供封装 create_react_agent(model, tools),一行得到标准 ReAct 图,可再自定义。
💡 面试加分项

画出「agent ⇄ tools + 条件边判 END」的结构,说清「agent 产出 tool_calls、tools 执行回写、条件边决定循环退出」,并提 create_react_agent 封装。


如何用 LangChain 实现一个 RAG 应用?

完整链路

Text
加载文档 -> 分块 -> embedding -> 存向量库 -> 检索 -> 拼 prompt -> 生成

实现

Python
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 1. 索引(离线)
docs = TextLoader("knowledge.txt").load()
chunks = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50).split_documents(docs)
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# 2. 检索 + 生成(在线)
prompt = ChatPromptTemplate.from_messages([
    ("system", "根据以下上下文回答问题:\n{context}"),
    ("human", "{question}"),
])
chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt | ChatOpenAI() | StrOutputParser()
)
answer = chain.invoke("什么是 RAG?")

关键点

  • 索引离线、检索生成在线两段分离。
  • {"context": retriever, "question": RunnablePassthrough()} 用 RunnableParallel 同时产出上下文与问题。
  • 检索质量决定 RAG 上限;可加 rerank、query 改写优化。
💡 面试加分项

完整背出「加载→分块→embedding→入库→检索→拼 prompt→生成」七步,并写出 {"context": retriever, ...} | prompt | model | parser 的 LCEL 链。


LangChain/LangGraph 生产部署有哪些注意事项?

注意事项

维度要点
持久化checkpoint 用 PostgresSaver(非内存),保证崩溃可恢复
并发与隔离用 thread_id/session_id 隔离会话;注意并发写 checkpoint
成本控制caching、prompt caching、小模型分工、限制步数/预算
可观测接 LangSmith/Langfuse,记录 trace/成本/延迟/错误
安全工具最小权限、沙箱执行、提示注入防护、高危人工确认
可靠性容错(with_fallbacks)、重试、限流、超时、死循环检测
流式与体验SSE 流式、前端进度展示、工具调用可见
版本管理模型/prompt 版本化,支持回滚
扩容无状态化(状态外置),容器化水平扩容
评估回归数据集 + evaluator,上线前回归、线上监控

关键原则

  • 状态外置:图/链本身无状态,状态进 checkpointer/数据库,才可水平扩展。
  • 可观测先行:没有 tracing,生产出问题无法定位。
  • 降级与兜底:模型/工具失败要有降级路径,不让整体挂掉。
💡 面试加分项

归纳「持久化 + 并发隔离 + 成本 + 可观测 + 安全 + 可靠性 + 状态外置」生产清单,突出「状态外置才能扩容、可观测先行」两条。