LangChain 源码剖析(三):连接提示词与大语言模型的核心纽带——LLMChain
每一篇文章都短小精悍,不啰嗦。
在 LangChain 框架中,LLMChain 是最基础也最常用的链(Chain),它承担着「格式化提示词→调用大语言模型→处理输出结果」的核心职责。无论是简单的文本生成,还是复杂流程中的一个环节,LLMChain 都是连接提示词模板(Prompt)与大语言模型(LLM)的关键纽带。本文将从架构定位、核心组件、工作流程到设计演进,全面解析这一组件的实现逻辑。

一、架构定位:LLMChain 是什么?
LLMChain 继承自 Chain 基类,是专门为「提示词驱动的语言模型调用」设计的具体实现。它解决了一个核心问题:如何将用户输入动态填充到提示词模板中,传递给语言模型,并将输出转换为可用格式。
与基类的关系
Chain(抽象基类)→ LLMChain(具体实现)
Chain 基类定义了「输入→处理→输出」的标准化流程,LLMChain 则聚焦于语言模型场景,实现了以下特化能力:
- 利用提示词模板(
PromptTemplate)动态生成输入; - 调用语言模型(
BaseLanguageModel或兼容的Runnable); - 通过输出解析器(
BaseLLMOutputParser)处理模型输出。
二、核心属性:LLMChain 的「三要素」
LLMChain 的核心功能由三个关键属性支撑,它们共同构成了「提示词→模型→输出」的完整链路:
1. prompt: BasePromptTemplate
提示词模板是 LLMChain 的「输入蓝图」,定义了输入的格式和变量。例如:
prompt = PromptTemplate(input_variables=["adjective"],template="请讲一个{adjective}的笑话" # 模板中{adjective}为变量
)
LLMChain 通过 prompt 将用户输入(如 {"adjective": "搞笑"})填充为完整提示词(如「请讲一个搞笑的笑话」)。
2. llm: Runnable[LanguageModelInput, ...]
语言模型是 LLMChain 的「计算核心」,负责处理格式化后的提示词并生成输出。它可以是:
- 基础语言模型(如
OpenAI、ChatGLM等BaseLanguageModel子类); - 兼容的
Runnable对象(LangChain 中表示「可运行组件」的接口,支持invoke、batch等方法)。
LLMChain 通过 llm 实现对语言模型的调用,屏蔽了不同模型的接口差异。
3. output_parser: BaseLLMOutputParser
输出解析器是 LLMChain 的「结果转换器」,负责将模型的原始输出(如字符串、BaseMessage)转换为业务所需的格式。默认使用 StrOutputParser(直接返回字符串),也可自定义(如解析为 JSON、列表等)。
其他关键属性
output_key: str = "text":输出结果在返回字典中的键名(默认text);return_final_only: bool = True:是否仅返回解析后的结果(True则不包含模型生成的原始信息);llm_kwargs: dict:传递给语言模型的额外参数(如temperature、max_tokens等)。
三、核心流程:从输入到输出的「五步走」
LLMChain 遵循 Chain 基类的标准化流程,其核心逻辑集中在 _call 方法,具体可拆解为五个步骤:
1. 输入处理:prep_prompts
将用户输入转换为语言模型可理解的提示词(PromptValue),是 LLMChain 的第一步。
def prep_prompts(self,input_list: list[dict[str, Any]], # 批量输入(如[{"adjective": "搞笑"}, {"adjective": "冷"}])run_manager: Optional[CallbackManagerForChainRun] = None
) -> tuple[list[PromptValue], Optional[list[str]]]:# 1. 处理停止词(stop):确保所有输入的stop参数一致stop = input_list[0].get("stop") if input_list else Noneif any(inputs.get("stop") != stop for inputs in input_list):raise ValueError("所有输入的stop参数必须一致")# 2. 填充提示词模板:将输入变量替换为实际值prompts = []for inputs in input_list:# 提取提示词模板所需的变量(过滤无关键)selected_inputs = {k: inputs[k] for k in self.prompt.input_variables}# 格式化提示词(如将{"adjective": "搞笑"}填充为"请讲一个搞笑的笑话")prompt = self.prompt.format_prompt(** selected_inputs)prompts.append(prompt)# 3. 日志输出(如果开启verbose)if run_manager and self.verbose:formatted_text = get_colored_text(prompt.to_string(), "green")run_manager.on_text(f"格式化后的提示词:\n{formatted_text}")return prompts, stop
作用:将批量输入转换为批量提示词,并统一处理停止词(模型生成时的终止标记),为调用语言模型做准备。
2. 模型调用:generate 与 agenerate
generate(同步)和 agenerate(异步)是调用语言模型的核心方法,支持批量输入处理。
def generate(self,input_list: list[dict[str, Any]], # 批量输入run_manager: Optional[CallbackManagerForChainRun] = None
) -> LLMResult:# 1. 准备提示词和停止词prompts, stop = self.prep_prompts(input_list, run_manager)# 2. 调用语言模型callbacks = run_manager.get_child() if run_manager else Noneif isinstance(self.llm, BaseLanguageModel):# 若llm是BaseLanguageModel(如OpenAI),直接调用generate_promptreturn self.llm.generate_prompt(prompts,stop=stop,callbacks=callbacks,**self.llm_kwargs # 传递额外参数(如temperature=0.7))else:# 若llm是Runnable(如prompt | llm的组合),通过bind+batch调用runnable = self.llm.bind(stop=stop,** self.llm_kwargs)results = runnable.batch(prompts, {"callbacks": callbacks})# 3. 转换结果为LLMResult格式(统一输出结构)generations = []for res in results:if isinstance(res, BaseMessage):# 若结果是消息(如Chat模型输出),包装为ChatGenerationgenerations.append([ChatGeneration(message=res)])else:# 若结果是字符串(如Completion模型输出),包装为Generationgenerations.append([Generation(text=res)])return LLMResult(generations=generations)
作用:批量调用语言模型,处理不同类型的 llm 输入(BaseLanguageModel 或 Runnable),并统一输出格式为 LLMResult(包含生成结果列表)。
3. 核心调用:_call 与 _acall
_call 是 LLMChain 执行的入口(同步版本),_acall 为异步版本,它们通过 generate/agenerate 获取结果并处理。
def _call(self,inputs: dict[str, Any], # 单条输入(如{"adjective": "搞笑"})run_manager: Optional[CallbackManagerForChainRun] = None
) -> dict[str, str]:# 1. 调用generate处理单条输入(包装为列表)response = self.generate([inputs], run_manager=run_manager)# 2. 转换结果为输出格式(通过create_outputs)return self.create_outputs(response)[0] # 取第一条结果
作用:将单条输入转换为批量输入调用 generate,再提取第一条结果返回,符合 Chain 基类的接口规范。
4. 输出处理:create_outputs
将模型生成的原始结果(LLMResult)转换为最终输出格式。
def create_outputs(self, llm_result: LLMResult) -> list[dict[str, Any]]:# 1. 解析每个生成结果result = [{self.output_key: self.output_parser.parse_result(generation), # 解析结果(如字符串→JSON)"full_generation": generation # 原始生成信息(如token数、置信度)}for generation in llm_result.generations # 遍历批量结果]# 2. 根据return_final_only决定是否保留原始信息if self.return_final_only:result = [{self.output_key: r[self.output_key]} for r in result]return result
示例:
若模型生成原始文本为 "为什么数学书总是很忧郁?因为它有太多的问题。",output_parser 为默认的 StrOutputParser,则输出为 {"text": "为什么数学书总是很忧郁?因为它有太多的问题。"}。
5. 便捷接口:predict 与 apredict
为简化调用,LLMChain 提供 predict 方法,直接传入关键字参数而非字典。
def predict(self, callbacks: Callbacks = None, **kwargs: Any) -> str:# 如llm.predict(adjective="搞笑") → 等价于llm.invoke({"adjective": "搞笑"})["text"]return self(kwargs, callbacks=callbacks)[self.output_key]
作用:降低使用门槛,适合简单场景(如直接生成文本,无需复杂参数)。
四、关键特性与设计亮点
1. 批量处理能力
通过 generate、apply 等方法支持批量输入,大幅提升处理效率。例如,同时生成 10 个不同主题的笑话,只需一次调用:
input_list = [{"adjective": adj} for adj in ["搞笑", "冷", "暖心"]]
results = llm_chain.apply(input_list) # 批量生成3个笑话
2. 兼容性设计
支持两种类型的 llm 输入:
BaseLanguageModel:传统语言模型(如OpenAI、Anthropic);Runnable:LangChain 新版推荐的可运行组件(如prompt | llm的组合)。
这种设计确保了 LLMChain 在框架升级(从旧版 Chain 到新版 Runnable)过程中的兼容性。
3. 可观测性支持
通过 run_manager 触发回调事件(如 on_text),在 verbose 模式下输出格式化后的提示词,方便调试:
Prompt after formatting:
请讲一个搞笑的笑话 # 绿色文本输出
4. 灵活的输出解析
通过 output_parser 支持多种输出格式转换,例如:
StrOutputParser:直接返回字符串(默认);JsonOutputParser:将结果解析为 JSON 字典;- 自定义解析器:如提取特定字段(如从生成文本中提取关键词)。
五、常用扩展方法
1. from_string:快速创建 LLMChain
通过模板字符串快速初始化 LLMChain,简化常用场景:
@classmethod
def from_string(cls, llm: BaseLanguageModel, template: str) -> LLMChain:prompt_template = PromptTemplate.from_template(template) # 从字符串创建提示词模板return cls(llm=llm, prompt=prompt_template)# 使用示例
llm_chain = LLMChain.from_string(llm=openai, template="请讲一个{adjective}的笑话")
2. predict:简化调用
直接通过关键字参数传入输入变量,无需构造字典:
result = llm_chain.predict(adjective="搞笑") # 等价于llm_chain.invoke({"adjective": "搞笑"})["text"]
六、设计演进:为什么 LLMChain 被标记为 deprecated?
代码中明确提到 LLMChain 已过时,推荐使用 Runnable 序列(如 prompt | llm | parser),原因如下:
1.更灵活的组合 :Runnable 序列支持更自由的组件拼接(如 prompt | llm | parser | tool),而 LLMChain 仅能固定处理「prompt→llm→parser」;
2. 更统一的接口 :Runnable 接口(invoke/batch/stream)统一了所有组件的调用方式,而 LLMChain 是特殊实现;
3. 更好的流式支持 **:Runnable 原生支持流式输出,而 LLMChain 的流式处理相对繁琐。
替代示例:
from langchain_core.runnables import RunnableSequence# 等价于LLMChain的Runnable序列
chain = RunnableSequence(prompt | llm | output_parser
)
# 调用方式与LLMChain一致
result = chain.invoke({"adjective": "搞笑"})
七、总结:LLMChain 的核心价值与局限
核心价值
-简化调用流程 :封装了「提示词填充→模型调用→结果解析」的全流程,降低入门门槛;
- 批量处理支持 :通过 generate/apply 高效处理批量输入,适合批量生成场景;
- 兼容性强 :同时支持旧版 BaseLanguageModel 和新版 Runnable,平滑过渡框架升级。
局限
-灵活性不足 :仅能处理「提示词→模型→解析器」的线性流程,复杂场景需嵌套其他链;
- 功能冗余 :新版 Runnable 序列通过简单拼接即可实现同等功能,且更轻量。
LLMChain 作为 LangChain 早期的核心组件,清晰展示了「提示词驱动模型调用」的设计思想,其源码中的批量处理、兼容性设计、可观测性支持等细节,仍是学习框架设计的重要范例。而其被 Runnable 序列替代的演进,则体现了框架设计中「简洁优于复杂」的原则 —— 好的架构会随需求演进,逐步剥离冗余,走向更灵活的组件化设计。
