从零实现一个最小版 LangChain

从零实现一个最小版 LangChain

用 ~300 行 Python 代码复刻 LangChain 的核心抽象,让你 1 小时看懂它的本质。

目标读者:有后端工程经验、想穿透 LangChain 的设计哲学而不是只会调 API 的人。


一、LangChain 到底是什么?

一句话:LangChain 是"把 LLM 应用拆成可组合组件"的框架

它本身不提供智能,只提供抽象粘合剂。就像:

传统后端 LangChain
Spring 不发明业务逻辑,只提供 IoC/AOP 容器 LangChain 不发明模型,只提供 LLM 调用的容器
Servlet → Filter → Controller 责任链 Prompt → LLM → Parser → Tool 责任链
Bean 依赖注入 Chain 组合(`prompt

核心洞察 :LLM 应用 = 输入变形 × 模型调用 × 输出解析 × 工具调用 × 状态管理 的流水线。LangChain 把这条流水线的每一段都抽成可替换的积木。


二、核心抽象(六块积木)

markdown 复制代码
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ PromptTemplate│──▶│     LLM      │──▶│ OutputParser │
└──────────────┘    └──────────────┘    └──────────────┘
        │                   ▲                    │
        │                   │                    ▼
        │            ┌──────────────┐    ┌──────────────┐
        └───────────▶│    Memory    │    │     Tool     │
                     └──────────────┘    └──────────────┘
                                                 │
                                                 ▼
                                         ┌──────────────┐
                                         │    Agent     │
                                         └──────────────┘
  1. LLM:对模型 API 的抽象(OpenAI / Anthropic / 本地模型统一接口)
  2. PromptTemplate:带变量的字符串模板
  3. OutputParser:把 LLM 的字符串输出结构化
  4. Chain:把前三者串成流水线
  5. Memory:多轮对话的状态存储
  6. Tool + Agent:让 LLM 能调外部函数,实现"推理---行动"循环

下面逐个从零实现。


三、最小实现(可直接运行)

准备:一个假的 LLM

为了让代码能独立跑通,先实现一个"假 LLM",真实场景替换成 OpenAI / Claude 的 SDK 调用即可。

python 复制代码
# file: minilangchain.py
from abc import ABC, abstractmethod
from typing import Any, Callable
import re
import json


class BaseLLM(ABC):
    """LLM 抽象基类:所有模型只暴露一个 invoke 方法"""
    @abstractmethod
    def invoke(self, prompt: str) -> str: ...


class FakeLLM(BaseLLM):
    """用于演示的假 LLM,按规则返回内容"""
    def __init__(self, responses: dict[str, str] | None = None):
        self.responses = responses or {}

    def invoke(self, prompt: str) -> str:
        for key, value in self.responses.items():
            if key in prompt:
                return value
        return f"[FakeLLM echo]: {prompt[:100]}"


class OpenAILLM(BaseLLM):
    """生产环境的真实现示意"""
    def __init__(self, model: str = "gpt-4o-mini", api_key: str = ""):
        from openai import OpenAI
        self.client = OpenAI(api_key=api_key)
        self.model = model

    def invoke(self, prompt: str) -> str:
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
        )
        return resp.choices[0].message.content

设计要点BaseLLM 只有一个 invoke(str) -> str。这就是 LangChain 最底层的契约------字符串进、字符串出。其他一切都围绕这个契约做变形。


积木 1:PromptTemplate

python 复制代码
class PromptTemplate:
    """
    带占位符的 prompt 模板。
    示例:PromptTemplate("你是{role},请回答:{question}")
    """
    def __init__(self, template: str):
        self.template = template
        self.variables = re.findall(r"\{(\w+)\}", template)

    def format(self, **kwargs) -> str:
        missing = set(self.variables) - set(kwargs.keys())
        if missing:
            raise ValueError(f"缺少变量: {missing}")
        return self.template.format(**kwargs)

一眼看穿 :就是 str.format() + 变量校验。LangChain 的 PromptTemplate 再复杂,核心也就这几行。


积木 2:OutputParser

LLM 吐出的是字符串,但后续代码需要结构化数据(dict / 对象 / 枚举)。Parser 负责这一步。

python 复制代码
class OutputParser(ABC):
    @abstractmethod
    def parse(self, text: str) -> Any: ...


class StrOutputParser(OutputParser):
    def parse(self, text: str) -> str:
        return text.strip()


class JSONOutputParser(OutputParser):
    """从 LLM 输出里提取 JSON"""
    def parse(self, text: str) -> dict:
        match = re.search(r"\{.*\}", text, re.DOTALL)
        if not match:
            raise ValueError(f"未找到 JSON: {text}")
        return json.loads(match.group())

设计要点:Parser 让"不可靠的字符串"变成"类型安全的数据"。这是 LangChain 之所以能工程化的关键------边界处做格式收敛。


积木 3:Chain(核心!)

Chain 是 LangChain 的灵魂------把 Prompt、LLM、Parser 串起来的流水线

python 复制代码
class Runnable(ABC):
    """可被 invoke 的东西,支持用 | 组合"""
    @abstractmethod
    def invoke(self, input: Any) -> Any: ...

    def __or__(self, other: "Runnable") -> "Chain":
        """支持 prompt | llm | parser 这种语法"""
        return Chain([self, other])


class Chain(Runnable):
    def __init__(self, steps: list[Runnable]):
        self.steps = []
        for step in steps:
            # 扁平化嵌套的 Chain
            if isinstance(step, Chain):
                self.steps.extend(step.steps)
            else:
                self.steps.append(step)

    def invoke(self, input: Any) -> Any:
        out = input
        for step in self.steps:
            out = step.invoke(out)
        return out


# 让之前定义的类都变成 Runnable
class PromptRunnable(Runnable):
    def __init__(self, template: PromptTemplate):
        self.template = template

    def invoke(self, input: dict) -> str:
        return self.template.format(**input)


class LLMRunnable(Runnable):
    def __init__(self, llm: BaseLLM):
        self.llm = llm

    def invoke(self, input: str) -> str:
        return self.llm.invoke(input)


class ParserRunnable(Runnable):
    def __init__(self, parser: OutputParser):
        self.parser = parser

    def invoke(self, input: str) -> Any:
        return self.parser.parse(input)

使用示例

python 复制代码
llm = FakeLLM({"首都": "北京"})
prompt = PromptTemplate("{country}的首都是哪里?")

chain = PromptRunnable(prompt) | LLMRunnable(llm) | ParserRunnable(StrOutputParser())

result = chain.invoke({"country": "中国"})
print(result)  # -> "北京"(FakeLLM 命中规则)

类比后端 :这就是 Unix 管道 / Scala 的 andThen / Java Stream 的 map.map.map------函数组合。LangChain 的 LCEL(LangChain Expression Language)本质上就是重新发明了一遍管道。


积木 4:Memory

聊天机器人需要记住上下文,Memory 就是这个"上下文管理器"。

python 复制代码
class Memory:
    """最简单的对话历史管理"""
    def __init__(self):
        self.history: list[tuple[str, str]] = []  # [(user, ai), ...]

    def add(self, user: str, ai: str):
        self.history.append((user, ai))

    def format(self) -> str:
        """把历史格式化进 prompt"""
        if not self.history:
            return ""
        lines = []
        for user, ai in self.history:
            lines.append(f"用户: {user}")
            lines.append(f"助手: {ai}")
        return "\n".join(lines)


class ChatChain:
    """带记忆的对话链"""
    def __init__(self, llm: BaseLLM, memory: Memory | None = None):
        self.llm = llm
        self.memory = memory or Memory()
        self.template = PromptTemplate(
            "以下是历史对话:\n{history}\n\n用户: {question}\n助手:"
        )

    def chat(self, question: str) -> str:
        prompt = self.template.format(
            history=self.memory.format(),
            question=question,
        )
        answer = self.llm.invoke(prompt)
        self.memory.add(question, answer)
        return answer

设计要点 :Memory 不是魔法,就是把历史字符串拼进 prompt。LangChain 的各种 Memory 变体(滑动窗口、摘要、向量召回)都是在"怎么压缩历史"上做文章。


积木 5:Tool + Agent(让 LLM 会用工具)

这是 LangChain 最"像 AI"的部分:LLM 不再只聊天,它能选择并调用函数

Tool 抽象
python 复制代码
class Tool:
    """可供 LLM 调用的工具"""
    def __init__(self, name: str, description: str, func: Callable[[str], str]):
        self.name = name
        self.description = description
        self.func = func

    def run(self, arg: str) -> str:
        return self.func(arg)


# 示例工具
def calculator(expr: str) -> str:
    try:
        return str(eval(expr, {"__builtins__": {}}, {}))
    except Exception as e:
        return f"计算错误: {e}"


def search(query: str) -> str:
    fake_db = {"天气": "北京今天晴,25℃", "股价": "苹果今日收盘 $180"}
    for k, v in fake_db.items():
        if k in query:
            return v
    return "未找到相关信息"


CALCULATOR_TOOL = Tool("calculator", "计算数学表达式,输入如 '1+2*3'", calculator)
SEARCH_TOOL = Tool("search", "搜索实时信息,输入搜索词", search)
Agent:ReAct 循环

Agent 的核心是 ReAct 模式(Reasoning + Acting):让 LLM 在"思考 → 行动 → 观察"循环里推进任务。

python 复制代码
AGENT_PROMPT = """你是一个能使用工具的 AI 助手。

可用工具:
{tools}

严格按以下格式输出(每步只输出一段):

Thought: <你的思考>
Action: <工具名>
Action Input: <工具输入>
Observation: <工具返回,由系统填入>
... (可重复多轮)
Thought: 我已得到答案
Final Answer: <最终回答>

问题: {question}
{scratchpad}"""


class Agent:
    def __init__(self, llm: BaseLLM, tools: list[Tool], max_steps: int = 5):
        self.llm = llm
        self.tools = {t.name: t for t in tools}
        self.max_steps = max_steps

    def _tools_desc(self) -> str:
        return "\n".join(f"- {t.name}: {t.description}" for t in self.tools.values())

    def run(self, question: str) -> str:
        scratchpad = ""
        for step in range(self.max_steps):
            prompt = AGENT_PROMPT.format(
                tools=self._tools_desc(),
                question=question,
                scratchpad=scratchpad,
            )
            output = self.llm.invoke(prompt)

            # 终止条件:LLM 给出 Final Answer
            if "Final Answer:" in output:
                return output.split("Final Answer:")[-1].strip()

            # 解析 Action / Action Input
            action_match = re.search(r"Action:\s*(\w+)", output)
            input_match = re.search(r"Action Input:\s*(.+)", output)
            if not (action_match and input_match):
                return f"[Agent 解析失败]:\n{output}"

            tool_name = action_match.group(1).strip()
            tool_input = input_match.group(1).strip()

            if tool_name not in self.tools:
                observation = f"错误: 工具 {tool_name} 不存在"
            else:
                observation = self.tools[tool_name].run(tool_input)

            # 把这一轮追加进 scratchpad,给下一轮 LLM 看
            scratchpad += f"\n{output}\nObservation: {observation}\n"

        return "[Agent 超出最大步数]"

使用示例

python 复制代码
# 真实场景用 OpenAILLM,这里用 FakeLLM 模拟
fake = FakeLLM({
    "scratchpad": """Thought: 我需要计算 2+3
Action: calculator
Action Input: 2+3""",
    "Observation: 5": """Thought: 我已得到答案
Final Answer: 2+3=5""",
})

agent = Agent(fake, [CALCULATOR_TOOL, SEARCH_TOOL])
print(agent.run("请帮我算 2+3"))
# -> "2+3=5"

核心洞察 :Agent = 循环调用 LLM + 解析 LLM 指令 + 执行函数 。所谓"智能体",本质是一个 while 循环。


积木 6:Retriever + VectorStore(RAG 的基石)

RAG(Retrieval-Augmented Generation)解决的痛点是:LLM 不知道你公司的私有数据。做法是先检索相关文档片段,再把片段塞进 prompt 让 LLM 参考作答。

RAG 流水线:

markdown 复制代码
原始文档 ──► TextSplitter ──► Embeddings ──► VectorStore
                                                  │
用户问题 ──► Embeddings ──► 相似度检索 ◄──────────┘
                                │
                                ▼
                     (相关片段 + 问题) ──► LLM ──► 回答
Document & TextSplitter
python 复制代码
class Document:
    """带元数据的文本片段"""
    def __init__(self, content: str, metadata: dict | None = None):
        self.content = content
        self.metadata = metadata or {}


class TextSplitter:
    """把长文档切成定长片段,片段之间可重叠(防止语义在边界被切断)"""
    def __init__(self, chunk_size: int = 200, overlap: int = 20):
        self.chunk_size = chunk_size
        self.overlap = overlap

    def split(self, text: str, metadata: dict | None = None) -> list[Document]:
        chunks = []
        start = 0
        while start < len(text):
            end = min(start + self.chunk_size, len(text))
            chunks.append(Document(text[start:end], metadata))
            if end == len(text):
                break
            start = end - self.overlap
        return chunks
VectorStore(用 Jaccard 相似度模拟向量检索)

真实 VectorStore 用 Embedding 模型把文本转成高维向量,用余弦相似度检索。这里用 Jaccard 相似度(词集合交并比) 替代,把抽象讲清楚不引入 numpy 依赖。

python 复制代码
class VectorStore:
    """
    最简实现。真实版本:
      - add() 时调用 Embeddings 模型把文本转向量,存到 FAISS/Chroma/Milvus
      - search() 时把 query 也转向量,用余弦相似度 top-k
    这里用 Jaccard 替代向量相似度,便于零依赖运行。
    """
    def __init__(self):
        self.docs: list[Document] = []

    def add(self, docs: list[Document]):
        self.docs.extend(docs)

    @staticmethod
    def _tokenize(text: str) -> set[str]:
        # 真实场景这里是 embedding 向量化
        return set(re.findall(r"\w+", text.lower()))

    def search(self, query: str, k: int = 3) -> list[Document]:
        q_tokens = self._tokenize(query)
        if not q_tokens:
            return []
        scored = []
        for doc in self.docs:
            d_tokens = self._tokenize(doc.content)
            if not d_tokens:
                continue
            score = len(q_tokens & d_tokens) / len(q_tokens | d_tokens)
            scored.append((score, doc))
        scored.sort(key=lambda x: x[0], reverse=True)
        return [doc for _, doc in scored[:k] if _ > 0]
Retriever(Runnable 化,可插入 Chain)
python 复制代码
class Retriever(Runnable):
    """把 VectorStore 包装成 Runnable,可用 | 串进 Chain"""
    def __init__(self, vectorstore: VectorStore, k: int = 3):
        self.vectorstore = vectorstore
        self.k = k

    def invoke(self, query: str) -> str:
        docs = self.vectorstore.search(query, self.k)
        if not docs:
            return "(未检索到相关内容)"
        return "\n---\n".join(d.content for d in docs)

设计要点 :Retriever 的输入是字符串(问题),输出也是字符串(拼好的上下文)------符合 Runnable 的契约 ,所以可以无缝插入 | 管道。这是 LangChain 抽象最优雅的地方:RAG 不是特殊的东西,只是管道里多加了一节水管。


四、把所有积木拼起来:一个完整的 RAG + Agent 应用

python 复制代码
class MiniLangChain:
    """所有抽象的集大成者"""
    def __init__(self, llm: BaseLLM):
        self.llm = llm
        self.memory = Memory()
        self.tools: list[Tool] = []
        self.vectorstore = VectorStore()
        self.splitter = TextSplitter(chunk_size=100, overlap=10)

    def add_tool(self, tool: Tool):
        self.tools.append(tool)

    def ingest(self, text: str, metadata: dict | None = None):
        """把文档切片入库,为 RAG 做准备"""
        docs = self.splitter.split(text, metadata)
        self.vectorstore.add(docs)

    def chain(self, template: str, parser: OutputParser | None = None):
        parser = parser or StrOutputParser()
        return (
            PromptRunnable(PromptTemplate(template))
            | LLMRunnable(self.llm)
            | ParserRunnable(parser)
        )

    def rag_chain(self, k: int = 3):
        """构建 RAG 链: 问题 -> 检索 -> 拼 prompt -> LLM -> 回答"""
        retriever = Retriever(self.vectorstore, k=k)
        rag_prompt = PromptTemplate(
            "基于以下上下文回答问题。若上下文没有答案,请回答\"不知道\"。\n\n"
            "上下文:\n{context}\n\n问题: {question}\n回答:"
        )

        def invoke(question: str) -> str:
            context = retriever.invoke(question)
            prompt = rag_prompt.format(context=context, question=question)
            return self.llm.invoke(prompt)

        return invoke

    def agent(self) -> Agent:
        return Agent(self.llm, self.tools)

    def chat(self, question: str) -> str:
        return ChatChain(self.llm, self.memory).chat(question)


# ============ 完整使用示例 ============
fake = FakeLLM({
    "LangChain": "LangChain 是一个让 LLM 应用组件化的 Python 框架",
    "首都": "北京",
})
app = MiniLangChain(fake)

# 1. 简单链
summarizer = app.chain("请用一句话总结: {text}")
print(summarizer.invoke({"text": "关于 LangChain 的长篇介绍..."}))

# 2. RAG: 先喂知识,再问
app.ingest("""
LangChain 是 2022 年由 Harrison Chase 创建的开源项目。
它的核心抽象包括 LLM、PromptTemplate、Chain、Memory、Tool、Agent。
LangChain 的配套产品 LangSmith 提供 tracing 和调试能力。
后来推出的 LangGraph 用状态机替代了线性 Chain,更适合复杂 Agent。
""")

rag = app.rag_chain(k=2)
print(rag("LangChain 是什么?"))
# LLM 会基于检索到的上下文回答,而不是瞎编

# 3. 带记忆的对话
app.chat("你好")
app.chat("我刚才说了什么?")  # 会记住上下文

# 4. Agent 使用工具
app.add_tool(CALCULATOR_TOOL)
app.add_tool(SEARCH_TOOL)
app.agent().run("今天北京天气怎么样?然后帮我算 23*17")

至此,你手工搭出了一个能做 RAG、对话、工具调用的 LLM 应用框架。 所有概念都压缩在 ~300 行里,且每一行都在你控制之下。


五、LangChain 真实代码 vs 我们的最小版

抽象 最小版 LangChain 真实实现
BaseLLM 1 个方法 BaseLanguageModel + BaseChatModel,支持流式、批量、异步、回调
PromptTemplate 正则替换 支持 few-shot、partial、MessagesPlaceholder、多模态
Chain ` ` 串联
Memory list 拼接 BufferMemorySummaryMemoryVectorStoreMemory(向量召回)
Tool 函数包装 支持 Pydantic 参数 Schema、异步、权限控制
Agent 字符串解析 ReAct 支持 function calling、OpenAI tools、LangGraph 状态机
Retriever / VectorStore Jaccard 相似度词袋 真实 Embeddings (OpenAI/BGE) + FAISS/Chroma/Milvus + 多种检索策略 (MMR、Hybrid、Re-rank)

结论 :LangChain 本质没什么神秘的------它是在这 6 块积木上加了 100 种变体,外加对几十家模型/向量库/工具的适配器。


六、用真实 LangChain 重写上面的示例

下面把第四节里那个"RAG + 对话 + Agent"应用用真实 LangChain 重写一遍。对照着看,你会立刻明白"我们自己搭的 MiniLangChain"和"真货"之间的映射关系。

环境准备

bash 复制代码
pip install langchain langchain-openai langchain-community langchain-chroma \
            langchain-text-splitters langgraph
export OPENAI_API_KEY=sk-...

LangChain 从 0.3 版本开始全面拥抱 LCEL (LangChain Expression Language,用 | 组合 Runnable)和 LangGraph (Agent 状态机)。下面的代码是 2024-2025 的推荐写法,不是早期的 LLMChain / AgentExecutor 那套老 API。


示例 1:简单链(对应我们的 app.chain(...))

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_template("请用一句话总结: {text}")

# 这就是 LCEL: 和我们 MiniLangChain 里的 | 组合一模一样
summarizer = prompt | llm | StrOutputParser()

print(summarizer.invoke({"text": "LangChain 是 LLM 应用的组件化框架..."}))

映射关系:

我们的最小版 真实 LangChain
PromptRunnable(PromptTemplate(...)) ChatPromptTemplate.from_template(...)
LLMRunnable(OpenAILLM(...)) ChatOpenAI(...)
ParserRunnable(StrOutputParser()) StrOutputParser()
我们的 ` ` 操作符

示例 2:RAG(对应我们的 app.rag_chain(...))

真实 LangChain 的 RAG 要比我们的 MiniLangChain 多两步:真实 Embeddings 模型真实向量库

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 1. 切片(对应 TextSplitter)
splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10)
docs = splitter.create_documents([
    """LangChain 是 2022 年由 Harrison Chase 创建的开源项目。
    它的核心抽象包括 LLM、PromptTemplate、Chain、Memory、Tool、Agent。
    LangChain 的配套产品 LangSmith 提供 tracing 和调试能力。
    后来推出的 LangGraph 用状态机替代了线性 Chain,更适合复杂 Agent。"""
])

# 2. 向量化 + 入库(对应 VectorStore.add)
#    OpenAIEmbeddings 会调 API 把每个 chunk 转成 1536 维向量
#    Chroma 是本地向量库,生产环境可换 FAISS/Milvus/Pinecone
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})

# 3. 组装 RAG 链(对应 rag_chain)
rag_prompt = ChatPromptTemplate.from_template(
    "基于以下上下文回答问题。若上下文没有答案,请回答\"不知道\"。\n\n"
    "上下文:\n{context}\n\n问题: {question}"
)

def format_docs(docs):
    return "\n---\n".join(d.page_content for d in docs)

# 这个 dict 写法是 LCEL 的并行分发: 同一个输入分别流向 context 和 question 两路
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | ChatOpenAI(model="gpt-4o-mini")
    | StrOutputParser()
)

print(rag_chain.invoke("LangChain 是什么?"))

关键差异:

我们的最小版 真实 LangChain
Jaccard 相似度(词集合交并比) OpenAI Embeddings + 余弦相似度(1536 维向量)
Python list 存文档 Chroma(本地)/FAISS/Milvus/Pinecone
rag_chain() 返回函数 LCEL 管道(原生支持流式、异步、batch)
手动拼 context 字符串 LCEL dict 并行分发 + RunnablePassthrough

示例 3:带记忆的对话(对应我们的 app.chat(...))

python 复制代码
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息插入点
    ("human", "{question}"),
])

chain = prompt | ChatOpenAI(model="gpt-4o-mini") | StrOutputParser()

# 按 session_id 隔离不同用户的对话历史
store: dict[str, InMemoryChatMessageHistory] = {}
def get_history(session_id: str) -> InMemoryChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

chat_with_memory = RunnableWithMessageHistory(
    chain,
    get_history,
    input_messages_key="question",
    history_messages_key="history",
)

# 注意 config 里的 session_id - 这是 LangChain 多租户的核心
cfg = {"configurable": {"session_id": "user_42"}}
print(chat_with_memory.invoke({"question": "你好,我叫杰克"}, config=cfg))
print(chat_with_memory.invoke({"question": "我刚才说了什么?"}, config=cfg))
# -> 会正确回答"你说你叫杰克"

关键差异:

我们的最小版 真实 LangChain
单例 Memory 存在内存 list RunnableWithMessageHistory + 可插拔 ChatMessageHistory(Redis/Postgres/DynamoDB)
手动拼 prompt 字符串 MessagesPlaceholder 自动注入
无多用户隔离 session_id 多租户开箱即用

示例 4:Agent(对应我们的 app.agent()...)

现代 LangChain 推荐用 LangGraphcreate_react_agent 而不是旧的 AgentExecutor。LangGraph 把 Agent 建模成状态图,比我们的 while 循环强大得多(支持分支、并行、人在回路、checkpoint 回放)。

python 复制代码
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

@tool
def calculator(expr: str) -> str:
    """计算一个数学表达式,输入如 '2+3*5'"""
    try:
        return str(eval(expr, {"__builtins__": {}}, {}))
    except Exception as e:
        return f"计算错误: {e}"

@tool
def search(query: str) -> str:
    """搜索实时信息,输入搜索词"""
    fake_db = {"天气": "北京今天晴,25℃", "股价": "苹果今日收盘 $180"}
    for k, v in fake_db.items():
        if k in query:
            return v
    return "未找到"

# 一行代码构建 ReAct Agent
agent = create_react_agent(
    ChatOpenAI(model="gpt-4o-mini"),
    tools=[calculator, search],
)

# 注意输入输出格式是消息列表
result = agent.invoke({
    "messages": [("user", "今天北京天气怎么样?然后帮我算 23*17")]
})
print(result["messages"][-1].content)

关键差异:

我们的最小版 真实 LangChain
字符串正则解析 Action: / Action Input: 原生 function calling(OpenAI/Anthropic 模型直接吐结构化 tool call)
手写 ReAct prompt LangGraph 内置 prompt + 状态机
while 循环手工推进 状态图调度,可中断、可回放、可并行工具调用
Tool(name, description, func) @tool 装饰器自动从函数签名和 docstring 提取 schema
无 Schema 校验 自动生成 Pydantic schema,输入类型校验

示例 5:把它们串成一个生产可用的应用

上面 4 个示例在生产里常常需要组合起来:带记忆 + 有 RAG 检索 + 能调工具的 Agent。这是真实业务里最常见的形态。

python 复制代码
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.tools import tool

# 把 retriever 包装成 tool,让 Agent 自己决定什么时候查知识库
@tool
def lookup_knowledge(query: str) -> str:
    """查询公司内部知识库"""
    docs = retriever.invoke(query)
    return "\n---\n".join(d.page_content for d in docs)

# checkpointer = Agent 的"记忆"(状态持久化)
agent = create_react_agent(
    ChatOpenAI(model="gpt-4o-mini"),
    tools=[calculator, search, lookup_knowledge],
    checkpointer=MemorySaver(),  # 生产环境换 PostgresSaver/RedisSaver
)

cfg = {"configurable": {"thread_id": "conversation_1"}}

# 多轮对话,Agent 自己决定什么时候 RAG、什么时候算数、什么时候搜
agent.invoke({"messages": [("user", "LangChain 核心组件有哪些?")]}, config=cfg)
agent.invoke({"messages": [("user", "那你刚才提到的第三个是什么?")]}, config=cfg)

这就是"LangChain 全家桶"的真实形态:LCEL 做管道、LangGraph 做 Agent、LangSmith 做 tracing(下一节提)。


整体对照表

能力 MiniLangChain(~300 行) 真实 LangChain
简单链 `prompt llm
RAG Jaccard + list OpenAIEmbeddings + Chroma/FAISS + LCEL 分发
Memory 单例 list 拼字符串 RunnableWithMessageHistory + 多后端
Agent 正则解析 ReAct 循环 LangGraph 状态机 + native function calling
Tracing / 调试 print LangSmith(DAG 可视化、token 计费、回放)
异步 / 流式 / batch 全部 Runnable 原生支持
模型切换 换个 BaseLLM 子类 langchain-* 包的导入路径,其余不动

看完这节应该明白 :你自己写的 MiniLangChain 在抽象层面 和真实 LangChain 是同构的------都是 Runnable 组合。真实 LangChain 多出来的价值,90% 在适配器生态 (对接 50+ 模型、30+ 向量库、100+ 工具)和周边工程能力(LangSmith tracing、流式、异步、checkpoint),而不在核心抽象。

这也是为什么建议"先裸写再决定用不用" ------ 你现在已经有能力判断了。


七、给后端工程师的理解捷径

如果你来自 Spring/Scala/Java 生态,可以这样映射:

Spring 概念 LangChain 对应
@Component Bean Runnable
BeanFactory LLM provider
Filter Chain LCEL Chain
@Cacheable set_llm_cache
AOP 切面 Callback Handler
ApplicationContext Agent(持有一堆 tools)
Spring Integration 的 EIP LangGraph 的 state machine

核心范式转变

  • 传统后端:确定性流程 + 偶尔的不确定输入
  • LLM 应用:不确定性组件 + 确定性的编排层

LangChain 做的就是把不确定性(LLM 输出)封在 Runnable.invoke() 里,让外层保持工程师熟悉的确定性组合模式。


八、什么时候不需要 LangChain?

诚实讲,LangChain 在 2024 年之后争议很大:

用它的场景

  • 要快速切换多个模型供应商(抽象价值)
  • 复杂多步骤 Agent、需要现成的 Memory/Retriever 实现
  • 需要 LangSmith 做 tracing 和调试

不用它的场景

  • 单一模型(比如只用 OpenAI),直接调 SDK 更简单
  • 对延迟和代码可读性要求高------LangChain 抽象层深,栈跟踪像噩梦
  • 复杂状态机应该用 LangGraph(LangChain 自己出的继任者)或 DIY

我的建议

先用 SDK 裸写一个 demo → 遇到"重复造抽象"的痛点时再引入 LangChain → 如果发现抽象比问题还复杂,就退回裸写。


九、延伸阅读


十、把代码跑起来

上文所有代码片段拼在一起就是可运行的 minilangchain.py(约 300 行)。跑一下:

bash 复制代码
pip install openai  # 仅 OpenAILLM 需要,FakeLLM 零依赖
python minilangchain.py

看懂这 300 行,LangChain 的 95% 概念你就都理解了。剩下 5% 是工程细节(流式、回调、异步、适配器、真实向量检索),等你真的用上再去补即可。


一句话总结 :LangChain = Prompt 模板化 + LLM 抽象化 + 组件管道化 + 工具调用循环。核心不是 AI,是软件工程。

相关推荐
幸福巡礼4 小时前
【LangChain 1.2 实战(一)】 概述
笔记·学习·langchain
茉莉玫瑰花茶4 小时前
LangChain 核心组件 [ 4 ]
langchain
戋风5 小时前
从源码到实战:LangChain4j 1.14 完整学习指南(14 课全解)
langchain
老陈说编程5 小时前
12. LangChain 6大核心调用方法:invoke/stream/batch同步异步全解析,新手也能轻松学会
开发语言·人工智能·python·深度学习·机器学习·ai·langchain
网络工程小王8 小时前
【LangChain Output Parser 输出解析器】输出篇
人工智能·学习·langchain
Zfox_9 小时前
【LangChain】核心组件(上)
后端·langchain·ai编程
yanghuashuiyue10 小时前
Deep Agents 框架-开发部署
langchain·langgraph·deepagents
FrontAI10 小时前
深入浅出 LangGraph —— 第12章:多Agent系统架构
人工智能·langchain·ai agent·langgraph
狐狐生风10 小时前
LangChain实现简易版-----PDF 文档问答机器人
人工智能·langchain·机器人·pdf·prompt