394 lines
13 KiB
Plaintext
394 lines
13 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# 05 LLM 链\n",
|
||
"\n",
|
||
"## 学习目标\n",
|
||
"1. 理解 LLMChain 的本质:Prompt 模板 + 模型 + 输出解析的组合\n",
|
||
"2. 掌握使用 LCEL(LangChain Expression Language)管道符构建链\n",
|
||
"3. 理解数据在链中的流动过程:输入 -> Prompt -> LLM -> 输出\n",
|
||
"4. 学会使用链进行单条调用、批量调用和流式输出"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 1. 什么是 LLMChain\n",
|
||
"\n",
|
||
"LLMChain 是 LangChain 中最基础、最常用的链类型。它表示一个简单的处理流程:\n",
|
||
"\n",
|
||
"```\n",
|
||
"用户输入 -> Prompt模板 -> 大模型 -> 输出结果\n",
|
||
"```\n",
|
||
"\n",
|
||
"在 LangChain 的新版(LCEL)中,我们不再使用专门的 `LLMChain` 类,而是直接用 `|` 管道符将组件串联起来:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"chain = prompt | llm\n",
|
||
"```\n",
|
||
"\n",
|
||
"这种方式更直观、更灵活,也是本课程推荐的标准写法。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 2. 构建第一个 LLMChain\n",
|
||
"\n",
|
||
"我们需要三个基本组件:\n",
|
||
"1. **Prompt 模板**:定义输入格式和系统提示\n",
|
||
"2. **LLM 模型**:实际调用的大模型\n",
|
||
"3. **链**:将两者用 `|` 连接"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"from dotenv import load_dotenv\n",
|
||
"\n",
|
||
"load_dotenv()\n",
|
||
"\n",
|
||
"\n",
|
||
"# 1. 创建模型\n",
|
||
"llm = ChatOpenAI(\n",
|
||
" model='qwen3.6-35b-A3b',\n",
|
||
" temperature=0.7\n",
|
||
")\n",
|
||
"\n",
|
||
"# 2. 创建 Prompt 模板\n",
|
||
"prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一个专业的翻译助手,只输出翻译结果,不做解释。'),\n",
|
||
" ('user', '请将以下文本翻译成英文:{text}')\n",
|
||
"])\n",
|
||
"\n",
|
||
"# 3. 构建链\n",
|
||
"chain = prompt | llm\n",
|
||
"\n",
|
||
"# 4. 运行链\n",
|
||
"result = chain.invoke({'text': '人工智能正在改变世界'})\n",
|
||
"print(result.content)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解析\n",
|
||
"\n",
|
||
"| 步骤 | 代码 | 作用 |\n",
|
||
"| --- | --- | --- |\n",
|
||
"| 1 | `ChatOpenAI(...)` | 创建模型实例,指定模型名称和温度 |\n",
|
||
"| 2 | `ChatPromptTemplate.from_messages(...)` | 创建提示模板,包含 system 和 user 消息 |\n",
|
||
"| 3 | `prompt \\| llm` | 用管道符连接,形成处理链 |\n",
|
||
"| 4 | `chain.invoke({'text': '...'})` | 传入变量,执行链并获取结果 |\n",
|
||
"\n",
|
||
"`invoke()` 返回的是 `AIMessage` 对象,用 `.content` 获取文本内容。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 3. 数据流详解\n",
|
||
"\n",
|
||
"当调用 `chain.invoke({'text': '人工智能正在改变世界'})` 时,数据在链中是这样流动的:\n",
|
||
"\n",
|
||
"```text\n",
|
||
"输入: {'text': '人工智能正在改变世界'}\n",
|
||
" |\n",
|
||
" v\n",
|
||
"prompt: 将变量填入模板\n",
|
||
" system: 你是一个专业的翻译助手,只输出翻译结果,不做解释。\n",
|
||
" user: 请将以下文本翻译成英文:人工智能正在改变世界\n",
|
||
" |\n",
|
||
" v\n",
|
||
"llm: 调用大模型 API\n",
|
||
" |\n",
|
||
" v\n",
|
||
"输出: AIMessage(content='AI is changing the world.')\n",
|
||
"```\n",
|
||
"\n",
|
||
"链的每个环节都是 **Runnable**(可运行对象),它们都实现了统一的接口:\n",
|
||
"- `.invoke(input)` — 单条调用\n",
|
||
"- `.batch(inputs)` — 批量调用\n",
|
||
"- `.stream(input)` — 流式输出\n",
|
||
"- `.ainvoke(input)` — 异步调用"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 4. 多变量输入\n",
|
||
"\n",
|
||
"Prompt 模板中可以定义多个变量,invoke 时传入字典即可:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"\n",
|
||
"llm = ChatOpenAI(model='qwen3.6-35b-A3b', temperature=0.7)\n",
|
||
"\n",
|
||
"# 多变量 Prompt 模板\n",
|
||
"prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一位{style}作家。'),\n",
|
||
" ('user', '请用{style}的风格写一段关于{topic}的{format},约{length}字。')\n",
|
||
"])\n",
|
||
"\n",
|
||
"chain = prompt | llm\n",
|
||
"\n",
|
||
"result = chain.invoke({\n",
|
||
" 'style': '浪漫主义',\n",
|
||
" 'topic': '星空',\n",
|
||
" 'format': '散文',\n",
|
||
" 'length': '100'\n",
|
||
"})\n",
|
||
"print(result.content)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 5. 批量调用(Batch)\n",
|
||
"\n",
|
||
"当需要处理多条输入时,使用 `.batch()` 可以一次性发送多个请求,效率更高:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"\n",
|
||
"llm = ChatOpenAI(model='qwen3.6-35b-A3b', temperature=0.7)\n",
|
||
"\n",
|
||
"prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一个简洁的助手,只输出结果。'),\n",
|
||
" ('user', '将以下中文翻译成英文:{text}')\n",
|
||
"])\n",
|
||
"\n",
|
||
"chain = prompt | llm\n",
|
||
"\n",
|
||
"# 批量输入\n",
|
||
"inputs = [\n",
|
||
" {'text': '你好'},\n",
|
||
" {'text': '谢谢'},\n",
|
||
" {'text': '再见'},\n",
|
||
" {'text': '人工智能'}\n",
|
||
"]\n",
|
||
"\n",
|
||
"results = chain.batch(inputs)\n",
|
||
"for i, result in enumerate(results):\n",
|
||
" print(f'{i+1}. {result.content}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 6. 流式输出(Stream)\n",
|
||
"\n",
|
||
"链也支持流式输出,模型会逐字返回结果:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"\n",
|
||
"llm = ChatOpenAI(model='qwen3.6-35b-A3b', temperature=0.7)\n",
|
||
"\n",
|
||
"prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一个诗人。'),\n",
|
||
" ('user', '写一首关于{topic}的短诗。')\n",
|
||
"])\n",
|
||
"\n",
|
||
"chain = prompt | llm\n",
|
||
"\n",
|
||
"print('生成中:', end='')\n",
|
||
"for chunk in chain.stream({'topic': '秋天'}):\n",
|
||
" print(chunk.content, end='')\n",
|
||
"print()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 7. 链的重用与组合\n",
|
||
"\n",
|
||
"定义好的链可以像函数一样多次复用,也可以作为更大链的一部分继续组合。本节演示三个核心概念:\n",
|
||
"\n",
|
||
"### 核心概念\n",
|
||
"\n",
|
||
"1. **StrOutputParser()**:输出解析器,自动把模型的 AIMessage 对象转换成纯字符串\n",
|
||
"2. **链的复用**:同一个 chain 变量可以多次调用 invoke(),传入不同输入\n",
|
||
"3. **链的组合**:把多个链用 `|` 连接起来,前一个链的输出作为后一个链的输入\n",
|
||
"\n",
|
||
"### 组合链的数据流\n",
|
||
"\n",
|
||
"```\n",
|
||
"输入: {'text': '大语言模型正在改变软件开发的方式'}\n",
|
||
" |\n",
|
||
" v\n",
|
||
"translate_chain: 中文 -> 英文\n",
|
||
" 输出: 'Large language models are changing the way software is developed.'\n",
|
||
" |\n",
|
||
" v\n",
|
||
"lambda x: {'text': x}: 把字符串包装成 summarize_chain 需要的字典格式\n",
|
||
" 输出: {'text': 'Large language models are changing...'}\n",
|
||
" |\n",
|
||
" v\n",
|
||
"summarize_chain: 总结成一句话\n",
|
||
" 输出: '大型语言模型正在改变软件开发的方式。'\n",
|
||
"```"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||
"\n",
|
||
"# 创建共享的 LLM 实例\n",
|
||
"llm = ChatOpenAI(model='qwen3.6-35b-A3b', temperature=0.7)\n",
|
||
"\n",
|
||
"# ========== 1. 定义可复用的翻译链 ==========\n",
|
||
"# 先创建翻译用的 Prompt 模板\n",
|
||
"translate_prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一个翻译助手,将中文翻译成英文。'),\n",
|
||
" ('user', '{text}') # {text} 是输入变量\n",
|
||
"])\n",
|
||
"\n",
|
||
"# 构建翻译链:Prompt -> LLM -> 字符串输出解析器\n",
|
||
"# StrOutputParser() 的作用:把模型返回的 AIMessage 对象直接转成 str\n",
|
||
"translate_chain = translate_prompt | llm | StrOutputParser()\n",
|
||
"\n",
|
||
"# ========== 2. 链的复用 ==========\n",
|
||
"# 同一个 translate_chain 可以像函数一样多次调用\n",
|
||
"# 每次 invoke() 传入不同的 {'text': '...'},得到对应的英文翻译\n",
|
||
"print(translate_chain.invoke({'text': '苹果'}))\n",
|
||
"print(translate_chain.invoke({'text': '香蕉'}))\n",
|
||
"print(translate_chain.invoke({'text': '桃子'}))\n",
|
||
"\n",
|
||
"# ========== 3. 链的组合 ==========\n",
|
||
"# 再定义一个总结链,同样接收 {text} 变量\n",
|
||
"summarize_prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '请用一句话总结以下内容。'),\n",
|
||
" ('user', '{text}')\n",
|
||
"])\n",
|
||
"summarize_chain = summarize_prompt | llm | StrOutputParser()\n",
|
||
"\n",
|
||
"# 组合链:翻译后的英文传给总结链\n",
|
||
"# translate_chain 的输出是字符串,但 summarize_chain 需要字典 {'text': ...}\n",
|
||
"# 所以中间加一个 lambda 函数做格式转换\n",
|
||
"full_chain = translate_chain | (lambda x: {'text': x}) | summarize_chain\n",
|
||
"\n",
|
||
"# 运行组合链:输入中文 -> 翻译成英文 -> 总结成中文\n",
|
||
"result = full_chain.invoke({'text': '大语言模型写代码的能力越来越强。使用ai开发代码让传统的软件开发方式被改变。'})\n",
|
||
"print(f'\\n翻译+总结:{result}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 8. 链的调试与查看中间结果\n",
|
||
"\n",
|
||
"使用 `StrOutputParser()` 可以自动将模型输出转换为字符串,省去手动 `.content` 的步骤。\n",
|
||
"\n",
|
||
"如果想查看链运行过程中的中间状态(比如格式化后的 Prompt 是什么样的),可以使用回调或调试工具:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||
"\n",
|
||
"llm = ChatOpenAI(model='qwen3.6-35b-A3b', temperature=0.7)\n",
|
||
"\n",
|
||
"prompt = ChatPromptTemplate.from_messages([\n",
|
||
" ('system', '你是一个助手。'),\n",
|
||
" ('user', '你好,我叫{name}')\n",
|
||
"])\n",
|
||
"\n",
|
||
"# 查看格式化后的 Prompt\n",
|
||
"formatted = prompt.format_messages(name='小明')\n",
|
||
"print('格式化后的 Prompt:')\n",
|
||
"for msg in formatted:\n",
|
||
" print(f' {msg.type}: {msg.content}')\n",
|
||
"\n",
|
||
"chain = prompt | llm\n",
|
||
"result = chain.invoke({'name': '小明'})\n",
|
||
"print(f'\\n模型回复:{result.content}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 9. 本节课练习\n",
|
||
"\n",
|
||
"1. 创建一个 LLMChain,将用户的输入翻译成法文(修改 system 提示和 user 模板)\n",
|
||
"2. 使用多变量输入,创建一个可以根据「风格」和「主题」生成不同文章的链\n",
|
||
"3. 使用 `.batch()` 一次性翻译 5 个不同的中文句子\n",
|
||
"4. 使用 `.stream()` 让模型逐字生成一段关于「未来科技」的短文\n",
|
||
"5. 尝试在链末尾加上 `| StrOutputParser()`,观察返回结果类型的变化"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": ".venv",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.11.3"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
}
|