基于 LangChain
^1.0版本(1.x 主线能力)
前置要求:Python 3.10+、uv、API Key(OpenAI / 硅基流动等兼容接口)
官方文档:https://python.langchain.com/docs/环境变量配置:所有 Demo 在调用 API 前需在项目根目录创建
.env文件,内容为OPENAI_API_KEY=sk-你的密钥,并在代码开头加一行from dotenv import load_dotenv; load_dotenv()。阅读提示:本文中的代码示例以教学展示为主,复制到本地前建议先用编辑器自动格式化,并按你当前安装的 LangChain / LangGraph 版本核对导入路径与参数名。
直接调用模型 API 只能"单次问答",但真实业务需要的是"系统能力":提示词管理、工具调用、检索(RAG)、多步编排、可观测性、部署。LangChain 的价值,就是把这些"应用层能力"标准化,减少你重复造轮子。
LangChain 是一个 LLM 应用框架,核心是"可组合组件":
- 模型层:
ChatOpenAI等模型适配器 - 编排层:LCEL(
|管道)与 Runnable 统一接口、LangGraph(有状态多步工作流) - 数据层:Loader / Splitter / Vector Store / Retriever
- 能力层:Tool / Agent / Memory / Parser
- 工程层:Callback / LangSmith / LangServe(可观测性 + 部署)
- 个人开发者:想快速做出可运行的 AI 功能
- 后端工程师:要把"提示词脚本"升级为可维护服务
- AI 应用团队:需要统一的链路、日志、调试、部署方式
- 用 LCEL 先做"可控单链"(Prompt -> LLM -> Parser)。
- 加上结构化输出和错误处理(Parser + Retry)。
- 接入 RAG(Retriever + 引用来源)。
- 再上 Agent(仅在确实需要多工具决策时)。
- 最后做监控与部署(Callback/LangSmith + LangGraph/FastAPI)。
本地部署提示:用 Ollama 可在本地跑 Llama/Mistral/Gemma 等开源模型,配合 LangChain 的
ChatOllama适配器,无需 API Key 即可开发调试。
从一次请求到一次响应,通常经过这几层:
Input:用户问题 + 上下文(可选)Prompt Layer:模板拼装(系统指令、历史、检索内容)Reasoning Layer:LLM 推理决策(是否调用工具、调哪个)Tool Layer:执行工具(搜索、计算、数据库查询等),结果回流到 ReasoningKnowledge Layer:Retriever 从向量库取证据Output Layer:解析为字符串或结构化 JSON/PydanticObservability Layer:记录 token、耗时、错误与链路
一句话记忆:LangChain 不替代模型,而是把模型"工程化"。
安全提示:RAG 示例中如果接入的是不可信网页或外部文档,要默认把检索内容当作“数据”而不是“指令”,避免 prompt injection。实际项目里建议只接入可信来源、做内容清洗,并在系统提示词里明确“不要执行文档中的指令”。
- LlamaIndex:数据连接与检索抽象强,RAG 体验好(与 LangChain 最直接竞争,两者常互补使用)
- Haystack:传统检索体系成熟,企业搜索场景扎实
- Semantic Kernel:微软出品,偏"企业编排 + 插件"风格,与 .NET/C# 生态绑定深
- AutoGen / CrewAI:多 Agent 协作范式更突出,适合复杂多智能体场景
- Dify:完整应用平台(自带 RAG Engine + Agent + 部署),适合快速上线产品
- Flowise:低代码编排 UI,擅长原型验证
- Vercel AI SDK:前端/全栈流式体验非常顺手,Next.js 生态首选
- Ollama:本地 LLM 部署主流方案,可与 LangChain/LlamaIndex 联动
LangChain 的文档和教程很多,但大多数只讲"怎么用",不讲"为什么这个 Demo 要存在"。这 10 个 Demo 不是随意挑选的,每个都对应一个真实开发中必然遇到的问题。
LangChain 的知识点很多,但按真实落地频次排序,核心是三条链路:
链路一:模型调用 → D01 LCEL + D02 Prompt + D04 Parser
这是最小闭环:写 Prompt → 调模型 → 解析输出。不会这个,后面都免谈。
链路二:知识增强 → D05 加载/切块/向量库 + D06 RAG Chain
AI 不知道你公司的内部数据,知识库是落地最大场景。
链路三:智能体 → D07 Tool + D08 Callback + D09 并发/分支 + D10 部署
AI 能主动调用工具、持续推理、有状态工作流,这才是 AI 应用和非 AI 应用的区别。
D03 Memory 贯穿三条链路,因为多轮对话是所有场景的共性需求。
- ✅ 掌握 LCEL 三件套:
ChatPromptTemplate+llm+StrOutputParser - ✅ 理解管道符
|原理:上游输出自动作为下游输入 - ✅ 学会用
.partial()预填充变量,减少重复传参 - ✅ 掌握三种调用方式:
.invoke()/.batch()/ 分步.format() - ✅ 理解
request_timeout、max_retries等生产级配置
你要做一个最小问答助手,输入一个主题就输出固定风格的介绍,并且要支持批量生成摘要、标题或营销文案。D01 就是把这类“Prompt -> 模型 -> 输出”的最小闭环先跑通。
# ========== LangChain 1.0 核心:LCEL 管道语法 ========== # 文件:demo01_lcel_basics.py # 官方文档:https://python.langchain.com/docs/concepts/lcel/ from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), max_tokens=1000, request_timeout=60, max_retries=2, ) prompt = ChatPromptTemplate.from_template( "你是一位{profession}专家。请用3句话介绍{topic},最后加一个彩蛋笑话。" ).partial(profession="技术写作") chain = prompt | llm | StrOutputParser() result = chain.invoke({"topic": "LangChain 框架"}) print("=== 单次调用 ===") print(result) rendered = prompt.format(topic="Python 装饰器") print("=== 渲染后的 Prompt ===") print(rendered) results = chain.batch( [ {"topic": "量子计算"}, {"topic": "区块链"}, {"topic": "神经网络"}, ] ) print("=== 批量调用 ===") for r in results: print(f"- {r[:60]}...")
ChatPromptTemplate.from_template(...) 把输入变量和提示词结构分离,方便复用
.partial(profession="技术写作") 预填固定变量,减少调用时重复传参
prompt | llm | StrOutputParser() 用 LCEL 串起提示词、模型和字符串解析
.invoke(...) 单次调用,最适合调试和在线服务
.batch([...]) 批量并发执行,适合离线生成和压测
- 模板变量名不一致会直接报错,
{topic}、{profession}必须和传参对应。 request_timeout和max_retries只是在工程层兜底,不是业务容错方案。.batch()适合并发,但不要把超长 prompt 一次性灌太多,容易超时。
- 把 Prompt 和模型参数拆成单独配置,方便 A/B 测试。
- 对外输出尽量走结构化格式,字符串只适合快速原型。
- 给示例固定一个本地脚本名,便于新手直接复制运行。
GPT plus 代充 只需 145uv add langchain langchain-openai python-dotenv uv run python demo01_lcel_basics.py
- 掌握 PromptTemplate(纯文本)和 ChatPromptTemplate(消息型)的用法
- 理解 MessagesPlaceholder 的作用:动态插入对话历史
- 学会用 .partial() 预填充变量,减少调用时传参
- 掌握 PipelinePromptTemplate 嵌套模板的用法
- 理解 SystemMessage / HumanMessage / AIMessage 三种消息类型的区别
你正在做客服或陪伴类应用,既要渲染一段稳定的系统人设,又要把历史对话动态插入到当前请求里。D02 重点就是让提示词从“拼字符串”升级成“可组合消息模板”。
# ========== Prompt Template 详解 ========== # 文件:demo02_prompt_template.py from langchain_core.prompts import ( ChatPromptTemplate, PromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate, SystemMessagePromptTemplate, PipelinePromptTemplate, ) from langchain_core.messages import HumanMessage, AIMessage from langchain_openai import ChatOpenAI from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) # 方式 A:纯文本 PromptTemplate simple_template = PromptTemplate.from_template( "请把以下中文翻译成{language}: {text}" ) result = simple_template.partial(language="English").format( text="LangChain 让 AI 开发变得简单有趣" ) print("渲染结果:", result) # 方式 B:聊天型 ChatPromptTemplate chat_prompt = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template( "你是一位{profession}专家。 " "你的工作风格:{style} " "回答时总用 emoji 增加趣味性。" ), MessagesPlaceholder(variable_name="chat_history", optional=True), HumanMessagePromptTemplate.from_template("{user_input}"), ]) print("模板变量:", chat_prompt.input_variables) messages = chat_prompt.format_messages( profession="Python", style="简洁有趣", user_input="什么是 LCEL?", chat_history=[ HumanMessage(content="你好,我想学 LangChain"), AIMessage(content="嗨!很高兴你想学 LangChain,它是一个 LLM 应用开发框架..."), ], ) response = llm.invoke(messages) print("AI 回复:", response.content) # 方式 C:PipelinePrompt introduction = PromptTemplate.from_template("你是 {character},一个乐于助人的 AI 助手。") main_prompt = PromptTemplate.from_template( "{introduction} 用户问:{question} 你的回答:" ) full_prompt = PipelinePromptTemplate( pipeline_prompts=[ ("introduction", introduction), ("main", main_prompt), ], final_prompt=PromptTemplate.from_template("{main}"), ) final = full_prompt.format(character="LangChain 助手", question="什么是 RAG?") print("PipelinePrompt 结果:", final)
PromptTemplate.from_template(...) 适合纯文本提示词和简单变量替换
ChatPromptTemplate.from_messages([...]) 适合多消息角色结构
MessagesPlaceholder(...) 给多轮历史预留一个插槽
SystemMessagePromptTemplate.from_template(...) 系统人设也可以带变量
PipelinePromptTemplate 把多个模板组合成一个更大的模板
partial()只对模板里真实存在的变量生效,变量没写进去就不会起作用。- 历史消息不要直接拼成普通字符串,优先用
MessagesPlaceholder。 PipelinePromptTemplate更适合复杂提示词,简单场景不要过度设计。
- 系统提示词尽量稳定,用户输入尽量放在后面。
- 历史消息只保留与当前任务相关的内容,避免上下文污染。
- 新项目先用
ChatPromptTemplate,再考虑更复杂的嵌套模板。
GPT plus 代充 只需 145uv add langchain langchain-openai python-dotenv uv run python demo02_prompt_template.py
- 掌握自定义 Memory 类的实现(纯 Python,不依赖旧版 langchain.memory)
- 理解 MessagesPlaceholder 如何与 Memory 配合注入历史
- 学会窗口记忆 WindowedMemory:限制历史长度防止 token 爆炸
- 理解摘要记忆 SummaryMemory:长对话时 LLM 自动压缩历史
- 掌握 get_buffer_string 把消息列表转为字符串
客服机器人、AI 助理和陪聊应用都会遇到同一个问题:用户希望它“记得我”,但历史又不能无限增长。D03 讲的是如何把“记忆”做成可控的工程组件,而不是把整段聊天一股脑塞给模型。
# ========== Memory 实现 ========== # 文件:demo03_memory.py from langchain_openai import ChatOpenAI from langchain_core.prompts import ( ChatPromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate, ) from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, get_buffer_string from langchain_core.output_parsers import StrOutputParser from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) class SimpleConversationMemory: """轻量级对话历史记忆""" def __init__(self): self.messages = [] def add_user_message(self, text: str): self.messages.append(HumanMessage(content=text)) def add_ai_message(self, text: str): self.messages.append(AIMessage(content=text)) def get_messages(self) -> list: return self.messages def clear(self): self.messages = [] prompt_with_memory = ChatPromptTemplate.from_messages([ SystemMessage(content="你是一个友好的 AI 助手,名字叫小 L。请基于对话历史回答。"), MessagesPlaceholder(variable_name="history", optional=True), HumanMessagePromptTemplate.from_template("{question}"), ]) chain = prompt_with_memory | llm | StrOutputParser() memory = SimpleConversationMemory() memory.add_user_message("我叫布鲁斯,是一名技术合伙人") memory.add_ai_message("你好布鲁斯!很高兴认识你,技术背景的合伙人。请问有什么我可以帮你的?") result = chain.invoke( ) print("AI 回复:", result) class WindowedMemory: """只保留最近 k 条消息的窗口记忆""" def __init__(self, k: int = 6): self.k = k self.messages = [] def add_user_message(self, text: str): self.messages.append(HumanMessage(content=text)) def add_ai_message(self, text: str): self.messages.append(AIMessage(content=text)) def get_messages(self) -> list: return self.messages[-self.k:] class SummaryMemory: """AI 摘要记忆:对话太长时自动压缩,节省 token""" def __init__(self, llm, max_history: int = 20): self.llm = llm self.max_history = max_history self.summary = "" self.current_messages = [] def add_user_message(self, text: str): self.current_messages.append(HumanMessage(content=text)) def add_ai_message(self, text: str): self.current_messages.append(AIMessage(content=text)) def get_context(self) -> str: recent = get_buffer_string(self.current_messages[-self.max_history:]) if self.summary: return f"【历史摘要】{self.summary} 【最近对话】 {recent}" return recent def should_summarize(self) -> bool: return len(self.current_messages) >= self.max_history * 2 def summarize(self): summary_prompt = ChatPromptTemplate.from_messages([ SystemMessage(content="请把以下对话压缩成一段简短摘要,保留关键信息:"), HumanMessagePromptTemplate.from_template("{messages}"), ]) self.summary = ( summary_prompt | self.llm | StrOutputParser() ).invoke() self.current_messages = []
SimpleConversationMemory 最基础的内存记忆容器
MessagesPlaceholder(variable_name="history") 把历史消息喂给模型
WindowedMemory 只保留最近几轮,控制上下文长度
get_buffer_string(...) 把消息列表转成可读字符串
SummaryMemory 用摘要压缩更长的对话历史
- 只用内存保存历史,服务重启后会丢失。
- 窗口记忆太短会忘掉关键上下文,太长又会贵。
- 摘要记忆本身也可能压缩错信息,适合“可容忍轻微误差”的场景。
- 生产里把历史放到 Redis、数据库或会话存储中。
- 长对话先摘要,再保留最近几轮原文。
- 把“记忆”与“检索”分开设计,不要混成一个变量。
GPT plus 代充 只需 145uv add langchain langchain-openai python-dotenv uv run python demo03_memory.py
- 掌握 JsonOutputParser 的用法和格式注入
- 掌握 PydanticOutputParser + BaseModel 实现带验证的结构化输出
- 理解 Field 的 description 如何引导 LLM 填字段
- 学会 RetryOutputParser 在解析失败时自动重试
- 理解 get_format_instructions() 的作用:告诉 LLM 怎么输出
当你要从简历、工单、订单备注、合同条款里抽取字段时,最怕模型胡说八道。D04 就是把“能读懂文本”升级为“能稳定吐出结构化数据”。
# ========== Output Parser 详解 ========== # 文件:demo04_output_parser.py from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessage from langchain_core.output_parsers import ( JsonOutputParser, PydanticOutputParser, CommaSeparatedListOutputParser, RetryOutputParser, ) from pydantic import BaseModel, Field, field_validator from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) json_parser = JsonOutputParser() json_prompt = ChatPromptTemplate.from_messages([ SystemMessage(content="你是一个数据提取助手。"), HumanMessagePromptTemplate.from_template( "从以下文本中提取信息,以 JSON 格式返回: {text} 格式要求: {format}" ), ]) chain_json = json_prompt | llm | json_parser result = chain_json.invoke( ) print("JSON 结果:", result) print("姓名:", result.get("name")) class PersonInfo(BaseModel): name: str = Field(description="人物姓名") age: int = Field(description="人物年龄(必须是整数)") city: str = Field(description="所在城市") skills: list[str] = Field(default_factory=list, description="掌握的技能列表") @field_validator("age") @classmethod def age_must_be_positive(cls, v: int) -> int: if v <= 0 or v > 150: raise ValueError(f"年龄 {v} 不合理!") return v pydantic_parser = PydanticOutputParser(pydantic_object=PersonInfo) pydantic_prompt = ( ChatPromptTemplate.from_messages([ SystemMessage(content="你是一个数据提取助手。"), HumanMessagePromptTemplate.from_template( "从以下文本中提取信息: {text} {format}" ), ]) .partial(format=pydantic_parser.get_format_instructions()) ) chain_pydantic = pydantic_prompt | llm | pydantic_parser person: PersonInfo = chain_pydantic.invoke( {"text": "布鲁斯,37岁,深圳技术合伙人,擅长 Python、Java、Go。"} ) print(f"姓名:{person.name},年龄:{person.age},城市:{person.city},技能:{person.skills}") retry_parser = RetryOutputParser.from_llm( parser=JsonOutputParser(), llm=llm, max_retries=3, ) retry_prompt = ChatPromptTemplate.from_messages([ SystemMessage(content="提取以下文本中的信息,以 JSON 返回。只返回 JSON。"), HumanMessagePromptTemplate.from_template("{text}"), ]) chain_with_retry = retry_prompt | llm | retry_parser list_parser = CommaSeparatedListOutputParser() list_prompt = ChatPromptTemplate.from_messages([ HumanMessagePromptTemplate.from_template("列出 {subject} 的 {n} 个优点,用逗号分隔。"), ]) list_chain = list_prompt | llm | list_parser result = list_chain.invoke({"subject": "Python", "n": 5}) print("列表结果:", result)
JsonOutputParser() 让模型输出 JSON 结构
PydanticOutputParser(...) 结合模型字段校验,适合生产
field_validator("age") 用 Python 侧再加一层校验
RetryOutputParser.from_llm(...) 解析失败时让模型重试修正
CommaSeparatedListOutputParser() 适合简单列表抽取
- 只让模型“输出 JSON”通常不够,最好同时注入格式说明。
JsonOutputParser只负责解析,不负责业务校验。RetryOutputParser能提高成功率,但不能替代 prompt 设计。
- 结构化输出优先选 Pydantic,字段约束更稳。
- 对外接口再包一层 schema 验证,不要直接信任模型输出。
- 如果是高价值字段抽取,最好加一层人工兜底或抽检。
GPT plus 代充 只需 145uv add langchain langchain-openai pydantic python-dotenv uv run python demo04_output_parser.py
- 掌握 TextLoader / PyPDFLoader / WebBaseLoader 三种文档加载方式
- 理解 RecursiveCharacterTextSplitter 的切块策略(chunk_size + overlap)
- 掌握 Chroma 向量库的 from_documents 和相似度检索 API
- 理解 similarity_search / with_score / MMR 三种检索方式的区别
- 学会 as_retriever() 将向量库转换为 LangChain 标准 Retriever 接口
企业知识库、FAQ 搜索、产品文档问答基本都要先把文本、PDF 和网页抓下来,再切块、向量化、入库。D05 讲的是知识库建设的“入库那一半”。
# ========== RAG 核心 ========== # 文件:demo05_rag_ingest.py from langchain_community.document_loaders import TextLoader, PyPDFLoader, WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma from dotenv import load_dotenv import os load_dotenv() embeddings = OpenAIEmbeddings( model="text-embedding-3-small", api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) loader = TextLoader("essay.txt", encoding="utf-8") documents = loader.load() print(f"文档数: {len(documents)}") pdf_loader = PyPDFLoader("paper.pdf") pdf_docs = pdf_loader.load() web_loader = WebBaseLoader("https://python.langchain.com/docs/introduction") web_docs = web_loader.load() text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, length_function=len, add_start_index=True, ) chunks = text_splitter.split_documents(documents) print(f"切块数: {len(chunks)}") vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db", ) query = "LangChain 的核心概念是什么?" docs = vectorstore.similarity_search(query=query, k=3) for i, doc in enumerate(docs, 1): print(f"[{i}] {doc.page_content[:150]}") docs_with_scores = vectorstore.similarity_search_with_score(query=query, k=3) for doc, score in docs_with_scores: print(f" [分数:{score:.4f}] {doc.page_content[:100]}") retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 3}, ) docs = retriever.invoke("LangChain 是什么?") print(f"Retriever 返回 {len(docs)} 个文档块")
补充说明:这里的
pdf_docs和web_docs只是演示不同 Loader 的用法。如果你想把三类来源都纳入同一个 RAG 流程,需要分别切块后统一入库,而不是只用documents这一支。
TextLoader / PyPDFLoader / WebBaseLoader 三类常见文档来源
RecursiveCharacterTextSplitter 把长文切成更适合检索的小块
Chroma.from_documents(...) 直接从文档块创建向量库
similarity_search_with_score(...) 既拿结果又看分数,方便调参
as_retriever() 转成标准 Retriever 接口
- 把所有文档都混在一起,不加 metadata,后面很难追踪来源。
- 切块太大检索不准,切块太小上下文不完整。
- 示例里
pdf_docs和web_docs没有真正入库,容易让新手误解。
- 入库前统一清洗、去重、加来源信息。
- 文档更新后要重建索引或做增量同步。
- 对高价值文档做来源白名单,避免把不可信网页直接接入。
GPT plus 代充 只需 145uv add langchain langchain-openai langchain-community langchain-chroma langchain-text-splitters uv run python demo05_rag_ingest.py
- 掌握 RAG Chain 的标准 LCEL 组装:retriever + format_docs + prompt + llm
- 理解 RunnablePassthrough:透传 question 参数到下游
- 学会多轮 RAG:历史指代合并(condense query)解决上下文问题
- 掌握 get_buffer_string 把消息列表转成字符串
- 理解 context 是字符串,不能用 MessagesPlaceholder
用户在知识库里提问时,经常会用“它”“这个功能”“上一条”来指代前文。D06 就是把检索和生成串起来,同时解决“知识库回答”和“多轮上下文”两个问题。
# ========== RAG Chain ========== # 文件:demo06_rag_chain.py from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_chroma import Chroma from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessage from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_core.messages import HumanMessage, AIMessage, get_buffer_string from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) embeddings = OpenAIEmbeddings( model="text-embedding-3-small", api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings) retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3}) def format_docs(docs): return " ".join(f"[来源{i+1}] {d.page_content}" for i, d in enumerate(docs)) rag_chain = ( {"question": RunnablePassthrough(), "context": retriever | format_docs} | ChatPromptTemplate.from_messages([ SystemMessage(content=( "你是知识渊博的助手,基于文档片段回答。" "如果文档中没有答案,说「没有找到相关信息」,不要编造。" )), HumanMessagePromptTemplate.from_template("文档内容: {context} 问题:{question}"), ]) | llm | StrOutputParser() ) result = rag_chain.invoke("LangChain 是什么?有哪些核心概念?") print("RAG 结果:", result[:200]) chat_history = [ HumanMessage(content="LangChain 支持哪些模型?"), AIMessage(content="LangChain 支持 OpenAI、Anthropic、Google Gemini、HuggingFace 等主流 LLM。"), ] condense_prompt = ChatPromptTemplate.from_messages([ SystemMessage(content="把对话历史和最新问题合并成一个独立的问题。"), HumanMessagePromptTemplate.from_template("历史:{chat_history} 最新问题:{question}"), ]) combined_query = ( condense_prompt | llm | StrOutputParser() ).invoke( ) print("合并检索词:", combined_query) docs = retriever.invoke(combined_query) final_chain = ( ChatPromptTemplate.from_messages([ SystemMessage(content="基于文档回答问题。"), HumanMessagePromptTemplate.from_template("文档: {context} 问题:{question}"), ]) | llm | StrOutputParser() ) answer = final_chain.invoke({"context": format_docs(docs), "question": combined_query}) print("最终答案:", answer)
RunnablePassthrough() 把原始问题透传下去 `retriever format_docs`
condense_prompt 把上下文问题改写成独立查询
get_buffer_string(chat_history) 把对话历史拼成可读文本
final_chain 基于检索内容生成最终答案
- 检索结果没来源,回答出来也难以审计。
- 改写问题这一步可能“改偏”,最好保留原问题。
- context 太长时,prompt 很快超窗。
- 给检索结果加来源标签或 URL。
- 对重要答案输出置信度或“未找到”兜底。
- 多轮 RAG 先做 query rewrite,再做 retrieval,效果更稳。
GPT plus 代充 只需 145uv add langchain langchain-openai langchain-chroma langchain-community python-dotenv uv run python demo06_rag_chain.py
- 掌握 @tool 装饰器定义工具的方式(LangChain 1.0 推荐)
- 理解工具的 docstring 自动成为 tool description
- 掌握 create_react_agent + AgentExecutor 的标准 Agent 实现
- 理解 hub.pull("hwchase17/react"):官方 ReAct 模板
- 理解 agent_scratchpad:存放中间推理结果
当用户想要一个会查天气、会做简单计算、还会搜索资料的助手时,工具调用就是必备能力。D07 的目标是把“模型会聊天”升级成“模型会办事”。
# ========== Tool + Agent ========== # 文件:demo07_tools_agent.py from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain import hub from langchain.agents import create_react_agent, AgentExecutor from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) @tool def multiply(a: int, b: int) -> int: """计算两个整数相乘""" return a * b @tool def get_weather(city: str) -> str: """查询城市天气""" weather_db = {"深圳": "晴天28C", "北京": "多云22C", "上海": "小雨18C"} return weather_db.get(city, f"暂无{city}数据") @tool def search_web(query: str) -> str: """在互联网上搜索信息""" return f"关于「{query}」的搜索结果(模拟):来自 Wikipedia、知乎..." react_prompt = hub.pull("hwchase17/react") researcher_agent = create_react_agent( llm=llm, tools=[multiply, get_weather, search_web], prompt=react_prompt, ) agent_executor = AgentExecutor.from_agent_and_tools( agent=researcher_agent, tools=[multiply, get_weather, search_web], max_iterations=10, handle_parsing_errors=True, verbose=True, ) result = agent_executor.invoke({"input": "深圳天气怎么样?然后把37和42相乘。"}) print("最终输出:", result["output"])
@tool 把 Python 函数注册为工具
hub.pull("hwchase17/react") 拉取 ReAct 模板
create_react_agent(...) 生成 Agent 核心
AgentExecutor 负责循环执行与结果汇总
max_iterations=10 避免 Agent 死循环
- 工具描述太抽象,模型不知道何时使用。
handle_parsing_errors=True只能兜底,不是根治。- 工具返回太长会让 Agent 上下文迅速膨胀。
- 只开放必要工具,最小权限原则很重要。
- 对工具入参做严格校验,避免模型乱传。
- 记录工具调用链路,方便排查误调用。
GPT plus 代充 只需 145uv add langchain langchain-openai langchainhub uv run python demo07_tools_agent.py
- 掌握 BaseCallbackHandler:继承并重写生命周期方法
- 理解 on_chain_start/end/error 和 on_llm_start/end/new_token 的触发时机
- 掌握通过 config={"callbacks": [...]} 注入自定义 Callback
- 理解 streaming=True 配合 on_llm_new_token 实现打字机效果
- 学会在 Callback 里提取 token_usage 统计成本
线上服务出了慢、错、贵的问题,最先需要的不是猜测,而是可观测性。D08 就是把链路日志、token 统计和流式输出统一起来。
# ========== Callback 可观测性 ========== # 文件:demo08_callbacks.py import logging import os from typing import Dict, List from dotenv import load_dotenv from langchain_core.callbacks import BaseCallbackHandler from langchain_core.output_parsers import StrOutputParser from langchain_core.outputs import LLMResult from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI load_dotenv() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) prompt = ChatPromptTemplate.from_template("用三句话讲一个关于 {topic} 的笑话") chain = prompt | llm | StrOutputParser() class MyCallbackHandler(BaseCallbackHandler): def on_chain_start(self, serialized: Dict, inputs: Dict, kwargs) -> None: logger.info(f"[Chain 开始] {inputs}") def on_chain_end(self, outputs: Dict, kwargs) -> None: logger.info(f"[Chain 结束] {str(outputs)[:100]}") def on_chain_error(self, error: Exception, kwargs) -> None: logger.error(f"[Chain 错误] {error}") def on_llm_start(self, serialized: Dict, prompts: List, kwargs) -> None: logger.info(f"[LLM 开始] {str(prompts)[:80]}...") def on_llm_end(self, response: LLMResult, kwargs) -> None: gen = response.generations[0][0] logger.info(f"[LLM 结束] {gen.text[:50]}...") if response.llm_output and "token_usage" in response.llm_output: usage = response.llm_output["token_usage"] logger.info(f"[Token] total=") def on_llm_new_token(self, token: str, kwargs) -> None: print(token, end="", flush=True) callback_handler = MyCallbackHandler() result = chain.invoke({"topic": "程序员"}, config={"callbacks": [callback_handler]}) print(" 最终结果:", result) class StreamingCallbackHandler(BaseCallbackHandler): def on_llm_new_token(self, token: str, kwargs) -> None: print(token, end="", flush=True) streaming_llm = ChatOpenAI( model="gpt-4o-mini", streaming=True, callbacks=[StreamingCallbackHandler()], api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) streaming_chain = prompt | streaming_llm | StrOutputParser() print(" 流式输出:") streaming_chain.invoke({"topic": "Python 编程"}) print(" ")
BaseCallbackHandler 自定义回调基类
on_chain_* 监听链执行阶段
on_llm_* 监听模型调用阶段
config={"callbacks": [...]} 动态注入回调
streaming=True 开启逐 token 输出
- 不开启流式时,
on_llm_new_token不会触发。 - 回调里做重活会拖慢整体响应。
- 某些后端不返回
token_usage,要容忍字段缺失。
- 用结构化日志记录请求 id 和会话 id。
- 回调只做轻量观察,不要在里面做复杂业务。
- 对 token 和耗时做监控,及时发现成本异常。
GPT plus 代充 只需 145uv add langchain langchain-openai python-dotenv uv run python demo08_callbacks.py
- 掌握 RunnableParallel:并行执行多个 Runnable,返回字典
- 掌握 RunnableBranch:条件分支,根据条件选择不同处理路径
- 掌握 RunnableLambda:在 LCEL 里嵌入任意 Python 逻辑
- 理解 RunnablePassthrough:透传上游输入到下游
- 理解 LCEL CoT 思维链的实现方式
一个请求往往要同时产出多个结果,比如摘要、统计和主回答;输入类型不同,还要走不同处理路径。D09 解决的就是 LCEL 的组合、分流和后处理问题。
# ========== LCEL 进阶 ========== # 文件:demo09_runnable_advanced.py from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessage from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnableLambda, RunnableBranch, RunnableParallel, RunnablePassthrough from dotenv import load_dotenv import os load_dotenv() llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) passthrough_demo = RunnablePassthrough() print("透传结果:", passthrough_demo.invoke({"topic": "LangChain"})) parallel_chain = RunnableParallel( { "openai_answer": ( ChatPromptTemplate.from_template("{topic} 是什么?用一句话回答。") | llm | StrOutputParser() ), "word_count": RunnableLambda( lambda x: f"「{x['topic']}」字符数:{len(x['topic'])}" ), } ) result = parallel_chain.invoke({"topic": "LangChain"}) print("并行结果:", result) def is_code(x): return "def " in x["input"] or "class " in x["input"] def is_math(x): return any(k in x["input"] for k in ["+", "-", "*", "/"]) branch_chain = RunnableBranch( (RunnableLambda(is_code), RunnableLambda(lambda x: f"[代码] {x['input']}")), (RunnableLambda(is_math), RunnableLambda(lambda x: f"[数学] {x['input']}")), RunnableLambda(lambda x: f"[普通] {x['input']}"), ) print(branch_chain.invoke({"input": "def hello(): pass"})) print(branch_chain.invoke({"input": "1 + 2 = 3"})) print(branch_chain.invoke({"input": "今天天气好"})) def add_suffix(text: str) -> str: return text + " [由 LCEL 处理]" chain = ( ChatPromptTemplate.from_template("介绍一下 {topic},不少于50字") | llm | StrOutputParser() | RunnableLambda(add_suffix) ) result = chain.invoke({"topic": "Python 编程语言"}) print("后处理结果:", result) cot_prompt = ChatPromptTemplate.from_messages([ SystemMessage(content=( "你是逻辑推理助手。按步骤回答:" "步骤1理解问题 步骤2列关键信息 步骤3逐步推理 步骤4给出答案" "用【步骤1】【步骤2】【步骤3】【步骤4】格式回答。" )), HumanMessagePromptTemplate.from_template("问题:{question}"), ]) cot_chain = cot_prompt | llm | StrOutputParser() result = cot_chain.invoke( {"question": "如果所有的猫都喜欢鱼,A是猫,B喜欢鱼,那么A和B一定都是猫吗?"} ) print("CoT 结果:", result)
RunnableParallel 并行执行多个分支
RunnableBranch 按条件分流
RunnableLambda 插入自定义 Python 逻辑
add_suffix 后处理生成内容
cot_prompt 用步骤化提示引导推理
- 分支条件太粗糙,容易误判。
- 并行结果格式不一致,后续汇总困难。
- 思维链更适合分析,不要把冗长推理直接暴露给终端用户。
- 并行输出尽量统一 schema。
- 分支规则先用简单规则,再考虑模型分类。
- 如果状态和分支都变复杂,考虑直接上 LangGraph。
GPT plus 代充 只需 145uv add langchain langchain-openai python-dotenv uv run python demo09_runnable_advanced.py
⚠️ 生态说明:本 Demo 使用
langgraph.prebuilt.create_react_agent,属于 LangGraph 包(而非 LangChain)。LangGraph 和 LangChain 是两个独立维护的包,各自有版本周期。Demo 07 展示的是langchain.agents方案,本 Demo 展示langgraph方案,两者适用场景不同。
- 掌握 LangGraph + FastAPI 部署 AI 服务的标准方式
- 理解 create_react_agent:LangGraph 内置 ReAct Agent
- 理解 thread_id:LangGraph 状态隔离,同 thread 共享状态
- 掌握 FastAPI 接口标准化定义:ChatRequest Pydantic 模型
- 了解 Docker 部署的标准化写法
# ========== LangGraph 部署 ========== # 文件:demo10_langgraph_deploy.py # --------------- server.py --------------- from fastapi import FastAPI from langgraph.prebuilt import create_react_agent from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain_core.messages import HumanMessage from pydantic import BaseModel, Field from dotenv import load_dotenv import os load_dotenv() app = FastAPI(title="LangGraph API", version="1.0") @tool def multiply(a: int, b: int) -> int: """计算两个整数相乘""" return a * b @tool def get_weather(city: str) -> str: """查询城市天气""" weather_db = {"深圳": "晴天28C", "北京": "多云22C"} return weather_db.get(city, f"暂无{city}数据") llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) graph = create_react_agent(model=llm, tools=[multiply, get_weather]) class ChatRequest(BaseModel): question: str = Field(..., description="用户问题") thread_id: str = Field(default="default", description="会话线程 ID") @app.post("/chat") def chat(req: ChatRequest): response = graph.invoke( {"messages": [HumanMessage(content=req.question)]}, config={"configurable": {"thread_id": req.thread_id}}, ) return {"answer": response["messages"][-1].content, "thread_id": req.thread_id} @app.get("/health") def health(): return {"status": "ok"} # 运行: uvicorn server:app --reload --port 8000 # --------------- client.py --------------- import requests resp = requests.post( "http://localhost:8000/chat", json={"question": "深圳天气?123乘以456是多少?", "thread_id": "user_001"}, timeout=30, ) print("状态:", resp.status_code, "响应:", resp.json()) # --------------- Docker --------------- # FROM python:3.11-slim # WORKDIR /app # RUN pip install uv && uv sync # COPY server.py ./ # EXPOSE 8000 # CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"] # 构建: docker build -t langgraph-api . # 运行: docker run -p 8000:8000 -e OPENAI_API_KEY=sk-xxx langgraph-api
如果你已经有一个能工作的 Agent,现在要把它变成一个可部署、可多轮、可扩容的 Web 服务,就要把图执行器包进 FastAPI。D10 处理的就是“从脚本到服务”的最后一公里。
LangGraph 是 LangChain 1.0 的新一代编排框架——支持有状态、多步、人机协作的复杂工作流,比 LCEL Chain 更强大。本 Demo 演示用 LangGraph + FastAPI 部署 AI 应用。
GPT plus 代充 只需 145# ========== LangGraph + FastAPI 部署 ========== # 官方文档:https://python.langchain.com/docs/langgraph/ from fastapi import FastAPI from langgraph.prebuilt import create_react_agent from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain_core.messages import HumanMessage from pydantic import BaseModel, Field from dotenv import load_dotenv import os import requests load_dotenv() @tool def multiply(a: int, b: int) -> int: """计算两个整数的乘积""" return a * b @tool def get_weather(city: str) -> str: """查询城市天气""" weather_db = {"深圳": "☀️ 晴天,28°C", "北京": "🌤️ 多云,22°C"} return weather_db.get(city, f"暂无 {city} 数据") llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, api_key=os.getenv("OPENAI_API_KEY"), base_url="https://api.openai.com/v1", ) # create_react_agent:LangGraph 内置的 ReAct Agent graph = create_react_agent(model=llm, tools=[multiply, get_weather]) app = FastAPI(title="LangGraph 助手 API", version="1.0") class ChatRequest(BaseModel): question: str = Field(..., description="用户问题") thread_id: str = Field(default="default", description="会话线程 ID") @app.post("/chat") def chat(req: ChatRequest): """对话接口""" response = graph.invoke( {"messages": [HumanMessage(content=req.question)]}, config={"configurable": {"thread_id": req.thread_id}}, ) ai_message = response["messages"][-1].content return {"answer": ai_message, "thread_id": req.thread_id} @app.get("/health") def health(): return {"status": "ok"} # 运行:uvicorn server:app --reload --port 8000 BASE_URL = "http://localhost:8000/chat" response = requests.post( BASE_URL, json={ "question": "深圳天气怎么样?123乘以456等于多少?", "thread_id": "user_001", }, timeout=30, ) print("状态码:", response.status_code) print("响应:", response.json()) # --------------- Docker 部署配置 --------------- # FROM python:3.11-slim # WORKDIR /app # COPY pyproject.toml ./ # RUN pip install uv && uv sync # COPY server.py ./ # EXPOSE 8000 # CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"] # 构建并运行: # docker build -t langgraph-api . # docker run -p 8000:8000 -e OPENAI_API_KEY=sk-xxx langgraph-api print("=== LangGraph 部署完成 ===") print("本地运行: uvicorn server:app --reload --port 8000") print("API 文档: http://localhost:8000/docs")
from langgraph.prebuilt import create_react_agent langgraph=LangChain 新一代编排框架 create_react_agent=快捷创建 ReAct Agent 4
@tool 装饰器定义工具 LangChain 1.0 简洁定义方式 10
create_react_agent(model=llm, tools=[...]) 创建 ReAct Agent messages 是 LangGraph 标准状态键 25
graph.invoke({"messages": [HumanMessage(...)]}, config={...}) 执行图 messages=对话历史,config=线程 ID(用于状态持久化) 26
response["messages"][-1].content 取最后一条 AI 回复 LangGraph 的状态以 messages 列表累积
- 没有状态管理时每次请求是独立的,需要 thread_id 实现多轮。
- 服务里未设置超时/并发限制,压力上来容易雪崩。
- 直接把敏感配置写进镜像层,存在泄露风险。
- 接入 LangSmith 做链路追踪和调试。
- 加入请求限流、鉴权、超时和错误码规范。
- 通过环境变量注入密钥,配置日志脱敏。
uv add langgraph langchain langchain-openai fastapi uvicorn uv run uvicorn server:app –reload –port 8000
chain = prompt | llm | parser PromptTemplate Demo 02
ChatPromptTemplate.from_messages([…]) MessagesPlaceholder Demo 03
MessagesPlaceholder(variable_name=“history”) Memory Demo 03 自定义类 + LCEL
MessagesPlaceholder OutputParser Demo 04
JsonOutputParser / PydanticOutputParser Document Loader Demo 05
TextLoader / PyPDFLoader / WebBaseLoader Text Splitter Demo 05
RecursiveCharacterTextSplitter Vector Store Demo 05
Chroma.from_documents(…) Retriever Demo 05
vectorstore.as_retriever() RAG Chain Demo 06
RunnablePassthrough + LCEL Tool Demo 07
@tool 装饰器 Agent Demo 07
create_react_agent / bind_tools Callback Demo 08
BaseCallbackHandler / .with_config(callbacks=[…]) Streaming Demo 08
llm(streaming=True) +
on_llm_new_token RunnableParallel Demo 09
RunnableParallel({…}) RunnableBranch Demo 09
RunnableBranch([…], default) RunnableLambda Demo 09
RunnableLambda(func) LangGraph Demo 10
create_react_agent / StateGraph
下一步建议:在硅基流动(siliconflow.cn)等平台申请免费 API Key,
用uv add langchain langchain-openai装好依赖,直接跑 Demo 01 验证环境。
官方文档:https://python.langchain.com/docs/
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/248713.html