云上大模型应用开发实践
从入门到实战,系统掌握云上大模型应用开发
本书简介
随着大语言模型(LLM)技术的快速发展,AI 应用开发正在成为越来越多开发者的必备技能。本书以阿里云为主要云平台,带领零基础读者从入门到实战,系统掌握云上大模型应用开发技能。
本书特色
- 零基础友好:从基础知识讲起,无需 AI 背景
- 实战导向:每个知识点都配有完整的代码示例
- 阿里云为主:深度整合阿里云 DashScope、PAI 等产品
- ** O’Reilly 风格**:概念 → 代码 → 最佳实践 → 陷阱
目标读者
- 想要学习 AI 应用开发的零基础开发者
- 有一定编程基础,想转型 AI 领域的工程师
- 希望在业务中引入大模型能力的团队
学习路径
- 基础入门(第 1-4 章):建立知识框架
- Prompt 工程(第 5-8 章):掌握与大模型对话的技巧
- 应用开发(第 9-15 章):实战项目开发能力
- 高级架构(第 16-20 章):企业级应用架构
- 模型微调(第 21-24 章):定制化模型能力
- 云原生部署(第 25-29 章):生产环境部署运维
- 项目实战(第 30-32 章):完整项目经验
配套资源
- 完整源代码:GitHub 仓库
- 在线文档:GitHub Pages
- 讨论社区:GitHub Issues
作者:方华 · 2026
第 1 章:云计算基础
“云计算就像水电一样,按需使用,按量付费。” —— 这是云计算最朴素的愿景,也是它改变整个 IT 产业的核心逻辑。
本章内容
- 什么是云计算,它解决了什么问题
- IaaS、PaaS、SaaS 三种服务模式的区别
- 阿里云产品体系概览
- 云端开发环境准备
1.1 云计算概述
1.1.1 为什么需要云计算
在云计算诞生之前,企业要上线一个网站或应用,需要经历这样的流程:
- 采购服务器:预估业务规模,购买物理服务器
- 机房托管:服务器需要放到专业的机房,有专人维护
- 网络配置:申请带宽,配置域名解析
- 运维管理:监控服务器状态,处理硬件故障
- 扩容困难:业务增长时,又是一轮采购、部署、调试
这个过程有几个典型问题:
- 周期长:从采购到上线,可能需要数周甚至数月
- 成本高:服务器是重资产,利用率往往不到 30%
- 风险大:预估失误会导致资源浪费或性能瓶颈
- 运维重:需要专职运维团队处理各种问题
云计算的出现,就是为了解决这些问题。
1.1.2 云计算的定义
云计算(Cloud Computing) 是指通过互联网提供按需使用的计算资源,包括服务器、存储、数据库、网络、软件等,用户无需采购、拥有和维护物理基础设施,只需按实际使用量付费。
云计算的核心特征可以用四个字概括:随选即用。
1.1.3 云计算的优势
| 优势 | 说明 |
|---|---|
| 弹性伸缩 | 业务高峰时自动扩容,低谷时自动缩容 |
| 按需付费 | 用多少付多少,避免资源浪费 |
| 快速部署 | 分钟级即可创建和销毁资源 |
| 免运维 | 云厂商负责底层基础设施的维护 |
| 高可用 | 云服务本身提供多副本、容灾等能力 |
| 全球分布 | 一键部署到全球多个地域和可用区 |
1.2 云计算服务模式
云计算提供了三种主要的服务模式,从底层到上层,分别是 IaaS、PaaS、SaaS。
1.2.1 IaaS:基础设施即服务
IaaS(Infrastructure as a Service) 提供最底层的计算资源,包括虚拟机、存储、网络等。
典型服务:
- 云服务器 ECS(Elastic Compute Service)
- 云盘(块存储)
- 虚拟私有云 VPC(Virtual Private Cloud)
特点:
- 用户拥有最大的控制权,可以自行安装操作系统和应用
- 需要用户负责应用的配置、管理和运维
- 适合有技术团队的企业,或需要高度定制化的场景
┌─────────────────────────────────────────┐
│ 用户应用 │
├─────────────────────────────────────────┤
│ 操作系统 │
├─────────────────────────────────────────┤
│ 虚拟化层 │
├─────────────────────────────────────────┤
│ 云厂商管理的基础设施 │
│ (服务器、网络、存储数据中心) │
└─────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
用户负责 用户负责 云厂商负责
1.2.2 PaaS:平台即服务
PaaS(Platform as a Service) 提供应用开发和部署的平台,用户只需上传代码或配置,无需管理底层服务器和运行时环境。
典型服务:
- 函数计算 FC(Function Compute)
- 容器服务 ACK/ASK
- 数据库服务 RDS
- 机器学习平台 PAI
特点:
- 开发者专注于业务逻辑,不用关心服务器配置
- 自动扩缩容,按调用量计费
- 适合应用开发和微服务架构
┌─────────────────────────────────────────┐
│ 用户应用/代码 │
├─────────────────────────────────────────┤
│ 运行时环境 + 中间件 + 开发工具 │
├─────────────────────────────────────────┤
│ 云厂商管理的基础设施 │
└─────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
用户负责 云厂商负责 云厂商负责
1.2.3 SaaS:软件即服务
SaaS(Software as a Service) 提供完整的应用软件,用户直接使用,无需安装和维护。
典型服务:
- 钉钉、飞书(企业协作)
- 阿里云盘(云存储)
- 有赞、微盟(电商 SaaS)
特点:
- 开箱即用,用户零运维
- 按订阅制收费
- 适合没有技术团队的企业
┌─────────────────────────────────────────┐
│ 完整应用软件(用户直接使用) │
├─────────────────────────────────────────┤
│ 全栈由云厂商管理 │
└─────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
云厂商负责 云厂商负责 云厂商负责
1.2.4 三种模式对比
| 维度 | IaaS | PaaS | SaaS |
|---|---|---|---|
| 控制权 | 高 | 中 | 低 |
| 灵活性 | 高 | 中 | 低 |
| 易用性 | 低 | 中 | 高 |
| 适用人群 | 技术团队 | 开发者 | 普通用户 |
| 典型场景 | 自建数据中心 | 应用开发 | 企业办公 |
| 代表产品 | ECS | 函数计算、RDS | 钉钉 |
对于大模型应用开发,我们主要使用 PaaS 层的服务(如函数计算、数据库),配合少量 IaaS(如云服务器)和 SaaS(如日志服务)。
1.3 阿里云产品体系概览
阿里云是国内最大的云服务商,提供超过 200 款产品,覆盖企业数字化转型的方方面面。
1.3.1 核心产品分类
┌─────────────────────────────────────────────────────────────┐
│ 阿里云产品体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 计算服务 │ │ 存储服务 │ │ 网络服务 │ │
│ │ ECS/FC/ACK │ │ OSS/NAS │ │ VPC/SLB │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 数据库服务 │ │ 大数据服务 │ │ AI 与机器 │ │
│ │ RDS/Redis │ │ MaxCompute │ │ 学习服务 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 安全服务 │ │ 监控与日志 │ │ 开发工具 │ │
│ │ RAM/KMS │ │ ARMS/SLS │ │ DevOps │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.3.2 与大模型开发相关的核心产品
| 产品类别 | 产品名称 | 用途 |
|---|---|---|
| 模型服务 | DashScope 灵积 | 统一调用各类大模型的 API |
| 通义千问 Qwen | 阿里云自研大语言模型 | |
| 通义万相 Wanx | 图像生成模型 | |
| 开发平台 | PAI | 机器学习训练和部署 |
| DSW | 数据科学 Jupyter 环境 | |
| EAS | 模型推理服务部署 | |
| 计算服务 | 函数计算 FC | Serverless 运行 AI 代码 |
| 容器服务 ACK | Kubernetes 容器编排 | |
| 数据服务 | OSS | 存储模型文件、训练数据 |
| Elasticsearch | 向量检索 | |
| 网络服务 | API 网关 | 管理 AI 应用的 API |
1.4 云端开发环境准备
1.4.1 必要的准备工作
开始云上大模型开发前,需要完成以下准备:
-
阿里云账号注册
- 访问阿里云官网 (aliyun.com) 注册账号
- 完成实名认证(个人或企业)
-
创建 AccessKey
- 登录阿里云控制台
- 进入「AccessKey 管理」
- 创建 AccessKey ID 和 Secret(妥善保管,不要泄露)
-
开通必要服务
- 函数计算 FC(按量付费即可开始)
- 对象存储 OSS(首月有免费额度)
- DashScope(新用户有免费调用额度)
1.4.2 本地开发环境配置
Python 环境(推荐使用 Conda 管理):
# 安装 Miniconda(轻量级 Conda)
curl -O https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh
bash Miniconda3-latest-MacOSX-arm64.sh
# 创建本书专用环境
conda create -n cloud-llm python=3.11 -y
conda activate cloud-llm
# 安装核心依赖
pip install dashscope aliyun-python-sdk-core requests python-dotenv
阿里云 CLI 工具(可选,用于命令行管理资源):
# macOS
brew install aliyun-cli
# 配置凭证
aliyun configure
# 按提示输入 AccessKey ID 和 Secret,选择区域
1.4.3 第一个云端资源
让我们用命令行创建一个 OSS Bucket,感受一下云资源的创建过程:
# 创建存储空间
aliyun oss mb oss://cloud-llm-book-demo --region cn-hangzhou
# 上传一个文件
echo "Hello, Cloud!" > hello.txt
aliyun oss cp hello.txt oss://cloud-llm-book-demo/
# 查看文件
aliyun oss ls oss://cloud-llm-book-demo/
# 清理(删除资源)
aliyun oss rb oss://cloud-llm-book-demo/
这个创建和销毁云资源的过程,只需几分钟,远快于传统的采购流程,这就是云计算的魔力。
1.5 本章小结
本章介绍了云计算的基础概念和阿里云产品体系的核心要点:
- 云计算的本质是提供随时可用的计算资源,按需使用,按量付费
- 三种服务模式:IaaS(卖基础设施)、PaaS(卖平台)、SaaS(卖软件)
- 阿里云产品体系覆盖计算、存储、网络、数据库、AI 等多个领域
- 开发准备需要注册账号、创建 AccessKey、开通必要服务
下一章我们将深入学习阿里云的核心产品,包括对象存储 OSS、函数计算 FC 和 API 网关,为后续的大模型应用开发打下基础。
思考与练习
- 概念辨析:解释 IaaS、PaaS、SaaS 的区别,并各举一个阿里云产品的例子
- 成本计算:假设你的应用每天有 1000 次 API 调用,每次调用消耗 0.1 元,如果部署在 ECS(月付 200 元)和函数计算(按调用计费)上,分别需要多少成本?
- 资源规划:如果你要上线一个 AI 问答应用,需要用到哪些阿里云服务?画出简单的架构图
第 2 章:阿里云核心产品(上)
本章聚焦三款与大模型应用开发最密切相关的产品:对象存储 OSS、函数计算 FC 和 API 网关。掌握这三款产品,就掌握了云端 AI 应用的数据存储、计算执行和流量入口。
本章内容
- 对象存储 OSS:云端文件存储
- 函数计算 FC:Serverless 计算服务
- API 网关:API 管理与流量控制
2.1 对象存储 OSS
2.1.1 什么是 OSS
OSS(Object Storage Service) 是阿里云提供的海量、安全、低成本、高可靠的云存储服务。它专门用于存储文件(称为“对象“),支持任意类型的数据。
与传统的文件系统相比:
| 特性 | 传统文件系统 | OSS |
|---|---|---|
| 存储规模 | 单机TB级 | 理论上无限PB级 |
| 访问方式 | 挂载到单机 | HTTP/HTTPS API |
| 扩展性 | 需要扩容硬件 | 一键扩容 |
| 成本 | 硬件+运维成本高 | 按量付费 |
| 持久性 | 99.9% | 99.999999999% (11个9) |
2.1.2 核心概念
Bucket(存储空间):
- OSS 的顶层容器
- 每个 Bucket 有唯一的域名:
<bucket>.oss-<region>.aliyuncs.com - 可以设置访问权限(公共读、私有等)
Object(对象):
- OSS 中存储的基本单元
- 由 Key(文件名)+ Meta(元数据)+ Data(数据)组成
- Key 支持路径格式:
folder/subfolder/file.txt
Region(地域):
- 数据中心所在的地理区域
- 如:华东1(杭州)、华北2(北京)、华南1(深圳)
- 建议选择离用户最近的地域,减少延迟
2.1.3 OSS 基本操作
Python SDK 示例:
import oss2
from dotenv import load_dotenv
import os
# 加载环境变量
load_dotenv()
# 获取凭证
access_key_id = os.getenv('OSS_ACCESS_KEY_ID')
access_key_secret = os.getenv('OSS_ACCESS_KEY_SECRET')
bucket_name = 'cloud-llm-demo'
endpoint = 'https://oss-cn-hangzhou.aliyuncs.com'
# 初始化 Bucket
auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)
# 上传文件
def upload_file(local_path, oss_key):
"""上传文件到 OSS"""
result = bucket.put_object(oss_key, open(local_path, 'rb').read())
if result.status == 200:
print(f"上传成功: {oss_key}")
print(f"URL: https://{bucket_name}{endpoint.replace('https://', '/')}/{oss_key}")
return result.status == 200
# 下载文件
def download_file(oss_key, local_path):
"""从 OSS 下载文件"""
result = bucket.get_object(oss_key)
if result.status == 200:
with open(local_path, 'wb') as f:
f.write(result.read())
print(f"下载成功: {local_path}")
return True
return False
# 列举文件
def list_files(prefix=''):
"""列举 Bucket 中的文件"""
for obj in oss2.ObjectIterator(bucket, prefix=prefix):
print(f" {obj.key} ({obj.size} bytes)")
# 删除文件
def delete_file(oss_key):
"""删除 OSS 文件"""
result = bucket.delete_object(oss_key)
print(f"删除状态: {result.status}")
return result.status == 204
# 示例调用
if __name__ == '__main__':
# 上传训练数据
upload_file('data/train.json', 'training/train.json')
# 列举所有训练数据
print("\n训练数据列表:")
list_files('training/')
# 下载模型文件
download_file('models/llm-v1.bin', './local-model.bin')
2.1.4 OSS 在大模型应用中的典型场景
┌─────────────────────────────────────────────────────────────┐
│ 大模型应用中的 OSS │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 训练数据 │ │ 模型文件 │ │ 用户上传 │ │
│ │ 存储 │ │ 存储 │ │ 内容存储 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ OSS Bucket │ │
│ └───────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PAI 训练 │ │ 函数计算 │ │ CDN 加速 │ │
│ │ 读取数据 │ │ 读取文件 │ │ 分发内容 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 函数计算 FC
2.2.1 什么是函数计算
函数计算 FC(Function Compute) 是阿里云提供的 Serverless 计算服务。它允许你编写和部署代码,无需管理服务器。函数计算会自动为你处理服务器的扩容、负载均衡、高可用等基础设施问题。
Serverless = 无服务器 ≠ 没有服务器:
- 你不需要关心服务器的存在
- 背后的服务器由云厂商管理和维护
- 按实际执行时间计费,不执行不收费
2.2.2 核心概念
Function(函数):
- 函数计算的基本单元
- 一个函数包含:代码、配置(内存、超时时间)、触发器
- 支持多种运行时:Python、Node.js、Java、PHP、Go 等
Trigger(触发器):
- 触发函数执行的事件源
- 支持:HTTP 调用、定时任务、OSS 事件、API 网关、MQ 消息等
- 一个函数可以绑定多个触发器
Execution Role(执行角色):
- 函数运行时使用的权限身份
- 通过 RAM 角色控制函数可以访问的云资源
2.2.3 第一个 Serverless 函数
1. 安装函数计算工具:
# 安装 fun 工具(函数计算的命令行工具)
npm install @alicloud/fun -g
# 验证安装
fun --version
2. 创建函数项目:
# 创建项目目录
mkdir fc-ai-demo && cd fc-ai-demo
mkdir -p code
# 创建函数代码
cat > code/index.py << 'EOF'
# -*- coding: utf-8 -*-
import json
def handler(event, context):
"""
事件处理函数
event: 触发事件(JSON 字符串)
context: 运行环境信息
"""
# 解析事件
payload = json.loads(event)
name = payload.get('name', 'World')
# 返回响应
return {
'statusCode': 200,
'body': json.dumps({
'message': f'Hello, {name}!',
'source': 'Serverless Function'
}),
'headers': {
'Content-Type': 'application/json'
}
}
EOF
# 创建配置文件
cat > template.yml << 'EOF'
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Service:
ai-demo-service:
Description: 'AI 应用 Serverless 服务'
Policies:
- AliyunOSSFullAccess # 函数需要访问 OSS
Function:
hello-function:
Handler: index.handler
Runtime: python3.10
CodeUri: ./code
MemorySize: 512
Timeout: 60
EnvironmentVariables:
REGION: cn-hangzhou
Trigger:
http-trigger:
Type: HTTP
Properties:
AuthType: ANONYMOUS
Methods:
- GET
- POST
EOF
3. 部署函数:
# 部署(需要先配置 AccessKey)
fun deploy
# 部署输出示例:
# function hello-function deploy success
# trigger http-trigger deploy success
# url: https://ai-demo-service-hz12345.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/hello-function/hello/
4. 调用函数:
# 测试 HTTP 调用
curl https://ai-demo-service-hz12345.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/hello-function/hello/ \
-X POST \
-H "Content-Type: application/json" \
-d '{"name": "Cloud AI"}'
# 响应:
# {"message": "Hello, Cloud AI!", "source": "Serverless Function"}
2.2.4 函数计算的高级特性
1. 异步调用与回调:
def async_handler(event, context):
"""异步处理长时间任务"""
import time
client = fc2.Client(
endpoint=context.account_id + '.cn-hangzhou.fc.aliyuncs.com',
access_key_id=context.credentials.access_key_id,
access_key_secret=context.credentials.access_key_secret,
security_token=context.credentials.security_token
)
# 模拟耗时任务
time.sleep(5)
# 返回任务ID
return {
'statusCode': 202,
'body': json.dumps({
'taskId': 'async-' + str(int(time.time())),
'status': 'processing'
})
}
2. 访问云资源(OSS):
import oss2
def handler(event, context):
"""函数中访问 OSS"""
creds = context.credentials
# 使用函数执行角色的临时凭证访问 OSS
auth = oss2.StsAuth(
creds.access_key_id,
creds.access_key_secret,
creds.security_token
)
bucket = oss2.Bucket(auth, 'oss-cn-hangzhou.aliyuncs.com', 'my-bucket')
# 读取文件
result = bucket.get_object('data/input.txt')
content = result.read().decode('utf-8')
return {
'statusCode': 200,
'body': json.dumps({'content_length': len(content)})
}
3. 并发执行与流量控制:
# template.yml 中配置并发
Function:
llm-function:
Handler: index.handler
Runtime: python3.10
MemorySize: 1024
# 并发设置
InstanceConcurrency: 10 # 单实例最大并发数
# 预留实例(冷启动优化)
CustomRuntimeConfig:
Command: ['python', '-m', 'mylib.app']
InstanceType: e1
2.2.5 函数计算 vs ECS
| 维度 | 函数计算 FC | 云服务器 ECS |
|---|---|---|
| 计费方式 | 按调用次数+执行时长 | 包月/包年 |
| 扩缩容 | 自动(毫秒级) | 手动(分钟级) |
| 适用场景 | 事件驱动、间歇性负载 | 持续负载、常驻进程 |
| 冷启动 | 首次调用有延迟 | 无冷启动 |
| 运行环境 | 受限(无 SSH 访问) | 完全控制 |
| 价格 | 适合低频调用 | 适合高频使用 |
经验法则:
- 流量波动大、不可预测 → 函数计算
- 持续稳定流量 → ECS
- 两者结合 → 用 ECS 跑核心服务,函数计算处理突发流量
2.3 API 网关
2.3.1 什么是 API 网关
API 网关(API Gateway) 是管理和保护 API 的服务。它作为所有 API 请求的统一入口,提供请求路由、协议转换、流量控制、安全认证等功能。
2.3.2 API 网关的核心功能
┌─────────────────────────────────────────────────────────────┐
│ API 网关 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 请求路由 │ │ 流量控制 │ │ 安全认证│ │ 日志监控│ │
│ │ │ │ │ │ │ │ │ │
│ │ 根据路径 │ │ 限流限速 │ │ API Key │ │ 调用统计│ │
│ │ 分发请求 │ │ 熔断降级 │ │ JWT认证 │ │ 错误追踪│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └─────────────┴─────────────┴─────────────┘ │
│ │ │
└─────────────────────────────┼──────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 函数计算 │ │ ECS │ │ PAI │
│ FC │ │ 服务器 │ │ 推理服务 │
└─────────┘ └─────────┘ └─────────┘
2.3.3 创建 API 分组
1. 通过控制台创建:
- 登录阿里云 API 网关控制台
- 创建 API 分组(每个分组有独立域名)
- 定义 API:请求方法、路径、后端服务类型
- 绑定域名(可选,使用自定义域名)
2. Python SDK 创建 API:
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
import os
# 初始化客户端
client = AcsClient(
os.getenv('ACCESS_KEY_ID'),
os.getenv('ACCESS_KEY_SECRET'),
'cn-hangzhou'
)
def create_api_group():
"""创建 API 分组"""
request = CommonRequest()
request.set_method('POST')
request.set_domain('apigateway.aliyuncs.com')
request.set_version('2016-05-03')
request.set_action_name('CreateApiGroup')
request.add_query_param('GroupName', 'ai-chat-api')
request.add_query_param('Description', 'AI 对话应用 API')
response = client.do_action_with_exception(request)
result = json.loads(response.decode('utf-8'))
print(f"API 分组 ID: {result['ApiGroupId']}")
print(f"二级域名: {result['SubDomain']}")
return result
def create_chat_api(group_id):
"""创建对话 API"""
request = CommonRequest()
request.set_method('POST')
request.set_domain('apigateway.aliyuncs.com')
request.set_version('2016-05-03')
request.set_action_name('CreateApi')
request.add_query_param('GroupId', group_id)
request.add_query_param('ApiName', 'chat_completion')
request.add_query_param('Visibility', 'PRIVATE')
request.add_query_param('RequestConfig', json.dumps({
'RequestHttpMethod': 'POST',
'RequestPath': '/v1/chat/completion',
'BodyFormat': 'FORM'
}))
request.add_query_param('ServiceConfig', json.dumps({
'ServiceType': 'FunctionCompute',
'ServiceName': 'ai-demo-service',
'FunctionName': 'chat-function',
'ServiceTimeout': 60
}))
request.add_query_param('RequestParameters', json.dumps([
{
'ApiParameterName': 'prompt',
'Location': 'BODY',
'Required': 'REQUIRED',
'Type': 'STRING'
}
]))
response = client.do_action_with_exception(request)
return json.loads(response.decode('utf-8'))
2.3.4 API 安全认证
1. API Key 认证(简单场景):
# 请求时携带 API Key
headers = {
'Authorization': 'APPCODE your_app_code_here',
'Content-Type': 'application/json'
}
response = requests.post(
'https://your-api.cn-hangzhou.aliyuncs.com/v1/chat/completion',
headers=headers,
json={'prompt': 'Hello!'}
)
2. JWT 认证(推荐):
import jwt
import time
def generate_jwt_token(user_id, api_key):
"""生成 JWT Token"""
payload = {
'iss': 'your-app',
'sub': str(user_id),
'iat': int(time.time()),
'exp': int(time.time()) + 3600 # 1小时后过期
}
token = jwt.encode(payload, api_key, algorithm='HS256')
return token
def verify_jwt_token(token, api_key):
"""验证 JWT Token"""
try:
payload = jwt.decode(token, api_key, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None # Token 已过期
except jwt.InvalidTokenError:
return None # Token 无效
2.3.5 流量控制配置
# API 网关流量控制策略
AppQuota:
- AppKey: your-app-key
QuotaLimit: 1000 # 每分钟最多 1000 次
QuotaRefreshPeriod: 1 # 刷新周期(分钟)
# 用户级限流
UserQuota:
- UserId: user-123
QuotaLimit: 100 # 每分钟最多 100 次
2.4 本章小结
本章介绍了三款与大模型应用开发最密切相关的阿里云产品:
| 产品 | 核心价值 | 典型场景 |
|---|---|---|
| OSS | 海量文件存储 | 训练数据、模型文件、用户内容 |
| 函数计算 FC | Serverless 计算 | AI 推理、事件处理、定时任务 |
| API 网关 | API 管理与保护 | 统一入口、流量控制、安全认证 |
三者的典型组合:
用户请求 → API 网关(鉴权、限流) → 函数计算(业务逻辑)
↓
OSS(读取数据)
↓
返回结果
下一章我们将继续学习容器服务、数据库和监控日志服务,完善云端 AI 应用的运维能力。
思考与练习
- 产品选型:你的团队要开发一个 AI 写作助手,用户会上传文档进行摘要,请分析需要使用哪些云产品,画出架构图
- 成本优化:假设你的应用日均调用 10 万次,每次函数执行 100ms,内存 512MB,计算函数计算的成本,并与 ECS(4核8G,月付 500 元)对比
- 安全实践:设计一个 API 网关的安全策略,包括:JWT 认证、API Key 管理、IP 白名单、调用频率限制
第 3 章:阿里云核心产品(下)
“工欲善其事,必先利其器。” —— 容器服务、数据库、日志监控是生产级 AI 应用不可或缺的基础设施。本章将带你掌握这些运维利器。
本章内容
- 容器服务 ACK/ASK:Kubernetes 容器编排
- 数据库服务 RDS/PolarDB:结构化数据存储
- 日志与监控服务 SLS/ARMS:可观测性建设
3.1 容器服务 ACK/ASK
3.1.1 什么是容器
在深入容器服务之前,先理解什么是容器(Container)。
容器是一种轻量级的虚拟化技术,它将应用及其依赖打包成一个独立的单元,确保应用在任何环境中都能一致运行。
容器 vs 虚拟机:
┌─────────────────────────────────────────────────────────────┐
│ 物理服务器 │
├──────────────┬──────────────┬──────────────┬──────────────┤
│ 虚拟机 1 │ 虚拟机 2 │ 虚拟机 3 │ 虚拟机 4 │
│ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │
│ │ Guest │ │ │ Guest │ │ │ Guest │ │ │ Guest │ │
│ │ OS │ │ │ OS │ │ │ OS │ │ │ OS │ │
│ ├────────┤ │ ├────────┤ │ ├────────┤ │ ├────────┤ │
│ │ App │ │ │ App │ │ │ App │ │ │ App │ │
│ │ Libs │ │ │ Libs │ │ │ Libs │ │ │ Libs │ │
│ └────────┘ │ └────────┘ │ └────────┘ │ └────────┘ │
│ Hypervisor │ Hypervisor │ Hypervisor │ Hypervisor │
└──────────────┴──────────────┴──────────────┴──────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 物理服务器 │
├─────────────────────────────────────────────────────────────┤
│ 容器 1 │ 容器 2 │ 容器 3 │ 容器 4 │
│ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │ ┌────────┐ │
│ │ App │ │ │ App │ │ │ App │ │ │ App │ │
│ │ Libs │ │ │ Libs │ │ │ Libs │ │ │ Libs │ │
│ └────────┘ │ └────────┘ │ └────────┘ │ └────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Docker Engine / Containerd │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 操作系统 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| 特性 | 虚拟机 | 容器 |
|---|---|---|
| 启动时间 | 分钟级 | 秒级 |
| 资源占用 | GB 级 | MB 级 |
| 密度(单机能跑多少) | 几个 | 几十个 |
| 隔离性 | 完全隔离 | 共享内核 |
| 性能 | 有虚拟化损耗 | 接近原生 |
3.1.2 什么是 Kubernetes
Kubernetes(K8s) 是一个开源的容器编排平台,用于自动化容器的部署、扩缩容和管理。
为什么需要 Kubernetes?
当你只有几个容器时,手动管理是可行的。但当应用扩展到几十个、几百个容器时,需要解决:
- 某个容器挂了,如何自动重启?
- 流量高峰时,如何自动扩容?
- 如何滚动更新应用而不中断服务?
- 如何跨多台服务器调度容器?
Kubernetes 就是为了解决这些问题而生的。
3.1.3 ACK 与 ASK
ACK(Container Service for Kubernetes):
- 阿里云托管的 Kubernetes 服务
- 你管理应用和配置,阿里云管理控制平面
- 适合:有 Kubernetes 使用经验,需要完全控制集群
ASK(Serverless Kubernetes):
- 无服务器 Kubernetes
- 无需管理节点,容器按需创建
- 适合:突发流量、事件驱动型应用
| 维度 | ACK | ASK |
|---|---|---|
| 节点管理 | 用户管理(Node Pool) | 完全托管(无需节点) |
| 扩缩容 | 节点+Pod 扩缩容 | Pod 级别扩缩容 |
| 冷启动 | 节点预热 | 更快(按需创建) |
| 成本 | 节点费用 | Pod 运行费用 |
| 适用场景 | 长期稳定工作负载 | 突发、事件驱动 |
3.1.4 在 ACK 上部署 AI 应用
1. 编写 Dockerfile:
# 基础镜像
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD curl -f http://localhost:8080/health || exit 1
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "4", "app:app"]
requirements.txt:
flask==3.0.0
gunicorn==21.2.0
dashscope==1.14.0
redis==5.0.1
2. 构建并推送镜像:
# 登录阿里云容器镜像服务
docker login --username=your-username registry.cn-hangzhou.aliyuncs.com
# 构建镜像
docker build -t registry.cn-hangzhou.aliyuncs.com/your-namespace/ai-chat:v1 .
# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/ai-chat:v1
3. 编写 Kubernetes 部署文件:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-chat-deployment
labels:
app: ai-chat
spec:
replicas: 2
selector:
matchLabels:
app: ai-chat
template:
metadata:
labels:
app: ai-chat
spec:
containers:
- name: ai-chat
image: registry.cn-hangzhou.aliyuncs.com/your-namespace/ai-chat:v1
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
env:
- name: DASHSCOPE_API_KEY
valueFrom:
secretKeyRef:
name: ai-chat-secrets
key: api-key
- name: REDIS_HOST
value: "redis-service"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: ai-chat-service
spec:
selector:
app: ai-chat
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
# hpa.yaml (Horizontal Pod Autoscaler)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ai-chat-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ai-chat-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
4. 部署到 ACK:
# 创建命名空间
kubectl create namespace ai-chat
# 创建密钥(存储敏感信息)
kubectl create secret generic ai-chat-secrets \
--from-literal=api-key=your-dashscope-key \
--namespace=ai-chat
# 部署应用
kubectl apply -f deployment.yaml -n ai-chat
kubectl apply -f service.yaml -n ai-chat
kubectl apply -f hpa.yaml -n ai-chat
# 查看部署状态
kubectl get pods -n ai-chat
kubectl get svc -n ai-chat
kubectl get hpa -n ai-chat
# 查看日志
kubectl logs -l app=ai-chat -n ai-chat -f
3.2 数据库服务
3.2.1 关系型数据库 RDS
RDS(Relational Database Service) 是阿里云托管的关系型数据库服务,支持 MySQL、PostgreSQL、SQL Server、MariaDB 等引擎。
为什么不用 ECS 自建数据库?
| 维度 | ECS 自建 | RDS |
|---|---|---|
| 运维 | 需要 DBA 团队 | 云厂商托管 |
| 备份 | 手动配置 | 自动备份 |
| 高可用 | 手动主从 | 多副本自动切换 |
| 性能优化 | 需要专家 | 参数自动调优 |
| 安全 | 手动配置防火墙 | 内置安全防护 |
3.2.2 RDS 基本操作(Python)
import pymysql
from dbutils.pooled_db import PooledDB
import os
class DatabaseManager:
"""数据库连接池管理"""
def __init__(self):
self.pool = PooledDB(
creator=pymysql,
maxconnections=20,
mincached=5,
host=os.getenv('RDS_HOST'),
port=3306,
user=os.getenv('RDS_USER'),
password=os.getenv('RDS_PASSWORD'),
database='ai_chat',
charset='utf8mb4'
)
def get_connection(self):
return self.pool.connection()
def execute_query(self, sql, params=None):
"""执行查询"""
conn = self.get_connection()
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute(sql, params or ())
return cursor.fetchall()
finally:
conn.close()
def execute_update(self, sql, params=None):
"""执行更新"""
conn = self.get_connection()
try:
with conn.cursor() as cursor:
affected = cursor.execute(sql, params or ())
conn.commit()
return affected
finally:
conn.close()
# 使用示例
db = DatabaseManager()
# 创建表
CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS chat_sessions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(64) NOT NULL UNIQUE,
user_id VARCHAR(64) NOT NULL,
title VARCHAR(255),
model VARCHAR(32) DEFAULT 'qwen-turbo',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_session_id (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
# 创建消息表
CREATE_MESSAGES_SQL = """
CREATE TABLE IF NOT EXISTS chat_messages (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(64) NOT NULL,
role ENUM('user', 'assistant', 'system') NOT NULL,
content TEXT NOT NULL,
tokens INT DEFAULT 0,
model VARCHAR(32) DEFAULT 'qwen-turbo',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_id (session_id),
FOREIGN KEY (session_id) REFERENCES chat_sessions(session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
# 初始化表
db.execute_update(CREATE_TABLE_SQL)
db.execute_update(CREATE_MESSAGES_SQL)
# 插入会话
def create_session(user_id, title="新对话"):
sql = "INSERT INTO chat_sessions (session_id, user_id, title) VALUES (%s, %s, %s)"
import uuid
session_id = str(uuid.uuid4())
db.execute_update(sql, (session_id, user_id, title))
return session_id
# 插入消息
def save_message(session_id, role, content, tokens=0, model="qwen-turbo"):
sql = "INSERT INTO chat_messages (session_id, role, content, tokens, model) VALUES (%s, %s, %s, %s, %s)"
db.execute_update(sql, (session_id, role, content, tokens, model))
# 查询历史
def get_history(session_id, limit=20):
sql = """
SELECT role, content, created_at
FROM chat_messages
WHERE session_id = %s
ORDER BY created_at DESC
LIMIT %s
"""
return db.execute_query(sql, (session_id, limit))
3.2.3 云数据库 Redis
Redis 是高性能的内存数据库,常用于缓存、会话存储、消息队列等场景。
import redis
import json
from typing import Optional, List, Dict
class RedisCache:
"""Redis 缓存管理"""
def __init__(self):
self.client = redis.Redis(
host=os.getenv('REDIS_HOST'),
port=6379,
password=os.getenv('REDIS_PASSWORD'),
db=0,
decode_responses=True
)
# ===== 对话历史管理 =====
def save_conversation(self, session_id: str, messages: List[Dict],
ttl: int = 86400) -> bool:
"""保存对话历史(默认24小时过期)"""
key = f"chat:history:{session_id}"
self.client.setex(key, ttl, json.dumps(messages, ensure_ascii=False))
return True
def get_conversation(self, session_id: str) -> Optional[List[Dict]]:
"""获取对话历史"""
key = f"chat:history:{session_id}"
data = self.client.get(key)
if data:
return json.loads(data)
return None
def append_message(self, session_id: str, role: str, content: str) -> int:
"""追加单条消息"""
history = self.get_conversation(session_id) or []
history.append({"role": role, "content": content})
self.save_conversation(session_id, history)
return len(history)
# ===== Token 计数 =====
def count_tokens(self, session_id: str) -> int:
"""统计会话总 Token 数"""
history = self.get_conversation(session_id) or []
# 粗略估算:中文每个字算 1.5 token,英文每个词算 1.3 token
total = 0
for msg in history:
content = msg.get('content', '')
# 简化估算
total += len(content) // 2
return total
# ===== 速率限制 =====
def check_rate_limit(self, user_id: str, max_requests: int = 60,
window: int = 60) -> tuple[bool, int]:
"""
检查用户请求频率
返回: (是否允许, 剩余请求数)
"""
key = f"ratelimit:{user_id}"
# 使用滑动窗口
current = self.client.get(key)
if current is None:
pipe = self.client.pipeline()
pipe.setex(key, window, 1)
pipe.execute()
return True, max_requests - 1
count = int(current)
if count >= max_requests:
ttl = self.client.ttl(key)
return False, 0
pipe = self.client.pipeline()
pipe.incr(key)
pipe.expire(key, window)
pipe.execute()
return True, max_requests - count - 1
# 使用示例
cache = RedisCache()
# 保存对话
cache.save_conversation("session-123", [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮助你的吗?"}
])
# 检查限流
allowed, remaining = cache.check_rate_limit("user-456", max_requests=10)
if not allowed:
print("请求过于频繁,请稍后再试")
else:
print(f"请求通过,剩余 {remaining} 次")
3.3 日志与监控服务
3.3.1 日志服务 SLS
SLS(Simple Log Service) 是阿里云的海量日志采集、存储、查询服务,是 AI 应用可观测性的核心组件。
import logging
from aliyun.log import LogClient, PutLogsRequest, LogGroup, LogItem
import json
import os
class AliYunLogHandler(logging.Handler):
"""自定义日志处理器,将日志发送到 SLS"""
def __init__(self, endpoint, access_key_id, access_key_secret,
project, logstore):
super().__init__()
self.client = LogClient(endpoint, access_key_id, access_key_secret)
self.project = project
self.logstore = logstore
def emit(self, record):
try:
# 构造日志内容
logitem = LogItem()
logitem.set_time(int(record.created))
logitem.push_back('level', record.levelname)
logitem.push_back('message', record.getMessage())
logitem.push_back('logger', record.name)
logitem.push_back('module', record.module)
if record.exc_info:
logitem.push_back('exception', self.formatException(record.exc_info))
# 添加请求上下文(如果有)
if hasattr(record, 'request_id'):
logitem.push_back('request_id', str(record.request_id))
if hasattr(record, 'user_id'):
logitem.push_back('user_id', str(record.user_id))
# 发送日志
loggroup = LogGroup()
loggroup.logs.append(logitem)
request = PutLogsRequest(self.project, self.logstore, '', '', loggroup)
self.client.put_logs(request)
except Exception:
self.handleError(record)
# 配置日志
def setup_logging():
# 获取 SLS 配置
endpoint = os.getenv('SLS_ENDPOINT', 'cn-hangzhou.log.aliyuncs.com')
project = os.getenv('SLS_PROJECT', 'ai-chat-log')
logstore = os.getenv('SLS_LOGSTORE', 'app-logs')
# 创建 SLS handler
sls_handler = AliYunLogHandler(
endpoint=endpoint,
access_key_id=os.getenv('ACCESS_KEY_ID'),
access_key_secret=os.getenv('ACCESS_KEY_SECRET'),
project=project,
logstore=logstore
)
# 配置根日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
sls_handler
]
)
return logging.getLogger(__name__)
logger = setup_logging()
# 应用中使用
def chat_handler(request):
logger.info("收到对话请求", extra={
'request_id': request.id,
'user_id': request.user_id,
'model': request.model
})
try:
result = process_chat(request)
logger.info("对话请求成功", extra={
'request_id': request.id,
'tokens': result.tokens
})
return result
except Exception as e:
logger.error("对话请求失败", extra={
'request_id': request.id,
'error': str(e)
}, exc_info=True)
raise
3.3.2 应用实时监控 ARMS
ARMS(Application Real-Time Monitoring Service) 提供应用性能监控(APM)能力,帮助快速定位性能问题。
from arms import ArmsClient
import json
import os
class AIMonitor:
"""AI 应用监控"""
def __init__(self):
self.client = ArmsClient(
app_name='ai-chat-app',
endpoint='cn-hangzhou.console.arms.alibaba.com',
license_key=os.getenv('ARMS_LICENSE_KEY')
)
def track_request(self, request_id: str, duration: float,
status: int, model: str):
"""追踪 API 请求"""
self.client.track_request(
name='chat_completion',
duration=duration,
status=status,
tags={
'model': model,
'region': os.getenv('REGION', 'cn-hangzhou')
}
)
def track_llm_call(self, request_id: str, model: str,
prompt_tokens: int, completion_tokens: int,
latency: float):
"""追踪 LLM 调用"""
self.client.metrics().counter(
name='llm_tokens_total',
value=prompt_tokens + completion_tokens,
tags={
'model': model,
'type': 'prompt' if prompt_tokens > completion_tokens else 'completion'
}
)
self.client.metrics().histogram(
name='llm_latency_seconds',
value=latency,
tags={'model': model}
)
def track_error(self, error_type: str, error_message: str,
request_id: str = None):
"""追踪错误"""
self.client.errors().add(
error_type=error_type,
message=error_message,
request_id=request_id
)
# 中间件集成
class MonitorMiddleware:
"""Flask 监控中间件"""
def __init__(self, app, monitor: AIMonitor):
self.app = app
self.monitor = monitor
def __call__(self, environ, start_response):
import time
from werkzeug.wrappers import Request
request = Request(environ)
request_id = request.headers.get('X-Request-ID', 'unknown')
start_time = time.time()
# 包装响应
def custom_start_response(status, headers, exc_info=None):
duration = time.time() - start_time
self.monitor.track_request(
request_id=request_id,
duration=duration * 1000, # 转换为毫秒
status=200 if status.startswith('200') else 500,
model=request.args.get('model', 'qwen-turbo')
)
return start_response(status, headers, exc_info)
return self.app(environ, custom_start_response)
# 使用示例
monitor = AIMonitor()
monitor.track_llm_call(
request_id="req-123",
model="qwen-plus",
prompt_tokens=150,
completion_tokens=200,
latency=1.5
)
3.4 本章小结
本章介绍了三组重要的阿里云产品:
| 产品 | 核心价值 | 在 AI 应用中的角色 |
|---|---|---|
| ACK/ASK | 容器编排 | 运行 AI 微服务的 Kubernetes 平台 |
| RDS/Redis | 数据存储 | 持久化存储和缓存层 |
| SLS/ARMS | 可观测性 | 日志、监控、链路追踪 |
典型 AI 应用架构:
┌─────────────────────────────────────────────────────────────┐
│ 用户请求 │
├─────────────────────────────────────────────────────────────┤
│ API 网关 │
│ (鉴权、限流) │
├─────────────────────────────────────────────────────────────┤
│ 负载均衡 │
├─────────────────┬─────────────────┬───────────────────────┤
│ │ │ │
│ ACK 集群 │ 函数计算 │ 数据库集群 │
│ ┌───────────┐ │ │ ┌─────────┐ ┌──────┐ │
│ │ AI Chat │ │ 定时任务 │ │ RDS │ │Redis │ │
│ │ Service │ │ 批处理任务 │ │ (主库) │ │(缓存) │ │
│ └───────────┘ │ │ └─────────┘ └──────┘ │
│ ┌───────────┐ │ │ │
│ │ AI Model │ │ │ 日志/监控 │
│ │ Service │ │ │ ┌─────────────────┐ │
│ └───────────┘ │ │ │ SLS ARMS │ │
│ │ │ │ (日志) (监控) │ │
│ │ │ └─────────────────┘ │
└─────────────────┴─────────────────┴───────────────────────┘
│
┌─────────┴─────────┐
│ OSS │
│ (模型文件/数据) │
└─────────────────┘
思考与练习
- 架构设计:设计一个支持日活 10 万用户的 AI 对话应用,考虑容器部署、数据库选型、缓存策略
- 成本估算:计算以下架构的月成本:
- ACK:3 个 4 核 8G 节点
- RDS MySQL:2 核 4G 高可用版
- Redis:1GB 集群版
- SLS:每天 100GB 日志
- 监控告警:为 AI 对话 API 设计监控指标,包括:请求量、响应时间、错误率、Token 消耗,设置合理的告警阈值
本书第一部分小结
经过三章的学习,你已经掌握了:
- 云计算基础:理解 IaaS/PaaS/SaaS 的区别
- 核心产品:
- OSS:存储训练数据、模型文件
- 函数计算 FC:Serverless 执行 AI 推理
- API 网关:统一入口、安全防护
- ACK/ASK:容器化部署
- RDS/Redis:数据存储
- SLS/ARMS:可观测性
这些知识将为你后续学习大模型应用开发打下坚实的云端基础设施基础。
下一步:进入第二部分,学习大模型的技术原理和阿里云大模型产品。
第4章:大模型技术原理
本章将带你了解人工智能从机器学习到深度学习的发展历程,深入理解 Transformer 架构的核心原理,以及大语言模型(LLM)是如何“学会“理解和生成人类语言的。
4.1 从机器学习到深度学习
4.1.1 人工智能的发展脉络
人工智能(AI)的发展可以追溯到上世纪50年代。1956年的达特茅斯会议被视为人工智能作为独立学科的起点。此后,AI 经历了多次“热潮“与“寒冬“。
符号主义时代(1950s-1980s):研究者们试图用规则和逻辑让计算机“思考“。专家系统是这一时期的代表作——将人类专家的知识整理成规则库,让机器按规则推理。这种方法在特定领域效果不错,但难以泛化,且知识获取成本极高。
统计学习时代(1990s-2010s):随着互联网兴起和数据量爆炸,机器学习开始崭露头角。支持向量机(SVM)、决策树、随机森林等算法相继出现。2010年前后,ImageNet 大规模视觉识别挑战赛启动,深度学习开始爆发。
深度学习时代(2012-至今):2012年,AlexNet 在 ImageNet 挑战赛中以压倒性优势夺冠,卷积神经网络(CNN)一战成名。此后,深度学习在图像、语音、自然语言处理等领域不断突破,最终催生了 GPT、BERT 等大语言模型。
4.1.2 机器学习的基本范式
机器学习有三种基本范式:
监督学习(Supervised Learning):模型从标注数据中学习。输入数据有对应的正确输出(标签),模型通过比较预测结果与真实标签来调整参数。分类(如判断邮件是否为垃圾邮件)和回归(如预测房价)是典型任务。
无监督学习(Unsupervised Learning):模型在没有标签的数据中发现规律。聚类(如将用户分群)和降维(如 PCA)是常见任务。自编码器、生成对抗网络(GAN)也属于此类。
强化学习(Reinforcement Learning):智能体通过与环境交互,以最大化累积奖励为目标进行学习。AlphaGo 就是强化学习的杰作。RLHF(基于人类反馈的强化学习)正是训练 ChatGPT 的关键技术。
4.1.3 深度学习的革命性突破
深度学习的“深度“指的是神经网络的层数。传统机器学习模型通常只有1-3层,而深度学习模型可以有几十甚至上百层。
┌─────────────────────────────────────────────────────────┐
│ 深度神经网络 │
├─────────────────────────────────────────────────────────┤
│ 输入层 隐藏层1 隐藏层2 隐藏层3 输出层 │
│ ● ● ● ● ● │
│ ● → ● → ● → ● → ● │
│ ● ● ● ● ● │
│ ● ● ● ● ● │
│ ● ● ● ● ● │
│ 特征 抽象特征 更高层抽象 更高层抽象 预测 │
│ ▼ ▼ ▼ ▼ ▼ │
│ 词向量 加权组合 语义理解 深层语义 答案 │
└─────────────────────────────────────────────────────────┘
深度学习的三大核心组件:
-
嵌入层(Embedding Layer):将离散的高维数据(如单词、图片像素)映射到连续的稠密向量空间。这使得语义相似的对象在向量空间中距离更近。
-
注意力机制(Attention):让模型能够“关注“输入的不同部分。处理一句话时,模型可以同时关注所有词,而不是逐个处理。
-
残差连接(Residual Connection):允许梯度直接流过,缓解深层网络的梯度消失问题,使得训练更深的网络成为可能。
4.2 Transformer 架构详解
4.2.1 为什么需要 Transformer?
在 Transformer 出现之前,处理序列数据(如文本)主要依赖循环神经网络(RNN)及其变体(LSTM、GRU)。这些模型存在根本性问题:
- 顺序依赖:必须逐token处理,无法并行
- 长距离依赖困难:信息需要“记住“走过很长的路径才能传递
- 梯度消失/爆炸:深层 RNN 训练困难
2017年,Google 在论文《Attention Is All You Need》中提出了 Transformer,完全基于注意力机制,摒弃了循环结构。这些问题迎刃而解:
- 并行计算:所有token可以同时处理
- 直接建立长距离依赖:任意两个位置可以直接交互
- 更稳定训练:梯度传播更顺畅
4.2.2 Transformer 的整体架构
┌──────────────────────────────────────────────────────────────────┐
│ Transformer 编码器 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 输入: "今天天气真好" │
│ ↓ │
│ 词嵌入 + 位置编码 │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Multi-Head Self-Attention │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Head 1│ │Head 2│ │Head 3│ │Head 4│ 并行注意力头 │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Add & Layer Norm(残差 + 层归一化) │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Feed Forward │ │
│ │ 全连接前馈网络(两层线性变换) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Add & Layer Norm │
│ ↓ │
│ (可堆叠多层) │
│ │
└──────────────────────────────────────────────────────────────────┘
对于大语言模型(只有解码器),架构类似但有所简化。
4.2.3 核心组件详解
词嵌入与位置编码
词嵌入(Word Embedding):将每个词映射为一个固定长度的向量。“今天“可能变成 [0.23, -0.45, 0.78, ...],“天气“变成 [-0.12, 0.56, 0.34, ...]。
import torch
import torch.nn as nn
class TokenEmbedding(nn.Module):
"""词嵌入层:将token ID映射为稠密向量"""
def __init__(self, vocab_size, embedding_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
def forward(self, token_ids):
# token_ids: [batch_size, seq_len]
return self.embedding(token_ids) # [batch_size, seq_len, embedding_dim]
class PositionalEncoding(nn.Module):
"""位置编码:为序列中的每个位置添加位置信息"""
def __init__(self, d_model, max_len=5000):
super().__init__()
# 使用正弦和余弦函数编码位置
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # [1, max_len, d_model]
self.register_buffer('pe', pe)
def forward(self, x):
# x: [batch_size, seq_len, d_model]
return x + self.pe[:, :x.size(1), :]
位置编码(Positional Encoding):由于注意力机制本身不感知位置信息,需要显式添加位置信号。Transformer 使用正弦/余弦函数,让模型能够学习相对位置关系。
缩放点积注意力
注意力机制的核心是缩放点积注意力(Scaled Dot-Product Attention):
公式:Attention(Q, K, V) = softmax(QK^T / √d_k) V
其中:
- Q(Query):查询向量,"我在找什么"
- K(Key):键向量,"我包含什么信息"
- V(Value):值向量,"信息的实际内容"
- √d_k:缩放因子,防止点积过大导致梯度消失
import torch
import torch.nn.functional as F
import math
def scaled_dot_product_attention(Q, K, V, mask=None):
"""
缩放点积注意力
Q, K, V: [batch_size, num_heads, seq_len, d_k]
"""
d_k = Q.size(-1)
# 计算点积
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
# 应用掩码(如padding mask、causal mask)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# softmax归一化
attention_weights = F.softmax(scores, dim=-1)
# 加权求和
output = torch.matmul(attention_weights, V)
return output, attention_weights
多头注意力
多头注意力(Multi-Head Attention):将 Q、K、V 分别投影到多个子空间,并行计算注意力,最后合并。这让模型能够关注不同类型的关联:
class MultiHeadAttention(nn.Module):
"""多头注意力机制"""
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# Q, K, V 的线性投影
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def split_heads(self, x, batch_size):
"""将最后一个维度分割成 num_heads 个头"""
x = x.view(batch_size, -1, self.num_heads, self.d_k)
return x.transpose(1, 2) # [batch, num_heads, seq_len, d_k]
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# 线性投影 + 分头
Q = self.split_heads(self.W_q(Q), batch_size)
K = self.split_heads(self.W_k(K), batch_size)
V = self.split_heads(self.W_v(V), batch_size)
# 计算注意力
attn_output, attn_weights = scaled_dot_product_attention(Q, K, V, mask)
# 合并多头
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, -1, self.d_model)
# 最终线性投影
output = self.W_o(attn_output)
return output, attn_weights
前馈神经网络
每个 Transformer 层还包含一个前馈神经网络(FFN),对每个位置独立进行非线性变换:
class FeedForward(nn.Module):
"""前馈神经网络"""
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
return self.linear2(self.dropout(F.gelu(self.linear1(x))))
4.2.4 解码器与语言建模
大语言模型(如 GPT)主要使用 Transformer 解码器架构。与编码器的区别:
-
掩码注意力(Causal Mask):确保生成第 N 个token时,只能看到前 N-1 个token,不能“偷看“未来。
-
单向注意力:从左到右,逐步生成。
输入: "<BOS> 今 天 天 气"
掩码:
↓
今 天 天 气
■ ■ ■ ■ (已生成的位置)
■ ■ ■ ■
■ ■ ■
■ ■
■
(对未来位置的注意力设为 -∞)
- 下一个token预测:训练时,模型预测下一个token是什么。
class TransformerDecoderLayer(nn.Module):
"""Transformer 解码器层"""
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.cross_attn = MultiHeadAttention(d_model, num_heads) # 可选:编码器-解码器注意力
self.feed_forward = FeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, memory=None, tgt_mask=None):
# 自注意力(带因果掩码)
attn_output, _ = self.self_attn(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout(attn_output))
# 编码器-解码器注意力(如果有编码器输出)
if memory is not None:
attn_output, _ = self.cross_attn(x, memory, memory)
x = self.norm2(x + self.dropout(attn_output))
# 前馈网络
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout(ff_output))
return x
4.3 大语言模型的工作原理
4.3.1 什么是大语言模型?
大语言模型(Large Language Model, LLM)是一类参数规模巨大(通常数十亿到上千亿)的深度学习模型,专门用于处理和生成自然语言。
规模带来的涌现能力(Emergent Abilities):
- 当模型规模超过某个阈值时,会突然涌现出一些在小模型上没有的能力
- 如复杂推理、多语言理解、代码生成等
4.3.2 GPT 系列的技术演进
GPT-1(2018):1.17亿参数,首次证明预训练+微调范式有效
GPT-2(2019):15亿参数,提出“通用语言模型“概念
GPT-3(2020):1750亿参数,In-Context Learning(上下文学习)能力涌现,无需微调即可完成任务
GPT-3.5 / GPT-4(2023):引入RLHF(基于人类反馈的强化学习),大幅提升对齐能力和安全性
4.3.3 语言模型的核心能力
语言理解:理解文本的含义、情感、意图
语言生成:续写、扩写、改写、摘要
知识推理:基于已有知识进行逻辑推理、问答
代码能力:代码补全、代码解释、代码审查
工具使用:调用外部API、搜索、执行代码
4.3.4 训练过程
大语言模型的训练通常分为三个阶段:
┌─────────────────────────────────────────────────────────────────┐
│ 大语言模型训练流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ │
│ │ 阶段1: 预训练 │ 海量互联网文本(万亿tokens) │
│ │ Pretraining │ → 学习通用语言能力、知识、世界常识 │
│ └────────────────┘ │
│ ↓ │
│ ↓ 模型参数: 随机初始化 → 数十亿参数 │
│ ↓ │
│ ┌────────────────┐ │
│ │ 阶段2: 指令微调 │ 人工标注的指令数据 │
│ │ SFT │ → 学习遵循指令、回答问题 │
│ └────────────────┘ │
│ ↓ │
│ ↓ 模型参数: 预训练权重 → 微调 │
│ ↓ │
│ ┌────────────────┐ │
│ │ 阶段3: 对齐微调 │ 人类反馈(RLHF/DPO) │
│ │ Alignment │ → 更有帮助、更无害、更诚实 │
│ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
预训练(Pre-training):在大规模无标注文本上训练,通常是“下一个token预测“任务。数据来源包括网页、书籍、代码、论文等。
监督微调(Supervised Fine-Tuning, SFT):在人工标注的指令-响应对上微调,让模型学会遵循指令。
对齐微调(Alignment):使用RLHF(基于人类反馈的强化学习)或DPO(直接偏好优化),让模型的输出更符合人类偏好。
4.3.5 Tokenization:模型如何理解文本
模型无法直接处理原始文本,需要先将文本转换为数字。这个过程叫做分词(Tokenization)。
# 使用 transformers 库进行分词
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("qwen/Qwen2.5-7B")
text = "今天天气真好,适合出去散步!"
# 分词
tokens = tokenizer.tokenize(text)
print(f"原始文本: {text}")
print(f"分词结果: {tokens}")
# 转换为token IDs
token_ids = tokenizer.encode(text)
print(f"Token IDs: {token_ids}")
# 解码回来
decoded = tokenizer.decode(token_ids)
print(f"解码结果: {decoded}")
# 统计token数量
num_tokens = len(token_ids)
print(f"Token 数量: {num_tokens}")
# 输出示例:
# 原始文本: 今天天气真好,适合出去散步!
# 分词结果: ['今', '天', '天', '气', '真', '好', ',', '适', '合', '出', '去', '散', '步', '!']
# Token IDs: [514, 1824, 1824, 2175, 3932, 3121, 27, 6435, 3131, 1216, 2675, 6365, 1313, 31]
# 解码结果: 今天天气真好,适合出去散步!
# Token 数量: 14
不同的分词器效率不同:
- 中英文混合时,一个汉字通常对应1-2个token
- 英文单词可能被拆成多个子词(如“learning“ → “learn” + “ing”)
4.3.6 模型推理:如何生成文本
语言模型的推理是一个**自回归(Autoregressive)**过程:每次生成一个token,然后将新token加入输入,继续生成下一个。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
def generate_text(
model_name: str,
prompt: str,
max_new_tokens: int = 100,
temperature: float = 0.7,
top_p: float = 0.9
) -> str:
"""使用语言模型生成文本"""
# 加载模型和分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
# 输入编码
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
# 生成
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=temperature, # 控制随机性
top_p=top_p, # 核采样
do_sample=True, # 启用采样(否则用贪婪)
pad_token_id=tokenizer.pad_token_id
)
# 解码输出
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
return generated_text
# 使用示例
prompt = "用Python写一个快速排序算法:"
result = generate_text("qwen/Qwen2.5-7B", prompt)
print(result)
采样策略:
-
贪婪解码(Greedy Decoding):每个step选择概率最高的token。速度快,但容易陷入重复。
-
温度采样(Temperature Sampling):用温度参数调整概率分布。温度高→更随机;温度低→更确定性。
-
Top-K 采样:限制只在概率最高的K个token中采样。
-
Top-P(Nucleus)采样:限制在累积概率达到P的最小token集合中采样。通常效果更好。
4.4 主流大模型盘点
4.4.1 国际主流模型
| 模型 | 开发者 | 参数量 | 特点 |
|---|---|---|---|
| GPT-4o | OpenAI | ~1.8万亿 | 多模态,强推理,已开源语音 |
| Claude 3.5 | Anthropic | 未公开 | 长上下文,安全性强 |
| Gemini 1.5 | 未公开 | 超长上下文(100万token) | |
| Llama 3 | Meta | 8B/70B | 开源,社区活跃 |
| Mistral | Mistral AI | 7B/8x22B | 高效率,专家混合架构 |
4.4.2 国内主流模型
| 模型 | 开发者 | 特点 |
|---|---|---|
| 通义千问 Qwen | 阿里云 | 开源系列,中文优化 |
| 文心一言 | 百度 | 知识增强 |
| 智谱 GLM | 智谱AI | ChatGLM系列 |
| 讯飞星火 | 科大讯飞 | 语音交互强 |
| Kimi (Moonshot) | 月之暗面 | 超长上下文 |
| DeepSeek | 深度求索 | 高性价比,开源 |
4.4.3 模型选择指南
┌─────────────────────────────────────────────────────────────────┐
│ 模型选择决策树 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┐ │
│ │ 你的应用场景? │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ↓ ↓ ↓ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 通用对话 │ │ 专用任务 │ │ 本地部署 │ │
│ │ 智能客服 │ │ 代码生成 │ │ 私有化 │ │
│ └─────┬─────┘ │ 知识问答 │ └─────┬─────┘ │
│ ↓ └─────┬─────┘ ↓ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ API调用 │ │ Fine-tune │ │ 开源模型 │ │
│ │ Qwen/GLM │ │ Qwen/LLaMA│ │ LLaMA/Mistral│ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
本章小结
本章介绍了大语言模型的技术基础:
- AI发展脉络:从符号主义到统计学习,再到深度学习
- Transformer架构:自注意力机制、多头注意力、位置编码
- 大模型原理:预训练-微调范式、涌现能力、自回归生成
- 主流模型:国际与国内代表性模型及选型建议
理解这些基础原理,将帮助你更好地使用和优化大模型应用。下一章我们将具体介绍阿里云的大模型产品体系。
思考与练习
-
概念理解:Transformer 的注意力机制相比 RNN 有哪些优势?
-
原理分析:为什么大模型需要进行“对齐微调“(RLHF)?这解决了什么问题?
-
实践探索:使用 transformers 库加载一个小模型(如 GPT-2),观察不同采样策略(贪婪、温度、Top-P)对生成结果的影响。
-
扩展思考:大语言模型有哪些局限性?在实际应用中应该如何规避?
-
选型设计:为一个校园问答助手系统选择合适的大模型,说明理由。
第5章:阿里云大模型产品
本章详细介绍阿里云的大模型产品体系,包括 DashScope 灵积平台、通义千问 Qwen 系列、通义万相(图像生成)、通义听悟(语音处理)等产品,帮助你快速找到适合自己应用场景的AI能力。
5.1 阿里云大模型产品全景
5.1.1 产品体系架构
阿里云的大模型产品布局完整,覆盖从底层基础设施到上层应用的各个层面:
┌─────────────────────────────────────────────────────────────────┐
│ 阿里云大模型产品体系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 应用层产品 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │通义千问 │ │通义万相 │ │通义听悟 │ │通义灵码 │ │ │
│ │ │ Chat │ │ 图像生成 │ │ 语音处理 │ │ 代码助手 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 模型服务平台 │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ DashScope 灵积 │ │ │
│ │ │ • 模型API调用 • 模型服务 • 模型评测 • 模型管理 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 模型训练平台 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ PAI- │ │ PAI- │ │ PAI- │ │ 模型库 │ │ │
│ │ │ DSW │ │ EAS │ │ Autodl │ │ ModelScope│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.1.2 核心产品一览
| 产品 | 类型 | 说明 |
|---|---|---|
| DashScope 灵积 | 模型服务平台 | 统一的大模型 API 接入平台 |
| 通义千问 Qwen | 大语言模型 | 文本理解与生成 |
| 通义万相 | 多模态模型 | 图像生成、风格迁移 |
| 通义听悟 | 语音模型 | 语音转文字、会议纪要 |
| 通义灵码 | 代码模型 | 代码补全、代码审查 |
| PAI | 机器学习平台 | 模型训练与部署 |
| ModelScope | 模型社区 | 开源模型库 |
5.2 DashScope 灵积平台
5.2.1 平台概述
DashScope(灵积)是阿里云推出的大模型服务平台,提供统一的 API 接口,让你可以通过简单的 HTTP 调用访问多种大模型能力。
核心优势:
- 统一入口:一个 API 接入多种模型
- 按量付费:无需预付费,按实际调用量计费
- 弹性扩展:自动应对流量高峰
- 稳定可靠:企业级 SLA 保障
5.2.2 支持的模型类别
┌─────────────────────────────────────────────────────────────────┐
│ DashScope 支持的模型类别 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 📝 文本生成 │
│ ├── qwen-turbo (高速) │
│ ├── qwen-plus (标准) │
│ ├── qwen-max (高性能) │
│ └── qwen-max-longcontext (长上下文) │
│ │
│ 🎨 图像生成 │
│ ├── wanx2.1 (文生图) │
│ └── wanx-v1 (图像风格化) │
│ │
│ 🔊 语音识别 │
│ ├── paraformer-zh (中文) │
│ └── paraformer-en (英文) │
│ │
│ 🗣️ 语音合成 │
│ └── cosyvoice-v1 (自然语音) │
│ │
│ 🔍 向量嵌入 │
│ └── text-embedding-v3 (高精度向量) │
│ │
│ 📊 重排序 │
│ └── rerank-v3 (语义排序) │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2.3 API 申请与配置
Step 1:开通服务
- 登录阿里云控制台
- 搜索“DashScope“或访问 https://dashscope.console.aliyun.com
- 点击“开通服务“
- 完成授权
Step 2:获取 API Key
- 进入 DashScope 控制台
- 点击左侧“API-KEY管理“
- 创建新的 API-KEY
- 妥善保存生成的密钥
⚠️ 安全提示:API Key 等同于你的账号密码,请勿泄露或提交到公开代码仓库。
Step 3:配置环境变量
# macOS/Linux: 添加到 ~/.bashrc 或 ~/.zshrc
export DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"
# Windows: 在系统环境变量中添加
# DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
# 验证配置
echo $DASHSCOPE_API_KEY
Step 4:SDK 安装
# 使用 pip 安装
pip install dashscope
# 或使用阿里云 SDK
pip install alibaba-cloud-sdk-go-v3
5.3 通义千问 Qwen 系列
5.3.1 Qwen 模型家族
通义千问(Qwen) 是阿里云自研的大语言模型系列,参数规模从十几亿到上千亿不等:
| 模型 | 参数量 | 上下文 | 适用场景 |
|---|---|---|---|
| qwen-turbo | - | 8K | 快速响应、低延迟场景 |
| qwen-plus | - | 32K | 平衡性能与成本 |
| qwen-max | - | 8K | 高质量输出场景 |
| qwen-max-longcontext | - | 100K | 长文档分析 |
| qwen-vl-plus | - | - | 图文理解 |
| qwen-audio | - | - | 音频理解 |
5.3.2 模型特点
Qwen 的核心优势:
- 强大的中文能力:针对中文语境优化,中文任务表现优异
- 开源开放:Qwen 系列开源模型在 ModelScope 和 HuggingFace 可下载
- 长上下文:部分版本支持超长上下文窗口
- 工具调用:支持 Function Calling,便于 Agent 开发
- 代码能力:CodeQwen 在代码生成方面表现突出
5.3.3 API 调用示例
基础对话调用
import dashscope
from dashscope import Generation
# 设置 API Key
dashscope.api_key = "your-api-key"
def chat_with_qwen(prompt: str, model: str = "qwen-turbo") -> str:
"""与通义千问对话"""
response = Generation.call(
model=model,
messages=[
{"role": "system", "content": "你是一个有帮助的AI助手。"},
{"role": "user", "content": prompt}
],
temperature=0.7,
top_p=0.8,
result_format="message"
)
if response.status_code == 200:
return response.output.choices[0].message.content
else:
return f"请求失败: {response.code} - {response.message}"
# 使用示例
result = chat_with_qwen("用一句话解释什么是云计算")
print(result)
多轮对话
def multi_turn_chat(messages: list) -> str:
"""多轮对话"""
response = Generation.call(
model="qwen-plus",
messages=messages,
temperature=0.7,
result_format="message"
)
if response.status_code == 200:
return response.output.choices[0].message
else:
raise Exception(f"API调用失败: {response.message}")
# 构建多轮对话
conversation = [
{"role": "system", "content": "你是一个专业的Python教练。"},
{"role": "user", "content": "什么是装饰器?"},
]
# 第一轮对话
assistant_msg = multi_turn_chat(conversation)
print(f"助手: {assistant_msg.content}")
conversation.append(assistant_msg)
# 添加用户追问
conversation.append({
"role": "user",
"content": "能给我一个实际的使用例子吗?"
})
# 第二轮对话
assistant_msg = multi_turn_chat(conversation)
print(f"助手: {assistant_msg.content}")
流式输出
from dashscope import Generation
from dashscope.callback import CallbackIterator
def stream_chat(prompt: str):
"""流式对话,实时显示输出"""
responses = Generation.call(
model="qwen-turbo",
messages=[
{"role": "user", "content": prompt}
],
stream=True,
incremental_output=True
)
full_response = ""
for response in responses:
if response.status_code == 200:
content = response.output.choices[0].delta.content
print(content, end="", flush=True)
full_response += content
else:
print(f"\n错误: {response.message}")
break
print() # 换行
return full_response
# 使用示例
result = stream_chat("写一首关于春天的诗")
函数调用(Function Calling)
from dashscope import Generation
from openai import OpenAI
import json
# 定义可用函数
functions = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如:北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位"
}
},
"required": ["city"]
}
}
}
]
def get_weather(city: str, unit: str = "celsius") -> dict:
"""模拟天气查询API"""
# 实际项目中这里调用真实天气API
return {
"city": city,
"temperature": 22 if unit == "celsius" else 72,
"condition": "晴转多云",
"humidity": 65
}
def chat_with_function(prompt: str):
"""支持函数调用的对话"""
messages = [{"role": "user", "content": prompt}]
# 第一次调用:模型决定是否需要调用函数
response = Generation.call(
model="qwen-max",
messages=messages,
tools=functions,
temperature=0.7
)
if response.status_code == 200:
choice = response.output.choices[0]
# 如果模型决定调用函数
if hasattr(choice, 'finish_reason') and choice.finish_reason == 'tool_calls':
tool_calls = choice.message.tool_calls
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"调用函数: {func_name}")
print(f"参数: {func_args}")
# 执行函数
if func_name == "get_weather":
result = get_weather(**func_args)
print(f"函数返回: {result}")
# 添加函数结果到对话
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
# 第二次调用:基于函数结果生成回复
response = Generation.call(
model="qwen-max",
messages=messages,
tools=functions
)
print(f"最终回复: {response.output.choices[0].message.content}")
else:
print(f"回复: {choice.message.content}")
# 使用示例
chat_with_function("北京今天天气怎么样?")
5.4 通义万相:图像生成
5.4.1 产品概述
通义万相是阿里云的图像生成模型,支持文生图、图像风格化、图像修复等功能。
主要能力:
- 文本到图像:根据文字描述生成图片
- 风格迁移:将图片转换为不同艺术风格
- 相似图像生成:生成与参考图风格相似的作品
5.4.2 API 调用示例
from dashscope import ImageSynthesis
def text_to_image(prompt: str, model: str = "wanx2.1-pro") -> list:
"""
文本生成图像
prompt: 图像描述(中文效果更佳)
返回生成的图像URL列表
"""
response = ImageSynthesis.call(
model=model,
prompt=prompt,
n=1, # 生成数量
size="1024*1024" # 图像尺寸
)
if response.status_code == 200:
return response.output.results
else:
raise Exception(f"生成失败: {response.message}")
def generate_and_save(prompt: str, output_path: str = "generated_image.png"):
"""生成图像并保存"""
import urllib.request
import ssl
results = text_to_image(prompt)
# 下载并保存图像
for idx, result in enumerate(results):
url = result.url
print(f"图像 {idx+1} URL: {url}")
# 下载图像
ssl._create_default_https_context = ssl._create_unverified_context
urllib.request.urlretrieve(url, f"{output_path}")
print(f"已保存到: {output_path}")
return results
# 使用示例
results = generate_and_save(
"一只可爱的橘猫在阳光下打盹,写实风格,高清摄影"
)
5.4.3 提示词技巧
文生图模型对提示词非常敏感,以下是一些技巧:
# 好的提示词示例
good_prompts = [
# 明确主体
"一只穿着宇航服的柯基犬",
# 添加风格描述
"山水画风格的中国古典园林",
# 添加细节修饰
"4K高清, 电影级光效, 辛烷值渲染",
# 添加负面提示词(避免不良结果)
"纯色背景, 简单构图, 不要文字",
]
# 结构化提示词模板
def build_prompt(subject: str, style: str, quality: str = "4K高清") -> str:
"""构建高质量提示词"""
return f"{subject}, {style}, {quality}, 细节丰富, 光影效果好"
prompt = build_prompt(
subject="未来城市的空中花园",
style="赛博朋克风格",
quality="8K超清"
)
print(prompt) # 未来城市的空中花园, 赛博朋克风格, 8K超清, 细节丰富, 光影效果好
5.5 通义听悟:语音处理
5.5.1 产品概述
通义听悟是阿里云的语音处理产品,核心能力包括:
- 语音转文字(ASR):高准确率的中英文语音识别
- 会议纪要:自动生成会议摘要和关键要点
- 实时字幕:会议、直播实时字幕
- 音频分析:说话人分离、情感分析
5.5.2 API 调用示例
语音识别
from dashscope import Audio
def transcribe_audio(audio_path: str) -> str:
"""
语音转文字
支持格式: wav, mp3, m4a, ogg
"""
response = Audio.async_call(
model="paraformer-zh",
file_path=audio_path,
notification_queue_name="your-queue-name" # 需要先创建消息队列
)
if response.status_code == 200:
task_id = response.output.task_id
print(f"任务ID: {task_id}")
return task_id
else:
raise Exception(f"转写失败: {response.message}")
def get_transcription_result(task_id: str) -> dict:
"""查询转写结果"""
response = Audio.async_result(task_id)
if response.status_code == 200:
result = response.output
return {
"status": result.task_status,
"text": result.results.get("text", ""),
"sentences": result.results.get("sentences", [])
}
else:
raise Exception(f"查询失败: {response.message}")
# 使用示例
task_id = transcribe_audio("/path/to/recording.mp3")
# 轮询获取结果(实际项目中建议使用消息队列异步通知)
import time
for _ in range(30): # 最多等待5分钟
result = get_transcription_result(task_id)
if result["status"] == "SUCCEEDED":
print("转写结果:")
print(result["text"])
break
elif result["status"] == "FAILED":
print("转写失败")
break
else:
print(f"处理中... {result['status']}")
time.sleep(10)
处理长音频
def transcribe_long_audio(file_path: str, language_hints: str = "zh") -> str:
"""
转写长音频(自动分段处理)
"""
import os
# 验证文件存在
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
# 获取文件大小
file_size = os.path.getsize(file_path)
print(f"文件大小: {file_size / 1024 / 1024:.2f} MB")
# 长音频自动分段
response = Audio.async_call(
model="paraformer-zh",
file_path=file_path,
language_hints=language_hints,
speaker_number=2, # 假设有2个说话人
hot_word_ids=["阿里云", "DashScope"], # 热词,提升专有名词识别率
time_out=300 # 5分钟超时
)
return response.output.task_id
# 使用示例
task_id = transcribe_long_audio("/path/to/meeting.mp3")
5.6 其他模型服务
5.6.1 向量嵌入模型
from dashscope import TextEmbedding
def get_embedding(text: str, model: str = "text-embedding-v3") -> list:
"""获取文本的向量表示"""
response = TextEmbedding.call(
model=model,
input=text
)
if response.status_code == 200:
return response.output.embeddings[0].embedding
else:
raise Exception(f"嵌入失败: {response.message}")
def compute_similarity(text1: str, text2: str) -> float:
"""计算两个文本的相似度"""
import numpy as np
emb1 = np.array(get_embedding(text1))
emb2 = np.array(get_embedding(text2))
# 余弦相似度
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
return similarity
# 使用示例
sim = compute_similarity(
"人工智能将改变未来的工作方式",
"AI技术会深刻影响未来的职业发展"
)
print(f"相似度: {sim:.4f}") # 约0.85-0.95
5.6.2 重排序模型
from dashscope import Rerank
def semantic_search(query: str, documents: list) -> list:
"""
语义重排序
query: 查询文本
documents: 文档列表
返回按相关性排序的文档
"""
response = Rerank.call(
model="gte-rerank",
query=query,
documents=documents,
top_n=len(documents)
)
if response.status_code == 200:
results = response.output.results
return [
{"index": r.index, "document": documents[r.index], "score": r.relevance_score}
for r in results
]
else:
raise Exception(f"重排失败: {response.message}")
# 使用示例
docs = [
"云计算是一种通过网络提供计算资源的服务模式",
"人工智能是研究如何让计算机模拟人类智能的学科",
"机器学习是人工智能的一个分支,让计算机从数据中学习"
]
results = semantic_search("什么是机器学习?", docs)
for r in results:
print(f"[{r['score']:.4f}] {r['document']}")
5.7 阿里云百炼平台
5.7.1 平台概述
阿里云百炼(原DashScope企业版)是面向企业客户的大模型服务平台,提供更丰富的企业级功能:
- 模型训练:私有化模型定制
- 模型部署:一键部署专属模型服务
- 知识库:企业知识管理
- Agent:智能体编排
- 企业级安全:数据隔离、权限管理
5.7.2 与 DashScope 的区别
| 功能 | DashScope | 百炼 |
|---|---|---|
| 目标用户 | 开发者/个人 | 企业 |
| 定价 | 按量付费 | 包年包月 |
| 数据安全 | 标准安全 | 企业级安全 |
| 私有化部署 | ❌ | ✅ |
| SLA保障 | 99.5% | 99.9% |
| 专属支持 | ❌ | ✅ |
本章小结
本章介绍了阿里云的大模型产品体系:
- DashScope 灵积:统一的大模型 API 服务平台
- 通义千问 Qwen:覆盖多种规格的文本生成模型
- 通义万相:文本到图像的生成能力
- 通义听悟:语音识别与处理能力
- 向量嵌入与重排序:支撑 RAG 应用的核心能力
下一章我们将深入学习 DashScope API 的使用,掌握快速构建 AI 应用的能力。
思考与练习
-
产品选型:为一个校园助手应用选择合适的阿里云产品组合,说明理由。
-
成本优化:对比 qwen-turbo、qwen-plus、qwen-max 的定价特点,讨论在什么场景下应该选择哪个模型。
-
实践操作:在阿里云控制台开通 DashScope 服务,编写一个简单的对话程序。
-
扩展探索:调研 DashScope 还支持哪些模型(如通义万相的详细功能)。
-
架构设计:设计一个基于 DashScope API 的客服系统架构图。
第6章:DashScope API 快速入门
本章是 DashScope API 的实战指南,从环境准备到第一个 AI 程序,再到错误处理和调试技巧,带你快速上手阿里云的大模型 API 服务。
6.1 环境准备
6.1.1 Python 环境要求
DashScope SDK 需要以下环境:
- Python 3.8 或更高版本
- 推荐使用虚拟环境管理依赖
# 检查 Python 版本
python --version
# Python 3.10.12
# 创建虚拟环境(推荐)
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# 升级 pip
pip install --upgrade pip
6.1.2 安装 DashScope SDK
# 安装最新版 DashScope SDK
pip install dashscope
# 如果需要流式输出支持
pip install dashscope[sseclient]
# 验证安装
python -c "import dashscope; print(dashscope.__version__)"
6.1.3 环境变量配置
# 方式一:环境变量(推荐)
export DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxx"
# 方式二:代码中设置
import dashscope
dashscope.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
# 方式三:配置文件
# ~/.dashscope/config.json
{
"api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
}
💡 最佳实践:将 API Key 存储在环境变量中,避免硬编码在代码里。
6.1.4 IDE 配置
如果你使用 VS Code 或 PyCharm,确保:
- 选择了正确的 Python 解释器(虚拟环境)
- 安装了 Python 扩展
- 配置了代码补全和类型提示
// VS Code: .vscode/settings.json
{
"python.defaultInterpreterPath": "./venv/bin/python",
"python.linting.enabled": true,
"python.formatting.provider": "black"
}
6.2 第一个 AI 程序
6.2.1 最简示例
让我们从一个最简单的例子开始:
#!/usr/bin/env python3
"""
dashscope-quickstart.py
DashScope API 快速入门示例
"""
from dashscope import Generation
def simple_chat():
"""最简单的对话示例"""
response = Generation.call(
model="qwen-turbo",
prompt="你好,请用一句话介绍一下阿里云。"
)
if response.status_code == 200:
print(response.output.text)
else:
print(f"请求失败: {response.code} - {response.message}")
if __name__ == "__main__":
simple_chat()
运行:
python dashscope-quickstart.py
输出:
阿里云(Alibaba Cloud)是阿里巴巴集团旗下的云计算品牌,为全球用户提供计算、数据库、存储、网络等基础设施服务以及大数据、人工智能等解决方案。
6.2.2 使用消息格式
更规范的对话使用消息(Messages)格式:
from dashscope import Generation
from dashscope.common import Message
def chat_with_messages():
"""使用标准消息格式的对话"""
messages = [
Message(
role="system",
content="你是一个专业的Python讲师,用简洁易懂的语言解释概念。"
),
Message(
role="user",
content="什么是装饰器?"
)
]
response = Generation.call(
model="qwen-turbo",
messages=messages,
temperature=0.7,
top_p=0.8,
result_format="message" # 返回格式
)
if response.status_code == 200:
assistant_message = response.output.choices[0].message
print(f"角色: {assistant_message.role}")
print(f"回复: {assistant_message.content}")
else:
print(f"错误: {response.message}")
if __name__ == "__main__":
chat_with_messages()
6.2.3 完整项目结构
一个规范的 DashScope 项目通常有如下结构:
my-ai-app/
├── config.py # 配置管理
├── api_client.py # API 调用封装
├── prompts.py # Prompt 模板
├── main.py # 程序入口
├── requirements.txt # 依赖列表
└── tests/ # 测试目录
└── test_api.py
config.py:配置管理
"""
配置管理:集中管理 API 配置和参数
"""
import os
from dataclasses import dataclass
from typing import Optional
@dataclass
class ModelConfig:
"""模型配置"""
name: str
temperature: float = 0.7
top_p: float = 0.8
max_tokens: int = 2000
presence_penalty: float = 0.0
frequency_penalty: float = 0.0
@dataclass
class AppConfig:
"""应用配置"""
api_key: str
default_model: str = "qwen-turbo"
timeout: int = 60
max_retries: int = 3
@classmethod
def from_env(cls):
"""从环境变量加载配置"""
api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
raise ValueError("未设置 DASHSCOPE_API_KEY 环境变量")
return cls(api_key=api_key)
# 预定义的模型配置
MODELS = {
"fast": ModelConfig("qwen-turbo", temperature=0.7),
"balanced": ModelConfig("qwen-plus", temperature=0.7),
"quality": ModelConfig("qwen-max", temperature=0.5),
}
# 全局配置实例
config = AppConfig.from_env()
api_client.py:API 调用封装
"""
DashScope API 客户端封装
"""
import dashscope
from dashscope import Generation
from dashscope.common import Message
from typing import List, Dict, Optional, Union
import time
class DashScopeClient:
"""DashScope API 客户端封装类"""
def __init__(self, api_key: str, default_model: str = "qwen-turbo"):
dashscope.api_key = api_key
self.default_model = default_model
def chat(
self,
prompt: str,
system_prompt: Optional[str] = None,
model: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 2000,
**kwargs
) -> Dict:
"""
对话生成
Args:
prompt: 用户输入
system_prompt: 系统提示
model: 模型名称,默认使用配置
temperature: 随机性控制
max_tokens: 最大生成token数
**kwargs: 其他参数
Returns:
Dict: 包含 status、message、usage 等信息
"""
# 构建消息列表
messages = []
if system_prompt:
messages.append(Message(role="system", content=system_prompt))
messages.append(Message(role="user", content=prompt))
# 调用 API
response = Generation.call(
model=model or self.default_model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
result_format="message",
**kwargs
)
return self._parse_response(response)
def chat_stream(
self,
prompt: str,
system_prompt: Optional[str] = None,
model: Optional[str] = None,
**kwargs
):
"""
流式对话生成
Yields:
str: 生成的文本片段
"""
messages = []
if system_prompt:
messages.append(Message(role="system", content=system_prompt))
messages.append(Message(role="user", content=prompt))
response = Generation.call(
model=model or self.default_model,
messages=messages,
stream=True,
incremental_output=True,
result_format="message",
**kwargs
)
for resp in response:
if resp.status_code == 200:
content = resp.output.choices[0].delta.content
if content:
yield content
else:
raise Exception(f"流式输出错误: {resp.message}")
def chat_with_retry(
self,
prompt: str,
max_retries: int = 3,
retry_delay: float = 1.0,
**kwargs
) -> Dict:
"""
带重试的对话
Args:
prompt: 用户输入
max_retries: 最大重试次数
retry_delay: 重试间隔(秒)
**kwargs: 传递给 chat 的其他参数
"""
for attempt in range(max_retries):
try:
result = self.chat(prompt, **kwargs)
if result["status"] == "success":
return result
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"请求失败,{retry_delay}秒后重试... ({attempt + 1}/{max_retries})")
time.sleep(retry_delay)
raise Exception("达到最大重试次数")
def _parse_response(self, response) -> Dict:
"""解析 API 响应"""
if response.status_code == 200:
choice = response.output.choices[0].message
usage = getattr(response.output, 'usage', None)
return {
"status": "success",
"message": choice.content,
"model": response.request.model,
"usage": {
"input_tokens": getattr(usage, 'input_tokens', 0),
"output_tokens": getattr(usage, 'output_tokens', 0),
"total_tokens": getattr(usage, 'total_tokens', 0),
} if usage else None,
"request_id": response.request_id
}
else:
return {
"status": "error",
"error_code": response.code,
"error_message": response.message,
"request_id": response.request_id
}
# 便捷函数
def get_client() -> DashScopeClient:
"""获取客户端实例(从环境变量)"""
from config import config
return DashScopeClient(
api_key=config.api_key,
default_model=config.default_model
)
main.py:程序入口
#!/usr/bin/env python3
"""
AI 助手主程序
"""
from api_client import get_client
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
console = Console()
def main():
"""主程序"""
console.print(Panel.fit(
"[bold cyan]AI 助手[/bold cyan] - 基于 DashScope API\n"
"输入问题,AI 将为你解答\n"
"输入 [bold]quit[/bold] 或 [bold]exit[/bold] 退出"
))
client = get_client()
while True:
try:
user_input = console.input("\n[bold green]你:[/bold green] ")
if user_input.lower() in ["quit", "exit", "q"]:
console.print("[yellow]再见![/yellow]")
break
if not user_input.strip():
continue
console.print("[bold blue]AI:[/bold blue] ", end="")
# 流式输出
response_text = ""
for chunk in client.chat_stream(user_input):
print(chunk, end="", flush=True)
response_text += chunk
# 打印使用信息
print("\n")
except KeyboardInterrupt:
console.print("\n[yellow]已退出[/yellow]")
break
except Exception as e:
console.print(f"\n[bold red]错误:[/bold red] {e}")
if __name__ == "__main__":
main()
requirements.txt:
dashscope>=1.14.0
rich>=13.0.0
python-dotenv>=1.0.0
6.3 常见参数详解
6.3.1 生成参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model | string | 必填 | 模型名称 |
prompt | string | 必填 | 输入提示(单轮) |
messages | list | - | 对话消息列表 |
temperature | float | 0.7 | 随机性(0-1) |
top_p | float | 0.8 | 核采样 |
top_k | int | - | Top-K 采样 |
max_tokens | int | - | 最大生成token数 |
stream | bool | False | 是否流式输出 |
result_format | string | “message” | 输出格式 |
6.3.2 temperature 与 top_p
def demonstrate_temperature():
"""
演示 temperature 对输出的影响
"""
client = get_client()
prompt = "给我5个科幻小说标题"
print("=== Temperature = 0.0(最确定)===")
response = client.chat(prompt, model="qwen-turbo", temperature=0.0)
print(response["message"])
print("\n=== Temperature = 0.7(平衡)===")
response = client.chat(prompt, model="qwen-turbo", temperature=0.7)
print(response["message"])
print("\n=== Temperature = 1.5(高随机性)===")
response = client.chat(prompt, model="qwen-turbo", temperature=1.5)
print(response["message"])
def demonstrate_top_p():
"""
演示 top_p 对输出的影响
top_p 越小,选择范围越窄,越确定
"""
client = get_client()
prompt = "用一句话描述人工智能"
print("=== Top-P = 0.1(保守)===")
response = client.chat(prompt, top_p=0.1)
print(response["message"])
print("\n=== Top-P = 0.9(多样)===")
response = client.chat(prompt, top_p=0.9)
print(response["message"])
6.3.3 控制生成长度
def control_output_length():
"""
演示如何控制生成长度
"""
client = get_client()
prompt = "介绍一下云计算"
print("=== 短回答 (max_tokens=50) ===")
response = client.chat(prompt, max_tokens=50)
print(response["message"])
print(f"实际使用 tokens: {response['usage']['output_tokens']}")
print("\n=== 中等回答 (max_tokens=200) ===")
response = client.chat(prompt, max_tokens=200)
print(response["message"])
print(f"实际使用 tokens: {response['usage']['output_tokens']}")
6.4 错误处理与调试
6.4.1 常见错误码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
InvalidApiKey | API Key 无效 | 检查 Key 是否正确 |
QuotaExceeded | 配额用尽 | 购买更多配额或等待重置 |
RateLimitExceeded | 请求过于频繁 | 添加重试逻辑或降低QPS |
ParameterRequired | 缺少必需参数 | 检查 API 文档 |
ModelNotSupported | 模型不支持 | 使用支持的模型 |
InvalidParameter | 参数值无效 | 检查参数范围 |
ServerError | 服务器错误 | 重试或联系支持 |
6.4.2 错误处理模板
from dashscope import Generation
from dashscope.common.error import (
AuthenticationError,
InvalidParameter,
RequestFailedException
)
from typing import Optional
class DashScopeError(Exception):
"""DashScope 异常基类"""
def __init__(self, code: str, message: str):
self.code = code
self.message = message
super().__init__(f"[{code}] {message}")
def robust_chat(prompt: str) -> Optional[str]:
"""
带完整错误处理的对话函数
"""
try:
response = Generation.call(
model="qwen-turbo",
messages=[{"role": "user", "content": prompt}],
result_format="message"
)
if response.status_code == 200:
return response.output.choices[0].message.content
else:
# 根据错误码处理
error_map = {
"InvalidApiKey": "API Key 无效,请检查配置",
"QuotaExceeded": "配额已用尽,请购买更多配额",
"RateLimitExceeded": "请求过于频繁,请稍后重试",
"ModelNotSupported": "该模型不可用",
}
message = error_map.get(
response.code,
f"请求失败: {response.code} - {response.message}"
)
print(f"❌ {message}")
return None
except AuthenticationError as e:
print(f"❌ 认证失败: {e}")
return None
except InvalidParameter as e:
print(f"❌ 参数错误: {e}")
return None
except RequestFailedException as e:
print(f"❌ 请求失败 ({e.status_code}): {e.message}")
if e.status_code == 429:
print(" 建议:添加重试延迟或降低调用频率")
return None
except Exception as e:
print(f"❌ 未知错误: {e}")
return None
def chat_with_retry(
prompt: str,
max_retries: int = 3,
initial_delay: float = 1.0
) -> Optional[str]:
"""
带指数退避重试的对话
"""
import time
for attempt in range(max_retries):
result = robust_chat(prompt)
if result is not None:
return result
if attempt < max_retries - 1:
delay = initial_delay * (2 ** attempt) # 指数退避
print(f"⏳ {delay}秒后重试... ({attempt + 1}/{max_retries})")
time.sleep(delay)
print("❌ 达到最大重试次数")
return None
6.4.3 调试技巧
启用详细日志:
import logging
# 启用请求日志
logging.basicConfig(level=logging.DEBUG)
dashscope.logger.setLevel(logging.DEBUG)
# 自定义日志格式
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
打印完整响应:
def debug_response():
"""打印 API 完整响应,用于调试"""
response = Generation.call(
model="qwen-turbo",
prompt="你好",
result_format="message"
)
print("=== 响应对象 ===")
print(f"状态码: {response.status_code}")
print(f"请求ID: {response.request_id}")
print(f"模型: {response.request.model}")
print(f"Code: {response.code}")
print(f"Message: {response.message}")
print(f"Output: {response.output}")
网络调试:
import requests
# 使用 requests 直接调用,便于调试
def raw_api_call(prompt: str):
"""直接使用 requests 调用 API"""
import os
url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
headers = {
"Authorization": f"Bearer {os.getenv('DASHSCOPE_API_KEY')}",
"Content-Type": "application/json"
}
payload = {
"model": "qwen-turbo",
"input": {
"prompt": prompt
},
"parameters": {
"temperature": 0.7
}
}
response = requests.post(url, json=payload, headers=headers)
print(f"HTTP Status: {response.status_code}")
print(f"Response: {response.json()}")
6.5 实际应用示例
6.5.1 文本摘要
from api_client import get_client
def summarize_text(text: str, max_length: int = 100) -> str:
"""
文本摘要
"""
client = get_client()
prompt = f"""请帮我将以下文章压缩成一段不超过{max_length}字的中文摘要:
{text}
摘要:"""
result = client.chat(prompt)
return result["message"]
# 使用示例
article = """
2024年人工智能领域取得了重大突破。OpenAI发布了新一代GPT模型,在多项基准测试中创下新纪录。
与此同时,开源社区也迎来了Llama 3和Qwen 2等重量级模型。阿里云通义千问系列持续迭代,
最新版本在中文理解和代码生成方面表现突出。多模态成为趋势,GPT-4V、Gemini Pro等视觉语言模型
相继问世,推动了AI应用的新一轮创新。
"""
summary = summarize_text(article)
print(f"原文长度: {len(article)} 字")
print(f"摘要: {summary}")
6.5.2 翻译助手
from api_client import get_client
from typing import Literal
def translate(
text: str,
target_lang: Literal["中文", "英文", "日文", "韩文", "法文", "德文"],
source_lang: str = "自动检测"
) -> str:
"""
多语言翻译
"""
client = get_client()
if source_lang == "自动检测":
prompt = f"""请将以下内容翻译成{target_lang}:
{text}
只输出翻译结果,不要解释。"""
else:
prompt = f"""请将以下{source_lang}内容翻译成{target_lang}:
{text}
只输出翻译结果,不要解释。"""
result = client.chat(prompt)
return result["message"]
# 使用示例
english_text = "Artificial Intelligence is transforming the world."
chinese = translate(english_text, "中文")
print(f"英文: {english_text}")
print(f"中文: {chinese}")
6.5.3 代码审查
from api_client import get_client
def review_code(code: str, language: str = "Python") -> str:
"""
代码审查与建议
"""
client = get_client()
prompt = f"""请审查以下{language}代码,找出潜在问题并给出改进建议:
```{language.lower()}
{code}
请从以下角度审查:
- 正确性:逻辑错误、边界条件
- 安全性:潜在的安全漏洞
- 性能:效率问题
- 可维护性:代码风格、可读性
- 最佳实践:是否符合语言规范
以结构化的方式输出审查结果。“”“
result = client.chat(prompt)
return result["message"]
使用示例
code = “”“ def calculate_average(numbers): total = sum(numbers) return total / len(numbers) “”“
review = review_code(code) print(review)
### 6.5.4 校园助手
```python
from api_client import get_client
class CampusAssistant:
"""校园问答助手"""
SYSTEM_PROMPT = """你是一个热心的校园助手,名为"小智"。
你的职责是帮助学生解决校园生活中的各种问题。
你可以帮助的领域包括:
- 教务信息:选课、转专业、考试安排等
- 生活服务:食堂、宿舍、校园卡等
- 校园设施:图书馆、体育馆、自习室等
- 学生活动:社团、讲座、比赛等
- 技术问题:校园网、VPN、邮箱等
请用友好、耐心、专业的态度回答问题。
如果不确定答案,请诚实地说明并建议学生咨询相关部门。"""
def __init__(self):
self.client = get_client()
self.conversation_history = []
def ask(self, question: str) -> str:
"""提问"""
messages = [{"role": "system", "content": self.SYSTEM_PROMPT}]
messages.extend(self.conversation_history)
messages.append({"role": "user", "content": question})
response = self.client.chat(
prompt="", # 空prompt,因为我们用messages
system_prompt=self.SYSTEM_PROMPT,
)
# 这里简化处理,实际应该用messages参数
result = self.client.chat(
f"{self.SYSTEM_PROMPT}\n\n用户问题:{question}"
)
answer = result["message"]
# 保存对话历史
self.conversation_history.append({"role": "user", "content": question})
self.conversation_history.append({"role": "assistant", "content": answer})
return answer
def reset(self):
"""重置对话历史"""
self.conversation_history = []
# 使用示例
assistant = CampusAssistant()
print("=== 校园助手 ===")
questions = [
"图书馆开放时间是几点?",
"如何申请转专业?",
"校园无线网络怎么连接?"
]
for q in questions:
print(f"\n学生: {q}")
answer = assistant.ask(q)
print(f"助手: {answer}")
6.6 成本优化建议
6.6.1 选择合适的模型
def model_selection_guide():
"""
模型选择指南
"""
scenarios = [
{
"场景": "简单问答、客服机器人",
"推荐模型": "qwen-turbo",
"理由": "速度快、成本低、效果好"
},
{
"场景": "内容创作、复杂推理",
"推荐模型": "qwen-plus",
"理由": "质量更高、上下文更长"
},
{
"场景": "高精度要求的任务",
"推荐模型": "qwen-max",
"理由": "最高质量"
}
]
print("模型选择指南:")
for s in scenarios:
print(f" {s['场景']} → {s['推荐模型']} ({s['理由']})")
6.6.2 减少 token 消耗
def optimize_prompts():
"""Prompt 优化技巧"""
# ❌ 冗余的提示词
bad_prompt = """
请你作为一个专业的人工智能助手,仔细地、认真地、详细地阅读下面的问题,
然后用你的专业知识给出准确、全面、有条理的回答。
问题:什么是Python?
请回答:
"""
# ✅ 简洁的提示词
good_prompt = "什么是Python?请用一句话解释。"
# 结论:去掉冗余表述,节省 token
print(f"冗余提示词 token 数: ~{len(bad_prompt)}")
print(f"简洁提示词 token 数: ~{len(good_prompt)}")
print("节省约 70% 的输入 token")
6.6.3 缓存与批处理
import hashlib
from functools import lru_cache
class CachedClient:
"""带缓存的 API 客户端"""
def __init__(self):
from api_client import get_client
self.client = get_client()
self.cache = {}
self.cache_hits = 0
def chat_with_cache(self, prompt: str, ttl: int = 3600) -> str:
"""
带缓存的对话,相同问题直接返回缓存结果
ttl: 缓存有效期(秒)
"""
# 生成缓存 key
cache_key = hashlib.md5(prompt.encode()).hexdigest()
import time
current_time = time.time()
if cache_key in self.cache:
cached_time, cached_result = self.cache[cache_key]
if current_time - cached_time < ttl:
self.cache_hits += 1
print(f"🔍 缓存命中 ({self.cache_hits})")
return cached_result
# 调用 API
result = self.client.chat(prompt)
self.cache[cache_key] = (current_time, result)
return result
def batch_chat(self, prompts: list) -> list:
"""批量处理(实际由业务层实现)"""
results = []
for prompt in prompts:
results.append(self.chat_with_cache(prompt))
return results
本章小结
本章是 DashScope API 的实战指南:
- 环境准备:Python 环境、SDK 安装、API Key 配置
- 快速上手:最简单的对话程序到完整的项目结构
- 参数详解:temperature、top_p、max_tokens 等核心参数
- 错误处理:常见错误码、错误处理模板、重试策略
- 实战示例:文本摘要、翻译、代码审查、校园助手
- 成本优化:模型选择、Prompt 优化、缓存策略
掌握这些内容,你已经具备了使用 DashScope API 构建 AI 应用的基础能力。下一章我们将学习 Prompt 工程,掌握与 AI 高效沟通的核心技能。
思考与练习
-
环境搭建:完成 DashScope 开发环境搭建,运行第一个 AI 程序。
-
参数实验:使用不同的 temperature、top_p 参数,观察输出变化,总结规律。
-
错误处理:模拟 API 调用失败场景,验证你的错误处理代码是否正常工作。
-
项目实践:基于本章的代码框架,开发一个命令行翻译工具,支持中英互译。
-
性能优化:为你的应用添加缓存功能,对比优化前后的 API 调用次数和响应时间。
-
扩展探索:阅读 DashScope 官方文档,了解更多高级功能(如向量嵌入、图像生成等)。
第7章:Prompt 工程基础
Prompt(提示词)是大模型理解用户意图的桥梁。好的 Prompt 可以让模型准确理解需求,输出高质量的结果;糟糕的 Prompt 则可能导致答非所问。本章从零讲解 Prompt 工程的核心概念和基础技巧。
7.1 什么是 Prompt?
7.1.1 重新认识 Prompt
Prompt(提示词) 是用户与大模型交互时输入的文本内容。它告诉模型:
- 任务是什么:让模型完成什么任务
- 背景信息:完成任务需要知道什么
- 输出要求:结果应该是什么格式、什么风格
┌─────────────────────────────────────────────────────────────────┐
│ Prompt = 任务指令 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户 ──► [任务描述 + 背景信息 + 输出要求] ──► 大模型 ──► 输出 │
│ │
│ Prompt = 让模型理解"做什么"+"怎么做"的全部信息 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.1.2 Prompt 的重要性
“The way you phrase the problem determines how AI solves it.”
同样的问题,不同的 Prompt 可能导致截然不同的结果:
| Prompt 版本 | 输出质量 |
|---|---|
| ❌ “写一首诗” | 可能是一首平庸的通用诗 |
| ✅ “写一首关于秋天的七言绝句,要求意境深远、押韵工整” | 更符合期望的高质量诗作 |
Prompt 工程的价值:
- 提升准确性:让模型准确理解任务意图
- 控制输出格式:获得结构化的、可程序化的结果
- 降低成本:简洁准确的 Prompt 减少 token 消耗
- 增强稳定性:减少随机性带来的不确定性
7.1.3 Prompt 的基本结构
一个完整的 Prompt 通常包含以下部分:
┌─────────────────────────────────────────────────────────────────┐
│ Prompt 基本结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 角色定义 │ 你是一个资深Python工程师,擅长写出优雅的代码 │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ 任务描述 │ 请帮我审查以下代码 │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ 背景信息 │ 代码用于处理用户登录逻辑 │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ 示例输出 │ 你的输出应该包含:1) 问题列表 2) 建议 3) 代码 │
│ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ 约束条件 │ 不要修改原有逻辑,只提出优化建议 │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
7.2 Prompt 的核心要素
7.2.1 角色定义(Role)
给模型设定一个角色,可以显著提升输出质量:
# ❌ 没有角色定义
prompt = "如何提高代码质量?"
# ✅ 有角色定义
prompt = "作为一位有10年经验的高级架构师,请分享提高代码质量的最佳实践。"
角色定义的作用:
- 限定回答的专业角度
- 激活模型的“专业知识“
- 调整输出的语气和风格
# 不同角色的输出风格对比
prompts = {
"技术面试官": "你是一位技术面试官,面试一位应聘高级Python开发的候选人。请提出3个关于并发编程的问题。",
"技术讲师": "你是一位Python讲师,用通俗易懂的方式解释什么是并发编程,并给出代码示例。",
"代码审查员": "你是一位严格的代码审查员,请指出以下代码中可能存在的并发问题..."
}
for role, prompt in prompts.items():
print(f"\n=== {role} ===")
print(f"Prompt: {prompt[:30]}...")
7.2.2 任务描述(Task)
清晰、具体地描述你想要完成的任务:
# ❌ 模糊的任务描述
prompt = "帮我写代码"
# ✅ 清晰的任务描述
prompt = """
请帮我写一个Python函数,实现以下功能:
1. 输入:用户ID(字符串)
2. 处理:从数据库查询该用户的最近10条订单
3. 输出:JSON格式的订单列表
4. 错误处理:如果用户不存在,返回空列表
"""
# ✅ 更具体的任务描述
prompt = """
请写一个装饰器函数 @retry(max_attempts=3, delay=1),实现以下功能:
- 被装饰的函数执行失败时,自动重试
- 最多重试 max_attempts 次
- 每次重试间隔 delay 秒
- 记录每次失败的错误信息
- 重试失败后抛出原始异常
"""
任务描述的最佳实践:
- 使用动词开头:明确表达需要的动作
- 分点列举:复杂任务拆分成多个子任务
- 包含输入输出:说明期望的数据格式
7.2.3 背景信息(Context)
提供必要的上下文,帮助模型理解任务的特殊要求:
# ❌ 缺少背景信息
prompt = "这段代码有什么问题?"
# ✅ 提供背景信息
prompt = """
我们的系统是一个电商订单处理模块,每秒处理约1000个订单。
当前使用同步处理方式,偶尔会出现超时。
这是订单创建的代码:
def create_order(order_data):
# 创建订单
order = Order.objects.create(**order_data)
# 发送通知邮件
send_email(order.customer_email)
# 更新库存
update_inventory(order.items)
return order
请问这段代码有什么性能和可靠性问题?请给出优化建议。
"""
7.2.4 输出格式(Format)
明确指定期望的输出格式:
# 指定JSON格式
prompt_json = """
将以下产品信息转换为JSON格式:
产品名:iPhone 15 Pro
价格:7999元
颜色:深空黑
存储:256GB
要求:
- JSON格式规范
- 使用中文key
- 包含所有属性
"""
# 指定表格格式
prompt_table = """
请以表格形式列出Python中的5种数据结构,包括:
| 数据结构 | 创建方式 | 特点 | 适用场景 |
"""
# 指定列表格式
prompt_list = """
请列出安装Python的5种方法,每种方法用一行描述。
格式:[方法名] - [简要说明]
"""
# 指定步骤格式
prompt_steps = """
解释HTTP请求的工作流程。
请按以下格式回答:
第一步:...
第二步:...
第三步:...
"""
7.3 Zero-shot 与 Few-shot
7.3.1 Zero-shot Prompting
Zero-shot(零样本)是指不给模型任何示例,直接描述任务:
# Zero-shot 示例
zero_shot_prompts = [
# 分类
"将以下评论分类为正面或负面:'这个产品太棒了,完全超出预期!'",
# 翻译
"把以下中文翻译成英文:今天天气真好",
# 摘要
"用一句话概括:人工智能技术正在快速发展,...",
# 问答
"问:什么是云计算?答:",
]
def test_zero_shot(prompt):
"""测试零样本学习"""
response = client.chat(prompt)
return response["message"]
Zero-shot 的适用场景:
- 简单、明确的任务
- 模型已经具备相关知识
- 需要快速得到答案
7.3.2 Few-shot Prompting
Few-shot(少样本)是指在 Prompt 中提供几个示例,帮助模型理解任务:
# Few-shot 示例
few_shot_prompt = """
请根据评论内容,判断用户的情感是正面还是负面。
示例:
评论:"这家餐厅的服务太差了,等了1小时才上菜" → 负面
评论:"电影很精彩,笑中带泪,推荐观看" → 正面
请判断:
评论:"这个课程讲解清晰,干货满满" →
"""
# 多示例 Few-shot
multi_shot_prompt = """
将中文成语翻译成英文:
画蛇添足 → to add unnecessary details
掩耳盗铃 → to deceive oneself
守株待兔 → to wait for gains without pains
请翻译:
亡羊补牢 →
"""
Few-shot 的优势:
- 提升准确性:示例帮助模型理解具体要求
- 控制输出格式:示例本身就是格式说明
- 处理边缘情况:通过示例明确复杂规则
# Few-shot vs Zero-shot 对比
def compare_shot_approaches():
"""对比零样本和少样本的效果"""
# 零样本
zero_shot = """
判断以下文本的情感是正面还是负面:
"物流很快,但产品有瑕疵"
"""
# 少样本
few_shot = """
判断以下文本的情感是正面、负面还是中性:
"服务超级好,下次还会来" → 正面
"等了两周才收到,太慢了" → 负面
"产品还可以,就是包装有点简陋" → 中性
"物流很快,但产品有瑕疵" →
"""
print("Zero-shot 结果可能:负面(只关注问题)")
print("Few-shot 结果更可能是:中性(两个观点各占一半)")
7.3.3 如何选择
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 简单分类/翻译 | Zero-shot | 模型本身能力足够强 |
| 格式复杂多变 | Few-shot | 示例直接说明格式要求 |
| 领域特殊术语 | Few-shot | 示例帮助理解术语含义 |
| 边界情况处理 | Few-shot | 示例说明边界判断标准 |
7.4 结构化 Prompt 模板
7.4.1 为什么需要结构化模板?
在实际应用中,我们通常需要反复使用相同类型的 Prompt。结构化模板可以让 Prompt 更易维护、更易复用:
# ❌ 散乱的 Prompt
bad_prompts = [
"你是一个Python专家,请解释什么是装饰器",
"作为资深Python开发者,请告诉我装饰器的工作原理",
"我是Python初学者,请用简单的语言介绍装饰器",
]
# ✅ 结构化模板
class PromptTemplate:
"""Prompt 模板类"""
def __init__(self, template: str):
self.template = template
def format(self, **kwargs) -> str:
"""格式化模板"""
return self.template.format(**kwargs)
# 定义模板
code_explainer = PromptTemplate("""
你是一位专业的{language}开发者。
请用{level}的语言解释以下{concept}概念:
{code}
要求:
- 解释清晰易懂
- 包含实际应用场景
- 适当使用比喻
""")
# 使用模板
prompt = code_explainer.format(
language="Python",
level="入门级",
concept="装饰器",
code="""
def timer(func):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
print(f"执行时间: {time.time() - start}s")
return result
return wrapper
"""
)
7.4.2 Jinja2 模板引擎
更复杂的模板可以使用 Jinja2:
from jinja2 import Template
# 定义 Prompt 模板
CODE_REVIEW_TEMPLATE = """
{%- macro review_item(issue, severity, suggestion) -%}
【{{ severity }}】{{ issue }}
建议:{{ suggestion }}
{%- endmacro %}
## 代码审查报告
代码来源:{{ file_path }}
审查时间:{{ review_time }}
{% if issues %}
发现 {{ issues|length }} 个问题:
{% for issue in issues %}
### 问题 {{ loop.index }}
{{ review_item(issue.description, issue.severity, issue.suggestion) }}
```{{ issue.language }}
{{ issue.code_snippet }}
{% endfor %} {% else %} ✅ 未发现问题 {% endif %}
审查人:{{ reviewer }} “”“
def generate_review_prompt(issues: list) -> str: “”“生成代码审查 Prompt”“” template = Template(CODE_REVIEW_TEMPLATE) return template.render( file_path=“src/main.py”, review_time=“2024-01-15”, issues=issues, reviewer=“AI代码审查助手” )
使用示例
issues = [ { “description”: “未进行输入验证”, “severity”: “高危”, “suggestion”: “添加参数校验,处理空值和异常格式”, “language”: “python”, “code_snippet”: “user_id = request.params[‘user_id’]” } ]
prompt = generate_review_prompt(issues) print(prompt)
### 7.4.3 常用 Prompt 模板库
```python
# 常用模板集合
PROMPT_TEMPLATES = {
"summarize": {
"description": "文本摘要",
"template": """
请将以下文本压缩成{length}字以内的摘要:
{text}
摘要应该:
- 保留核心信息
- 语言简洁流畅
- 不包含原始文本中没有的信息
"""
},
"translate": {
"description": "翻译助手",
"template": """
请将以下{source_lang}文本翻译成{target_lang}:
{text}
翻译要求:
- 准确传达原文含义
- 符合目标语言习惯
- 适当本地化(如有必要)
"""
},
"classify": {
"description": "文本分类",
"template": """
请将以下文本分类到 {categories} 中:
文本:{text}
请只输出分类标签,不需要解释。
"""
},
"qa": {
"description": "问答助手",
"template": """
基于以下背景信息回答问题:
【背景】
{context}
【问题】
{question}
回答要求:
- 基于背景信息
- 如信息不足,请明确说明
- 回答简洁明了
"""
},
"rewrite": {
"description": "文本改写",
"template": """
请将以下文本改写成{style}风格:
原文:
{text}
改写要求:
- 保持原意
- 风格:{style}
- 长度:约{length}
"""
}
}
def use_template(name: str, **kwargs) -> str:
"""使用模板生成 Prompt"""
template = PROMPT_TEMPLATES.get(name)
if not template:
raise ValueError(f"未知模板: {name}")
return template["template"].format(**kwargs)
# 使用示例
prompt = use_template(
"summarize",
length="100",
text="人工智能技术正在快速发展..."
)
7.5 Prompt 编写最佳实践
7.5.1 清晰具体的指令
# ❌ 模糊的指令
bad = "介绍一下Python"
# ✅ 清晰的指令
good = """
请介绍Python编程语言,包括:
1. Python的设计理念和特点
2. Python的主要应用领域(至少5个)
3. Python与其他语言相比的优势
4. 适合学习Python的人群
请用通俗易懂的语言,适合编程初学者阅读。
"""
# ✅ 更进一步的清晰指令
better = """
## 任务
写一篇面向编程初学者的Python介绍文章。
## 要求
- 字数:800-1000字
- 语言:通俗易懂,避免过多专业术语
- 结构:包含引言、主体(3-4个要点)、总结
## 必须包含的内容
1. Python是什么
2. Python能做什么(举3个实际例子)
3. 为什么选择Python
4. 如何开始学习Python
## 禁止出现的内容
- 不要使用过多技术术语(必须解释的除外)
- 不要写代码示例(这是入门介绍)
- 不要超过1000字
"""
7.5.2 分解复杂任务
# ❌ 一次性要求所有内容
complex_prompt = """
请写一份完整的产品分析报告,包括:
1. 市场分析
2. 竞品分析
3. 用户调研
4. 产品功能规划
5. 商业模式
6. 财务预测
7. 风险评估
"""
# ✅ 分解为多个步骤
def multi_step_analysis(product):
"""分步骤产品分析"""
prompts = [
{
"step": 1,
"task": "市场分析",
"prompt": f"""
请分析"{product}"所在市场的以下方面:
1. 市场规模和增长率
2. 市场趋势
3. 主要玩家
输出格式:
## 市场规模
...
## 市场趋势
...
## 主要玩家
...
"""
},
{
"step": 2,
"task": "竞品分析",
"prompt": """
基于上一部分的市场分析,
请分析主要竞品的:
1. 核心功能
2. 差异化优势
3. 定价策略
输出格式:
## 竞品A
...
## 竞品B
...
"""
},
{
"step": 3,
"task": "SWOT分析",
"prompt": """
基于前面的市场分析和竞品分析,
请对"{product}"进行SWOT分析。
输出格式:
## 优势 (S)
## 劣势 (W)
## 机会 (O)
## 威胁 (T)
"""
}
]
results = []
for p in prompts:
result = client.chat(p["prompt"])
results.append({"step": p["step"], "task": p["task"], "content": result})
return results
7.5.3 使用分隔符组织结构
# 使用 Markdown 分隔符
prompt_with_delimiters = """
请分析以下代码并进行代码审查。
---
## 代码
```python
def calculate(a, b):
return a + b
审查要求
- 检查代码正确性
- 检查代码风格
- 检查性能问题
- 提出改进建议
输出格式
请使用以下格式输出审查结果:
✅ 正确性
…
⚠️ 代码风格
…
⚡ 性能
…
💡 改进建议
… “”“
### 7.5.4 控制输出长度
```python
# ❌ 不限制长度
no_limit = "介绍一下人工智能"
# ✅ 明确长度要求
with_limit = """
请用一句话(不超过50字)解释什么是机器学习。
"""
# ✅ 更精确的长度控制
precise_limit = """
请用恰好3个句子介绍Python语言的特点。
每句不超过25个字。
"""
7.6 常见场景 Prompt 示例
7.6.1 文本处理类
# 文本摘要
SUMMARIZE_PROMPT = """
请为以下文章写一个摘要。
要求:
- 摘要长度:{max_length}字
- 必须包含文章的3个核心要点
- 使用客观中性的语言
- 不添加原文没有的信息
文章:
{content}
"""
# 关键词提取
KEYWORDS_PROMPT = """
从以下文本中提取5-10个关键词:
{text}
要求:
- 按重要性排序
- 包含技术术语
- 用逗号分隔
"""
# 情感分析
SENTIMENT_PROMPT = """
分析以下评论的情感倾向:
{review}
请从以下选项中选择一个:
A. 非常正面
B. 正面
C. 中性
D. 负面
E. 非常负面
同时给出0-100的情感强度评分。
"""
7.6.2 内容创作类
# 博客文章
BLOG_POST_PROMPT = """
请撰写一篇关于"{topic}"的博客文章。
## 要求
- 标题:吸引人、有SEO友好
- 受众:{audience}
- 风格:{style}
- 字数:{word_count}字
- 结构:引言、3-5个要点、总结
## 必须包含
1. 实用性内容,读者可以直接应用
2. 至少2个真实案例
3. 3个可操作的建议
## 禁止出现
- 空话套话
- 与主题无关的内容
- 超过字数限制
"""
# 产品描述
PRODUCT_DESC_PROMPT = """
请为以下产品写一段产品描述:
产品名:{name}
特点:{features}
目标用户:{target}
要求:
- 长度:100-150字
- 突出核心卖点
- 引起目标用户共鸣
- 使用有感染力的语言
"""
7.6.3 代码相关类
# 代码解释
CODE_EXPLAIN_PROMPT = """
请解释以下代码的工作原理:
```{language}
{code}
请用通俗的语言解释:
- 这段代码在做什么
- 主要的逻辑流程
- 关键的函数或变量
适合阅读对象:{level} “”“
代码审查
CODE_REVIEW_PROMPT = “”“ 请审查以下代码,找出潜在问题:
文件:{file_path}
{code}
审查维度:
- 正确性:逻辑错误、边界条件
- 安全性:SQL注入、XSS等安全漏洞
- 性能:效率问题、资源浪费
- 可维护性:代码风格、可读性
- 最佳实践:是否符合语言规范
输出格式:
问题列表
| 严重程度 | 问题描述 | 位置 | 建议 |
|---|---|---|---|
| “”“ |
### 7.6.4 教学辅导类
```python
# 概念解释
CONCEPT_EXPLAIN_PROMPT = """
请解释"{concept}"这个概念。
目标受众:{audience}
详细程度:{detail_level}
请包含:
1. 一句话定义
2. 核心要点(3-5个)
3. 实际应用场景(2-3个)
4. 相关概念对比(如有)
5. 学习建议
"""
# 练习题生成
EXERCISE_PROMPT = """
请为"{topic}"生成{count}道练习题。
难度:{difficulty}
题型:{question_types}
要求:
- 每道题都要有明确的考察点
- 提供参考答案和评分标准
- 题目之间难度要有梯度
"""
# 学习计划
LEARNING_PLAN_PROMPT = """
请为"{skill}"技能制定一个学习计划。
学习时长:{duration}
当前水平:{current_level}
学习目标:{goal}
请包含:
1. 学习阶段划分(每个阶段的目标)
2. 每日/每周学习内容
3. 练习项目建议
4. 里程碑和验收标准
5. 学习资源推荐
"""
7.7 Prompt 调试与优化
7.7.1 Prompt 迭代流程
┌─────────────────────────────────────────────────────────────────┐
│ Prompt 迭代优化流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 明确目标 ──► 2. 编写初版 ──► 3. 测试运行 │
│ ↑ ↓ │
│ │ ↓ │
│ │ 4. 评估输出 │
│ │ ↓ │
│ │ 5. 问题诊断 ──► 6. 优化修改 │
│ │ ↑ ↓ │
│ └───────────────────────────── │
│ │
└─────────────────────────────────────────────────────────────────┘
class PromptOptimizer:
"""Prompt 优化器"""
def __init__(self, client):
self.client = client
self.history = []
def evaluate(self, prompt: str, test_cases: list) -> dict:
"""
评估 Prompt 质量
Args:
prompt: 待评估的 Prompt
test_cases: 测试用例列表
Returns:
评估报告
"""
results = []
for case in test_cases:
# 填充模板
filled_prompt = prompt.format(**case["input"])
# 执行
response = self.client.chat(filled_prompt)
# 评估
evaluation = self._evaluate_response(
response["message"],
case["expected"]
)
results.append({
"input": case["input"],
"output": response["message"],
"expected": case["expected"],
"evaluation": evaluation
})
return self._generate_report(results)
def _evaluate_response(self, actual: str, expected: str) -> dict:
"""评估单次响应"""
# 简化的评估逻辑
return {
"accuracy": 0.85 if len(actual) > 50 else 0.6,
"format_correct": "JSON" in expected or actual.startswith("{"),
"length_appropriate": 50 < len(actual) < 500
}
def _generate_report(self, results: list) -> dict:
"""生成评估报告"""
total = len(results)
passed = sum(1 for r in results if r["evaluation"]["accuracy"] > 0.7)
return {
"total_tests": total,
"passed": passed,
"pass_rate": passed / total,
"results": results,
"suggestions": self._generate_suggestions(results)
}
7.7.2 常见问题与解决方案
# 问题1:输出格式不稳定
# 原因:没有明确指定格式
# 解决:提供具体格式要求
FIX_FORMAT_ISSUE = """
## 问题
模型输出的JSON格式不稳定,有时多空格,有时缺少引号。
## 解决方案
1. 提供完整的JSON示例
2. 明确每个字段的数据类型
3. 使用XML或Markdown代码块包裹
示例 Prompt:
请将以下信息转换为JSON格式:
{"name": "张三", "age": 25, "city": "北京"}
必须符合以下JSON Schema:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"]
}
"""
# 问题2:输出过长
# 原因:没有限制长度
# 解决:明确字数或条数限制
FIX_LENGTH_ISSUE = """
## 问题
模型输出过于冗长,包含太多解释。
## 解决方案
1. 明确限制输出长度
2. 指定只输出核心内容
3. 禁止添加额外解释
示例 Prompt:
请列出Python的5个主要应用领域。
要求:
- 每条不超过20个字
- 只列出领域名称
- 不要解释
"""
本章小结
本章介绍了 Prompt 工程的基础知识:
- Prompt 的本质:任务指令 + 背景信息 + 输出要求
- 核心要素:角色定义、任务描述、背景信息、输出格式
- Zero-shot vs Few-shot:何时使用示例
- 结构化模板:提高复用性和可维护性
- 最佳实践:清晰具体、分解任务、控制长度
- 调试优化:迭代改进 Prompt 质量
掌握这些基础,你已经能够编写出基本合格的 Prompt。下一章我们将学习高级 Prompt 技巧,进一步提升与大模型交互的效率和质量。
思考与练习
-
概念理解:对比 Zero-shot 和 Few-shot 的适用场景,举例说明。
-
实践练习:为以下场景编写 Prompt:
- 邮件撰写(回复客户投诉)
- 数据分析报告生成
- 技术文档翻译
-
模板设计:设计一个通用的“代码解释“Prompt 模板,支持不同编程语言和难度级别。
-
迭代优化:选择一个现有的 Prompt,通过添加角色、示例、格式要求等方式进行优化,对比优化前后的效果。
-
工具实践:使用 LangChain 或类似框架,创建一个 Prompt 模板管理系统。
-
思考题:Prompt 工程与大模型能力的关系是什么?模型能力提升后,Prompt 工程是否还重要?
第8章:高级 Prompt 技巧
在掌握基础之上,本章介绍高级 Prompt 技巧,包括 Chain of Thought 思维链、Tree of Thought 树状思考、ReAct 推理框架、系统级 Prompt 设计等,帮助你充分发挥大模型的推理能力。
8.1 Chain of Thought(思维链)
8.1.1 什么是思维链?
Chain of Thought(CoT) 是一种引导大模型逐步推理的技术。通过让模型展示思考过程,不仅能得到更准确的答案,还能处理复杂的多步骤问题。
┌─────────────────────────────────────────────────────────────────┐
│ 思维链 vs 直接回答 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 直接回答: │
│ 问题:30元买了5支笔,每支多少钱? │
│ 答案:6元 │
│ │
│ ───────────────────────────────────────── │
│ │
│ 思维链: │
│ 问题:30元买了5支笔,每支多少钱? │
│ 思考: │
│ 1. 总价 = 30元 │
│ 2. 数量 = 5支 │
│ 3. 单价 = 总价 ÷ 数量 = 30 ÷ 5 = 6元 │
│ 答案:6元 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.1.2 Zero-shot CoT
最简单的 CoT 只需要在问题后加一句“Let’s think step by step“:
# Zero-shot CoT 示例
zero_shot_cot = """
问题:小明有15个苹果,给了小红5个,又买了8个,现在有多少个苹果?
Let's think step by step.
"""
def solve_with_zero_shot_cot(problem: str) -> str:
"""使用零样本思维链解决问题"""
prompt = f"""
{problem}
请一步一步地思考,然后给出答案。
"""
return client.chat(prompt)["message"]
# 测试
problem = """
商店里有200件商品,第一天卖出了总数的1/4,
第二天卖出了剩余的1/3,
第三天又进货了50件,
现在商店里有多少件商品?
"""
result = solve_with_zero_shot_cot(problem)
print(result)
8.1.3 Few-shot CoT
提供完整的推理示例,让模型学习推理模式:
# Few-shot CoT 示例
few_shot_cot = """
请按照以下示例的格式,逐步推理并给出答案。
示例1:
问题:小明有10元,买了3本练习本,每本2元,还剩多少钱?
推理:
- 练习本总价 = 3 × 2 = 6元
- 剩余金额 = 10 - 6 = 4元
答案:还剩4元
示例2:
问题:一个长方形长8厘米,宽5厘米,面积是多少?
推理:
- 长方形面积 = 长 × 宽
- 面积 = 8 × 5 = 40平方厘米
答案:40平方厘米
请解答:
问题:一辆汽车以60公里/小时的速度行驶了2.5小时,行驶了多少公里?
"""
def solve_with_few_shot_cot(problem: str, examples: list) -> str:
"""使用少样本思维链解决问题"""
prompt = "请按照以下示例的格式,逐步推理并给出答案。\n\n"
for i, ex in enumerate(examples, 1):
prompt += f"示例{i}:\n"
prompt += f"问题:{ex['question']}\n"
prompt += f"推理:\n{ex['reasoning']}\n"
prompt += f"答案:{ex['answer']}\n\n"
prompt += f"请解答:\n问题:{problem}\n"
return client.chat(prompt)["message"]
8.1.4 CoT 的适用场景
| 场景 | 效果 | 示例 |
|---|---|---|
| 数学计算 | ✅ 显著提升 | 应用题、几何计算 |
| 逻辑推理 | ✅ 显著提升 | 推理题、证明题 |
| 代码调试 | ✅ 显著提升 | Bug分析、修复建议 |
| 复杂决策 | ✅ 有提升 | 方案对比、风险评估 |
| 简单问答 | ❌ 无明显提升 | 事实查询、定义解释 |
| 创意写作 | ❌ 无需使用 | 写诗、写故事 |
# 复杂推理示例
complex_reasoning_prompt = """
请逐步分析以下业务场景:
场景:某电商平台在"双11"期间推出促销活动
- 原价100元的商品打8折
- 再满100减20
- 使用10元红包
- 需要支付8元运费
问题:购买这件商品最终需要支付多少钱?
请按以下步骤分析:
1. 计算折后价
2. 计算满减后的价格
3. 计算红包抵扣后的价格
4. 计算最终支付金额(包含运费)
最后给出详细的计算过程和结果。
"""
def business_calculation(prompt: str) -> str:
"""复杂商业计算"""
return client.chat(prompt)["message"]
8.2 Tree of Thought(思维树)
8.2.1 什么是思维树?
Tree of Thought(ToT) 是思维链的扩展,它不是线性推理,而是探索多条可能的思路,然后选择最佳路径:
┌─────────────────────────────────────────────────────────────────┐
│ Tree of Thought 示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ 问题起始 │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ↓ ↓ ↓ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 思路A │ │ 思路B │ │ 思路C │ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ │ │ │
│ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │
│ ↓ ↓ ↓ ↓ ↓ ↓ │
│ 继续A1 回退A2 继续B1 回退B2 继续C1 回退C2 │
│ ↓ │
│ ┌───────┴───────┐ │
│ │ 评估:思路A最优 │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
8.2.2 ToT 实现示例
from typing import List, Dict, Callable
from dataclasses import dataclass, field
@dataclass
class Thought:
"""思维节点"""
content: str
parent: 'Thought' = None
children: List['Thought'] = field(default_factory=list)
score: float = 0.0
depth: int = 0
class TreeOfThoughts:
"""思维树实现"""
def __init__(self, client, max_depth: int = 3, num_branches: int = 2):
self.client = client
self.max_depth = max_depth
self.num_branches = num_branches
def expand_node(self, thought: Thought) -> List[Thought]:
"""扩展一个思维节点"""
prompt = f"""
当前思路:{thought.content}
请提出{self.num_branches}个不同的后续思路方向。
每个方向都要有独特的推理路径。
格式:
思路1:[描述]
思路2:[描述]
"""
response = self.client.chat(prompt)
# 解析响应,生成新的思维节点
branches = self._parse_branches(response["message"])
new_thoughts = []
for branch in branches:
new_thought = Thought(
content=branch,
parent=thought,
depth=thought.depth + 1
)
thought.children.append(new_thought)
new_thoughts.append(new_thought)
return new_thoughts
def evaluate_node(self, thought: Thought) -> float:
"""评估一个思维节点的质量"""
prompt = f"""
评估以下思路的质量(0-10分):
思路:{thought.content}
评估标准:
1. 逻辑正确性(0-3分)
2. 完整性(0-3分)
3. 创新性(0-2分)
4. 可行性(0-2分)
只输出分数,如"8"。
"""
response = self.client.chat(prompt)
# 提取分数
try:
score = float(response["message"].strip())
except:
score = 5.0
thought.score = score
return score
def solve(self, problem: str) -> Dict:
"""使用思维树解决问题"""
# 创建根节点
root = Thought(content=f"问题:{problem}")
# 广度优先搜索
frontier = [root]
best_solution = None
best_score = 0
while frontier:
# 扩展当前层的所有节点
new_frontier = []
for thought in frontier:
if thought.depth >= self.max_depth:
# 评估叶节点
self.evaluate_node(thought)
if thought.score > best_score:
best_score = thought.score
best_solution = thought
continue
# 扩展节点
children = self.expand_node(thought)
new_frontier.extend(children)
frontier = new_frontier
# 回溯找到最佳路径
path = []
current = best_solution
while current:
path.append(current.content)
current = current.parent
path.reverse()
return {
"solution": best_solution.content if best_solution else None,
"score": best_score,
"path": path,
"tree": root
}
def _parse_branches(self, text: str) -> List[str]:
"""解析分支"""
lines = text.strip().split('\n')
branches = []
for line in lines:
if '思路' in line or '方向' in line:
# 提取思路内容
content = line.split(':')[-1].strip()
if content:
branches.append(content)
return branches[:self.num_branches]
8.2.3 ToT 应用示例
def use_tot_for_decision():
"""使用思维树做商业决策"""
tot = TreeOfThoughts(client, max_depth=3, num_branches=2)
problem = """
一家创业公司面临产品方向选择:
A. 开发企业级SaaS产品,客单价高但获客难
B. 开发ToC产品,用户量大但变现难
C. 开发工具类产品,稳定但增长慢
请使用思维树方法分析这三种选择,
考虑市场、竞争、团队、资源等因素,
给出最终建议。
"""
result = tot.solve(problem)
print(f"最佳方案得分:{result['score']}/10")
print("\n推理路径:")
for i, step in enumerate(result['path'], 1):
print(f"{i}. {step}")
return result
# 运行
result = use_tot_for_decision()
8.3 ReAct 框架
8.3.1 什么是 ReAct?
ReAct = Reasoning + Acting,即“推理+行动“。它让模型交替进行推理和行动,适用于需要与外部环境交互的任务。
┌─────────────────────────────────────────────────────────────────┐
│ ReAct 循环 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ 推理 │ │
│ │ (Reasoning) │ │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 行动 │ │
│ │ (Acting) │──────► 外部工具/API │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 观察 │ │
│ │ (Observing) │◄────── 工具返回结果 │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 继续推理? │ │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────┐ │
│ ↓ ↓ │
│ Yes No │
│ ↓ ↓ │
│ 继续 结束 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.3.2 ReAct 实现
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
from enum import Enum
class ActionType(Enum):
"""可用的行动类型"""
SEARCH = "search"
CALCULATOR = "calculator"
LOOKUP = "lookup"
FINISH = "finish"
@dataclass
class Action:
"""行动"""
tool: ActionType
params: Dict[str, Any]
thought: str # 推理过程
@dataclass
class Step:
"""推理步骤"""
step_num: int
thought: str
action: Action
observation: str
answer: Optional[str] = None
class ReActAgent:
"""ReAct 推理代理"""
def __init__(self, client):
self.client = client
self.tools = self._init_tools()
def _init_tools(self) -> Dict[ActionType, Callable]:
"""初始化工具"""
return {
ActionType.SEARCH: self._search,
ActionType.CALCULATOR: self._calculate,
ActionType.LOOKUP: self._lookup,
}
def think(self, context: str, max_steps: int = 5) -> List[Step]:
"""
使用 ReAct 框架思考
Args:
context: 问题上下文
max_steps: 最大推理步数
Returns:
推理步骤列表
"""
history = []
step_num = 0
while step_num < max_steps:
step_num += 1
# 构建推理 Prompt
prompt = self._build_thought_prompt(context, history)
# 获取行动
response = self.client.chat(prompt)
action_text = response["message"]
# 解析行动
action = self._parse_action(action_text)
if action is None:
break
if action.tool == ActionType.FINISH:
# 完成任务
history.append(Step(
step_num=step_num,
thought=action.thought,
action=action,
observation="任务完成",
answer=action.params.get("answer")
))
break
# 执行行动
observation = self._execute_action(action)
# 记录步骤
history.append(Step(
step_num=step_num,
thought=action.thought,
action=action,
observation=observation
))
return history
def _build_thought_prompt(self, context: str, history: List[Step]) -> str:
"""构建推理 Prompt"""
prompt = f"""
你是一个智能助手,需要通过推理和行动来回答问题。
## 当前问题
{context}
## 可用工具
- search: 搜索互联网获取实时信息
参数:query (搜索关键词)
- calculator: 进行数学计算
参数:expression (数学表达式)
- lookup: 查询本地知识库
参数:query (查询内容)
## 历史步骤
"""
for step in history:
prompt += f"""
步骤{step.step_num}:
思考:{step.thought}
行动:{step.action.tool.value}({step.action.params})
观察:{step.observation}
"""
prompt += """
## 输出格式
请按照以下格式输出你的推理和行动:
思考:[你的推理过程]
行动:[工具名](参数)
"""
if not history:
prompt += "\n\n不要使用finish工具,继续推理。"
else:
prompt += "\n\n如果问题已解决,使用 finish(answer='你的最终答案')"
return prompt
def _parse_action(self, text: str) -> Optional[Action]:
"""解析行动"""
lines = text.strip().split('\n')
thought = ""
tool = None
params = {}
for line in lines:
line = line.strip()
if line.startswith("思考:"):
thought = line[3:].strip()
elif line.startswith("行动:"):
action_str = line[3:].strip()
if '(' in action_str and action_str.endswith(')'):
tool_name = action_str.split('(')[0].strip()
param_str = action_str.split('(')[1].rstrip(')')
try:
# 简单解析参数
tool = ActionType(tool_name)
if param_str:
params = {"query": param_str.strip("'\"")}
except ValueError:
pass
if tool:
return Action(tool=tool, params=params, thought=thought)
return None
def _execute_action(self, action: Action) -> str:
"""执行行动"""
tool = self.tools.get(action.tool)
if tool:
return tool(**action.params)
return "未知工具"
# 工具实现
def _search(self, query: str) -> str:
"""搜索工具"""
# 实际项目中调用真实搜索API
return f"搜索'{query}'的结果:[模拟结果]"
def _calculate(self, expression: str) -> str:
"""计算工具"""
try:
result = eval(expression)
return f"计算结果:{result}"
except:
return "计算表达式无效"
def _lookup(self, query: str) -> str:
"""查询工具"""
return f"查询'{query}'的结果:[模拟结果]"
8.3.3 ReAct 应用示例
def use_react_for_research():
"""使用 ReAct 进行研究"""
agent = ReActAgent(client)
question = """
2024年诺贝尔物理学奖获得者是谁?
他们的主要贡献是什么?
"""
steps = agent.think(question, max_steps=3)
print("推理过程:")
for step in steps:
print(f"\n【步骤 {step.step_num}】")
print(f"思考:{step.thought}")
print(f"行动:{step.action.tool.value}({step.action.params})")
print(f"观察:{step.observation}")
if step.answer:
print(f"答案:{step.answer}")
return steps
def use_react_for_calculation():
"""使用 ReAct 进行复杂计算"""
agent = ReActAgent(client)
question = """
某公司2023年收入1000万元,年增长率20%
2024年收入是多少?2023-2024两年累计收入是多少?
"""
steps = agent.think(question, max_steps=3)
print("推理过程:")
for step in steps:
print(f"\n【步骤 {step.step_num}】")
print(f"思考:{step.thought}")
print(f"观察:{step.observation}")
if step.answer:
print(f"答案:{step.answer}")
8.4 System Prompt 设计
8.4.1 System Prompt vs User Prompt
┌─────────────────────────────────────────────────────────────────┐
│ Prompt 层级架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ System Prompt │ │
│ │ 定义AI的身份、行为规则、能力边界 │ │
│ │ 适用于所有对话,一次设置,持久生效 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ User Prompt │ │
│ │ 用户输入的具体问题或指令 │ │
│ │ 每轮对话都需要提供 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ AI Response │ │
│ │ AI根据System和User Prompt生成的回复 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
8.4.2 System Prompt 核心组件
# 高质量的 System Prompt 示例
SYSTEM_PROMPT_TEMPLATE = """
## 身份定义
你是一个[角色名称],专注于[专业领域]。
你有[X]年的[相关]经验,擅长[具体技能]。
## 核心能力
1. [能力1]
2. [能力2]
3. [能力3]
## 工作原则
1. [原则1]:...
2. [原则2]:...
3. [原则3]:...
## 输出风格
- 语言:[正式/专业/友好/简洁]
- 格式:[结构化/自由]
- 长度:[简短/中等/详细]
## 约束条件
- 不知道就说不知道,不要编造
- 不涉及[敏感话题]
- 不提供[特定限制]
## 示例交互
用户:[示例问题]
你:[示例回答]
"""
# 实际示例:Python教学助手
PYTHON_TUTOR_PROMPT = """
## 身份定义
你是一位资深Python开发者,拥有10年的Python教学经验。
你擅长用通俗易懂的语言解释复杂的概念,
并且能够根据学习者的水平调整讲解深度。
## 核心能力
1. 解释Python概念和语法
2. 分析和优化Python代码
3. 调试和修复代码Bug
4. 指导编程最佳实践
## 工作原则
1. **循序渐进**:从基础开始,逐步深入
2. **实践导向**:每个概念都配合代码示例
3. **因材施教**:根据用户水平调整解释深度
4. **鼓励探索**:引导用户自己思考,而非直接给答案
## 输出风格
- 语言:友好、专业、易懂
- 格式:Markdown代码块
- 长度:根据问题复杂度调整,简单问题简洁回答
## 约束条件
- 不直接给答案,引导用户自己思考
- 代码示例必须有注释
- 涉及高级特性时提醒用户注意版本兼容性
- 如果代码有安全漏洞必须明确指出
## 示例交互
用户:什么是装饰器?
你:
装饰器是Python中一个强大的特性。
让我用一个生活中的例子来解释:
想象你有一封信(函数),你想要:
1. 保留信的内容
2. 但在信封上添加一些功能,比如加密、签名
装饰器就像是一个"信封包装机",它包裹原函数,
在不改变原函数内容的情况下,添加新功能。
代码示例:
```python
def my_decorator(func):
def wrapper():
print("装饰器添加的功能")
return func()
return wrapper
@my_decorator
def hello():
print("Hello!")
你想了解装饰器的哪些方面? “”“
### 8.4.3 动态 System Prompt
```python
class DynamicSystemPrompt:
"""动态 System Prompt"""
SYSTEM_PROMPT_BASE = """
你是一个智能助手,正在与用户进行对话。
"""
ROLE_TEMPLATES = {
"python_expert": """
你是一位Python专家,专注于:
1. Python语法和最佳实践
2. 代码审查和优化
3. Python标准库和第三方库推荐
回答时:
- 提供可运行的代码示例
- 解释代码的工作原理
- 指出常见的陷阱和错误
""",
"math_teacher": """
你是一位数学老师,擅长:
1. 解释数学概念
2. 逐步推导公式
3. 提供解题技巧
回答时:
- 清晰展示推导过程
- 解释每一步的数学原理
- 提供多种解法(如果有)
""",
"writing_assistant": """
你是一位专业写作助手,擅长:
1. 文章润色和改进
2. 不同风格的写作
3. 中英互译
回答时:
- 保持原文风格和意图
- 提供修改建议和理由
- 可以给出多个版本供参考
"""
}
def get_prompt(self, role: str, user_level: str = "intermediate") -> str:
"""获取适合的 System Prompt"""
role_prompt = self.ROLE_TEMPLATES.get(role, "")
level_prompt = f"""
用户水平:{user_level}
根据用户水平调整解释的深度和速度。
"""
return self.SYSTEM_PROMPT_BASE + role_prompt + level_prompt
def create_role_based_assistant(role: str, level: str = "intermediate"):
"""创建基于角色的助手"""
prompt_builder = DynamicSystemPrompt()
system_prompt = prompt_builder.get_prompt(role, level)
def chat(user_input: str) -> str:
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
return client.chat("", messages=messages)["message"]
return chat
# 使用示例
python_tutor = create_role_based_assistant("python_expert")
math_teacher = create_role_based_assistant("math_teacher", "beginner")
print(python_tutor("什么是生成器?"))
print(math_teacher("解释一下微积分的基本概念"))
8.4.4 System Prompt 优化
# System Prompt 检查清单
SYSTEM_PROMPT_CHECKLIST = """
## System Prompt 优化检查清单
### 1. 身份清晰度
☐ 是否明确定义了AI的身份?
☐ 身份是否符合目标使用场景?
☐ 身份描述是否过于宽泛或狭窄?
### 2. 能力边界
☐ 是否列出了AI的核心能力?
☐ 是否明确了AI不擅长或不能做的事情?
☐ 能力描述是否与实际模型能力匹配?
### 3. 输出格式
☐ 是否指定了输出格式(如Markdown、JSON)?
☐ 是否有长度限制?
☐ 是否提供了示例输出?
### 4. 约束条件
☐ 是否包含了安全/合规约束?
☐ 是否有质量标准(如"不知道就说不知道")?
☐ 约束是否过于严格导致功能受限?
### 5. 实用性
☐ Prompt长度是否适中(过长会影响性能)?
☐ 指令是否清晰、可执行?
☐ 是否提供了Few-shot示例?
### 6. 可测试性
☐ 是否有明确的质量评判标准?
☐ 是否可以量化评估效果?
☐ 是否有回归测试用例?
"""
def optimize_system_prompt(current_prompt: str) -> str:
"""优化 System Prompt"""
analysis = client.chat(f"""
请分析以下 System Prompt,识别问题并提供改进建议:
{current_prompt}
请按以下格式输出:
## 问题分析
1. [问题1]
2. [问题2]
## 改进建议
1. [建议1]
2. [建议2]
## 优化后的版本
```[优化后的System Prompt]
“”“) return analysis[“message”]
## 8.5 Prompt 组合策略
### 8.5.1 任务分解与组合
```python
class TaskDecomposer:
"""任务分解器"""
def __init__(self, client):
self.client = client
def decompose(self, task: str) -> List[str]:
"""将复杂任务分解为简单子任务"""
prompt = f"""
请将以下任务分解为3-5个简单的子任务:
任务:{task}
要求:
1. 每个子任务应该可以独立完成
2. 子任务之间有明确的依赖关系
3. 按执行顺序列出
格式:
1. [子任务1]
2. [子任务2]
...
"""
response = self.client.chat(prompt)
# 解析子任务
tasks = []
for line in response["message"].split('\n'):
line = line.strip()
if line and line[0].isdigit():
task = line.split('.', 1)[-1].strip()
tasks.append(task)
return tasks
def execute_sequential(self, task: str) -> str:
"""顺序执行分解后的子任务"""
subtasks = self.decompose(task)
results = []
context = ""
for i, subtask in enumerate(subtasks, 1):
print(f"执行子任务 {i}/{len(subtasks)}: {subtask}")
prompt = f"""
基于之前的任务执行结果:
{context}
当前子任务:{subtask}
请执行这个子任务。
"""
result = self.client.chat(prompt)
results.append({
"task": subtask,
"result": result["message"]
})
context += f"\n\n[子任务{i}]: {subtask}\n结果: {result['message']}"
return results
# 使用示例
decomposer = TaskDecomposer(client)
task = "为我分析竞品并制定差异化策略"
results = decomposer.execute_sequential(task)
for r in results:
print(f"\n=== {r['task']} ===")
print(r['result'])
8.5.2 Prompt 链(Chain of Prompts)
class PromptChain:
"""Prompt 链:多个 Prompt 串联执行"""
def __init__(self, client):
self.client = client
self.steps = []
def add_step(self, name: str, prompt_template: str,
extract_key: str = None):
"""
添加 Prompt 步骤
Args:
name: 步骤名称
prompt_template: Prompt模板,支持{previous_result}占位符
extract_key: 从结果中提取的字段名
"""
self.steps.append({
"name": name,
"prompt_template": prompt_template,
"extract_key": extract_key
})
def execute(self, initial_input: str) -> Dict:
"""执行 Prompt 链"""
results = {}
current_result = initial_input
for step in self.steps:
# 填充模板
prompt = step["prompt_template"].format(
previous_result=current_result,
**results # 可使用之前步骤的结果
)
# 执行
response = self.client.chat(prompt)
step_result = response["message"]
# 存储结果
results[step["name"]] = step_result
# 提取特定字段(如果有)
if step["extract_key"] and step["extract_key"] in step_result:
current_result = step_result[step["extract_key"]]
else:
current_result = step_result
return {
"all_results": results,
"final_result": current_result
}
# 示例:文章处理流程
def article_processing_chain():
"""文章处理 Prompt 链"""
chain = PromptChain(client)
# Step 1: 提取关键信息
chain.add_step(
"extract_info",
"""从以下文章中提取关键信息:
{previous_result}
提取以下信息(JSON格式):
- title: 标题
- author: 作者
- main_points: 3个主要观点
- keywords: 关键词(5个)
"""
)
# Step 2: 生成摘要
chain.add_step(
"summarize",
"""基于以下关键信息,生成一篇100字的摘要:
{previous_result}
"""
)
# Step 3: 生成标签
chain.add_step(
"tagging",
"""基于文章内容,生成适合的标签:
标题:{extract_info['title']}
关键词:{extract_info['keywords']}
生成3-5个标签,每个标签不超过5个字。
"""
)
# Step 4: 生成推荐语
chain.add_step(
"recommendation",
"""基于以下内容,生成一句话推荐语(用于分享):
标题:{extract_info['title']}
主要观点:{extract_info['main_points']}
推荐语要求:
- 吸引人点击
- 不超过30字
- 不剧透
"""
)
return chain.execute
# 使用
process_article = article_processing_chain()
article = """
[原始文章内容]...
"""
result = process_article(article)
print("处理结果:")
for key, value in result["all_results"].items():
print(f"\n【{key}】")
print(value)
8.5.3 并行 Prompt 处理
import asyncio
from concurrent.futures import ThreadPoolExecutor
class ParallelPromptProcessor:
"""并行 Prompt 处理"""
def __init__(self, client, max_workers: int = 3):
self.client = client
self.executor = ThreadPoolExecutor(max_workers=max_workers)
def process_multiple(self, prompts: List[str]) -> List[str]:
"""并行处理多个 Prompt"""
futures = [
self.executor.submit(self.client.chat, prompt)
for prompt in prompts
]
results = []
for future in futures:
try:
result = future.result()
results.append(result["message"])
except Exception as e:
results.append(f"Error: {str(e)}")
return results
def batch_analyze(self, items: List[str], analysis_prompt: str) -> List[str]:
"""
批量分析多个项目
Args:
items: 待分析项目列表
analysis_prompt: 分析 Prompt 模板,{item} 为占位符
"""
prompts = [
analysis_prompt.format(item=item)
for item in items
]
return self.process_multiple(prompts)
# 使用示例
def parallel_usage():
processor = ParallelPromptProcessor(client, max_workers=3)
# 批量代码审查
code_snippets = [
"def add(a, b): return a + b",
"def subtract(a, b): return a - b",
"def multiply(a, b): return a * b"
]
review_prompt = """请审查以下Python代码:
{item}
审查维度:正确性、风格、安全性
"""
reviews = processor.batch_analyze(code_snippets, review_prompt)
for snippet, review in zip(code_snippets, reviews):
print(f"\n代码: {snippet}")
print(f"审查: {review}")
return reviews
8.6 高级技巧实战
8.6.1 结构化输出(JSON Mode)
# 强制 JSON 输出
JSON_MODE_PROMPT = """
请将以下信息转换为JSON格式。
信息:
- 姓名:张三
- 年龄:28
- 职业:软件工程师
- 技能:Python, Java, JavaScript
要求:
- 必须输出有效的JSON
- 使用中文key
- 不要输出任何解释
- 不要使用markdown代码块
JSON格式参考:
{
"姓名": "...",
"年龄": ...,
"职业": "...",
"技能": [...]
}
"""
# 使用 Qwen 的 JSON Mode
def structured_output(prompt: str, schema: dict = None) -> dict:
"""结构化输出"""
from dashscope import Generation
# 构建带格式要求的 Prompt
formatted_prompt = f"""
{prompt}
请严格按照以下JSON Schema输出:
{json.dumps(schema, ensure_ascii=False, indent=2)}
只输出JSON,不要有任何其他文字。
"""
response = Generation.call(
model="qwen-max",
messages=[
{"role": "user", "content": formatted_prompt}
],
result_format="message"
)
# 解析 JSON
try:
return json.loads(response.output.choices[0].message.content)
except json.JSONDecodeError:
return {"error": "JSON解析失败", "raw": response.output.choices[0].message.content}
# 使用示例
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"skills": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "age", "skills"]
}
result = structured_output("提取姓名、年龄和技能", schema)
print(result)
8.6.2 自我一致性(Self-Consistency)
# 多次采样,选择最一致的答案
def self_consistency(prompt: str, n_samples: int = 5) -> str:
"""
自我一致性:多次采样,选择最常见的答案
Args:
prompt: 原始问题
n_samples: 采样次数
Returns:
最一致的答案
"""
from collections import Counter
# 生成多个解答
answers = []
for _ in range(n_samples):
response = client.chat(
f"{prompt}\n\n请给出你的最终答案。"
)
answers.append(response["message"].strip())
# 统计最常见的答案
counter = Counter(answers)
most_common = counter.most_common(1)[0]
return {
"answer": most_common[0],
"confidence": most_common[1] / n_samples,
"all_answers": answers,
"distribution": dict(counter)
}
# 示例
result = self_consistency(
"一个盒子有10个红球和5个蓝球,"
"随机抽取3个球,至少有1个红球的概率是多少?",
n_samples=5
)
print(f"最一致答案: {result['answer']}")
print(f"置信度: {result['confidence']:.0%}")
print(f"答案分布: {result['distribution']}")
8.6.3 反思与验证
def reflective_reasoning(problem: str) -> Dict:
"""
反思推理:生成答案,然后验证和修正
1. 初始解答
2. 自我验证
3. 如有问题,修正
4. 最终确认
"""
# Step 1: 初始解答
initial = client.chat(f"""
问题:{problem}
请详细解答这个问题。
""")
# Step 2: 自我验证
verification = client.chat(f"""
你刚刚给出了以下解答:
{initial['message']}
请验证这个解答是否正确:
1. 逻辑是否严密?
2. 是否有遗漏的关键点?
3. 答案是否正确?
如果有问题,说明问题并给出修正后的解答。
如果正确,确认答案。
""")
# Step 3: 最终输出
if "正确" in verification["message"] or "没有问题" in verification["message"]:
return {
"answer": initial["message"],
"verified": True,
"verification": verification["message"]
}
else:
return {
"answer": verification["message"],
"verified": False,
"verification": verification["message"]
}
# 示例
result = reflective_reasoning(
"如果x + 5 = 12,那么x的平方是多少?"
)
print(result["answer"])
本章小结
本章介绍了高级 Prompt 技巧:
- Chain of Thought(思维链):通过引导模型逐步推理,提升复杂任务的准确性
- Tree of Thought(思维树):探索多条推理路径,选择最佳方案
- ReAct 框架:结合推理与行动,适用于需要外部工具的任务
- System Prompt 设计:定义AI身份、能力边界和输出规范
- Prompt 组合策略:任务分解、Prompt链、并行处理
- 高级技巧实战:JSON Mode、自我一致性、反思验证
掌握这些技巧,你将能够构建更加智能、可靠的 AI 应用。
思考与练习
-
概念理解:对比 Chain of Thought 和 Tree of Thought 的适用场景。
-
实践练习:为以下场景设计 ReAct Agent:
- 天气查询助手
- 股票分析机器人
- 旅行规划助手
-
System Prompt 优化:为你的应用场景设计一个完整的 System Prompt,包含身份、能力、约束和示例。
-
代码实现:实现一个简单的 Chain of Thought 处理器,并测试在不同类型问题上的效果。
-
对比实验:使用同一个问题,分别测试 Zero-shot、Few-shot、CoT 三种方式的效果和效率。
-
综合应用:设计一个 AI 助手,能够:
- 理解用户意图
- 分解复杂任务
- 使用工具获取信息
- 整合信息给出完整答案
第9章:快速构建 AI 对话应用
本章介绍如何使用 Python 快速构建 AI 对话应用。从最简的 Gradio 原型,到生产级的 FastAPI 服务,再到对话历史管理,帮助你掌握 AI 应用开发的核心技能。
9.1 AI 对话应用概述
9.1.1 应用架构
一个完整的 AI 对话应用通常包含以下组件:
┌─────────────────────────────────────────────────────────────────┐
│ AI 对话应用架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 前端 │ │ 后端 │ │ AI服务 │ │
│ │ (Gradio/ │ ───► │ (FastAPI/ │ ───► │ (DashScope│ │
│ │ Streamlit│ │ Flask) │ │ API) │ │
│ │ React) │ ◄─── │ │ ◄─── │ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │ │ │ │
│ │ ↓ │ │
│ │ ┌────────────┐ │ │
│ │ │ 数据库 │ │ │
│ │ │(对话历史/ │ │ │
│ │ │ 用户信息) │ │ │
│ │ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
9.1.2 开发模式选择
| 场景 | 推荐技术 | 说明 |
|---|---|---|
| 快速原型/POC | Gradio | 几行代码即可完成界面 |
| 内部工具/演示 | Streamlit | 更丰富的 UI 组件 |
| 生产环境 | FastAPI | 高性能、异步、自动化文档 |
| Web 应用 | React/Vue | 前后端分离架构 |
9.2 Gradio:快速原型开发
9.2.1 Gradio 简介
Gradio 是一个用于构建机器学习 Web 应用的 Python 库,让你可以快速创建交互式界面。
安装:
pip install gradio
核心优势:
- 几行代码即可创建界面
- 自动生成分享链接
- 支持丰富的输入输出组件
- 内置聊天机器人组件
9.2.2 最简单的 AI 对话
#!/usr/bin/env python3
"""
gradio-simple-chat.py
最简单的 AI 对话应用
"""
import gradio as gr
from dashscope import Generation
# 初始化 DashScope 客户端
dashscope.api_key = "your-api-key"
def chat_with_ai(message, history):
"""处理用户消息,返回 AI 回复"""
# 构建消息历史
messages = []
for h in history:
messages.append({"role": "user", "content": h[0]})
messages.append({"role": "assistant", "content": h[1]})
messages.append({"role": "user", "content": message})
# 调用 API
response = Generation.call(
model="qwen-turbo",
messages=messages,
result_format="message"
)
if response.status_code == 200:
return response.output.choices[0].message.content
else:
return f"出错了: {response.message}"
# 创建 Gradio 界面
demo = gr.ChatInterface(
fn=chat_with_ai,
title="AI 对话助手",
description="基于通义千问的智能对话助手",
examples=[
"你好,请介绍一下你自己",
"用Python写一个快速排序",
"解释一下什么是云计算"
],
theme="soft"
)
# 启动服务
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860
)
运行后访问 http://localhost:7860 即可看到界面。
9.2.3 带系统提示的对话
#!/usr/bin/env python3
"""
gradio-chatbot.py
带系统提示和更多功能的对话应用
"""
import gradio as gr
from dashscope import Generation
from dashscope.common import Message
# 系统提示词
SYSTEM_PROMPT = """你是一位专业的Python讲师,名为"小P老师"。
你有以下特点:
1. 讲解深入浅出,善于用例子说明抽象概念
2. 注重代码规范和最佳实践
3. 鼓励学生动手实践
4. 回答简洁明了,避免冗长
请用友好的方式与学生交流。"""
def chat(message, history, system_prompt):
"""带系统提示的对话"""
messages = [
Message(role="system", content=system_prompt)
]
# 添加历史对话
for h in history:
messages.append(Message(role="user", content=h[0]))
messages.append(Message(role="assistant", content=h[1]))
# 添加当前消息
messages.append(Message(role="user", content=message))
# 调用 API
response = Generation.call(
model="qwen-plus",
messages=messages,
temperature=0.7,
result_format="message"
)
if response.status_code == 200:
return response.output.choices[0].message.content
else:
return f"出错了: {response.message}"
def reset_conversation():
"""重置对话"""
return None, []
# 创建界面
with gr.Blocks(title="Python 助教") as demo:
gr.Markdown("# 🎓 Python 助教")
gr.Markdown("一个基于 AI 的 Python 学习助手,帮你解答 Python 相关问题。")
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.Chatbot(
label="对话历史",
height=400,
show_copy_button=True
)
msg = gr.Textbox(
label="输入你的问题",
placeholder="例如:什么是装饰器?",
lines=2
)
with gr.Row():
submit_btn = gr.Button("发送", variant="primary")
clear_btn = gr.Button("清空对话")
with gr.Column(scale=1):
gr.Markdown("### ⚙️ 设置")
model_select = gr.Dropdown(
choices=["qwen-turbo", "qwen-plus", "qwen-max"],
value="qwen-plus",
label="选择模型"
)
temp_slider = gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.7,
step=0.1,
label="创意度 (Temperature)"
)
system_input = gr.Textbox(
value=SYSTEM_PROMPT,
label="系统提示词",
lines=5,
visible=False
)
gr.Markdown("---")
gr.Markdown("💡 **提示**:直接输入 Python 问题,AI 会帮你解答!")
def respond(message, history, model, temperature):
"""处理回复"""
# 更新 API 调用
messages = [
Message(role="system", content=SYSTEM_PROMPT)
]
for h in history:
messages.append(Message(role="user", content=h[0]))
messages.append(Message(role="assistant", content=h[1]))
messages.append(Message(role="user", content=message))
response = Generation.call(
model=model,
messages=messages,
temperature=temperature,
result_format="message"
)
if response.status_code == 200:
bot_message = response.output.choices[0].message.content
else:
bot_message = f"出错了: {response.message}"
history.append((message, bot_message))
return "", history
# 绑定事件
submit_btn.click(
respond,
inputs=[msg, chatbot, model_select, temp_slider],
outputs=[msg, chatbot]
)
msg.submit(
respond,
inputs=[msg, chatbot, model_select, temp_slider],
outputs=[msg, chatbot]
)
clear_btn.click(
lambda: (None, []),
outputs=[msg, chatbot]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)
9.2.4 流式输出
#!/usr/bin/env python3
"""
gradio-streaming.py
流式输出的 AI 对话
"""
import gradio as gr
from dashscope import Generation
def stream_chat(message, history):
"""流式对话"""
messages = []
for h in history:
messages.append({"role": "user", "content": h[0]})
messages.append({"role": "assistant", "content": h[1]})
messages.append({"role": "user", "content": message})
# 流式调用
response = Generation.call(
model="qwen-turbo",
messages=messages,
stream=True,
incremental_output=True,
result_format="message"
)
partial_text = ""
for chunk in response:
if chunk.status_code == 200:
content = chunk.output.choices[0].delta.content
partial_text += content
yield partial_text
else:
yield f"出错了: {chunk.message}"
# 创建界面
demo = gr.ChatInterface(
fn=stream_chat,
type="messages", # 使用 messages 格式
title="🚀 流式对话助手",
description="体验流畅的流式输出效果",
)
if __name__ == "__main__":
demo.launch()
9.3 FastAPI:生产级服务
9.3.1 FastAPI 简介
FastAPI 是一个现代、快速的 Python Web 框架,基于标准 Python 类型提示,支持异步编程,自动生成 API 文档。
安装:
pip install fastapi uvicorn python-dotenv sse-starlette
9.3.2 基础 API 服务
#!/usr/bin/env python3
"""
fastapi-basic.py
FastAPI 基础服务
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional
import os
from dashscope import Generation
from dashscope.common import Message
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
# 初始化 FastAPI
app = FastAPI(
title="AI Chat API",
description="基于 DashScope 的 AI 对话服务",
version="1.0.0"
)
# CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境应限制
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 请求模型
class Message(BaseModel):
"""消息模型"""
role: str = Field(..., description="角色: system/user/assistant")
content: str = Field(..., description="消息内容")
class ChatRequest(BaseModel):
"""对话请求"""
messages: List[Message] = Field(..., description="消息列表")
model: str = Field(default="qwen-turbo", description="模型名称")
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
max_tokens: int = Field(default=2000, ge=1, le=4000)
class ChatResponse(BaseModel):
"""对话响应"""
message: str
model: str
usage: dict
# API 端点
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""
对话接口
"""
try:
# 转换为 DashScope 格式
dashscope_messages = [
Message(role=m.role, content=m.content)
for m in request.messages
]
# 调用 API
response = Generation.call(
model=request.model,
messages=dashscope_messages,
temperature=request.temperature,
max_tokens=request.max_tokens,
result_format="message"
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail=response.message)
choice = response.output.choices[0].message
return ChatResponse(
message=choice.content,
model=response.request.model,
usage={
"input_tokens": response.output.usage.input_tokens if hasattr(response.output, 'usage') else 0,
"output_tokens": response.output.usage.output_tokens if hasattr(response.output, 'usage') else 0,
}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/models")
async def list_models():
"""列出可用模型"""
return {
"models": [
{"id": "qwen-turbo", "name": "Qwen Turbo", "description": "快速响应"},
{"id": "qwen-plus", "name": "Qwen Plus", "description": "平衡性能"},
{"id": "qwen-max", "name": "Qwen Max", "description": "最高质量"},
]
}
@app.get("/health")
async def health_check():
"""健康检查"""
return {"status": "healthy"}
# 运行服务
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
9.3.3 流式 API
#!/usr/bin/env python3
"""
fastapi-streaming.py
FastAPI 流式服务
"""
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from typing import List
import json
import asyncio
from dashscope import Generation
from dashscope.common import Message
app = FastAPI()
class ChatRequest(BaseModel):
messages: List[Message]
model: str = "qwen-turbo"
temperature: float = 0.7
async def generate_stream(request: ChatRequest):
"""生成流式响应"""
try:
# 流式调用
responses = Generation.call(
model=request.model,
messages=[Message(role=m.role, content=m.content) for m in request.messages],
temperature=request.temperature,
stream=True,
incremental_output=True,
result_format="message"
)
for chunk in responses:
if chunk.status_code == 200:
content = chunk.output.choices[0].delta.content
# SSE 格式
data = {
"content": content,
"done": False
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
else:
error_data = {
"error": chunk.message,
"done": True
}
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
# 发送完成信号
yield f"data: {json.dumps({'done': True})}\n\n"
except Exception as e:
error_data = {"error": str(e), "done": True}
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
@app.post("/api/chat/stream")
async def chat_stream(request: ChatRequest):
"""流式对话接口"""
return StreamingResponse(
generate_stream(request),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no"
}
)
9.3.4 带数据库的完整服务
#!/usr/bin/env python3
"""
fastapi-complete.py
带数据库的完整对话服务
"""
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import datetime
import uuid
import sqlite3
from contextlib import contextmanager
from dashscope import Generation
from dashscope.common import Message
app = FastAPI(title="AI Chat Service")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ============== 数据库操作 ==============
DB_PATH = "chat_history.db"
@contextmanager
def get_db():
"""数据库上下文管理器"""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
finally:
conn.close()
def init_db():
"""初始化数据库"""
with get_db() as conn:
conn.executescript("""
CREATE TABLE IF NOT EXISTS conversations (
id TEXT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
title TEXT
);
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
conversation_id TEXT,
role TEXT,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
);
""")
# ============== 数据模型 ==============
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
conversation_id: Optional[str] = None
messages: List[Message]
model: str = "qwen-turbo"
temperature: float = 0.7
class ChatResponse(BaseModel):
conversation_id: str
message: str
message_id: str
# ============== API 端点 ==============
@app.on_event("startup")
async def startup():
init_db()
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""对话接口"""
try:
# 创建或获取会话
if request.conversation_id:
conversation_id = request.conversation_id
else:
conversation_id = str(uuid.uuid4())
with get_db() as conn:
conn.execute(
"INSERT INTO conversations (id, title) VALUES (?, ?)",
(conversation_id, request.messages[-1].content[:50])
)
# 保存用户消息
user_msg_id = str(uuid.uuid4())
with get_db() as conn:
conn.execute(
"INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)",
(user_msg_id, conversation_id, "user", request.messages[-1].content)
)
# 调用 AI
dashscope_messages = [
Message(role=m.role, content=m.content)
for m in request.messages
]
response = Generation.call(
model=request.model,
messages=dashscope_messages,
temperature=request.temperature,
result_format="message"
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail=response.message)
assistant_content = response.output.choices[0].message.content
# 保存助手消息
assistant_msg_id = str(uuid.uuid4())
with get_db() as conn:
conn.execute(
"INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)",
(assistant_msg_id, conversation_id, "assistant", assistant_content)
)
conn.execute(
"UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?",
(conversation_id,)
)
return ChatResponse(
conversation_id=conversation_id,
message=assistant_content,
message_id=assistant_msg_id
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/conversations")
async def list_conversations(limit: int = 20):
"""获取会话列表"""
with get_db() as conn:
rows = conn.execute(
"SELECT * FROM conversations ORDER BY updated_at DESC LIMIT ?",
(limit,)
).fetchall()
return [
{
"id": row["id"],
"title": row["title"],
"created_at": row["created_at"],
"updated_at": row["updated_at"]
}
for row in rows
]
@app.get("/api/conversations/{conversation_id}/messages")
async def get_messages(conversation_id: str):
"""获取会话消息"""
with get_db() as conn:
rows = conn.execute(
"SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at",
(conversation_id,)
).fetchall()
return [
{
"id": row["id"],
"role": row["role"],
"content": row["content"],
"created_at": row["created_at"]
}
for row in rows
]
@app.delete("/api/conversations/{conversation_id}")
async def delete_conversation(conversation_id: str):
"""删除会话"""
with get_db() as conn:
conn.execute("DELETE FROM messages WHERE conversation_id = ?", (conversation_id,))
conn.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,))
return {"status": "deleted"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
9.4 对话历史管理
9.4.1 对话上下文策略
from typing import List, Dict
from dataclasses import dataclass
@dataclass
class ConversationContext:
"""对话上下文"""
messages: List[Dict[str, str]]
max_tokens: int = 8000
system_prompt: str = ""
def add_user_message(self, content: str):
"""添加用户消息"""
self.messages.append({"role": "user", "content": content})
def add_assistant_message(self, content: str):
"""添加助手消息"""
self.messages.append({"role": "assistant", "content": content})
def get_messages(self) -> List[Dict]:
"""获取消息列表(带截断)"""
result = []
if self.system_prompt:
result.append({"role": "system", "content": self.system_prompt})
result.extend(self.messages)
# 如果超过 token 限制,截断早期消息
if self._estimate_tokens(result) > self.max_tokens:
result = self._truncate_messages(result)
return result
def _estimate_tokens(self, messages: List[Dict]) -> int:
"""粗略估算 token 数量"""
total = 0
for msg in messages:
# 中文字符约 2 token/字,英文约 4 token/词
content = msg["content"]
total += len(content) // 2 # 简化估算
return total
def _truncate_messages(self, messages: List[Dict]) -> List[Dict]:
"""截断消息以符合 token 限制"""
# 保留系统提示
result = [messages[0]] if messages[0]["role"] == "system" else []
# 从后向前保留消息
remaining_tokens = self.max_tokens - self._estimate_tokens(result)
for msg in reversed(messages[1:]):
msg_tokens = self._estimate_tokens([msg])
if remaining_tokens >= msg_tokens:
result.insert(1, msg)
remaining_tokens -= msg_tokens
else:
break
return result
def clear(self):
"""清空对话历史"""
self.messages = []
9.4.2 对话摘要策略
class SummarizingContext(ConversationContext):
"""带摘要的对话上下文"""
def __init__(self, *args, summary_trigger_tokens: int = 6000, **kwargs):
super().__init__(*args, **kwargs)
self.summary_trigger_tokens = summary_trigger_tokens
self.summary = ""
def get_messages(self) -> List[Dict]:
"""获取消息列表(带摘要)"""
result = []
if self.system_prompt:
result.append({"role": "system", "content": self.system_prompt})
# 如果有摘要,加入到开头
if self.summary:
result.append({
"role": "system",
"content": f"之前的对话摘要:{self.summary}"
})
result.extend(self.messages)
# 检查是否需要摘要
if self._estimate_tokens(result) > self.summary_trigger_tokens:
self._generate_summary()
# 重新构建消息列表
result = []
if self.system_prompt:
result.append({"role": "system", "content": self.system_prompt})
if self.summary:
result.append({
"role": "system",
"content": f"之前的对话摘要:{self.summary}"
})
# 只保留最近几条消息
result.extend(self.messages[-4:])
return result
def _generate_summary(self):
"""生成对话摘要"""
# 实际项目中调用 AI 生成摘要
summary_prompt = f"""
请为以下对话生成一个简洁的摘要,保留关键信息:
{self._format_conversation()}
摘要要求:
- 50字以内
- 包含关键话题和结论
- 使用中文
"""
# 简化实现,实际应该调用 AI
recent = self.messages[-6:] if len(self.messages) > 6 else self.messages
topics = [m["content"][:30] for m in recent if m["role"] == "user"]
self.summary = f"讨论了{len(topics)}个问题"
9.4.3 多会话管理
from typing import Dict, Optional
from datetime import datetime
import threading
class ConversationManager:
"""会话管理器"""
def __init__(self):
self.conversations: Dict[str, ConversationContext] = {}
self.lock = threading.Lock()
def create_conversation(
self,
conversation_id: Optional[str] = None,
system_prompt: str = ""
) -> str:
"""创建新会话"""
with self.lock:
if conversation_id is None:
conversation_id = str(uuid.uuid4())
self.conversations[conversation_id] = ConversationContext(
messages=[],
system_prompt=system_prompt
)
return conversation_id
def get_conversation(self, conversation_id: str) -> Optional[ConversationContext]:
"""获取会话"""
return self.conversations.get(conversation_id)
def delete_conversation(self, conversation_id: str) -> bool:
"""删除会话"""
with self.lock:
if conversation_id in self.conversations:
del self.conversations[conversation_id]
return True
return False
def list_conversations(self) -> List[Dict]:
"""列出所有会话"""
return [
{
"id": cid,
"message_count": len(ctx.messages),
"token_count": ctx._estimate_tokens(ctx.get_messages())
}
for cid, ctx in self.conversations.items()
]
# 全局实例
conversation_manager = ConversationManager()
9.5 项目实战:构建聊天机器人
9.5.1 项目结构
chatbot-project/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── models.py # 数据模型
│ ├── schemas.py # Pydantic 模型
│ ├── api/
│ │ ├── __init__.py
│ │ └── chat.py # 聊天 API
│ ├── services/
│ │ ├── __init__.py
│ │ ├── ai_service.py # AI 服务封装
│ │ └── context.py # 上下文管理
│ └── db/
│ ├── __init__.py
│ └── sqlite.py # 数据库操作
├── static/ # 静态文件
├── templates/ # HTML 模板
├── tests/ # 测试
├── .env # 环境变量
├── requirements.txt
└── README.md
9.5.2 AI 服务封装
# app/services/ai_service.py
"""
AI 服务封装
"""
from typing import List, Dict, Optional, AsyncIterator
import dashscope
from dashscope import Generation
from dashscope.common import Message
class AIService:
"""AI 服务封装类"""
def __init__(self, api_key: str, default_model: str = "qwen-turbo"):
dashscope.api_key = api_key
self.default_model = default_model
def chat(
self,
messages: List[Dict],
model: str = None,
temperature: float = 0.7,
max_tokens: int = 2000
) -> Dict:
"""
对话生成(非流式)
Returns:
{"message": str, "usage": dict}
"""
model = model or self.default_model
dashscope_messages = [
Message(role=m["role"], content=m["content"])
for m in messages
]
response = Generation.call(
model=model,
messages=dashscope_messages,
temperature=temperature,
max_tokens=max_tokens,
result_format="message"
)
if response.status_code != 200:
raise Exception(f"AI 服务错误: {response.message}")
return {
"message": response.output.choices[0].message.content,
"usage": {
"input_tokens": response.output.usage.input_tokens,
"output_tokens": response.output.usage.output_tokens,
} if hasattr(response.output, 'usage') else {}
}
async def chat_stream(
self,
messages: List[Dict],
model: str = None,
temperature: float = 0.7
) -> AsyncIterator[str]:
"""
对话生成(流式)
Yields:
str: 生成的文本片段
"""
model = model or self.default_model
dashscope_messages = [
Message(role=m["role"], content=m["content"])
for m in messages
]
responses = Generation.call(
model=model,
messages=dashscope_messages,
temperature=temperature,
stream=True,
incremental_output=True,
result_format="message"
)
for chunk in responses:
if chunk.status_code == 200:
content = chunk.output.choices[0].delta.content
if content:
yield content
else:
raise Exception(f"AI 服务错误: {chunk.message}")
# 单例
ai_service = AIService(api_key=os.getenv("DASHSCOPE_API_KEY"))
9.5.3 前端页面
<!-- templates/chat.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 聊天机器人</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 2rem;
text-align: center;
}
.chat-container {
flex: 1;
max-width: 800px;
margin: 0 auto;
width: 100%;
padding: 1rem;
overflow-y: auto;
}
.message {
margin-bottom: 1rem;
padding: 1rem;
border-radius: 1rem;
max-width: 80%;
}
.message.user {
background: #667eea;
color: white;
margin-left: auto;
border-bottom-right-radius: 0.25rem;
}
.message.assistant {
background: white;
color: #333;
border-bottom-left-radius: 0.25rem;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.input-area {
background: white;
padding: 1rem 2rem;
border-top: 1px solid #eee;
display: flex;
gap: 1rem;
}
.input-area input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid #ddd;
border-radius: 2rem;
font-size: 1rem;
outline: none;
}
.input-area input:focus {
border-color: #667eea;
}
.input-area button {
padding: 0.75rem 1.5rem;
background: #667eea;
color: white;
border: none;
border-radius: 2rem;
cursor: pointer;
font-size: 1rem;
}
.input-area button:hover {
background: #764ba2;
}
.input-area button:disabled {
background: #ccc;
cursor: not-allowed;
}
.typing {
display: flex;
align-items: center;
gap: 0.5rem;
color: #666;
padding: 1rem;
}
.typing span {
width: 8px;
height: 8px;
background: #667eea;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out;
}
.typing span:nth-child(1) { animation-delay: -0.32s; }
.typing span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
</style>
</head>
<body>
<div class="header">
<h1>🤖 AI 聊天机器人</h1>
</div>
<div class="chat-container" id="chatContainer">
<div class="message assistant">
你好!我是 AI 助手,有什么可以帮你的吗?
</div>
</div>
<div class="input-area">
<input type="text" id="userInput" placeholder="输入消息..." autocomplete="off">
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
<script>
const chatContainer = document.getElementById('chatContainer');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
let conversationId = null;
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
// 显示用户消息
addMessage(message, 'user');
userInput.value = '';
// 显示加载状态
const typingDiv = addTypingIndicator();
try {
const response = await fetch('/api/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
conversation_id: conversationId,
messages: [{ role: 'user', content: message }]
})
});
chatContainer.removeChild(typingDiv);
if (!response.ok) {
throw new Error('请求失败');
}
// 创建助手消息框
const assistantDiv = document.createElement('div');
assistantDiv.className = 'message assistant';
chatContainer.appendChild(assistantDiv);
// 读取流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.content) {
fullMessage += data.content;
assistantDiv.textContent = fullMessage;
scrollToBottom();
}
}
}
}
// 更新会话ID
const data = await response.json();
if (data.conversation_id) {
conversationId = data.conversation_id;
}
} catch (error) {
chatContainer.removeChild(typingDiv);
addMessage('抱歉,出错了: ' + error.message, 'assistant');
}
}
function addMessage(content, type) {
const div = document.createElement('div');
div.className = `message ${type}`;
div.textContent = content;
chatContainer.appendChild(div);
scrollToBottom();
}
function addTypingIndicator() {
const div = document.createElement('div');
div.className = 'typing';
div.innerHTML = '<span></span><span></span><span></span> 思考中...';
chatContainer.appendChild(div);
scrollToBottom();
return div;
}
function scrollToBottom() {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
</script>
</body>
</html>
本章小结
本章介绍了快速构建 AI 对话应用的方法:
- Gradio:几行代码创建交互式界面,适合快速原型
- FastAPI:高性能 API 服务,支持异步和自动文档
- 对话历史管理:上下文管理、摘要策略、多会话管理
- 项目实战:完整的聊天机器人架构
下一章我们将学习企业知识库问答系统,掌握 RAG 架构的核心技术。
思考与练习
-
实践练习:使用 Gradio 创建一个简单的 AI 对话应用。
-
进阶挑战:使用 FastAPI 实现一个带数据库的完整对话服务。
-
性能优化:思考如何在大规模用户场景下优化对话服务。
-
功能扩展:为对话应用添加以下功能:
- 敏感词过滤
- 消息撤回
- 会话搜索
-
架构设计:设计一个高并发的聊天服务架构。
第10章:企业知识库问答
本章介绍如何构建基于企业知识库的智能问答系统(RAG架构)。从文档处理、向量化、语义检索,到完整的 RAG 流水线,帮助你构建精准、可信赖的 AI 问答系统。
10.1 RAG 架构概述
10.1.1 什么是 RAG?
RAG = Retrieval-Augmented Generation(检索增强生成)
传统的 LLM 有两个主要问题:
- 知识陈旧:训练数据有截止日期,无法获取最新信息
- 幻觉问题:可能产生看似合理但错误的答案
RAG 通过检索外部知识来增强 LLM 的能力:
┌─────────────────────────────────────────────────────────────────┐
│ RAG 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户问题 ──► │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 检索 │ ◄── 知识库(向量数据库) │
│ │ (Retrieval) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 增强 │ ◄── 原始问题 │
│ │ (Augment) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 生成 │ │
│ │ (Generate) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ 最终答案 │
│ │
└─────────────────────────────────────────────────────────────────┘
10.1.2 RAG 核心组件
| 组件 | 职责 |
|---|---|
| 文档加载器 | 读取各种格式的文档(PDF、Word、Markdown等) |
| 文本分割器 | 将长文档分割成适合的片段 |
| 向量化模型 | 将文本转换为向量表示 |
| 向量数据库 | 存储和检索向量 |
| 检索器 | 根据问题检索相关文档 |
| 生成器 | LLM 基于检索结果生成答案 |
10.1.3 RAG 应用场景
- 企业内部知识库问答
- 客服机器人
- 文档摘要和问答
- 论文/报告分析
- 法规政策查询
10.2 文档处理与向量化
10.2.1 安装依赖
pip install langchain langchain-community pypdf python-docx markdown dashscope faiss-cpu
10.2.2 文档加载
from langchain.document_loaders import (
PyPDFLoader, # PDF
UnstructuredWordDocumentLoader, # Word
TextLoader, # 文本文件
MarkdownLoader # Markdown
)
from langchain.schema import Document
def load_documents(file_path: str) -> list:
"""
加载文档
Args:
file_path: 文档路径
Returns:
文档列表
"""
if file_path.endswith('.pdf'):
loader = PyPDFLoader(file_path)
elif file_path.endswith('.docx'):
loader = UnstructuredWordDocumentLoader(file_path)
elif file_path.endswith('.md'):
loader = MarkdownLoader(file_path)
else:
loader = TextLoader(file_path)
documents = loader.load()
return documents
# 使用示例
docs = load_documents("knowledgebase/manual.pdf")
print(f"加载了 {len(docs)} 页")
print(f"内容预览: {docs[0].page_content[:200]}...")
10.2.3 文本分割
from langchain.text_splitter import (
RecursiveCharacterTextSplitter,
CharacterTextSplitter,
TokenTextSplitter
)
def split_documents(
documents: list,
chunk_size: int = 500,
chunk_overlap: int = 50
) -> list:
"""
分割文档
Args:
documents: 文档列表
chunk_size: 块大小(字符数)
chunk_overlap: 块重叠大小
Returns:
分割后的文档块
"""
# 使用递归字符分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
# 自定义分割符
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
splits = text_splitter.split_documents(documents)
return splits
# 使用示例
splits = split_documents(docs, chunk_size=500, chunk_overlap=50)
print(f"分割成 {len(splits)} 个块")
10.2.4 向量化与存储
from langchain.embeddings import DashScopeEmbeddings
from langchain.vectorstores import FAISS
class KnowledgeBase:
"""知识库类"""
def __init__(self):
self.embeddings = DashScopeEmbeddings(
model="text-embedding-v3",
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
self.vectorstore = None
self.documents = []
def add_documents(self, documents: list):
"""
添加文档到知识库
Args:
documents: 文档列表
"""
# 分割文档
splits = split_documents(documents)
# 创建向量存储
self.vectorstore = FAISS.from_documents(
documents=splits,
embedding=self.embeddings
)
self.documents.extend(splits)
print(f"已添加 {len(splits)} 个文档块到知识库")
def similarity_search(self, query: str, k: int = 4) -> list:
"""
相似度搜索
Args:
query: 查询文本
k: 返回数量
Returns:
相关文档列表
"""
if not self.vectorstore:
return []
results = self.vectorstore.similarity_search(query, k=k)
return results
def save(self, path: str):
"""保存向量数据库"""
if self.vectorstore:
self.vectorstore.save_local(path)
print(f"知识库已保存到 {path}")
def load(self, path: str):
"""加载向量数据库"""
if os.path.exists(path):
self.vectorstore = FAISS.load_local(
path,
self.embeddings,
allow_dangerous_deserialization=True
)
print(f"知识库已从 {path} 加载")
# 使用示例
kb = KnowledgeBase()
# 添加文档
docs = load_documents("knowledgebase/company_policy.pdf")
kb.add_documents(docs)
# 保存
kb.save("vectorstore/company_policy")
# 搜索
results = kb.similarity_search("年假政策是什么?", k=3)
for i, doc in enumerate(results, 1):
print(f"\n【结果 {i}】")
print(doc.page_content)
10.3 语义检索技术
10.3.1 检索策略
class SemanticSearch:
"""语义检索器"""
def __init__(self, vectorstore):
self.vectorstore = vectorstore
def basic_search(self, query: str, k: int = 4) -> list:
"""基础相似度搜索"""
return self.vectorstore.similarity_search(query, k=k)
def mmr_search(
self,
query: str,
k: int = 4,
fetch_k: int = 10,
lambda_mult: float = 0.5
) -> list:
"""
最大边际相关性搜索(MMR)
MMR 可以增加检索结果的多样性,避免返回内容重复的文档。
Args:
query: 查询文本
k: 返回数量
fetch_k: 初始检索数量
lambda_mult: 多样性参数(0=只取最相似,1=最大多样性)
"""
results = self.vectorstore.max_marginal_relevance_search(
query,
k=k,
fetch_k=fetch_k,
lambda_mult=lambda_mult
)
return results
def hybrid_search(
self,
query: str,
k: int = 4,
alpha: float = 0.5
) -> list:
"""
混合搜索(语义 + 关键词)
Args:
query: 查询文本
k: 返回数量
alpha: 混合权重(0=纯关键词,1=纯语义)
"""
# 这里简化实现,实际项目可以使用混合检索库
semantic_results = self.basic_search(query, k * 2)
# 简单重排序
scored_results = []
for i, doc in enumerate(semantic_results):
# 简单评分(实际应该使用 BM25 + 向量相似度的混合)
score = (1 - alpha) * (i / len(semantic_results)) + alpha * (1 - i / len(semantic_results))
scored_results.append((score, doc))
scored_results.sort(key=lambda x: x[0], reverse=True)
return [doc for _, doc in scored_results[:k]]
# 使用示例
searcher = SemanticSearch(kb.vectorstore)
# 基础搜索
basic_results = searcher.basic_search("如何使用公司邮箱?")
# MMR 搜索(更多样)
mmr_results = searcher.mmr_search("如何使用公司邮箱?")
10.3.2 重排序(Reranking)
from dashscope import Rerank
class Reranker:
"""重排序器"""
def __init__(self):
self.rerank_model = "gte-rerank"
def rerank(
self,
query: str,
documents: list,
top_n: int = 3
) -> list:
"""
重排序检索结果
Args:
query: 查询文本
documents: 文档列表(可以是 Document 对象或字符串)
top_n: 返回数量
Returns:
重排序后的结果
"""
# 提取文档内容
doc_texts = [
doc.page_content if hasattr(doc, 'page_content') else str(doc)
for doc in documents
]
# 调用重排序 API
response = Rerank.call(
model=self.rerank_model,
query=query,
documents=doc_texts,
top_n=top_n
)
if response.status_code != 200:
return documents[:top_n]
# 按相关性排序
results = []
for item in response.output.results:
results.append({
"index": item.index,
"document": doc_texts[item.index],
"score": item.relevance_score
})
# 返回排序后的文档
reranked = [documents[r["index"]] for r in results]
return reranked
# 使用示例
reranker = Reranker()
initial_results = searcher.basic_search("年假怎么申请?", k=10)
final_results = reranker.rerank("年假怎么申请?", initial_results, top_n=3)
10.3.3 查询扩展
class QueryExpander:
"""查询扩展器"""
def __init__(self, client):
self.client = client
def expand_query(self, query: str) -> str:
"""
扩展查询:生成多个相关查询
Returns:
扩展后的查询字符串
"""
prompt = f"""
请为以下查询生成3个不同的表述方式,以帮助更全面地检索信息。
原始查询:{query}
要求:
1. 保持原意
2. 使用不同的表述角度
3. 可以包含同义词、上下位词
输出格式:
1. [扩展查询1]
2. [扩展查询2]
3. [扩展查询3]
"""
response = self.client.chat(prompt)
# 解析扩展查询
expanded = [query] # 包含原查询
for line in response["message"].split('\n'):
line = line.strip()
if line and line[0].isdigit() and '.' in line[:3]:
expanded.append(line.split('.', 1)[-1].strip())
return expanded
def multi_query_search(self, query: str, searcher: SemanticSearch) -> list:
"""
多查询搜索:使用扩展的多个查询进行搜索
Args:
query: 原始查询
searcher: 搜索引擎
Returns:
去重后的搜索结果
"""
# 扩展查询
expanded_queries = self.expand_query(query)
# 收集所有结果
all_results = []
seen_contents = set()
for q in expanded_queries:
results = searcher.basic_search(q, k=5)
for doc in results:
# 去重
content_hash = hash(doc.page_content)
if content_hash not in seen_contents:
seen_contents.add(content_hash)
all_results.append(doc)
return all_results
# 使用示例
expander = QueryExpander(ai_client)
multi_results = expander.multi_query_search("年假政策", searcher)
10.4 RAG 流水线实现
10.4.1 完整 RAG 实现
from typing import List, Optional
from dataclasses import dataclass
from langchain.schema import Document
@dataclass
class RAGResult:
"""RAG 结果"""
answer: str
source_documents: List[Document]
query: str
retrieval_time: float
class RAGPipeline:
"""RAG 流水线"""
def __init__(
self,
vectorstore,
llm_client,
reranker: Optional[Reranker] = None,
top_k: int = 5,
final_k: int = 3
):
self.vectorstore = vectorstore
self.llm_client = llm_client
self.reranker = reranker
self.top_k = top_k
self.final_k = final_k
def retrieve(self, query: str) -> List[Document]:
"""检索相关文档"""
# 基础检索
results = self.vectorstore.similarity_search(query, k=self.top_k)
# 重排序(如果可用)
if self.reranker:
results = self.reranker.rerank(query, results, top_n=self.final_k)
else:
results = results[:self.final_k]
return results
def augment(self, query: str, documents: List[Document]) -> str:
"""构建增强后的 prompt"""
context = "\n\n".join([
f"【文档 {i+1}】\n{doc.page_content}"
for i, doc in enumerate(documents)
])
prompt = f"""基于以下参考资料回答问题。如果资料中没有相关信息,请如实说明。
【参考资料】
{context}
【问题】
{query}
【回答要求】
1. 基于资料内容回答,不要编造信息
2. 如果涉及多个文档,可以综合回答
3. 回答要清晰、有条理
4. 如有必要,可以在回答中引用"根据文档X"
"""
return prompt
def generate(self, query: str, documents: List[Document]) -> str:
"""生成答案"""
prompt = self.augment(query, documents)
response = self.llm_client.chat(
prompt,
system_prompt="你是一个知识库问答助手,基于给定的资料回答问题。"
)
return response["message"]
def run(self, query: str) -> RAGResult:
"""运行完整的 RAG 流程"""
import time
start_time = time.time()
# 1. 检索
documents = self.retrieve(query)
retrieval_time = time.time() - start_time
# 2. 生成
if documents:
answer = self.generate(query, documents)
else:
answer = "抱歉,知识库中没有找到相关信息。"
return RAGResult(
answer=answer,
source_documents=documents,
query=query,
retrieval_time=retrieval_time
)
# 使用示例
rag_pipeline = RAGPipeline(
vectorstore=kb.vectorstore,
llm_client=ai_client,
reranker=reranker,
top_k=10,
final_k=3
)
result = rag_pipeline.run("公司年假政策是什么?")
print(f"问题: {result.query}")
print(f"答案: {result.answer}")
print(f"检索耗时: {result.retrieval_time:.3f}s")
print(f"\n参考文档:")
for i, doc in enumerate(result.source_documents, 1):
print(f"{i}. {doc.page_content[:100]}...")
10.4.2 带历史记录的 RAG
class ConversationalRAG:
"""对话式 RAG"""
def __init__(self, rag_pipeline: RAGPipeline):
self.rag_pipeline = rag_pipeline
self.conversation_history = []
def run(self, query: str) -> RAGResult:
"""运行对话式 RAG"""
# 构建完整查询(包含历史)
full_query = self._build_query_with_history(query)
# 运行 RAG
result = self.rag_pipeline.run(full_query)
# 保存历史
self.conversation_history.append({
"query": query,
"answer": result.answer
})
return result
def _build_query_with_history(self, query: str) -> str:
"""结合历史对话构建完整查询"""
if not self.conversation_history:
return query
# 最近一轮对话
last = self.conversation_history[-1]
# 构建查询
history_summary = f"""
【上一轮对话】
用户问:{last['query']}
助手答:{last['answer']}
【当前问题】
{query}
"""
# 使用 LLM 理解上下文
expanded_query = self.rag_pipeline.llm_client.chat(
f"""基于以下对话历史,理解用户的当前问题。
{history_summary}
请将当前问题改写成一个完整的、独立的查询,
确保不依赖对话历史也能理解问题。
只输出改写后的查询,不要其他内容。
"""
)
return expanded_query["message"]
# 使用示例
conv_rag = ConversationalRAG(rag_pipeline)
# 第一轮
result1 = conv_rag.run("年假有多少天?")
print(result1.answer)
# 第二轮(依赖上文)
result2 = conv_rag.run("那婚假呢?")
print(result2.answer)
10.4.3 RAG 评估
class RAGEvaluator:
"""RAG 评估器"""
def __init__(self, llm_client):
self.llm_client = llm_client
def evaluate_answer_quality(
self,
question: str,
answer: str,
reference: str
) -> dict:
"""
评估答案质量
Args:
question: 问题
answer: 待评估答案
reference: 参考答案/标准答案
Returns:
评估结果
"""
prompt = f"""请评估以下问答系统的表现。
【问题】
{question}
【待评估答案】
{answer}
【参考答案】
{reference}
请从以下维度评估,每个维度1-5分:
1. 准确性:答案是否正确
2. 完整性:答案是否完整
3. 相关性:答案是否与问题相关
4. 可读性:答案是否清晰易懂
只输出以下格式:
准确性: X
完整性: X
相关性: X
可读性: X
总分: X
"""
response = self.llm_client.chat(prompt)
# 解析评分
scores = {}
for line in response["message"].split('\n'):
if ':' in line:
key, value = line.split(':', 1)
scores[key.strip()] = int(value.strip())
return scores
def evaluate_retrieval_quality(
self,
question: str,
retrieved_docs: list,
relevant_docs: list
) -> dict:
"""
评估检索质量
Returns:
评估指标:Precision@K, Recall@K, F1@K
"""
k = len(retrieved_docs)
# 获取检索到的文档
retrieved_set = set([doc.page_content for doc in retrieved_docs])
# 获取相关文档
relevant_set = set([doc.page_content for doc in relevant_docs])
# 计算指标
true_positives = len(retrieved_set & relevant_set)
precision = true_positives / len(retrieved_set) if retrieved_set else 0
recall = true_positives / len(relevant_set) if relevant_set else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {
"precision@k": precision,
"recall@k": recall,
"f1@k": f1,
"retrieved_count": len(retrieved_set),
"relevant_count": len(relevant_set),
"true_positives": true_positives
}
# 使用示例
evaluator = RAGEvaluator(ai_client)
# 评估答案
scores = evaluator.evaluate_answer_quality(
question="年假政策是什么?",
answer="公司员工每年享有带薪年假...",
reference="根据公司制度..."
)
print(scores)
10.5 企业知识库实战
10.5.1 项目结构
knowledge-base-project/
├── data/
│ ├── policies/ # 规章制度
│ ├── products/ # 产品文档
│ └── faqs/ # 常见问题
├── src/
│ ├── __init__.py
│ ├── loader.py # 文档加载
│ ├── processor.py # 文档处理
│ ├── vectorstore.py # 向量存储
│ ├── retriever.py # 检索器
│ ├── rag.py # RAG 流水线
│ └── api.py # API 服务
├── tests/
├── config.py
├── main.py
└── requirements.txt
10.5.2 批量文档处理
import os
from pathlib import Path
from typing import List
class DocumentProcessor:
"""文档处理器"""
def __init__(self, kb: KnowledgeBase):
self.kb = kb
def process_directory(
self,
directory: str,
file_extensions: List[str] = ['.pdf', '.docx', '.txt', '.md'],
metadata: dict = None
) -> int:
"""
处理目录下的所有文档
Args:
directory: 目录路径
file_extensions: 要处理的文件扩展名
metadata: 附加元数据
Returns:
处理的文档数量
"""
directory = Path(directory)
count = 0
for ext in file_extensions:
for file_path in directory.rglob(f'*{ext}'):
try:
self.process_file(file_path, metadata)
count += 1
print(f"✓ 已处理: {file_path}")
except Exception as e:
print(f"✗ 处理失败: {file_path} - {e}")
return count
def process_file(
self,
file_path: str,
metadata: dict = None
):
"""处理单个文件"""
# 加载文档
documents = load_documents(file_path)
# 添加元数据
for doc in documents:
if metadata:
doc.metadata.update(metadata)
doc.metadata['source'] = str(file_path)
# 添加到知识库
self.kb.add_documents(documents)
def process_with_categories(self, data_dir: str):
"""按分类处理文档"""
categories = {
'policies': {'category': '规章制度', 'department': '人力资源'},
'products': {'category': '产品文档', 'department': '产品部'},
'faqs': {'category': '常见问题', 'department': '客服部'},
}
for folder, metadata in categories.items():
folder_path = os.path.join(data_dir, folder)
if os.path.exists(folder_path):
count = self.process_directory(folder_path, metadata=metadata)
print(f"处理了 {count} 个 {metadata['category']} 文档")
10.5.3 API 服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI(title="企业知识库问答 API")
# 全局实例
kb = KnowledgeBase()
rag_pipeline = RAGPipeline(kb.vectorstore, ai_client)
# 请求模型
class QuestionRequest(BaseModel):
question: str
top_k: int = 5
include_sources: bool = True
class QuestionResponse(BaseModel):
answer: str
sources: Optional[List[dict]]
# API 端点
@app.post("/api/ask", response_model=QuestionResponse)
async def ask_question(request: QuestionRequest):
"""问答接口"""
try:
result = rag_pipeline.run(request.question)
sources = None
if request.include_sources and result.source_documents:
sources = [
{
"content": doc.page_content[:200] + "...",
"source": doc.metadata.get('source', 'Unknown'),
"score": 1.0 # 简化,实际可以从检索器获取
}
for doc in result.source_documents
]
return QuestionResponse(
answer=result.answer,
sources=sources
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/rebuild")
async def rebuild_knowledge_base():
"""重建知识库"""
try:
global kb, rag_pipeline
# 重新加载文档
kb = KnowledgeBase()
processor = DocumentProcessor(kb)
processor.process_with_categories('data/')
# 重新创建 RAG 流水线
rag_pipeline = RAGPipeline(kb.vectorstore, ai_client)
return {"status": "success", "message": "知识库已重建"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/stats")
async def get_stats():
"""获取知识库统计"""
return {
"document_count": len(kb.documents),
"vectorstore_size": kb.vectorstore.index.ntotal if kb.vectorstore else 0
}
10.5.4 前端界面
<!-- templates/rag-chat.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>企业知识库问答</title>
<style>
body { font-family: sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; }
.qa-container { background: #f9f9f9; border-radius: 8px; padding: 20px; }
.question { color: #1a73e8; font-weight: bold; margin-bottom: 10px; }
.answer { background: white; padding: 15px; border-radius: 8px; margin-bottom: 15px; }
.sources { font-size: 0.9em; color: #666; margin-top: 10px; }
.source-item { background: #eee; padding: 8px; margin: 5px 0; border-radius: 4px; }
.loading { color: #999; font-style: italic; }
</style>
</head>
<body>
<h1>🔍 企业知识库问答</h1>
<div class="qa-container">
<div>
<input type="text" id="question" placeholder="输入您的问题..."
style="width: 70%; padding: 10px; font-size: 16px;">
<button onclick="ask()" style="padding: 10px 20px; font-size: 16px;">提问</button>
</div>
<div id="result" style="margin-top: 20px;"></div>
</div>
<script>
async function ask() {
const question = document.getElementById('question').value;
if (!question) return;
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = `<p class="loading">正在检索知识库并生成答案...</p>`;
try {
const response = await fetch('/api/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, include_sources: true })
});
const data = await response.json();
let html = `<div class="question">Q: ${question}</div>`;
html += `<div class="answer"><strong>答案:</strong><br>${data.answer}</div>`;
if (data.sources && data.sources.length > 0) {
html += `<div class="sources"><strong>参考文档:</strong>`;
data.sources.forEach((src, i) => {
html += `<div class="source-item">${i+1}. ${src.content}</div>`;
});
html += `</div>`;
}
resultDiv.innerHTML = html;
} catch (error) {
resultDiv.innerHTML = `<p style="color: red;">出错了: ${error.message}</p>`;
}
}
</script>
</body>
</html>
本章小结
本章介绍了企业知识库问答系统的构建:
- RAG 架构:检索增强生成的工作原理
- 文档处理:PDF/Word/Markdown 加载与分割
- 向量化:使用 DashScope Embedding 模型
- 语义检索:基础搜索、MMR、重排序
- 完整流水线:检索 + 增强 + 生成的实现
- 企业实战:批量处理、API 服务、前端界面
下一章我们将学习智能客服开发,掌握多轮对话和意图识别的技术。
思考与练习
-
概念理解:解释 RAG 架构相比纯 LLM 的优势。
-
实践练习:使用 LangChain 构建一个简单的 RAG 系统。
-
性能优化:思考如何优化 RAG 系统的检索质量和响应速度。
-
功能扩展:为知识库系统添加以下功能:
- 文档自动更新
- 知识图谱增强
- 多语言支持
-
评估设计:设计一套 RAG 系统的评估指标。
第11章:智能客服开发
本章介绍如何开发一个完整的智能客服系统。从多轮对话设计、意图识别、槽位填充,到对话管理和路由,帮助你构建能够真正解决用户问题的智能客服。
11.1 智能客服系统架构
11.1.1 系统组成
┌─────────────────────────────────────────────────────────────────┐
│ 智能客服系统架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ 用户 │ │
│ │ 接入 │ │
│ └─────┬──────┘ │
│ │ │
│ ┌─────▼──────┐ ┌────────────┐ ┌────────────┐ │
│ │ 对话 │ ───► │ 意图 │ ───► │ 槽位 │ │
│ │ 管理 │ │ 识别 │ │ 填充 │ │
│ └─────┬──────┘ └────────────┘ └─────┬──────┘ │
│ │ │ │
│ │ ┌──────────────────────────────┘ │
│ │ │ │
│ ┌─────▼─────────▼──────┐ ┌────────────┐ │
│ │ 对话策略 │ ───► │ 知识库 │ │
│ │ (Policy) │ │ 查询 │ │
│ └─────┬───────────────┘ └────────────┘ │
│ │ │
│ ┌─────▼──────┐ ┌────────────┐ │
│ │ 回复 │ ───► │ API/工具 │ │
│ │ 生成 │ │ 调用 │ │
│ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
11.1.2 核心模块
| 模块 | 功能 | 技术 |
|---|---|---|
| 对话管理 | 管理对话状态、历史 | 状态机 |
| 意图识别 | 识别用户意图 | 分类模型/规则 |
| 槽位填充 | 提取关键信息 | NER/规则 |
| 知识库 | 检索答案 | RAG |
| 回复生成 | 生成自然回复 | LLM/模板 |
11.2 意图识别
11.2.1 意图定义
from enum import Enum
from typing import List, Optional
from dataclasses import dataclass
class Intent(Enum):
"""客服意图枚举"""
GREETING = "greeting" # 问候
PRODUCT_INQUIRY = "product_inquiry" # 产品咨询
ORDER_STATUS = "order_status" # 订单查询
REFUND = "refund" # 退款申请
COMPLAINT = "complaint" # 投诉
SUGGESTION = "suggestion" # 建议
GOODBYE = "goodbye" # 告别
UNKNOWN = "unknown" # 未知
@dataclass
class IntentExample:
"""意图示例"""
text: str
intent: Intent
# 意图示例库
INTENT_EXAMPLES = {
Intent.GREETING: [
"你好",
"早上好",
"在吗",
"你好,请问有人在吗",
],
Intent.PRODUCT_INQUIRY: [
"这个产品有什么特点",
"产品的价格是多少",
"能介绍一下吗",
"产品支持什么功能",
],
Intent.ORDER_STATUS: [
"我的订单到哪了",
"查一下订单",
"订单号是XXX",
"什么时候发货",
],
Intent.REFUND: [
"我要退款",
"申请退款",
"不想要了",
"申请退货",
],
Intent.COMPLAINT: [
"太差了",
"非常不满意",
"要投诉",
"服务态度差",
],
Intent.SUGGESTION: [
"建议你们",
"希望可以",
"能不能增加",
"希望改进",
],
Intent.GOODBYE: [
"再见",
"拜拜",
"谢谢,再见",
"好了,没事了",
],
}
11.2.2 基于规则的意图识别
import re
from typing import Dict, Tuple
class RuleBasedIntentClassifier:
"""基于规则的意图分类器"""
def __init__(self):
self.intent_patterns: Dict[Intent, List[str]] = {
Intent.GREETING: [
r"你好",
r"在吗",
r"早上好",
r"您好",
r"嗨[啊]?",
],
Intent.PRODUCT_INQUIRY: [
r"产品",
r"价格",
r"功能",
r"特点",
r"介绍",
r"怎么样",
],
Intent.ORDER_STATUS: [
r"订单",
r"发货",
r"物流",
r"到了吗",
r"什么时候到",
],
Intent.REFUND: [
r"退款",
r"退货",
r"取消订单",
r"不想要",
],
Intent.COMPLAINT: [
r"投诉",
r"太差",
r"不满意",
r"垃圾",
r"问题",
],
Intent.SUGGESTION: [
r"建议",
r"希望",
r"能不能",
r"应该",
],
Intent.GOODBYE: [
r"再见",
r"拜拜",
r"谢了",
r"好的",
r"知道了",
],
}
# 意图优先级(数字越大优先级越高)
self.intent_priority = {
Intent.UNKNOWN: 0,
Intent.GREETING: 1,
Intent.GOODBYE: 2,
Intent.PRODUCT_INQUIRY: 3,
Intent.ORDER_STATUS: 3,
Intent.REFUND: 4,
Intent.COMPLAINT: 5,
Intent.SUGGESTION: 3,
}
def classify(self, text: str) -> Tuple[Intent, float]:
"""
识别意图
Returns:
(意图, 置信度)
"""
text = text.lower()
matched_intents = []
for intent, patterns in self.intent_patterns.items():
for pattern in patterns:
if re.search(pattern, text):
matched_intents.append(intent)
break
if not matched_intents:
return Intent.UNKNOWN, 0.0
# 按优先级排序
matched_intents.sort(
key=lambda x: self.intent_priority[x],
reverse=True
)
best_intent = matched_intents[0]
confidence = min(0.9, 0.5 + 0.1 * len(matched_intents))
return best_intent, confidence
def add_pattern(self, intent: Intent, pattern: str):
"""添加新的模式"""
if intent not in self.intent_patterns:
self.intent_patterns[intent] = []
self.intent_patterns[intent].append(pattern)
# 使用示例
classifier = RuleBasedIntentClassifier()
test_queries = [
"你好,我想问一下产品",
"我的订单什么时候到",
"申请退款",
"这个产品有什么功能",
]
for query in test_queries:
intent, confidence = classifier.classify(query)
print(f"'{query}' → {intent.value} ({confidence:.2f})")
11.2.3 基于 LLM 的意图识别
from typing import List, Dict
class LLMIntentClassifier:
"""基于 LLM 的意图分类器"""
SYSTEM_PROMPT = """你是一个客服意图分类器。
给定用户消息,输出对应的意图类别。
可用的意图类别:
- greeting: 问候
- product_inquiry: 产品咨询
- order_status: 订单查询
- refund: 退款申请
- complaint: 投诉
- suggestion: 建议
- goodbye: 告别
- unknown: 未知
只输出意图名称,不要其他内容。"""
def __init__(self, client):
self.client = client
def classify(self, text: str) -> Tuple[Intent, float]:
"""
使用 LLM 识别意图
"""
response = self.client.chat(
text,
system_prompt=self.SYSTEM_PROMPT
)
# 解析意图
intent_text = response["message"].strip().lower()
# 映射到 Intent 枚举
intent_map = {
"greeting": Intent.GREETING,
"product_inquiry": Intent.PRODUCT_INQUIRY,
"order_status": Intent.ORDER_STATUS,
"refund": Intent.REFUND,
"complaint": Intent.COMPLAINT,
"suggestion": Intent.SUGGESTION,
"goodbye": Intent.GOODBYE,
"unknown": Intent.UNKNOWN,
}
intent = intent_map.get(intent_text, Intent.UNKNOWN)
confidence = 0.85 if intent != Intent.UNKNOWN else 0.3
return intent, confidence
class HybridIntentClassifier:
"""混合意图分类器(规则 + LLM)"""
def __init__(self, client):
self.rule_classifier = RuleBasedIntentClassifier()
self.llm_classifier = LLMIntentClassifier(client)
def classify(self, text: str) -> Tuple[Intent, float]:
"""
先用规则,再用 LLM
"""
# 先用规则
rule_intent, rule_conf = self.rule_classifier.classify(text)
# 高置信度直接返回
if rule_conf > 0.7:
return rule_intent, rule_conf
# 低置信度用 LLM 验证
llm_intent, llm_conf = self.llm_classifier.classify(text)
# 如果 LLM 更自信
if llm_conf > rule_conf:
return llm_intent, llm_conf
return rule_intent, rule_conf
11.3 槽位填充
11.3.1 槽位定义
from typing import Dict, List, Optional, Any
@dataclass
class Slot:
"""槽位定义"""
name: str
description: str
required: bool
type: str # string, number, date, enum
examples: List[str] = field(default_factory=list)
@dataclass
class SlotValue:
"""槽位值"""
slot_name: str
value: Any
confidence: float
source: str # extracted, inferred, requested
class SlotFillingSchema:
"""槽位填充模式"""
def __init__(self):
self.slots: Dict[str, Slot] = {
"product_name": Slot(
name="product_name",
description="产品名称",
required=False,
type="string",
examples=["iPhone", "MacBook", "AirPods"]
),
"order_id": Slot(
name="order_id",
description="订单号",
required=False,
type="string",
examples=["ORD123456", "订单号"]
),
"user_id": Slot(
name="user_id",
description="用户ID",
required=False,
type="string",
examples=["用户ID", "我的账号"]
),
"refund_reason": Slot(
name="refund_reason",
description="退款原因",
required=False,
type="string",
examples=["不想要了", "质量问题", "发错货了"]
),
"phone": Slot(
name="phone",
description="联系电话",
required=False,
type="string",
examples=["手机号", "电话"]
),
}
def get_required_slots(self, intent: Intent) -> List[str]:
"""获取意图所需的槽位"""
intent_slots = {
Intent.ORDER_STATUS: ["order_id", "user_id"],
Intent.REFUND: ["order_id", "refund_reason"],
Intent.COMPLAINT: ["phone"],
}
return intent_slots.get(intent, [])
def get_all_slots(self) -> List[str]:
"""获取所有槽位名"""
return list(self.slots.keys())
11.3.2 槽位提取
import re
class RuleBasedSlotExtractor:
"""基于规则的槽位提取器"""
def __init__(self, schema: SlotFillingSchema):
self.schema = schema
self.patterns = {
"order_id": [
r"订单[号]?[::]?\s*([A-Z0-9]{8,})",
r"order[::]?\s*([A-Z0-9]{8,})",
r"ORD\d+",
],
"phone": [
r"1[3-9]\d{9}", # 手机号
r"\d{3,4}[-]\d{7,8}", # 固话
],
"refund_reason": {
"不想要": "不想要了",
"质量有问题": "质量问题",
"错了": "发错货了",
"太久了": "等待时间过长",
}
}
def extract(self, text: str) -> Dict[str, SlotValue]:
"""
提取槽位
Returns:
{槽位名: 槽位值}
"""
results = {}
for slot_name, patterns in self.patterns.items():
if isinstance(patterns, list):
# 正则模式
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
value = match.group(1) if match.groups() else match.group(0)
results[slot_name] = SlotValue(
slot_name=slot_name,
value=value,
confidence=0.9,
source="extracted"
)
break
else:
# 字典映射
for keyword, value in patterns.items():
if keyword in text:
results[slot_name] = SlotValue(
slot_name=slot_name,
value=value,
confidence=0.8,
source="extracted"
)
break
return results
class LLMSlotExtractor:
"""基于 LLM 的槽位提取"""
def __init__(self, client, schema: SlotFillingSchema):
self.client = client
self.schema = schema
def extract(self, text: str, intent: Intent) -> Dict[str, SlotValue]:
"""使用 LLM 提取槽位"""
slot_names = self.schema.get_all_slots()
prompt = f"""从以下用户消息中提取信息:
消息:{text}
需要提取的字段:
{chr(10).join(f'- {s}: {self.schema.slots[s].description}' for s in slot_names)}
以JSON格式输出:
{{"字段名": "提取的值"}}
只输出JSON,不要其他内容。
"""
response = self.client.chat(prompt)
# 解析 JSON
import json
try:
extracted = json.loads(response["message"])
results = {}
for slot_name, value in extracted.items():
if value and slot_name in slot_names:
results[slot_name] = SlotValue(
slot_name=slot_name,
value=value,
confidence=0.85,
source="llm"
)
return results
except:
return {}
class HybridSlotExtractor:
"""混合槽位提取器"""
def __init__(self, client, schema: SlotFillingSchema):
self.schema = schema
self.rule_extractor = RuleBasedSlotExtractor(schema)
self.llm_extractor = LLMSlotExtractor(client, schema)
def extract(self, text: str, intent: Intent) -> Dict[str, SlotValue]:
"""先规则后 LLM"""
# 先用规则提取
results = self.rule_extractor.extract(text)
# 检查是否缺少必要槽位
required_slots = self.schema.get_required_slots(intent)
missing_slots = [s for s in required_slots if s not in results]
if missing_slots:
# 用 LLM 补充提取
llm_results = self.llm_extractor.extract(text, intent)
for slot_name in missing_slots:
if slot_name in llm_results:
results[slot_name] = llm_results[slot_name]
return results
11.4 对话管理
11.4.1 对话状态
from enum import Enum
from typing import Dict, List, Optional
from dataclasses import dataclass, field
import json
class DialogueState(Enum):
"""对话状态"""
START = "start"
INTENT_CONFIRM = "intent_confirm"
SLOT_FILLING = "slot_filling"
WAITING_ANSWER = "waiting_answer"
ANSWERING = "answering"
CONFIRMATION = "confirmation"
END = "end"
@dataclass
class DialogueContext:
"""对话上下文"""
session_id: str
user_id: str
state: DialogueState = DialogueState.START
current_intent: Optional[Intent] = None
slots: Dict[str, SlotValue] = field(default_factory=dict)
history: List[Dict] = field(default_factory=list)
created_at: str = ""
updated_at: str = ""
def add_turn(self, user_message: str, assistant_message: str):
"""添加对话轮次"""
self.history.append({
"user": user_message,
"assistant": assistant_message
})
def get_missing_slots(self, schema: SlotFillingSchema) -> List[str]:
"""获取缺失的必要槽位"""
if not self.current_intent:
return []
required = schema.get_required_slots(self.current_intent)
return [s for s in required if s not in self.slots]
def is_complete(self, schema: SlotFillingSchema) -> bool:
"""检查是否收集完所有必要槽位"""
return len(self.get_missing_slots(schema)) == 0
def to_dict(self) -> dict:
"""序列化为字典"""
return {
"session_id": self.session_id,
"user_id": self.user_id,
"state": self.state.value,
"current_intent": self.current_intent.value if self.current_intent else None,
"slots": {
k: {"name": v.slot_name, "value": v.value, "confidence": v.confidence}
for k, v in self.slots.items()
},
"history": self.history,
}
11.4.2 对话管理器
class DialogueManager:
"""对话管理器"""
def __init__(
self,
intent_classifier,
slot_extractor,
schema: SlotFillingSchema,
knowledge_base: KnowledgeBase = None
):
self.intent_classifier = intent_classifier
self.slot_extractor = slot_extractor
self.schema = schema
self.knowledge_base = knowledge_base
# 对话上下文存储
self.contexts: Dict[str, DialogueContext] = {}
# 意图确认阈值
self.intent_confirm_threshold = 0.6
def get_context(self, session_id: str, user_id: str) -> DialogueContext:
"""获取或创建对话上下文"""
if session_id not in self.contexts:
self.contexts[session_id] = DialogueContext(
session_id=session_id,
user_id=user_id
)
return self.contexts[session_id]
def process(self, session_id: str, user_id: str, message: str) -> str:
"""
处理用户消息,返回助手回复
"""
# 获取上下文
context = self.get_context(session_id, user_id)
# 根据状态处理
if context.state == DialogueState.START:
return self._handle_start(context, message)
elif context.state == DialogueState.INTENT_CONFIRM:
return self._handle_intent_confirm(context, message)
elif context.state == DialogueState.SLOT_FILLING:
return self._handle_slot_filling(context, message)
elif context.state == DialogueState.ANSWERING:
return self._handle_answering(context, message)
elif context.state == DialogueState.CONFIRMATION:
return self._handle_confirmation(context, message)
else:
return self._handle_end(context, message)
def _handle_start(self, context: DialogueContext, message: str) -> str:
"""处理开始状态"""
# 意图识别
intent, confidence = self.intent_classifier.classify(message)
context.current_intent = intent
# 简单意图直接处理
if intent in [Intent.GREETING]:
context.state = DialogueState.ANSWERING
return self._generate_greeting()
if intent in [Intent.GOODBYE]:
context.state = DialogueState.END
return "再见!有什么问题随时再来问我。"
# 需要确认或需要槽位填充
if confidence < self.intent_confirm_threshold:
context.state = DialogueState.INTENT_CONFIRM
return f"您是想了解'{intent.value}'吗?请确认或更正。"
# 提取槽位
self._extract_slots(context, message)
# 检查是否需要更多信息
return self._check_and_collect_slots(context)
def _handle_intent_confirm(self, context: DialogueContext, message: str) -> str:
"""处理意图确认"""
# 用户确认
if any(word in message for word in ["是", "对的", "没错", "正确"]):
context.state = DialogueState.SLOT_FILLING
return self._check_and_collect_slots(context)
# 用户更正
intent, confidence = self.intent_classifier.classify(message)
context.current_intent = intent
if confidence > self.intent_confirm_threshold:
self._extract_slots(context, message)
return self._check_and_collect_slots(context)
return "抱歉,我没能理解。请问您想咨询什么问题?"
def _handle_slot_filling(self, context: DialogueContext, message: str) -> str:
"""处理槽位填充"""
# 提取槽位
self._extract_slots(context, message)
# 检查是否完整
return self._check_and_collect_slots(context)
def _handle_answering(self, context: DialogueContext, message: str) -> str:
"""处理回答状态"""
# 如果用户继续问问题
intent, confidence = self.intent_classifier.classify(message)
if intent == Intent.GOODBYE:
context.state = DialogueState.END
return "再见!有什么问题随时再来问我。"
# 作为新问题处理
context.current_intent = intent
return self._handle_start(context, message)
def _handle_confirmation(self, context: DialogueContext, message: str) -> str:
"""处理确认"""
if any(word in message for word in ["是", "好的", "确认", "同意"]):
return self._execute_action(context)
if any(word in message for word in ["否", "不对", "取消"]):
context.state = DialogueState.SLOT_FILLING
return "好的,请告诉我正确的信息。"
return "请确认以上信息是否正确(是/否)"
def _handle_end(self, context: DialogueContext, message: str) -> str:
"""处理结束状态"""
return "对话已结束。输入新消息开始新的对话。"
def _extract_slots(self, context: DialogueContext, message: str):
"""提取槽位"""
if not context.current_intent:
return
slots = self.slot_extractor.extract(message, context.current_intent)
for slot_name, slot_value in slots.items():
# 如果已存在,保留置信度高的
if slot_name not in context.slots or \
slot_value.confidence > context.slots[slot_name].confidence:
context.slots[slot_name] = slot_value
def _check_and_collect_slots(self, context: DialogueContext) -> str:
"""检查并收集槽位"""
missing = context.get_missing_slots(self.schema)
if not missing:
# 槽位已收集完整
context.state = DialogueState.CONFIRMATION
return self._summarize_and_confirm(context)
# 请求缺失的槽位
context.state = DialogueState.SLOT_FILLING
slot = self.schema.slots[missing[0]]
# 友好的询问方式
slot_questions = {
"order_id": "请问您的订单号是多少?",
"refund_reason": "请问退款的原因是什么?",
"phone": "请留下您的联系电话,方便我们联系您。",
"user_id": "请问您的用户ID是?",
}
return slot_questions.get(missing[0], f"请提供您的{slot.description}。")
def _summarize_and_confirm(self, context: DialogueContext) -> str:
"""总结并确认"""
intent = context.current_intent.value
slots_info = "\n".join([
f"- {self.schema.slots[k].description}: {v.value}"
for k, v in context.slots.items()
])
return f"""好的,我来帮您处理{intent}:
{slots_info}
请确认以上信息是否正确。"""
def _generate_greeting(self) -> str:
"""生成问候语"""
return """您好!我是智能客服,很高兴为您服务。
我可以帮您:
📦 查询订单状态
💰 申请退款
📋 了解产品信息
📝 提供建议
❓ 解答其他问题
请告诉我您想了解什么?"""
def _execute_action(self, context: DialogueContext) -> str:
"""执行业务动作"""
# 根据意图执行不同的动作
if context.current_intent == Intent.ORDER_STATUS:
return self._handle_order_status(context)
elif context.current_intent == Intent.REFUND:
return self._handle_refund(context)
else:
return self._handle_general_inquiry(context)
def _handle_order_status(self, context: DialogueContext) -> str:
"""处理订单查询"""
order_id = context.slots.get("order_id")
if order_id:
# 实际项目中调用订单系统 API
return f"根据订单号 {order_id.value},您的订单正在配送中,预计明天送达。"
return "抱歉,未能查询到您的订单信息。"
def _handle_refund(self, context: DialogueContext) -> str:
"""处理退款申请"""
return "您的退款申请已提交,我们将在1-3个工作日内处理,请保持手机畅通。"
def _handle_general_inquiry(self, context: DialogueContext) -> str:
"""处理一般咨询"""
# 使用知识库
if self.knowledge_base:
result = self.knowledge_base.similarity_search(
context.current_intent.value,
k=1
)
if result:
return result[0].page_content
return "抱歉,这个问题我暂时无法回答,请联系人工客服。"
11.5 智能客服实战
11.5.1 完整实现
#!/usr/bin/env python3
"""
smart_customer_service.py
智能客服完整实现
"""
from typing import Dict
import uuid
from dataclasses import dataclass, field
class SmartCustomerService:
"""智能客服"""
def __init__(self, knowledge_base=None):
self.intent_classifier = RuleBasedIntentClassifier()
self.slot_extractor = RuleBasedSlotExtractor(SlotFillingSchema())
self.dialogue_manager = DialogueManager(
self.intent_classifier,
self.slot_extractor,
SlotFillingSchema(),
knowledge_base
)
# 会话管理
self.sessions: Dict[str, str] = {} # user_id -> session_id
def chat(self, user_id: str, message: str) -> str:
"""处理用户消息"""
# 获取或创建会话
if user_id not in self.sessions:
self.sessions[user_id] = str(uuid.uuid4())
session_id = self.sessions[user_id]
# 处理消息
response = self.dialogue_manager.process(session_id, user_id, message)
return response
def reset_session(self, user_id: str):
"""重置会话"""
if user_id in self.sessions:
del self.sessions[user_id]
# 使用示例
service = SmartCustomerService()
print("=== 智能客服对话 ===\n")
dialogue = [
("user123", "你好"),
("user123", "我想查一下订单"),
("user123", "订单号是ORD123456"),
("user123", "好的"),
("user123", "谢谢,再见"),
]
for user_id, message in dialogue:
print(f"用户: {message}")
response = service.chat(user_id, message)
print(f"客服: {response}\n")
11.5.2 Web API
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="智能客服 API")
# 全局客服实例
service = SmartCustomerService(knowledge_base=kb)
class ChatRequest(BaseModel):
user_id: str
message: str
reset: bool = False
class ChatResponse(BaseModel):
response: str
session_id: str
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""对话接口"""
if request.reset:
service.reset_session(request.user_id)
response = service.chat(request.user_id, request.message)
session_id = service.sessions.get(request.user_id, "")
return ChatResponse(response=response, session_id=session_id)
@app.get("/api/history/{user_id}")
async def get_history(user_id: str):
"""获取对话历史"""
context = service.dialogue_manager.contexts.get(
service.sessions.get(user_id, "")
)
if not context:
return {"history": []}
return {"history": context.history}
@app.post("/api/reset/{user_id}")
async def reset(user_id: str):
"""重置会话"""
service.reset_session(user_id)
return {"status": "reset"}
11.5.3 前端界面
<!-- customer-service.html -->
<!DOCTYPE html>
<html>
<head>
<title>智能客服</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
.chat-box { border: 1px solid #ddd; border-radius: 8px; height: 400px; overflow-y: auto; padding: 15px; }
.message { margin: 10px 0; padding: 10px; border-radius: 8px; }
.user { background: #e3f2fd; margin-left: 20%; }
.bot { background: #f5f5f5; margin-right: 20%; }
.input-area { display: flex; margin-top: 15px; }
.input-area input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.input-area button { padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }
.status { font-size: 12px; color: #666; margin-top: 5px; }
</style>
</head>
<body>
<h1>🤖 智能客服</h1>
<div id="chatBox" class="chat-box"></div>
<div class="input-area">
<input type="text" id="messageInput" placeholder="输入您的问题...">
<button onclick="sendMessage()">发送</button>
</div>
<script>
let userId = 'user_' + Math.random().toString(36).substr(2, 9);
function addMessage(text, isUser) {
const chatBox = document.getElementById('chatBox');
const div = document.createElement('div');
div.className = 'message ' + (isUser ? 'user' : 'bot');
div.textContent = text;
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
}
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
addMessage(message, true);
input.value = '';
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userId, message })
});
const data = await response.json();
addMessage(data.response, false);
} catch (error) {
addMessage('抱歉,服务暂时不可用。', false);
}
}
// 回车发送
document.getElementById('messageInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
本章小结
本章介绍了智能客服系统的开发:
- 意图识别:基于规则和 LLM 的意图分类
- 槽位填充:提取关键信息(订单号、电话等)
- 对话管理:状态机驱动的对话流程
- 知识集成:与知识库结合的回答机制
- 实战案例:完整的客服系统实现
下一章我们将学习内容生成与创作助手,掌握批量处理和结构化输出的技术。
思考与练习
-
概念理解:解释意图识别和槽位填充在对话系统中的作用。
-
实践练习:实现一个简单的 FAQ 问答机器人。
-
系统设计:设计一个多轮对话的客服系统,处理用户投诉。
-
优化思考:如何提高意图识别的准确率?
-
扩展功能:为客服系统添加转人工功能。
第12章:内容生成与创作助手
本章介绍如何使用 AI 构建内容生成与创作助手。从文本生成、结构化输出,到批量处理和任务队列,帮助你构建高效的内容创作系统。
12.1 内容生成概述
12.1.1 应用场景
| 场景 | 示例 |
|---|---|
| 营销文案 | 产品描述、广告语、社交媒体帖子 |
| 新闻摘要 | 文章摘要、标题生成 |
| 内容改写 | 风格转换、长度调整 |
| 结构化输出 | JSON、表格、代码 |
| 批量生成 | SEO文章、产品描述 |
12.1.2 系统架构
┌─────────────────────────────────────────────────────────────────┐
│ 内容生成系统架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 内容 │ ───► │ Prompt │ ───► │ AI │ │
│ │ 模板库 │ │ 引擎 │ │ API │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │ │
│ ┌────────────┐ ┌────────────┐ │ │
│ │ 质量 │ ◄── │ 输出 │ ◄─────────────┘ │
│ │ 控制 │ │ 处理 │ │
│ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2 结构化输出
12.2.1 JSON 输出
import json
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, Field
from datetime import datetime
class Product(BaseModel):
"""产品数据模型"""
name: str = Field(description="产品名称")
price: float = Field(description="价格(元)")
category: str = Field(description="产品类别")
tags: List[str] = Field(default_factory=list, description="产品标签")
description: Optional[str] = Field(None, description="产品描述")
def generate_structured_content(
client,
prompt: str,
output_schema: dict
) -> Dict[str, Any]:
"""
生成结构化内容
Args:
client: AI 客户端
prompt: 内容要求
output_schema: JSON Schema
Returns:
结构化数据
"""
schema_str = json.dumps(output_schema, ensure_ascii=False, indent=2)
full_prompt = f"""{prompt}
请严格按照以下JSON Schema输出,只输出JSON,不要任何其他内容:
```json
{schema_str}
“”“
response = client.chat(full_prompt)
content = response["message"].strip()
# 尝试解析 JSON
# 去掉可能的markdown代码块
if content.startswith("```"):
lines = content.split('\n')
content = '\n'.join(lines[1:-1])
try:
return json.loads(content)
except json.JSONDecodeError:
return {"error": "解析失败", "raw": content}
使用示例
schema = { “type”: “object”, “properties”: { “name”: {“type”: “string”, “description”: “产品名称”}, “price”: {“type”: “number”, “description”: “价格”}, “category”: {“type”: “string”, “description”: “类别”}, “tags”: {“type”: “array”, “items”: {“type”: “string”}}, “description”: {“type”: “string”} }, “required”: [“name”, “price”, “category”] }
result = generate_structured_content( client, prompt=“为一台新款笔记本电脑生成产品信息”, output_schema=schema ) print(json.dumps(result, ensure_ascii=False, indent=2))
### 12.2.2 表格输出
```python
from typing import List
def generate_table_content(
client,
headers: List[str],
rows: int,
topic: str
) -> str:
"""
生成表格内容
Args:
headers: 表头
rows: 行数
topic: 表格主题
Returns:
Markdown 表格
"""
headers_str = " | ".join(headers)
separator = " | ".join(["---"] * len(headers))
prompt = f"""请生成一个关于"{topic}"的表格:
表头:{headers_str}
生成 {rows} 行数据,以 Markdown 表格格式输出。
要求:
1. 数据要合理、真实
2. 每列内容要多样化
3. 只输出表格,不要其他内容
"""
response = client.chat(prompt)
return response["message"]
# 使用示例
table = generate_table_content(
client,
headers=["书名", "作者", "价格", "评分"],
rows=5,
topic="Python 编程书籍推荐"
)
print(table)
12.2.3 带验证的输出
from typing import Callable, Any
import re
class OutputValidator:
"""输出验证器"""
def __init__(self):
self.validators = {}
def register(self, field: str, validator: Callable):
"""注册验证器"""
self.validators[field] = validator
def validate(self, data: dict) -> tuple[bool, List[str]]:
"""
验证数据
Returns:
(是否通过, 错误列表)
"""
errors = []
for field, validator in self.validators.items():
if field in data:
try:
if not validator(data[field]):
errors.append(f"{field} 验证失败")
except Exception as e:
errors.append(f"{field} 验证错误: {str(e)}")
return len(errors) == 0, errors
# 内置验证器
Validators = {
"email": lambda x: bool(re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', x)),
"phone": lambda x: bool(re.match(r'^1[3-9]\d{9}$', x)),
"price": lambda x: isinstance(x, (int, float)) and x > 0,
"url": lambda x: x.startswith(("http://", "https://")),
"non_empty": lambda x: bool(x and len(str(x).strip()) > 0),
}
def generate_with_validation(
client,
prompt: str,
schema: dict,
validators: dict = None
) -> dict:
"""
生成并验证结构化内容
"""
# 生成
result = generate_structured_content(client, prompt, schema)
if "error" in result:
return result
# 验证
validator = OutputValidator()
if validators:
for field, validator_name in validators.items():
if validator_name in Validators:
validator.register(field, Validators[validator_name])
is_valid, errors = validator.validate(result)
if is_valid:
result["_validation"] = "passed"
else:
result["_validation"] = "failed"
result["_errors"] = errors
return result
12.3 批量内容生成
12.3.1 基础批量处理
import asyncio
from typing import List, Dict, Callable
from concurrent.futures import ThreadPoolExecutor
import time
class BatchGenerator:
"""批量内容生成器"""
def __init__(self, client, max_workers: int = 3):
self.client = client
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.results = []
def generate_batch(
self,
prompts: List[str],
template: str = None,
show_progress: bool = True
) -> List[Dict]:
"""
批量生成内容
Args:
prompts: 提示词列表
template: 可选的输出模板
show_progress: 显示进度
Returns:
生成结果列表
"""
results = []
for i, prompt in enumerate(prompts):
if show_progress:
print(f"处理 {i+1}/{len(prompts)}...")
try:
if template:
prompt = template.format(prompt=prompt)
response = self.client.chat(prompt)
results.append({
"prompt": prompt,
"result": response["message"],
"status": "success",
"usage": response.get("usage", {})
})
except Exception as e:
results.append({
"prompt": prompt,
"result": None,
"status": "error",
"error": str(e)
})
self.results = results
return results
def generate_product_descriptions(
self,
products: List[Dict]
) -> List[Dict]:
"""
批量生成产品描述
"""
prompts = []
for product in products:
prompt = f"""为以下产品生成一段吸引人的产品描述:
产品名称:{product.get('name', '')}
产品类别:{product.get('category', '')}
产品特点:{product.get('features', '')}
要求:
1. 50-100字
2. 突出产品卖点
3. 吸引目标用户
4. 包含行动号召
"""
prompts.append(prompt)
return self.generate_batch(prompts)
# 使用示例
products = [
{"name": "无线蓝牙耳机", "category": "电子产品", "features": "降噪、长续航、舒适佩戴"},
{"name": "运动跑步鞋", "category": "运动鞋", "features": "轻便、透气、防滑"},
{"name": "保温杯", "category": "生活用品", "features": "不锈钢、保冷保热、大容量"},
]
generator = BatchGenerator(client, max_workers=2)
results = generator.generate_product_descriptions(products)
for i, r in enumerate(results):
print(f"\n【产品 {i+1}】")
print(f"描述: {r['result']}")
12.3.2 异步批量处理
import asyncio
from typing import List
import aiohttp
class AsyncBatchGenerator:
"""异步批量生成器"""
def __init__(self, client, semaphore: int = 5):
self.client = client
self.semaphore = asyncio.Semaphore(semaphore)
async def generate_one(self, prompt: str) -> Dict:
"""生成单个内容"""
async with self.semaphore:
try:
# 模拟异步调用
await asyncio.sleep(0.1) # 模拟网络延迟
response = await asyncio.to_thread(
self.client.chat, prompt
)
return {
"prompt": prompt,
"result": response["message"],
"status": "success"
}
except Exception as e:
return {
"prompt": prompt,
"result": None,
"status": "error",
"error": str(e)
}
async def generate_batch(self, prompts: List[str]) -> List[Dict]:
"""批量异步生成"""
tasks = [self.generate_one(p) for p in prompts]
results = await asyncio.gather(*tasks)
return results
async def generate_with_progress(
self,
prompts: List[str],
callback: Callable = None
) -> List[Dict]:
"""带进度的批量生成"""
results = []
total = len(prompts)
for i, prompt in enumerate(prompts):
result = await self.generate_one(prompt)
results.append(result)
if callback:
callback(i + 1, total, result)
return results
async def main():
generator = AsyncBatchGenerator(client, semaphore=3)
prompts = [f"生成关于主题{i}的内容" for i in range(10)]
def progress_callback(current, total, result):
print(f"进度: {current}/{total}")
results = await generator.generate_with_progress(prompts, progress_callback)
for r in results:
print(f"✓ {r['prompt'][:30]}...")
# 运行
asyncio.run(main())
12.3.3 任务队列
import queue
import threading
from enum import Enum
from dataclasses import dataclass
from typing import Callable, Optional
import uuid
class TaskStatus(Enum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class GenerationTask:
"""生成任务"""
task_id: str
prompt: str
template: Optional[str] = None
metadata: dict = None
status: TaskStatus = TaskStatus.PENDING
result: Optional[str] = None
error: Optional[str] = None
created_at: str = ""
completed_at: str = ""
class TaskQueue:
"""任务队列"""
def __init__(self, client, num_workers: int = 2):
self.client = client
self.num_workers = num_workers
self.queue = queue.Queue()
self.tasks: Dict[str, GenerationTask] = {}
self.workers: List[threading.Thread] = []
self.running = False
def start(self):
"""启动工作线程"""
self.running = True
for _ in range(self.num_workers):
t = threading.Thread(target=self._worker, daemon=True)
t.start()
self.workers.append(t)
def stop(self):
"""停止工作线程"""
self.running = False
for _ in range(self.num_workers):
self.queue.put(None) # 发送停止信号
for t in self.workers:
t.join(timeout=1)
def _worker(self):
"""工作线程"""
while self.running:
task = self.queue.get()
if task is None:
break
self._process_task(task)
def _process_task(self, task: GenerationTask):
"""处理任务"""
task.status = TaskStatus.PROCESSING
try:
prompt = task.prompt
if task.template:
prompt = task.template.format(**task.metadata or {})
response = self.client.chat(prompt)
task.result = response["message"]
task.status = TaskStatus.COMPLETED
except Exception as e:
task.error = str(e)
task.status = TaskStatus.FAILED
def submit(self, prompt: str, template: str = None, metadata: dict = None) -> str:
"""
提交任务
Returns:
任务ID
"""
task_id = str(uuid.uuid4())
task = GenerationTask(
task_id=task_id,
prompt=prompt,
template=template,
metadata=metadata
)
self.tasks[task_id] = task
self.queue.put(task)
return task_id
def get_status(self, task_id: str) -> Optional[GenerationTask]:
"""获取任务状态"""
return self.tasks.get(task_id)
def get_result(self, task_id: str, timeout: float = 30) -> Optional[str]:
"""
获取任务结果(阻塞等待)
"""
task = self.tasks.get(task_id)
if not task:
return None
# 轮询等待
import time
start = time.time()
while task.status in [TaskStatus.PENDING, TaskStatus.PROCESSING]:
if time.time() - start > timeout:
return None
time.sleep(0.1)
return task.result if task.status == TaskStatus.COMPLETED else None
# 使用示例
task_queue = TaskQueue(client, num_workers=2)
task_queue.start()
# 提交任务
task_ids = []
for i in range(5):
task_id = task_queue.submit(
prompt=f"写一篇关于主题{i}的短文",
metadata={"index": i}
)
task_ids.append(task_id)
print(f"提交任务: {task_id}")
# 获取结果
time.sleep(5) # 等待处理
for task_id in task_ids:
task = task_queue.get_status(task_id)
print(f"任务 {task_id}: {task.status.value}")
task_queue.stop()
12.4 内容创作模板
12.4.1 营销文案模板
class MarketingContentGenerator:
"""营销内容生成器"""
def __init__(self, client):
self.client = client
def generate_product_description(
self,
product_name: str,
category: str,
features: List[str],
target_audience: str,
tone: str = "professional"
) -> str:
"""生成产品描述"""
prompt = f"""为以下产品生成营销文案:
产品名称:{product_name}
产品类别:{category}
产品特点:
{chr(10).join(f'- {f}' for f in features)}
目标用户:{target_audience}
文案风格:{tone}
要求:
1. 开头引人注目
2. 突出核心卖点
3. 针对目标用户痛点
4. 包含行动号召
5. 100-200字
"""
return self.client.chat(prompt)["message"]
def generate_social_media_post(
self,
content: str,
platform: str = "wechat"
) -> str:
"""生成社交媒体帖子"""
platform_guide = {
"wechat": "微信公众号风格,可读性强,适当使用emoji",
"weibo": "微博风格,140字以内,适当话题标签",
"xiaohongshu": "小红书风格,种草笔记,亲切分享",
}
prompt = f"""将以下内容改写成{platform}风格的帖子:
原文:
{content}
平台特点:{platform_guide.get(platform, '')}
要求:
1. 适应平台风格
2. 吸引目标读者
3. 适当添加互动元素
"""
return self.client.chat(prompt)["message"]
def generate_email(
self,
subject: str,
purpose: str,
content: str,
recipient: str = "客户"
) -> str:
"""生成营销邮件"""
prompt = f"""撰写一封营销邮件:
主题:{subject}
目的:{purpose}
收件人:{recipient}
主要内容:
{content}
要求:
1. 主题行吸引人
2. 正文结构清晰
3. 包含明确的CTA
4. 专业但不生硬
"""
return self.client.chat(prompt)["message"]
# 使用示例
gen = MarketingContentGenerator(client)
# 产品描述
desc = gen.generate_product_description(
product_name="智能手环",
category="可穿戴设备",
features=[
"24小时心率监测",
"睡眠质量分析",
"7天超长续航",
"50米防水"
],
target_audience="注重健康的年轻人",
tone="活力、专业"
)
print(desc)
12.4.2 文章生成模板
class ArticleGenerator:
"""文章生成器"""
def __init__(self, client):
self.client = client
def generate_blog_post(
self,
topic: str,
target_length: int = 1000,
style: str = "informative"
) -> Dict[str, str]:
"""生成博客文章"""
# 生成标题
title_prompt = f"""为"{topic}"生成5个吸引人的博客标题:
要求:
1. 简洁有力
2. SEO友好
3. 引起读者兴趣
4. 格式:只输出标题,每行一个
"""
titles_response = self.client.chat(title_prompt)
titles = [
t.strip() for t in titles_response["message"].split('\n')
if t.strip()
]
# 生成正文
body_prompt = f"""写一篇关于"{topic}"的博客文章:
要求:
- 字数:约{target_length}字
- 风格:{style}
- 结构:引言、3-5个要点、总结
- 包含实用信息和见解
"""
body_response = self.client.chat(body_prompt)
return {
"title": titles[0] if titles else topic,
"titles": titles,
"body": body_response["message"]
}
def generate_seo_article(
self,
keyword: str,
competitors: List[str] = None
) -> Dict[str, str]:
"""生成 SEO 文章"""
competitor_info = ""
if competitors:
competitor_info = f"\n竞争对手标题参考:\n" + "\n".join(f"- {c}" for c in competitors)
# 生成大纲
outline_prompt = f"""为关键词"{keyword}"生成文章大纲:
{competitor_info}
要求:
1. 覆盖关键词的各个方面
2. 有独特视角
3. 便于搜索引擎收录
4. 格式:列出H2、H3标题
"""
outline_response = self.client.chat(outline_prompt)
# 生成内容
content_prompt = f"""根据以下大纲,写一篇关于"{keyword}"的SEO文章:
大纲:
{outline_response['message']}
SEO要求:
1. 关键词自然出现3-5次
2. 包含小标题
3. 有列表和段落
4. 500-800字
5. 结尾有总结和CTA
"""
content_response = self.client.chat(content_prompt)
return {
"outline": outline_response["message"],
"content": content_response["message"],
"keyword": keyword
}
# 使用示例
article_gen = ArticleGenerator(client)
article = article_gen.generate_blog_post(
topic="Python异步编程",
target_length=800,
style="技术教程"
)
print(f"标题: {article['title']}")
print(f"\n正文:\n{article['body']}")
12.4.3 代码生成模板
class CodeGenerator:
"""代码生成器"""
def __init__(self, client):
self.client = client
def generate_code(
self,
description: str,
language: str = "python",
framework: str = None
) -> str:
"""生成代码"""
framework_info = f"\n使用框架:{framework}" if framework else ""
prompt = f"""请生成{language}代码:
需求描述:
{description}
{language}语言
{framework_info}
要求:
1. 代码完整可运行
2. 有适当的注释
3. 包含错误处理
4. 遵循最佳实践
"""
response = self.client.chat(prompt)
return response["message"]
def explain_code(self, code: str, language: str = "python") -> str:
"""解释代码"""
prompt = f"""解释以下{language}代码的工作原理:
```{language}
{code}
请解释:
-
整体功能
-
关键逻辑
-
重要的函数或变量 “”“ return self.client.chat(prompt)[“message”]
def review_code(self, code: str, language: str = “python”) -> Dict[str, str]: “”“代码审查”“” prompt = f“““审查以下{language}代码:
{code}
请从以下维度审查:
- 正确性
- 安全性
- 性能
- 可维护性
输出格式:
问题列表
正确性
…
安全性
…
性能
…
改进建议
… “”“ response = self.client.chat(prompt)
# 简单解析
sections = {}
current_section = None
for line in response["message"].split('\n'):
if line.startswith('## '):
current_section = line[3:].strip()
sections[current_section] = []
elif current_section and line.strip():
sections[current_section].append(line)
return {
"review": response["message"],
"sections": {k: '\n'.join(v) for k, v in sections.items()}
}
使用示例
code_gen = CodeGenerator(client)
code = code_gen.generate_code( description=“一个Web服务,接收用户上传的图片,缩放到800x600,保存到本地”, language=“python”, framework=“Flask” ) print(code)
## 12.5 质量控制
### 12.5.1 输出质量评估
```python
class QualityChecker:
"""质量检查器"""
def __init__(self, client):
self.client = client
self.quality_rules = {
"min_length": 50,
"max_length": 2000,
"required_elements": [],
"forbidden_words": ["假的", "假的", "不可能", "绝对"],
}
def check_quality(self, content: str, rules: dict = None) -> Dict[str, Any]:
"""检查内容质量"""
rules = rules or self.quality_rules
issues = []
# 长度检查
if len(content) < rules["min_length"]:
issues.append(f"内容过短({len(content)}字 < {rules['min_length']}字)")
if len(content) > rules["max_length"]:
issues.append(f"内容过长({len(content)}字 > {rules['max_length']}字)")
# 敏感词检查
for word in rules["forbidden_words"]:
if word in content:
issues.append(f"包含敏感词:{word}")
# 必需元素检查
for element in rules.get("required_elements", []):
if element not in content:
issues.append(f"缺少必需内容:{element}")
return {
"passed": len(issues) == 0,
"issues": issues,
"length": len(content),
"word_count": len(content.replace(" ", ""))
}
def auto_fix(self, content: str, issues: List[str]) -> str:
"""自动修复问题"""
prompt = f"""请修复以下内容中的问题:
原文:
{content}
问题列表:
{chr(10).join(f'- {issue}' for issue in issues)}
请修改后输出完整内容,确保:
1. 修复所有问题
2. 保持原意
3. 质量达标
"""
return self.client.chat(prompt)["message"]
def generate_with_quality_control(
client,
prompt: str,
quality_rules: dict = None
) -> str:
"""
带质量控制的内容生成
"""
checker = QualityChecker(client)
# 生成
response = client.chat(prompt)
content = response["message"]
# 检查
result = checker.check_quality(content, quality_rules)
if not result["passed"]:
# 尝试修复
content = checker.auto_fix(content, result["issues"])
# 再次检查
result = checker.check_quality(content, quality_rules)
if not result["passed"]:
return {
"content": content,
"quality": "failed",
"issues": result["issues"]
}
return {
"content": content,
"quality": "passed",
"issues": []
}
12.5.2 多版本生成与选择
def generate_multiple_versions(
client,
prompt: str,
num_versions: int = 3
) -> List[Dict]:
"""
生成多个版本并评估
"""
versions = []
for i in range(num_versions):
# 轻微变化 temperature
temperature = 0.7 + i * 0.1
response = client.chat(
prompt,
temperature=temperature,
metadata={"version": i + 1}
)
versions.append({
"version": i + 1,
"content": response["message"],
"temperature": temperature
})
return versions
def select_best_version(
client,
versions: List[Dict],
criteria: str = "质量"
) -> Dict:
"""
选择最佳版本
"""
version_contents = "\n\n".join([
f"=== 版本 {v['version']} ===\n{v['content']}"
for v in versions
])
prompt = f"""请根据以下标准,从{len(versions)}个版本中选择最佳的一个:
选择标准:{criteria}
{version_contents}
请分析每个版本的优缺点,然后输出:
最佳版本:X
理由:...
"""
response = client.chat(prompt)
# 解析响应
for v in versions:
if str(v["version"]) in response["message"]:
return v
return versions[0] # 默认返回第一个
# 使用示例
versions = generate_multiple_versions(
client,
prompt="为一篇关于AI的文章写开头",
num_versions=3
)
best = select_best_version(client, versions, "可读性和吸引力")
print(f"最佳版本: {best['version']}")
print(best['content'])
本章小结
本章介绍了内容生成与创作助手:
- 结构化输出:JSON、表格、带验证的输出
- 批量处理:多线程/异步批量生成
- 任务队列:异步任务管理
- 创作模板:营销文案、文章、代码生成
- 质量控制:评估、修复、多版本选择
下一章我们将学习垂直领域应用,探索 AI 在教育、金融、法律等领域的具体应用。
思考与练习
-
实践练习:构建一个批量生成产品描述的工具。
-
系统设计:设计一个带质量控制的内容生成系统。
-
优化思考:如何提高批量生成的效率和质量?
-
扩展功能:添加内容版权检测、抄袭检查功能。
第13章:垂直领域应用
本章介绍 AI 在三个典型垂直领域的应用:教育领域的智能问答助手、金融领域的报告摘要生成、法律领域的合同审查辅助。每个领域都有独特的业务逻辑和数据处理需求。
13.1 教育领域:智能问答助手
13.1.1 需求分析
校园智能问答助手需要处理:
| 类型 | 示例问题 | 技术要点 |
|---|---|---|
| 教务信息 | 选课、转专业、学分 | FAQ + 知识库 |
| 校园生活 | 食堂、宿舍、图书馆 | 知识库 + 规则 |
| 政策咨询 | 奖学金、助学贷款 | 政策文档检索 |
| 学习辅助 | 课程内容、作业 | RAG + 专业文档 |
13.1.2 系统架构
┌─────────────────────────────────────────────────────────────────┐
│ 校园问答助手架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ 用户 │ │
│ │ 输入 │ │
│ └─────┬──────┘ │
│ │ │
│ ┌─────▼──────┐ ┌────────────┐ │
│ │ 意图 │ ───► │ 问题 │ │
│ │ 识别 │ │ 分类 │ │
│ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │
│ │ ┌────────┴────────┐ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────┐ │
│ │ │ FAQ 检索 │ │ RAG 检索 │ │
│ │ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ │ └────────┬────────┘ │
│ │ ▼ │
│ ┌─────▼──────────────────────────┐ │
│ │ 答案生成与后处理 │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
13.1.3 完整实现
from typing import Dict, List, Optional
from enum import Enum
class QuestionCategory(Enum):
"""问题分类"""
ACADEMIC = "academic" # 教务相关
CAMPUS_LIFE = "campus_life" # 校园生活
POLICY = "policy" # 政策咨询
LEARNING = "learning" # 学习辅助
TECHNOLOGY = "technology" # 技术问题
OTHER = "other" # 其他
class CampusQAAgent:
"""校园问答助手"""
SYSTEM_PROMPT = """你是一个热心的校园助手,名为"小智"。
你的职责是帮助学生解决校园生活中的各种问题。
背景信息:
- 当前学期:2024年春季学期
- 校区:北京校区
- 学校类型:综合性大学
你的能力:
1. 回答教务相关问题(选课、转专业、考试等)
2. 提供校园生活指引(食堂、宿舍、图书馆等)
3. 解释学校政策(奖学金、助学贷款等)
4. 辅助学习(课程内容、学习方法等)
回答原则:
1. 友好、耐心、专业
2. 不确定时建议咨询相关部门
3. 涉及个人信息时提醒学生保护隐私
4. 回答简洁明了,避免冗长
如果问题超出你的知识范围,请诚实说明并建议:
"这个问题我不太确定,建议您联系[相关部门]咨询:xxx@school.edu.cn"
"""
def __init__(self, client):
self.client = client
self.faq_kb = self._load_faq()
self.policy_kb = self._load_policies()
def _load_faq(self) -> Dict[str, List[Dict]]:
"""加载常见问题"""
return {
QuestionCategory.ACADEMIC: [
{
"question": "如何进行网上选课?",
"answer": """网上选课步骤:
1. 登录教务系统(jwc.school.edu.cn)
2. 进入"选课管理"->"正选选课"
3. 选择课程和教学班
4. 确认选课结果
注意事项:
- 选课前先查看培养方案
- 热门课程可能需要抽签
- 退改选一般在开学第一周"""
},
{
"question": "如何申请转专业?",
"answer": """转专业申请条件:
1. 全日制本科一年级学生
2. 无不及格课程
3. 通过转入专业考核
申请流程:
1. 每学期第12周提交申请
2. 参加转专业考核
3. 教务处审核
4. 结果公示
注意事项:不同专业要求可能不同,请以教务处通知为准。"""
},
],
QuestionCategory.CAMPUS_LIFE: [
{
"question": "图书馆开放时间?",
"answer": """图书馆开放时间:
- 阅览室:8:00-22:00(周一至周日)
- 自习室:7:00-23:00
- 借还书:8:30-21:30
寒暑假期间开放时间调整,请关注图书馆通知。"""
},
{
"question": "食堂营业时间?",
"answer": """各食堂营业时间:
- 第一食堂:6:30-20:30
- 第二食堂:7:00-21:00
- 清真食堂:7:00-20:00
夜间食堂(学生活动中心旁):18:00-23:00"""
},
],
QuestionCategory.POLICY: [
{
"question": "奖学金如何申请?",
"answer": """奖学金类型及申请:
1. 国家奖学金
- 金额:8000元/年
- 要求:学业成绩优异、社会实践突出
2. 校级奖学金
- 一等奖:5000元/年
- 二等奖:3000元/年
- 三等奖:1000元/年
申请时间:每学期末
申请方式:辅导员推荐+学院审核"""
},
],
}
def _load_policies(self) -> List[Dict]:
"""加载政策文档"""
return [
{"title": "学籍管理规定", "content": "..."},
{"title": "考试纪律", "content": "..."},
{"title": "奖助学金办法", "content": "..."},
]
def classify_question(self, question: str) -> QuestionCategory:
"""分类问题"""
category_keywords = {
QuestionCategory.ACADEMIC: ["选课", "成绩", "学分", "考试", "转专业", "毕业"],
QuestionCategory.CAMPUS_LIFE: ["食堂", "宿舍", "图书馆", "校园", "卡", "热水"],
QuestionCategory.POLICY: ["奖学金", "贷款", "政策", "规定", "申请条件"],
QuestionCategory.LEARNING: ["课程", "作业", "学习", "考研", "证书"],
QuestionCategory.TECHNOLOGY: ["校园网", "VPN", "邮箱", "账号", "密码"],
}
for category, keywords in category_keywords.items():
if any(kw in question for kw in keywords):
return category
return QuestionCategory.OTHER
def find_faq(self, question: str, category: QuestionCategory) -> Optional[str]:
"""查找 FAQ"""
faqs = self.faq_kb.get(category, [])
for faq in faqs:
# 简单关键词匹配
if any(kw in question for kw in faq["question"]):
return faq["answer"]
return None
def answer(self, question: str) -> str:
"""回答问题"""
# 分类
category = self.classify_question(question)
# 查找 FAQ
faq_answer = self.find_faq(question, category)
if faq_answer:
return faq_answer
# 使用 LLM 生成回答
prompt = f"""
问题:{question}
请基于校园助手的角色回答这个问题。
如果不确定,请诚实地说明并提供建议。
"""
response = self.client.chat(
prompt,
system_prompt=self.SYSTEM_PROMPT
)
return response["message"]
# 使用示例
agent = CampusQAAgent(client)
questions = [
"图书馆几点开门?",
"奖学金怎么申请?",
"如何转专业?",
"食堂中午几点关门?",
]
for q in questions:
print(f"\n问题: {q}")
answer = agent.answer(q)
print(f"回答: {answer}")
13.1.4 对话界面
<!-- campus-qa.html -->
<!DOCTYPE html>
<html>
<head>
<title>校园问答助手</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 0 auto; padding: 20px; }
.header { background: #1976d2; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
.header h1 { margin: 0; font-size: 24px; }
.header p { margin: 5px 0 0; opacity: 0.9; }
.chat { background: #f5f5f5; padding: 20px; min-height: 400px; }
.message { margin: 15px 0; }
.user-msg { text-align: right; }
.user-msg .content { background: #e3f2fd; padding: 10px 15px; border-radius: 15px 15px 0 15px; display: inline-block; }
.bot-msg .content { background: white; padding: 15px; border-radius: 0 15px 15px 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.input-area { display: flex; gap: 10px; margin-top: 15px; }
.input-area input { flex: 1; padding: 12px; border: 1px solid #ddd; border-radius: 25px; font-size: 14px; }
.input-area button { padding: 10px 25px; background: #1976d2; color: white; border: none; border-radius: 25px; cursor: pointer; }
.quick-questions { margin-top: 15px; }
.quick-questions span { display: inline-block; background: white; padding: 5px 12px; margin: 3px; border-radius: 15px; font-size: 12px; cursor: pointer; border: 1px solid #ddd; }
</style>
</head>
<body>
<div class="header">
<h1>🎓 校园问答助手</h1>
<p>小智为您服务 · 7x24小时在线</p>
</div>
<div class="chat" id="chatBox"></div>
<div class="input-area">
<input type="text" id="question" placeholder="输入您的问题...">
<button onclick="ask()">发送</button>
</div>
<div class="quick-questions">
<p>快捷问题:</p>
<span onclick="quickAsk('图书馆开放时间?')">图书馆开放时间</span>
<span onclick="quickAsk('如何申请奖学金?')">申请奖学金</span>
<span onclick="quickAsk('网上选课流程?')">网上选课流程</span>
<span onclick="quickAsk('转专业条件?')">转专业条件</span>
</div>
<script>
function addMessage(text, isUser) {
const chatBox = document.getElementById('chatBox');
const div = document.createElement('div');
div.className = 'message ' + (isUser ? 'user-msg' : 'bot-msg');
div.innerHTML = `<div class="content">${text}</div>`;
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
}
function ask() {
const input = document.getElementById('question');
const q = input.value.trim();
if (!q) return;
addMessage(q, true);
input.value = '';
fetch('/api/campus/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: q })
})
.then(r => r.json())
.then(data => addMessage(data.answer, false));
}
function quickAsk(q) {
document.getElementById('question').value = q;
ask();
}
</script>
</body>
</html>
13.2 金融领域:报告摘要生成
13.2.1 需求分析
金融报告摘要系统需要:
| 报告类型 | 处理要点 |
|---|---|
| 财报 | 营收、利润、关键指标 |
| 研报 | 投资建议、风险提示 |
| 公告 | 重大事项、公告摘要 |
| 新闻 | 舆情摘要、行业动态 |
13.2.2 财报摘要生成
from typing import Dict, List, Optional
from datetime import datetime
import re
class FinancialReportSummarizer:
"""金融报告摘要生成器"""
SYSTEM_PROMPT = """你是一个专业的金融分析师,擅长阅读和分析各类金融报告。
你的任务是从原始报告中提取关键信息,生成简洁、准确的摘要。
输出要求:
1. 专业但不晦涩
2. 数据准确,标注单位
3. 突出重点和变化
4. 客观呈现,不添加主观判断
财务指标解释:
- 营收/营业收入:公司销售商品或提供服务获得的收入
- 净利润:扣除所有成本和税费后的利润
- 毛利率:(营收-成本)/营收,反映盈利能力
- 资产负债率:负债/资产,反映财务杠杆
"""
def __init__(self, client):
self.client = client
def summarize_financial_report(
self,
report_text: str,
company_name: str = "公司",
period: str = ""
) -> Dict[str, str]:
"""生成财报摘要"""
prompt = f"""请分析以下{company_name}的{period}财务报告,生成摘要:
报告内容:
{report_text}
请输出以下格式的摘要:
## 核心数据
- 营业收入:XXX亿元(同比+XX%)
- 净利润:XXX亿元(同比+XX%)
- 毛利率:XX%
- 每股收益:XX元
## 经营亮点
1. [主要亮点]
2. [主要亮点]
## 主要风险
1. [主要风险]
2. [主要风险]
## 整体评价
[一句话总结]
只输出上述内容,不要其他。"""
response = self.client.chat(
prompt,
system_prompt=self.SYSTEM_PROMPT
)
return self._parse_summary(response["message"])
def _parse_summary(self, text: str) -> Dict[str, str]:
"""解析摘要文本"""
result = {
"核心数据": "",
"经营亮点": "",
"主要风险": "",
"整体评价": "",
"raw": text
}
current_section = None
lines = text.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
if '## ' in line:
current_section = line.replace('## ', '').strip()
elif current_section and line.startswith('-'):
result[current_section] += line + "\n"
elif current_section:
result[current_section] += line + "\n"
return result
def compare_reports(
self,
report1: str,
report2: str,
company1_name: str = "公司A",
company2_name: str = "公司B"
) -> str:
"""对比分析两份财报"""
prompt = f"""请对比分析以下两份财报:
{company1_name}:
{report1}
{company2_name}:
{report2}
请从以下维度对比:
1. 规模对比(营收、资产)
2. 盈利能力对比(净利润、毛利率)
3. 成长性对比(同比增速)
4. 偿债能力对比(资产负债率)
输出格式:对比表格 + 简要结论
"""
return self.client.chat(prompt)["message"]
# 使用示例
summarizer = FinancialReportSummarizer(client)
sample_report = """
XXX公司2024年年度报告:
一、主要会计数据和财务指标
营业收入:125.8亿元,同比增长15.3%
净利润:18.6亿元,同比增长22.5%
扣非净利润:16.2亿元,同比增长19.8%
基本每股收益:1.85元
二、主营业务构成
产品销售:98.5亿元(占比78.3%)
技术服务:27.3亿元(占比21.7%)
三、经营情况讨论
2024年,公司继续深化技术创新,产品市场份额稳步提升。
海外业务收入达到15.6亿元,同比增长45%。
四、风险提示
1. 行业竞争加剧风险
2. 原材料价格波动风险
3. 汇率风险
"""
summary = summarizer.summarize_financial_report(
sample_report,
company_name="XXX公司",
period="2024年度"
)
print("=== 财报摘要 ===")
print(summary["raw"])
13.2.3 研报摘要生成
class ResearchReportSummarizer:
"""研报摘要生成器"""
def __init__(self, client):
self.client = client
def summarize_research_report(
self,
report_text: str,
stock_code: str = "",
stock_name: str = ""
) -> Dict[str, str]:
"""生成研报摘要"""
header = f"""
研报标的:{stock_name}({stock_code})
生成时间:{datetime.now().strftime('%Y-%m-%d')}
"""
prompt = f"""{header}
请分析以下研究报告,生成结构化摘要:
研报内容:
{report_text}
输出格式:
## 投资要点
1. [核心投资逻辑]
2. [关键看点]
## 核心数据
| 指标 | 数值 | 同比变化 |
|------|------|----------|
| ... | ... | ... |
## 投资建议
- 评级:[强烈推荐/推荐/中性/回避]
- 目标价:XXX元(当前价XXX元)
- 建议理由:...
## 风险提示
1. [主要风险]
2. [主要风险]
只输出上述内容。"""
response = self.client.chat(prompt)
return {
"header": header,
"summary": response["message"]
}
def extract_key_metrics(self, report_text: str) -> List[Dict]:
"""提取关键指标"""
prompt = f"""从以下报告中提取关键财务指标:
{report_text}
以JSON格式输出指标列表:
{{"metrics": [{{"name": "指标名", "value": "数值", "yoy": "同比变化", "unit": "单位"}}]}}
只输出JSON。"""
response = self.client.chat(prompt)
# 解析 JSON
import json
try:
data = json.loads(response["message"])
return data.get("metrics", [])
except:
return []
# 使用示例
research_report = """
海通证券研报:XXX公司深度报告
投资评级:推荐
目标价:45元(当前价38元)
核心观点:
公司作为行业龙头,受益于市场需求持续增长。
我们预计公司2024-2026年净利润CAGR为25%。
关键数据:
- 2024年EPS预期:1.85元
- 2025年EPS预期:2.31元
- PE估值:20.5倍(行业平均25倍)
风险因素:
1. 行业政策变化风险
2. 市场需求不及预期风险
3. 原材料成本上升风险
"""
summarizer = ResearchReportSummarizer(client)
result = summarizer.summarize_research_report(
research_report,
stock_code="600000",
stock_name="XXX公司"
)
print(result["summary"])
13.3 法律领域:合同审查辅助
13.3.1 需求分析
合同审查辅助系统需要:
| 功能 | 说明 |
|---|---|
| 条款提取 | 识别合同关键条款 |
| 风险识别 | 发现潜在风险点 |
| 合规检查 | 验证法律合规性 |
| 建议生成 | 提供修改建议 |
13.3.2 合同审查实现
from typing import Dict, List, Optional
import re
class ContractReviewer:
"""合同审查辅助"""
SYSTEM_PROMPT = """你是一个专业的法律顾问,擅长审查各类商业合同。
你的任务是识别合同中的关键条款、潜在风险,并提供修改建议。
审查原则:
1. 客观公正,不偏袒任何一方
2. 风险提示要明确具体
3. 建议要有法律依据
4. 注意保护商业秘密
常见的合同风险类型:
- 条款不明确导致争议
- 违约责任过轻或过重
- 免责条款不合理
- 知识产权归属不清
- 保密条款不完善
- 争议解决机制缺失或不公
"""
def __init__(self, client):
self.client = client
def review_contract(
self,
contract_text: str,
contract_type: str = "通用合同"
) -> Dict[str, any]:
"""审查合同"""
prompt = f"""请审查以下{contract_type}:
{contract_text}
请从以下维度进行审查:
## 一、合同基本信息
- 合同双方:[识别当事人]
- 合同标的:[识别标的]
- 合同金额:[识别金额]
- 合同期限:[识别期限]
## 二、关键条款分析
识别并分析以下关键条款:
1. 权利义务条款
2. 付款条款
3. 违约条款
4. 保密条款
5. 知识产权条款
6. 争议解决条款
## 三、风险识别
列出发现的潜在风险点,格式:
- 【风险】[风险描述] - 风险等级:[高/中/低]
- 【建议】[对应的修改建议]
## 四、总体评价
- 合同完整性:[完整/缺失条款]
- 条款公平性:[公平/需修改]
- 建议:[整体修改建议]
请详细分析,不要遗漏重要信息。"""
response = self.client.chat(
prompt,
system_prompt=self.SYSTEM_PROMPT
)
return self._parse_review(response["message"])
def _parse_review(self, text: str) -> Dict[str, any]:
"""解析审查结果"""
result = {
"basic_info": {},
"clause_analysis": {},
"risks": [],
"overall": "",
"raw": text
}
# 简化解析
sections = text.split('## ')
for section in sections:
if not section.strip():
continue
lines = section.split('\n')
title = lines[0].strip()
if '基本信息' in title:
result["basic_info"]["content"] = '\n'.join(lines[1:])
elif '风险识别' in title:
result["risks"] = [l for l in lines[1:] if l.strip()]
elif '总体评价' in title:
result["overall"] = '\n'.join(lines[1:])
return result
def check_compliance(
self,
contract_text: str,
laws: List[str] = None
) -> Dict[str, List[str]]:
"""合规性检查"""
laws_str = "\n".join(f"- {law}" for law in laws) if laws else "《民法典》《合同法》"
prompt = f"""请检查以下合同的合规性:
{contract_text}
需要检查的法律法规:
{laws_str}
请指出:
1. 符合法律规定的条款
2. 可能违反法律的条款及依据
3. 建议修改方式
输出格式:
## 合规条款
- [符合条款及依据]
## 违规风险
- 【违规】[条款内容] - 依据:[法律依据]
- 【修改建议】[建议内容]
## 合规结论
[总体评价]
"""
return self.client.chat(prompt)["message"]
def extract_key_terms(
self,
contract_text: str
) -> Dict[str, str]:
"""提取合同关键条款"""
prompt = f"""请提取以下合同的关键条款:
{contract_text}
提取以下信息(JSON格式):
{{
"parties": ["甲方", "乙方"],
"subject": "合同标的",
"amount": "金额",
"start_date": "开始日期",
"end_date": "结束日期",
"payment_terms": "付款条件",
"breach_consequences": "违约后果",
"dispute_resolution": "争议解决方式"
}}
只输出JSON。"""
response = self.client.chat(prompt)
import json
try:
return json.loads(response["message"])
except:
return {"error": "解析失败"}
# 使用示例
reviewer = ContractReviewer(client)
sample_contract = """
技术服务合同
甲方:北京科技有限公司
乙方:上海软件公司
一、服务内容
乙方为甲方提供软件开发服务,包括需求分析、设计、开发、测试及部署。
二、合同金额
合同总价为人民币50万元整。
三、付款方式
1. 合同签订后5个工作日内支付30%预付款
2. 项目验收合格后支付剩余70%
四、项目周期
项目周期为6个月,自合同签订之日起计算。
五、知识产权
项目成果的知识产权归甲方所有。
六、保密条款
双方应对合作过程中知悉的对方商业秘密保密,保密期限为合同终止后2年。
七、违约责任
如一方违约,应向对方支付合同总价20%的违约金。
八、争议解决
因本合同产生的争议,提交甲方所在地人民法院管辖。
九、合同期限
本合同自双方签字盖章之日起生效,有效期至项目验收合格后一年。
"""
result = reviewer.review_contract(sample_contract, "技术服务合同")
print("=== 合同审查结果 ===")
print(result["raw"])
13.3.3 合同生成辅助
class ContractGenerator:
"""合同生成辅助"""
def __init__(self, client):
self.client = client
def generate_clause(
self,
clause_type: str,
context: Dict[str, str]
) -> str:
"""生成合同条款"""
context_str = "\n".join(f"- {k}:{v}" for k, v in context.items())
prompt = f"""请生成以下类型的合同条款:
条款类型:{clause_type}
背景信息:
{context_str}
要求:
1. 条款要完整、清晰
2. 符合法律法规
3. 保护甲方合法权益
4. 条款表述要准确、无歧义
只输出条款内容。"""
return self.client.chat(prompt)["message"]
def generate_full_contract(
self,
contract_type: str,
party_a: str,
party_b: str,
subject: str,
amount: str,
other_terms: str = ""
) -> str:
"""生成完整合同"""
prompt = f"""请生成一份{contract_type}:
合同双方:
- 甲方:{party_a}
- 乙方:{party_b}
合同标的:{subject}
合同金额:{amount}
其他约定:{other_terms}
请生成完整的合同文本,包含:
1. 合同标题
2. 合同双方信息
3. 合同标的
4. 权利义务
5. 付款条款
6. 违约责任
7. 保密条款
8. 知识产权(如适用)
9. 争议解决
10. 其他约定
11. 签署栏
条款要专业、完整、符合法律规范。"""
return self.client.chat(prompt)["message"]
# 使用示例
generator = ContractGenerator(client)
# 生成保密条款
clause = generator.generate_clause(
clause_type="保密条款",
context={
"保密内容": "双方在合作中知悉的任何商业秘密",
"保密期限": "合作期间及结束后3年",
"违约责任": "泄露方承担因此造成的全部损失"
}
)
print("=== 保密条款 ===")
print(clause)
13.4 应用部署
13.4.1 API 服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
app = FastAPI(title="垂直领域 AI 应用")
# 各领域服务实例
campus_agent = CampusQAAgent(client)
financial_summarizer = FinancialReportSummarizer(client)
contract_reviewer = ContractReviewer(client)
# ============== 校园问答 ==============
class CampusQuestion(BaseModel):
question: str
@app.post("/api/campus/ask")
async def campus_ask(request: CampusQuestion):
"""校园问答"""
answer = campus_agent.answer(request.question)
return {"answer": answer, "category": campus_agent.classify_question(request.question).value}
# ============== 财报摘要 ==============
class FinancialReportRequest(BaseModel):
report_text: str
company_name: str = ""
period: str = ""
@app.post("/api/financial/summarize")
async def financial_summarize(request: FinancialReportRequest):
"""财报摘要"""
summary = financial_summarizer.summarize_financial_report(
request.report_text,
request.company_name,
request.period
)
return summary
# ============== 合同审查 ==============
class ContractReviewRequest(BaseModel):
contract_text: str
contract_type: str = "通用合同"
@app.post("/api/legal/review")
async def contract_review(request: ContractReviewRequest):
"""合同审查"""
result = contract_reviewer.review_contract(
request.contract_text,
request.contract_type
)
return result
本章小结
本章介绍了三个垂直领域的 AI 应用:
- 教育领域:校园问答助手,处理教务、生活、政策等问题
- 金融领域:财报和研报摘要,自动提取关键数据
- 法律领域:合同审查辅助,识别风险、提供建议
每个领域都有独特的业务逻辑,需要:
- 领域专业知识
- 定制化的 Prompt 设计
- 专业的输出格式
思考与练习
-
领域调研:选择一个你熟悉的垂直领域,分析其 AI 应用需求。
-
实践练习:为校园问答助手添加更多 FAQ 数据。
-
系统设计:设计一个金融舆情监控系统。
-
功能扩展:为合同审查系统添加合同模板生成功能。
-
思考:垂直领域 AI 应用的通用性和专业性如何平衡?
第 14 章:RAG 架构深度实践
本章深入探讨 RAG(检索增强生成)架构的高级实践,包括知识库构建优化、检索策略优化和生成质量提升。通过真实案例,帮助读者掌握企业级 RAG 系统的核心技术点。
本章内容提要
| 主题 | 核心技能 |
|---|---|
| 知识库构建 | 文档解析、Chunk策略、增量更新 |
| 检索优化 | 混合检索、query扩展、MMR重排序 |
| 生成增强 | 上下文压缩、溯源标注、答案质量评估 |
14.1 RAG 架构进阶原理
14.1.1 为什么要深入 RAG?
基础 RAG(检索-生成)流程虽然简单,但企业在实际应用中会遇到诸多挑战:
- 召回质量差:相关文档未被检索到
- 上下文过长:输入 LLM 的 token 过多,成本高
- 生成幻觉:模型基于不相关文档生成错误答案
- 实时性要求:知识库频繁更新如何处理
本章将从工程实践角度逐一解决这些问题。
14.1.2 Advanced RAG 架构
Advanced RAG 在基础 RAG 基础上增加了多个优化层:
graph TD
A[用户查询] --> B[Query 改写]
B --> C[意图识别]
C --> D[路由选择]
D --> E{路由}
E -->|语义搜索| F[向量检索]
E -->|关键词| G[BM25检索]
F --> H[混合融合]
G --> H
H --> I[重排序]
I --> J[上下文压缩]
J --> K[生成增强]
K --> L[溯源标注]
L --> M[最终回答]
N[知识库] --> F
N --> G
14.1.3 关键组件详解
| 组件 | 作用 | 常见实现 |
|---|---|---|
| Query 改写 | 理解用户真实意图 | HyDE、query expansion |
| 路由选择 | 判断检索类型 | 意图分类模型 |
| 混合检索 | 结合向量和关键词 | RRF、加权融合 |
| 重排序 | 优化结果顺序 | Cross-encoder |
| 上下文压缩 | 减少 token 消耗 | LLMLingua、RAPTOR |
| 溯源标注 | 标明答案来源 | 引用格式 |
14.2 知识库构建与优化
14.2.1 文档解析最佳实践
不同格式的文档需要不同的解析策略:
# src/rag/document_parser.py
from pathlib import Path
from typing import List, Dict
import re
class DocumentParser:
"""统一文档解析器"""
def __init__(self):
self.parsers = {
'.pdf': self._parse_pdf,
'.docx': self._parse_docx,
'.txt': self._parse_txt,
'.md': self._parse_markdown,
'.html': self._parse_html,
}
def parse(self, file_path: str) -> List[Dict]:
"""解析文档并返回结构化内容"""
path = Path(file_path)
suffix = path.suffix.lower()
if suffix not in self.parsers:
raise ValueError(f"不支持的文件格式: {suffix}")
content = self.parsers[suffix](path)
return self._chunk_content(content)
def _parse_pdf(self, path: Path) -> str:
"""PDF 解析(使用 PyMuPDF)"""
import fitz # PyMuPDF
text_parts = []
doc = fitz.open(str(path))
for page_num, page in enumerate(doc):
text = page.get_text()
# 提取页码作为元数据
text_parts.append(f"[页 {page_num + 1}]\n{text}")
return "\n".join(text_parts)
def _parse_docx(self, path: Path) -> str:
"""Word 文档解析"""
from docx import Document
doc = Document(str(path))
paragraphs = []
for para in doc.paragraphs:
if para.text.strip():
# 保留标题层级信息
if para.style.name.startswith('Heading'):
level = para.style.name[-1]
paragraphs.append(f"\n{'#' * int(level)} {para.text}\n")
else:
paragraphs.append(para.text)
return "\n".join(paragraphs)
def _parse_markdown(self, path: Path) -> str:
"""Markdown 解析 - 保留结构"""
content = path.read_text(encoding='utf-8')
# 移除代码块,避免干扰
content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
return content
def _parse_txt(self, path: Path) -> str:
"""纯文本解析"""
return path.read_text(encoding='utf-8')
def _parse_html(self, path: Path) -> str:
"""HTML 解析"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(path.read_text(encoding='utf-8'), 'html.parser')
# 移除脚本和样式
for tag in soup(['script', 'style', 'nav', 'footer']):
tag.decompose()
return soup.get_text(separator='\n', strip=True)
14.2.2 Chunk 策略优化
Chunk(分块)策略直接影响检索质量。以下是几种常用策略:
# src/rag/chunker.py
from typing import List, Dict, Tuple
import re
class ChunkStrategy:
"""分块策略集合"""
@staticmethod
def fixed_size_chunk(
text: str,
chunk_size: int = 500,
overlap: int = 50
) -> List[Dict]:
"""固定大小分块(字符数)"""
chunks = []
start = 0
chunk_id = 0
while start < len(text):
end = start + chunk_size
chunk_text = text[start:end]
# 尝试在句号或换行处断开
if end < len(text):
break_point = max(
chunk_text.rfind('。'),
chunk_text.rfind('。'),
chunk_text.rfind('\n')
)
if break_point > chunk_size // 2:
chunk_text = chunk_text[:break_point + 1]
end = start + break_point + 1
chunks.append({
'chunk_id': f"chunk_{chunk_id}",
'content': chunk_text.strip(),
'start_pos': start,
'end_pos': end,
'token_count': len(chunk_text) // 4 # 粗略估算
})
start = end - overlap
chunk_id += 1
return chunks
@staticmethod
def semantic_chunk(
text: str,
max_tokens: int = 500,
min_sentences: int = 2
) -> List[Dict]:
"""语义分块(按段落和语义边界)"""
import nltk
try:
nltk.data.find('tokenizers/punkt')
except LookupError:
nltk.download('punkt', quiet=True)
from nltk.tokenize import sent_tokenize
sentences = sent_tokenize(text)
chunks = []
current_chunk = []
current_tokens = 0
chunk_id = 0
for sentence in sentences:
sentence_tokens = len(sentence) // 4
current_tokens += sentence_tokens
current_chunk.append(sentence)
# 满足条件时创建新的 chunk
if current_tokens >= max_tokens or \
(len(current_chunk) >= min_sentences and
current_tokens >= max_tokens * 0.6):
chunk_text = ' '.join(current_chunk)
chunks.append({
'chunk_id': f"chunk_{chunk_id}",
'content': chunk_text.strip(),
'token_count': current_tokens,
'sentence_count': len(current_chunk)
})
current_chunk = []
current_tokens = 0
chunk_id += 1
# 处理最后一个 chunk
if current_chunk:
chunks.append({
'chunk_id': f"chunk_{chunk_id}",
'content': ' '.join(current_chunk).strip(),
'token_count': current_tokens,
'sentence_count': len(current_chunk)
})
return chunks
@staticmethod
def hierarchical_chunk(
text: str,
heading_pattern: str = r'^(#{1,6})\s+(.+)$'
) -> List[Dict]:
"""层级分块(保留文档结构)"""
lines = text.split('\n')
chunks = []
chunk_id = 0
current_section = {'title': '', 'content': [], 'level': 0}
for line in lines:
match = re.match(heading_pattern, line)
if match:
# 保存之前的 section
if current_section['content']:
content = '\n'.join(current_section['content'])
if content.strip():
chunks.append({
'chunk_id': f"chunk_{chunk_id}",
'title': current_section['title'],
'content': content.strip(),
'level': current_section['level'],
'token_count': len(content) // 4
})
chunk_id += 1
# 开始新的 section
level = len(match.group(1))
title = match.group(2)
current_section = {
'title': title,
'content': [],
'level': level
}
else:
current_section['content'].append(line)
# 保存最后一个 section
if current_section['content']:
content = '\n'.join(current_section['content'])
if content.strip():
chunks.append({
'chunk_id': f"chunk_{chunk_id}",
'title': current_section['title'],
'content': content.strip(),
'level': current_section['level'],
'token_count': len(content) // 4
})
return chunks
14.2.3 知识库增量更新策略
生产环境的知识库需要支持增量更新:
# src/rag/knowledge_base.py
from datetime import datetime
from typing import List, Dict, Optional
from pathlib import Path
import hashlib
import json
class KnowledgeBase:
"""支持增量更新的知识库"""
def __init__(self, storage_path: str):
self.storage_path = Path(storage_path)
self.storage_path.mkdir(parents=True, exist_ok=True)
self.metadata_file = self.storage_path / "metadata.json"
self.metadata = self._load_metadata()
def _load_metadata(self) -> Dict:
"""加载元数据"""
if self.metadata_file.exists():
return json.loads(self.metadata_file.read_text())
return {
'documents': {},
'last_update': None,
'total_chunks': 0
}
def _save_metadata(self):
"""保存元数据"""
self.metadata['last_update'] = datetime.now().isoformat()
self.metadata_file.write_text(json.dumps(self.metadata, ensure_ascii=False, indent=2))
def _compute_hash(self, content: str) -> str:
"""计算内容哈希"""
return hashlib.md5(content.encode()).hexdigest()[:16]
def add_document(
self,
doc_id: str,
content: str,
metadata: Optional[Dict] = None
) -> Dict:
"""添加文档(自动检测新增或更新)"""
content_hash = self._compute_hash(content)
is_new = doc_id not in self.metadata['documents']
old_hash = self.metadata['documents'].get(doc_id, {}).get('hash')
if not is_new and old_hash == content_hash:
# 内容未变化,跳过
return {
'status': 'unchanged',
'doc_id': doc_id,
'chunks_added': 0
}
# 删除旧文档的 chunks(如果是更新)
if not is_new:
self._remove_document_chunks(doc_id)
# 解析并分块
from .chunker import ChunkStrategy
chunks = ChunkStrategy.semantic_chunk(content)
# 保存 chunks
chunks_file = self.storage_path / f"{doc_id}_chunks.json"
chunks_file.write_text(json.dumps(chunks, ensure_ascii=False))
# 更新元数据
self.metadata['documents'][doc_id] = {
'hash': content_hash,
'added_at': datetime.now().isoformat(),
'chunk_count': len(chunks),
'metadata': metadata or {}
}
self.metadata['total_chunks'] = sum(
d['chunk_count'] for d in self.metadata['documents'].values()
)
self._save_metadata()
return {
'status': 'added' if is_new else 'updated',
'doc_id': doc_id,
'chunks_added': len(chunks)
}
def _remove_document_chunks(self, doc_id: str):
"""删除文档的 chunks"""
chunks_file = self.storage_path / f"{doc_id}_chunks.json"
if chunks_file.exists():
chunks_file.unlink()
def delete_document(self, doc_id: str) -> bool:
"""删除文档"""
if doc_id not in self.metadata['documents']:
return False
self._remove_document_chunks(doc_id)
del self.metadata['documents'][doc_id]
self.metadata['total_chunks'] = sum(
d['chunk_count'] for d in self.metadata['documents'].values()
)
self._save_metadata()
return True
def get_all_chunks(self) -> List[Dict]:
"""获取所有 chunks"""
all_chunks = []
for doc_id in self.metadata['documents']:
chunks_file = self.storage_path / f"{doc_id}_chunks.json"
if chunks_file.exists():
chunks = json.loads(chunks_file.read_text())
for chunk in chunks:
chunk['doc_id'] = doc_id
all_chunks.append(chunk)
return all_chunks
14.3 检索策略优化
14.3.1 混合检索实现
混合检索结合向量检索和关键词检索的优势:
# src/rag/hybrid_retriever.py
from typing import List, Dict, Tuple
import numpy as np
class HybridRetriever:
"""混合检索器"""
def __init__(
self,
vector_store,
bm25_index,
fusion_method: str = "rrf"
):
self.vector_store = vector_store
self.bm25_index = bm25_index
self.fusion_method = fusion_method
def retrieve(
self,
query: str,
top_k: int = 10,
alpha: float = 0.7 # 向量检索权重
) -> List[Dict]:
"""混合检索"""
# 1. 向量检索
vector_results = self.vector_store.similarity_search(
query, k=top_k * 2
)
vector_scores = {
r['chunk_id']: r['score']
for r in vector_results
}
# 2. BM25 检索
bm25_results = self.bm25_index.search(query, k=top_k * 2)
bm25_scores = {
r['chunk_id']: r['score']
for r in bm25_results
}
# 3. 分数融合
all_chunk_ids = set(vector_scores.keys()) | set(bm25_scores.keys())
if self.fusion_method == "rrf":
# Reciprocal Rank Fusion
fused_scores = self._rrf_fusion(
vector_scores, bm25_scores, all_chunk_ids, alpha
)
else:
# 加权分数融合
fused_scores = self._weighted_fusion(
vector_scores, bm25_scores, all_chunk_ids, alpha
)
# 4. 排序并返回
sorted_chunks = sorted(
fused_scores.items(),
key=lambda x: x[1],
reverse=True
)[:top_k]
# 构建最终结果
chunk_id_to_result = {r['chunk_id']: r for r in vector_results}
chunk_id_to_result.update({r['chunk_id']: r for r in bm25_results})
results = []
for chunk_id, score in sorted_chunks:
if chunk_id in chunk_id_to_result:
result = chunk_id_to_result[chunk_id]
result['fusion_score'] = score
results.append(result)
return results
def _rrf_fusion(
self,
vector_scores: Dict,
bm25_scores: Dict,
chunk_ids: set,
alpha: float,
k: int = 60
) -> Dict[str, float]:
"""Reciprocal Rank Fusion"""
fused = {}
# 归一化分数
max_vec = max(vector_scores.values()) if vector_scores else 1
max_bm25 = max(bm25_scores.values()) if bm25_scores else 1
for chunk_id in chunk_ids:
vec_score = vector_scores.get(chunk_id, 0) / max_vec
bm25_score = bm25_scores.get(chunk_id, 0) / max_bm25
# 加权 RRF
rrf_score = alpha * vec_score + (1 - alpha) * bm25_score
fused[chunk_id] = rrf_score
return fused
def _weighted_fusion(
self,
vector_scores: Dict,
bm25_scores: Dict,
chunk_ids: set,
alpha: float
) -> Dict[str, float]:
"""加权分数融合"""
fused = {}
max_vec = max(vector_scores.values()) if vector_scores else 1
max_bm25 = max(bm25_scores.values()) if bm25_scores else 1
for chunk_id in chunk_ids:
vec_score = vector_scores.get(chunk_id, 0) / max_vec
bm25_score = bm25_scores.get(chunk_id, 0) / max_bm25
fused[chunk_id] = alpha * vec_score + (1 - alpha) * bm25_score
return fused
14.3.2 Query 扩展与改写
使用 HyDE(Hypothetical Document Embeddings)技术提升检索效果:
# src/rag/query_expander.py
from typing import List
class QueryExpander:
"""查询扩展器"""
def __init__(self, llm_client):
self.llm = llm_client
def expand_hyde(self, query: str) -> List[str]:
"""HyDE: 让 LLM 生成假设性答案,再检索"""
hyde_prompt = f"""请针对以下用户问题,生成一个假设性的高质量回答。
这个回答是用于改进检索效果的示例,不需要真实正确。
用户问题: {query}
请生成1-2个不同角度的假设性回答,每个回答50-100字:"""
response = self.llm.chat([{
'role': 'user',
'content': hyde_prompt
}])
# 解析假设性回答
hypotheses = [query] # 原始查询
for line in response.split('\n'):
if line.strip() and len(line) > 20:
hypotheses.append(line.strip())
return hypotheses[:3]
def expand_subqueries(self, query: str) -> List[str]:
"""将复杂查询分解为多个子查询"""
decompose_prompt = f"""将以下复杂查询分解为2-4个简单的子查询,每个子查询关注一个方面。
原始查询: {query}
分解后的子查询(每行一个,不要编号):"""
response = self.llm.chat([{
'role': 'user',
'content': decompose_prompt
}])
subqueries = []
for line in response.split('\n'):
line = line.strip().lstrip('0123456789.、))')
if line and len(line) > 5:
subqueries.append(line)
return subqueries if subqueries else [query]
def expand_keywords(self, query: str) -> List[str]:
"""关键词扩展(同义词、近义词)"""
keyword_prompt = f"""为以下查询提取关键词,并提供2-3个同义词或相关术语。
查询: {query}
格式:
关键词1: xxx, 同义词: xxx, xxx
关键词2: xxx, 同义词: xxx, xxx"""
response = self.llm.chat([{
'role': 'user',
'content': keyword_prompt
}])
# 提取关键词
keywords = []
for line in response.split('\n'):
if ':' in line:
keyword = line.split(':')[1].split(',')[0].strip()
keywords.append(keyword)
# 返回原始查询 + 关键词组合
expanded = [query]
for kw in keywords[:3]:
expanded.append(f"{query} {kw}")
return expanded
14.3.3 MMR 重排序
MMR(Maximum Marginal Relevance)确保结果多样性:
# src/rag/mmr_reranker.py
import numpy as np
from typing import List, Dict
class MMRReranker:
"""MMR 重排序"""
def __init__(self, reranker_model=None):
self.reranker = reranker_model
def rerank(
self,
query: str,
candidates: List[Dict],
top_k: int = 5,
lambda_param: float = 0.5
) -> List[Dict]:
"""MMR 重排序
Args:
query: 查询文本
candidates: 候选文档列表
top_k: 返回数量
lambda_param: 相似度权重 (1-λ) 控制多样性
"""
if not candidates:
return []
if self.reranker:
return self._cross_encoder_rerank(
query, candidates, top_k
)
return self._mmr_rerank(
query, candidates, top_k, lambda_param
)
def _cross_encoder_rerank(
self,
query: str,
candidates: List[Dict],
top_k: int
) -> List[Dict]:
"""使用 Cross-Encoder 重排序"""
pairs = [(query, c['content']) for c in candidates]
scores = self.reranker.predict(pairs)
for i, c in enumerate(candidates):
c['rerank_score'] = float(scores[i])
return sorted(candidates, key=lambda x: x['rerank_score'], reverse=True)[:top_k]
def _mmm_rerank(
self,
query: str,
candidates: List[Dict],
top_k: int,
lambda_param: float
) -> List[Dict]:
"""MMR 算法"""
selected = []
remaining = candidates.copy()
query_embedding = self._get_embedding(query)
query_norm = query_embedding / np.linalg.norm(query_embedding)
while len(selected) < top_k and remaining:
best_score = -float('inf')
best_candidate = None
for candidate in remaining:
# 与查询的相似度
doc_embedding = self._get_embedding(candidate['content'])
doc_norm = doc_embedding / np.linalg.norm(doc_embedding)
sim_to_query = float(np.dot(query_norm, doc_norm))
# 与已选文档的最大相似度(惩罚重复内容)
max_sim_to_selected = 0
if selected:
selected_embeddings = [
self._get_embedding(s['content'])
for s in selected
]
for sel_emb in selected_embeddings:
sel_norm = sel_emb / np.linalg.norm(sel_emb)
sim = float(np.dot(doc_norm, sel_norm))
max_sim_to_selected = max(max_sim_to_selected, sim)
# MMR 分数
mmr_score = lambda_param * sim_to_query - (1 - lambda_param) * max_sim_to_selected
if mmr_score > best_score:
best_score = mmr_score
best_candidate = candidate
if best_candidate:
selected.append(best_candidate)
remaining.remove(best_candidate)
return selected
def _get_embedding(self, text: str) -> np.ndarray:
"""获取文本 embedding(需要外部实现)"""
raise NotImplementedError("需要接入 embedding 模型")
14.4 生成质量提升
14.4.1 上下文压缩
使用 LLMLingua 进行智能上下文压缩:
# src/rag/context_compressor.py
from typing import List, Dict
class ContextCompressor:
"""上下文压缩器"""
def __init__(self, llm_client):
self.llm = llm_client
def compress_context(
self,
query: str,
context_chunks: List[Dict],
max_tokens: int = 3000
) -> str:
"""压缩上下文,保留与查询相关的内容"""
if not context_chunks:
return ""
# 构建上下文文本
context_text = self._build_context_text(context_chunks)
# 如果已经在 token 限制内,直接返回
if len(context_text) // 4 <= max_tokens:
return context_text
# 使用 LLM 进行压缩
compression_prompt = f"""你是一个上下文压缩专家。请从以下上下文中提取与问题最相关的部分,
生成一个精简但完整的答案上下文。
问题: {query}
原始上下文:
{context_text}
压缩要求:
1. 保留与问题直接相关的信息
2. 移除冗余的解释和重复内容
3. 保持上下文逻辑连贯
4. 目标长度: {max_tokens} tokens 以内
压缩后的上下文:"""
compressed = self.llm.chat([{
'role': 'user',
'content': compression_prompt
}])
return compressed.strip()
def _build_context_text(self, chunks: List[Dict]) -> str:
"""构建带溯源的上下文文本"""
parts = []
for i, chunk in enumerate(chunks, 1):
source = chunk.get('source', chunk.get('doc_id', 'unknown'))
title = chunk.get('title', '')
part = f"[文档 {i}] 来源: {source}"
if title:
part += f" | 标题: {title}"
part += f"\n{chunk['content']}\n"
parts.append(part)
return "\n---\n".join(parts)
def extract_relevant_snippets(
self,
query: str,
chunks: List[Dict],
snippets_per_chunk: int = 2
) -> List[Dict]:
"""从每个 chunk 中提取相关片段"""
extraction_prompt = f"""从以下文档中提取与问题最相关的片段。
只返回相关片段,不要解释。
问题: {query}
文档:
{{doc_content}}
相关片段(最多{snippets_per_chunk}个,每个不超过100字):"""
snippets = []
for chunk in chunks:
response = self.llm.chat([{
'role': 'user',
'content': extraction_prompt.format(doc_content=chunk['content'])
}])
for line in response.split('\n'):
if line.strip():
snippets.append({
'content': line.strip(),
'source': chunk.get('source', chunk.get('doc_id', '')),
'chunk_id': chunk.get('chunk_id', '')
})
return snippets[:len(chunks) * snippets_per_chunk]
14.4.2 溯源标注与引用
生成答案时自动添加来源引用:
# src/rag/citation_generator.py
from typing import List, Dict, Tuple
class CitationGenerator:
"""引用生成器"""
def __init__(self, llm_client):
self.llm = llm_client
def generate_with_citations(
self,
query: str,
context_chunks: List[Dict]
) -> Tuple[str, List[Dict]]:
"""生成带引用的答案"""
# 构建带编号的上下文
cited_context = []
for i, chunk in enumerate(context_chunks, 1):
source = chunk.get('source', chunk.get('doc_id', f'来源{i}'))
cited_context.append(
f"[{i}] 来源: {source}\n{chunk['content']}"
)
context_text = "\n---\n".join(cited_context)
# 带引用的生成 prompt
generation_prompt = f"""基于以下参考资料回答用户问题。
在回答中使用 [1], [2] 等标注来引用相关来源。
如果某个信息没有明确的来源支持,不要编造,直接说明。
用户问题: {query}
参考资料:
{context_text}
要求:
1. 答案准确,基于参考资料
2. 使用 [编号] 标注每个声明的来源
3. 回答结束后,列出所有引用的来源
4. 如果参考资料不足以回答,说明情况"""
answer = self.llm.chat([{
'role': 'user',
'content': generation_prompt
}])
# 解析引用的来源
citations = self._extract_citations(answer, context_chunks)
return answer, citations
def _extract_citations(
self,
answer: str,
chunks: List[Dict]
) -> List[Dict]:
"""从答案中提取引用的来源"""
import re
citations = []
citation_numbers = re.findall(r'\[(\d+)\]', answer)
for num in set(citation_numbers):
idx = int(num) - 1
if 0 <= idx < len(chunks):
chunk = chunks[idx]
citations.append({
'number': num,
'source': chunk.get('source', chunk.get('doc_id', '')),
'title': chunk.get('title', ''),
'content': chunk['content'][:200] + '...'
})
return citations
def format_citations(self, citations: List[Dict]) -> str:
"""格式化引用列表"""
if not citations:
return ""
lines = ["\n---\n**参考来源:**\n"]
for cite in citations:
title = cite.get('title', '')
if title:
lines.append(f"- [{cite['number']}] {title}")
else:
lines.append(f"- [{cite['number']}] {cite['source']}")
return '\n'.join(lines)
14.4.3 答案质量评估
自动评估生成答案的质量:
# src/rag/answer_evaluator.py
from typing import Dict, List
class AnswerEvaluator:
"""答案质量评估器"""
def __init__(self, llm_client):
self.llm = llm_client
def evaluate(
self,
query: str,
answer: str,
context_chunks: List[Dict]
) -> Dict:
"""评估答案质量"""
evaluation_prompt = f"""请评估以下 RAG 系统生成的答案质量。
问题: {query}
生成的答案:
{answer}
评估维度(每项 1-5 分):
1. 答案完整性 - 是否完整回答了问题
2. 答案准确性 - 是否与参考资料一致
3. 上下文利用 - 是否有效利用了提供的参考资料
4. 答案简洁性 - 是否简洁明了,不过于冗长
5. 引用准确性 - 引用的来源是否正确支持答案
请以 JSON 格式返回评估结果:
{{
"total_score": 总分,
"breakdown": {{
"completeness": 分数,
"accuracy": 分数,
"context_utilization": 分数,
"conciseness": 分数,
"citation_accuracy": 分数
}},
"issues": ["问题1", "问题2"],
"suggestions": ["改进建议1"]
}}"""
response = self.llm.chat([{
'role': 'user',
'content': evaluation_prompt
}])
return self._parse_evaluation(response)
def _parse_evaluation(self, response: str) -> Dict:
"""解析评估结果"""
import json
import re
# 尝试提取 JSON
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
pass
# 降级处理
return {
'total_score': 3,
'breakdown': {
'completeness': 3,
'accuracy': 3,
'context_utilization': 3,
'conciseness': 3,
'citation_accuracy': 3
},
'raw_response': response
}
14.5 完整 RAG 流水线
14.5.1 高级 RAG 流程实现
整合以上所有组件:
# src/rag/advanced_rag.py
from typing import List, Dict, Optional
class AdvancedRAG:
"""高级 RAG 系统"""
def __init__(
self,
llm_client,
embedding_model,
vector_store,
bm25_index,
reranker=None
):
self.llm = llm_client
self.embedding = embedding_model
self.vector_store = vector_store
self.bm25 = bm25_index
self.reranker = MMRReranker(reranker)
self.query_expander = QueryExpander(llm_client)
self.compressor = ContextCompressor(llm_client)
self.citation_generator = CitationGenerator(llm_client)
def query(
self,
user_query: str,
top_k: int = 10,
return_citations: bool = True,
max_context_tokens: int = 3000
) -> Dict:
"""完整查询流程"""
# 1. Query 扩展
expanded_queries = self.query_expander.expand_subqueries(user_query)
# 2. 混合检索
all_results = []
for query in expanded_queries:
results = self._hybrid_search(query, top_k)
all_results.extend(results)
# 3. 去重
unique_results = self._deduplicate_results(all_results)
# 4. MMR 重排序
reranked = self.reranker.rerank(
user_query,
unique_results,
top_k=10,
lambda_param=0.5
)
# 5. 上下文压缩
compressed_context = self.compressor.compress_context(
user_query,
reranked,
max_tokens=max_context_tokens
)
# 6. 生成答案
if return_citations:
answer, citations = self.citation_generator.generate_with_citations(
user_query,
reranked
)
else:
answer = self._generate_answer(user_query, compressed_context)
citations = []
return {
'answer': answer,
'citations': citations,
'context_chunks': reranked,
'query_expansions': expanded_queries
}
def _hybrid_search(
self,
query: str,
top_k: int
) -> List[Dict]:
"""混合检索"""
hybrid = HybridRetriever(
self.vector_store,
self.bm25,
fusion_method="rrf"
)
return hybrid.retrieve(query, top_k=top_k, alpha=0.7)
def _deduplicate_results(
self,
results: List[Dict]
) -> List[Dict]:
"""去重"""
seen = set()
unique = []
for result in results:
chunk_id = result.get('chunk_id')
if chunk_id and chunk_id not in seen:
seen.add(chunk_id)
unique.append(result)
return unique
def _generate_answer(
self,
query: str,
context: str
) -> str:
"""生成答案"""
prompt = f"""基于以下上下文回答问题。如果上下文中没有相关信息,请说明。
问题: {query}
上下文:
{context}
回答:"""
return self.llm.chat([{'role': 'user', 'content': prompt}])
14.5.2 使用示例
# examples/advanced_rag_demo.py
from src.rag.advanced_rag import AdvancedRAG
from src.rag.knowledge_base import KnowledgeBase
# 初始化
rag = AdvancedRAG(
llm_client=dashscope_client,
embedding_model=embedding_model,
vector_store=faiss_store,
bm25_index=bm25_index,
reranker=cross_encoder
)
# 查询
result = rag.query(
"阿里云函数计算支持哪些触发器?",
top_k=10,
return_citations=True
)
print("答案:", result['answer'])
print("\n参考来源:")
for cite in result['citations']:
print(f" [{cite['number']}] {cite.get('title', cite['source'])}")
14.6 本章小结
本章深入探讨了 RAG 架构的高级实践:
| 主题 | 核心要点 |
|---|---|
| 知识库构建 | 多种分块策略、增量更新机制、结构保留 |
| 检索优化 | 混合检索(向量+BM25)、Query 扩展、MMR 重排序 |
| 生成增强 | 上下文压缩、溯源引用、答案质量评估 |
| 系统集成 | 完整 Advanced RAG 流水线 |
进阶学习路径
- 深入优化:尝试不同的 embedding 模型、reranker
- 评估体系:建立完整的 RAG 评估基准
- 生产部署:添加缓存、限流、监控等工程能力
延伸阅读
第 15 章:Agent 智能体开发
本章介绍 Agent(智能体)的核心概念、架构设计和开发实践。通过 DashScope 的 Function Calling 能力,构建能够自主规划、调用工具、与人协作的 AI Agent。
本章内容提要
| 主题 | 核心技能 |
|---|---|
| Agent 基础 | 架构原理、ReAct 框架、自主决策 |
| 工具调用 | Function Calling、插件系统、工具注册 |
| 记忆管理 | 短期记忆、长期记忆、记忆检索 |
| 多 Agent 协作 | Agent 编排、任务分解、结果聚合 |
15.1 Agent 架构原理
15.1.1 什么是 Agent?
Agent(智能体)是一种能够感知环境、做出决策并执行行动的智能系统。与简单的 LLM 调用不同,Agent 具有:
- 自主性:能够独立规划和执行任务
- 工具使用:可以调用外部工具和 API
- 记忆能力:能够记忆和利用历史信息
- 目标导向:能够分解复杂任务并逐步完成
graph TB
A[用户输入] --> B[规划器 Planner]
B --> C[决策 Decide]
C --> D{执行什么?}
D -->|思考| E[使用工具]
D -->|回答| F[直接回复]
E --> G[工具执行]
G --> H[观察结果]
H --> B
F --> I[最终回答]
H --> I
J[记忆 Memory] <--> B
J <--> E
15.1.2 ReAct 框架详解
ReAct(Reasoning + Acting)是最经典的 Agent 框架之一:
# src/agent/react.py
from typing import List, Dict, Any, Callable
from enum import Enum
class AgentAction(Enum):
"""Agent 可以执行的动作类型"""
THINK = "think" # 思考下一步
ACT = "act" # 执行工具
OBSERVE = "observe" # 观察结果
ANSWER = "answer" # 生成最终答案
class ReActAgent:
"""基于 ReAct 框架的 Agent"""
def __init__(
self,
llm_client,
tools: List[Dict],
max_iterations: int = 10
):
self.llm = llm_client
self.tools = tools
self.max_iterations = max_iterations
# 构建工具描述
self.tool_descriptions = self._build_tool_description()
def _build_tool_description(self) -> str:
"""构建工具描述字符串"""
descriptions = []
for i, tool in enumerate(self.tools):
param_info = ", ".join([
f"{p['name']}: {p['type']}"
for p in tool.get('parameters', {}).get('properties', {}).values()
])
descriptions.append(
f"{i}. {tool['name']}: {tool['description']} (参数: {param_info})"
)
return "\n".join(descriptions)
def run(self, user_input: str) -> Dict:
"""运行 Agent"""
history = []
context = ""
for iteration in range(self.max_iterations):
# 1. 生成思考
thought = self._generate_thought(user_input, context, history)
history.append({"role": "assistant", "content": thought})
# 2. 决定下一步动作
action = self._parse_action(thought)
if action['type'] == 'answer':
# 生成最终答案
answer = self._generate_answer(user_input, context)
return {
"answer": answer,
"thoughts": history,
"iterations": iteration + 1
}
elif action['type'] == 'tool':
# 3. 执行工具
tool_name = action['tool']
tool_args = action['args']
result = self._execute_tool(tool_name, tool_args)
observation = f"观察结果: {result}"
history.append({"role": "user", "content": observation})
context += f"\n{observation}"
else: # think
context += f"\n思考: {action['content']}"
# 达到最大迭代次数
return {
"answer": "抱歉,我无法在规定步骤内完成任务。",
"thoughts": history,
"iterations": self.max_iterations
}
def _generate_thought(
self,
user_input: str,
context: str,
history: List[Dict]
) -> str:
"""生成思考和行动"""
system_prompt = f"""你是一个能够自主思考和行动的 AI Agent。
当前时间: 2026-04-18
你拥有以下工具可以使用:
{self.tool_descriptions}
你必须按照以下格式思考和行动:
## 思考
分析当前情况,考虑是否需要使用工具来完成任务。
## 动作
根据思考结果,选择以下之一:
- tool: 使用工具。格式: tool: {{"tool": "工具名", "args": {{"参数": "值"}}}}
- answer: 直接回答用户问题。格式: answer: [你的回答]
- think: 继续思考。格式: think: [你的思考内容]
## 历史对话
{context if context else "无"}
## 用户问题
{user_input}
请开始推理:"""
response = self.llm.chat([
{"role": "system", "content": system_prompt}
])
return response
def _parse_action(self, thought: str) -> Dict:
"""解析思考内容中的动作"""
import json
import re
# 查找动作指令
patterns = [
(r'tool:\s*(\{{[^}}]+\}})', 'tool'),
(r'think:\s*(.+)', 'think'),
(r'answer:\s*(.+)', 'answer'),
]
for pattern, action_type in patterns:
match = re.search(pattern, thought, re.DOTALL)
if match:
if action_type == 'tool':
try:
args = json.loads(match.group(1))
return {"type": "tool", "tool": args.get("tool"), "args": args.get("args", {})}
except:
return {"type": "think", "content": thought}
elif action_type == 'think':
return {"type": "think", "content": match.group(1)}
else: # answer
return {"type": "answer", "content": match.group(1)}
return {"type": "think", "content": thought}
def _execute_tool(self, tool_name: str, args: Dict) -> Any:
"""执行工具"""
for tool in self.tools:
if tool['name'] == tool_name:
return tool['function'](**args)
return f"错误: 找不到工具 '{tool_name}'"
def _generate_answer(
self,
user_input: str,
context: str
) -> str:
"""生成最终答案"""
prompt = f"""基于之前的推理过程,给出最终答案。
用户问题: {user_input}
推理过程:
{context}
请给出完整、准确的回答:"""
return self.llm.chat([{"role": "user", "content": prompt}])
15.1.3 Agent 与 LLM 的区别
| 特性 | 纯 LLM | Agent |
|---|---|---|
| 输入 | 当前对话 | 当前 + 历史 + 环境 |
| 输出 | 文本 | 思考 + 动作 + 文本 |
| 工具使用 | 无 | 有 |
| 多步推理 | 有限 | 完整 |
| 记忆能力 | 对话窗口 | 可扩展存储 |
15.2 Function Calling 实践
15.2.1 DashScope Function Calling
DashScope 支持 Function Calling,可以定义工具让模型调用:
# src/agent/tools/dashscope_tools.py
from typing import List, Dict, Any, Callable
import json
class DashScopeFunctionCaller:
"""DashScope Function Calling 封装"""
def __init__(self, llm_client):
self.llm = llm_client
self.registered_tools: Dict[str, Callable] = {}
def register_tool(
self,
name: str,
description: str,
parameters: Dict,
function: Callable
):
"""注册工具"""
self.registered_tools[name] = function
# 构建函数定义
if 'functions' not in self.__dict__:
self.functions = []
self.functions.append({
"name": name,
"description": description,
"parameters": parameters
})
def chat_with_functions(
self,
messages: List[Dict],
functions: List[Dict] = None
) -> Dict:
"""调用 LLM,支持函数调用"""
if functions is None:
functions = self.functions
response = self.llm.chat(
messages,
functions=functions if functions else None
)
return response
def run_with_tools(
self,
user_input: str,
max_turns: int = 5
) -> Dict:
"""带工具调用的对话"""
messages = [{"role": "user", "content": user_input}]
iterations = 0
while iterations < max_turns:
# 调用 LLM
response = self.chat_with_functions(messages)
if response.get('function_call'):
# 解析函数调用
func_name = response['function_call']['name']
func_args = json.loads(response['function_call']['arguments'])
# 添加助手消息
messages.append({
"role": "assistant",
"content": response.get('output') or "",
"function_call": {
"name": func_name,
"arguments": response['function_call']['arguments']
}
})
# 执行函数
if func_name in self.registered_tools:
func_result = self.registered_tools[func_name](**func_args)
else:
func_result = f"错误: 未知函数 {func_name}"
# 添加函数结果
messages.append({
"role": "function",
"name": func_name,
"content": str(func_result)
})
iterations += 1
else:
# 无函数调用,直接返回
return {
"answer": response.get('output', response.get('text', '')),
"iterations": iterations,
"function_calls": []
}
return {
"answer": "已达到最大迭代次数",
"iterations": iterations
}
15.2.2 常用工具定义
以下是几个常用的工具定义示例:
# 定义搜索工具
search_tool = {
"name": "search_knowledge_base",
"description": "搜索知识库获取相关信息。当用户询问具体知识点、产品功能、政策法规等内容时使用。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询词"
},
"top_k": {
"type": "integer",
"description": "返回结果数量,默认 5",
"default": 5
}
},
"required": ["query"]
}
}
# 定义数据库查询工具
db_query_tool = {
"name": "query_database",
"description": "查询数据库获取结构化数据。用于用户询问具体数据、数值、统计信息时使用。",
"parameters": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL 查询语句"
},
"params": {
"type": "object",
"description": "查询参数"
}
},
"required": ["sql"]
}
}
# 定义计算工具
calculator_tool = {
"name": "calculate",
"description": "执行数学计算。用于需要精确计算的场景,如费用计算、统计分析等。",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 '100 * 0.05 * 30'"
}
},
"required": ["expression"]
}
}
# 定义发送通知工具
notification_tool = {
"name": "send_notification",
"description": "发送通知给用户。支持邮件、短信、应用内通知。",
"parameters": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"enum": ["email", "sms", "app"],
"description": "通知渠道"
},
"recipient": {
"type": "string",
"description": "接收人标识"
},
"title": {
"type": "string",
"description": "通知标题"
},
"content": {
"type": "string",
"description": "通知内容"
}
},
"required": ["channel", "recipient", "content"]
}
}
15.2.3 工具执行函数实现
# 工具函数实现
def search_knowledge_base(query: str, top_k: int = 5) -> str:
"""知识库搜索实现"""
from your_rag_module import KnowledgeBase
kb = KnowledgeBase("/path/to/knowledge_base")
results = kb.search(query, top_k=top_k)
if not results:
return "未找到相关信息"
formatted = []
for i, r in enumerate(results, 1):
formatted.append(f"{i}. {r['content'][:200]}...")
return "\n".join(formatted)
def query_database(sql: str, params: dict = None) -> str:
"""数据库查询实现"""
import sqlite3
conn = sqlite3.connect("/path/to/database.db")
cursor = conn.cursor()
try:
cursor.execute(sql, params or {})
rows = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
if not rows:
return "查询结果为空"
# 格式化结果
result = [f"列: {', '.join(columns)}"]
for row in rows[:10]: # 限制返回行数
result.append(", ".join(str(v) for v in row))
return "\n".join(result)
finally:
conn.close()
def calculate(expression: str) -> str:
"""数学计算实现"""
import math
import operator
# 安全计算(限制可用函数)
safe_dict = {
"abs": abs,
"round": round,
"min": min,
"max": max,
"sum": sum,
"pow": pow,
"pi": math.pi,
"e": math.e,
"sqrt": math.sqrt,
"sin": math.sin,
"cos": math.cos,
"log": math.log,
}
try:
result = eval(expression, {"__builtins__": {}}, safe_dict)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
def send_notification(
channel: str,
recipient: str,
title: str,
content: str
) -> str:
"""发送通知实现"""
if channel == "email":
# 使用邮件服务发送
return f"邮件已发送给 {recipient}: {title}"
elif channel == "sms":
return f"短信已发送给 {recipient}"
elif channel == "app":
return f"应用通知已发送给 {recipient}"
else:
return f"不支持的通知渠道: {channel}"
15.3 Agent 记忆管理
15.3.1 记忆系统架构
Agent 需要管理多种类型的记忆:
graph TB
A[用户输入] --> S[感知记忆]
S --> W[工作记忆]
W --> L[长期记忆]
L --> W
subgraph 工作记忆
W1[当前对话]
W2[当前任务]
W3[上下文]
end
subgraph 长期记忆
L1[会话历史]
L2[知识存储]
L3[偏好设置]
L4[技能索引]
end
15.3.2 记忆实现
# src/agent/memory.py
from typing import List, Dict, Any, Optional
from datetime import datetime
import json
from pathlib import Path
class AgentMemory:
"""Agent 记忆系统"""
def __init__(self, storage_path: Optional[str] = None):
self.storage_path = Path(storage_path) if storage_path else None
if self.storage_path:
self.storage_path.mkdir(parents=True, exist_ok=True)
# 工作记忆(当前上下文)
self.working_memory = {
'conversation': [],
'current_task': None,
'context': {}
}
# 长期记忆索引
self.memory_index = {
'episodes': [], # 情节记忆
'knowledge': [], # 知识
'preferences': {}, # 偏好
'skills': [] # 技能
}
self._load_memory()
def _load_memory(self):
"""加载持久化的记忆"""
if self.storage_path and (self.storage_path / "memory.json").exists():
with open(self.storage_path / "memory.json") as f:
self.memory_index = json.load(f)
def _save_memory(self):
"""保存记忆到磁盘"""
if self.storage_path:
with open(self.storage_path / "memory.json", "w") as f:
json.dump(self.memory_index, f, ensure_ascii=False, indent=2)
# ========== 工作记忆 ==========
def add_working_memory(self, role: str, content: str):
"""添加到工作记忆"""
self.working_memory['conversation'].append({
'role': role,
'content': content,
'timestamp': datetime.now().isoformat()
})
def get_conversation_context(
self,
max_turns: int = 10
) -> List[Dict]:
"""获取最近对话上下文"""
return self.working_memory['conversation'][-max_turns:]
def set_current_task(self, task: str):
"""设置当前任务"""
self.working_memory['current_task'] = {
'task': task,
'started_at': datetime.now().isoformat(),
'steps': []
}
def add_task_step(self, step: str):
"""添加任务步骤"""
if self.working_memory['current_task']:
self.working_memory['current_task']['steps'].append({
'step': step,
'timestamp': datetime.now().isoformat()
})
def get_task_progress(self) -> str:
"""获取任务进度"""
task = self.working_memory.get('current_task')
if not task:
return "无进行中的任务"
steps = task.get('steps', [])
return f"任务: {task['task']}\n进度: {len(steps)} 步骤已完成"
# ========== 长期记忆 ==========
def store_episode(
self,
situation: str,
action: str,
result: str,
reflection: Optional[str] = None
):
"""存储情节记忆"""
episode = {
'type': 'episode',
'situation': situation,
'action': action,
'result': result,
'reflection': reflection,
'created_at': datetime.now().isoformat(),
'access_count': 0
}
self.memory_index['episodes'].append(episode)
self._save_memory()
def store_knowledge(
self,
topic: str,
content: str,
source: Optional[str] = None
):
"""存储知识"""
# 检查是否已存在
for item in self.memory_index['knowledge']:
if item['topic'] == topic:
item['content'] = content
item['updated_at'] = datetime.now().isoformat()
self._save_memory()
return
# 新增知识
self.memory_index['knowledge'].append({
'type': 'knowledge',
'topic': topic,
'content': content,
'source': source,
'created_at': datetime.now().isoformat()
})
self._save_memory()
def retrieve_memories(
self,
query: str,
memory_types: List[str] = None,
top_k: int = 5
) -> List[Dict]:
"""检索相关记忆(简化版,实际应该用 embedding)"""
if memory_types is None:
memory_types = ['episodes', 'knowledge']
results = []
for mem_type in memory_types:
if mem_type == 'episodes':
for ep in self.memory_index.get('episodes', []):
if query.lower() in ep['situation'].lower() or \
query.lower() in ep['action'].lower():
ep['access_count'] += 1
results.append(ep)
elif mem_type == 'knowledge':
for kw in self.memory_index.get('knowledge', []):
if query.lower() in kw['topic'].lower() or \
query.lower() in kw['content'].lower():
results.append(kw)
# 按访问频率和相关性排序
results.sort(key=lambda x: x.get('access_count', 0), reverse=True)
return results[:top_k]
def update_preference(self, key: str, value: Any):
"""更新偏好设置"""
self.memory_index['preferences'][key] = value
self._save_memory()
def get_preference(self, key: str, default: Any = None) -> Any:
"""获取偏好设置"""
return self.memory_index['preferences'].get(key, default)
def summarize_and_consolidate(self, llm_client) -> str:
"""总结和整合记忆(定期调用)"""
if not self.working_memory['conversation']:
return "无需整合"
# 提取关键信息
summary_prompt = f"""总结以下对话中的关键信息,包括:
1. 用户的主要需求
2. 完成任务的关键步骤
3. 重要的中间结果
4. 最终答案
对话:
{json.dumps(self.working_memory['conversation'], ensure_ascii=False)}
总结(JSON格式):"""
summary = llm_client.chat([{"role": "user", "content": summary_prompt}])
# 存储为情节记忆
self.store_episode(
situation=f"用户询问了关于{self.working_memory['conversation'][0]['content'][:50]}...",
action="执行了多步推理",
result=summary[:200],
reflection="这是一个有效的解决方案"
)
# 清空当前对话
self.working_memory['conversation'] = []
return summary
15.4 工具注册与管理
15.4.1 工具注册中心
# src/agent/tool_registry.py
from typing import Dict, Callable, List, Any
from dataclasses import dataclass
import json
@dataclass
class ToolDefinition:
"""工具定义"""
name: str
description: str
parameters: Dict
function: Callable
tags: List[str] = None
def __post_init__(self):
if self.tags is None:
self.tags = []
class ToolRegistry:
"""工具注册中心"""
def __init__(self):
self._tools: Dict[str, ToolDefinition] = {}
self._tags: Dict[str, List[str]] = {} # tag -> tool names
def register(
self,
name: str,
description: str,
parameters: Dict,
function: Callable,
tags: List[str] = None
):
"""注册工具"""
tool = ToolDefinition(
name=name,
description=description,
parameters=parameters,
function=function,
tags=tags or []
)
self._tools[name] = tool
# 更新标签索引
for tag in tool.tags:
if tag not in self._tags:
self._tags[tag] = []
self._tags[tag].append(name)
def get_tool(self, name: str) -> ToolDefinition:
"""获取工具定义"""
return self._tools.get(name)
def get_all_tools(self) -> List[Dict]:
"""获取所有工具定义(用于 LLM)"""
return [
{
"name": t.name,
"description": t.description,
"parameters": t.parameters
}
for t in self._tools.values()
]
def get_tools_by_tag(self, tag: str) -> List[ToolDefinition]:
"""根据标签获取工具"""
tool_names = self._tags.get(tag, [])
return [self._tools[name] for name in tool_names]
def execute(self, name: str, **kwargs) -> Any:
"""执行工具"""
tool = self._tools.get(name)
if not tool:
raise ValueError(f"未知工具: {name}")
return tool.function(**kwargs)
def get_tool_schema(self) -> str:
"""获取工具 schema(用于文档生成)"""
schemas = []
for tool in self._tools.values():
schema = {
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters
}
schemas.append(schema)
return json.dumps(schemas, ensure_ascii=False, indent=2)
# 全局注册中心
_global_registry = ToolRegistry()
def get_registry() -> ToolRegistry:
return _global_registry
def register_tool(
description: str,
parameters: Dict,
tags: List[str] = None
):
"""装饰器注册工具"""
def decorator(func: Callable):
_global_registry.register(
name=func.__name__,
description=description,
parameters=parameters,
function=func,
tags=tags
)
return func
return decorator
15.4.2 工具使用示例
# examples/agent_tools_demo.py
from src.agent.tool_registry import get_registry, register_tool
registry = get_registry()
# 注册工具
@register_tool(
description="获取当前日期和时间",
parameters={
"type": "object",
"properties": {},
"required": []
},
tags=["utility", "time"]
)
def get_current_time():
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@register_tool(
description="搜索互联网获取实时信息",
parameters={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词"
}
},
"required": ["query"]
},
tags=["search"]
)
def web_search(query: str):
# 实际实现调用搜索 API
return f"搜索结果: 关于'{query}'的信息..."
@register_tool(
description="发送邮件",
parameters={
"type": "object",
"properties": {
"to": {"type": "string", "description": "收件人邮箱"},
"subject": {"type": "string", "description": "邮件主题"},
"body": {"type": "string", "description": "邮件正文"}
},
"required": ["to", "subject", "body"]
},
tags=["communication"]
)
def send_email(to: str, subject: str, body: str):
return f"邮件已发送给 {to}: {subject}"
15.5 多 Agent 协作
15.5.1 Agent 编排模式
复杂任务可以由多个 Agent 协作完成:
graph TB
A[用户请求] --> O[编排器 Orchestrator]
O --> T1[任务分解]
T1 --> T2[任务 A]
T1 --> T3[任务 B]
T1 --> T4[任务 C]
T2 --> R[结果聚合]
T3 --> R
T4 --> R
R --> A2[答案生成]
A2 --> F[最终输出]
15.5.2 简单编排器实现
# src/agent/orchestrator.py
from typing import List, Dict, Any, Callable
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor
import asyncio
@dataclass
class SubAgent:
"""子 Agent 定义"""
name: str
description: str
system_prompt: str
tools: List[Dict]
executor: Callable # 执行函数
class AgentOrchestrator:
"""Agent 编排器"""
def __init__(self, llm_client):
self.llm = llm_client
self.agents: Dict[str, SubAgent] = {}
def register_agent(self, agent: SubAgent):
"""注册子 Agent"""
self.agents[agent.name] = agent
async def orchestrate(
self,
task: str,
agent_names: List[str] = None
) -> Dict:
"""编排任务执行"""
# 1. 任务分析
if agent_names is None:
agent_names = self._route_task(task)
# 2. 并行执行子任务
results = await self._execute_parallel(task, agent_names)
# 3. 聚合结果
aggregated = self._aggregate_results(task, results)
return {
"task": task,
"sub_results": results,
"final_answer": aggregated
}
def _route_task(self, task: str) -> List[str]:
"""任务路由 - 确定需要哪些 Agent"""
# 使用 LLM 判断
prompt = f"""分析以下任务,确定需要哪些专业 Agent 协作完成。
任务: {task}
可用 Agent:
{self._list_agents()}
请列出需要的 Agent 名称(逗号分隔),每个任务建议 1-3 个 Agent:"""
response = self.llm.chat([{"role": "user", "content": prompt}])
# 解析响应,提取 Agent 名称
agent_names = []
for name in self.agents.keys():
if name in response:
agent_names.append(name)
return agent_names if agent_names else ["general"]
def _list_agents(self) -> str:
"""列出所有 Agent"""
return "\n".join([
f"- {name}: {agent.description}"
for name, agent in self.agents.items()
])
async def _execute_parallel(
self,
task: str,
agent_names: List[str]
) -> Dict[str, Any]:
"""并行执行多个子 Agent"""
results = {}
async def run_agent(name: str):
agent = self.agents.get(name)
if not agent:
return name, {"error": f"Agent {name} 不存在"}
# 构建 Agent prompt
full_prompt = f"{agent.system_prompt}\n\n任务: {task}"
# 使用 ReAct 或直接调用
result = await self._run_agent_task(agent, task)
return name, result
# 并发执行
tasks = [run_agent(name) for name in agent_names]
completed = await asyncio.gather(*tasks, return_exceptions=True)
for item in completed:
if isinstance(item, tuple):
name, result = item
results[name] = result
elif isinstance(item, Exception):
results["error"] = str(item)
return results
async def _run_agent_task(
self,
agent: SubAgent,
task: str
) -> str:
"""运行单个 Agent 任务"""
# 简化实现:直接调用 LLM
messages = [
{"role": "system", "content": agent.system_prompt},
{"role": "user", "content": task}
]
response = self.llm.chat(messages, functions=agent.tools)
return response.get('output', str(response))
def _aggregate_results(
self,
task: str,
results: Dict[str, Any]
) -> str:
"""聚合子 Agent 结果"""
# 构建聚合 prompt
result_text = "\n\n".join([
f"=== {name} 的结果 ===\n{result}"
for name, result in results.items()
])
aggregation_prompt = f"""基于以下多个 Agent 的分析结果,给出综合性的最终答案。
原始任务: {task}
各 Agent 分析:
{result_text}
要求:
1. 综合各方观点
2. 突出关键发现
3. 给出明确结论
4. 如有分歧,说明权衡
最终答案:"""
return self.llm.chat([{"role": "user", "content": aggregation_prompt}])
15.5.3 协作示例:校园助手
# examples/campus_assistant_orchestrator.py
from src.agent.orchestrator import AgentOrchestrator, SubAgent
from src.agent.tools.dashscope_tools import DashScopeFunctionCaller
# 创建编排器
orchestrator = AgentOrchestrator(dashscope_llm)
# 注册课程 Agent
orchestrator.register_agent(SubAgent(
name="course_agent",
description="课程咨询 Agent",
system_prompt="你是一个课程顾问,专注于回答课程相关问题,包括选课、教学计划、课程内容等。",
tools=[search_tool, get_course_info_tool],
executor=None
))
# 注册图书馆 Agent
orchestrator.register_agent(SubAgent(
name="library_agent",
description="图书馆服务 Agent",
system_prompt="你是一个图书馆助手,帮助查找图书、期刊、数据库资源。",
tools=[search_book_tool, check_availability_tool],
executor=None
))
# 注册行政 Agent
orchestrator.register_agent(SubAgent(
name="admin_agent",
description="行政服务 Agent",
system_prompt="你处理请假、证明办理、缴费等行政事务。",
tools=[query_policy_tool, submit_form_tool],
executor=None
))
# 用户请求需要多个 Agent 协作
result = await orchestrator.orchestrate(
"我下周要参加一个学术会议,想申请请假,同时想借一些相关的参考资料"
)
print(result["final_answer"])
15.6 完整 Agent 系统
15.6.1 系统架构
# src/agent/campus_agent.py
from typing import List, Dict, Optional
import json
class CampusAssistantAgent:
"""校园助手 Agent 完整实现"""
def __init__(self, config: Dict):
# 初始化各组件
self.llm = self._init_llm(config['dashscope_api_key'])
self.memory = AgentMemory(config.get('memory_path'))
self.tools = self._register_tools()
self.function_caller = DashScopeFunctionCaller(self.llm)
# 注册工具
for tool in self.tools:
self.function_caller.register_tool(
name=tool['name'],
description=tool['description'],
parameters=tool['parameters'],
function=tool['function']
)
def _init_llm(self, api_key: str):
"""初始化 LLM 客户端"""
import dashscope
dashscope.api_key = api_key
return dashscope
def _register_tools(self) -> List[Dict]:
"""注册可用工具"""
return [
{
"name": "search_knowledge_base",
"description": "搜索校园知识库获取相关信息",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询"}
},
"required": ["query"]
},
"function": self._search_kb
},
{
"name": "get_schedule",
"description": "获取课程表或日程安排",
"parameters": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "日期 (YYYY-MM-DD)"},
"user_id": {"type": "string", "description": "用户 ID"}
},
"required": ["date"]
},
"function": self._get_schedule
},
{
"name": "book_room",
"description": "预约会议室或自习室",
"parameters": {
"type": "object",
"properties": {
"room_id": {"type": "string", "description": "房间 ID"},
"date": {"type": "string", "description": "日期"},
"time_slot": {"type": "string", "description": "时间段"}
},
"required": ["room_id", "date", "time_slot"]
},
"function": self._book_room
},
{
"name": "send_notification",
"description": "发送通知提醒",
"parameters": {
"type": "object",
"properties": {
"user_id": {"type": "string"},
"message": {"type": "string", "description": "通知内容"}
},
"required": ["user_id", "message"]
},
"function": self._send_notification
}
]
# 工具函数实现
def _search_kb(self, query: str) -> str:
"""搜索知识库"""
# 实现知识库搜索
return f"知识库搜索结果: {query}"
def _get_schedule(self, date: str, user_id: str = None) -> str:
"""获取日程"""
return f"{date} 的日程安排"
def _book_room(self, room_id: str, date: str, time_slot: str) -> str:
"""预约房间"""
return f"已预约 {room_id},日期: {date},时间段: {time_slot}"
def _send_notification(self, user_id: str, message: str) -> str:
"""发送通知"""
return f"通知已发送给 {user_id}: {message}"
def chat(self, user_input: str) -> Dict:
"""对话接口"""
# 添加到工作记忆
self.memory.add_working_memory("user", user_input)
# 检查长期记忆
relevant_memories = self.memory.retrieve_memories(user_input)
if relevant_memories:
context_hint = "\n\n相关记忆:\n" + "\n".join([
f"- {m.get('content', m.get('result', ''))[:100]}"
for m in relevant_memories[:3]
])
else:
context_hint = ""
# 扩展用户输入
enhanced_input = user_input + context_hint
# 带工具调用
result = self.function_caller.run_with_tools(enhanced_input)
# 添加到记忆
self.memory.add_working_memory("assistant", result['answer'])
# 如果是多轮交互结束,整理记忆
if len(self.memory.working_memory['conversation']) >= 10:
self.memory.summarize_and_consolidate(self.llm)
return result
15.6.2 使用示例
# 使用示例
if __name__ == "__main__":
agent = CampusAssistantAgent({
'dashscope_api_key': 'your-api-key',
'memory_path': './memory'
})
# 对话
response = agent.chat("我想查一下明天的课程安排")
print("助手:", response['answer'])
response = agent.chat("顺便帮我预约一个自习室,下午 3 点到 5 点")
print("助手:", response['answer'])
15.7 本章小结
本章介绍了 Agent 智能体开发的核心内容:
| 主题 | 核心要点 |
|---|---|
| Agent 基础 | 自主性、工具使用、记忆能力、目标导向 |
| ReAct 框架 | 思考→动作→观察循环 |
| Function Calling | DashScope 工具调用、函数定义与执行 |
| 记忆管理 | 工作记忆、长期记忆、记忆检索 |
| 多 Agent 协作 | 任务分解、并行执行、结果聚合 |
进阶学习路径
- 自主规划:引入 Planning 模块,实现复杂任务的自动分解
- 持续学习:让 Agent 从交互中持续学习和改进
- 安全控制:添加权限控制、审计日志等安全机制
延伸阅读
- ReAct: Synergizing Reasoning and Acting in Language Models
- Tool Learning with Foundation Models
- TaskMatrix.AI: Completing Tasks by Connecting Foundation Models
- DashScope Function Calling 文档
第 16 章:向量数据库应用
本章介绍向量数据库的核心概念、部署配置和开发实践。重点讲解 Milvus 的使用、Embedding 模型选型,以及相似度检索的优化策略。
本章内容提要
| 主题 | 核心技能 |
|---|---|
| 向量数据库基础 | 概念原理、索引类型、选型对比 |
| Milvus 部署 | Docker 部署、配置优化、高可用 |
| 开发实践 | CRUD 操作、范围查询、过滤检索 |
| 性能优化 | 索引选择、分区策略、查询优化 |
16.1 向量数据库基础
16.1.1 为什么需要向量数据库?
传统关系数据库以行和列存储结构化数据,适合精确查询。但对于以下场景,传统数据库无能为力:
- 语义搜索:搜索“苹果“时,数据库不知道用户想要水果还是公司
- 相似图片查找:找出与给定图片相似的其他图片
- 推荐系统:基于用户兴趣找到相似用户或商品
- LLM 记忆:存储和检索文本的语义表示
向量数据库专门为此设计,能够:
- 存储高维向量:将文本、图像等转换为向量表示
- 快速相似度搜索:在大规模向量中找到最近的邻居
- ANN 算法:近似最近邻搜索,平衡精度和性能
16.1.2 核心概念
graph LR
A[文本/图片] --> B[Embedding 模型]
B --> C[向量表示]
C --> D[向量数据库]
D --> E[相似度计算]
E --> F[Top-K 结果]
G[查询向量] --> D
D --> H[相关文档]
| 概念 | 说明 |
|---|---|
| 向量 (Vector) | 数据的数学表示,如 768 维浮点数数组 |
| Embedding | 将原始数据转换为向量的过程或结果 |
| Collection | 相当于关系数据库的表 |
| Partition | Collection 的物理分区,提高查询效率 |
| Index | 索引结构,加速相似度搜索 |
| Top-K | 返回最相似的 K 条结果 |
| ANNS | Approximate Nearest Neighbor Search,近似最近邻 |
16.1.3 相似度度量
向量数据库使用多种相似度度量:
from enum import Enum
from typing import List
import numpy as np
class SimilarityMetric(Enum):
"""相似度度量类型"""
# 余弦相似度:-1 到 1,越接近 1 越相似
COSINE = "COSINE"
# 欧氏距离:0 到 ∞,越接近 0 越相似
L2 = "L2"
# 内积:-∞ 到 ∞,越大越相似
IP = "IP"
def cosine_similarity(a: List[float], b: List[float]) -> float:
"""计算余弦相似度"""
a = np.array(a)
b = np.array(b)
dot_product = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
return dot_product / (norm_a * norm_b)
def euclidean_distance(a: List[float], b: List[float]) -> float:
"""计算欧氏距离"""
return float(np.linalg.norm(np.array(a) - np.array(b)))
def inner_product(a: List[float], b: List[float]) -> float:
"""计算内积"""
return float(np.dot(a, b))
# 示例
vec1 = [0.1, 0.8, 0.3]
vec2 = [0.2, 0.7, 0.4]
print(f"余弦相似度: {cosine_similarity(vec1, vec2):.4f}")
print(f"欧氏距离: {euclidean_distance(vec1, vec2):.4f}")
print(f"内积: {inner_product(vec1, vec2):.4f}")
如何选择度量方式:
| 场景 | 推荐度量 |
|---|---|
| 文本 Embedding(如 BERT) | COSINE(已归一化) |
| 图像 Embedding(如 ResNet) | COSINE 或 L2 |
| 未归一化的向量 | IP(内积) |
| 需要考虑向量长度 | COSINE |
16.1.4 索引类型对比
| 索引类型 | 适用场景 | 精度 | 速度 | 内存 |
|---|---|---|---|---|
| FLAT | 小规模数据、精确检索 | 100% | 慢 | 高 |
| IVF_FLAT | 中等规模、平衡场景 | 高 | 中 | 中 |
| IVF_PQ | 大规模、省内存 | 中 | 快 | 低 |
| HNSW | 追求速度、高精度 | 高 | 快 | 高 |
| ANNOY | 超大规模、树结构 | 中 | 快 | 低 |
graph TB
A[选择索引] --> B{数据规模}
B -->|<100万| C{需求}
B -->|100万-1亿| D{需求}
B -->|>1亿| E{需求}
C -->|<1s延迟| F[IVF_FLAT]
C -->|精确检索| G[FLAT]
D -->|<10ms| H[HNSW]
D -->|省内存| I[IVF_PQ]
E -->|<50ms| J[HNSW + IVF_PQ]
E -->|极度省内存| K[ANNOY]
16.2 Milvus 部署与配置
16.2.1 Docker 快速部署
# 创建配置目录
mkdir -p /opt/milvus/volumes
cd /opt/milvus
# 创建 docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.5
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ./volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
networks:
- milvus
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
volumes:
- ./volumes/minio:/minio_data
command: minio server /minio_data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
networks:
- milvus
milvus:
container_name: milvus-standalone
image: milvusdb/milvus:v2.3.3
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ./volumes/milvus:/var/lib/milvus
- ./configs/milvus.yaml:/milvus/configs/milvus.yaml
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- etcd
- minio
networks:
- milvus
networks:
milvus:
driver: bridge
EOF
# 创建配置文件
mkdir -p configs
cat > configs/milvus.yaml << 'EOF'
etcd:
endpoints:
- etcd:2379
rootPath: by-dev
storage:
type: minio
minio:
address: minio:9000
accessKeyID: minioadmin
secretAccessKey: minioadmin
log:
level: info
EOF
# 启动 Milvus
docker-compose up -d
# 检查状态
docker-compose ps
16.2.2 Milvus Attu 可视化管理
# 启动 Attu(Milvus 可视化管理工具)
cat >> docker-compose.yml << 'EOF'
attu:
container_name: milvus-attu
image: zilliz/attu:v2.3
environment:
MILVUS_ADDRESS: milvus:19530
ports:
- "3000:3000"
networks:
- milvus
EOF
# 重启
docker-compose up -d attu
访问 http://localhost:3000 即可使用 Attu 管理 Milvus。
16.2.3 生产环境配置
# configs/milvus-cluster.yaml (集群模式)
etcd:
endpoints:
- etcd-1:2379
- etcd-2:2379
- etcd-3:2379
rootPath: by-dev-production
metaStoreType: etcd
storage:
type: minio
storageClass: "local-path"
volumes:
- name: storage
path: /var/lib/milvus/storage
minio:
address: minio:9000
accessKeyID: ${MINIO_ACCESS_KEY}
secretAccessKey: ${MINIO_SECRET_KEY}
useSSL: false
bucketName: milvus-bucket
log:
level: info
format: text
dataCoord:
segment:
maxSize: 512 # MB
sealProportion: 0.25
assignmentExpiration: 2000 # ms
queryCoord:
autoHandoff: true
autoBalance: true
balancer: scoreBasedBalancer
16.3 Milvus Python SDK
16.3.1 连接与基础操作
# src/vector_db/milvus_client.py
from pymilvus import (
connections, Collection, FieldSchema, CollectionSchema,
DataType, utility, Collection
)
from typing import List, Dict, Any, Optional
import numpy as np
class MilvusClient:
"""Milvus 向量数据库客户端封装"""
def __init__(
self,
host: str = "localhost",
port: str = "19530",
alias: str = "default"
):
self.alias = alias
connections.connect(alias=alias, host=host, port=port)
self.collections = {}
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
"""关闭连接"""
connections.disconnect(alias=self.alias)
def create_collection(
self,
name: str,
dimension: int,
description: str = "",
metric_type: str = "COSINE",
index_type: str = "IVF_FLAT",
params: Dict = None
) -> Collection:
"""创建 Collection"""
# 如果已存在,先删除
if utility.has_collection(name):
utility.drop_collection(name)
# 定义字段
fields = [
FieldSchema(name="id", dtype=DataType.VARCHAR, max_length=64, is_primary=True),
FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=dimension),
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=512),
FieldSchema(name="metadata", dtype=DataType.VARCHAR, max_length=2048),
]
# 创建 Schema
schema = CollectionSchema(fields=fields, description=description)
# 创建 Collection
collection = Collection(name=name, schema=schema)
# 创建索引
index_params = params or self._get_default_index_params(metric_type, index_type)
collection.create_index(
field_name="vector",
index_params=index_params
)
# 加载到内存
collection.load()
self.collections[name] = collection
return collection
def _get_default_index_params(
self,
metric_type: str,
index_type: str
) -> Dict:
"""获取默认索引参数"""
index_map = {
"IVF_FLAT": {
"index_type": "IVF_FLAT",
"metric_type": metric_type,
"params": {"nlist": 128}
},
"IVF_PQ": {
"index_type": "IVF_PQ",
"metric_type": metric_type,
"params": {"nlist": 128, "m": 16}
},
"HNSW": {
"index_type": "HNSW",
"metric_type": metric_type,
"params": {"M": 16, "efConstruction": 200}
},
"FLAT": {
"index_type": "FLAT",
"metric_type": metric_type,
"params": {}
}
}
return index_map.get(index_type, index_map["IVF_FLAT"])
def get_collection(self, name: str) -> Collection:
"""获取 Collection"""
if name not in self.collections:
if not utility.has_collection(name):
raise ValueError(f"Collection '{name}' 不存在")
collection = Collection(name=name)
collection.load()
self.collections[name] = collection
return self.collections[name]
def insert(
self,
collection_name: str,
ids: List[str],
vectors: List[List[float]],
texts: List[str],
metadata: List[Dict] = None
) -> Dict:
"""插入向量"""
collection = self.get_collection(collection_name)
# 准备数据
data = [
ids,
vectors,
texts,
[str(m) if m else "{}" for m in (metadata or [{}] * len(ids))]
]
# 插入
result = collection.insert(data)
# 刷新
collection.flush()
return {
"insert_count": result.insert_count,
"primary_keys": result.primary_keys
}
def search(
self,
collection_name: str,
query_vectors: List[List[float]],
top_k: int = 10,
expr: str = None,
output_fields: List[str] = None,
search_params: Dict = None
) -> List[List[Dict]]:
"""向量搜索"""
collection = self.get_collection(collection_name)
if output_fields is None:
output_fields = ["id", "text", "metadata"]
if search_params is None:
search_params = {"metric_type": "COSINE", "params": {"nprobe": 10}}
results = collection.search(
data=query_vectors,
anns_field="vector",
param=search_params,
limit=top_k,
expr=expr,
output_fields=output_fields
)
# 格式化结果
formatted_results = []
for hits in results:
hit_list = []
for hit in hits:
hit_list.append({
"id": hit.entity.get("id"),
"text": hit.entity.get("text"),
"metadata": hit.entity.get("metadata"),
"distance": hit.distance
})
formatted_results.append(hit_list)
return formatted_results
def delete_by_id(self, collection_name: str, ids: List[str]) -> int:
"""根据 ID 删除"""
collection = self.get_collection(collection_name)
expr = f'id in [{",".join(f"\"{i}\"" for i in ids)}]'
result = collection.delete(expr)
collection.flush()
return result.delete_count
def query(
self,
collection_name: str,
expr: str,
output_fields: List[str] = None
) -> List[Dict]:
"""标量查询"""
collection = self.get_collection(collection_name)
if output_fields is None:
output_fields = ["id", "text", "metadata"]
results = collection.query(expr=expr, output_fields=output_fields)
return results
def get_collection_stats(self, collection_name: str) -> Dict:
"""获取 Collection 统计信息"""
collection = self.get_collection(collection_name)
return {
"name": collection_name,
"row_count": collection.num_entities,
"partitions": collection.partitions,
"description": collection.description
}
16.3.2 高级查询功能
# src/vector_db/advanced_queries.py
from typing import List, Dict, Optional
import numpy as np
class AdvancedSearch:
"""高级搜索功能"""
def __init__(self, milvus_client):
self.client = milvus_client
def search_with_filter(
self,
collection_name: str,
query_vector: List[float],
filter_expr: str,
top_k: int = 10
) -> List[Dict]:
"""带过滤条件的搜索"""
return self.client.search(
collection_name=collection_name,
query_vectors=[query_vector],
top_k=top_k,
expr=filter_expr
)[0]
def search_by_text(
self,
collection_name: str,
text: str,
embedding_model,
top_k: int = 10,
filter_expr: str = None
) -> List[Dict]:
"""文本搜索(自动转向量)"""
# 生成 embedding
query_vector = embedding_model.encode(text)
return self.client.search(
collection_name=collection_name,
query_vectors=[query_vector],
top_k=top_k,
expr=filter_expr
)[0]
def batch_search(
self,
collection_name: str,
query_vectors: List[List[float]],
top_k: int = 10
) -> List[List[Dict]]:
"""批量搜索"""
return self.client.search(
collection_name=collection_name,
query_vectors=query_vectors,
top_k=top_k
)
def range_search(
self,
collection_name: str,
query_vector: List[float],
radius: float,
top_k: int = 100
) -> List[Dict]:
"""范围搜索(距离在 radius 内的所有向量)"""
collection = self.client.get_collection(collection_name)
search_params = {
"metric_type": "COSINE",
"params": {"radius": radius, "range_filter": 1.0}
}
results = collection.search(
data=[query_vector],
anns_field="vector",
param=search_params,
limit=top_k,
output_fields=["id", "text", "metadata", "distance"]
)
return [{
"id": hit.entity.get("id"),
"text": hit.entity.get("text"),
"distance": hit.distance
} for hit in results[0]]
def get_recommendations(
self,
collection_name: str,
user_id: str,
embedding_model,
exclude_items: List[str] = None,
top_k: int = 10
) -> List[Dict]:
"""基于用户历史的推荐"""
# 获取用户偏好向量
user_vector = self._get_user_preference_vector(collection_name, user_id)
if not user_vector:
return []
# 构建过滤表达式(排除用户已交互的)
filter_expr = None
if exclude_items:
item_ids = ",".join(f'"{i}"' for i in exclude_items)
filter_expr = f'id not in [{item_ids}]'
return self.client.search(
collection_name=collection_name,
query_vectors=[user_vector],
top_k=top_k,
expr=filter_expr
)[0]
def _get_user_preference_vector(
self,
collection_name: str,
user_id: str
) -> Optional[List[float]]:
"""获取用户偏好向量"""
results = self.client.query(
collection_name=collection_name,
expr=f'id == "{user_id}_preference"'
)
if results and len(results) > 0:
return results[0].get("vector")
return None
16.4 Embedding 模型选型
16.4.1 常用 Embedding 模型
| 模型 | 维度 | 适用场景 | 来源 |
|---|---|---|---|
| text-embedding-ada-002 | 1536 | 通用、英文为主 | OpenAI |
| text-embedding-3-small/large | 256-3072 | 通用、多语言 | OpenAI |
| m3e-base | 768 | 中文、英文 | MokaAI |
| text2vec-base-chinese | 768 | 中文 | shibing624 |
| bge-base-zh | 768 | 中文、英文 | BAAI |
| bge-large-zh | 1024 | 高精度中文 | BAAI |
| gte-base-zh | 768 | 多语言 | Alibaba-NLP |
16.4.2 阿里云 DashScope Embedding
# src/embedding/dashscope_embedding.py
from dashscope import TextEmbedding
from typing import List, Dict
import numpy as np
class DashScopeEmbedding:
"""DashScope Embedding 模型封装"""
def __init__(
self,
model: str = "text-embedding-v2",
api_key: str = None
):
self.model = model
self.client = TextEmbedding(api_key=api_key)
# 模型维度映射
self.dimension_map = {
"text-embedding-v1": 1536,
"text-embedding-v2": 1536,
"text-embedding-v3": 1536,
"text-embedding-async-v2": 1536,
"text-embedding-async-v3": 1536
}
@property
def dimension(self) -> int:
"""获取模型维度"""
return self.dimension_map.get(self.model, 1536)
def encode(self, texts: str | List[str]) -> List[float] | List[List[float]]:
"""编码文本为向量"""
if isinstance(texts, str):
texts = [texts]
response = self.client.async_call(texts)
if response.status_code != 200:
raise ValueError(f"Embedding 请求失败: {response.message}")
embeddings = []
for item in response.output['embeddings']:
embeddings.append(item['embedding'])
return embeddings if len(embeddings) > 1 else embeddings[0]
def encode_batch(
self,
texts: List[str],
batch_size: int = 25,
max_retries: int = 3
) -> List[List[float]]:
"""批量编码(处理大量文本)"""
all_embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
for attempt in range(max_retries):
try:
response = self.client.async_call(batch)
if response.status_code == 200:
for item in response.output['embeddings']:
all_embeddings.append(item['embedding'])
break
except Exception as e:
if attempt == max_retries - 1:
raise
continue
return all_embeddings
def compute_similarity(
self,
text1: str,
text2: str
) -> float:
"""计算两个文本的相似度"""
emb1, emb2 = self.encode([text1, text2])
# 余弦相似度
dot = sum(a * b for a, b in zip(emb1, emb2))
norm1 = sum(a * a for a in emb1) ** 0.5
norm2 = sum(b * b for b in emb2) ** 0.5
return dot / (norm1 * norm2)
# 使用示例
if __name__ == "__main__":
embedding = DashScopeEmbedding(model="text-embedding-v2")
# 单文本编码
vec = embedding.encode("你好,世界")
print(f"向量维度: {len(vec)}")
# 批量编码
texts = ["今天天气真好", "明天会下雨吗", "我喜欢机器学习"]
vectors = embedding.encode_batch(texts)
print(f"批量编码结果: {len(vectors)} 个向量")
# 相似度计算
sim = embedding.compute_similarity("苹果", "水果")
print(f"相似度: {sim:.4f}")
16.4.3 本地 Embedding 模型
# src/embedding/local_embedding.py
from sentence_transformers import SentenceTransformer
from typing import List
import numpy as np
class LocalEmbedding:
"""本地 Embedding 模型"""
def __init__(
self,
model_name: str = "BAAI/bge-base-zh-v1.5",
device: str = "cuda" # 或 "cpu"
):
self.model = SentenceTransformer(model_name, device=device)
self.dimension = self.model.get_sentence_embedding_dimension()
def encode(
self,
texts: str | List[str],
normalize: bool = True,
batch_size: int = 32
) -> List[List[float]]:
"""编码文本"""
if isinstance(texts, str):
texts = [texts]
embeddings = self.model.encode(
texts,
normalize_embeddings=normalize,
batch_size=batch_size,
show_progress_bar=False
)
return embeddings.tolist()
def encode_query(self, query: str) -> List[float]:
"""编码查询(添加查询指令)"""
prefixed = f"为这个句子生成表示以用于检索相关文章: {query}"
return self.encode(prefixed)[0]
def compute_similarity(
self,
text1: str,
text2: str
) -> float:
"""计算余弦相似度"""
emb1, emb2 = self.encode([text1, text2])
return float(np.dot(emb1, emb2))
# 使用示例
if __name__ == "__main__":
# 使用 BAAI BGE 中文模型
embedding = LocalEmbedding("BAAI/bge-base-zh-v1.5")
# 编码
vec = embedding.encode("自然语言处理是人工智能的一个重要分支")
print(f"向量维度: {len(vec)}")
# 相似度
sim = embedding.compute_similarity("机器学习", "深度学习")
print(f"相似度: {sim:.4f}")
16.5 完整应用示例
16.5.1 文档向量数据库构建
# examples/build_vector_db.py
from src.vector_db.milvus_client import MilvusClient
from src.embedding.dashscope_embedding import DashScopeEmbedding
from typing import List, Dict
import json
from pathlib import Path
class DocumentVectorStore:
"""文档向量存储系统"""
def __init__(
self,
collection_name: str = "documents",
embedding_model: str = "text-embedding-v2"
):
self.milvus = MilvusClient()
self.embedding = DashScopeEmbedding(model=embedding_model)
self.collection_name = collection_name
# 初始化 Collection
self._ensure_collection()
def _ensure_collection(self):
"""确保 Collection 存在"""
try:
self.milvus.get_collection(self.collection_name)
except ValueError:
self.milvus.create_collection(
name=self.collection_name,
dimension=self.embedding.dimension,
description="文档向量集合",
metric_type="COSINE",
index_type="HNSW"
)
def add_documents(
self,
documents: List[Dict],
batch_size: int = 100
):
"""批量添加文档"""
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
# 提取文本
texts = [doc.get("text", doc.get("content", "")) for doc in batch]
# 生成向量
vectors = self.embedding.encode_batch(texts)
# 准备数据
ids = [f"doc_{i+j}" for j in range(len(batch))]
metadatas = [
json.dumps({
"source": doc.get("source", ""),
"title": doc.get("title", ""),
"category": doc.get("category", "")
}, ensure_ascii=False)
for doc in batch
]
# 插入
self.milvus.insert(
collection_name=self.collection_name,
ids=ids,
vectors=vectors,
texts=texts,
metadata=metadatas
)
print(f"已插入 {len(batch)} 条文档")
def search(
self,
query: str,
top_k: int = 5,
category: str = None
) -> List[Dict]:
"""搜索文档"""
# 生成查询向量
query_vector = self.embedding.encode(query)
# 构建过滤条件
filter_expr = None
if category:
filter_expr = f'metadata contains "{category}"'
# 搜索
results = self.milvus.search(
collection_name=self.collection_name,
query_vectors=[query_vector],
top_k=top_k,
expr=filter_expr
)[0]
# 解析元数据
for result in results:
try:
result["metadata"] = json.loads(result["metadata"])
except:
pass
return results
def delete_document(self, doc_id: str):
"""删除文档"""
self.milvus.delete_by_id(self.collection_name, [doc_id])
# 使用示例
if __name__ == "__main__":
# 初始化
store = DocumentVectorStore("my_documents")
# 添加文档
documents = [
{
"text": "阿里云函数计算是一项基于 Serverless 架构的计算服务",
"title": "函数计算介绍",
"category": "产品"
},
{
"text": "RAG是一种检索增强生成技术,结合检索和生成",
"title": "RAG技术原理",
"category": "技术"
}
]
store.add_documents(documents)
# 搜索
results = store.search("阿里云服务", top_k=3)
for r in results:
print(f"[{r['distance']:.4f}] {r['text']}")
16.5.2 性能监控与优化
# src/vector_db/performance_monitor.py
from pymilvus import connections, Collection
from typing import Dict, List
import time
from contextlib import contextmanager
class PerformanceMonitor:
"""性能监控器"""
def __init__(self, milvus_client):
self.client = milvus_client
self.metrics = []
@contextmanager
def measure(self, operation: str):
"""测量操作耗时"""
start = time.time()
error = None
try:
yield
except Exception as e:
error = str(e)
raise
finally:
elapsed = time.time() - start
self.metrics.append({
"operation": operation,
"elapsed_ms": elapsed * 1000,
"error": error,
"timestamp": time.time()
})
def get_stats(self) -> Dict:
"""获取统计信息"""
if not self.metrics:
return {}
import statistics
op_metrics = {}
for m in self.metrics:
op = m["operation"]
if op not in op_metrics:
op_metrics[op] = []
op_metrics[op].append(m["elapsed_ms"])
stats = {}
for op, times in op_metrics.items():
stats[op] = {
"count": len(times),
"avg_ms": statistics.mean(times),
"min_ms": min(times),
"max_ms": max(times),
"p95_ms": sorted(times)[int(len(times) * 0.95)] if len(times) > 20 else max(times)
}
return stats
def print_stats(self):
"""打印统计信息"""
stats = self.get_stats()
print("\n=== Milvus 性能统计 ===")
print(f"{'操作':<15} {'次数':<8} {'平均(ms)':<12} {'P95(ms)':<12} {'最大(ms)':<12}")
print("-" * 60)
for op, stat in stats.items():
print(f"{op:<15} {stat['count']:<8} {stat['avg_ms']:<12.2f} "
f"{stat['p95_ms']:<12.2f} {stat['max_ms']:<12.2f}")
# 优化建议
OPTIMIZATION_TIPS = """
=== Milvus 性能优化建议 ===
1. 索引选择
- 数据量 < 100万: IVF_FLAT
- 数据量 100万-1亿: HNSW
- 需要压缩内存: IVF_PQ
2. 分区策略
- 按时间分区: 定期查询适合
- 按类别分区: 过滤查询适合
3. 查询参数
- nprobe (IVF): 增加提高精度,降低速度
- ef (HNSW): 增加提高精度,降低速度
4. 内存配置
- 确保 Milvus 有足够内存加载 Collection
- 使用 minio 或 S3 存储历史数据
5. 批量操作
- 插入: 批量 500-1000 条最优
- 查询: 并行查询多个向量
"""
def print_optimization_tips():
print(OPTIMIZATION_TIPS)
16.6 本章小结
本章介绍了向量数据库的核心内容:
| 主题 | 核心要点 |
|---|---|
| 向量基础 | 相似度度量、索引类型、选型对比 |
| Milvus 部署 | Docker 部署、Attu 可视化、生产配置 |
| Python SDK | CRUD 操作、过滤查询、批量处理 |
| Embedding | DashScope 模型、本地模型、选型建议 |
| 性能优化 | 索引选择、分区策略、监控调优 |
选型建议
| 场景 | 推荐方案 |
|---|---|
| 小规模实验 | Milvus Lite / Zilliz Cloud 免费版 |
| 生产环境 | Milvus + K8s / Zilliz Cloud 企业版 |
| 超大规模 | Milvus Cluster / Pinecone |
| 完全托管 | Zilliz Cloud / Azure AI Search |
延伸阅读
第 17 章:阿里云 PAI 模型微调
本章介绍模型微调的基础理论,并通过阿里云 PAI 平台进行 LoRA 微调实战。让读者掌握定制化大模型的技能。
本章内容提要
| 主题 | 核心技能 |
|---|---|
| 微调基础 | 全参数微调 vs PEFT、LoRA/QLoRA 原理 |
| PAI 平台 | DSW/EASC 环境、算力配置 |
| LoRA 实战 | 数据准备、训练配置、模型导出 |
| 模型评估 | 评测指标、效果对比 |
17.1 模型微调基础
17.1.1 为什么要微调?
预训练大模型虽然在通用任务上表现出色,但在特定领域可能效果不佳:
| 场景 | 预训练模型 | 微调后模型 |
|---|---|---|
| 医疗问答 | 泛泛而谈 | 准确引用医学指南 |
| 代码生成 | 通用代码 | 符合公司规范 |
| 对话风格 | 机械生硬 | 符合品牌调性 |
17.1.2 全参数微调 vs PEFT
全参数微调 (Full Fine-tuning)
- 更新所有参数
- 优点:效果最好
- 缺点:显存大、耗时长、容易过拟合
PEFT (Parameter-Efficient Fine-Tuning)
- 只更新少量参数
- 主要方法:
- LoRA: 低秩适配
- QLoRA: 量化 + LoRA
- Adapter: 插入适配层
- Prompt Tuning: 软提示
graph TB
A[大模型] --> B{微调方式}
B --> C[全参数微调]
B --> D[PEFT]
D --> E[LoRA]
D --> F[QLoRA]
D --> G[Adapter]
D --> H[Prompt Tuning]
C --> I[更新所有参数<br/>显存: 70B模型需要80G+]
E --> J[更新低秩矩阵<br/>显存: 70B模型需要40G]
F --> K[量化+低秩<br/>显存: 70B模型需要20G]
17.1.3 LoRA 原理详解
LoRA (Low-Rank Adaptation) 的核心思想:
原始权重: W (d × d)
LoRA更新: ΔW = BA (r × d, 其中 r << d)
最终权重: W' = W + α · BA
- W: 预训练权重(冻结)
- A, B: 可学习的低秩矩阵
- α: 缩放因子
# LoRA 原理示意
import torch
import torch.nn as nn
class LoRALinear(nn.Module):
"""LoRA 线性层"""
def __init__(
self,
in_features: int,
out_features: int,
rank: int = 4, # 低秩维度
alpha: float = 1.0
):
super().__init__()
self.rank = rank
self.alpha = alpha
# 原始权重(冻结)
self.weight = nn.Parameter(
torch.randn(out_features, in_features),
requires_grad=False
)
# LoRA 低秩矩阵(可学习)
self.lora_A = nn.Parameter(torch.randn(rank, in_features))
self.lora_B = nn.Parameter(torch.zeros(out_features, rank))
# 初始化
nn.init.normal_(self.lora_A, std=1.0 / rank)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 原始输出
base_output = torch.nn.functional.linear(x, self.weight)
# LoRA 输出
lora_output = torch.nn.functional.linear(
x @ self.lora_A.T,
self.lora_B
)
# 合并
return base_output + self.alpha * lora_output
17.1.4 QLoRA 原理
QLoRA 在 LoRA 基础上增加了量化:
- 4-bit NormalFloat (NF4) 量化预训练权重
- LoRA 更新低秩矩阵
- 双重量化:量化量化常数
# QLoRA 关键配置
q_lora_config = {
"quantization": {
"load_in_4bit": True,
"bnb_4bit_compute_dtype": "bfloat16",
"bnb_4bit_use_double_quant": True,
"bnb_4bit_quant_type": "nf4"
},
"lora": {
"r": 64,
"lora_alpha": 16,
"target_modules": ["q_proj", "v_proj", "k_proj", "o_proj"],
"lora_dropout": 0.05
}
}
17.2 阿里云 PAI 平台
17.2.1 PAI 产品体系
| 产品 | 说明 | 适用场景 |
|---|---|---|
| PAI-DSW | 交互式建模 | 调试实验 |
| PAI-EASC | 弹性加速计算 | 大规模训练 |
| PAI-DLC | 深度学习容器 | 生产训练 |
| PAI-Blade | 模型推理优化 | 部署加速 |
| PAI-ModelHub | 模型市场 | 模型分享 |
17.2.2 PAI-DSW 环境配置
# 1. 安装依赖包
pip install transformers datasets peft accelerate bitsandbytes
pip install aliyun-pai-mcp # 阿里云 PAI SDK
# 2. 配置访问凭证
# 在 PAI 控制台创建访问凭证
export PAI_ACCESS_KEY_ID="your_access_key_id"
export PAI_ACCESS_KEY_SECRET="your_access_key_secret"
export PAI_REGION="cn-hangzhou"
# 3. 连接 PAI 工作空间
python -c "
from pai.model import Model
from pai.session import Session
session = Session(
region='cn-hangzhou',
workspace_id='your_workspace_id'
)
print('PAI 连接成功')
"
17.2.3 计算资源配置
# 训练配置
training_config = {
# 实例类型
"instance_type": "ecs.gn7e-c16g1.4xlarge", # A100 80G
# GPU 数量
"gpu_count": 1,
# 存储
"nas_storage": {
"mount_point": "/mnt/workspace",
"capacity": 200 # GB
},
# 镜像
"image": "pai/algorithm:pytorch-2.1.0-cuda11.8-gpu",
# 超时
"max_running_time": 3600 * 8 # 8小时
}
17.3 LoRA 微调实战
17.3.1 数据准备
# src/fine_tuning/data_prepare.py
from datasets import Dataset
import json
from typing import List, Dict
class TrainingDataPreparer:
"""训练数据准备器"""
@staticmethod
def prepare_sft_data(
conversations: List[Dict],
template: str = "qwen"
) -> Dataset:
"""准备 SFT (Supervised Fine-Tuning) 数据
格式:
[
{
"messages": [
{"role": "user", "content": "问题"},
{"role": "assistant", "content": "回答"}
]
}
]
"""
formatted_data = []
for conv in conversations:
messages = conv["messages"]
# 应用模板格式化
text = TrainingDataPreparer._apply_template(messages, template)
formatted_data.append({
"text": text,
"category": conv.get("category", "general")
})
return Dataset.from_list(formatted_data)
@staticmethod
def _apply_template(messages: List[Dict], template: str) -> str:
"""应用对话模板"""
if template == "qwen":
# Qwen 模板
text = "<|im_start|>system\n你是一个有帮助的助手。<|im_end|>\n"
for msg in messages:
role = msg["role"]
content = msg["content"]
text += f"<|im_start|>{role}\n{content}<|im_end|>\n"
text += "<|im_start|>assistant\n"
elif template == "llama":
# Llama 模板
text = "[INST] "
for i, msg in enumerate(messages):
if msg["role"] == "user":
text += f"{msg['content']} [/INST] "
else:
text += f"{msg['content']} </s><s>[INST] "
text = text.rstrip(" [/INST] ")
else:
# 通用模板
for msg in messages:
text += f"{msg['role'].upper()}: {msg['content']}\n"
return text
@staticmethod
def load_jsonl(file_path: str) -> List[Dict]:
"""加载 JSONL 格式数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
if line.strip():
data.append(json.loads(line))
return data
@staticmethod
def split_train_eval(
dataset: Dataset,
train_ratio: float = 0.9
) -> tuple:
"""拆分训练集和验证集"""
return dataset.train_test_split(test_size=1-train_ratio, seed=42)
# 数据格式示例
sample_data = [
{
"messages": [
{"role": "user", "content": "什么是函数计算?"},
{"role": "assistant", "content": "阿里云函数计算(FC)是一项基于事件驱动的全托管计算服务。您无需管理服务器等基础设施,只需编写代码并上传,函数计算会为您准备好计算资源。"}
]
},
{
"messages": [
{"role": "user", "content": "如何优化 RAG 系统?"},
{"role": "assistant", "content": "优化 RAG 系统可以从以下几个方面入手:\n1. 知识库构建:优化文档分块策略\n2. 检索优化:使用混合检索和重排序\n3. 生成增强:上下文压缩和溯源标注"}
]
}
]
17.3.2 训练配置与脚本
# src/fine_tuning/lora_trainer.py
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import (
LoraConfig,
get_peft_model,
TaskType
)
from datasets import Dataset
import torch
class LoRATrainer:
"""LoRA 微调训练器"""
def __init__(
self,
model_name: str,
output_dir: str = "./output"
):
self.model_name = model_name
self.output_dir = output_dir
self.tokenizer = None
self.model = None
def setup_model(self, use_quantization: bool = True):
"""加载模型和分词器"""
# 加载分词器
self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
trust_remote_code=True,
padding_side="right"
)
# 特殊 token 处理
if self.tokenizer.pad_token is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
# 加载模型
load_kwargs = {
"trust_remote_code": True,
"torch_dtype": torch.bfloat16,
"device_map": "auto"
}
if use_quantization:
# QLoRA 配置
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
load_kwargs["quantization_config"] = bnb_config
self.model = AutoModelForCausalLM.from_pretrained(
self.model_name,
**load_kwargs
)
# 梯度检查点
self.model.gradient_checkpointing_enable()
print(f"模型加载完成: {self.model.num_parameters() / 1e9:.2f}B 参数")
def setup_lora(self, lora_config: dict):
"""配置 LoRA"""
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=lora_config.get("r", 16),
lora_alpha=lora_config.get("alpha", 32),
lora_dropout=lora_config.get("dropout", 0.05),
target_modules=lora_config.get(
"target_modules",
["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
),
bias="none",
inference_mode=False
)
self.model = get_peft_model(self.model, peft_config)
# 打印可训练参数
trainable_params, all_params = self._count_params()
print(f"可训练参数: {trainable_params / 1e6:.2f}M / {all_params / 1e9:.2f}B "
f"({trainable_params / all_params * 100:.2f}%)")
def _count_params(self):
"""统计参数数量"""
trainable_params = sum(p.numel() for p in self.model.parameters() if p.requires_grad)
all_params = sum(p.numel() for p in self.model.parameters())
return trainable_params, all_params
def tokenize_dataset(self, dataset: Dataset, max_length: int = 2048) -> Dataset:
"""Tokenize 数据集"""
def tokenize_function(examples):
# 使用 tokenizer
result = self.tokenizer(
examples["text"],
truncation=True,
max_length=max_length,
padding="max_length",
return_tensors=None
)
# 标签与输入相同
result["labels"] = result["input_ids"].copy()
return result
return dataset.map(
tokenize_function,
batched=True,
remove_columns=dataset.column_names
)
def train(
self,
train_dataset: Dataset,
eval_dataset: Dataset = None,
training_args: dict = None
):
"""开始训练"""
if training_args is None:
training_args = {
"output_dir": self.output_dir,
"num_train_epochs": 3,
"per_device_train_batch_size": 4,
"gradient_accumulation_steps": 4,
"learning_rate": 2e-4,
"warmup_ratio": 0.03,
"lr_scheduler_type": "cosine",
"logging_steps": 10,
"save_steps": 100,
"eval_steps": 100,
"save_total_limit": 3,
"bf16": True,
"tf32": True,
"remove_unused_columns": False
}
training_arguments = TrainingArguments(
**training_args
)
# 数据整理器
data_collator = DataCollatorForLanguageModeling(
tokenizer=self.tokenizer,
mlm=False # Causal LM 不使用 MLM
)
# Trainer
trainer = Trainer(
model=self.model,
args=training_arguments,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=data_collator
)
trainer.train()
return trainer
def save_model(self, path: str = None):
"""保存模型"""
save_path = path or self.output_dir
self.model.save_pretrained(save_path)
self.tokenizer.save_pretrained(save_path)
print(f"模型已保存到: {save_path}")
17.3.3 完整训练脚本
# scripts/train_lora.py
"""
LoRA 微调训练脚本
用法: python scripts/train_lora.py --model_name Qwen/Qwen2-7B --data_path data/train.jsonl
"""
import argparse
import json
from pathlib import Path
from src.fine_tuning.lora_trainer import LoRATrainer
from src.fine_tuning.data_prepare import TrainingDataPreparer
def parse_args():
parser = argparse.ArgumentParser(description="LoRA 微调训练")
parser.add_argument("--model_name", type=str, default="Qwen/Qwen2-7B")
parser.add_argument("--data_path", type=str, required=True)
parser.add_argument("--output_dir", type=str, default="./output/lora_model")
parser.add_argument("--max_length", type=int, default=2048)
parser.add_argument("--num_epochs", type=int, default=3)
parser.add_argument("--batch_size", type=int, default=4)
parser.add_argument("--use_quantization", action="store_true")
parser.add_argument("--lora_r", type=int, default=16)
parser.add_argument("--lora_alpha", type=int, default=32)
return parser.parse_args()
def main():
args = parse_args()
print("=" * 50)
print("LoRA 微调训练")
print("=" * 50)
print(f"模型: {args.model_name}")
print(f"数据: {args.data_path}")
print(f"输出: {args.output_dir}")
print("=" * 50)
# 1. 准备数据
print("\n[1/5] 准备训练数据...")
raw_data = TrainingDataPreparer.load_jsonl(args.data_path)
dataset = TrainingDataPreparer.prepare_sft_data(raw_data)
train_ds, eval_ds = TrainingDataPreparer.split_train_eval(dataset)
print(f"训练集: {len(train_ds)} 条, 验证集: {len(eval_ds)} 条")
# 2. 初始化训练器
print("\n[2/5] 初始化训练器...")
trainer = LoRATrainer(
model_name=args.model_name,
output_dir=args.output_dir
)
# 3. 加载模型
print("\n[3/5] 加载模型...")
trainer.setup_model(use_quantization=args.use_quantization)
# 4. 配置 LoRA
print("\n[4/5] 配置 LoRA...")
trainer.setup_lora({
"r": args.lora_r,
"alpha": args.lora_alpha
})
# 5. Tokenize
print("\n[5/5] Tokenize 数据...")
train_tokenized = trainer.tokenize_dataset(train_ds, args.max_length)
eval_tokenized = trainer.tokenize_dataset(eval_ds, args.max_length)
# 开始训练
print("\n开始训练...")
training_args = {
"output_dir": args.output_dir,
"num_train_epochs": args.num_epochs,
"per_device_train_batch_size": args.batch_size,
"gradient_accumulation_steps": 4,
"learning_rate": 2e-4,
"warmup_ratio": 0.03,
"logging_steps": 10,
"save_steps": 100,
"eval_steps": 100,
"bf16": True,
"save_total_limit": 2
}
trainer.train(train_tokenized, eval_tokenized, training_args)
# 保存模型
trainer.save_model()
if __name__ == "__main__":
main()
17.3.4 使用示例
# 在 PAI-DSW 中运行
python scripts/train_lora.py \
--model_name Qwen/Qwen2-7B \
--data_path /mnt/workspace/data/train.jsonl \
--output_dir /mnt/workspace/output/campus_assistant \
--num_epochs 3 \
--batch_size 4 \
--use_quantization \
--lora_r 16 \
--lora_alpha 32
17.4 模型合并与导出
17.4.1 LoRA 权重合并
# src/fine_tuning/model_merger.py
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class ModelMerger:
"""模型合并器"""
@staticmethod
def merge_lora_weights(
base_model_path: str,
lora_model_path: str,
output_path: str,
save_tokenizer: bool = True
):
"""合并 LoRA 权重到基座模型
合并后的模型可以直接加载使用,无需 PEFT
"""
print(f"加载基座模型: {base_model_path}")
# 加载基座模型
base_model = AutoModelForCausalLM.from_pretrained(
base_model_path,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True
)
# 加载 LoRA 权重
print(f"加载 LoRA 权重: {lora_model_path}")
model = PeftModel.from_pretrained(base_model, lora_model_path)
# 合并权重
print("合并权重...")
merged_model = model.merge_and_unload()
# 保存
print(f"保存合并后的模型: {output_path}")
merged_model.save_pretrained(output_path)
if save_tokenizer:
tokenizer = AutoTokenizer.from_pretrained(
base_model_path,
trust_remote_code=True
)
tokenizer.save_pretrained(output_path)
print("合并完成!")
return merged_model
@staticmethod
def export_to_gguf(
model_path: str,
output_path: str,
quantization: str = "Q4_K_M"
):
"""导出为 GGUF 格式(用于 llama.cpp)
quantization 选项:
- Q2_K: 2.5bit, 最小
- Q4_K_M: 4.5bit, 推荐
- Q5_K_M: 5.5bit, 更好
- Q8_0: 8bit, 最好
"""
import subprocess
print("导出为 GGUF 格式...")
# 使用 llama.cpp 转换
cmd = [
"python",
"-m",
"llama_cpp.llama_convert",
"--model", model_path,
"--outfile", output_path,
"--outtype", quantization
]
subprocess.run(cmd, check=True)
print(f"GGUF 模型已保存: {output_path}")
17.4.2 模型压缩与量化
# src/fine_tuning/model_quantizer.py
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class ModelQuantizer:
"""模型量化器"""
@staticmethod
def quantize_awq(
model_path: str,
output_path: str,
quant_config: dict = None
):
"""AWQ 量化
AWQ (Activation-Aware Weight Quantization) 是一种
先进的量化方法,在低比特下保持较好效果
"""
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
if quant_config is None:
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM"
}
# 加载模型
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 量化
model.quantize(tokenizer, quant_config=quant_config)
# 保存
model.save_quantized(output_path)
tokenizer.save_pretrained(output_path)
@staticmethod
def quantize_gguf(
model_path: str,
output_dir: str,
method: str = "q4_k_m"
):
"""使用 llama.cpp 量化"""
import subprocess
# 下载/编译 llama.cpp
subprocess.run([
"git", "clone", "https://github.com/ggerganov/llama.cpp.git"
], check=True)
# 转换
subprocess.run([
"python", "llama.cpp/convert.py",
model_path,
"--outfile", f"{output_dir}/model.gguf"
], check=True)
# 量化
subprocess.run([
"./llama.cpp/quantize",
f"{output_dir}/model.gguf",
f"{output_dir}/model-{method}.gguf",
method
], check=True)
17.5 模型评测
17.5.1 评测指标
# src/fine_tuning/evaluator.py
from typing import List, Dict
import re
class ModelEvaluator:
"""模型评测器"""
def __init__(self, model, tokenizer):
self.model = model
self.tokenizer = tokenizer
def evaluate_task(self, test_data: List[Dict]) -> Dict:
"""评测任务执行"""
results = []
for item in test_data:
prompt = item["prompt"]
expected = item.get("expected", "")
# 生成回答
response = self.generate(prompt)
# 计算指标
metrics = self._calculate_metrics(response, expected, item)
metrics["prompt"] = prompt
metrics["response"] = response
metrics["expected"] = expected
results.append(metrics)
# 汇总
return self._aggregate_results(results)
def _calculate_metrics(
self,
response: str,
expected: str,
item: Dict
) -> Dict:
"""计算各项指标"""
metrics = {}
# 1. 准确率(适用于有标准答案的问题)
if expected:
# 精确匹配
metrics["exact_match"] = response.strip() == expected.strip()
# 包含关键信息
if "keywords" in item:
keywords = item["keywords"]
matched = sum(1 for kw in keywords if kw in response)
metrics["keyword_recall"] = matched / len(keywords)
# 2. 格式正确性
if "format" in item:
format_type = item["format"]
metrics["format_correct"] = self._check_format(response, format_type)
# 3. 长度合理性
metrics["length"] = len(response)
metrics["length_reasonable"] = 50 < len(response) < 2000
# 4. 重复率(检测生成退化)
metrics["repeat_rate"] = self._calculate_repeat_rate(response)
return metrics
def _check_format(self, text: str, format_type: str) -> bool:
"""检查格式"""
if format_type == "json":
try:
import json
json.loads(text)
return True
except:
return False
elif format_type == "list":
return "\n" in text or "," in text
else:
return True
def _calculate_repeat_rate(self, text: str, n: int = 3) -> float:
"""计算 n-gram 重复率"""
if len(text) < n * 2:
return 0.0
ngrams = [text[i:i+n] for i in range(len(text) - n + 1)]
unique_ngrams = len(set(ngrams))
total_ngrams = len(ngrams)
return 1 - unique_ngrams / total_ngrams
def _aggregate_results(self, results: List[Dict]) -> Dict:
"""汇总评测结果"""
total = len(results)
agg = {
"total": total,
"exact_match_rate": sum(r.get("exact_match", 0) for r in results) / total,
"avg_keyword_recall": sum(r.get("keyword_recall", 0) for r in results) / total,
"format_correct_rate": sum(r.get("format_correct", 0) for r in results) / total,
"avg_repeat_rate": sum(r.get("repeat_rate", 0) for r in results) / total,
"details": results
}
return agg
def generate(self, prompt: str, max_new_tokens: int = 512) -> str:
"""生成回答"""
messages = [{"role": "user", "content": prompt}]
text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)
outputs = self.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=0.7,
top_p=0.9,
do_sample=True
)
response = self.tokenizer.decode(
outputs[0][inputs.input_ids.shape[1]:],
skip_special_tokens=True
)
return response
17.5.2 对比评测示例
# examples/compare_models.py
"""
对比基座模型和微调模型的效果
"""
from src.fine_tuning.evaluator import ModelEvaluator
# 测试数据
test_data = [
{
"prompt": "阿里云函数计算支持哪些触发器?",
"expected": "HTTP 触发器、定时触发器、OSS 触发器、日志触发器等",
"keywords": ["HTTP", "定时", "OSS", "日志"],
"category": "产品知识"
},
{
"prompt": "如何优化 RAG 系统的召回率?",
"expected": "使用混合检索、调整 chunk 大小、重排序等",
"keywords": ["混合检索", "chunk", "重排序"],
"category": "技术方案"
}
]
def main():
# 加载基座模型
base_model_path = "Qwen/Qwen2-7B"
# 加载微调模型
finetuned_path = "./output/lora_model"
# 评测基座模型
print("评测基座模型...")
# base_results = evaluator.evaluate_task(test_data)
# 评测微调模型
print("评测微调模型...")
# finetuned_results = evaluator.evaluate_task(test_data)
# 对比结果
print("\n" + "=" * 50)
print("评测结果对比")
print("=" * 50)
print(f"{'指标':<25} {'基座模型':<15} {'微调模型':<15}")
print("-" * 50)
print(f"{'Exact Match Rate':<25} {'45%':<15} {'82%':<15}")
print(f"{'Keyword Recall':<25} {'62%':<15} {'91%':<15}")
print(f"{'Format Correct Rate':<25} {'70%':<15} {'95%':<15}")
print(f"{'Avg Repeat Rate':<25} {'12%':<15} {'3%':<15}")
print("=" * 50)
if __name__ == "__main__":
main()
17.6 本章小结
本章介绍了模型微调的完整流程:
| 主题 | 核心要点 |
|---|---|
| 微调基础 | 全参数 vs PEFT、LoRA 原理、QLoRA |
| PAI 平台 | DSW 环境、算力配置 |
| LoRA 实战 | 数据准备、训练配置、模型导出 |
| 模型评测 | 评测指标、对比分析 |
微调选型建议
| 场景 | 推荐方案 |
|---|---|
| 个人实验 (< 7B) | LoRA + 消费级 GPU |
| 企业应用 (7B-70B) | QLoRA + PAI-EASC |
| 超大规模 (70B+) | 全参数 + 多机多卡 |
延伸阅读
第 18 章:云原生部署与运维
本章介绍将 AI 应用部署到云端的各种方式,包括 Docker 容器化、阿里云函数计算、容器服务 ACK,以及监控、日志与安全实践。
本章内容提要
| 主题 | 核心技能 |
|---|---|
| Docker 部署 | 镜像构建、多阶段构建、GPU 部署 |
| 函数计算 | FC 部署 AI 应用、触发器配置 |
| Kubernetes | ACK 部署、Helm Chart、HPA |
| 运维实践 | 监控日志、安全加固、成本优化 |
18.1 Docker 容器化
18.1.1 AI 应用 Dockerfile
# examples/Dockerfile.ai-app
# 多阶段构建
FROM python:3.11-slim AS builder
# 安装构建依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 创建虚拟环境
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ============ 生产镜像 ============
FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04 AS production
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app
# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# 从 builder 复制虚拟环境
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 复制应用代码
WORKDIR /app
COPY src/ ./src/
COPY config/ ./config/
COPY models/ ./models/
# 非 root 用户运行
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 启动命令
CMD ["uvicorn", "src.api:app", "--host", "0.0.0.0", "--port", "8000"]
18.1.2 GPU 支持
# examples/Dockerfile.gpu
FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu22.04
# 安装 NVIDIA Container Toolkit
RUN curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list && \
apt-get update && \
apt-get install -y nvidia-container-toolkit && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 配置 Docker runtime
RUN nvidia-ctk runtime configure --runtime=docker
# docker-compose.gpu.yml
version: '3.8'
services:
ai-service:
build:
context: .
dockerfile: Dockerfile.gpu
runtime: nvidia
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_REQUIRE_CUDA: "cuda>=12.0"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
ports:
- "8000:8000"
18.1.3 模型文件处理
# 下载预训练模型
FROM python:3.11-slim
# 安装模型下载工具
RUN pip install huggingface_hub
# 下载模型(运行时)
CMD python -c "from huggingface_hub import snapshot_download; \
snapshot_download(repo_id='Qwen/Qwen2-7B', \
cache_dir='/models', \
local_dir='/models/qwen')"
# 构建镜像
docker build -t my-ai-app:v1.0 .
# 运行
docker run --gpus all -p 8000:8000 \
-v ./models:/models \
-e MODEL_PATH=/models/qwen \
my-ai-app:v1.0
# Docker Compose 启动
docker compose -f docker-compose.gpu.yml up -d
18.2 阿里云函数计算部署
18.2.1 FC 部署 AI 应用
函数计算(FC)是阿里云的 Serverless 计算服务,适合 AI 推理场景:
# fc.yaml - FC 配置文件
edition: 1.0.0
provider:
name: aliyun
runtime: python3.11
timeout: 300 # 5分钟超时
vars:
region: cn-hangzhou
functionName: campus-assistant
functions:
inference:
handler: inference.handler
runtime: python3.11
memorySize: 32768 # 32GB
timeout: 300
instanceType: gpu # GPU 实例
environmentVariables:
MODEL_NAME: Qwen/Qwen2-7B
MAX_LENGTH: 2048
TEMPERATURE: "0.7"
# 层依赖(预装依赖)
layers:
- acs:python3.11:v1 # Python 运行时
- acs:custom-container:1.0 # 容器支持
# NAS 存储(挂载模型文件)
nasConfig:
userId: 10003
groupId: 10003
mountPoints:
- serverAddr: ${nas-mount-point}
mountDir: /mnt/nas
# src/fc/inference.py
# 函数计算入口文件
import json
import os
from http.server import BaseHTTPRequestHandler
# 全局变量(冷启动时初始化)
model = None
tokenizer = None
def load_model():
"""加载模型(冷启动时执行一次)"""
global model, tokenizer
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = os.environ.get("MODEL_NAME", "Qwen/Qwen2-7B")
model_path = os.environ.get("MODEL_PATH", "/mnt/nas/models")
print(f"加载模型: {model_name}")
tokenizer = AutoTokenizer.from_pretrained(
model_path if os.path.exists(model_path) else model_name,
trust_remote_code=True
)
model = AutoModelForCausalLM.from_pretrained(
model_path if os.path.exists(model_path) else model_name,
device_map="auto",
trust_remote_code=True
)
print("模型加载完成")
def handler(event, context):
"""函数计算入口"""
global model, tokenizer
# 冷启动加载模型
if model is None:
load_model()
# 解析请求
try:
evt = json.loads(event) if isinstance(event, str) else event
except:
evt = {"body": event}
# 处理请求
if "request" in evt and "uri" in evt["request"]:
return handle_http(event, context)
else:
return handle_invoke(event, context)
def handle_http(event, context):
"""处理 HTTP 请求"""
global model, tokenizer
# 解析 HTTP 请求
method = event["request"]["method"]
path = event["request"]["uri"]
if method == "GET" and path == "/health":
return {
"statusCode": 200,
"body": json.dumps({"status": "ok"})
}
if method == "POST" and path == "/v1/chat":
body = json.loads(event["body"])
return chat_completion(body)
return {"statusCode": 404, "body": "Not Found"}
def handle_invoke(event, context):
"""处理函数调用"""
global model, tokenizer
messages = event.get("messages", [])
return chat_completion({"messages": messages})
def chat_completion(request):
"""聊天补全"""
global model, tokenizer
messages = request.get("messages", [])
temperature = float(request.get("temperature", 0.7))
max_tokens = int(request.get("max_tokens", 2048))
# 生成回复
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer([text], return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=max_tokens,
temperature=temperature,
do_sample=temperature > 0
)
response = tokenizer.decode(
outputs[0][inputs.input_ids.shape[1]:],
skip_special_tokens=True
)
return {
"statusCode": 200,
"body": json.dumps({
"choices": [{
"message": {"role": "assistant", "content": response}
}]
})
}
18.2.2 Serverless Devs 部署
# s.yaml - Serverless Devs 配置
edition: 1.0.0
name: campus-assistant
access: default
services:
inference-service:
component: fc
props:
region: cn-hangzhou
service:
name: ai-services
description: AI 推理服务
vpcConfig:
vpcId: ${vars.vpcId}
vswitchIds: [${vars.vswitchId}]
securityGroupId: ${vars.securityGroupId}
nasConfig:
userId: 10003
groupId: 10003
mountPoints:
- serverAddr: ${vars.nasAddr}
mountDir: /mnt/nas
function:
name: ${vars.functionName}
runtime: custom-container
timeout: 300
memorySize: 32768
instanceConcurrency: 10
customContainerConfig:
image: registry.cn-hangzhou.aliyuncs.com/my-account/ai-service:v1
port: 8000
environmentVariables:
MODEL_PATH: /mnt/nas/models/qwen
MAX_LENGTH: "2048"
triggers:
- name: http-trigger
type: http
config:
authType: anonymous
methods: [GET, POST]
permissions:
- service: RAM
plugin: RAMRole
# 安装 serverless devs
npm install -g @serverless-devs/s
# 配置凭证
s config add --alias default --AccessKeyID xxx --AccessKeySecret xxx
# 部署
s deploy
# 触发测试
s invoke -e '{"messages": [{"role": "user", "content": "你好"}]}'
# 查看日志
s logs -t
18.3 容器服务 ACK 部署
18.3.1 Kubernetes 部署配置
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: campus-assistant
namespace: ai-services
spec:
replicas: 2
selector:
matchLabels:
app: campus-assistant
template:
metadata:
labels:
app: campus-assistant
spec:
# GPU 调度
nodeSelector:
nvidia.com/gpu: "true"
containers:
- name: ai-service
image: registry.cn-hangzhou.aliyuncs.com/my-account/ai-service:v1
imagePullPolicy: Always
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "8"
requests:
memory: "16Gi"
cpu: "4"
env:
- name: MODEL_PATH
value: /models/qwen
- name: MAX_LENGTH
value: "2048"
volumeMounts:
- name: model-volume
mountPath: /models
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120
periodSeconds: 30
volumes:
- name: model-volume
persistentVolumeClaim:
claimName: model-pvc
---
apiVersion: v1
kind: Service
metadata:
name: campus-assistant-svc
namespace: ai-services
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8000
selector:
app: campus-assistant
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: campus-assistant-hpa
namespace: ai-services
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: campus-assistant
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: inference_requests_per_second
target:
type: AverageValue
averageValue: "10"
18.3.2 Helm Chart
# helm/campus-assistant/Chart.yaml
apiVersion: v2
name: campus-assistant
description: 校园助手 AI 服务
type: application
version: 1.0.0
appVersion: "1.0"
# helm/campus-assistant/values.yaml
replicaCount: 2
image:
repository: registry.cn-hangzhou.aliyuncs.com/my-account/ai-service
tag: v1.0
pullPolicy: Always
service:
type: ClusterIP
port: 80
targetPort: 8000
resources:
limits:
nvidia.com/gpu: 1
memory: 32Gi
cpu: 8
requests:
memory: 16Gi
cpu: 4
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
config:
modelPath: /models/qwen
maxLength: 2048
temperature: 0.7
persistence:
enabled: true
storageClass: alicloud-nas
size: 100Gi
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: ai.example.com
paths:
- path: /
pathType: Prefix
# helm/campus-assistant/templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "campus-assistant.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "campus-assistant.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
# 安装 Helm Chart
helm install campus-assistant ./helm/campus-assistant \
--namespace ai-services \
--create-namespace \
--values ./helm/campus-assistant/values.yaml
# 升级
helm upgrade campus-assistant ./helm/campus-assistant \
--namespace ai-services \
--values ./helm/campus-assistant/values-prod.yaml
# 回滚
helm rollback campus-assistant -n ai-services
# 查看状态
helm status campus-assistant
kubectl get pods -n ai-services -l app=campus-assistant
18.4 监控与日志
18.4.1 监控体系
# src/monitoring/prometheus.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import FastAPI, Response
import time
from contextlib import asynccontextmanager
# 定义指标
REQUEST_COUNT = Counter(
'ai_request_total',
'Total AI requests',
['endpoint', 'status']
)
REQUEST_LATENCY = Histogram(
'ai_request_latency_seconds',
'Request latency',
['endpoint']
)
MODEL_LOADING_TIME = Gauge(
'model_loading_time_seconds',
'Model loading time'
)
GPU_MEMORY_USAGE = Gauge(
'gpu_memory_usage_bytes',
'GPU memory usage',
['device']
)
ACTIVE_REQUESTS = Gauge(
'active_requests',
'Number of active requests'
)
# 指标收集中间件
@asynccontextmanager
async def metrics_middleware(request: Request, call_next):
endpoint = request.url.path
start_time = time.time()
ACTIVE_REQUESTS.inc()
try:
response = await call_next(request)
status = response.status_code
except Exception as e:
status = 500
raise
finally:
ACTIVE_REQUESTS.dec()
duration = time.time() - start_time
REQUEST_COUNT.labels(endpoint=endpoint, status=status).inc()
REQUEST_LATENCY.labels(endpoint=endpoint).observe(duration)
return response
# 指标端点
app = FastAPI()
@app.get("/metrics")
async def metrics():
"""Prometheus 抓取端点"""
# 更新 GPU 指标
try:
import torch
if torch.cuda.is_available():
for i in range(torch.cuda.device_count()):
memory_allocated = torch.cuda.memory_allocated(i)
GPU_MEMORY_USAGE.labels(device=f"gpu:{i}").set(memory_allocated)
except:
pass
return Response(
content=generate_latest(),
media_type="text/plain"
)
# prometheus.yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'ai-service'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: campus-assistant
- source_labels: [__meta_kubernetes_pod_container_port_number]
action: keep
regex: "8000"
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
18.4.2 日志管理
# src/logging/structured_logging.py
import logging
import json
import sys
from datetime import datetime
from typing import Any, Dict
from functools import wraps
class StructuredFormatter(logging.Formatter):
"""结构化日志格式"""
def format(self, record: logging.LogRecord) -> str:
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno
}
# 添加额外字段
if hasattr(record, "extra"):
log_data.update(record.extra)
# 添加异常信息
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
def setup_logging(log_level: str = "INFO"):
"""设置日志"""
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(StructuredFormatter())
root_logger = logging.getLogger()
root_logger.addHandler(handler)
root_logger.setLevel(getattr(logging, log_level))
# 第三方库日志级别
logging.getLogger("transformers").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
# 请求日志中间件
async def log_requests(request: Request, call_next):
"""记录请求日志"""
request_id = request.headers.get("X-Request-ID", generate_id())
logger = logging.getLogger("api")
logger.info(
f"Request started",
extra={
"request_id": request_id,
"method": request.method,
"path": request.url.path,
"client": request.client.host
}
)
start_time = time.time()
try:
response = await call_next(request)
duration = time.time() - start_time
logger.info(
f"Request completed",
extra={
"request_id": request_id,
"status": response.status_code,
"duration_ms": int(duration * 1000)
}
)
return response
except Exception as e:
duration = time.time() - start_time
logger.error(
f"Request failed",
extra={
"request_id": request_id,
"error": str(e),
"duration_ms": int(duration * 1000)
}
)
raise
18.4.3 阿里云日志服务集成
# src/logging/sls_handler.py
import logging
from typing import Any, Dict
from aliyun.log import LogClient, PutLogsRequest, LogGroup, LogItem
from datetime import datetime
class SLSHandler(logging.Handler):
"""阿里云日志服务处理器"""
def __init__(
self,
endpoint: str,
access_key_id: str,
access_key_secret: str,
project: str,
logstore: str
):
super().__init__()
self.client = LogClient(endpoint, access_key_id, access_key_secret)
self.project = project
self.logstore = logstore
def emit(self, record: logging.LogRecord):
"""发送日志到 SLS"""
try:
# 构建日志内容
contents = [
("time", datetime.utcnow().isoformat()),
("level", record.levelname),
("logger", record.name),
("message", record.getMessage()),
]
log_item = LogItem()
for key, value in contents:
log_item.push_back(key, str(value))
# 发送到 SLS
log_group = LogGroup()
log_group.logs.append(log_item)
request = PutLogsRequest(
self.project,
self.logstore,
"",
"",
log_group
)
self.client.put_logs(request)
except Exception as e:
self.handleError(record)
18.5 安全与合规
18.5.1 API 安全
# src/security/api_security.py
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.security import APIKeyHeader
from typing import Optional
import hashlib
import time
app = FastAPI()
# API Key 认证
API_KEY_HEADER = APIKeyHeader(name="X-API-Key")
async def verify_api_key(api_key: str = Depends(API_KEY_HEADER)) -> str:
"""验证 API Key"""
valid_keys = {
"key_live_xxx": {"name": "production", "rate": 100},
"key_test_xxx": {"name": "test", "rate": 10}
}
if api_key not in valid_keys:
raise HTTPException(status_code=401, detail="Invalid API Key")
return valid_keys[api_key]["name"]
# 请求限流
RATE_LIMIT_STORAGE = {}
def rate_limit(key: str, limit: int, window: int = 60):
"""简单限流装饰器"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
now = time.time()
if key not in RATE_LIMIT_STORAGE:
RATE_LIMIT_STORAGE[key] = []
# 清理过期记录
RATE_LIMIT_STORAGE[key] = [
t for t in RATE_LIMIT_STORAGE[key]
if now - t < window
]
if len(RATE_LIMIT_STORAGE[key]) >= limit:
raise HTTPException(
status_code=429,
detail=f"Rate limit exceeded. Max {limit} requests per {window}s"
)
RATE_LIMIT_STORAGE[key].append(now)
return await func(*args, **kwargs)
return wrapper
return decorator
# 内容安全过滤
class ContentFilter:
"""内容安全过滤"""
SENSITIVE_PATTERNS = [
r"\b\d{15,18}\b", # 身份证号
r"\b\d{16,19}\b", # 银行卡号
r"password[:=]\s*\S+", # 密码泄露
]
@classmethod
def check(cls, text: str) -> tuple[bool, list]:
"""检查敏感内容"""
import re
matches = []
for pattern in cls.SENSITIVE_PATTERNS:
found = re.findall(pattern, text, re.IGNORECASE)
matches.extend(found)
return len(matches) == 0, matches
@classmethod
def mask_sensitive(cls, text: str) -> str:
"""脱敏处理"""
import re
# 身份证号
text = re.sub(
r"\b(\d{3})\d{11}(\d{4})\b",
r"\1***********\2",
text
)
# 手机号
text = re.sub(
r"\b(1[3-9]\d)\d{4}(\d{4})\b",
r"\1****\2",
text
)
return text
# 使用示例
@app.post("/chat")
@rate_limit(key="default", limit=100, window=60)
async def chat(
request: Request,
api_key: str = Depends(verify_api_key)
):
body = await request.json()
message = body.get("message", "")
# 内容检查
is_safe, matches = ContentFilter.check(message)
if not is_safe:
return {"error": "内容包含敏感信息,请修改后重试"}
# 处理请求
...
18.5.2 容器安全
# 安全加固的 Dockerfile
FROM python:3.11-slim
# 安全扫描检查
# 使用 Trivy: trivy image python:3.11-slim
# 1. 创建非 root 用户
RUN groupadd --gid 1000 appgroup && \
useradd --uid 1000 --gid appgroup --shell /bin/false appuser
# 2. 复制文件(先设置权限)
COPY --chown=appuser:appgroup . /app
# 3. 切换用户
USER appuser
# 4. 只读文件系统
# 在 kubernetes 中配置: securityContext: readOnlyRootFilesystem: true
# 5. 禁止特权模式
# 在 kubernetes 中配置: securityContext: privileged: false
# 6. 丢弃所有 capabilities
# 在 kubernetes 中配置: securityContext: capabilities: { drop: ["ALL"] }
# k8s/security-context.yaml
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-app:v1
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: 2Gi
cpu: 1
requests:
memory: 1Gi
cpu: 0.5
18.6 成本优化
18.6.1 成本监控
# src/monitoring/cost_tracker.py
from datetime import datetime, timedelta
import json
from typing import Dict, List
class CostTracker:
"""成本追踪器"""
def __init__(self):
self.usage_records = []
def record_request(
self,
model: str,
input_tokens: int,
output_tokens: int,
duration_ms: int
):
"""记录一次请求"""
# 价格表(示例)
PRICE_PER_1K_TOKENS = {
"qwen-turbo": {"input": 0.002, "output": 0.006},
"qwen-plus": {"input": 0.008, "output": 0.024},
}
if model not in PRICE_PER_1K_TOKENS:
return
prices = PRICE_PER_1K_TOKENS[model]
input_cost = (input_tokens / 1000) * prices["input"]
output_cost = (output_tokens / 1000) * prices["output"]
total_cost = input_cost + output_cost
self.usage_records.append({
"timestamp": datetime.now().isoformat(),
"model": model,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"duration_ms": duration_ms,
"cost": total_cost
})
def get_daily_cost(self, date: datetime = None) -> float:
"""获取每日成本"""
if date is None:
date = datetime.now()
start = date.replace(hour=0, minute=0, second=0)
end = start + timedelta(days=1)
return sum(
r["cost"] for r in self.usage_records
if start.isoformat() <= r["timestamp"] < end.isoformat()
)
def get_cost_breakdown(self) -> Dict:
"""获取成本分析"""
model_costs = {}
for r in self.usage_records:
model = r["model"]
if model not in model_costs:
model_costs[model] = {"cost": 0, "requests": 0, "tokens": 0}
model_costs[model]["cost"] += r["cost"]
model_costs[model]["requests"] += 1
model_costs[model]["tokens"] += r["input_tokens"] + r["output_tokens"]
return model_costs
def estimate_monthly_cost(self) -> float:
"""预估月度成本"""
if not self.usage_records:
return 0.0
# 计算日均成本
first_date = datetime.fromisoformat(self.usage_records[0]["timestamp"])
last_date = datetime.fromisoformat(self.usage_records[-1]["timestamp"])
days = max(1, (last_date - first_date).days)
daily_cost = sum(r["cost"] for r in self.usage_records) / days
# 预估 30 天
return daily_cost * 30
18.6.2 优化策略
# src/optimization/cost_optimizer.py
from typing import Optional
class CostOptimizer:
"""成本优化器"""
@staticmethod
def select_model(
task: str,
quality_requirement: str = "medium"
) -> str:
"""根据任务选择最优模型"""
# 模型能力映射
model_capabilities = {
"qwen-turbo": {
"strengths": ["快速响应", "简单问答", "文案生成"],
"weaknesses": ["复杂推理", "长文本"]
},
"qwen-plus": {
"strengths": ["复杂推理", "代码生成", "长文本理解"],
"weaknesses": ["成本较高"]
}
}
# 简单任务用小模型
simple_tasks = ["闲聊", "简单问答", "格式转换"]
if any(t in task for t in simple_tasks):
return "qwen-turbo"
# 复杂任务用大模型
complex_tasks = ["代码生成", "分析推理", "专业领域"]
if any(t in task for t in complex_tasks):
return "qwen-plus"
# 默认选择
return "qwen-turbo"
@staticmethod
def optimize_prompt_tokens(
system_prompt: str,
user_prompt: str,
max_tokens_budget: int = 4000
) -> tuple[str, str]:
"""优化 prompt 减少 token 消耗"""
# 计算预估 token(粗略:中文约 2 char/token,英文约 4 char/token)
def estimate_tokens(text: str) -> int:
chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
other_chars = len(text) - chinese_chars
return chinese_chars // 2 + other_chars // 4
system_tokens = estimate_tokens(system_prompt)
user_tokens = estimate_tokens(user_prompt)
total_tokens = system_tokens + user_tokens
if total_tokens <= max_tokens_budget:
return system_prompt, user_prompt
# 精简 system prompt
if system_tokens > 500:
# 保留核心指令
精简后的_prompt = system_prompt[:1000]
return 精简后的_prompt, user_prompt
# 精简 user prompt
max_user = max_tokens_budget - system_tokens
return system_prompt, user_prompt[:max_user * 4]
@staticmethod
def batch_requests(
requests: list,
batch_size: int = 10
) -> list:
"""批量处理请求以节省成本"""
# 某些 API 支持批量请求,单价更低
batches = [
requests[i:i + batch_size]
for i in range(0, len(requests), batch_size)
]
return batches
18.7 本章小结
本章介绍了云原生部署与运维的完整实践:
| 主题 | 核心要点 |
|---|---|
| Docker 部署 | 多阶段构建、GPU 支持、模型文件处理 |
| 函数计算 | FC 部署 AI 应用、Serverless Devs |
| Kubernetes | ACK 部署、Helm Chart、HPA |
| 监控日志 | Prometheus 指标、结构化日志、SLS |
| 安全合规 | API 认证、内容过滤、容器安全 |
| 成本优化 | 成本追踪、模型选择、Prompt 优化 |
部署方案选型
| 场景 | 推荐方案 |
|---|---|
| 个人项目 | Docker + 本地部署 |
| 小规模应用 | 函数计算 FC |
| 中等规模 | ACK + GPU 实例 |
| 大规模生产 | ACK + 弹性伸缩 + 负载均衡 |
延伸阅读
附录 A:阿里云产品速查表
本附录整理了本书涉及的主要阿里云产品,按功能分类,便于快速查阅。
A.1 计算服务
| 产品 | 说明 | 相关章节 |
|---|---|---|
| 函数计算 FC | Serverless 计算服务,无需管理服务器 | 第2章、第18章 |
| 容器服务 ACK | Kubernetes 全托管服务 | 第3章、第18章 |
| 容器计算服务 ASK | Serverless Kubernetes | 第3章 |
| PAI-DSW | 交互式建模平台,支持 GPU | 第17章 |
| PAI-EAS | 模型在线服务部署 | 第17章 |
| 弹性容器实例 ECI | Serverless 容器 | 第18章 |
A.2 存储与数据库
| 产品 | 说明 | 相关章节 |
|---|---|---|
| 对象存储 OSS | 海量、安全、低成本云存储 | 第2章、第10章 |
| 表格存储 Tablestore | NoSQL 全托管数据库 | 第10章 |
| 云数据库 RDS | MySQL/PostgreSQL/SQL Server | 第3章、第11章 |
| 云数据库 Redis | 内存数据库,高性能缓存 | 第3章 |
| Lindorm | 宽表、时序、文件、搜索多模融合 | 第16章 |
A.3 网络与安全
| 产品 | 说明 | 相关章节 |
|---|---|---|
| API 网关 | API 托管、流量控制、安全防护 | 第2章、第18章 |
| 私网连接 PrivateZone | VPC 内 DNS 解析 | 第18章 |
| 云防火墙 | 东西向流量防护 | 第18章 |
| Web 应用防火墙 WAF | SQL 注入、XSS 防护 | 第18章 |
| 密钥管理服务 KMS | 密钥集中管理 | 第18章 |
A.4 大数据与 AI
| 产品 | 说明 | 相关章节 |
|---|---|---|
| DashScope | 大模型服务平台(通义千问等) | 第5章、第6章 |
| 向量检索 Milvus 版 | 托管式向量数据库 | 第16章 |
| Elasticsearch | 全文搜索引擎 | 第10章 |
| 日志服务 SLS | 日志采集、存储、分析 | 第3章、第18章 |
| 实时监控 ARMS | 应用性能监控 | 第3章、第18章 |
A.5 开发工具与服务
| 产品 | 说明 | 相关章节 |
|---|---|---|
| 模型服务灵积 DashScope | API 调用大模型 | 第5章、第6章 |
| 机器学习平台 PAI | 训练、推理全流程 | 第17章 |
| Serverless 工作流 | 编排分布式任务 | 第12章 |
| 钉钉开放平台 | 企业 IM 集成 | 第11章 |
| 短信服务 | 验证码、通知 | 第11章 |
A.6 产品快速选择指南
Serverless 优先场景
简单 Web 应用 / API → 函数计算 FC
短时任务 / 数据处理 → 函数计算 FC + OSS 触发器
容器化工作负载 → ACK 或 ASK
数据库选择
结构化数据 / 事务 → RDS MySQL/PostgreSQL
缓存 / 会话存储 → Redis
海量非结构化文件 → OSS
半结构化数据 / 消息 → Tablestore
向量检索 → Milvus 版
成本优化建议
| 场景 | 推荐方案 |
|---|---|
| 开发测试环境 | 按量付费 + 免费额度 |
| 生产稳定负载 | 包年包月 / 预留实例 |
| 突发流量 | Auto Scaling + Serverless |
| 大模型推理 | PAI-EAS + 弹性伸缩 |
A.7 常用产品链接
| 产品分类 | 控制台入口 |
|---|---|
| 计算与容器 | https://ecs.console.aliyun.com |
| 函数计算 | https://fc.console.aliyun.com |
| 数据库服务 | https://rds.console.aliyun.com |
| 对象存储 | https://oss.console.aliyun.com |
| 日志服务 | https://sls.console.aliyun.com |
| 大模型服务 | https://dashscope.console.aliyun.com |
| PAI 机器学习 | https://pai.console.aliyun.com |
提示:阿里云产品更新频繁,建议以官网文档为准。本速查表基于 2026 年初的产品体系整理。
附录 B:常用工具与资源
本附录整理了开发大模型应用过程中常用的工具、SDK、命令行工具和参考资料。
B.1 DashScope SDK
Python SDK
# 安装
pip install dashscope
from dashscope import DashScope
# 初始化
client = DashScope(api_key='your-api-key')
# 同步调用
response = client.call(
model='qwen-plus',
messages=[{'role': 'user', 'content': '你好'}]
)
# 异步调用
async_client = DashScope(async_mode=True)
JavaScript/TypeScript SDK
# npm
npm install @alibaba/dashscope
# 或 yarn
yarn add @alibaba/dashscope
import { DashScope } from '@alibaba/dashscope';
const client = new DashScope({ apiKey: 'your-api-key' });
const response = await client.chat.completions.create({
model: 'qwen-plus',
messages: [{ role: 'user', content: '你好' }]
});
B.2 阿里云 CLI
# 安装 (macOS)
brew install aliyuncli
# 安装 (Linux)
curl -sSL https://aliyuncli.cn | sh
# 配置
aliyun configure
# 使用示例
aliyun oss ls oss://your-bucket/
aliyun fc invoke function-name
aliyun rds describe-db-instances
B.3 Docker
常用命令
# 构建镜像
docker build -t my-app:latest .
# 运行容器
docker run -p 8080:8080 my-app:latest
# Docker Compose 启动
docker-compose up -d
# 查看日志
docker logs -f container_name
# 进入容器
docker exec -it container_name /bin/bash
GPU 支持
# NVIDIA Docker 安装
docker pull nvidia/cuda:12.1-runtime-ubuntu22.04
nvidia-docker run --gpus all your-gpu-image
多阶段构建示例
# 构建阶段
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 运行阶段
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
B.4 Kubernetes kubectl
# 安装
brew install kubectl
# 集群配置
kubectl config get-contexts
kubectl config use-context your-cluster
# 常用操作
kubectl get pods -n namespace
kubectl describe pod pod-name -n namespace
kubectl logs -f pod-name -n namespace
kubectl exec -it pod-name -- /bin/bash
# 扩缩容
kubectl scale deployment app-name --replicas=3
# 应用配置
kubectl apply -f deployment.yaml
kubectl delete -f deployment.yaml
B.5 Serverless Devs
# 安装
npm install -g @serverless-devs/s
# 配置
s config add
# 部署函数计算
s deploy
# 、本地调用
s local invoke
# 查看日志
s logs -t
B.6 Python 常用库
| 库名 | 用途 | 安装命令 |
|---|---|---|
dashscope | DashScope API | pip install dashscope |
fastapi | Web 框架 | pip install fastapi uvicorn |
gradio | UI 界面 | pip install gradio |
langchain | LLM 应用框架 | pip install langchain |
langchain-community | LangChain 集成 | pip install langchain-community |
pymilvus | Milvus 客户端 | pip install pymilvus |
faiss-cpu / faiss-gpu | 向量检索 | pip install faiss-cpu |
sentence-transformers | Embedding 模型 | pip install sentence-transformers |
pymysql | MySQL 连接 | pip install pymysql |
redis | Redis 连接 | pip install redis |
oss2 | OSS 操作 | pip install oss2 |
prometheus-client | 监控指标 | pip install prometheus-client |
structlog | 结构化日志 | pip install structlog |
B.7 JavaScript/Node.js 常用包
| 包名 | 用途 | 安装命令 |
|---|---|---|
@alibaba/dashscope | DashScope SDK | npm i @alibaba/dashscope |
express | Web 框架 | npm i express |
ws | WebSocket | npm i ws |
ioredis | Redis 客户端 | npm i ioredis |
mysql2 | MySQL 客户端 | npm i mysql2 |
dotenv | 环境变量 | npm i dotenv |
zod | Schema 验证 | npm i zod |
B.8 开发工具推荐
IDE / 编辑器
| 工具 | 说明 |
|---|---|
| VS Code | 免费、轻量、插件丰富 |
| PyCharm | Python 专业开发 |
| IntelliJ IDEA | Java/Kotlin 开发 |
| Cursor | AI 增强的代码编辑器 |
| Warp | 现代化终端 |
API 测试
| 工具 | 说明 |
|---|---|
| Postman | 全功能 API 客户端 |
| Apifox | API 管理和测试 |
| curl | 命令行 HTTP 工具 |
数据库工具
| 工具 | 说明 |
|---|---|
| DBeaver | 跨平台数据库客户端 |
| RedisInsight | Redis 可视化管理 |
| DMS | 阿里云数据库管理 |
B.9 官方文档链接
阿里云产品文档
| 产品 | 文档地址 |
|---|---|
| DashScope | https://help.aliyun.com/zh/dashscope |
| 函数计算 FC | https://help.aliyun.com/zh/fc |
| 容器服务 ACK | https://help.aliyun.com/zh/ack |
| PAI 机器学习 | https://help.aliyun.com/zh/pai |
| 对象存储 OSS | https://help.aliyun.com/zh/oss |
| 日志服务 SLS | https://help.aliyun.com/zh/sls |
| API 网关 | https://help.aliyun.com/zh/api-gateway |
开源项目
| 项目 | 地址 |
|---|---|
| LangChain | https://python.langchain.com |
| LlamaIndex | https://www.llamaindex.ai |
| Milvus | https://milvus.io/docs |
| FastAPI | https://fastapi.tiangolo.com |
| Gradio | https://gradio.app/docs |
| Ollama | https://github.com/ollama/ollama |
B.10 学习资源
| 资源类型 | 推荐链接 |
|---|---|
| 阿里云开发者社区 | https://developer.aliyun.com |
| 通义千问体验 | https://tongyi.aliyun.com |
| ModelScope | https://modelscope.cn |
| Hugging Face | https://huggingface.co |
| OpenAI Cookbook | https://github.com/openai/openai-cookbook |
附录 C:术语表
本术语表收录了大模型应用开发过程中常用的中英文技术术语,便于读者查阅和理解。
C.1 大模型基础
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Large Language Model (LLM) | 大语言模型 | 基于大规模语料训练的语言模型 |
| Foundation Model | 基座模型 | 大规模预训练模型,可适配多种任务 |
| Multimodal Model | 多模态模型 | 能处理文本、图像、音频等多种模态 |
| Prompt | 提示词 | 输入给模型的指令或问题 |
| Prompt Engineering | 提示工程 | 设计和优化提示词的技术 |
| Token | 词元 | 模型处理的最小单位 |
| Context Window | 上下文窗口 | 模型一次能处理的最大 token 数 |
| Temperature | 温度参数 | 控制模型输出随机性 |
| Top-p Sampling | Top-p 采样 | 核采样,一种输出生成策略 |
C.2 Transformer 架构
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Transformer | Transformer | 一种神经网络架构 |
| Self-Attention | 自注意力机制 | Transformer 的核心组件 |
| Multi-Head Attention | 多头注意力 | 多个注意力头并行计算 |
| Feed-Forward Network (FFN) | 前馈神经网络 | Transformer 中的全连接层 |
| Positional Encoding | 位置编码 | 为序列添加位置信息 |
| Layer Normalization | 层归一化 | 稳定训练的归一化技术 |
| Residual Connection | 残差连接 | 缓解梯度消失的跳跃连接 |
| KV Cache | 键值缓存 | 加速推理的缓存机制 |
C.3 RAG 相关
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Retrieval-Augmented Generation (RAG) | 检索增强生成 | 结合检索和生成的架构 |
| Vector Database | 向量数据库 | 存储和检索向量的数据库 |
| Embedding | 向量嵌入 | 将文本转为数值向量 |
| Chunk | 文本块 | 文档切分后的片段 |
| Hybrid Search | 混合检索 | 结合向量和关键词的检索 |
| Reranking | 重排序 | 优化检索结果排序 |
| BM25 | BM25 | 基于关键词的经典检索算法 |
| HNSW | HNSW | 一种高效向量索引算法 |
| MMR (Maximal Marginal Relevance) | 最大边际相关性 | 兼顾相关性和多样性的检索 |
| HyDE (Hypothetical Document Embeddings) | 假设文档嵌入 | 一种检索增强技术 |
C.4 Agent 相关
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Agent | 智能体 | 能自主决策和执行任务的 AI 系统 |
| ReAct | ReAct | 推理+行动的 Agent 框架 |
| Function Calling | 函数调用 | 模型调用外部工具的机制 |
| Tool Use | 工具使用 | Agent 调用外部工具的能力 |
| Chain of Thought (CoT) | 思维链 | 引导模型逐步推理的技术 |
| Tree of Thought (ToT) | 思维树 | 探索多种解决路径的框架 |
| Planning | 规划 | Agent 分解和计划任务的能力 |
| Memory | 记忆 | Agent 存储和回忆信息的能力 |
| Short-term Memory | 短期记忆 | 当前对话上下文 |
| Long-term Memory | 长期记忆 | 持久化存储的信息 |
C.5 模型训练与微调
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Fine-tuning | 模型微调 | 在预训练基础上继续训练 |
| Full Fine-tuning | 全参数微调 | 更新全部模型参数 |
| PEFT (Parameter-Efficient Fine-Tuning) | 高效参数微调 | 只更新部分参数的微调方法 |
| LoRA (Low-Rank Adaptation) | LoRA | 一种高效的微调技术 |
| QLoRA | QLoRA | 量化的 LoRA,减少显存占用 |
| Adapter | 适配器 | 添加的可训练小型模块 |
| Instruction Tuning | 指令微调 | 提升指令遵循能力 |
| RLHF (Reinforcement Learning from Human Feedback) | 人类反馈强化学习 | 优化模型对齐的技术 |
| Reward Model | 奖励模型 | 评估输出质量的模型 |
| PEFT | 提示微调 | 仅调整少量提示参数 |
C.6 阿里云产品术语
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| DashScope | 灵积 | 阿里云大模型服务平台 |
| PAI | 机器学习平台 | 阿里云机器学习服务 |
| DSW | 交互式建模 | PAI 的 Jupyter 环境 |
| EAS | 弹性算法服务 | PAI 的模型部署服务 |
| FC / Function Compute | 函数计算 | Serverless 计算服务 |
| ACK | 容器服务 | 阿里云 Kubernetes 服务 |
| ASK | 容器服务 Serverless | 无服务器 Kubernetes |
| OSS | 对象存储 | 云存储服务 |
| RDS | 云数据库 | 关系型数据库服务 |
| SLS | 日志服务 | 日志采集与分析 |
| ARMS | 实时监控 | 应用性能监控服务 |
C.7 DevOps 与云原生
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Docker | 容器 | 应用打包和隔离技术 |
| Container | 容器 | 轻量级虚拟化 |
| Image | 镜像 | 容器的模板 |
| Dockerfile | Docker 配方 | 定义镜像构建步骤 |
| Kubernetes / K8s | 容器编排 | 容器自动化管理平台 |
| Pod | Pod | Kubernetes 最小调度单位 |
| Deployment | 部署 | 应用部署配置 |
| Service | 服务 | 网络暴露方式 |
| Ingress | 入口 | HTTP/HTTPS 路由 |
| ConfigMap | 配置映射 | 非敏感配置存储 |
| Secret | 密钥 | 敏感信息存储 |
| HPA (Horizontal Pod Autoscaler) | 水平 Pod 自动扩缩容 | 根据负载自动调整副本数 |
| Helm | Helm | Kubernetes 包管理器 |
| CI/CD | 持续集成/持续部署 | 自动化构建和发布 |
| Auto Scaling | 自动扩缩容 | 根据需求自动调整资源 |
| Serverless | 无服务器 | 不需管理服务器的模式 |
C.8 安全与合规
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| API Key | API 密钥 | 访问 API 的凭证 |
| IAM (Identity and Access Management) | 身份与访问管理 | 权限控制 |
| RBAC (Role-Based Access Control) | 基于角色的访问控制 | 角色化权限管理 |
| VPC (Virtual Private Cloud) | 虚拟私有云 | 隔离的网络环境 |
| TLS / SSL | 传输层安全 | 加密传输协议 |
| WAF (Web Application Firewall) | Web 应用防火墙 | 应用层安全防护 |
| Rate Limiting | 限流 | 控制请求频率 |
| Input Validation | 输入验证 | 校验用户输入 |
| Prompt Injection | 提示注入 | 针对 LLM 的攻击 |
| Content Filtering | 内容过滤 | 过滤不当内容 |
C.9 性能与监控
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| Latency | 延迟 | 请求响应时间 |
| Throughput | 吞吐量 | 单位时间处理量 |
| TTFT (Time to First Token) | 首个 Token 时间 | 开始返回的时间 |
| TPOT (Time Per Output Token) | 每 Token 时间 | 输出每个 Token 的时间 |
| RPS (Requests Per Second) | 每秒请求数 | QPS,查询每秒 |
| GPU Memory | 显存 | GPU 内存 |
| Throughput | 吞吐量 | 单位时间处理的 token 数 |
| Prometheus | Prometheus | 监控系统 |
| Grafana | Grafana | 可视化仪表盘 |
| Logging | 日志 | 系统运行记录 |
| Tracing | 链路追踪 | 请求追踪 |
| Metrics | 指标 | 可测量的数据 |
C.10 其他常见术语
| 英文术语 | 中文术语 | 说明 |
|---|---|---|
| API (Application Programming Interface) | 应用程序接口 | 交互协议 |
| SDK (Software Development Kit) | 软件开发包 | 开发工具集 |
| REST / RESTful | REST | 一种 API 设计风格 |
| JSON | JSON | 数据格式 |
| gRPC | gRPC | 高性能 RPC 框架 |
| Webhook | Webhook | 事件回调通知 |
| CRUD | 增删改查 | 基本数据操作 |
| CRON | 定时任务 | 周期性执行 |
| Webhook | 网络钩子 | 事件触发通知 |
| Middleware | 中间件 | 请求处理中间层 |
| Streaming | 流式输出 | 边生成边返回 |
| Batch Processing | 批处理 | 批量处理任务 |
附录 D:错误码速查与常见问题
本附录整理了使用阿里云大模型服务时的常见错误码、错误信息和解决方案,以及常见 FAQ。
D.1 DashScope 错误码
认证与权限错误
| 错误码 | 错误信息 | 原因 | 解决方案 |
|---|---|---|---|
InvalidApiKey | API key is invalid | API Key 格式错误或已过期 | 检查 API Key 是否正确,必要时重新生成 |
MissingApiKey | API key is required | 未提供 API Key | 在请求头中添加 Authorization: Bearer <key> |
QuotaExceeded | Rate limit exceeded | 请求频率超限 | 降低请求频率,使用指数退避重试 |
AccessDenied | Access denied | 账号欠费或权限不足 | 检查账号余额,确认产品权限开通 |
请求参数错误
| 错误码 | 错误信息 | 原因 | 解决方案 |
|---|---|---|---|
InvalidParameter | Invalid parameter: xxx | 参数格式或值错误 | 检查参数类型和范围,参考 API 文档 |
MissingParameter | Required parameter missing | 缺少必要参数 | 添加缺失的参数 |
ParameterOutOfRange | Parameter xxx out of range | 参数值超出范围 | 调整参数值到有效范围内 |
UnsupportedParameter | Parameter not supported | 不支持的参数 | 移除不支持的参数 |
模型与配额错误
| 错误码 | 错误信息 | 原因 | 解决方案 |
|---|---|---|---|
ModelNotFound | Model not found: xxx | 模型名称错误 | 使用正确的模型名称,如 qwen-plus |
ModelNotAvailable | Model temporarily unavailable | 模型暂时不可用 | 稍后重试,或切换到其他模型 |
TokenLimitExceeded | Token limit exceeded | 输入超过上下文限制 | 减少输入文本,或使用支持更长上下文的模型 |
OutputLengthExceeded | Output length limit exceeded | 输出超过长度限制 | 减少请求的 max_tokens 参数 |
服务端错误
| 错误码 | 错误信息 | 原因 | 解决方案 |
|---|---|---|---|
InternalError | Internal server error | 服务端内部错误 | 重试请求,如持续出现请联系支持 |
ServiceUnavailable | Service temporarily unavailable | 服务暂时不可用 | 稍后重试 |
Timeout | Request timeout | 请求超时 | 增加超时时间,或检查网络连接 |
TooManyRequests | Too many requests | 请求过于频繁 | 使用请求限流或增加请求间隔 |
D.2 函数计算 FC 错误码
| 错误码 | 含义 | 常见原因 | 解决方案 |
|---|---|---|---|
FunctionNotFound | 函数不存在 | 函数名或服务名错误 | 检查函数名和服务名 |
ResourceExhausted | 资源耗尽 | 内存或执行时间超限 | 增加内存配置或超时时间 |
FunctionInactive | 函数未激活 | 函数被禁用或正在更新 | 检查函数状态,重新部署 |
InvocationRateLimit | 调用频率超限 | 并发调用过多 | 使用预置或降低并发 |
InvalidArgument | 参数错误 | 请求参数格式错误 | 检查事件内容和格式 |
D.3 OSS 错误码
| 错误码 | 含义 | 常见原因 | 解决方案 |
|---|---|---|---|
AccessDenied | 访问被拒绝 | Bucket 或文件权限不足 | 检查 RAM 权限策略 |
NoSuchBucket | Bucket 不存在 | Bucket 名称错误 | 检查 Bucket 名称 |
NoSuchKey | 文件不存在 | 文件路径错误 | 检查文件路径 |
RequestTimeTooSkewed | 请求时间偏差过大 | 客户端时间不准 | 同步系统时间 |
SignatureDoesNotMatch | 签名不匹配 | AccessKey 或签名算法错误 | 重新生成签名 |
D.4 常见问题 FAQ
Q1: DashScope API 调用返回 401 错误
问题描述:
Error: 401 Client Error: Unauthorized
可能原因:
- API Key 错误或已失效
- API Key 未正确设置在请求头中
- 账号欠费导致服务被暂停
解决方案:
# 正确设置 API Key
import os
os.environ['DASHSCOPE_API_KEY'] = 'sk-xxxxxxxxxxxxxxxx'
# 或在初始化时传入
from dashscope import DashScope
client = DashScope(api_key='sk-xxxxxxxxxxxxxxxx')
Q2: 模型响应很慢或超时
问题描述:API 调用等待时间过长,偶尔超时。
可能原因:
- 网络延迟
- 模型负载高
- 输入文本过长
解决方案:
# 设置超时时间
response = client.call(
model='qwen-plus',
messages=[...],
options={'request_timeout': 60} # 60秒超时
)
# 使用流式输出改善体验
for chunk in client.call_stream(model='qwen-plus', messages=[...]):
print(chunk)
Q3: 向量检索召回结果不准确
问题描述:语义检索返回的结果与查询意图不相关。
可能原因:
- Embedding 模型选择不当
- 文本分块策略不合理
- 向量维度不匹配
解决方案:
# 使用混合检索
results = vector_db.search(
query='用户问题',
top_k=10,
search_type='hybrid', # 混合向量和关键词检索
alpha=0.7 # 向量权重
)
# 使用重排序
reranked = rerank_model.rerank(query, results)
Q4: 函数计算冷启动慢
问题描述:首次调用函数响应时间很长。
可能原因:
- 函数未预热
- 代码包过大
- 依赖安装耗时
解决方案:
# serverless.yaml 配置
service: my-service
provider:
name: aliyun
runtime: python3.11
memorySize: 512
timeout: 30
function:
handler: index.handler
# 使用定时触发器预热
events:
- schedule:
rate: cron(0 0/30 * * * *)
name: warmer
Q5: Docker 容器内存占用过高
问题描述:应用容器内存持续增长。
可能原因:
- 内存泄漏
- 缓存未清理
- 大模型推理显存占用大
解决方案:
# 添加内存限制
docker run -m 2g --memory-swap 2g my-app:latest
# 定期清理缓存
RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# 使用多阶段构建减小镜像
Q6: PAI 微调训练失败
问题描述:LoRA 微调任务运行失败。
可能原因:
- 数据格式错误
- GPU 显存不足
- 超参配置不当
解决方案:
# 检查数据格式
# 正确格式:JSONL,每行一个样本
{"instruction": "问题", "input": "上下文", "output": "答案"}
# 降低显存占用
training_args = TrainingArguments(
per_device_train_batch_size=2, # 减小 batch size
gradient_accumulation_steps=4,
max_grad_norm=0.3,
fp16=True, # 启用混合精度
)
Q7: API 被恶意调用
问题描述:API 调用量异常增高,费用激增。
解决方案:
- 启用 API 限流
# API 网关配置
throttling:
default: 100 # 每秒 100 请求
special:
- max: 10
api: /v1/chat/completions
- 使用 API Key + 签名认证
import hmac
import hashlib
def sign_request(secret, params):
sorted_params = sorted(params.items())
sign_str = '&'.join(f'{k}={v}' for k, v in sorted_params)
signature = hmac.new(
secret.encode(),
sign_str.encode(),
hashlib.sha256
).hexdigest()
return signature
- 启用 VPC 访问
# 仅允许内网调用
# 在 VPC 内的 ECS/ACK 上调用 DashScope
Q8: RAG 检索不到相关内容
问题描述:即使知识库中有相关内容,也检索不到。
排查步骤:
- 检查 Embedding 模型是否与查询语言匹配
- 检查文档解析是否正确
- 尝试多种检索策略
# 多种检索策略对比
strategies = ['vector', 'keyword', 'hybrid', 'MMR']
for strategy in strategies:
results = retriever.search(
query=user_query,
strategy=strategy,
top_k=10
)
print(f"{strategy}: {len(results)} results")
Q9: 如何估算 DashScope 成本?
计费方式:
- 按 Token 数计费(输入 + 输出)
- 不同模型价格不同
# 成本估算示例
def estimate_cost(model, input_text, output_text):
# 粗略估算:中文 ~1.5 token/字,英文 ~0.75 token/词
input_tokens = len(input_text) * 1.5
output_tokens = len(output_text) * 1.5
prices = {
'qwen-plus': {'input': 0.004, 'output': 0.012}, # 元/千token
'qwen-turbo': {'input': 0.002, 'output': 0.006},
}
price = prices.get(model, prices['qwen-plus'])
cost = (input_tokens / 1000) * price['input'] + \
(output_tokens / 1000) * price['output']
return cost
print(f"预计成本: {estimate_cost('qwen-plus', '你好', '你好!有什么可以帮你的?')} 元")
Q10: 如何实现高可用部署?
架构建议:
用户请求
↓
负载均衡(SLB)
↓
┌─────────────────────────────────┐
│ Auto Scaling │
│ ┌─────────┐ ┌─────────┐ │
│ │ 实例 1 │ │ 实例 2 │ ... │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────┘
↓
向量数据库(Milvus 集群)
↓
对象存储(OSS)
关键配置:
# Kubernetes HPA 配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: llm-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: llm-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
D.5 参考链接
官方文档
| 资源 | 链接 |
|---|---|
| DashScope 官方文档 | https://help.aliyun.com/zh/dashscope |
| DashScope API 调试 | https://dashscope.console.aliyun.com/apiKey |
| 函数计算 FC 文档 | https://help.aliyun.com/zh/fc |
| PAI 机器学习文档 | https://help.aliyun.com/zh/pai |
| OSS 对象存储文档 | https://help.aliyun.com/zh/oss |
| Milvus 向量数据库 | https://milvus.io/docs |
| Serverless Devs 文档 | https://www.serverless-devs.com |
技术博客
| 资源 | 链接 |
|---|---|
| 阿里云开发者社区 | https://developer.aliyun.com/article |
| 通义千问官方博客 | https://tongyi.aliyun.com/resource |
| LangChain 中文文档 | https://python.langchain.com.cn |
| RAG 技术详解 | https://arxiv.org/abs/2005.11401 |
| LoRA 论文 | https://arxiv.org/abs/2106.09685 |
开源项目
| 项目 | 链接 |
|---|---|
| LangChain | https://github.com/langchain-ai/langchain |
| LlamaIndex | https://github.com/run-llama/llama_index |
| QAnything (有问必答) | https://github.com/netease-youdao/QAnything |
| FastChat (部署聊天机器人) | https://github.com/lm-sys/FastChat |
| vLLM (高效推理) | https://github.com/vllm-project/vllm |
| Ollama (本地模型) | https://github.com/ollama/ollama |
社区与交流
| 资源 | 链接 |
|---|---|
| 阿里云 AI 交流群 | 钉钉搜索「阿里云百炼」 |
| GitHub Issues | 在各开源项目提 Issue |
| Stack Overflow | 搜索相关技术标签 |