init
This commit is contained in:
parent
04ae209fdc
commit
147eb780f4
|
|
@ -0,0 +1,193 @@
|
|||
# LangChain + Ollama 零基础学习指南
|
||||
|
||||
> 本地运行、完全免费的 LLM 应用开发学习
|
||||
|
||||
## 环境准备
|
||||
|
||||
### 1. 安装 Ollama
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install ollama
|
||||
|
||||
# 或下载: https://ollama.ai/download
|
||||
```
|
||||
|
||||
### 2. 下载模型
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
ollama serve
|
||||
|
||||
# 下载推荐模型(新终端)
|
||||
ollama pull qwen2.5:7b # 主力模型,中文强
|
||||
ollama pull bge-m3 # Embedding 模型,中文效果好
|
||||
|
||||
# 可选模型
|
||||
ollama pull llama3.2:3b # 轻量级
|
||||
ollama pull codellama:7b # 代码专用
|
||||
```
|
||||
|
||||
### 3. 安装 Python 依赖
|
||||
|
||||
```bash
|
||||
cd /Users/kazusa/llm/mini_lm
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
pip install langchain langchain-ollama langchain-community faiss-cpu python-dotenv pydantic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 学习路线图
|
||||
|
||||
```
|
||||
📅 Week 1: 基础篇
|
||||
├── Day 1: LLM 调用 + Prompt Template + Output Parser + Chain 基础
|
||||
├── Day 2: LCEL 表达式语言深入
|
||||
├── Day 3: Memory(记忆管理)
|
||||
└── Day 4: 综合练习
|
||||
|
||||
📅 Week 2: 核心篇
|
||||
├── Day 5: Tool(工具定义与调用)
|
||||
├── Day 6: RAG 基础(向量化 + 检索)
|
||||
├── Day 7: RAG 进阶(文档加载 + 分割策略)
|
||||
└── Day 8: 综合练习
|
||||
|
||||
📅 Week 3: Agent 篇 ⭐
|
||||
├── Day 9: Agent 基础(工具调用 Agent)
|
||||
├── Day 10: ReAct Agent(推理+行动循环)
|
||||
├── Day 11: 自定义 Agent
|
||||
└── Day 12: 综合练习
|
||||
|
||||
📅 Week 4: 实战篇
|
||||
├── Day 13: 项目1 - 智能文档问答助手
|
||||
├── Day 14: 项目2 - 代码审查助手
|
||||
└── Day 15: 部署与优化
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 课程目录
|
||||
|
||||
### Day 1: 基础入门
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day1/01_hello_llm.py` | 第一次调用 | ChatOllama、invoke |
|
||||
| `src/day1/02_prompt_template.py` | 提示词模板 | ChatPromptTemplate、变量替换 |
|
||||
| `src/day1/03_output_parser.py` | 输出解析 | StrOutputParser、JsonOutputParser |
|
||||
| `src/day1/04_chain_basics.py` | 链式调用 | LCEL、管道符 `|` |
|
||||
| `src/day1/05_practice.py` | 综合练习 | 组合运用 |
|
||||
|
||||
### Day 2: LCEL 深入 ✅ 今日学习
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day2/01_lcel_basics.py` | LCEL 语法 | RunnableSequence |
|
||||
| `src/day2/02_parallel.py` | 并行执行 | RunnableParallel |
|
||||
| `src/day2/03_passthrough.py` | 数据透传 | RunnablePassthrough |
|
||||
| `src/day2/04_branch.py` | 条件分支 | RunnableBranch |
|
||||
|
||||
### Day 3: Memory 记忆
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day3/01_chat_history.py` | 对话历史 | InMemoryChatMessageHistory |
|
||||
| `src/day3/02_memory_chain.py` | 记忆链 | RunnableWithMessageHistory |
|
||||
| `src/day3/03_window_memory.py` | 窗口记忆 | 限制历史长度 |
|
||||
|
||||
### Day 5: Tool 工具
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day5/01_tool_basics.py` | 工具定义 | @tool 装饰器 |
|
||||
| `src/day5/02_bind_tools.py` | 绑定工具 | bind_tools |
|
||||
| `src/day5/03_tool_execution.py` | 执行工具 | ToolMessage |
|
||||
|
||||
### Day 6-7: RAG 检索增强
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day6/01_embeddings.py` | 向量化 | OllamaEmbeddings |
|
||||
| `src/day6/02_vectorstore.py` | 向量存储 | FAISS |
|
||||
| `src/day6/03_retriever.py` | 检索器 | as_retriever |
|
||||
| `src/day6/04_rag_chain.py` | RAG 链 | 完整 RAG 流程 |
|
||||
| `src/day7/01_doc_loaders.py` | 文档加载 | TextLoader、DirectoryLoader |
|
||||
| `src/day7/02_text_splitter.py` | 文本分割 | RecursiveCharacterTextSplitter |
|
||||
|
||||
### Day 9-11: Agent 智能体 ⭐
|
||||
|
||||
| 文件 | 主题 | 核心概念 |
|
||||
|------|------|----------|
|
||||
| `src/day9/01_agent_basics.py` | Agent 基础 | create_tool_calling_agent |
|
||||
| `src/day9/02_agent_executor.py` | 执行器 | AgentExecutor |
|
||||
| `src/day10/01_react_agent.py` | ReAct | 推理-行动循环 |
|
||||
| `src/day11/01_custom_agent.py` | 自定义 Agent | 完全控制 Agent 行为 |
|
||||
|
||||
### Day 13-14: 实战项目
|
||||
|
||||
| 文件 | 项目 | 功能 |
|
||||
|------|------|------|
|
||||
| `src/projects/doc_qa_assistant.py` | 文档问答助手 | RAG + Memory |
|
||||
| `src/projects/code_reviewer.py` | 代码审查助手 | Tool + Agent |
|
||||
|
||||
---
|
||||
|
||||
## 核心概念速查
|
||||
|
||||
### LCEL(LangChain Expression Language)
|
||||
|
||||
```python
|
||||
# 管道符 | 连接组件
|
||||
chain = prompt | llm | parser
|
||||
|
||||
# 等价于
|
||||
chain = RunnableSequence(prompt, llm, parser)
|
||||
```
|
||||
|
||||
### 组件类型
|
||||
|
||||
| 组件 | 输入 | 输出 | 作用 |
|
||||
|------|------|------|------|
|
||||
| Prompt | dict | Messages | 构建提示词 |
|
||||
| LLM | Messages | AIMessage | 生成回复 |
|
||||
| Parser | AIMessage | str/dict | 解析输出 |
|
||||
| Retriever | str | Documents | 检索文档 |
|
||||
| Tool | dict | str | 执行操作 |
|
||||
|
||||
---
|
||||
|
||||
## 运行说明
|
||||
|
||||
```bash
|
||||
# 确保 Ollama 服务运行
|
||||
ollama serve
|
||||
|
||||
# 激活虚拟环境
|
||||
source venv/bin/activate
|
||||
|
||||
# 运行课程文件
|
||||
python src/day1/01_hello_llm.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模型推荐
|
||||
|
||||
| 场景 | 模型 | 命令 |
|
||||
|------|------|------|
|
||||
| 日常学习 | qwen2.5:7b | `ollama pull qwen2.5:7b` |
|
||||
| 低配机器 | qwen2.5:3b | `ollama pull qwen2.5:3b` |
|
||||
| RAG Embedding | bge-m3 | `ollama pull bge-m3` |
|
||||
| 代码任务 | codellama:7b | `ollama pull codellama:7b` |
|
||||
|
||||
---
|
||||
|
||||
## 学习建议
|
||||
|
||||
1. **动手为主**:每个文件都要运行,修改参数观察变化
|
||||
2. **循序渐进**:按 Day 顺序学习,不要跳跃
|
||||
3. **做好笔记**:在代码注释中记录你的理解
|
||||
4. **综合练习**:每个阶段结束后完成 practice 文件
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
从零训练一个迷你语言模型
|
||||
|
||||
第 1 天:字符级 RNN(GRU)
|
||||
|
||||
环境要求
|
||||
- Python 3.10+
|
||||
- PyTorch(Apple Silicon 支持 MPS)
|
||||
|
||||
安装与准备
|
||||
- python -m venv .venv
|
||||
- source .venv/bin/activate
|
||||
- pip install torch
|
||||
|
||||
运行
|
||||
- python day1_char_rnn.py
|
||||
|
||||
你应该看到
|
||||
- 每 100 步输出一次 loss
|
||||
- 每 200 步输出一次采样文本
|
||||
|
||||
说明
|
||||
- 这是一个极小的数据集和模型,只为验证完整训练流程。
|
||||
- 下一步我们会把硬编码文本替换为文件,并加入评估指标。
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import math
|
||||
import random
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
TEXT = (
|
||||
"起初只有一个简单的故事。"
|
||||
"它不长,但足够教会模型去猜下一个字符。"
|
||||
"我们会在这段文字上训练一个很小的语言模型,并观察它学习。"
|
||||
"实践胜过空谈,所以先从小处开始,不断迭代。"
|
||||
"很快我们会用更大的数据替换它。"
|
||||
"现在先保持小而清晰。"
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
# 序列长度(模型一次看到的字符数)
|
||||
seq_len: int = 64
|
||||
# 批大小
|
||||
batch_size: int = 32
|
||||
# 字符嵌入维度
|
||||
embed_dim: int = 64
|
||||
# GRU 隐藏层维度
|
||||
hidden_dim: int = 128
|
||||
# GRU 层数
|
||||
num_layers: int = 1
|
||||
# 学习率
|
||||
lr: float = 2e-3
|
||||
# 训练步数
|
||||
steps: int = 800
|
||||
# 采样频率
|
||||
sample_every: int = 200
|
||||
# 采样长度
|
||||
sample_len: int = 200
|
||||
# 采样温度(越高越随机)
|
||||
temperature: float = 0.9
|
||||
|
||||
|
||||
class CharRNN(nn.Module):
|
||||
def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers):
|
||||
super().__init__()
|
||||
# 字符索引 -> 向量
|
||||
self.embed = nn.Embedding(vocab_size, embed_dim)
|
||||
# GRU 用于处理序列上下文
|
||||
self.rnn = nn.GRU(
|
||||
embed_dim,
|
||||
hidden_dim,
|
||||
num_layers=num_layers,
|
||||
batch_first=True,
|
||||
)
|
||||
# 隐藏状态 -> 下一个字符的 logits
|
||||
self.fc = nn.Linear(hidden_dim, vocab_size)
|
||||
|
||||
def forward(self, x, h=None):
|
||||
# x: (batch, seq_len)
|
||||
x = self.embed(x)
|
||||
# out: (batch, seq_len, hidden_dim)
|
||||
out, h = self.rnn(x, h)
|
||||
# logits: (batch, seq_len, vocab_size)
|
||||
logits = self.fc(out)
|
||||
return logits, h
|
||||
|
||||
|
||||
def build_vocab(text):
|
||||
# 构建字符表与双向映射
|
||||
chars = sorted(set(text))
|
||||
stoi = {ch: i for i, ch in enumerate(chars)}
|
||||
itos = {i: ch for ch, i in stoi.items()}
|
||||
return chars, stoi, itos
|
||||
|
||||
|
||||
def encode(text, stoi):
|
||||
# 文本 -> 索引序列
|
||||
return torch.tensor([stoi[ch] for ch in text], dtype=torch.long)
|
||||
|
||||
|
||||
def get_batch(data, cfg):
|
||||
# 随机抽取若干段序列,生成输入 x 和目标 y(y 是 x 向后移一位)
|
||||
max_start = len(data) - cfg.seq_len - 1
|
||||
starts = torch.randint(0, max_start, (cfg.batch_size,))
|
||||
x = torch.stack([data[s : s + cfg.seq_len] for s in starts])
|
||||
y = torch.stack([data[s + 1 : s + cfg.seq_len + 1] for s in starts])
|
||||
return x, y
|
||||
|
||||
|
||||
def sample(model, stoi, itos, device, cfg, start="起初"):
|
||||
# 从给定起始串开始,按概率逐字符采样
|
||||
model.eval()
|
||||
# 如果起始字符不在词表中,使用文本开头的字符兜底
|
||||
safe_start = "".join(ch for ch in start if ch in stoi)
|
||||
if not safe_start:
|
||||
safe_start = TEXT[:1]
|
||||
idx = torch.tensor([stoi[ch] for ch in safe_start], dtype=torch.long, device=device)
|
||||
idx = idx.unsqueeze(0)
|
||||
h = None
|
||||
with torch.no_grad():
|
||||
for _ in range(cfg.sample_len):
|
||||
logits, h = model(idx, h)
|
||||
# 只取最后一个位置的预测
|
||||
logits = logits[:, -1, :] / cfg.temperature
|
||||
probs = F.softmax(logits, dim=-1)
|
||||
next_id = torch.multinomial(probs, num_samples=1)
|
||||
idx = torch.cat([idx, next_id], dim=1)
|
||||
out = "".join(itos[i] for i in idx[0].tolist())
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
cfg = Config()
|
||||
# 字符表与编码
|
||||
chars, stoi, itos = build_vocab(TEXT)
|
||||
data = encode(TEXT, stoi)
|
||||
|
||||
# Apple Silicon 上优先用 MPS
|
||||
device = "mps" if torch.backends.mps.is_available() else "cpu"
|
||||
model = CharRNN(len(chars), cfg.embed_dim, cfg.hidden_dim, cfg.num_layers).to(device)
|
||||
opt = torch.optim.AdamW(model.parameters(), lr=cfg.lr)
|
||||
|
||||
start_time = time.time()
|
||||
model.train()
|
||||
for step in range(1, cfg.steps + 1):
|
||||
x, y = get_batch(data, cfg)
|
||||
x = x.to(device)
|
||||
y = y.to(device)
|
||||
|
||||
logits, _ = model(x)
|
||||
# 交叉熵:预测下一个字符
|
||||
loss = F.cross_entropy(logits.view(-1, logits.size(-1)), y.view(-1))
|
||||
|
||||
opt.zero_grad()
|
||||
loss.backward()
|
||||
# 梯度裁剪,防止梯度爆炸
|
||||
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
|
||||
opt.step()
|
||||
|
||||
if step % 100 == 0:
|
||||
elapsed = time.time() - start_time
|
||||
speed = step / max(elapsed, 1e-6)
|
||||
print(f"step {step} | loss {loss.item():.4f} | {speed:.1f} steps/s")
|
||||
|
||||
if step % cfg.sample_every == 0:
|
||||
print("-" * 60)
|
||||
print(sample(model, stoi, itos, device, cfg))
|
||||
print("-" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 固定随机种子,便于复现
|
||||
random.seed(42)
|
||||
torch.manual_seed(42)
|
||||
main()
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# LangChain 核心
|
||||
langchain>=0.3.0
|
||||
langchain-core>=0.3.0
|
||||
langchain-community>=0.3.0
|
||||
|
||||
# Ollama 支持
|
||||
langchain-ollama>=0.2.0
|
||||
|
||||
# OpenAI 支持(可选)
|
||||
langchain-openai>=0.2.0
|
||||
|
||||
# 向量存储
|
||||
faiss-cpu>=1.8.0
|
||||
|
||||
# 数据验证
|
||||
pydantic>=2.0.0
|
||||
|
||||
# 环境变量
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
# 其他工具
|
||||
tiktoken>=0.7.0
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
Day 1 - 课程 1: 第一次调用 LLM
|
||||
=============================
|
||||
|
||||
学习目标:
|
||||
- 理解 ChatOllama 的基本用法
|
||||
- 掌握 invoke() 方法
|
||||
- 了解消息类型(SystemMessage, HumanMessage, AIMessage)
|
||||
|
||||
核心概念:
|
||||
- ChatOllama: LangChain 对 Ollama 的封装
|
||||
- invoke(): 同步调用方法
|
||||
- Messages: LangChain 的消息抽象
|
||||
"""
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
# ==================== 1. 创建模型 ====================
|
||||
# model: 指定 Ollama 中的模型名(ollama list 查看已下载模型)
|
||||
# temperature: 0-1,越低越确定性,越高越随机
|
||||
# base_url: Ollama 服务地址,默认 http://localhost:11434
|
||||
|
||||
llm = ChatOllama(
|
||||
model="qwen2.5:7b",
|
||||
temperature=0,
|
||||
# base_url="http://localhost:11434" # 默认值,一般不用改
|
||||
)
|
||||
|
||||
print("=" * 50)
|
||||
print("1. 最简单的调用 - 直接传字符串")
|
||||
print("=" * 50)
|
||||
|
||||
response = llm.invoke("你好,用一句话介绍你自己")
|
||||
print(f"类型: {type(response)}")
|
||||
print(f"内容: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 2. 使用消息对象 ====================
|
||||
print("=" * 50)
|
||||
print("2. 使用消息对象 - 带系统提示")
|
||||
print("=" * 50)
|
||||
|
||||
messages = [
|
||||
SystemMessage(content="你是一个Python专家,回答要简洁,不超过50字"),
|
||||
HumanMessage(content="什么是列表推导式?")
|
||||
]
|
||||
|
||||
response = llm.invoke(messages)
|
||||
print(f"回答: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 3. 多轮对话模拟 ====================
|
||||
print("=" * 50)
|
||||
print("3. 多轮对话模拟")
|
||||
print("=" * 50)
|
||||
|
||||
# 手动构建对话历史
|
||||
conversation = [
|
||||
SystemMessage(content="你是一个友好的助手"),
|
||||
HumanMessage(content="我叫小明"),
|
||||
AIMessage(content="你好小明!很高兴认识你!"), # 模拟之前的回复
|
||||
HumanMessage(content="我叫什么名字?") # 当前问题
|
||||
]
|
||||
|
||||
response = llm.invoke(conversation)
|
||||
print(f"回答: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 4. 流式输出 ====================
|
||||
print("=" * 50)
|
||||
print("4. 流式输出 - 逐字显示")
|
||||
print("=" * 50)
|
||||
|
||||
print("回答: ", end="", flush=True)
|
||||
for chunk in llm.stream("用3句话介绍机器学习"):
|
||||
print(chunk.content, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 修改 temperature 为 0.9,多次运行观察输出变化
|
||||
练习 2: 尝试不同的系统提示,如"你是一个诗人"、"你是一个程序员"
|
||||
练习 3: 构建一个 5 轮的对话历史,测试模型是否能记住上下文
|
||||
"""
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
Day 1 - 课程 2: Prompt Template(提示词模板)
|
||||
==========================================
|
||||
|
||||
学习目标:
|
||||
- 理解为什么需要 Prompt Template
|
||||
- 掌握 ChatPromptTemplate 的创建方式
|
||||
- 学会使用变量占位符
|
||||
|
||||
核心概念:
|
||||
- PromptTemplate: 字符串模板
|
||||
- ChatPromptTemplate: 聊天消息模板
|
||||
- invoke(): 填充变量生成最终 prompt
|
||||
|
||||
为什么需要模板?
|
||||
- 复用:同一模板可以用不同变量
|
||||
- 维护:集中管理提示词
|
||||
- 类型安全:明确需要哪些变量
|
||||
"""
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.prompts import (
|
||||
ChatPromptTemplate,
|
||||
HumanMessagePromptTemplate,
|
||||
MessagesPlaceholder,
|
||||
PromptTemplate,
|
||||
SystemMessagePromptTemplate,
|
||||
)
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
|
||||
# ==================== 1. 基础字符串模板 ====================
|
||||
print("=" * 50)
|
||||
print("1. 基础字符串模板 - PromptTemplate")
|
||||
print("=" * 50)
|
||||
|
||||
# 使用 {变量名} 作为占位符
|
||||
template = PromptTemplate.from_template(
|
||||
"请用{language}语言写一个{function_name}函数,功能是{description}"
|
||||
)
|
||||
|
||||
# 查看模板信息
|
||||
print(f"模板变量: {template.input_variables}")
|
||||
|
||||
# 填充变量
|
||||
prompt = template.invoke({
|
||||
"language": "Python",
|
||||
"function_name": "fibonacci",
|
||||
"description": "计算斐波那契数列第n项"
|
||||
})
|
||||
print(f"生成的prompt: {prompt.text}")
|
||||
print()
|
||||
|
||||
# ==================== 2. 聊天消息模板 ====================
|
||||
print("=" * 50)
|
||||
print("2. 聊天消息模板 - ChatPromptTemplate")
|
||||
print("=" * 50)
|
||||
|
||||
# 方式1:from_messages - 最常用
|
||||
chat_template = ChatPromptTemplate.from_messages([
|
||||
("system", "你是一个{role},回答风格要{style}"),
|
||||
("human", "{question}")
|
||||
])
|
||||
|
||||
messages = chat_template.invoke({
|
||||
"role": "幽默的程序员",
|
||||
"style": "轻松有趣",
|
||||
"question": "什么是递归?"
|
||||
})
|
||||
|
||||
print(f"生成的消息: {messages}")
|
||||
print()
|
||||
|
||||
# 调用 LLM
|
||||
response = llm.invoke(messages)
|
||||
print(f"回答: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 3. 使用消息模板类 ====================
|
||||
print("=" * 50)
|
||||
print("3. 使用消息模板类(更精细的控制)")
|
||||
print("=" * 50)
|
||||
|
||||
# 分别创建系统消息和用户消息模板
|
||||
system_template = SystemMessagePromptTemplate.from_template(
|
||||
"你是{company}公司的客服,公司主营{business}"
|
||||
)
|
||||
human_template = HumanMessagePromptTemplate.from_template(
|
||||
"客户问题:{question}"
|
||||
)
|
||||
|
||||
chat_template = ChatPromptTemplate.from_messages([
|
||||
system_template,
|
||||
human_template
|
||||
])
|
||||
|
||||
messages = chat_template.invoke({
|
||||
"company": "美味餐厅",
|
||||
"business": "中式快餐",
|
||||
"question": "你们营业到几点?"
|
||||
})
|
||||
|
||||
response = llm.invoke(messages)
|
||||
print(f"客服回答: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 4. 多变量复杂模板 ====================
|
||||
print("=" * 50)
|
||||
print("4. 代码审查模板示例")
|
||||
print("=" * 50)
|
||||
|
||||
code_review_template = ChatPromptTemplate.from_messages([
|
||||
("system", """你是一个资深{language}开发者,负责代码审查。
|
||||
审查时请关注:
|
||||
1. 代码风格和可读性
|
||||
2. 潜在的 bug
|
||||
3. 性能问题
|
||||
4. 安全漏洞
|
||||
|
||||
请用{output_format}格式输出审查结果。"""),
|
||||
("human", "请审查以下代码:\n```{language}\n{code}\n```")
|
||||
])
|
||||
|
||||
messages = code_review_template.invoke({
|
||||
"language": "Python",
|
||||
"output_format": "Markdown",
|
||||
"code": """def get_user(id):
|
||||
sql = f"SELECT * FROM users WHERE id = {id}"
|
||||
return db.execute(sql)"""
|
||||
})
|
||||
|
||||
response = llm.invoke(messages)
|
||||
print(f"审查结果:\n{response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 5. 部分变量填充 ====================
|
||||
print("=" * 50)
|
||||
print("5. 部分变量填充 - partial")
|
||||
print("=" * 50)
|
||||
|
||||
# 创建通用模板
|
||||
template = ChatPromptTemplate.from_messages([
|
||||
("system", "你是{company}的AI助手"),
|
||||
("human", "{question}")
|
||||
])
|
||||
|
||||
# 预填充公司名,创建专用模板
|
||||
company_template = template.partial(company="LangChain学习小组")
|
||||
|
||||
# 使用时只需要提供 question
|
||||
messages = company_template.invoke({"question": "你们是做什么的?"})
|
||||
response = llm.invoke(messages)
|
||||
print(f"回答: {response.content}")
|
||||
print()
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 创建一个"翻译助手"模板,支持指定源语言、目标语言和待翻译文本
|
||||
练习 2: 创建一个"面试官"模板,可以指定面试岗位和难度级别
|
||||
练习 3: 创建一个"故事生成器"模板,支持指定主角、背景和风格
|
||||
"""
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
"""
|
||||
Day 1 - 课程 3: Output Parser(输出解析器)
|
||||
=========================================
|
||||
|
||||
学习目标:
|
||||
- 理解为什么需要输出解析
|
||||
- 掌握常用解析器:StrOutputParser、JsonOutputParser
|
||||
- 学会使用 Pydantic 定义结构化输出
|
||||
|
||||
核心概念:
|
||||
- Parser: 将 LLM 的文本输出转换为程序可用的格式
|
||||
- format_instructions: 告诉 LLM 期望的输出格式
|
||||
- Pydantic: Python 的数据验证库
|
||||
|
||||
为什么需要解析器?
|
||||
- LLM 输出是字符串,程序需要结构化数据
|
||||
- 自动提取关键信息
|
||||
- 类型验证和错误处理
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_ollama import ChatOllama
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
|
||||
# ==================== 1. StrOutputParser ====================
|
||||
print("=" * 50)
|
||||
print("1. StrOutputParser - 最简单的解析器")
|
||||
print("=" * 50)
|
||||
|
||||
# 默认 LLM 返回的是 AIMessage 对象
|
||||
response = llm.invoke("说一个笑话")
|
||||
print(f"原始类型: {type(response)}")
|
||||
print(f"原始内容: {response}")
|
||||
print()
|
||||
|
||||
# 使用 StrOutputParser 提取纯文本
|
||||
parser = StrOutputParser()
|
||||
text = parser.invoke(response)
|
||||
print(f"解析后类型: {type(text)}")
|
||||
print(f"解析后内容: {text}")
|
||||
print()
|
||||
|
||||
# ==================== 2. 在链中使用解析器 ====================
|
||||
print("=" * 50)
|
||||
print("2. 在链中使用解析器")
|
||||
print("=" * 50)
|
||||
|
||||
prompt = ChatPromptTemplate.from_template("用一句话解释{concept}")
|
||||
|
||||
# 不使用解析器
|
||||
chain_without_parser = prompt | llm
|
||||
result1 = chain_without_parser.invoke({"concept": "人工智能"})
|
||||
print(f"不使用解析器: {type(result1)} -> {result1}")
|
||||
print()
|
||||
|
||||
# 使用解析器
|
||||
chain_with_parser = prompt | llm | StrOutputParser()
|
||||
result2 = chain_with_parser.invoke({"concept": "人工智能"})
|
||||
print(f"使用解析器: {type(result2)} -> {result2}")
|
||||
print()
|
||||
|
||||
# ==================== 3. JsonOutputParser ====================
|
||||
print("=" * 50)
|
||||
print("3. JsonOutputParser - JSON 格式输出")
|
||||
print("=" * 50)
|
||||
|
||||
# 定义期望的数据结构
|
||||
class BookInfo(BaseModel):
|
||||
"""书籍信息"""
|
||||
title: str = Field(description="书名")
|
||||
author: str = Field(description="作者")
|
||||
year: int = Field(description="出版年份")
|
||||
summary: str = Field(description="一句话简介")
|
||||
|
||||
# 创建 JSON 解析器
|
||||
json_parser = JsonOutputParser(pydantic_object=BookInfo)
|
||||
|
||||
# 获取格式说明(会告诉 LLM 期望什么格式)
|
||||
format_instructions = json_parser.get_format_instructions()
|
||||
print(f"格式说明:\n{format_instructions}\n")
|
||||
|
||||
# 构建提示词
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
("system", "你是一个图书馆助手。请严格按照指定格式输出。\n{format_instructions}"),
|
||||
("human", "介绍一下《{book_name}》这本书")
|
||||
])
|
||||
|
||||
# 构建链
|
||||
chain = prompt | llm | json_parser
|
||||
|
||||
# 调用(注意:需要传入 format_instructions)
|
||||
try:
|
||||
result = chain.invoke({
|
||||
"book_name": "三体",
|
||||
"format_instructions": format_instructions
|
||||
})
|
||||
print(f"解析结果类型: {type(result)}")
|
||||
print(f"书名: {result['title']}")
|
||||
print(f"作者: {result['author']}")
|
||||
print(f"年份: {result['year']}")
|
||||
print(f"简介: {result['summary']}")
|
||||
except Exception as e:
|
||||
print(f"解析失败: {e}")
|
||||
print()
|
||||
|
||||
# ==================== 4. 复杂结构化输出 ====================
|
||||
print("=" * 50)
|
||||
print("4. 复杂结构化输出 - 代码审查")
|
||||
print("=" * 50)
|
||||
|
||||
class CodeIssue(BaseModel):
|
||||
"""代码问题"""
|
||||
line: int = Field(description="问题所在行号")
|
||||
severity: str = Field(description="严重程度: low/medium/high")
|
||||
description: str = Field(description="问题描述")
|
||||
|
||||
class CodeReview(BaseModel):
|
||||
"""代码审查结果"""
|
||||
score: int = Field(description="代码质量评分 1-10")
|
||||
issues: List[CodeIssue] = Field(description="发现的问题列表")
|
||||
suggestions: List[str] = Field(description="改进建议列表")
|
||||
summary: str = Field(description="审查总结")
|
||||
|
||||
review_parser = JsonOutputParser(pydantic_object=CodeReview)
|
||||
|
||||
schema = CodeReview.model_json_schema()
|
||||
print(json.dumps(schema, indent=2, ensure_ascii=False))
|
||||
|
||||
review_prompt = ChatPromptTemplate.from_messages([
|
||||
("system", """你是一个资深代码审查专家。
|
||||
请仔细审查代码并按照指定格式输出结果。
|
||||
{format_instructions}"""),
|
||||
("human", "审查以下Python代码:\n```python\n{code}\n```")
|
||||
])
|
||||
|
||||
review_chain = review_prompt | llm | review_parser
|
||||
|
||||
code_to_review = """
|
||||
def calculate_total(items):
|
||||
total = 0
|
||||
for i in range(len(items)):
|
||||
total = total + items[i]['price'] * items[i]['quantity']
|
||||
return total
|
||||
"""
|
||||
|
||||
try:
|
||||
review_result = review_chain.invoke({
|
||||
"code": code_to_review,
|
||||
"format_instructions": review_parser.get_format_instructions()
|
||||
})
|
||||
|
||||
print(f"评分: {review_result['score']}/10")
|
||||
print(f"\n发现的问题:")
|
||||
for issue in review_result['issues']:
|
||||
print(f" - [行 {issue['line']}][{issue['severity']}] {issue['description']}")
|
||||
print(f"\n改进建议:")
|
||||
for suggestion in review_result['suggestions']:
|
||||
print(f" - {suggestion}")
|
||||
print(f"\n总结: {review_result['summary']}")
|
||||
except Exception as e:
|
||||
print(f"解析失败: {e}")
|
||||
print()
|
||||
|
||||
# ==================== 5. 简化写法:with_structured_output ====================
|
||||
print("=" * 50)
|
||||
print("5. 简化写法 - with_structured_output")
|
||||
print("=" * 50)
|
||||
|
||||
# 注意:此功能需要模型支持结构化输出
|
||||
# Ollama 的某些模型可能不完全支持,如果失败请使用上面的方式
|
||||
|
||||
class SimpleAnswer(BaseModel):
|
||||
"""简单回答"""
|
||||
answer: str = Field(description="答案")
|
||||
confidence: float = Field(description="置信度 0-1")
|
||||
|
||||
try:
|
||||
# 让 LLM 直接输出结构化数据
|
||||
structured_llm = llm.with_structured_output(SimpleAnswer)
|
||||
result = structured_llm.invoke("法国的首都是哪里?")
|
||||
print(f"答案: {result.answer}")
|
||||
print(f"置信度: {result.confidence}")
|
||||
except Exception as e:
|
||||
print(f"with_structured_output 不支持,请使用 JsonOutputParser: {e}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 创建一个解析器,提取新闻文章的标题、摘要、关键词
|
||||
练习 2: 创建一个解析器,解析餐厅评论的评分、优点、缺点
|
||||
练习 3: 修改代码审查解析器,添加"是否建议合并"字段
|
||||
"""
|
||||
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
"""
|
||||
Day 1 - 课程 4: Chain 基础(链式调用)
|
||||
====================================
|
||||
|
||||
学习目标:
|
||||
- 理解 LCEL(LangChain Expression Language)
|
||||
- 掌握管道符 | 的用法
|
||||
- 学会组合多个组件构建工作流
|
||||
|
||||
核心概念:
|
||||
- LCEL: LangChain 的表达式语言
|
||||
- Runnable: 所有可链接组件的基类
|
||||
- 管道符 |: 连接组件,前一个输出作为后一个输入
|
||||
|
||||
LCEL 优势:
|
||||
- 简洁:用 | 代替复杂的嵌套
|
||||
- 统一:所有组件遵循相同接口
|
||||
- 强大:支持并行、分支、错误处理
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
# ==================== 1. 基础链 ====================
|
||||
print("=" * 50)
|
||||
print("1. 基础链: prompt | llm | parser")
|
||||
print("=" * 50)
|
||||
|
||||
# 最基础的链:提示词 -> 模型 -> 解析
|
||||
prompt = ChatPromptTemplate.from_template("用一句话解释{concept}")
|
||||
|
||||
# 方式1:使用管道符
|
||||
chain = prompt | llm | parser
|
||||
|
||||
result = chain.invoke({"concept": "量子计算"})
|
||||
print(f"结果: {result}")
|
||||
print()
|
||||
|
||||
# ==================== 2. 查看链的结构 ====================
|
||||
print("=" * 50)
|
||||
print("2. 查看链的结构")
|
||||
print("=" * 50)
|
||||
|
||||
print(f"链的类型: {type(chain)}")
|
||||
print(f"链的步骤: {chain}")
|
||||
|
||||
# 可以获取链的 schema
|
||||
print(f"\n输入 Schema: {chain.input_schema.model_json_schema()}")
|
||||
print()
|
||||
|
||||
# ==================== 3. 多步骤链 ====================
|
||||
print("=" * 50)
|
||||
print("3. 多步骤链: 翻译 -> 总结")
|
||||
print("=" * 50)
|
||||
|
||||
# 第一步:翻译成英文
|
||||
translate_prompt = ChatPromptTemplate.from_template(
|
||||
"将以下中文翻译成英文,只输出翻译结果:\n{text}"
|
||||
)
|
||||
translate_chain = translate_prompt | llm | parser
|
||||
|
||||
# 第二步:用英文总结
|
||||
summarize_prompt = ChatPromptTemplate.from_template(
|
||||
"Summarize the following text in one sentence:\n{english_text}"
|
||||
)
|
||||
summarize_chain = summarize_prompt | llm | parser
|
||||
|
||||
# 手动串联
|
||||
chinese_text = "人工智能正在改变我们的生活方式,从智能手机到自动驾驶汽车,AI无处不在。"
|
||||
|
||||
english = translate_chain.invoke({"text": chinese_text})
|
||||
print(f"翻译结果: {english}")
|
||||
|
||||
summary = summarize_chain.invoke({"english_text": english})
|
||||
print(f"英文总结: {summary}")
|
||||
print()
|
||||
|
||||
# ==================== 4. RunnableLambda - 自定义函数 ====================
|
||||
print("=" * 50)
|
||||
print("4. RunnableLambda - 在链中插入自定义逻辑")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
# 定义处理函数
|
||||
def add_prefix(text: str) -> str:
|
||||
return f"[AI回答] {text}"
|
||||
|
||||
|
||||
def word_count(text: str) -> dict:
|
||||
return {
|
||||
"text": text,
|
||||
"word_count": len(text),
|
||||
"char_count": len(text.replace(" ", "")),
|
||||
}
|
||||
|
||||
|
||||
# 创建 Runnable
|
||||
add_prefix_runnable = RunnableLambda(add_prefix)
|
||||
word_count_runnable = RunnableLambda(word_count)
|
||||
|
||||
# 组合链
|
||||
chain_with_lambda = (
|
||||
ChatPromptTemplate.from_template("用20字以内回答:{question}")
|
||||
| llm
|
||||
| parser
|
||||
| add_prefix_runnable
|
||||
| word_count_runnable
|
||||
)
|
||||
|
||||
|
||||
result = chain_with_lambda.invoke({"question": "什么是Python?"})
|
||||
print(f"结果: {result}")
|
||||
print()
|
||||
|
||||
# ==================== 5. RunnablePassthrough - 数据透传 ====================
|
||||
print("=" * 50)
|
||||
print("5. RunnablePassthrough - 保留原始输入")
|
||||
print("=" * 50)
|
||||
|
||||
# 场景:既要 LLM 的输出,也要保留原始问题
|
||||
from langchain_core.runnables import RunnableParallel
|
||||
|
||||
chain_with_passthrough = RunnableParallel(
|
||||
answer=ChatPromptTemplate.from_template("回答:{question}") | llm | parser,
|
||||
original_question=RunnablePassthrough(), # 透传原始输入
|
||||
)
|
||||
|
||||
result = chain_with_passthrough.invoke({"question": "天空为什么是蓝色的?"})
|
||||
print(f"原始问题: {result['original_question']}")
|
||||
print(f"AI回答: {result['answer']}")
|
||||
print()
|
||||
|
||||
# ==================== 6. 链的批量处理 ====================
|
||||
print("=" * 50)
|
||||
print("6. 批量处理 - batch")
|
||||
print("=" * 50)
|
||||
|
||||
simple_chain = (
|
||||
ChatPromptTemplate.from_template("用一个词描述{thing}的特点") | llm | parser
|
||||
)
|
||||
|
||||
# 批量处理多个输入
|
||||
inputs = [{"thing": "太阳"}, {"thing": "月亮"}, {"thing": "星星"}]
|
||||
|
||||
results = simple_chain.batch(inputs)
|
||||
for inp, res in zip(inputs, results):
|
||||
print(f"{inp['thing']}: {res}")
|
||||
print()
|
||||
|
||||
# ==================== 7. 完整示例:问答增强链 ====================
|
||||
print("=" * 50)
|
||||
print("7. 完整示例:问答增强链")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
def enhance_question(question: str) -> str:
|
||||
"""增强问题,添加上下文要求"""
|
||||
return f"{question}\n\n请提供详细解释,并举一个例子。"
|
||||
|
||||
|
||||
def format_response(response: str) -> str:
|
||||
"""格式化响应"""
|
||||
lines = response.strip().split("\n")
|
||||
formatted = []
|
||||
for i, line in enumerate(lines, 1):
|
||||
if line.strip():
|
||||
formatted.append(f" {line}")
|
||||
return "\n".join(formatted)
|
||||
|
||||
|
||||
qa_chain = (
|
||||
RunnableLambda(lambda x: {"question": enhance_question(x["question"])})
|
||||
| ChatPromptTemplate.from_template("{question}")
|
||||
| llm
|
||||
| parser
|
||||
| RunnableLambda(format_response)
|
||||
)
|
||||
|
||||
result = qa_chain.invoke({"question": "什么是装饰器?"})
|
||||
print(f"回答:\n{result}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 创建一个"中文 -> 英文 -> 日文"的翻译链
|
||||
练习 2: 创建一个链,先生成代码,然后自动添加注释
|
||||
练习 3: 创建一个链,输入关键词,输出包含标题、正文、标签的文章结构
|
||||
"""
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
练习 1: 创建一个"中文 -> 英文 -> 日文"的翻译链
|
||||
练习 2: 创建一个链,先生成代码,然后自动添加注释
|
||||
练习 3: 创建一个链,输入关键词,输出包含标题、正文、标签的文章结构
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableLambda
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
print("=" * 50)
|
||||
print('练习 1: 创建一个"中文 -> 英文 -> 日文"的翻译链')
|
||||
print("=" * 50)
|
||||
|
||||
en_prompt = ChatPromptTemplate.from_template("将下列这段话翻译成英文:{text}")
|
||||
jp_prompt = ChatPromptTemplate.from_template("将下列这段话翻译成日文:{text}")
|
||||
|
||||
|
||||
def package_translate(text: str) -> dict:
|
||||
return {"text": text}
|
||||
|
||||
|
||||
def print_answer(ans: str):
|
||||
print(f"llm回答:{ans}")
|
||||
return ans
|
||||
|
||||
|
||||
package_step = RunnableLambda[str, dict](package_translate)
|
||||
print_step = RunnableLambda[str, str](print_answer)
|
||||
|
||||
translate_chain = (
|
||||
package_step
|
||||
| en_prompt
|
||||
| llm
|
||||
| parser
|
||||
| print_step
|
||||
| package_step
|
||||
| jp_prompt
|
||||
| llm
|
||||
| parser
|
||||
| print_step
|
||||
)
|
||||
result = translate_chain.invoke("你好啊")
|
||||
print(f"回答:\n{result}")
|
||||
|
||||
print("=" * 50)
|
||||
print('练习 2: 创建一个链,先生成代码,然后自动添加注释')
|
||||
print("=" * 50)
|
||||
|
||||
ChatPromptTemplate.from_template("请用Python帮我实现:{text}")
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
"""
|
||||
Day 1 - 课程 5: 综合练习
|
||||
======================
|
||||
|
||||
今日知识点回顾:
|
||||
1. ChatOllama - 调用本地 LLM
|
||||
2. ChatPromptTemplate - 构建提示词模板
|
||||
3. OutputParser - 解析 LLM 输出
|
||||
4. Chain (LCEL) - 链式组合组件
|
||||
|
||||
本练习将综合运用以上知识,构建一个实用的小工具。
|
||||
"""
|
||||
|
||||
from langchain_ollama import ChatOllama
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
|
||||
from langchain_core.runnables import RunnableLambda, RunnableParallel
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
# ==================== 练习 1: 多语言翻译助手 ====================
|
||||
print("=" * 60)
|
||||
print("练习 1: 多语言翻译助手")
|
||||
print("=" * 60)
|
||||
|
||||
class TranslationResult(BaseModel):
|
||||
"""翻译结果"""
|
||||
original: str = Field(description="原文")
|
||||
translated: str = Field(description="译文")
|
||||
source_lang: str = Field(description="源语言")
|
||||
target_lang: str = Field(description="目标语言")
|
||||
|
||||
translation_parser = JsonOutputParser(pydantic_object=TranslationResult)
|
||||
|
||||
translation_prompt = ChatPromptTemplate.from_messages([
|
||||
("system", """你是一个专业翻译。将文本从{source_lang}翻译成{target_lang}。
|
||||
{format_instructions}"""),
|
||||
("human", "翻译:{text}")
|
||||
])
|
||||
|
||||
translation_chain = translation_prompt | llm | translation_parser
|
||||
|
||||
# 测试
|
||||
try:
|
||||
result = translation_chain.invoke({
|
||||
"source_lang": "中文",
|
||||
"target_lang": "英文",
|
||||
"text": "学习永无止境",
|
||||
"format_instructions": translation_parser.get_format_instructions()
|
||||
})
|
||||
print(f"原文 ({result['source_lang']}): {result['original']}")
|
||||
print(f"译文 ({result['target_lang']}): {result['translated']}")
|
||||
except Exception as e:
|
||||
print(f"翻译失败: {e}")
|
||||
print()
|
||||
|
||||
# ==================== 练习 2: 智能摘要生成器 ====================
|
||||
print("=" * 60)
|
||||
print("练习 2: 智能摘要生成器")
|
||||
print("=" * 60)
|
||||
|
||||
class ArticleSummary(BaseModel):
|
||||
"""文章摘要"""
|
||||
title: str = Field(description="提取或生成的标题")
|
||||
summary: str = Field(description="100字以内的摘要")
|
||||
keywords: List[str] = Field(description="3-5个关键词")
|
||||
sentiment: str = Field(description="情感倾向: positive/negative/neutral")
|
||||
|
||||
summary_parser = JsonOutputParser(pydantic_object=ArticleSummary)
|
||||
|
||||
summary_prompt = ChatPromptTemplate.from_messages([
|
||||
("system", """你是一个文本分析专家。分析给定文章并提取关键信息。
|
||||
{format_instructions}"""),
|
||||
("human", "分析以下文章:\n\n{article}")
|
||||
])
|
||||
|
||||
summary_chain = summary_prompt | llm | summary_parser
|
||||
|
||||
article = """
|
||||
人工智能(AI)正在以前所未有的速度改变我们的世界。从智能语音助手到自动驾驶汽车,
|
||||
从医疗诊断到金融分析,AI的应用已经渗透到生活的方方面面。
|
||||
|
||||
特别是大语言模型的出现,如GPT系列和开源的LLaMA系列,让机器理解和生成人类语言
|
||||
的能力达到了新的高度。这些模型不仅能进行对话,还能写代码、创作文章、分析数据。
|
||||
|
||||
然而,AI的快速发展也带来了一些担忧。隐私问题、就业影响、算法偏见等议题需要
|
||||
社会各界共同面对和解决。未来,如何在享受AI带来便利的同时,确保其安全和公平
|
||||
发展,将是人类需要认真思考的问题。
|
||||
"""
|
||||
|
||||
try:
|
||||
result = summary_chain.invoke({
|
||||
"article": article,
|
||||
"format_instructions": summary_parser.get_format_instructions()
|
||||
})
|
||||
print(f"标题: {result['title']}")
|
||||
print(f"摘要: {result['summary']}")
|
||||
print(f"关键词: {', '.join(result['keywords'])}")
|
||||
print(f"情感: {result['sentiment']}")
|
||||
except Exception as e:
|
||||
print(f"分析失败: {e}")
|
||||
print()
|
||||
|
||||
# ==================== 练习 3: 代码生成器 ====================
|
||||
print("=" * 60)
|
||||
print("练习 3: 代码生成器(带注释)")
|
||||
print("=" * 60)
|
||||
|
||||
# 步骤1:生成代码
|
||||
code_gen_prompt = ChatPromptTemplate.from_template(
|
||||
"""用{language}语言实现以下功能,只输出代码,不要解释:
|
||||
功能:{requirement}"""
|
||||
)
|
||||
code_gen_chain = code_gen_prompt | llm | parser
|
||||
|
||||
# 步骤2:添加注释
|
||||
comment_prompt = ChatPromptTemplate.from_template(
|
||||
"""为以下代码添加详细的中文注释,包括函数说明和关键步骤解释:
|
||||
```
|
||||
{code}
|
||||
```
|
||||
输出完整的带注释的代码:"""
|
||||
)
|
||||
comment_chain = comment_prompt | llm | parser
|
||||
|
||||
# 组合:生成 -> 注释
|
||||
def generate_and_comment(inputs: dict) -> str:
|
||||
code = code_gen_chain.invoke(inputs)
|
||||
commented = comment_chain.invoke({"code": code})
|
||||
return commented
|
||||
|
||||
full_chain = RunnableLambda(generate_and_comment)
|
||||
|
||||
result = full_chain.invoke({
|
||||
"language": "Python",
|
||||
"requirement": "实现二分查找算法"
|
||||
})
|
||||
print(f"生成的代码:\n{result}")
|
||||
print()
|
||||
|
||||
# ==================== 练习 4: 并行处理 - 多角度分析 ====================
|
||||
print("=" * 60)
|
||||
print("练习 4: 并行处理 - 多角度分析")
|
||||
print("=" * 60)
|
||||
|
||||
# 同时从多个角度分析一个主题
|
||||
topic_analysis_chain = RunnableParallel(
|
||||
definition=ChatPromptTemplate.from_template("用一句话定义{topic}") | llm | parser,
|
||||
advantages=ChatPromptTemplate.from_template("列出{topic}的3个主要优点") | llm | parser,
|
||||
challenges=ChatPromptTemplate.from_template("列出{topic}面临的3个主要挑战") | llm | parser,
|
||||
future=ChatPromptTemplate.from_template("预测{topic}未来5年的发展趋势") | llm | parser,
|
||||
)
|
||||
|
||||
result = topic_analysis_chain.invoke({"topic": "远程办公"})
|
||||
|
||||
print("【定义】")
|
||||
print(result['definition'])
|
||||
print("\n【优点】")
|
||||
print(result['advantages'])
|
||||
print("\n【挑战】")
|
||||
print(result['challenges'])
|
||||
print("\n【未来趋势】")
|
||||
print(result['future'])
|
||||
print()
|
||||
|
||||
# ==================== 挑战练习 ====================
|
||||
"""
|
||||
挑战 1: 创建一个"面试模拟器"
|
||||
- 输入:岗位名称、难度级别
|
||||
- 输出:3个面试问题 + 参考答案
|
||||
- 使用结构化输出
|
||||
|
||||
挑战 2: 创建一个"周报生成器"
|
||||
- 输入:本周完成的任务列表(纯文本)
|
||||
- 输出:格式化的周报(包含摘要、详情、下周计划)
|
||||
- 使用多步骤链
|
||||
|
||||
挑战 3: 创建一个"代码 Review Bot"
|
||||
- 输入:代码片段
|
||||
- 并行输出:安全性分析、性能分析、可读性分析、改进建议
|
||||
- 使用 RunnableParallel
|
||||
"""
|
||||
|
||||
print("=" * 60)
|
||||
print("Day 1 学习完成!")
|
||||
print("=" * 60)
|
||||
print("""
|
||||
今日掌握的核心技能:
|
||||
✅ 使用 ChatOllama 调用本地模型
|
||||
✅ 使用 ChatPromptTemplate 创建可复用的提示词
|
||||
✅ 使用 OutputParser 将输出转为结构化数据
|
||||
✅ 使用 LCEL (|) 链接多个组件
|
||||
✅ 使用 RunnableParallel 并行处理
|
||||
✅ 使用 RunnableLambda 插入自定义逻辑
|
||||
|
||||
明日预告:Day 2 - LCEL 深入
|
||||
- RunnableSequence 详解
|
||||
- 条件分支 RunnableBranch
|
||||
- 错误处理和重试
|
||||
- 异步调用
|
||||
""")
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
Day 2 - 课程 1: LCEL 基础语法
|
||||
==========================
|
||||
|
||||
学习目标:
|
||||
- 理解 RunnableSequence 的概念
|
||||
- 对比 | 管道符与显式序列的写法
|
||||
- 学会查看链的输入输出 schema
|
||||
|
||||
核心概念:
|
||||
- RunnableSequence: LCEL 的基础组合方式
|
||||
- invoke/batch: 调用方式
|
||||
- schema: 输入输出规范
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableLambda, RunnableSequence
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
print("=" * 50)
|
||||
print("1. RunnableSequence vs 管道符 |")
|
||||
print("=" * 50)
|
||||
|
||||
prompt = ChatPromptTemplate.from_template("用一句话解释{concept}")
|
||||
|
||||
# 管道符写法
|
||||
pipe_chain = prompt | llm | parser
|
||||
|
||||
# 显式序列写法
|
||||
seq_chain = RunnableSequence(prompt, llm, parser)
|
||||
|
||||
result1 = pipe_chain.invoke({"concept": "向量数据库"})
|
||||
result2 = seq_chain.invoke({"concept": "向量数据库"})
|
||||
|
||||
print(f"管道符结果: {result1}")
|
||||
print(f"序列结果: {result2}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("2. 查看链的 Schema")
|
||||
print("=" * 50)
|
||||
|
||||
print(f"输入 Schema: {pipe_chain.input_schema.model_json_schema()}")
|
||||
print(f"输出 Schema: {pipe_chain.output_schema.model_json_schema()}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("3. 链中插入 RunnableLambda")
|
||||
print("=" * 50)
|
||||
|
||||
# 先处理输入,再生成回答
|
||||
normalize = RunnableLambda(lambda x: {"concept": x["concept"].strip()})
|
||||
|
||||
chain = normalize | prompt | llm | parser
|
||||
result = chain.invoke({"concept": " 提示词工程 "})
|
||||
print(f"结果: {result}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("4. batch 批量调用")
|
||||
print("=" * 50)
|
||||
|
||||
inputs = [
|
||||
{"concept": "检索增强"},
|
||||
{"concept": "多模态"},
|
||||
{"concept": "函数调用"}
|
||||
]
|
||||
|
||||
results = pipe_chain.batch(inputs)
|
||||
for inp, res in zip(inputs, results):
|
||||
print(f"{inp['concept']}: {res}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 把 RunnableSequence 改成三步:先加前缀,再调用 LLM,再解析
|
||||
练习 2: 使用 RunnableLambda 给输入加上 "请用 20 字以内" 的约束
|
||||
练习 3: 给 batch 输入增加 5 个概念,观察输出风格差异
|
||||
"""
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
Day 2 - 课程 2: RunnableParallel 并行执行
|
||||
=====================================
|
||||
|
||||
学习目标:
|
||||
- 理解 RunnableParallel 的输入输出结构
|
||||
- 学会同时生成多个结果
|
||||
- 掌握并行链的典型场景
|
||||
|
||||
核心概念:
|
||||
- RunnableParallel: 并行执行多个 Runnable
|
||||
- 输出为 dict
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableLambda, RunnableParallel
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
print("=" * 50)
|
||||
print("1. 基础并行:摘要 + 关键词")
|
||||
print("=" * 50)
|
||||
|
||||
summary_prompt = ChatPromptTemplate.from_template(
|
||||
"请用一句话总结以下内容:\n{text}"
|
||||
)
|
||||
keywords_prompt = ChatPromptTemplate.from_template(
|
||||
"从文本中提取 3 个关键词,用逗号分隔:\n{text}"
|
||||
)
|
||||
|
||||
parallel_chain = RunnableParallel(
|
||||
summary=summary_prompt | llm | parser,
|
||||
keywords=keywords_prompt | llm | parser,
|
||||
)
|
||||
|
||||
text = "LLM 应用常见流程包括提示词设计、向量检索、工具调用与输出解析。"
|
||||
result = parallel_chain.invoke({"text": text})
|
||||
print(f"摘要: {result['summary']}")
|
||||
print(f"关键词: {result['keywords']}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("2. 并行 + 原始输入")
|
||||
print("=" * 50)
|
||||
|
||||
keep_input = RunnableParallel(
|
||||
original=RunnableLambda(lambda x: x["text"]),
|
||||
rewrite=ChatPromptTemplate.from_template("改写成更口语的表达:{text}") | llm | parser,
|
||||
)
|
||||
|
||||
result = keep_input.invoke({"text": "LangChain 让 LLM 应用组件化并可组合。"})
|
||||
print(f"原文: {result['original']}")
|
||||
print(f"改写: {result['rewrite']}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("3. 并行处理多个视角")
|
||||
print("=" * 50)
|
||||
|
||||
views_chain = RunnableParallel(
|
||||
beginner=ChatPromptTemplate.from_template("用初学者能懂的话解释:{topic}") | llm | parser,
|
||||
expert=ChatPromptTemplate.from_template("用专家视角总结要点:{topic}") | llm | parser,
|
||||
analogy=ChatPromptTemplate.from_template("给出一个生活化比喻:{topic}") | llm | parser,
|
||||
)
|
||||
|
||||
result = views_chain.invoke({"topic": "向量检索"})
|
||||
print(f"初学者解释: {result['beginner']}")
|
||||
print(f"专家要点: {result['expert']}")
|
||||
print(f"生活化比喻: {result['analogy']}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 新增一个并行分支,生成一个标题
|
||||
练习 2: 把 RunnableParallel 改成 dict 写法
|
||||
练习 3: 在并行输出中加入原始输入的长度统计
|
||||
"""
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
Day 2 - 课程 3: RunnablePassthrough 数据透传
|
||||
======================================
|
||||
|
||||
学习目标:
|
||||
- 理解 RunnablePassthrough 的作用
|
||||
- 学会在链中保留原始输入
|
||||
- 掌握 assign 用法扩展输入
|
||||
|
||||
核心概念:
|
||||
- RunnablePassthrough: 原样透传输入
|
||||
- assign: 给输入添加新字段
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
print("=" * 50)
|
||||
print("1. 保留原始输入")
|
||||
print("=" * 50)
|
||||
|
||||
answer_chain = ChatPromptTemplate.from_template("回答问题:{question}") | llm | parser
|
||||
|
||||
chain = RunnablePassthrough.assign(
|
||||
answer=answer_chain
|
||||
)
|
||||
|
||||
result = chain.invoke({"question": "什么是向量嵌入?"})
|
||||
print(f"问题: {result['question']}")
|
||||
print(f"回答: {result['answer']}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("2. 透传 + 自定义字段")
|
||||
print("=" * 50)
|
||||
|
||||
with_meta = RunnablePassthrough.assign(
|
||||
length=RunnableLambda(lambda x: len(x["text"])),
|
||||
summary=ChatPromptTemplate.from_template("用一句话总结:{text}") | llm | parser,
|
||||
)
|
||||
|
||||
result = with_meta.invoke({"text": "LCEL 可以把多个组件像管道一样连接起来。"})
|
||||
print(f"文本: {result['text']}")
|
||||
print(f"长度: {result['length']}")
|
||||
print(f"总结: {result['summary']}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("3. 结合 RunnableLambda 预处理")
|
||||
print("=" * 50)
|
||||
|
||||
normalize = RunnableLambda(lambda x: {"question": x["question"].strip()})
|
||||
|
||||
chain = normalize | RunnablePassthrough.assign(answer=answer_chain)
|
||||
result = chain.invoke({"question": " LCEL 的优势是什么? "})
|
||||
print(f"问题: {result['question']}")
|
||||
print(f"回答: {result['answer']}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 用 assign 添加一个字段,记录问题是否包含“为什么”
|
||||
练习 2: 输出中加入当前时间戳(可以用 datetime)
|
||||
练习 3: 把回答链改成三步:改写问题 -> 回答 -> 解析
|
||||
"""
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
Day 2 - 课程 4: RunnableBranch 条件分支
|
||||
===================================
|
||||
|
||||
学习目标:
|
||||
- 理解 RunnableBranch 的分支选择逻辑
|
||||
- 学会根据输入走不同链路
|
||||
- 构建简单的“路由器”
|
||||
|
||||
核心概念:
|
||||
- RunnableBranch: 按条件选择 Runnable
|
||||
- 条件函数: 输入 -> bool
|
||||
"""
|
||||
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.runnables import RunnableBranch, RunnableLambda
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
|
||||
parser = StrOutputParser()
|
||||
|
||||
print("=" * 50)
|
||||
print("1. 基于问题类型的分支")
|
||||
print("=" * 50)
|
||||
|
||||
why_prompt = ChatPromptTemplate.from_template("请用逻辑清晰的方式解释原因:{question}")
|
||||
how_prompt = ChatPromptTemplate.from_template("请用步骤列表回答:{question}")
|
||||
what_prompt = ChatPromptTemplate.from_template("请用一句话定义:{question}")
|
||||
|
||||
branch = RunnableBranch(
|
||||
(lambda x: "为什么" in x["question"], why_prompt | llm | parser),
|
||||
(lambda x: "怎么" in x["question"] or "如何" in x["question"], how_prompt | llm | parser),
|
||||
what_prompt | llm | parser,
|
||||
)
|
||||
|
||||
questions = [
|
||||
{"question": "为什么向量检索比关键字检索更好?"},
|
||||
{"question": "如何搭建一个本地 LLM 服务?"},
|
||||
{"question": "什么是 LCEL?"},
|
||||
]
|
||||
|
||||
for q in questions:
|
||||
result = branch.invoke(q)
|
||||
print(f"Q: {q['question']}")
|
||||
print(f"A: {result}")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("2. 结合预处理做路由")
|
||||
print("=" * 50)
|
||||
|
||||
normalize = RunnableLambda(lambda x: {"question": x["question"].strip()})
|
||||
|
||||
chain = normalize | branch
|
||||
result = chain.invoke({"question": " 怎么进行提示词优化? "})
|
||||
print(f"结果: {result}")
|
||||
|
||||
# ==================== 练习 ====================
|
||||
"""
|
||||
练习 1: 新增一个分支处理“对比”类问题
|
||||
练习 2: 把条件函数改成正则匹配
|
||||
练习 3: 尝试用分支实现“短问题 -> 简答 / 长问题 -> 详答”
|
||||
"""
|
||||
Loading…
Reference in New Issue