该文所有代码均来源于datawhale,文末有来源链接,其他部分均为个人的整理与理解。
在介绍LangChain核心组件之前,先来说明一点基础知识。
system、user、assistant是什么?
不论是在调用大模型API或者是做工作流编排时写大模型提示词,我们总会看到system、user、assistant这几部分,那他们的作用到底是什么?
system(系统角色)
用于设定模型的行为准则、身份、语气、输出格式或全局约束。它位于对话的最开始,优先级最高 。
例子: "你是一位专业翻译官,只输出翻译结果,不解释任何内容。"
user(用户角色)
代表真实用户输入的指令或问题。用户通过此角色向模型提出需求、提问或提供材料。
assistant(助手角色)
表示模型生成的回复内容,常用于多轮对话中,在对话历史中,将模型之前生成的结果作为assistant传回,以维持多轮对话的一致性 。
我们平时和deepseek、豆包等对话时会发现这些大模型好像记住了我们之前说的话,但实际上他们没有记忆功能,只会看上下文,同样是把历史对话信息全部拼接起来输入,将历史回答作为assistant继续传入大模型,让人看似是有记忆功能。
怎么用
以下代码仅作为示例说明system、user、assistant的作用
python
messages = [
{"role": "system", "content": "你是热情的AI助手,回答用中文,结尾加一句鼓励的话。"},
{"role": "user", "content": "我最近总失眠怎么办?"},
{"role": "assistant", "content": "失眠很困扰... 试试睡前远离手机和咖啡因,并固定作息时间。加油,你能睡个好觉的!"},
{"role": "user", "content": "还有更具体的方法吗?"}
]
注意点
- 当然,我们需要注意的是,即使提示词中没有system规定的行为准则也可以,或者也可以将system部分规定的身份、行为准则信息等放在user中,可以自行对比下system信息独立和将这部分信息放在user中的区别。
- 因此,system 不是必需的,但推荐使用以稳定模型行为。
- 在多轮对话中,必须把历史 assistant 回复一并传入,否则模型会丢失记忆。
LangChain核心组件模块一:模型接口
LangChain提供两类模型接口,LLM和Chat Model,不论是哪类模型接口,LangChain 都对所有大模型做了统一封装,不用关心底层是 OpenAI、Anthropic、通义千问、文心一言还是本地 Llama,调用方式都是一样的,换模型时代码几乎不用改。
LLM(文本补全模型:一步式生成任务)
特点:这是LangChain框架中较早期的模型接口,输入输出都是纯文本,没有角色(system/user/assistant)的概念,根据字符串形式的提示词输入,给出输出,适合简单的一步式生成任务,不适合多轮问答。
ChatModel(聊天模型:多轮对话)
ChatModel是为多轮对话场景设计的,不接收纯文本,而是接收一个包含多轮对话的消息列表,每个消息都带有特定的角色(system/user/assistatnt),模型内部会将这些带有角色的消息列表处理为适合对话的格式,并输出新的消息作为回复。
多轮对话示例
python
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
print("API_KEY=",API_KEY,"\nBASE_URL=",BASE_URL,"\nMODEL_NAME=",MODEL_NAME)
if not API_KEY:
raise ValueError("未检测到API_KEY,请检查.env文件是否配置正确或者获取是否正确")
chat_model = ChatOpenAI(api_key=API_KEY, base_url=BASE_URL, model_name=MODEL_NAME,temperature=0.3, max_tokens=200)
# 初始化对话历史
history = [{"role":"system", "content":"你是一个耐心的学习助手,回复简洁易懂,适合高校学生理解"}]
# 第一轮对话
history.append({"role":"user", "content":"请用3句话解释什么是LangChain"})
result = chat_model.invoke(history)
print("[第一轮回复]:", result.content)
# 将模型的回复添加到对话历史中
history.append({"role":"assistant", "content":result.content})
# 第二轮会话
# 追问,模型需要上下文才能理解它
history.append({"role":"user", "content":"它的核心组件有哪些?"})
print("第二轮会话输入 = ", history)
result = chat_model.invoke(history)
print("[第二轮回复]:", result.content)
# 继续将模型的回复添加到对话历史中
history.append({"role":"assistant", "content":result.content})
# 第三轮会话
history.append({"role":"user", "content":"给我一个简单的使用场景"})
print("第三轮会话输入 = ", history)
result = chat_model.invoke(history)
print("[第三轮回复]:", result.content)
LangChain核心组件模块二:Prompt Template(提示词模板)
提示词模板是一个将动态变量注入静态文本的结构化工具,旨在解决重复写相似提示词的问题,并确保输入格式的一致性与可维护性。
具体说明:将固定提示词写在模板中,模板中插入动态变量,可以通过修改动态变量实现提示词可复用、可维护。比如针对场景:给不同角色的人员生成某个技术的学习方案,如果没有提示词模板,我们每次都需要写很长一段,你是xxx,你的任务是xxx,输出应该是xxx以及其他的一些限制条件,但是有了提示词模板之后,针对该场景,实际上可以写一个通用的提示词模板,将不同角色人员以及技术类型等设置为动态变量,调用时只需要换动态变量即可,不需要每次都写大段的提示词。
LangChain中提示词模板分类
LangChain为不同场景提供了专用的提示词模板类型,通常分为两大类:一类用于文本补全模型(LLM),另一类用于聊天模型(Chat Model)。
LangChain中的模板类型有以下几种:PromptTemplate、ChatPromptTemplate、MessagesPlaceHolder、FewShotPromptTemplate、PipelinePromptTemplate、BasePromptTemplate。本文只介绍其中两种,用于简单文本补全模型的PromptTemplate和实现少样本学习的FewShotPromptTemplate。
PromptTemplate使用举例
PromptTemplate用于简单的文本补全模型(LLM),或生成单一字符串提示词
python
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL") # 从环境变量读取,未配置时默认为None(使用OpenAI官方地址)
MODEL_NAME = os.getenv("MODEL_NAME")
print("API_KEY=",API_KEY,'\nBASE_URL=',BASE_URL,'\nMODEL_NAME=',MODEL_NAME)
if not API_KEY:
raise ValueError("未检测到 API_KEY,请检查 .env 文件是否配置正确")
chat_model = ChatOpenAI(api_key=API_KEY, base_url=BASE_URL, model=MODEL_NAME, temperature=0.3, max_tokens=200)
# 1. 定义提示词模板
# 方法1
prompt_template = PromptTemplate.from_template("请给{user_role}写一段50字左右的{subject}学习建议,预言简洁实用,分2个小要点")
# 方法2
# input_variables:动态参数列表(这里是user_role和subject)
# template:提示词模板字符串,用{参数名}表示动态参数
# prompt_template = PromptTemplate(
# input_variables = ["user_role", "subject"],
# template = "请给{user_role}写一段50字左右的{subject}学习建议,预言简洁实用,分2个小要点"
# )
# 2. 格式化模板(传入具体参数,生成完整提示词)
# 给"高校学生"生成"LangChain"学习建议
formatted_prompt = prompt_template.format(user_role = "程序员",subject = "LangChain")
print("格式化后的提示词:", formatted_prompt)
# 3. 调用模型生成结果
result = chat_model.invoke([{"role":"user", "content":formatted_prompt}])
print("\n生成的学习建议:", result.content)
代码中核心概念讲解
一个提示词模板主要包含三个部分:
模板字符串(template):包含用 {变量名} 标记占位符的文本。
输入变量(Input Variables):列出模板中所有需要动态填充的变量名。
格式化(Format):调用 .format() 方法传入具体值,生成最终提示词。
FewShotPromptTemplate使用举例
实现少样本学习,通过在提示词中动态注入少量输入-输出示例来引导模型按照期望的格式、风格生成回答。
下面代码是生成"某学科学习方法的"案例,要求最给出的学习方法格式为"核心目标:xxx;学习步骤:1.xxx 2.xxx;注意事项:xxx"。该代码中给了模型2个示例,让模型根据示例给出新学科的学习方法:
python
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
if not API_KEY:
raise ValueError("未检测到 API_KEY,请检查 .env 文件是否配置正确")
chat_model = ChatOpenAI(api_key=API_KEY,base_url=BASE_URL,model=MODEL_NAME,temperature=0.3,max_tokens=200)
# 1. 定义示例(少样本的核心:给模型看的参考案例)
examples = [
{
"subject": "Python编程",
"method": "核心目标:掌握基础语法和常用库;学习步骤:1. 学习变量、函数等基础语法 2. 实操小项目(如计算器) 3. 学习Pandas、Matplotlib库;注意事项:多动手实操,遇到错误及时调试。"
},
{
"subject": "机器学习",
"method": "核心目标:理解基础算法原理和应用场景;学习步骤:1. 复习数学基础(线性代数、概率) 2. 学习经典算法(线性回归、决策树) 3. 用Scikit-learn实操;注意事项:先理解原理,再动手实现,避免死记硬背。"
}
]
# 2. 定义示例模板(告诉模型如何解析示例)
example_template = """
学科:{subject}
学习方法:{method}
"""
example_prompt = PromptTemplate(
input_variables=["subject", "method"],
template=example_template
)
# 3. 定义最终的提示词模板(包含示例和用户需求)
few_shot_prompt = FewShotPromptTemplate(
examples=examples, # 传入示例
example_prompt=example_prompt, # 示例模板
prefix = "请给出以下学科的学习目标",
input_variables=["new_subject"], # 动态参数:用户要查询的新学科
suffix="学科:{new_subject}\n学习方法:" # 最终给用户的提示(在示例之后)
)
# 4. 格式化模板(传入新学科:LangChain)
formatted_prompt = few_shot_prompt.format(new_subject="LangChain")
print("少样本提示词:", formatted_prompt)
# 5. 调用模型生成结果
result = chat_model.invoke([{"role": "user", "content": formatted_prompt}])
print("\n生成的LangChain学习方法:")
print(result.content)
代码中核心概念讲解
| 组件 | 作用 |
|---|---|
| examples | 示例列表,每个示例是一个字典,键对应模板中的变量名,值是对应的内容 |
| examples_prompt | 用于格式化单个示例的 PromptTemplate 对象,定义每个示例的输出格式 |
| prefix | 前缀字符串,位于所有示例之前,通常用于说明任务目标 |
| suffix | 后缀字符串,位于所有示例之后,通常包含最终的输入变量占位符 |
| input_variables | 模板中需要动态填充的变量名列表,通常仅在 suffix 中使用 |
| example_seperator | 示例之间的分隔符,默认为换行符 |
ExampleSelector动态模板使用举例
ExampleSelector 是 LangChain 中用于从庞大的示例集中,动态找出最相关、最适合当前输入的小部分示例的工具。它的核心目的是在保证模型性能的同时,优化提示词的 Token 消耗和提高示例的针对性
LangChain 提供了几种内置的示例选择器:
- LengthBasedExampleSelector (长度控制):根据输入长度,动态调整提供的示例数量。当输入很长时,自动减少示例以防超出上下文窗口;输入短时,则提供更多示例。
- SemanticSimilarityExampleSelector (语义相似):通过计算输入与示例的向量相似度,选择最相关的示例。
- MaxMarginalRelevanceExampleSelector:在保证语义相关的基础上,也考虑示例之间的多样性,避免选出内容高度重复的示例。
- NGramOverlapExampleSelector (词级重叠):基于输入和示例之间 N-Gram(连续 N 个词)的重叠度来选择,适用于对特定关键词或短语敏感的任务。
- 自定义选择器:当内置选择器无法满足特殊逻辑时,可以继承 BaseExampleSelector 并实现 select_examples 和 add_example 方法,创建完全自定义的筛选逻辑。
python
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_core.example_selectors import BaseExampleSelector, LengthBasedExampleSelector
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
import json
from typing import Dict, List
# 1. 环境初始化(工程化标准操作:环境变量管理密钥)
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
if not API_KEY:
raise ValueError("未检测到 API_KEY,请检查 .env 文件是否配置正确")
chat_model = ChatOpenAI(api_key=API_KEY, base_url=BASE_URL, model=MODEL_NAME, temperature=0.3, max_tokens=300)
# 2. 工程化示例管理:从JSON文件加载示例(避免硬编码,便于维护)
with open("learning_method_examples.json", "r", encoding="utf-8") as f:
examples = json.load(f) # 从JSON中直接提取示例数据列表
# 3. 方案A:ExampleSelector按长度筛选示例(控制提示词总长度)
# example_selector = LengthBasedExampleSelector(
# examples=examples,
# example_prompt=PromptTemplate(
# input_variables=["subject", "difficulty", "method"],
# template="学科:{subject}\n难度:{difficulty}\n学习方法:{method}\n"
# ),
# max_length=150, # 控制示例总长度,避免提示词过长
# get_text_length=lambda x: len(x) # 长度计算函数
# )
# 4. 方案B(推荐):自定义ExampleSelector按难度筛选示例
# 当需要根据用户输入的特征(如难度)精准匹配示例时,可自定义ExampleSelector
class DifficultyExampleSelector(BaseExampleSelector):
"""根据用户输入的 difficulty 字段筛选样本"""
def __init__(self, examples: List[Dict[str, str]]):
self.examples = examples
def add_example(self, example: Dict[str, str]) -> None:
self.examples.append(example)
def select_examples(self, input_variables: Dict[str, str]) -> List[Dict]:
# 获取用户输入的难度等级,如果没有提供则默认为 'easy'
target_difficulty = input_variables.get("difficulty", "easy")
# 过滤出匹配难度的所有示例
return [ex for ex in self.examples if ex.get("difficulty") == target_difficulty]
# 本案例使用方案B(按难度筛选),如需使用方案A(按长度筛选),取消方案A的注释并注释掉方案B即可
example_selector = DifficultyExampleSelector(examples=examples)
# 5. 构建工程化少样本模板
few_shot_prompt = FewShotPromptTemplate(
example_selector=example_selector, # 替换固定examples为动态选择器
example_prompt=PromptTemplate(
input_variables=["subject", "difficulty", "method"],
template="学科:{subject}\n难度:{difficulty}\n学习方法:{method}\n"
),
example_separator="\n", # 控制examples示例之间的分隔方式
prefix="少样本提示:",
input_variables=["new_subject", "difficulty"], # 新增难度参数
suffix="参考以上示例,回答:\n学科:{new_subject}\n难度:{difficulty}\n学习方法:",
)
# 6. 动态生成不同难度的提示词
# 场景1:生成入门级LangChain学习方法
formatted_prompt_easy = few_shot_prompt.format(new_subject="LangChain",difficulty="easy")
print("入门级少样本提示词:", formatted_prompt_easy)
result_easy = chat_model.invoke([{"role": "user", "content": formatted_prompt_easy}])
print("\n入门级学习方法:", result_easy.content)
# 场景2:生成进阶级LangChain学习方法
formatted_prompt_hard = few_shot_prompt.format(new_subject="LangChain",difficulty="hard")
print("\n进阶级少样本提示词:", formatted_prompt_hard)
result_hard = chat_model.invoke([{"role": "user", "content": formatted_prompt_hard}])
print("\n进阶级学习方法:", result_hard.content)
核心代码解释
自定义的选择器需要实现以下两个核心抽象方法:
add_example(self, example: Dict[str, str]) -> Any: 添加新示例到选择器的存储中。
select_examples(self, input_variables: Dict[str, str]) -> List[dict]: 根据输入变量 input_variables(来自用户的最新请求),从示例库中筛选并返回一个相关的示例列表。最终可以将任何自定义逻辑(比如关键词匹配、调用外部API、基于特定业务规则的打分等)集成到LangChain的 FewShotPromptTemplate 中,实现对提示词中示例的灵活管理
使用自定义选择器实现提示词模板的步骤
- 准备示例数据集
- 定义选择器类
- 初始化选择器
- 将选择器集成到 FewShotPromptTemplate
LangChain核心组件模块三:Output Parser(输出解析器)
输出解析器的作用:大模型默认返回纯文本,但程序一般需要结构化数据,输出解析器的作用就是将大模型返回的文本转换成我们需要的格式。
一个标准的输出解析器主要通过两个核心方法来工作:
get_format_instructions():这个方法会返回一个字符串,其中包含了指导LLM应如何组织其输出的格式说明。需要将这个说明嵌入到发送给模型的提示词中。
parse():这个方法接收LLM返回的原始字符串,按照预定义的规则将其解析成目标数据结构(如字典、列表、Pydantic对象等)。如果解析失败,通常会抛出异常。
| 解析器类型 | 功能描述 |
|---|---|
| StrOutputParser | 将LLM返回的AIMessage对象,统一转换为纯字符串 |
| CommaSeparatedListOutputParser | 将模型的输出解析为一个由逗号分隔的列表 |
| DatetimeOutputParser | 将LLM输出中的日期时间信息解析为标准的 datetime 对象 |
| JsonOutputParser | 指导LLM输出有效的JSON格式字符串,并将其解析为Python字典 |
| PydanticOutputParser | 较强大的解析器。允许使用Pydantic模型来定义复杂的数据结构,并自动进行类型验证 |
| StructuredOutputParser | 较老的解析器,用于解析简单的JSON-like结构,但功能相对有限,字段只能为字符串类型 |
| OutputFixingParser | "修复型"解析器。它包装另一个解析器,当后者解析失败时,它会调用LLM自身来修正错误的输出 |
| EnumOutputParser | 将LLM的输出解析为Python Enum 中的一个预定义值 |
| PandasDataFrameOutputParser | 专门用于指导LLM以操作pandas DataFrame的格式输出数据 |
StrOutputParser使用举例
python
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
import os
# 1. 环境初始化
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
# 2. 初始化模型(无需支持原生结构化输出)
llm = ChatOpenAI(api_key=API_KEY,base_url=BASE_URL,model=MODEL_NAME,temperature=0.3)
result_without_parser = llm.invoke("请简要介绍 LangChain 输出解析层的作用")
print("解析前:", result_without_parser.content)
print(type(result_without_parser))
# 3. 创建 StrOutputParser
# 核心作用:将 LLM 返回的 AIMessage 对象,统一转为纯字符串(str)
parser = StrOutputParser()
# 4. 链式调用:模型 → 字符串解析
chain = llm | parser #管道符|是 LangChain 的链式语法,先让模型生成回答 → 再把回答传给解析器 → 输出最终字符串
result = chain.invoke("请简要介绍 LangChain 输出解析层的作用")
print("StrOutputParser 解析后的字符串:", result)
print("\n解析结果类型:", type(result)) # str
JsonOutputParser使用举例
JsonOutputParser的作用:(1)告诉大模型:必须输出标准 JSON 格式。(2)把大模型返回的字符串,自动转成 Python 字典
python
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
# 1. 环境与模型初始化(省略,同方案1)
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
llm = ChatOpenAI(api_key=API_KEY,base_url=BASE_URL,model=MODEL_NAME,temperature=0.3)
# 2. 创建 JSON 解析器(无需额外配置,默认引导模型输出 JSON)
parser = JsonOutputParser()
print(parser.get_format_instructions())
# 3. 构建提示模板(无需手动嵌入格式指令,解析器自动关联)
prompt = PromptTemplate(
template="请介绍1个LangChain的组件,输出组件名和核心功能。{format_instructions}",
input_variables=[],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 4. 链式调用(LangChain ≥1.0.0 推荐方式,自动完成提示+调用+解析)
chain = prompt | llm | parser
result = chain.invoke({}) # 无输入参数,传入空字典
print("解析后的JSON(Python字典):")
print(result)
#print("获取单个字段:", result.get('tool_name', None)) # 可直接用于业务逻辑
核心代码解释
- 构建提示词模板部分出现了partial_variables ,该变量的作用是将已绑定的 format_instructions 值填入占位符。实际上是将变量值存储在了 PromptTemplate 对象的内部字典 中。真正的字符串替换发生在调用 invoke() 或 format() 方法时。
- 问题:partial_variables和input_variables的区别是什么,为什么不直接将parser.get_format_instructions()写在template的占位符的地方?
个人理解: - 1.input_variables是在调用的时候传入变量值,而partial_variables是模板构建的时候绑定变量,调用时无需再传入;input_variables更适合传入用户关心的业务数据,也就是由最终用户可以输入的信息,比如:要查询的城市、要翻译的句子,partial_variables更适合开发者预设的技术参数,比如输出格式、当前日期。
- 方便更换不同的输出解析器,更换解析器时,不需要再修改提示词模板。
partial_variables在这个例子中只是体现了一种更好的工程实践。它让代码意图更明确:template负责"说什么"(业务内容),partial_variables负责"怎么说"(输出格式)。
PydanticOutputParser使用举例
python
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from dotenv import load_dotenv
import os
# 1. 环境与模型初始化
load_dotenv()
API_KEY = os.getenv("API_KEY")
BASE_URL = os.getenv("BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
print("API_KEY=",API_KEY,'\nBASE_URL=',BASE_URL,'\nMODEL_NAME=',MODEL_NAME)
llm = ChatOpenAI(api_key=API_KEY,base_url=BASE_URL,model=MODEL_NAME,temperature=0.3)
# 2. 定义 Pydantic 数据模型
class ToolInfo(BaseModel):
tool_name: str = Field(description="LangChain开发工具的名称,如 LangSmith")
function: str = Field(description="工具的核心功能,30字以内")
difficulty: str = Field(description="学习难度,仅可选:简单 / 中等 / 复杂")
# 3. 创建解析器
parser = PydanticOutputParser(pydantic_object=ToolInfo)
print("parser.format=", parser.get_format_instructions())
# 4. Prompt + Chain
prompt = PromptTemplate(
template="{user_input},严格按照要求输出。\n{format_instructions}",
input_variables=["user_input"],
partial_variables={
"format_instructions": parser.get_format_instructions()
}
)
chain = prompt | llm | parser
result = chain.invoke({"user_input": "请介绍1个 Python 开发工具"})
print("解析后的结构化数据(Pydantic 模型对象):", result)
# print("字段校验 difficulty:", result.difficulty)
print("转化为字典:", result.model_dump())
核心代码解释
Pydantic 数据模型
Pydantic数据模型的作用
- 结构化 LLM(大模型)的输出(让 AI 返回固定格式的 JSON / 对象)
- 定义工具 / 函数调用的参数规范
- 数据校验和类型安全
- 配合 PydanticOutputParser 让 AI 返回固定格式结果
如何定义?
- 继承 BaseModel:所有模型必须从 pydantic.BaseModel 继承
- 变量类型要明确:str/int/float/bool/List等,比如上述代码中的变量都为str类型
- 必须写 Field (description):LLM 靠描述理解字段含义,不写会导致输出错误
PydanticOutputParser在此处的作用
该示例代码中PydanticOutputParser负责生成格式说明,告诉 LLM 将返回的文本解析成 ToolInfo 实例。解析器会根据参数参数为 pydantic_object=ToolInfo,读取字段类型、描述和约束。
ChatModel:LangChain的对话模型
PromptTemplate:用户如何可以更方便的提问OutputParser:想要实现的是如何得到用户想要的输出。
本文代码来源:datawhale