系统讲解如何从零开始构建基于大模型(LLM)的 AI 应用
覆盖大模型基础、应用开发实践与模型能力拓展三大模块
一、大模型基础
1.1 LLM 机制
定义
**LLM(Large Language Model,大型语言模型)是一种基于深度学习的神经网络模型,通过在海量文本数据上进行预训练,学习语言的统计规律,能够理解并生成自然语言。
本质
**基于上下文信息推断下一段要输出的内容。
核心概念
Transformer 架构
-
自注意力机制(Self-Attention) :使模型能够关注文本中的不同位置,捕捉长距离依赖关系
-
编码器-解码器结构:
- 编码器:将输入文本转换为向量表示
- 解码器:基于编码器的输出,逐个生成输出 Token
预训练 + 微调范式
- 预训练阶段:在大规模无标注数据上训练,学习通用语言知识(这是最耗时和最昂贵的部分)
- 微调阶段:在特定任务的标注数据上进行微调,使模型适应特定应用场景
Token(分词)
定义:Token 是模型处理文本的最小单位。模型不直接处理文字,而是先将文本分割成 Token,再进行处理。
工作原理:
原文本: "Hello World, 你好世界"
↓
分词: ["Hello", " World", ",", " 你", "好", "世", "界"]
↓
ID 编码: [15496, 2160, 11, 368, 1492, 1014, 1470]
↓
模型处理:向量化、计算注意力机制
Token 计数规则:
- 英文:1 个单词 ≈ 1-2 个 Token(如 "hello" = 1 Token,"world," = 2 Token)
- 中文:1 个字 ≈ 1 个 Token(如 "你好世界" = 4 Token)
- 标点符号:1 个 ≈ 1 个 Token
为什么重要?
- 💰 成本计算:OpenAI API 按 Token 数计费,不是按字数。理解 Token 能精确预测成本
- 📏 长度限制:每个模型都有 Token 上限(如 GPT-4 为 128K Token),超过限制会报错
- 🎯 性能优化:减少不必要 Token 能加快推理速度、降低成本
示例:
# 文本长度估算
text = "What is machine learning?" # 5 个单词
tokens = 6 # 实际约 6 个 Token(比单词数稍多)
# 成本计算(GPT-4 为例)
input_tokens = 100
output_tokens = 50
cost = (input_tokens * 0.00003 + output_tokens * 0.00006) # 美元
Embedding(嵌入)
定义:Embedding 是将文本转换为高维向量的过程。每段文本都变成一个数值向量,向量中的每个维度捕捉文本的某种语义特性。在大型模型中,Embedding 是一种将高维数据(如文本、图像、视频等)转换为低维向量表示的技术。
核心思想:
将离散数据映射到连续的向量空间,使得相似的数据点在向量空间中的距离较近,而不相似的数据点则距离较远。
工作原理:
文本输入: "机器学习是人工智能的重要分支"
↓
Embedding 模型:[转换过程]
↓
输出向量: [0.123, -0.456, 0.789, ..., 0.012]
↑ ↑ ↑ ↑
语义特征 写作风格 领域标签 其他特征
核心性质:
- 相似性:语义相近的文本对应的向量距离接近
- 可计算:可以用向量距离(余弦相似度、欧几里得距离)计算两段文本的相似程度
- 高维:通常 1024-1536 维,捕捉丰富的语义信息
应用场景:
场景 1: 语义搜索
输入:用户问题的 Embedding
↓
[向量数据库中检索最相似的文档 Embedding]
↓
返回:最相关的文档(用于 RAG)
场景 2: 文本相似度计算
文本 A Embedding: [0.1, 0.2, 0.3, ...]
文本 B Embedding: [0.1, 0.2, 0.35, ...]
相似度 = cos(A, B) = 0.98 → 非常相似
场景 3: 聚类和分类
将数千个文本 Embedding 作为特征
↓
使用聚类算法(K-means)
↓
自动分组相似文本
为什么重要?
- 🔍 语义搜索:构建 RAG 系统的核心技术,支持高效检索相关知识
- 📚 向量数据库:Embedding 使文本可存储在专门优化的向量数据库中
- 🧠 机器学习:Embedding 向量可作为下游任务的特征输入
- 💡 理解语义:捕捉文本深层含义,而非关键词匹配
示例:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 获取 Embedding
text = "机器学习很有趣"
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embedding = response.data[0].embedding
# 输出:[0.023, -0.045, 0.156, ..., 0.089] # 1536 维向量
# 计算相似度
text_a = "Python 是最好的编程语言"
text_b = "我喜欢用 Python 编程"
embed_a = get_embedding(text_a)
embed_b = get_embedding(text_b)
similarity = cosine_similarity(embed_a, embed_b) # 0.92,高度相似
Temperature(温度)
定义:Temperature(温度)是控制模型输出随机性的参数,范围通常为 0-2(或 0-1)。
工作原理:
原始输出概率: [0.7, 0.2, 0.1] → 下一个词有 70% 概率是 A
有 20% 概率是 B
有 10% 概率是 C
温度应用公式: 新概率 = exp(log_prob / temperature)
结果取决于温度值...
不同温度值的效果:
| 温度值 | 行为 | 输出特点 | 适用场景 |
|---|---|---|---|
| 0 | 完全确定性 | 每次相同,选概率最高的词 | 事实类任务(Q&A、翻译) |
| 0.3-0.7 | 低随机性 | 稳定但有变化,保持逻辑 | 专业写作、代码生成 |
| 0.7-1.0 | 平衡 | 创意和准确的平衡 | 推荐值,通用任务 |
| 1.0-1.5 | 高随机性 | 多样化、创意强 | 创意写作、头脑风暴 |
| 2.0 | 最大随机性 | 可能不连贯、混乱 | 测试/实验 |
可视化示例:
温度 = 0.1(确定性高)
生成结果:
"机器学习是人工智能的一个分支。"
"机器学习是人工智能的一个分支。" ← 完全相同
"机器学习是人工智能的一个分支。"
温度 = 0.7(平衡)
生成结果:
"机器学习是人工智能的一个分支。"
"机器学习是 AI 的重要组成部分。" ← 表达方式不同,意思一致
"深度学习属于机器学习领域。" ← 不同角度
温度 = 1.5(创意高)
生成结果:
"机器学习: 数字时代的魔法!"
"众所周知,机器学习颠覆了世界..." ← 可能过于夸张
"有趣的是,ML 和 AI 关系很复杂..."
为什么重要?
- 🎯 任务匹配:不同任务需要不同的温度值才能得到最佳结果
- 💡 创意控制:用户可以根据需求调整模型的创意程度
- 🔄 可重复性:低温度确保结果可重复,便于测试
- 📝 多样化:高温度生成多个不同版本,便于选择
代码示例:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 场景 1: 事实类任务(翻译、代码)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "翻译: Hello World"}],
temperature=0 # 确定性高,每次相同
)
# 场景 2: 创意写作
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "写一个短故事"}],
temperature=1.2 # 创意强,多样化
)
# 场景 3: 通用任务(推荐)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "你好,请介绍 Python"}],
temperature=0.7 # 平衡值
)
最佳实践:
- 📊 Q&A 和事实类:
temperature = 0或0.3(确定性) - 💻 代码生成:
temperature = 0.2-0.5(准确性优先) - 📝 内容生成:
temperature = 0.7-0.9(创意和准确平衡) - 🎨 头脑风暴:
temperature = 1.0-1.5(最大创意) - ⚠️ 避免:
temperature > 1.5(可能产生无意义内容)
有什么用?
- 🎯 文本理解与生成:能够理解复杂指令,生成连贯、自然的文本
- 🧠 知识存储:将大规模知识编码到模型参数中
- 🔄 任务迁移:预训练的模型可以快速适应各种新任务
- 💡 创意应用:支撑聊天机器人、内容生成、代码编写等众多应用
1.2 提示词(Prompt)
定义
Prompt(提示词) 是用户提供给 LLM 的输入文本或指令,用于引导模型生成特定的输出。好的 Prompt 直接影响模型的输出质量。
基本结构
[指令] + [上下文] + [输入] + [输出格式]
示例:
指令(定义角色):你是一个专业的翻译。
上下文(背景信息):我有一段技术文档需要翻译。
输入(任务目标):The model uses attention mechanism to capture dependencies.
输出格式(输出要求):请用中文进行翻译,保留专业术语的原文。
结果:该模型使用注意力机制(attention mechanism)来捕捉依赖关系。
常见技巧
| 技巧 | 说明 | 例子 |
|---|---|---|
| 角色设定 | 让模型扮演特定身份 | "你是一个资深产品经理..." |
| 思维链(CoT) | 让模型逐步推理 | "请一步步分析这个问题..." |
| Few-shot 示例 | 提供几个示例学习任务 | 给出2-3个问答对,然后提问 |
| 指令分解 | 将复杂任务分解为步骤 | "首先...其次...最后..." |
有什么用?
- ⚡ 快速指挥模型:无需代码修改,通过文本直接控制模型行为
- 📈 提升输出质量:设计良好的 Prompt 可以显著改善模型输出
- 💰 降低成本:好的 Prompt 减少重试次数,节省 API 调用成本
- 🎨 灵活定制:适应各种垂直领域需求(医疗、法律、教育等)
1.3 提示工程(Prompt Engineering)
定义
Prompt Engineering(提示工程) 是一门系统化学科,研究如何科学地设计、优化和评估 Prompt,以获得 LLM 最佳性能的方法论和实践。提示工程是为了引导大模型给出更好的答案。
核心方法论
1. 迭代测试
- 第一轮:设计初始 Prompt,测试基本功能
- 第二轮:基于输出质量分析问题,修改 Prompt
- 第三轮:持续优化直到满足需求
- 类似于软件开发的敏捷迭代
2. 模板化
- 建立可复用的 Prompt 模板库
- 针对不同任务维护标准化模板
- 降低后续开发成本
3. 评估指标
- 相关性:输出是否回答了问题
- 准确性:信息是否正确
- 完整性:是否包含所需所有信息
- 格式合规:输出是否符合指定格式
工具和技术
| 工具 | 用途 |
|---|---|
| Prompt Template | 参数化 Prompt 模板,便于动态注入变量 |
| Prompt Tuning | 通过梯度下降优化 Prompt 的嵌入表示 |
| Prompt Versioning | 版本管理,跟踪 Prompt 演变历史 |
| A/B Testing | 对比不同 Prompt 的效果 |
提示技术
1. 零样本提示(Zero-shot Prompting)
定义:直接给出任务指令,不提供任何示例,模型根据指令和自身知识直接完成任务。
工作原理:
输入:直接的任务指令(无示例)
↓
LLM 理解任务
↓
基于通用知识和预训练生成答案
适用场景:
- ✅ 通用任务(翻译、总结、分类)
- ✅ 模型已有充分知识的领域
- ✅ 快速验证模型能力
示例:
用户:请将这句话翻译成英文:"机器学习很有趣"
模型:Machine learning is very interesting.
优点:
- ⚡ 快速、简洁
- 📝 无需准备示例
- 💰 最少的 Token 消耗
缺点:
- ❌ 对复杂任务效果较差
- ❌ 模型容易误解意图
- ❌ 输出质量不稳定
2. 少样本提示(Few-shot Prompting)
定义:在 Prompt 中提供 2-5 个任务示例,模型通过这些示例学习任务模式,然后完成新的任务。
工作原理:
输入 Prompt:
[示例 1] 输入 + 预期输出
[示例 2] 输入 + 预期输出
[示例 3] 输入 + 预期输出
[新任务] 输入 → ?
↓
模型识别模式
↓
基于模式完成新任务
示例:
任务:情感分类
示例 1:
输入:这个产品太棒了!✨
输出:正面
示例 2:
输入:非常失望,质量很差
输出:负面
示例 3:
输入:还可以,一般般
输出:中性
新任务:
输入:我非常喜欢这个设计
输出:正面 ← 模型推断
核心原则:
- 📊 数量:通常 2-5 个示例最优(太多反而降低效果)
- 🎯 质量:选择代表性和多样性的示例
- 📍 顺序:示例顺序会影响模型表现(最好把最相似的示例放最后)
优点:
- 📈 显著提升模型性能(10-20% 提升)
- 🎓 模型快速学习新模式
- 🔄 比微调快得多
缺点:
- 📝 需要准备高质量示例
- 💰 Token 消耗增加
- ⏱️ 不适合极长的示例
代码示例:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
prompt = """
任务:将英文句子分类为积极或消极
示例:
英文:I love this product!
类型:积极
英文:This is terrible.
类型:消极
英文:The weather is good today.
类型:积极
现在请分类:
英文:I am disappointed with this service.
类型:"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
print(response.choices[0].message.content) # 输出:消极
3. 思维链提示(Chain-of-Thought, CoT)
定义:要求模型在给出最终答案前,先逐步推理和解释思考过程,可以显著提高模型在复杂推理任务上的准确率。
工作原理:
问题:如果一个盒子里有 5 个红球和 3 个蓝球,随机取出一个球是红球的概率是多少?
❌ 直接提问(容易出错):
模型:50%
✅ 思维链提问(改进):
模型:
第一步:计算总球数 = 5 + 3 = 8
第二步:红球数 = 5
第三步:概率 = 红球数 / 总球数 = 5 / 8 = 0.625 = 62.5%
触发方式:
方式 1:简单指令
指令:请一步步分析这个问题...
方式 2:少样本 + 思维链
示例:
问题:9*11 是多少?
答案:
第一步:9 × 11 = 9 × (10 + 1)
第二步:= 9 × 10 + 9 × 1
第三步:= 90 + 9 = 99
现在你来:
问题:8 × 12 是多少?
效果对比:
| 任务 | 直接提问 | 使用 CoT | 提升 |
|---|---|---|---|
| 算术推理 | 60% | 87% | +27% |
| 逻辑推理 | 45% | 82% | +37% |
| 常识推理 | 58% | 79% | +21% |
适用场景:
- 🧮 数学计算和逻辑推理
- 🤔 复杂问题分析
- 📊 多步骤决策
- 🔍 需要解释的任务
代码示例:
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://api.moonshot.cn/v1",
)
question = "一个农民有 15 只羊,丢失了 3 只,后来买了 7 只。现在有多少只羊?"
# ❌ 不使用思维链:直接提问,容易出错
prompt_without_cot = f"问题:{question}\n答案:"
# ✅ 使用思维链:引导模型逐步推理
prompt_with_cot = f"""问题:{question}
请一步步计算:
第一步:计算丢失后的数量
第二步:计算买入后的数量
第三步:得出最终答案
答案:"""
def ask(prompt: str) -> str:
response = client.chat.completions.create(
model="moonshot-v1-8k",
messages=[{"role": "user", "content": prompt}],
temperature=0, # 推理任务设为 0,保证结果稳定
)
return response.choices[0].message.content
print("=== 不使用 CoT ===")
print(ask(prompt_without_cot))
# 可能输出:19(正确)或其他错误答案
print("\n=== 使用 CoT ===")
print(ask(prompt_with_cot))
# 输出:
# 第一步:15 - 3 = 12 只
# 第二步:12 + 7 = 19 只
# 第三步:现在有 19 只羊
技巧:推理任务建议将
temperature设为0,让模型以最确定的方式推理,避免引入随机性导致计算出错。
4. ReAct 框架(Reasoning + Acting)
定义:ReAct 是一种结合"推理"和"行动"的框架,模型不仅思考和推理,还能主动调用工具、执行动作、观察结果,形成闭环反馈,解决复杂现实问题。
工作流程:
问题:2024 年中国 GDP 是多少?
思考 (Think) → 这需要最新数据,我应该搜索
↓
行动 (Act) → 调用 [搜索工具]
↓
观察 (Observe) → 得到搜索结果:"2024 年中国 GDP 增长 5.3%..."
↓
思考 → 数据有点模糊,让我再搜索具体数值
↓
行动 → 再次搜索
↓
观察 → 得到结果:"2024 年中国 GDP 约 17.9 万亿元"
↓
思考 → 现在信息足够了
↓
行动 → 生成最终答案
核心要素:
Thought → Action → Observation → Thought → Action → Observation → ... → Final Answer
(思考) (行动) (观察) (思考) (行动) (观察) (最终答案)
提示模板:
问题:{question}
你可以使用以下工具:
- 搜索工具:用于实时搜索最新信息
- 计算器:进行数学计算
- 数据库:查询历史数据
格式要求:
Thought:你的思考
Action:使用的工具名称
Action Input:工具的输入
Observation:工具返回的结果
... (如需要可重复)
Thought:基于 Observation 的最终想法
Final Answer:最终答案
开始吧!
Thought:"""
实际示例:
问题:巴黎现在的天气如何?
思考:我需要获取实时天气信息,应该使用天气 API
行动:调用_weather_api(city="Paris")
观察:{"temperature": 12°C, "condition": "多云", "humidity": 65%}
思考:信息足够了
行动:生成答案
最终答案:巴黎现在气温 12°C,天气多云,湿度 65%
ReAct vs 思维链的区别:
| 特性 | CoT | ReAct |
|---|---|---|
| 逻辑推理 | ✅ 强 | ✅ 强 |
| 工具调用 | ❌ 不支持 | ✅ 支持 |
| 实时信息 | ❌ 不支持 | ✅ 支持 |
| 复杂任务 | ⚠️ 有限 | ✅ 强大 |
| 可观测性 | ✅ 完整 | ✅ 完整 |
| 实现复杂度 | 低 | 高 |
应用场景:
- 🔍 需要实时信息的问答(股票价格、天气预报)
- 🔧 需要工具调用的任务(数据分析、文件处理)
- 🤖 智能体应用(自动执行多步工作流)
- 📊 复杂决策支持系统
代码示例(伪代码) :
from langchain.agents import create_react_agent
from langchain.tools import Tool
from langchain.llms import OpenAI
# 定义工具
search_tool = Tool(
name="search",
func=google_search,
description="用于搜索最新信息"
)
calculator_tool = Tool(
name="calculator",
func=calculate,
description="用于计算数学表达式"
)
# 创建 ReAct Agent
agent = create_react_agent(
llm=OpenAI(),
tools=[search_tool, calculator_tool],
prompt=react_prompt_template
)
# 运行
result = agent.run("2024 年中国 GDP 增长多少?")
四大提示技术对比总结
| 技术 | 复杂度 | 效果 | Token 消耗 | 适用任务 |
|---|---|---|---|---|
| 零样本 | 低 | 一般 | 少 | 简单通用任务 |
| 少样本 | 低 | 良好 | 中 | 需要学习模式 |
| 思维链 | 中 | 优 | 中 | 推理/数学题 |
| ReAct | 高 | 最优 | 多 | 复杂工作流/实时任务 |
使用建议:
- 🚀 快速开始:先用零样本
- 📈 提升效果:加入少样本示例
- 🧠 复杂推理:使用思维链
- 🤖 自动化工作:采用 ReAct 框架
有什么用?
- 🎯 系统化优化:从试错到科学方法的升级
- 📊 性能量化:通过指标定量评估 Prompt 质量
- 🔁 复用与扩展:积累可复用的优质 Prompt
- 🚀 提升效率:标准化流程加快开发速度
二、应用开发
2.1 OpenAI API
定义
OpenAI API 是 OpenAI 公司提供的云服务接口,开发者可以通过 API 调用 GPT-3.5、GPT-4 等先进语言模型,而无需自己部署和维护模型。
核心功能
模型选择
- GPT-3.5:引入RLHF(基于人类反馈的强化学习),显著提升对话理解与指令遵循能力,是ChatGPT的基础。
- GPT-4:多模态输入(图像+文本),逻辑推理、复杂任务处理能力大幅提升。
- GPT-4.5:增强对话自然度与“情商”,提升情感理解与回应质量。
- GPT-5:被OpenAI称为“博士级专家”,具备深度推理、自主工具使用、多模态处理等能力。
- Embedding 模型:专门用于生成文本向量表示
API 调用方式(Python)
代码示例:
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://api.moonshot.cn/v1", # 兼容 OpenAI 格式,换 base_url 即可切换模型商
)
response = client.chat.completions.create(
model="moonshot-v1-8k",
messages=[
{"role": "user", "content": "你好,请介绍一下 Python"}
],
temperature=0.7, # 创意程度(0-1)
max_tokens=500 # 最大输出长度
)
print(response.choices[0].message.content)
工程问题
| 问题 | 解决方案 |
|---|---|
| Token 限制 | 监控输入/输出 Token 数,设置 max_tokens |
| 速率控制 | 使用 Retry 机制,指数退避重试 |
| 成本管理 | 使用廉价模型(GPT-3.5),启用缓存 |
| 延迟问题 | 采用流式输出(streaming),实时显示结果 |
| Error 处理 | 捕获 API 异常,实现容错机制 |
流式应答(Streaming)
定义:流式应答是指模型生成内容时,不等全部生成完毕再返回,而是边生成边传输,客户端逐块(chunk)接收并实时显示。
工作原理:
普通模式(Non-streaming):
模型生成 → 完整结果缓冲 → 一次性返回 → 用户看到结果
等待时间:全部生成完才看到(可能几秒到十几秒)
流式模式(Streaming):
模型生成第1个词 → 立即推送 → 用户看到第1个词
模型生成第2个词 → 立即推送 → 用户看到第2个词
...
用户感受:像打字机一样逐字出现(首字延迟极低)
代码示例:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 开启流式:只需加 stream=True
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "介绍一下深度学习"}],
stream=True, # 关键参数
)
# 逐块接收并打印(实时显示效果)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True)
print() # 最后换行
普通模式 vs 流式模式对比:
| 对比项 | 普通模式 | 流式模式 |
|---|---|---|
| 首字延迟 | 高(等全部生成) | 极低(立刻开始) |
| 用户体验 | 等待后突然出现 | 逐字打印,流畅自然 |
| 适用场景 | 后台批量处理 | 对话、实时交互界面 |
| 代码复杂度 | 低 | 略高(需迭代 chunk) |
| Token 计费 | 相同 | 相同(不影响费用) |
注意事项:
- ⚠️ 流式模式下无法直接获取
usage(Token 统计),需要自行累加 chunk - ✅ 对话类应用强烈推荐开启流式,用户体验提升明显
- ✅ 后端批处理或结果需要完整再处理时,使用普通模式即可
实际应用场景:
- 💬 聊天界面:实现 ChatGPT 同款打字机效果
- 📝 长文生成:文章、报告生成时避免用户长时间等待
- 🔄 实时翻译:边输入边翻译,即时反馈
底层协议:SSE(Server-Sent Events)
定义:SSE 是 HTTP 协议的一种扩展,允许服务器主动向客户端持续推送文本数据,是流式应答在 Web 层的具体实现方式。OpenAI API 的流式输出底层正是基于 SSE。
SSE vs 其他实时通信方式:
| 方式 | 方向 | 协议 | 适用场景 |
|---|---|---|---|
| SSE | 服务器 → 客户端(单向) | HTTP | AI 流式输出、消息推送 |
| WebSocket | 双向 | WS | 聊天室、实时协作、游戏 |
| 轮询(Polling) | 客户端主动问 | HTTP | 简单状态查询(低效) |
为什么 AI 流式用 SSE 而不是 WebSocket?
因为 AI 对话是典型的"服务器主动推送"场景,单向就够了,SSE 更简单、无需握手升级协议。
SSE 数据格式(原始报文长这样):
HTTP/1.1 200 OK
Content-Type: text/event-stream ← 关键 Header
data: {"choices":[{"delta":{"content":"你"}}]}
data: {"choices":[{"delta":{"content":"好"}}]}
data: {"choices":[{"delta":{"content":"!"}}]}
data: [DONE] ← 结束标志
前端如何接收 SSE(浏览器原生 API):
// 浏览器端:使用 EventSource 接收 SSE
const source = new EventSource("/api/chat");
source.onmessage = (event) => {
if (event.data === "[DONE]") {
source.close(); // 关闭连接
return;
}
const chunk = JSON.parse(event.data);
const text = chunk.choices[0].delta.content || "";
document.getElementById("output").innerText += text; // 实时追加显示
};
Python 后端如何返回 SSE(FastAPI 示例):
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import OpenAI
import json
app = FastAPI()
client = OpenAI(api_key="your-api-key")
@app.post("/api/chat")
async def chat(question: str):
def generate():
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": question}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
# SSE 格式:data: {...}\n\n
yield f"data: {json.dumps({'content': content})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
关键点总结:
- 📡 SSE = HTTP 长连接 +
text/event-stream格式,浏览器原生支持 - 🔁 每条消息格式固定:
data: <内容>\n\n,[DONE]表示结束 - ✅ OpenAI SDK 已封装好 SSE 解析,直接用
stream=True即可,无需手动处理 - 🌐 前端用
EventSource或fetch+ReadableStream接收
有什么用?
- 🌐 云端调用:无需本地 GPU,随处可用
- 💼 企业级支持:稳定的可用性和技术支持
- 🔄 模型更新:自动获得最新模型改进
- 📱 快速原型化:迅速开发 AI 应用原型
2.2 LangChain
定义
LangChain 是一个开源 Python 框架,简化了基于 LLM 的复杂应用开发。它提供统一接口访问多个 LLM,同时支持构建高级应用所需的各种组件。LangChain可以理解成一个简化大模型接入的框架。
核心组件
| 组件 | 功能 | 例子 |
|---|---|---|
| LLM | LLM 模型包装器 | OpenAI、Anthropic、HuggingFace 等 |
| PromptTemplate | Prompt 模板 | 参数化 Prompt,支持动态注入 |
| Memory | 对话历史管理 | 记忆历史消息,支持摘要和缓冲 |
| Chain | 任务流程编排 | 连接多个组件实现复杂工作流 |
| Tools | 外部工具集成 | 搜索、计算器、数据库查询等 |
| Agent | 智能体 | 自主决策调用工具完成任务 |
基础抽象详解
1. ChatModel(聊天模型)
定义:LangChain 对各家 LLM 提供统一的调用接口,切换模型只需改一行代码。
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
temperature=0.7,
)
response = llm.invoke("用一句话介绍机器学习")
print(response.content)
消息类型(多轮对话的基础):
| 类型 | 说明 | 对应角色 |
|---|---|---|
SystemMessage | 系统指令,设定模型行为 | system |
HumanMessage | 用户输入 | user |
AIMessage | 模型回复 | assistant |
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(content="你是一个专业的 Python 老师"),
HumanMessage(content="什么是列表推导式?"),
]
response = llm.invoke(messages)
2. PromptTemplate(提示词模板)
定义:将 Prompt 参数化,通过占位符动态注入变量,方便复用和管理。
from langchain_core.prompts import ChatPromptTemplate
# 定义模板,{topic} 是占位符
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的{role}"),
("human", "请用{style}的方式解释:{topic}"),
])
# 填入变量,生成实际 Prompt
filled = prompt.invoke({
"role": "Python 老师",
"style": "简单易懂",
"topic": "装饰器",
})
print(filled.to_string())
# 输出:[SystemMessage(...), HumanMessage("请用简单易懂的方式解释:装饰器")]
为什么用模板而不是直接拼字符串?
- ✅ 统一管理,易于修改和版本控制
- ✅ 支持多语言、多场景复用
- ✅ 避免手动拼接出错
3. OutputParser(输出解析器)
定义:将 LLM 输出的原始字符串解析成结构化数据(如 JSON、列表、Python 对象),方便后续程序处理。
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
parser = JsonOutputParser()
prompt = ChatPromptTemplate.from_template(
"提取以下文本中的人名和年龄,以 JSON 格式返回。\n文本:{text}\n{format_instructions}",
).partial(format_instructions=parser.get_format_instructions())
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
)
chain = prompt | llm | parser
result = chain.invoke({"text": "小明今年 25 岁,小红今年 23 岁"})
print(result)
# [{"name": "小明", "age": 25}, {"name": "小红", "age": 23}]
常用解析器:
| 解析器 | 输出类型 | 适用场景 |
|---|---|---|
StrOutputParser | 纯字符串 | 通用文本输出 |
JsonOutputParser | dict/list | 结构化数据提取 |
PydanticOutputParser | Pydantic 对象 | 强类型结构化输出 |
CommaSeparatedListOutputParser | 列表 | 关键词、选项提取 |
三大基础抽象关系总览
用户输入
↓
PromptTemplate ← 动态填充变量,生成结构化 Prompt
↓
ChatModel ← 调用模型,返回 AIMessage
↓
OutputParser ← 解析为字符串 / JSON / 对象
↓
最终输出
三者用
|串联即是一条完整的 LangChain Chain:
chain = prompt | llm | parser
LangChain 生态
多个 LLM 提供商 多个工具
↓ ↓
┌─────────────────────────────────┐
│ LangChain 框架 │
├─────────────────────────────────┤
│ • 统一接口 │
│ • 流程编排 │
│ • 记忆管理 │
│ • Tool 调用 │
└─────────────────────────────────┘
↓
应用场景(聊天机器人、RAG、Agent)
支持的 LLM 和工具:
- LLM:OpenAI、Google、Anthropic、HuggingFace
- 工具:搜索、数据库、API、计算器
- 向量数据库:Pinecone、ChromaDB、Weaviate
有什么用?
- 🏗️ 降低开发复杂度:无需从零开始构建基础设施
- 🔗 快速集成:轻松组合多个 LLM 和工具
- 📚 可扩展架构:支持扩展自定义组件
- ⚡ 生产就绪:提供日志、监控、错误处理机制
2.3 应用类型
2.3.1 聊天机器人(Chatbot)
定义: 基于 LLM 的交互式应用,支持多轮对话和上下文记忆。
核心特点:
- 理解用户意图
- 维护对话历史
- 生成自然响应
- 支持多轮交互
示例:
用户:推荐一个好用的 Python 库
Bot:根据您的需求,我推荐 Pandas...
用户:它的性能如何?
Bot:Pandas 在处理小到中等规模数据时性能优异...(理解上文内容)
有什么用:
- 💬 客服自动化
- 🤖 虚拟助手
- 📚 教育辅导
- 🎯 个性化推荐
实战代码(流式输出版):
from pydantic import SecretStr
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
# 初始化模型(这里以 Moonshot 为例,换成 OpenAI 只需改 model/api_key/base_url)
chat_model = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
temperature=0.7
)
# 主循环:持续接收用户输入,流式输出回复
while True:
user_input = input("You:> ")
if user_input.lower() == "exit": # 输入 exit 退出
break
stream = chat_model.stream([HumanMessage(content=user_input)])
for chunk in stream:
print(chunk.content, end="", flush=True) # 逐 chunk 打印,实现打字机效果
print() # 回复结束后换行
说明:
chat_model.stream()开启流式应答,结合 1.3 节的 SSE 原理,每个chunk就是服务器推送的一条数据base_url可替换为任意兼容 OpenAI 接口格式的服务(如 OpenAI、DeepSeek、智谱等)- 当前为单轮对话(每次只传当前消息),多轮版本需维护
messages历史列表
2.3.2 RAG(检索增强生成)
场景引入
如果你打算为自己的业务开发一个聊天机器人,让它了解你的业务知识,通常有两种可行方案:
方案 原理 成本 适用场景 模型微调 用业务数据对已有模型继续训练 中(需要标注数据 + GPU) 业务知识相对固定、追求更自然的表达 RAG 检索时把业务信息塞进上下文,让模型据此作答 低(只需整理文档) 业务知识频繁更新、快速落地 训练一个属于自己的大模型成本高昂,不在考虑之列。RAG 是大多数业务场景的首选,下面详细介绍。
定义: RAG 结合检索(Retrieval)和生成(Generation),从外部知识库检索相关文档,再用 LLM 基于检索结果生成答案。所谓检索增强生成,就是我们在本地检索到相关的内容,把它增强到提示词里,然后再去做内容生成。
工作流程:
用户问题
↓
[检索器] 从知识库查询相关文档
↓
[LLM] 基于检索结果生成答案
↓
最终答案(准确、可验证)
向量数据库(Vector Database)
RAG 的检索环节依赖向量数据库来实现语义搜索。理解它之前,先理解什么是向量(Embedding)。
什么是 Embedding(向量化)?
定义:Embedding 是把文本(或图片、音频等)转化为一组浮点数(向量)的过程。语义相近的文本,向量在高维空间中的位置也相近。
"我喜欢吃苹果" → [0.12, -0.34, 0.87, ..., 0.05] (1536 维)
"我爱吃水果" → [0.11, -0.32, 0.85, ..., 0.04] ← 非常接近
"今天天气很好" → [-0.76, 0.51, -0.23, ..., 0.91] ← 差距很大
相似度通过余弦相似度计算(向量夹角越小 → 越相似),取值 -1 ~ 1,1 表示完全相同。
from langchain_openai import OpenAIEmbeddings
import numpy as np
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 将文本转为向量
vec1 = embeddings.embed_query("我喜欢吃苹果")
vec2 = embeddings.embed_query("我爱吃水果")
vec3 = embeddings.embed_query("今天天气很好")
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(cosine_similarity(vec1, vec2)) # ≈ 0.93 ← 高度相似
print(cosine_similarity(vec1, vec3)) # ≈ 0.21 ← 几乎无关
向量数据库的作用
传统数据库(MySQL / PostgreSQL)做的是精确匹配,例如 WHERE title = '机器学习'。
向量数据库做的是语义相似度搜索,即"找出和这句话最相近的 N 条文档"。
用户问:"深度学习是什么?"
↓ 向量化
查询向量:[0.34, -0.12, ...]
↓ ANN(近似最近邻)搜索
从数百万文档向量中,找出最相近的 Top-K 条
↓
返回:["神经网络基础.txt 第3段", "ML入门指南.pdf 第12页", ...]
常见向量数据库对比
| 数据库 | 部署方式 | 特点 | 适用场景 |
|---|---|---|---|
| Chroma | 本地 / 容器 | 轻量、易上手,LangChain 原生支持 | 开发调试、小型项目 |
| FAISS | 本地库 | Meta 开源,速度极快,纯内存 | 离线批量检索 |
| Pinecone | 云服务 | 全托管,开箱即用 | 快速上线,无运维 |
| Weaviate | 本地 / 云 | 支持多模态,功能完整 | 企业级知识库 |
| Milvus | 本地 / 云 | 国产,高并发,支持十亿级向量 | 超大规模生产环境 |
索引(Index)
定义:索引是 RAG 系统的"知识准备"阶段,将原始文档加工成可被高效检索的向量结构,并持久化存储。没有高质量的索引,检索结果再好的向量数据库也找不到正确内容。
索引的构建是一次性(或增量更新)的离线过程,由四个步骤串联完成:
原始文档
↓ ① 文档加载(Load)
↓ ② 文本分块(Split)
↓ ③ 向量化(Embed)
↓ ④ 存储(Store)
↓
向量索引(可供检索)
① 文档加载(Document Loader)
将各种格式的原始文件读取为统一的 Document 对象。
| 加载器 | 支持格式 |
|---|---|
PyPDFLoader | PDF 文件 |
TextLoader | .txt 纯文本 |
UnstructuredWordDocumentLoader | Word (.docx) |
WebBaseLoader | 网页 URL |
CSVLoader | CSV 表格 |
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
# 加载 PDF
loader = PyPDFLoader("business_manual.pdf")
docs = loader.load() # 返回 List[Document],每页一个 Document
# 加载网页
loader = WebBaseLoader("https://example.com/faq")
docs = loader.load()
② 文本分块(Text Splitter)
LLM 有上下文窗口限制,且检索精度随文本长度增加而下降,因此需要把长文档切成合适大小的块。
原始文档(10000 字)
↓
切成若干块(每块 500 字,相邻块重叠 50 字)
↓
[块1: 0-500字] [块2: 450-950字] [块3: 900-1400字] ...
↑ 重叠区域保留上下文,避免信息在边界被截断
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块最大字符数
chunk_overlap=50, # 相邻块重叠字符数
separators=["\n\n", "\n", "。", "!", "?", " "], # 优先按段落/句子切
)
chunks = splitter.split_documents(docs)
print(f"原始文档 {len(docs)} 页 → 分块后 {len(chunks)} 个块")
分块策略选择:
| 策略 | 适用场景 | 说明 |
|---|---|---|
| 固定大小分块 | 通用场景 | 简单稳定,RecursiveCharacterTextSplitter |
| 按句子分块 | 问答类文档 | 保持语义完整性 |
| 按段落/标题分块 | 结构化文档(Markdown、技术文档) | 语义边界最清晰 |
| 语义分块 | 高精度要求 | 用 Embedding 计算相似度决定切割点,效果最好但最慢 |
③ 向量化(Embedding)
调用 Embedding 模型将每个文本块转为向量(详见上节「什么是 Embedding」)。
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 批量向量化(SDK 内部自动分批,避免超限)
vectors = embeddings.embed_documents([chunk.page_content for chunk in chunks])
④ 存储(Store)
将"文本块 + 对应向量"一起写入向量数据库,生成持久化索引。
from langchain_community.vectorstores import Chroma
# 一步完成:向量化 + 存储,并持久化到本地目录
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db", # 持久化路径,下次直接加载,无需重新构建
)
# 下次直接从磁盘加载,无需重新索引
# vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
索引质量影响检索效果:
| 索引问题 | 现象 | 解决方案 |
|---|---|---|
| 分块太大 | 检索噪音多,LLM 无法聚焦 | 减小 chunk_size |
| 分块太小 | 上下文不足,答案不完整 | 增大 chunk_size 或增大重叠 |
| 重叠不够 | 关键信息被截断在边界 | 增大 chunk_overlap |
| 文档质量差 | 扫描件、乱码、格式混乱 | 预处理清洗,使用 OCR |
RAG + 向量数据库完整流程
阶段一:知识入库(离线)
业务文档(PDF / Word / 网页)
↓ 文档加载(DocumentLoader)
↓ 文本分块(TextSplitter) ← 按固定长度 + 重叠切片
↓ Embedding 模型向量化
↓ 存入向量数据库
阶段二:检索问答(在线)
用户提问
↓ Embedding 向量化
↓ 向量数据库语义搜索 → Top-K 相关文档
↓ 拼入 Prompt("根据以下资料回答:{文档}\n问题:{用户提问}")
↓ LLM 生成答案
↓ 返回给用户(可附带来源引用)
代码示例(使用 HuggingFace 本地 Embedding + Chroma + LangChain):
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from pydantic import SecretStr
from huggingface_hub import snapshot_download
import os
# 设置国内镜像,加速模型下载
os.environ['HF_ENDPOINT'] = 'https://mirrors.tuna.tsinghua.edu.cn/hugging-face'
# 1. 下载 Embedding 模型到本地(首次运行需联网,之后直接读本地缓存)
# all-MiniLM-L6-v2:轻量高效的句子向量模型,384 维,完全本地运行,无需 API
local_dir = snapshot_download(
repo_id="sentence-transformers/all-MiniLM-L6-v2"
)
# 2. 准备业务文档(实际场景从文件加载,参见索引章节)
docs = [
Document(page_content="我们的退款政策:购买后 7 天内可申请全额退款。"),
Document(page_content="会员分为普通会员和 VIP 会员,VIP 享受 8 折优惠。"),
Document(page_content="客服工作时间:周一至周五 9:00-18:00。"),
]
# 3. 初始化本地 Embedding 模型并构建向量索引
embeddings = HuggingFaceEmbeddings(
model_name=local_dir # 使用本地路径,离线可用
)
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings
)
# 4. 构建检索器(取最相关 2 条)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 5. 定义 Prompt 模板
prompt = ChatPromptTemplate.from_template("""
根据以下业务资料回答用户问题,如果资料中没有相关信息,请说"暂无相关信息"。
业务资料:
{context}
用户问题:{question}
""")
# 6. 初始化 LLM(Moonshot,兼容 OpenAI 接口格式)
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"), # 替换为你的真实 Key
base_url="https://api.moonshot.cn/v1",
temperature=0.7,
)
# 7. RAG 问答函数:检索 → 拼 Prompt → 生成答案
def rag_chain(question: str) -> str:
retrieved_docs = retriever.invoke(question)
context = "\n".join(d.page_content for d in retrieved_docs)
chain = prompt | llm | StrOutputParser()
return chain.invoke({"context": context, "question": question})
# 8. 提问
print(rag_chain("退款需要多少天?"))
print(rag_chain("VIP 有什么优惠?"))
print(rag_chain("客服几点上班?"))
说明:
- Embedding 模型把模型下载到本地,使用本地
all-MiniLM-L6-v2,完全离线运行,无需调用 OpenAI Embedding API,节省费用- LLM 使用 Moonshot,可替换为任意兼容 OpenAI 格式的服务(OpenAI / DeepSeek / 智谱等)
snapshot_download首次会下载约 90MB 模型文件,之后自动命中本地缓存,无需重复下载
为什么需要 RAG?
| 问题 | RAG 解决方案 |
|---|---|
| 知识过时 | 从最新知识库检索 |
| 幻觉问题 | 基于事实文档生成,减少幻觉 |
| 专有知识 | 集成企业内部知识库 |
| 可追溯性 | 可引用信息来源 |
应用场景:
- 📄 企业知识库问答
- 🏥 医疗诊断辅助
- ⚖️ 法律文件查询
- 📚 学术资料检索
有什么用:
- ✅ 提高答案准确性
- 📖 支持实时知识更新
- 🔍 提供可信度和可溯源性
- 💼 适应企业级应用
2.3.3 Agent(智能体)
定义: Agent(Intelligent Agent)是一个自主决策系统,能够感知环境、规划行动、调用工具、反思结果,最终完成复杂任务。
广义上,任何通过传感器感知环境、通过执行器作用于环境的事物都是智能体:人用眼睛感知、用手行动;机器人用摄像头感知、用电机行动。LLM Agent 则用"用户输入 + 工具返回结果"作为感知,用"生成文本 + 调用工具"作为行动。
Agent vs 普通 LLM 调用
| 对比项 | 普通 LLM 调用 | Agent |
|---|---|---|
| 执行方式 | 单次请求 → 单次回复 | 多轮循环,直到任务完成 |
| 工具使用 | 无 | 可调用搜索、代码执行、数据库等外部工具 |
| 自主决策 | 无,完全由用户驱动 | 自主判断下一步该做什么 |
| 适用任务 | 单轮问答、文本生成 | 多步骤、需要外部信息的复杂任务 |
ReAct:Agent 的核心运行机制
主流 Agent 使用 ReAct(Reasoning + Acting)框架运行,每一轮循环包含三步:
Thought(思考):分析当前状态,决定下一步怎么做
↓
Action(行动):调用某个工具,传入参数
↓
Observation(观察):拿到工具返回的结果
↓
重复循环,直到 Thought 认为任务已完成 → 输出 Final Answer
具体示例(任务:查北京今天天气):
用户:北京今天天气怎么样?
[循环第1轮]
Thought: 我需要查询实时天气,应该调用搜索工具
Action: search("北京今天天气")
Observation: 晴,26°C,东南风3级,空气质量良好
Thought: 已获得足够信息,可以回答用户了
Final Answer: 北京今天晴天,气温26°C,东南风3级,空气质量良好,适合出行。
Tool Calling(工具调用)
定义:Tool Calling 是让 LLM 能够"调用外部函数"的机制。开发者定义工具的名称、参数和描述,LLM 自主决定何时调用哪个工具、传什么参数。
工作原理:
开发者 → 定义工具列表(名称 + 参数描述)→ 传给 LLM
LLM → 判断需要工具 → 返回结构化的工具调用请求(JSON)
开发者 → 执行工具 → 把结果传回 LLM
LLM → 基于工具结果生成最终回复
LangChain 代码示例:
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_agent
from pydantic import SecretStr
# 1. 定义工具(@tool 装饰器,docstring 是给 LLM 看的工具描述,描述越清晰 Agent 调用越准确)
@tool
def get_weather(city: str) -> str:
"""查询指定城市的当前天气。输入城市名称,返回天气信息。"""
# 实际场景接真实天气 API,这里用模拟数据
weather_data = {
"北京": "晴,26°C,东南风3级",
"上海": "多云,22°C,东风2级",
"广州": "小雨,28°C,南风4级",
}
return weather_data.get(city, f"暂无 {city} 的天气数据")
@tool
def calculate(expression: str) -> str:
"""计算数学表达式。输入合法的 Python 数学表达式,返回计算结果。"""
try:
result = eval(expression) # 生产环境请用安全的沙箱替代 eval
return str(result)
except Exception as e:
return f"计算出错:{e}"
# 2. 初始化 Moonshot(temperature=0 降低随机性,Agent 场景推荐)
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
temperature=0,
)
# 3. 创建 Agent(create_agent 会自动处理 Prompt 和工具绑定)
agent = create_agent(
model=llm,
tools=[get_weather, calculate],
system_prompt="你是一个有用的助手,可以查天气和做计算。遇到天气或计算问题时优先调用工具。",
)
# 4. 运行(输入为标准消息列表格式)
result = agent.invoke({
"messages": [
{
"role": "user",
"content": "北京今天天气怎么样?另外帮我算一下 (365 * 24) 是多少"
}
]
})
print(result["messages"][-1].content)
# 北京今天天气晴,26°C,东南风3级。365 × 24 = 8760 小时。
说明:
create_agent是 LangChain 新版简洁 API,内部自动完成 Prompt 构建和工具绑定,无需手动创建AgentExecutor- 输入 / 输出均使用标准消息列表格式(
messages),和 OpenAI Chat API 保持一致result["messages"][-1]取最后一条消息,即 Agent 的最终回复
Agent 常用工具类型
| 工具类型 | 典型实现 | 用途 |
|---|---|---|
| 网络搜索 | Tavily / SerpAPI / DuckDuckGo | 获取实时信息 |
| 代码执行 | Python REPL | 数学计算、数据分析 |
| 文件读写 | 自定义工具 | 读取本地文件、写入结果 |
| 数据库查询 | SQL 工具 | 查询业务数据 |
| RAG 检索 | 向量数据库检索器 | 查询知识库(Agent + RAG 组合) |
| HTTP 请求 | 自定义 API 调用 | 对接第三方服务 |
应用场景
- 🕷️ 网页自动爬取和数据提取
- 📊 自动数据分析与报告生成
- 🛒 自动采购和订单处理
- 🐛 代码调试和自动修复
- 🤖 客服自动化(结合 RAG + Tool Calling)
2.3.4 MCP(模型上下文协议)
MCP(Model Context Protocol) 是 Anthropic 于 2024 年底发布的开放协议,定义了 AI 模型与外部工具 / 数据源之间的标准化通信方式。
为什么需要 MCP?
在 MCP 出现之前,每个 AI 应用都要为每个工具单独写一套集成代码,重复造轮子。MCP 解决的是 "N × M 集成爆炸" 问题:
没有 MCP(N 个模型 × M 个工具 = N×M 套集成代码):
Claude ──自定义代码── GitHub
Claude ──自定义代码── Slack
GPT-4 ──自定义代码── GitHub
GPT-4 ──自定义代码── Slack
...(每加一个工具,每个模型都要重写)
有了 MCP(N + M):
Claude ──MCP── MCP Server ──标准接口── GitHub
GPT-4 ──MCP── MCP Server ──标准接口── Slack
...(工具只需写一次 MCP Server,任意模型复用)
MCP 架构
| 角色 | 说明 |
|---|---|
| MCP Host | AI 应用本体(如 Claude Desktop、Cursor、自己写的 Agent) |
| MCP Client | Host 内置的协议客户端,负责与 Server 通信 |
| MCP Server | 暴露工具 / 资源的服务,可以是本地进程或远程 HTTP 服务 |
MCP vs 传统 Tool Calling
| 对比项 | 传统 Tool Calling | MCP |
|---|---|---|
| 工具定义 | 开发者在代码里手写 schema | MCP Server 统一声明,客户端自动发现 |
| 复用性 | 绑定特定模型 / 框架 | 任何支持 MCP 的客户端均可复用 |
| 生态 | 各平台各自为政 | 统一协议,工具共享 |
| 连接方式 | 函数调用 | JSON-RPC over stdio / HTTP SSE |
代码示例
第一步:创建 MCP Server
用 mcp 库暴露一个"查天气"工具:
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
def get_weather(city: str) -> str:
"""查询指定城市的天气,输入城市名称返回天气描述。"""
mock_data = {
"北京": "晴,气温 22°C,东北风 3 级",
"上海": "多云,气温 18°C,东风 2 级",
"广州": "阵雨,气温 26°C,南风 4 级",
}
return mock_data.get(city, f"{city} 暂无天气数据")
if __name__ == "__main__":
mcp.run() # 默认通过 stdio 启动
pip install mcp[cli]
python server.py
第二步:LangChain 接入 MCP Server
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
import asyncio
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
)
async def main():
# 实例化 MCP 客户端,声明要连接的 Server
client = MultiServerMCPClient({
"weather": {
"command": "python",
"args": ["server.py"], # 启动上面写的 server.py
"transport": "stdio",
}
})
tools = await client.get_tools() # 自动发现 Server 暴露的所有工具
agent = create_agent(model=llm, tools=tools)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": "北京今天天气怎么样?"}]
})
print(result["messages"][-1].content)
asyncio.run(main())
小结: MCP 是 AI 工具生态的"USB 接口"——统一标准,即插即用。随着越来越多工具发布官方 MCP Server(GitHub、Slack、Figma、各类数据库),Agent 的能力边界将大幅扩展,开发者只需专注于业务逻辑,不再重复造集成层。
2.4 工程实践
2.4.1 长期记忆
问题: 标准 LLM 只能记住当前对话,无法跨会话保存用户信息。
解决方案:使用向量数据库
用户历史信息
↓
[向量化] 转换为向量表示
↓
[向量数据库] ChromaDB / Pinecone 存储
↓
新对话时,检索相关历史信息
↓
LLM 基于历史上下文生成回应
常见向量数据库:
| 数据库 | 特点 | 适用场景 |
|---|---|---|
| ChromaDB | 轻量级、开源、本地部署 | 小到中型应用、快速原型 |
| Pinecone | 云托管、性能高、易扩展 | 大规模应用、企业级 |
| Weaviate | 功能丰富、支持混合查询 | 复杂搜索需求 |
| Milvus | 开源、高性能、支持 GPU | 大规模向量搜索 |
代码示例:
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.documents import Document
from pydantic import SecretStr
from huggingface_hub import snapshot_download
import os
os.environ['HF_ENDPOINT'] = 'https://mirrors.tuna.tsinghua.edu.cn/hugging-face'
local_dir = snapshot_download(repo_id="sentence-transformers/all-MiniLM-L6-v2")
embeddings = HuggingFaceEmbeddings(model_name=local_dir)
# persist_directory 持久化到磁盘,重启进程后记忆依然存在
memory_store = Chroma(
collection_name="user_memory",
embedding_function=embeddings,
persist_directory="./memory_db",
)
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
temperature=0.7,
)
prompt = ChatPromptTemplate.from_template("""
你是一个记得用户信息的助手。根据以下已知信息回答用户问题。
已知信息:
{memory}
用户:{question}
""")
def remember(text: str):
"""把信息存入长期记忆"""
memory_store.add_documents([Document(page_content=text)])
print(f"✅ 已记住:{text}")
def chat(question: str) -> str:
"""对话时自动检索相关记忆"""
docs = memory_store.similarity_search(question, k=3)
memory = "\n".join(d.page_content for d in docs) if docs else "暂无相关记忆"
chain = prompt | llm | StrOutputParser()
return chain.invoke({"memory": memory, "question": question})
# ── 模拟第一次对话(存入记忆)────────────────────────────────────
remember("用户叫小明,是一名 Python 开发者")
remember("小明喜欢喝咖啡,不喜欢喝茶")
remember("小明的公司在北京,做 AI 相关业务")
# ── 模拟第二次对话(跨会话检索记忆)─────────────────────────────
print(chat("我叫什么名字?")) # → 你叫小明
print(chat("我在哪个城市工作?")) # → 你在北京工作
print(chat("我喜欢喝什么?")) # → 你喜欢喝咖啡
说明:
persist_directory将向量索引落盘,重启程序后直接加载,无需重新写入记忆,实现真正的跨会话长期记忆。
问题: 重复调用 API 浪费成本和时间。我们为什么要使用缓存呢?主要就是为了减少访问低速服务的次数,提高访问速度。
解决方案:缓存重复请求
import time
from langchain_openai import ChatOpenAI
from langchain_core.globals import set_llm_cache
from langchain_community.cache import InMemoryCache, SQLiteCache
from pydantic import SecretStr
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
)
# ── 方式 1:内存缓存(进程重启后失效,适合开发调试)──────────────────
set_llm_cache(InMemoryCache())
# ── 方式 2:SQLite 持久化缓存(重启后依然有效,适合生产)────────────
# set_llm_cache(SQLiteCache(database_path=".langchain_cache.db"))
# 缓存对 LLM 透明,无需修改调用代码
question = "用一句话解释什么是机器学习"
t0 = time.time()
print(llm.invoke(question).content) # 第一次:真实调用 API
print(f"首次耗时:{time.time() - t0:.2f}s")
t1 = time.time()
print(llm.invoke(question).content) # 第二次:命中缓存,直接返回
print(f"缓存命中:{time.time() - t1:.4f}s") # 耗时接近 0
运行输出:
机器学习是一种使计算机系统通过数据和算法自动改进其性能的技术,从而实现模式识别、预测和决策等任务。
首次耗时:1.86s
机器学习是一种使计算机系统通过数据和算法自动改进其性能的技术,从而实现模式识别、预测和决策等任务。
缓存命中:0.0008s
同一个问题,缓存命中后耗时从 1.86s → 0.0008s,提速约 2300 倍,且不消耗任何 Token。
缓存策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 精确匹配缓存 | Prompt 完全相同时返回缓存 | 高频重复查询 |
| 语义缓存 | 相似语义的 Prompt 共享缓存 | 降低成本 |
| 智能失效 | 定期更新过期缓存 | 涉及时效性数据 |
有什么用:
- 💰 大幅降低 API 成本(可节省 50%-80%)
- ⚡ 提升响应速度(缓存命中可 1000x 加速)
- 📊 减少 API 调用频率和网络延迟
2.4.3 集中接入
问题: 多个应用分别调用 LLM,管理复杂。
解决方案:构建统一网关
应用 A 应用 B 应用 C
↓ ↓ ↓
┌──────────────────────────┐
│ AI 服务网关 │
├──────────────────────────┤
│ • 请求路由 │
│ • 速率限制 │
│ • 成本计算 │
│ • 监控和日志 │
│ • 故障转移 │
└──────────────────────────┘
↓
┌──────────┬──────────┬──────────┐
│OpenAI │Google │Anthropic │
└──────────┴──────────┴──────────┘
网关职责:
- 🚦 路由和负载均衡:智能分配请求到不同 LLM
- 💳 成本管理:按模型、用户、应用维度统计成本
- 🔐 安全控制:认证、授权、数据脱敏
- 📊 监控告警:性能指标、错误率、延迟
- 🔄 故障转移:某个 LLM 不可用时自动切换
有什么用:
- 🏢 企业级管理
- 💰 成本优化和控制
- 🔒 统一安全策略
- 📈 可观测性和可维护性
推荐工具:One API
自己从零搭建网关成本较高,推荐直接使用开源项目 One API,开箱即用地实现上述所有网关能力。
LLM API 管理 & 分发系统,统一 API 适配,可用于 Key 管理与二次分发。单可执行文件,提供 Docker 镜像,一键部署。
支持的模型渠道(30+):
| 类型 | 支持渠道 |
|---|---|
| 国际主流 | OpenAI / Azure OpenAI、Anthropic Claude、Google Gemini、Mistral、xAI、Cohere |
| 国内主流 | 通义千问、文心一言、讯飞星火、智谱 ChatGLM、豆包、腾讯混元、DeepSeek、Moonshot |
| 其他 | Ollama(本地模型)、硅基流动、Groq、Coze、together.ai 等 |
核心功能:
- 🔑 统一 Key 管理:所有模型用同一个 API Key 访问,对业务代码完全透明
- ⚖️ 负载均衡 & 故障转移:多渠道自动切换,某个模型挂了不影响服务
- 💳 额度 & 用量管理:按用户、应用设置配额和费率,防止超支
- 📊 监控 & 日志:实时查看调用量、费用、错误率
- 🌊 Stream 支持:完整支持流式输出,打字机效果无缝兼容
一键部署(Docker):
docker run --name one-api \
-p 3000:3000 \
-v /data/one-api:/data \
-d justsong/one-api
部署后访问 http://localhost:3000,默认账号 root / 密码 123456。
业务代码接入方式(只需改 base_url,其余代码不变):
from langchain_openai import ChatOpenAI
from pydantic import SecretStr
# base_url 指向 One API 地址,api_key 使用 One API 生成的令牌
llm = ChatOpenAI(
model="gpt-4o-mini", # 模型名称由 One API 后台统一映射
api_key=SecretStr("one-api-token"),
base_url="http://localhost:3000/v1",
)
三、模型能力
3.1 开源模型
定义
开源模型 是由社区或组织开放源代码和模型权重的大语言模型,开发者可以下载、部署和微调。
主流开源大模型
| 模型 | 机构 | 优势 | 适用场景 |
|---|---|---|---|
| Llama 2 / 3 | Meta | 性能优异、社区活跃 | 通用对话、代码生成 |
| Qwen | 阿里巴巴 | 中文能力强、多语言 | 中文应用、多语言任务 |
| ChatGLM | 清华大学 | 中文优化、轻量 | 中文对话、本地部署 |
| Falcon | TII | 高效能、少依赖 | 资源受限场景 |
| Mistral | Mistral AI | 快速、高效 | 实时应用 |
| DeepSeek | DeepSeek | 成本低、开源 | 经济型应用 |
部署方式
代码示例
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from pydantic import SecretStr
llm = ChatOpenAI(
model="moonshot-v1-8k",
api_key=SecretStr("your-api-key"),
base_url="https://api.moonshot.cn/v1",
temperature=0.7,
)
prompt = ChatPromptTemplate.from_template("请用一句话回答:{question}")
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"question": "什么是大语言模型?"}))
# → 大语言模型是基于海量文本数据训练的深度学习模型,能理解和生成自然语言文本。
闭源 vs 开源对比
| 维度 | 闭源(GPT-5) | 开源(Llama) |
|---|---|---|
| 性能 | 最强 | 较弱,但快速进步 |
| 成本 | 按 Token 计费 | 免费,但需计算资源 |
| 隐私 | 数据上传到云端 | 本地部署,完全隐私 |
| 定制 | 不可修改 | 可微调适配 |
| 可用性 | 依赖网络 | 可离线运行 |
| 学习成本 | 低(直接用 API) | 高(需部署运维) |
有什么用?
- 🔓 完全开源:可审计、可信任
- 💰 成本优化:大规模应用可大幅降低成本
- 🔒 数据隐私:敏感信息本地处理,不出网
- 🎯 垂直定制:针对特定领域微调优化
- 🌍 全球可用:无地域限制
3.2 模型微调(Fine-tuning)
定义
微调 是在预训练模型基础上,使用特定领域或任务的数据进行训练,使模型适应特定应用需求的过程。
为什么需要微调?
通用 LLM 微调后模型
↓ ↓
• 知识泛化 • 领域知识深化
• 一般能力 • 特定任务优化
• 可能不适配专业领域 • 精准、可靠
场景示例:
- 🏥 医疗:微调模型识别医学术语、诊断辅助
- ⚖️ 法律:微调模型理解法律条款、合同分析
- 💰 金融:微调模型进行风险评估、投资建议
微调方法对比
| 方法 | 原理 | 成本 | 效果 | 难度 |
|---|---|---|---|---|
| 全参数微调 | 更新所有参数 | 高 | 最优 | 高 |
| LoRA | 低秩适配,更新少量参数 | 低 | 优 | 低 |
| P-Tuning | 前缀调参,添加可学习前缀 | 很低 | 良 | 很低 |
| In-context Learning | 不更新,仅改 Prompt | 无 | 一般 | 很低 |
具体对比:
成本 ↑
│ 全参数微调
│ ▲
│ │ │
│ │ │ LoRA
│ │ │ ▲
│ │ │ │ │
│ │ │ │ │ P-Tuning
│ ──────────────────→ 效果
In-context
微调流程
1. 数据准备
↓
• 收集任务相关数据
• 标注和清洗
• 格式转换
↓
2. 模型选择
↓
• 选择基础模型(Llama、Qwen 等)
• 选择微调方法(LoRA 推荐)
↓
3. 训练
↓
• 配置超参数(学习率、Epoch 等)
• 监控训练过程
• 定期评估
↓
4. 评估和优化
↓
• 在验证集上评估
• 对比微调前后性能
• 调整参数重新训练
↓
5. 部署
↓
• 量化压缩(可选)
• 本地或云端部署
• 上线监控
注意事项
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 过拟合 | 训练数据太少,模型记住数据而非学习规律 | 增加数据、正则化、早停 |
| 灾难性遗忘 | 微调后模型丧失预训练知识 | 使用较小学习率、混合原始数据 |
| 资源消耗 | 全参数微调需要大量 GPU 内存 | 使用 LoRA、量化、梯度累积 |
| 伦理合规 | 微调后模型可能有偏差 | 数据审计、RLHF 对齐 |
有什么用?
- 🎯 专业化定制:适配特定领域和任务
- 📊 性能提升:相比通用模型,准确率提升 20%-50%
- 💰 成本优化:本地微调模型比频繁调用 API 廉价
- 🔒 隐私保护:敏感数据不需要上传到云端
- 🚀 快速推断:小模型推断速度快,成本低
相关资源
官方文档
开源项目
- 📦 LangChain
- 📦 LlamaIndex
- 📦 Ollama