|
2 weeks ago | |
---|---|---|
doc | 3 months ago | |
img | 2 weeks ago | |
src | 2 weeks ago | |
.gitignore | 3 months ago | |
README.md | 2 weeks ago | |
pom.xml | 3 months ago |
README.md
前置
课程大纲
- 课程介绍
- AI智能化云盘大课:后端分布式大项目+结合AI大模型智能体开发+业务应用
- 众多新技术+后端业务领域超多解决方案+AI大模型多案例应用场景落地
- 多语言开发:云盘板块采用Java开发后端项目,AI智能化板块采用Python+LangChain框架+大模型开发;
- AI大模型Agent开发和主流解决方案和三方类库等多案例实战
- 项目核心技术体系
- 后端业务全新技术:SpringBoot3.X全家桶+JDK21+超多实用java生态类库+大文件传输处理
- AI大模型全新技术:Python3.1X+FastAPI框架+全新LangChain框架+向量数据库Milvus+多个大模型
- 智能化云盘 多数一线大厂正在研发的业务领域,全网首个后端业务+AI大模型一起的项目教程
- LLM大模型支持在线大模型调用和本地私有化部署,包括不限于ChatGLM、GPT-4、通义千问、LLaMa等
- 后端业务开发板块:打造私有化云盘,大文件上传、秒传、下载、在线分享等核心模块
- AI大模型Agent板块
- LLM大模型Prompt工程、RAG知识库构建、Agent智能体开发
- Memory长短期记忆、LCEL、Tools自定义工具、MaaS模型服务搭建等
- 后端业务全新技术:SpringBoot3.X全家桶+JDK21+超多实用java生态类库+大文件传输处理
- 核心业务模块应用场景
- 业务应用类似阿里/百度云盘、NAS等,支持多类型文件存储和处理,支持多类型存储架构
- 基于云盘存储文件,结合LLM大模型,开发多个Agent智能体,应用多个业务场景,包括不限于
- 从0到1讲解AI大模型基础+项目实战,拓展前端/后端工程师必备的人工智能知识和应用实战
- 智能机器人Chat助理:长短期记忆的个人助理、智能客服、智能销售顾问等
- 企业问答知识库: 知识库检索内容问答、自定义上传到知识库、在线解析URL地址、实时联网搜索等
- 文档AI助手:文档概要总结、内容进行分段总结、AIGC营销内容生产等
- 基于云盘存储文件,结合LLM大模型,开发多个Agent智能体,应用多个业务场景,包括不限于
- 业务应用类似阿里/百度云盘、NAS等,支持多类型文件存储和处理,支持多类型存储架构
- AI大模型的行业解决方案和案例库参考
高频问题解答
-
问题:大模型是直接调用API吗,就是调用通义千问或者文心一言接口吗?
- ,LLM大模型只是简单调用API?那如何和后端业务+数据库数据联动?
- 架构也和微服务类似,LLM重试机制、兜底降级机制等等怎么做?敏感数据敢上传外部?
- 比如
- 某一次失败之后应该怎么处理,还有日志生成、管理资源、性能优化、准确性等等
- 这一些都是很关键的,靠普通的API是解决不了的,这个就是大课的部分解决方案,还有更多!!!!
- 通义千问或者文心一言都是一个基层模型底座,这些大模型类似我们的操作系统,不是商业应用程序
- 类似我们会基于操作系统上开发App软件;那AI应用就是基于这些大模型作为底座,开发上层的商业智能化应用
- 比如
- 公司需要做智能知识库、行业智能客服、智慧政务、AI律师、AI客服等,那就没法用这些平台
- 因为你公司不可能把敏感数据上传上去,而且也没法做到;
- 比如律师行业,医疗行业,财税行业等专业领域知识都是。
- 所以直接调用外部的API完全不一样,像通义千问等只是通用大模型,适合个人提升效率啥的,这个很容易。
- 但是达不到商用级别,也难和公司的业务结合一起;
- 像很多公司都是有沉淀很多历史的资料,文档记录,案例等,而且又敏感,不能上传外部的LLM平台
- 所以都需要私有化部署,针对公司本身所处的行业进行深度定制和优化,结合常规的后端和前端项目整合一起
-
问题:学完这个大课,可以开发怎么样的项目和应用呢?
-
AI文档助手
- 你可以给一堆专业文档,包括word文档、PDF等,让AI工具帮你生成 文档总结做周报、季度汇报等
- 给公司培训的的时候,可以从网上寻找很多资料,但是杂乱分散,可以让AI帮你整理和汇总,排版清晰
- 让AI帮你写多类型跳槽简历、毕业论文
-
企业知识库
- 将企业的各类知识资源进行智能化归类、整合,形成一套问题与答案的集合
- 【企业内部知识共享】作为企业内部的知识共享平台,帮助员工快速获取所需知识,提高团队协作效率
- 【客户服务】AI企业问答知识库可以为客户提供快速准确的解答服务 理解客户的问题并给出相应的答案
- 【员工培训】AI企业问答知识库还可以作为员工培训的平台,根据员工的个人需求进行定制化培训
-
私人AI助理
- 聊天与陪伴:私人AI助理可以陪伴用户聊天、讲笑话、玩小游戏等,提供轻松愉快的休闲娱乐体验。
- 个性化推荐:根据用户的喜好和行为习惯,推荐音乐、电影、书籍等娱乐内容。
- 健康管理:监测家庭成员的健康状况,提供运动、饮食建议,甚至可以协助医生进行诊断
-
特定领域智能聊天机器人
-
通过给AI一系列资料,单独训练特定领域,然后让帮我们做出决策
-
比如
-
各个大公司财报和历史股票行情信息,让AI汇总和给出指导建议
-
给出医院检查报告等,AI训练可以给出诊断和建议
-
给出特定领域销售部门的日常话术和专业知识, 充当智能客服
-
-
-
-
问题:什么是AI大模型应用,什么是AI大模型底层原理?课程是重点讲解哪块?
- AI大模型应用开发
- 就是我们用的很多人工智能工具,比如【智能美颜相机】【智能客服机器人】
- LLM应用层面很多很多:企业问答、智能律师、智慧政务、税务等
- 多数公司都是开发这类应用产品,包括App,网站等,使用人员和市场需求最多,90%占比
- AI大模型底层原理
- 就是为啥他的更加智能,采用什么数学算法,为啥更加智能,刨根问底
- 需要高学历,需要看很多行业英文论文、高等数学知识、算法原理等,岗位和市场需求少 10%占比
- 如果个人喜欢编写代码,实现具体的功能,且想要快速看到应用效果,则AI大模型应用开发
- 如果你对算数学模型、机器学习等有很好的基础,有精力进行深入研究则可以学习AI大模型底层原理
- 我们这个课程对于侧重AI大模型应用开发,如果你是0基础,之前是前端/后端/测试/大数据等背景则推荐
- 建议优先学习AI大模型应用开发,然后在进一步学习LLM算法方面知识提升
- AI大模型应用开发
-
问题:后端业务+LLM大模型课程对电脑配置有什么要求,常规几千块的电脑能学不?
- 常规电脑即可学习,虽然后端项目涉及多个中间件,LLM大模型训练和私有化部署等需要用到大量硬件资源
- 课程会教采用云服务器和三方算力平台解决这类问题,几十块就可以搞定,所以不用担心。
- 课程会讲多个LLM大模型,封装成底层,容易切换不同的大模型,包括在线和离线私有化部署的大模型
- LLM大模型参数有几亿和几十亿、几百亿等参数规模,这个是需要比较大的算力资源
- 学习的时候可以使用少点参数进行练习,生产的时候可以根据公司需要选择不同级别的参数规模,结合硬件
- 这些都需要掌握,不同的级别的项目才好根据情况进行选择
技术栈要求和内容安排
-
大课技术栈概览
- 基础工具环境:AI大模型编码插件+JDK21+IDEA旗舰版+VSCode+Python3.1X+Linux服务器
- 后端高并发技术:新版SpringBoot3.X+MybatisPlus+Lombok+Hutool+Mysql8.X+多个开源工具包
- 中间件+存储技术:Redis7.X+Kafak3.X-Kraft架构|RabbitMQ+分布式文件存储MinIO或OSS存储引擎
- 前后端分离架构下的 Vue3+ AntDesign+ Nginx网关+多个前端开源组件 (提供完整代码)
- 超多AI大模型+模型库应用:新版GPT/ChatGLM/通义千问等+Huggingface/ModelScope等
- AI大模型技术:FastAPI框架+全新LangChain框架+向量数据库Milvus+多个大模型高频类库等
- LLM框架组件:Model+Prompt+Agent+Chains+Memory+Indexes+RAG+ReAct等
- DevOps上线部署:Jenkins CICD + 阿里云Git仓库+ 阿里云ECS 服务器+ Docker容器编排调度
- ....更多精彩
-
内容安排说明
- 前置必备技术栈:SpringBoot + Mysql +Redis + Kafka|RabbitMQ + Docker +Linux
- 其他新技术栈:Python + LangChain + FastAPI + Milvus +MinIO 等大课里面会讲
需求文档和架构图
为什么技术Leader需要掌握产品需求文档
-
核心:有些不懂技术的产品经理没法编写特定领域的项目需求文档
-
技术知识缺乏:
- 特定领域的项目可能需要特定的技术知识。
- 如果产品经理缺乏相关技术背景,难以理解技术实现的复杂性和可行性,从而难以准确描述技术需求。
-
沟通障碍
- 产品经理需要与技术团队紧密合作,以确保需求的可实现性。
- 如果产品经理不懂技术,他们可能难以与技术团队有效沟通,导致需求文档中的技术细节不准确或不完整。
-
风险评估不足:
- 不懂技术的产品经理可能无法准确评估技术实现的风险,这可能导致项目在实施过程中遇到预料之外的问题。
-
需求优先级判断失误:
- 技术背景可以帮助产品经理判断哪些需求对项目成功最为关键。
- 缺乏技术背景的产品经理可能难以做出正确的优先级排序。
-
一份合格的产品需求文档(多数内容有即可,不同团队要求大体类似)
## 1. 标题页
- **产品名称**:[产品名称]
- **版本/修订号**:[版本号]
- **编制日期**:[编制日期]
- **编制人**:[编制人姓名]
- **审核人**:[审核人姓名]
## 2. 目录
- 根据文档内容创建目录,方便快速跳转到各个部分。
## 3. 引言
### 3.1 目的
- 简要说明编写此文档的目的。
### 3.2 背景
- 描述产品的背景信息,包括市场机会、业务需求等。
### 3.3 定义
- 对文档中使用的专业术语或缩写词进行定义。
## 4. 产品概述
### 4.1 产品愿景
- 描述产品的长远目标和愿景。
### 4.2 产品目标
- 明确产品的短期和长期目标。
### 4.3 用户和市场
- 描述目标用户群体和市场定位。
## 5. 功能需求
### 5.1 功能列表
- 列出产品需要实现的所有功能。
### 5.2 功能描述
- 对每个功能进行详细描述,包括用户故事或用例。
## 6. 非功能需求
### 6.1 性能要求
- 描述产品的性能标准,如响应时间、并发用户数等。
### 6.2 安全要求
- 列出产品必须满足的安全标准。
### 6.3 可用性要求
- 描述产品的易用性和可访问性要求。
### 6.4 法律和标准
- 指出产品需要遵守的法律、法规和行业标准。
## 7. 技术和开发约束
- 列出技术栈、开发平台、第三方服务等技术约束。
## 8. 项目计划
- 提供产品开发的时间线和里程碑。
## 9. 预算和资源
- 概述项目的预算和所需资源。
## 10. 风险评估
- 识别项目可能面临的风险,并提出相应的缓解措施。
## 11. 附件
- 包括市场调研报告、竞品分析、用户访谈记录等支持文档。
AI智能化云盘需求文档说明
架构图的作用和绘制技巧
- 什么是架构图
- 架构图 = 架构 + 图
- 用图的形式把系统架构展示出来,配上简单的文案
- 一图胜千言,解决沟通障碍,给不同的【业务方】看懂
- 业务方很多,不同人看到角度不一样,你让【产品经理】看 【物理部署视图】他看得懂?
-
架构图是给人看的,这些人我们习惯称为【业务方、客户】,有哪些人?
- 人员
- 上级:你的公司Leader(晋升汇报)、老板、外部投资人
- 团队内:产品、运营、测试、技术、运维同学
- 外部:最终系统使用的用户
- 好比阿里这边评定绩效,有一项就是业务方评分
- 你做的外部用户的活动系统,测试同学会进行测试,太多bug肯定就不行
- 你做的给运营同学使用的系统,不能提升她运营的效率,业务方是否满意?
- 人员
-
为什么要搞出这么多个架构图?用一个图不行吗?
-
一开始确实是一个图表示系统架构设计
-
但是业务方很多,不同人看到角度不一样,你让软件用户看物理部署视图?他看得懂?
- 要明确沟通交流面向的客户
- 开发人员、运维人员、项目经理、软件最终用户、客户
-
避免在一张图中展示所有细节,根据受众的需要简化信息,突出关键组件和关系。
-
不同架构视图承载不同的架构设计决策,支持不同的目标和用途
-
架构图也不能太多(过度文档化)维护更新起来成本大
-
- 不同架构图应该使用哪种方法来画?
- 可以用的表示法和工具很多,没有太多的限制,把握对应的视图关注点才是关键
- Xmind、EdrawMax、PPT、PowerDesigner
- OmniGraffle、Visio、Process On
- 开始阶段不要陷入过度设计中,没那么多需求不一定要那么多图(你是否有那么多客户)
- 可以用的表示法和工具很多,没有太多的限制,把握对应的视图关注点才是关键
-
常见架构图作用对比
-
产品/应用/产品业务架构
-
表达业务是如何开展的,服务于业务目标,通过描绘业务上下层关系,简单的业务视图降低业务系统的复杂
-
是对整个系统实现的总体架构 , 应用架构和系统架构很大类似
-
一方面承接业务架构的落地,一方面影响技术选型
-
注意:一般应用架构图【不加入太多技术框架和实现】
-
下面这个是什么架构图(产品架构图-方便技术和产品沟通,图片阿里云官方网站VOD视频点播)
-
- 技术架构
- 应用架构本身只关心需要哪些应用系统,不关心在整个项目中你需要使用哪些技术
- 技术架构则是实现应用架构的承接方,识别技术需求,进行技术选型,描述技术之间的关系
- 解决的问题包括
- 技术层面的分层、开发语言、框架的选择
- 通信技术、存储技术的选择、非功能性需求的技术选择等
-
教你画架构图
-
在画架构图之前,想清楚3个问题,架构图想表达什么?有什么用?给谁看?
- 表达是业务系统之间的关系,梳理业务结构
- 将复杂的业务逻辑简单化,降低理解难度,更方便业务方理解
- 给业务方查看,业务相关干系人
-
业务架构图
- 表达业务是如何开展的,服务于业务目标,通过描绘业务上下层关系,简单的业务视图降低业务系统的复杂度,提高客户理解度
- 图中【尽量不出现技术】的字眼,不同架构图的读者是不同的,确保能看懂。
- 架构图中模块的划分粒度,一定要合适,既不能太宽泛,也不能太细粒度
- 无技术背景人员可参与实现的讨论,向技术人员描述解决方案核心要做什么,必须实现的关键是什么
- 明白一个点
- 先有业务,再有系统,微服务/系统/中心 是类似概念
- 系统是来实现业务的,比如电商业务里面A系统、B系统
-
业务架构类型
-
画图三步走(不同架构图通用法则)
- 分层
- 业务按照层级进行划分,各个层级属于独立的版块
- 下层为上层提供服务能力支撑,比如:laaS / PaaS / SaaS
- 分模块
- 同层级中进行小归类;属于平行关系,可以独立存在
- 理清架构图类型、业务要全面、专业术语一致、图形清晰美观、颜色类型划分合理
- 不同颜色可以表示当下要做的,未来要做的
- 分功能
- 独立功能划分出来,即业务入口
- 业务方重点关注的功能点,可以认为是微服务划分
- 分层
-
如何判断架构图的好和坏?
- 业务抽象设计的合理性,是否满足高内聚、低耦合的要求,不能太宽泛,也不能太细粒度
- 层级划分目标系统边界,自下而上 或 由上而下,一般包括 基础设施、数据层、应用层、用户层四个层次
- 使用清晰的布局,确保组件之间的连接线不交叉,易于跟踪。
- 使用颜色和样式来区分不同类型的组件,但不要过度使用,以免分散注意力。
- 纵向分层 上层依赖于下层越底层,越是基础服务;横向并列关系,级别相同
- 理清架构图类型、业务要全面、专业术语一致、图形清晰美观、颜色类型划分合理
- 最重要是:你的业务方能 满意+看懂!!!
AI智能化云盘应用架构图讲解
-
什么是应用架构图
-
是对整个系统实现的总体架构 , 应用架构和系统架构很大类似
-
一方面承接业务架构的落地,一方面影响技术选型
- 注意:一般应用架构图【不加入太多技术框架和实现】
-
作用
- 根据业务场景 对系统进分层,指出开发的原则、系统各个层次的应用服务
-
业务方
- 研发人员,各层级架构师,各层级技术管理者
-
分类
- 多系统应用架构,用来分层次说明不同系统间的业务逻辑关系、系统边界等,比如 分布式、微服务
- 单系统应用架构,用来分层次说明系统的组成模块和功能点之间的业务逻辑关系,比如单体应用
-
常规分层
- 表示-展现层:负责用户体验
- 业务-服务层:负责业务逻辑
- 数据-访问层:负责数据库存取
-
-
画图三步走
- 分层
- 业务按照层级进行划分,各个层级属于独立的版块
- 下层为上层提供服务能力支撑,比如:laaS / PaaS / SaaS
- 分模块
- 同层级中进行小归类;属于平行关系,可以独立存在
- 理清架构图类型、业务要全面、专业术语一致、图形清晰美观、颜色类型划分合理
- 不同颜色可以表示当下要做的,未来要做的
- 分功能
- 独立功能划分出来,即业务入口
- 业务方重点关注的功能点,可以认为是微服务划分
- 分层
-
新一代AI智能化云盘应用架构图(找bug)
AI智能化云盘技术架构图和作业提交
-
什么是技术架构
-
应用架构本身只关心需要哪些应用系统,不关心在整个项目中你需要使用哪些技术
-
技术架构则是实现应用架构的承接方,识别技术需求,进行技术选型,描述技术之间的关系
-
解决的问题包括
- 技术层面的分层、开发语言、框架的选择
- 通信技术、存储技术的选择、非功能性需求的技术选择等
-
案例
-
-
新一代AI智能化云盘技术选型(下面只是部分技术栈)
- 基础工具环境:AI大模型编码插件+JDK21+IDEA旗舰版+VSCode+Python3.1X+Linux服务器
- 后端高并发技术:新版SpringBoot3.X+MybatisPlus+Lombok+Hutool+Mysql8.X+多个开源工具包
- 中间件+存储技术:Redis7.X+Kafak3.X-Kraft架构+分布式文件存储MinIO或OSS存储引擎
- 前后端分离架构下的 Vue3+ AntDesign+ Nginx网关+多个前端开源组件 (提供完整代码)
- 超多AI大模型+模型库应用:新版GPT/ChatGLM/通义千问等+Huggingface/ModelScope等
- AI大模型技术:FastAPI框架+全新LangChain框架+向量数据库Milvus+多个大模型高频类库等
- LLM框架组件:Model+Prompt+Agent+Chains+Memory+Indexes+RAG+ReAct等
- DevOps上线部署:Jenkins CICD + 阿里云Git仓库+ 阿里云ECS 服务器+ Docker容器编排调度
开发环境搭建
AI编码插件
AI会淘汰程序员?
- AI技术的发展一定程度上改变我们程序员的工作方式,例如自动化一些重复性任务,辅助程序员进行代码审查和优化等
- 也可以编写包括中等程度的CURD、算法等;但AI很难完全替代程序员,可以很大程度辅助我们工程师
- 程序员在创造力、人际沟通、适应新技术、解决复杂问题以及法律责任等方面具有不可替代的优势,AI背锅?
- 如果程序员不懂技术,你能判断AI写的代码上生产环境?出问题你可以排查?
AI编码插件对比
-
CodeGeeX(清华大学+智谱AI)
- 地址:https://codegeex.cn/
- 优点:
- 多语言代码生成模型,支持代码生成与补全、自动添加注释、代码翻译以及智能问答等功能
- 支持多种主流编程语言,并适配多种主流IDE
- 对于个人开发者完全免费,国内开发,无需额外连接VPN
- 缺点
- 对于复杂的场景,AI工具可能提供错误的答案
-
通义灵码(阿里)
- 地址:https://tongyi.aliyun.com/lingma
- 优点:
- 基于通义大模型,提供代码智能生成、研发智能问答能力
- 支持行级/函数级实时续写,自然语言生成代码
- 生成单元测试,支持多种测试框架。
- 支持多种主流编程语言
- 缺点
- 单元测试生成功能表现一般
- 高级功能需要付费
-
GitHub Copilot
- 地址:https://github.com/features/copilot/
- 优点:
- 根据提示自动生成代码,提高开发效率
- 学习项目中的代码风格,获取足够多的上下文,并根据其生成代码
- 支持多种编程语言,适用范围广
- 缺点:
- 可能存在隐私问题
- 功能收费,对于个人开发者成本较高
-
其他比较牛的(都需要科学上网):
- Cursor、Claude
- 能够完成复杂的任务,并且可以与其他系统集成,支持多种应用场景,包括独立开发程序
- 一个是目前适配AI最好的代码编辑器,一个是目前AI编程能力最强的大模型。
SpringBoot3.X本地开发环境创建
技术版本
- Maven-3.9以上:
mvn -version
- JDK-21版本(LTS版本 主流应该是26到28年)
- 新版IDEA-旗舰版
- 框架版本-SpringBoot3.X
项目创建 ycloud-aipan
- 快速创建地址:https://start.spring.io/
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
</parent>
依赖初始化
- 项目依赖配置添加
<properties>
<java.version>21</java.version>
<aws-java-sdk-s3.version>1.12.730</aws-java-sdk-s3.version>
<mybatisplus.version>3.5.6</mybatisplus.version>
<hutool-all.version>5.8.27</hutool-all.version>
<common-io.version>2.8.0</common-io.version>
<fastjson.version>2.0.42</fastjson.version>
<mysql.version>8.0.27</mysql.version>
</properties>
- 工程依赖配置 最佳建议:把这章这集的代码导入到你们IDEA里面,进行构建
<dependencies>
<!-- Spring Boot Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 切面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 数据库连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- AWS S3 SDK -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-java-sdk-s3.version}</version>
</dependency>
<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>
<!-- MyBatis-Plus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!-- 代码自动生成依赖 begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- 代码自动生成依赖 end-->
<!-- Hutool依赖 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<!-- Fastjson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- knife4j 依赖,接口文档工具 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
Linux操作系统EOL解决方案
- 操作系统停止维护EOL(End of Life)
- 大家也知道很多生产环境操作系统都是使用CentOS,尤其是互联网公司
- 但是CentOS官方在24年尾的时候停止了支持,这个就涉及到切换系统
- 建议
- Linux大体是类似的,迁移需要周期,常规25到28年还会是多数公司的首选CentOS
- 所以大家还是需要掌握这个主流的系统;如果新项目则可以选择其他操作系统
- 常见的 CentOS 替代方案,包括 AlmaLinux、Rocky Linux、Oracle Linux、Ubuntu 和 Debian
- Rocky Linux9.X以上 ,推荐2核4G或4核8G
Docker镜像加速+软件安装
- 软件安装
-
Docker-ce社区版本
-
Mysql8.X
- 可视化工具自己选择
-
Redis7.X
-
安装脚本
————————Docker-ce社区版本————————
#运行以下命令,下载docker-ce的yum源。
sudo wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#运行以下命令,安装Docker。
sudo yum -y install docker-ce
#执行以下命令,检查Docker是否安装成功。
sudo docker -v
#执行以下命令,启动Docker服务,并设置开机自启动。
sudo systemctl start docker
sudo systemctl enable docker
#执行以下命令,查看Docker是否启动。
sudo systemctl status docker
#配置Docker镜像加速
([ -f /etc/docker/daemon.json ] || mkdir -p /etc/docker) && echo '{ "registry-mirrors" : [ "https://docker.m.daocloud.io", "https://noohub.ru", "https://huecker.io", "https://dockerhub.timeweb.cloud" ] }' > /etc/docker/daemon.json && sudo systemctl restart docker && sleep 1 && docker info | grep -A 4 "Registry Mirrors"
# ————————Mysql8.X安装————————
#创建目录
mkdir -p /home/data/mysql/
#创建配置文件
touch /home/data/mysql/my.cnf
#部署
docker run \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=xdclass.net168 \
-v /home/data/mysql/conf:/etc/mysql/conf.d \
-v /home/data/mysql/data:/var/lib/mysql:rw \
-v /home/data/mysql/my.cnf:/etc/mysql/my.cnf \
--name xdclass_mysql \
--restart=always \
-d mysql:8.0
# ————————Redis7.X————————
docker run -itd --name xdclass-redis -p 6379:6379 -v /mydata/redis/data:/data redis:7.0.8 --requirepass abc123456
纳入阿里云Git版本控制
基于git协议的代码仓库
- github 全球最大同性交友社区
- gitee 开源中国
- gitlab 开源的git仓库平台,阿里等大厂就是基于这个搭建
- codeup 阿里云上的免费git仓库
- 配置ssh
- 纳入管理
项目规范说明和工具类封装
响应工具、通用工具、Json工具、对象拷贝工具、枚举状态码、全局异常处理
- 响应工具
/**
* 响应类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonData {
/**
* 状态码 0 表示成功
*/
private Integer code;
/**
* 数据
*/
private Object data;
/**
* 描述
*/
private String msg;
/**
* 获取远程调用数据
*
* @param typeReference 数据类型的引用
* @param <T> 泛型类型
* @return 返回解析后的对象
*/
public <T> T getData(Class<T> typeReference) {
return JSON.parseObject(JSON.toJSONString(data), typeReference);
}
/**
* 成功,不传入数据
*
* @return 返回一个状态码为0的JsonData对象
*/
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}
/**
* 成功,传入数据
*
* @param data 成功时返回的数据
* @return 返回一个JsonData对象,其中包含状态码0和传入的数据
*/
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}
/**
* 失败,传入描述信息
*
* @param msg 失败时的描述信息
* @return 返回一个JsonData对象,其中包含状态码-1和传入的描述信息
*/
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}
/**
* 自定义状态码和错误信息
*
* @param code 自定义的状态码
* @param msg 自定义的错误信息
* @return 返回一个JsonData对象,其中包含传入的状态码和错误信息
*/
public static JsonData buildCodeAndMsg(int code, String msg) {
return new JsonData(code, null, msg);
}
/**
* 自定义状态码和错误信息
*
* @param codeEnum 自定义的状态码枚举
* @return 返回一个JsonData对象,其中包含传入的状态码枚举对应的状态码和错误信息
*/
public static JsonData buildResult(BizCodeEnum codeEnum) {
return JsonData.buildCodeAndMsg(codeEnum.getCode(), codeEnum.getMessage());
}
/**
* 判断当前JsonData对象是否表示成功
*
* @return 如果状态码为0,则返回true,表示成功;否则返回false,表示失败
*/
public boolean isSuccess() {
return code == 0;
}
}
- 通用工具
package org.ycloud.aipan.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.PrintWriter;
@Slf4j
public class CommonUtil {
/**
* 响应json数据给前端
*
* @param response HttpServletResponse对象,用于向客户端发送响应
* @param obj 需要转换为json格式的对象
*/
public static void sendJsonMessage(HttpServletResponse response, Object obj) {
// 设置响应内容类型为json,并指定字符编码为utf-8
response.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = response.getWriter()) {
// 将对象转换为json字符串并写入响应输出流
writer.print(JsonUtil.obj2Json(obj));
// 刷新缓冲区,确保数据被发送到客户端
response.flushBuffer();
} catch (IOException e) {
// 捕获并记录异常信息
log.warn("响应json数据给前端异常:{}", e.getMessage());
}
}
/**
* 根据文件名称获取文件后缀
*
* @param fileName 文件名
* @return 文件后缀名
*/
public static String getFileSuffix(String fileName) {
// 从文件名中提取后缀名
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
/**
* 根据文件后缀,生成文件存储路径:年/月/日/uuid.suffix 格式
*
* @param fileName 文件名
* @return 生成的文件存储路径
*/
public static String getFilePath(String fileName) {
// 获取文件后缀名
String suffix = getFileSuffix(fileName);
// 生成文件在存储桶中的唯一键
return StrUtil.format("{}/{}/{}/{}.{}", DateUtil.thisYear(), DateUtil.thisMonth() + 1, DateUtil.thisDayOfMonth(), IdUtil.randomUUID(), suffix);
}
}
- Json工具
@Slf4j
public class JsonUtil {
// 创建一个ObjectMapper对象,用于处理JSON数据的序列化和反序列化
private static final ObjectMapper MAPPER = new ObjectMapper();
// 静态代码块,用于初始化ObjectMapper对象的配置
static {
//设置可用单引号
MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//序列化的时候序列对象的所有属性
MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS);
//反序列化的时候如果多了其他属性,不抛出异常
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//下划线和驼峰互转
//mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
//如果是空对象的时候,不抛异常
MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
/**
* 获取ObjectMapper对象
*
* @return ObjectMapper对象
*/
public static ObjectMapper get() {
return MAPPER;
}
/**
* 将对象转换为JSON字符串
*
* @param obj 要转换的对象
* @return JSON字符串
*/
public static String obj2Json(Object obj) {
String jsonStr = null;
try {
jsonStr = MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("json格式化异常", e);
}
return jsonStr;
}
/**
* 将JSON字符串转换为对象
*
* @param jsonStr 要转换的JSON字符串
* @param beanType 目标对象的类型
* @return 转换后的对象
*/
public static <T> T json2Obj(String jsonStr, Class<T> beanType) {
T obj = null;
try {
obj = MAPPER.readValue(jsonStr, beanType);
} catch (Exception e) {
log.error("json格式化异常", e);
}
return obj;
}
/**
* 将JSON数据转换为对象列表
*
* @param jsonData 要转换的JSON数据
* @param beanType 目标对象的类型
* @return 转换后的对象列表
*/
public static <T> List<T> json2List(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
// 使用ObjectMapper将JSON数据转换为对象列表
return MAPPER.readValue(jsonData, javaType);
} catch (Exception e) {
log.error("json格式化异常", e);
}
// 返回空列表
return new ArrayList<>(0);
}
}
- 对象拷贝工具
/**
* SpringBeanUtil 工具类,提供了对象属性复制的功能。
*/
public class SpringBeanUtil {
/**
* 复制属性
*
* @param <T> 目标对象类型
* @param source 源对象
* @param target 目标对象类型
* @return 复制后的目标对象
*/
public static <T> T copyProperties(Object source, Class<T> target) {
try {
T t = target.getConstructor().newInstance();
BeanUtils.copyProperties(source, t);
return t;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 复制一份具有相同属性的列表
*
* @param sourceList 源列表
* @param target 目标对象的类型
* @param <T> 目标对象的类型
* @return 复制后的目标列表
*/
public static <T> List<T> copyProperties(List<?> sourceList, Class<T> target) {
ArrayList<T> targetList = new ArrayList<>();
sourceList.forEach(source -> {
T t = copyProperties(source, target);
targetList.add(t);
});
return targetList;
}
/**
* 复制属性
*
* @param source 源对象
* @param target 目标对象
*/
public static void copyProperties(Object source, Object target){
BeanUtils.copyProperties(source,target);
}
}
- 枚举状态码
@Getter
@AllArgsConstructor
public enum BizCodeEnum {
/**
* 账号
*/
ACCOUNT_REPEAT(250001, "账号已经存在"),
ACCOUNT_UNREGISTER(250002, "账号不存在"),
ACCOUNT_PWD_ERROR(250003, "账号或者密码错误"),
ACCOUNT_UNLOGIN(250004, "账号未登录"),
/**
* 文件操作相关
*/
FILE_NOT_EXISTS(220404, "文件不存在"),
FILE_RENAME_REPEAT(220405, "文件名重复"),
FILE_DEL_BATCH_ILLEGAL(220406, "文件删除参数错误"),
FILE_TYPE_ERROR(220407, "文件类型错误"),
FILE_CHUNK_TASK_NOT_EXISTS(230408, "分片任务不存在"),
FILE_CHUNK_NOT_ENOUGH(230409, "分片数量不匹配,合并不够"),
FILE_STORAGE_NOT_ENOUGH(240403, "存储空间不足"),
FILE_TARGET_PARENT_ILLEGAL(250403, "目标父级目录不合法"),
SHARE_CANCEL_ILLEGAL(260403, "取消分享失败,参数不合法"),
SHARE_CODE_ILLEGAL(260404, "分享码不合法"),
SHARE_NOT_EXIST(260405, "分享不存在"),
SHARE_CANCEL(260406, "分享已取消"),
SHARE_EXPIRED(260407, "分享已过期"),
SHARE_FILE_ILLEGAL(260408, "分享的文件不合规");
private final int code;
private final String message;
}
- 全局异常处理
/**
* 业务异常类,继承自 RuntimeException
* 用于封装业务逻辑中的异常信息
*/
@Data
public class BizException extends RuntimeException {
/**
* 异常代码
*/
private int code;
/**
* 异常消息
*/
private String msg;
/**
* 异常详细信息
*/
private String detail;
/**
* 构造函数,使用自定义的异常代码和消息
*
* @param code 异常代码
* @param message 异常消息
*/
public BizException(Integer code, String message) {
// 调用父类构造函数,设置异常消息
super(message);
// 设置异常代码
this.code = code;
// 设置异常消息
this.msg = message;
}
/**
* 构造函数,使用 BizCodeEnum 枚举中的异常代码和消息
*
* @param bizCodeEnum 业务代码枚举
*/
public BizException(BizCodeEnum bizCodeEnum) {
// 调用父类构造函数,设置异常消息
super(bizCodeEnum.getMessage());
// 设置异常代码
this.code = bizCodeEnum.getCode();
// 设置异常消息
this.msg = bizCodeEnum.getMessage();
}
/**
* 构造函数,使用 BizCodeEnum 枚举中的异常代码和消息,并包含原始异常的详细信息
*
* @param bizCodeEnum 业务代码枚举
* @param e 原始异常
*/
public BizException(BizCodeEnum bizCodeEnum, Exception e) {
// 调用父类构造函数,设置异常消息
super(bizCodeEnum.getMessage());
// 设置异常代码
this.code = bizCodeEnum.getCode();
// 设置异常消息
this.msg = bizCodeEnum.getMessage();
// 设置异常详细信息
this.detail = e.toString();
}
}
/**
* 自定义异常处理器
* 用于捕获并处理全局异常,返回统一的JSON格式响应
*/
@ControllerAdvice
@Slf4j
public class CustomExceptionHandler {
/**
* 处理所有异常的方法
*
* @param e 捕获到的异常对象
* @return JsonData对象,包含错误码和错误信息
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public JsonData handler(Exception e){
// 判断异常是否为业务异常
if(e instanceof BizException bizException){
// 记录业务异常日志
log.error("[业务异常]",e);
// 返回业务异常的错误码和错误信息
return JsonData.buildCodeAndMsg(bizException.getCode(),bizException.getMsg());
}else {
// 记录系统异常日志
log.error("[系统异常]",e);
// 返回系统异常的错误信息
return JsonData.buildError("系统异常");
}
}
}
存储引擎MinIO和AWS-S3常规API实战
分布式文件存储行业解决方案和技术选型分析
-
背景说明
- 数据爆炸的时代,产生的数据量不断地在攀升,基本都离不开文件存储
- 存储单位从KB、MB、GB、TB、PB到ZB级别的数据,图片、文档、素材、静态化页面、长短视频、安装包等一系列文件
- 业务应用内存储
- 传统的javaweb项目, 文件数量达到一定后占据大量的内存、磁盘和带宽, 无法满足海量请求的业务
- 开发容易-扩容难
- 分布式文件系统(Distributed File System)
- 海量数据对存储提出了新的要求,从而诞生了分布式文件存储
- 文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连
- 扩容容易-开发难
- 数据爆炸的时代,产生的数据量不断地在攀升,基本都离不开文件存储
-
目前业界比较多的解决方案
-
免费:MinIO
- 官网:https://minio.org.cn/
- 是一个高性能、分布式的对象存储系统,完全兼容Amazon S3协议
- 学习成本低,安装运维简单,主流语言的客户端整合都有, 号称最强的对象存储文件服务器
- 提供简单的Web界面和广泛的API支持,方便集成和开发
- 适用于各种规模的部署,从个人小型项目到大型企业级应用
- 提供数据加密功能、访问控制、身份验证功能
- 具有高可用性,可以在分布式环境中运行,并自动处理数据的冗余和复制
- 高度可扩展性,可以根据需求增加更多的存储节点或容量来扩展存储规模
-
花钱:云厂商
- 阿里云OSS、七牛云、亚马逊云
-
面试官:智能化云盘如何选型哪类存储呢,自建或者云厂商如何思考,为啥选择这个?
-
选云厂商理由
-
优点:开发简单,功能强大,容易维护(不同网络下图片质量、水印、加密策略、扩容、加速)
-
缺点:要钱, 个性化处理,未来转移比较复杂,不排除有些厂商会提供一键迁移工具
-
-
-
-
选开源MinIO的理由
-
优点:功能强大、可以根据业务做二次的定制,新一代分布式文件存储系统,容器化结合强大,更重要的是免费
-
缺点:自己需要有专门的团队进行维护、扩容等
-
- 推荐答案
- 参考一:由于平台业务特殊性,多数企业会考虑【私有化】部署,因此如果绑定外部对象存储,则迁移麻烦
- 参考二:公司现有的分布式文件存储基建平台采用的是MinIO,技术团队也比较熟悉,也满足业务需求
Docker容器化部署分布式文件存储MinIO实战
-
部署MinIO实战
mkdir -p /minio/data chmod 777 /minio/data docker run \ -d --restart=always \ --name minio \ --hostname minio-server \ -p 9000:9000 \ -p 9001:9001 \ -v /app/docker/minio/data:/bitnami/minio/data \ -e MINIO_ROOT_USER="minio_root" \ -e MINIO_ROOT_PASSWORD="minio_123456" \ -e MINIO_DEFAULT_BUCKETS="bucket" \ -e "MINIO_SERVER_URL=http://39.108.115.28:9000" \ bitnami/minio:2023.12.7
- 端口说明
- 9000端口是用于内部访问,比如通过SpringBoot接口间接访问MinIO
- 9001端口是用于外部访问,即通过浏览器访问
- 安装实战
- 网络安全组开放端口 9000, 9001
- 访问:ip+9001端口
- 端口说明
-
操作
- 界面登录
- 文件上传下载
-
疑惑点:那么多存储引擎,是否有行业标准接口协议呢?类似JDBC一样,可以对接多个数据库
SpringBoot3.X整合MinIO存储原生方案
-
需求
- SpringBoot3.X整合MinIO文件上传开发实战,采用原生方案
-
编码实战
-
项目增加依赖
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.3.7</version> </dependency>
-
配置文件
# minio配置 minio: endpoint: http://39.108.115.28:9000 access-key: minio_root access-secret: minio_123456 bucket-name: ai-pan
-
配置类
@Data @Component @ConfigurationProperties(prefix = "minio") public class MinioConfig { @Value("endpoint") private String endpoint; @Value("access-key") private String accessKey; @Value("access-secret") private String accessSecret; @Value("bucket-name") private String bucketName; // 预签名url过期时间(ms) private Long PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000L; }
-
测试文件上传
@PostMapping("/upload") public JsonData upload(@RequestParam("file") MultipartFile file) { return JsonData.buildSuccess(minioService.upload(file)); } @Override public String upload(MultipartFile file) { // 获取上传文件名 String filename = CommonUtil.getFilePath(file.getOriginalFilename()); try { InputStream inputStream = file.getInputStream(); minioClient.putObject(PutObjectArgs.builder() .bucket(minioConfig.getBucketName()) .object(filename) .stream(inputStream, file.getSize(), -1) .contentType(file.getContentType()) .build()); } catch (Exception e) { throw new BizException(BizCodeEnum.FILE_REMOTE_UPLOAD_FAILED,e); } return minioConfig.getEndpoint() + "/" + minioConfig.getBucketName() + "/" + filename; }
-
AWS-S3通用存储协议介绍和项目依赖配置
-
什么是Amazon S3
- Amazon S3(Amazon Simple Storage Service)是亚马逊提供的一种对象存储服务,行业领先的可扩展性、数据可用性和性能
- 就类似阿里云OSS、七牛云OSS、MinIO等多个存储服务一样
-
Amazon S3协议
- 是Amazon Simple Storage Service(简称Amazon S3)的接口规范
- 它是一种基于HTTP协议的RESTful API,用于访问Amazon Web Services(AWS)提供的对象存储服务
- S3-API: https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_Operations_Amazon_Simple_Storage_Service.html
- 支持阿里云OSS、七牛云OSS(对象存储服务)
- 在一定程度上与Amazon S3协议兼容,可以使用S3 API来操作OSS多数操作
- 存在一些差异,如ACL权限定义、存储类型处理,需要单独处理
- 支持MinIO
- 兼容Amazon S3协议的对象存储服务器,它提供了与Amazon S3完全相同的S3 API兼容性
- 在公共云、私有云中 ,MinIO支持广泛的S3 API,包括S3 Select和AWS Signature V4,复杂的查询和身份验证 。
- Amazon S3构建的应用程序可以无缝迁移到MinIO,无需任何代码更改
-
如何用?
- 项目添加依赖,配置相关底层存储即可
- 是亚马逊提供的官方软件开发工具包,用在Java程序与Amazon Simple Storage Service(S3)进行交互
- AWS Java SDK for S3提供了创建S3客户端、上传、下载、列出、复制、删除S3存储桶中的对象等功能
<!-- AWS S3 SDK --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> <version>${aws-java-sdk-s3.version}</version> </dependency>
- 代码配置
/** * 配置类,用于定义Bean并配置Amazon S3客户端 */ @Configuration public class AmazonS3Config { // 注入Minio配置类,用于获取访问密钥和Endpoint等信息 @Resource private MinioConfig minioConfig; /** * 创建并配置Amazon S3客户端 * * @return AmazonS3 实例,用于与Amazon S3服务进行交互 */ @Bean(name = "amazonS3Client") public AmazonS3 amazonS3Client() { // 设置连接时的参数 ClientConfiguration config = new ClientConfiguration(); // 设置连接方式为HTTP,可选参数为HTTP和HTTPS config.setProtocol(Protocol.HTTP); // 设置网络访问超时时间 config.setConnectionTimeout(5000); config.setUseExpectContinue(true); // 使用Minio配置中的访问密钥和秘密密钥创建AWS凭证 AWSCredentials credentials = new BasicAWSCredentials(minioConfig.getAccessKey(), minioConfig.getAccessSecret()); // 设置Endpoint AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder .EndpointConfiguration(minioConfig.getEndpoint(), Regions.US_EAST_1.name()); // 使用以上配置创建并返回Amazon S3客户端实例 return AmazonS3ClientBuilder.standard() .withClientConfiguration(config) .withCredentials(new AWSStaticCredentialsProvider(credentials)) .withEndpointConfiguration(endpointConfiguration) .withPathStyleAccessEnabled(true).build(); } }
- 项目添加依赖,配置相关底层存储即可
-
AWS-S3通用存储案例接口测试和封装实战
-
案例代码测试-Bucket相关操作
@SpringBootTest @Slf4j class AmazonS3ClientTests { @Autowired private AmazonS3Client amazonS3Client; /** * 判断bucket是否存在 */ @Test public void testBucketExists() { boolean bucketExist = amazonS3Client.doesBucketExist("ai-pan1"); log.info("bucket是否存在:{}", bucketExist); } /** * 创建bucket */ @Test public void testCreateBucket() { String bucketName = "ai-pan1"; Bucket bucket = amazonS3Client.createBucket(bucketName); log.info("bucket:{}", bucket); } /** * 删除bucket */ @Test public void testDeleteBucket() { String bucketName = "ai-pan1"; amazonS3Client.deleteBucket(bucketName); } /** * 获取全部bucket */ @Test public void testListBuckets() { for (Bucket bucket : amazonS3Client.listBuckets()) { log.info("bucket:{}", bucket.getName()); } } /** * 根据bucket名称获取bucket详情 */ @Test public void testGetBucket() { String bucketName = "ai-pan1"; Optional<Bucket> optionalBucket = amazonS3Client.listBuckets().stream().filter(bucket -> bucketName.equals(bucket.getName())).findFirst(); if (optionalBucket.isPresent()) { log.info("bucket:{}", optionalBucket.get()); } else { log.info("bucket不存在"); } } }
-
案例代码测试-文件相关操作
/** * 上传单个文件,直接写入文本 */ @Test public void testUploadFile() { PutObjectResult putObject = amazonS3Client.putObject("ai-pan", "test1.txt", "hello world11"); log.info("putObject:{}", putObject); } /** * 上传单个文件,直接写入文本 */ @Test public void testUploadFile2() { amazonS3Client.putObject("ai-pan", "test2.txt", new File("/Users/xdclass/Desktop/dpan.sql")); } /** * 上传文件 包括文件夹路径 不带斜杠 都一样 */ @Test public void testUploadFileWithDir1() { amazonS3Client.putObject("ai-pan", "aa/bb/test3.txt", new File("/Users/xdclass/Desktop/dpan.sql")); } /** * 上传文件 包括文件夹路径 带斜杠 都一样 */ @Test public void testUploadFileWithDir2() { amazonS3Client.putObject("ai-pan", "/a/b/test4.txt", new File("/Users/xdclass/Desktop/dpan.sql")); } /** * 上传文件,输入流的方式 带上文件元数据 */ @Test @SneakyThrows public void testUploadFileWithMetadata() { try (FileInputStream fileInputStream = new FileInputStream("/Users/xdclass/Desktop/dpan.sql");) { ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType("text/plain"); amazonS3Client.putObject("ai-pan", "/meta/test5.txt", fileInputStream, objectMetadata); } } /** * 上传文件,输入流的方式 带上文件元数据 */ @Test @SneakyThrows public void testUploadFileWithMetadata2() { try (FileInputStream stream = new FileInputStream("/Users/xdclass/Desktop/dpan.sql");) { byte[] bytes = IOUtils.toByteArray(stream); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType("text/plain"); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); // 上传 amazonS3Client.putObject("ai-pan", "/meta/testIO.txt", byteArrayInputStream, objectMetadata); } } /** * 获取文件 */ @Test @SneakyThrows public void testGetObject() { try (FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/xdclass/Desktop/test5.txt"));) { S3Object s3Object = amazonS3Client.getObject("ai-pan", "/meta/test5.txt"); s3Object.getObjectContent().transferTo(fileOutputStream); } } /** * 删除文件 */ @Test public void testDeleteObject() { amazonS3Client.deleteObject("ai-pan", "/meta/test5.txt"); } /** * 生成文件访问地址 */ @Test public void testGeneratePresignedUrl() { // 预签名url过期时间(ms) long PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000L; // 计算预签名url的过期日期 Date expireDate = DateUtil.offsetMillisecond(new Date(), (int) PRE_SIGN_URL_EXPIRE); // 创建生成预签名url的请求,并设置过期时间和HTTP方法, withMethod是生成的URL访问方式 GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest("ai-pan", "/meta/test5.txt") .withExpiration(expireDate).withMethod(HttpMethod.GET); // 生成预签名url URL preSignedUrl = amazonS3Client.generatePresignedUrl(request); // 输出预签名url System.out.println(preSignedUrl.toString()); }
存储引擎-设计模式案例实战和AI代码一键优化
策略模式设计模式应用-文件存储引擎抽取方案
-
策略模式(Strategy Pattern)
-
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
-
定义共享接口:首先定义共享接口,接口规定了所有支持的算法必须遵循的规则。
-
实现具体策略:为这个接口提供多种不同的实现,每个实现代表一个具体的算法或行为。
-
比如
- 淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的,
- 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
-
优点:
- 算法的封装:策略模式将算法封装在独立的策略类中,使得算法可以独立于使用它们的客户端变化。
- 易于扩展:新增算法时,只需新增一个实现了共享接口的策略类,无需修改原有代码。
- 简化单元测试:可以单独对每个策略进行单元测试。
缺点:
- 客户端需要知道所有策略类:客户端需要了解所有策略类的存在,以便能够选择合适的策略。
- 增加系统复杂性:如果策略类数量过多,可能会增加系统的复杂性。
-
角色
- Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化【不复杂可以去除】
- Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
- ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
-
-
应用场景
- 外出旅游,选择骑自行车、坐汽车、飞机等,每一种旅行方式都是一个策略
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么可以使用策略模式
- 不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式来封装算法
-
为什么要抽象存储引擎接口
- 将文件存储引擎的接口抽象出来,具体实现可以多种,提高系统的灵活性和可维护性。
- 允许我们根据不同的需求和环境(如开发、测试、生产)灵活切换不同的存储解决方案
- 优点
- 灵活性和可扩展性:通过定义一个统一的存储接口,我们可以在不修改客户端代码的情况下引入新的存储解决方案。
- 解耦:将存储逻辑从业务逻辑中解耦,使得存储引擎的变化不影响业务逻辑。
- 易于测试:可以针对接口编写单元测试,而不必依赖具体的存储实现。
- 代码复用:多个项目可以共享相同的存储接口,提高代码复用率。
- 简化维护:统一的接口使得维护和更新存储逻辑变得更加简单。
- 缺点
- 复杂性增加:需要额外定义接口和可能的抽象类,增加了系统的复杂性。
- 性能考虑:接口调用可能引入额外的性能开销,尤其是在接口频繁调用的情况下。
- 实现一致性:确保所有存储策略实现都遵循相同的接口规范,需要严格的代码审查和测试。
- 注意
- 其实aws-java-sdk-s3本身就是封装好了,支持多个存储的,为啥我们又要加一层呢???
- 假想下
- 万一我以后不用aws-java-sdk-s3,那岂不是四处要修改aws-java-sdk-s3的API方法
- 但如果我加了一层,其他地方使用的话,后续修改换别的SDK,我只需要修改我自己封装的那层即可
SpringBoot3.X整合MinIO存储AWS-S3封装
封装存储引擎接口设计【常规版】
-
- 定义一个名为StorageEngine的接口,包含多个方法
- 可以跟进需求,实现
StorageEngine
接口的不同存储策略- LocalFileStorageEngine:使用本地文件系统作为存储。
- S3StorageEngine:使用Amazon S3作为存储。
- DatabaseStorageEngine:使用数据库存储文件元数据和内容。
- MinIOStorageEngine:使用MinIO存储文件内容。
- ...
- 使用策略模式的优势
- 客户端代码与存储实现解耦:客户端代码只需与
StorageEngine
接口交互,不需要关心具体的存储细节。 - 易于切换存储策略:根据不同的业务需求或环境(开发、测试、生产)灵活切换不同的存储策略。
- 支持A/B测试:可以同时运行多个存储策略,进行性能和效果比较。
- 客户端代码与存储实现解耦:客户端代码只需与
-
抽取文件操作相关接口 StoreEngine
public interface StoreEngine { /*=====================Bucket相关===========================*/ /** * 检查指定的存储桶是否存在于当前的存储系统中 * * @param bucketName 存储桶的名称 * @return 如果存储桶存在,则返回true;否则返回false */ boolean bucketExists(String bucketName); /** * 删除指定名称的存储桶 * * @param bucketName 存储桶的名称 * @return 如果存储桶删除成功,则返回true;否则返回false */ boolean removeBucket(String bucketName); /** * 创建一个新的存储桶 * * @param bucketName 新存储桶的名称 */ void createBucket(String bucketName); /** * 获取当前存储系统中的所有存储桶列表 * * @return 包含所有存储桶的列表 */ List<Bucket> getAllBucket(); /*===================文件处理相关=============================*/ /** * 列出指定桶中的所有对象 * * @param bucketName 桶名称 * @return 包含桶中所有对象摘要的列表 */ List<S3ObjectSummary> listObjects(String bucketName); /** * 判断文件是否存在 */ boolean doesObjectExist(String bucketName, String objectKey); /** * 将本地文件上传到指定桶 * * @param bucketName 桶名称 * @param objectKey 上传后对象的名称 * @param localFileName 本地文件的路径 * @return 上传是否成功 */ boolean upload(String bucketName, String objectKey, String localFileName); /** * 将multipart文件上传到指定桶 * * @param bucketName 桶名称 * @param objectKey 上传后对象的名称 * @param file 要上传的multipart文件 * @return 上传是否成功 */ boolean upload(String bucketName, String objectKey, MultipartFile file); /** * 从指定桶中删除对象 * * @param bucketName 桶名称 * @param objectKey 要删除的对象的名称 * @return 删除是否成功 */ boolean delete(String bucketName, String objectKey); /*===================下载相关=============================*/ /** * 获取指定对象的下载URL * * @param bucketName 桶名称 * @param remoteFileName 对象的名称 * @param timeout URL的有效时长 * @param unit URL有效时长的时间单位 * @return 对象的下载URL */ String getDownloadUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit); /** * 将指定对象下载到HTTP响应中 * * @param bucketName 桶名称 * @param objectKey 对象的名称 * @param response HTTP响应对象,用于输出下载的对象 */ void download2Response(String bucketName, String objectKey, HttpServletResponse response); }
- 实现文件存储引擎操作相关接口StoreEngine
@Component @Slf4j public class MinioFileStoreEngine implements StoreEngine { @Resource private AmazonS3Client amazonS3Client; @Override public boolean bucketExists(String bucketName) { return amazonS3Client.doesBucketExistV2(bucketName); } @Override public boolean removeBucket(String bucketName) { try { if (bucketExists(bucketName)) { List<S3ObjectSummary> objects = listObjects(bucketName); if (!objects.isEmpty()) { return false; } amazonS3Client.deleteBucket(bucketName); return !bucketExists(bucketName); } } catch (Exception e) { log.error("errorMsg={}", e.getMessage()); return false; } return false; } @Override public void createBucket(String bucketName) { if (bucketExists(bucketName)) { log.info("Bucket {} already exists.", bucketName); return; } try { Bucket bucket = amazonS3Client.createBucket(bucketName); log.info("Bucket {} created.", bucketName); } catch (Exception e) { log.error("errorMsg={}", e.getMessage()); } } @Override public List<Bucket> getAllBucket() { return amazonS3Client.listBuckets(); } @Override public List<S3ObjectSummary> listObjects(String bucketName) { if (bucketExists(bucketName)) { ListObjectsV2Result result = amazonS3Client.listObjectsV2(bucketName); return result.getObjectSummaries(); } return List.of(); } @Override public boolean doesObjectExist(String bucketName, String objectKey) { return amazonS3Client.doesObjectExist(bucketName, objectKey); } @Override public boolean upload(String bucketName, String objectName, String localFileName) { try { File file = new File(localFileName); amazonS3Client.putObject(bucketName, objectName, file); return true; } catch (Exception e) { log.error("errorMsg={}", e.getMessage()); return false; } } @Override public boolean upload(String bucketName, String objectKey, MultipartFile file) { try { ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(file.getSize()); objectMetadata.setContentType(file.getContentType()); amazonS3Client.putObject(bucketName, objectKey, file.getInputStream(), objectMetadata); return true; } catch (Exception e) { log.error("errorMsg={}", e.getMessage()); return false; } } @Override public boolean delete(String bucketName, String objectKey) { try { amazonS3Client.deleteObject(bucketName, objectKey); return true; } catch (Exception e) { log.error("errorMsg={}", e); return false; } } @Override public String getDownloadUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) { try { Date expiration = new Date(System.currentTimeMillis() + unit.toMillis(timeout)); return amazonS3Client.generatePresignedUrl(bucketName, remoteFileName, expiration).toString(); } catch (Exception e) { log.error("errorMsg {}", e); return null; } } @Override @SneakyThrows public void download2Response(String bucketName, String objectKey, HttpServletResponse response) { S3Object s3Object = amazonS3Client.getObject(bucketName, objectKey); response.setHeader("Content-Disposition", "attachment;filename=" + objectKey.substring(objectKey.lastIndexOf("/") + 1)); response.setContentType("application/force-download"); response.setCharacterEncoding("UTF-8"); IOUtils.copy(s3Object.getObjectContent(), response.getOutputStream()); } }
- 思考:上面代码有什么问题?哪里可以优化的
AI大模型编码效能提升-一键优化代码案例实战
-
上述潜在问题与风险
- 异常处理不一致:
- 多个方法中使用了不同的异常处理逻辑,部分方法直接捕获 Exception,而没有具体处理特定的异常类型。这可能导致隐藏
- 潜在的错误信息。
- 异常日志记录不完整,只记录了 e.getMessage(),而没有记录完整的堆栈信息,不利于调试。
- 资源未关闭:
- 在 download2Response 方法中,s3Object.getObjectContent() 返回的输入流没有关闭,可能会导致资源泄漏。
- 硬编码的响应头:
- download2Response 方法中的响应头设置是硬编码的,缺乏灵活性和可配置性。
- 空返回值:
- 多个方法在异常情况下返回 null 或 false,这可能会导致调用方需要额外的空值检查,增加了复杂性。
- 缺少边界条件检查:
- upload 方法中没有对 localFileName 和 file 进行有效性检查,可能会导致 NullPointerException。
- S3 客户端实例化:
- amazonS3Client 的实例化方式未明确,如果每次调用都创建新实例,可能会导致性能问题。
- 更多....
- 异常处理不一致:
-
AI一键优化代码案例实战
- 注意
- 并非AI优化的代码可以直接使用,关系到Prompt编写、上下文等,务必要结合实际情况和代码审查再使用
- 可以辅助工程师更好的优化代码和发现问题,提高程序的健壮性
- 注意
/optimize 补充接口文档和参数注释,优化代码,统一异常和日志打印,不要使用自定义异常,出错的话log记录即可
AI智能化云盘数据库设计和逆向工程
AI智能化云盘文件存储设计和核心关系
-
思考:文件存储,如果老板让去负责,你会如何设计?假如你没接触过这个领域,看同行竞品
-
百度网盘
-
智能云盘
-
-
云盘存储相关设计说明
-
任何文件都有一个唯一标识,我们统一命名为 identifier,同个文件产生的标识是不变的
-
唯一标识(identifier)可以采用多个方案,也有对应的类库
-
哈希函数(如MD5、SHA-256)
- 优点:
- 唯一性:理论上 不同的文件内容会产生不同的哈希值,保证了标识的唯一性。
- 快速计算:哈希函数可以快速计算出文件的哈希值。
- 安全性:对于SHA-256等哈希算法,抗碰撞性较强,不易被篡改。
- 缺点:
- 安全性问题:对于MD5,由于其抗碰撞性较弱,已经不推荐用于安全敏感的应用。
- 存储和比较:哈希值需要存储和比较,对于非常大的文件系统,这可能会增加存储和计算开销。
- 优点:
-
基于内容的指纹(如SimHash、Locality-Sensitive Hashing)
- 优点:
- 相似性检测:适用于检测相似或重复的文件,可以容忍文件内容的微小变化。
- 减少存储:通过减少哈希值的位数来减少存储需求。
- 缺点:
- 计算复杂性:相比于简单的哈希函数,这些算法可能需要更复杂的计算。
- 误判率:在某些情况下可能会有误判,即不同的文件产生相同的指纹。
- 优点:
-
文件元数据组合
- 优点:
- 简单易实现:通过文件的大小、创建时间、修改时间等元数据生成标识。
- 快速检索:基于元数据的检索通常很快。
- 缺点:
- 非唯一性:不同的文件可能具有相同的元数据,特别是在文件被复制或修改的情况下。
- 不稳定性:文件的元数据(如修改时间)可能会改变,导致标识失效
- 优点:
-
-
方案:采用MD5, 相关标识可以前端和后端保持一定规则,前端上传的时候生成标识传递给后端
-
账号表-文件表和关联关系表设计说明
- 三个关键表说明
- account表:存储用户的基本信息,如用户名、密码、头像等。这是用户身份验证和个性化设置的基础。
- file表:存储文件的元数据,包括文件名、大小、后缀、唯一标识符(MD5)等。主要用于跟踪文件的属性和文件的唯一性
- account_file表:
- 存储用户与文件之间的关系,包括文件的层级结构(文件夹和子文件),以及文件的类型和大小等信息。
- 这个表允许一个用户有多个子文件和文件夹,并且可以表示文件的层级关系
- 如果没有
account_file
表,- 每个用户都重复上传,随着文件数量的增加,没有
account_file
表来组织文件结构,file
表会变得非常大,性能问题 - 无法有效地表示文件和文件夹的层级结构
- 实现文件的移动、复制、删除等操作会变得复杂,因为没有一个明确的结构来跟踪文件的层级和用户关系
- 权限管理也会变得更加复杂,因为没有一个清晰的结构来定义哪些文件可以被哪些用户访问。
- 每个用户都重复上传,随着文件数量的增加,没有
-
智能化云盘设计的3个表理解清楚
-
账号表
- 记录账号相关基础信息
- 关键字段
id 即后续用的 account_id username password role 用户角色 COMMON, ADMIN
-
账号文件关系表
- 记录对应账号下的文件和文件夹、关系等
- 关键字段
id account_id 账号ID is_dir 是否是目录,0不是文件夹,1是文件夹 parent_id 上层文件夹ID,顶层文件夹为0 file_id 文件ID,真正存储的文件 file_name 文件名和实际存储的文件名区分开来,可能重命名
-
文件表
- 记录文件相关的物理存储信息
id 即file_id account_id 哪个账号上传的 file_name 文件名 object_key 文件的key, 格式 日期/md5.拓展名,比如 2024/11/13/921674fd-cdaf-459a-be7b-109469e7050d.png identifier 唯一标识,文件MD5
-
AI智能化云盘数据库设计和字段说明
- 数据库ER图设计(后续还有调整相关表结构)
- 导入建表语句
智能化云盘数据库逆向工程配置生成
- 配置数据库
public class MyBatisPlusGenerator {
public static void main(String[] args) {
String userName = "root";
String password = "xx";
String serverInfo = "127.0.0.1:3306";
String targetModuleNamePath = "/";
String dbName = "ycloud-aipan";
String[] tables = {
"account", "file","account_file","file_chunk", "file_suffix","file_type", "share", "share_file", "storage"
};
// 使用 FastAutoGenerator 快速配置代码生成器
FastAutoGenerator.create("jdbc:mysql://"+serverInfo+"/"+dbName+"?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&tinyInt1isBit=true", userName, password)
.globalConfig(builder -> {
builder.author("everyone") // 设置作者
.commentDate("yyyy-MM-dd")
.enableSpringdoc()
.disableOpenDir() //禁止打开输出目录
.dateType(DateType.ONLY_DATE) //定义生成的实体类中日期类型 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
.outputDir(System.getProperty("user.dir") + targetModuleNamePath + "/src/main/java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("org.ycloud.aipan") // 父包模块名
.entity("model") //Entity 包名 默认值:entity
.mapper("mapper") //Mapper 包名 默认值:mapper
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + targetModuleNamePath + "/src/main/resources/mapper")); // 设置mapperXml生成路,默认存放在mapper的xml下
})
.dataSourceConfig(builder -> {//Mysql下tinyint字段转换
builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
if (JdbcType.TINYINT == metaInfo.getJdbcType()) {
return DbColumnType.BOOLEAN;
}
return typeRegistry.getColumnType(metaInfo);
});
})
.strategyConfig(builder -> {
builder.addInclude(tables) // 设置需要生成的表名 可变参数
.entityBuilder()// Entity策略配置
.enableFileOverride() // 开启生成Entity层文件覆盖
.idType(IdType.ASSIGN_ID)//主键策略 雪花算法自动生成的id
.enableLombok() //开启lombok
.logicDeleteColumnName("del")// 说明逻辑删除是哪个字段
.enableTableFieldAnnotation()// 属性加上注解说明
.formatFileName("%sDO") //格式化生成的文件名称
.controllerBuilder().disable()// Controller策略配置,这里不生成Controller层
.serviceBuilder().disable()// Service策略配置,这里不生成Service层
.mapperBuilder()// Mapper策略配置
.enableFileOverride() // 开启生成Mapper层文件覆盖
.formatMapperFileName("%sMapper")// 格式化Mapper文件名称
.superClass(BaseMapper.class) //继承的父类
.enableBaseResultMap() // 开启生成resultMap,
.enableBaseColumnList() // 开启生成Sql片段
.formatXmlFileName("%sMapper"); // 格式化xml文件名称
})
.templateConfig(builder -> {
// 不生成Controller
builder.disable(TemplateType.CONTROLLER,TemplateType.SERVICE,TemplateType.SERVICE_IMPL);
})
.execute(); // 执行生成
}
}
账号模块开发和Knife4j接口文档配置
Knife4j接口文档工具
-
什么是Knife4j
- 一个为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui。
- 提供了新的Web页面,更符合使用习惯和审美;补充了一些注解,扩展了原生Swagger的功能;
- 是一个更小巧、轻量且功能强悍的接口文档管理工具
-
核心功能
- 文档说明:详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息。
- 在线调试:提供在线接口联调功能,自动解析当前接口参数,返回接口响应内容、headers、响应时间、响应状态码等信息。
- 接口搜索:提供强大的接口搜索功能,支持按接口地址、请求方法、接口描述等关键字进行搜索。
- 接口过滤:提供接口过滤功能,可以根据接口分组、接口标签、接口地址等条件进行过滤。
- 自定义主题:支持自定义主题,定制个性化的API文档界面。
- 丰富的扩展功能:如接口排序、接口分组、接口标签等,进一步丰富了API文档管理的功能。
-
配置实战
- 添加依赖
<!-- knife4j 依赖,接口文档工具 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency>
- 创建配置类
/** * Knife4j配置 ,默认是下面 * <p> * knife4j 访问地址:http://localhost:8080/doc.html * Swagger2.0访问地址:http://localhost:8080/swagger-ui.html * Swagger3.0访问地址:http://localhost:8080/swagger-ui/index.html */ @Slf4j @Configuration public class Knife4jConfig { @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("AI智能云盘系统 API") .version("1.0-SNAPSHOT") .description("AI智能云盘系统") .termsOfService("https://www.xxx.net") .license(new License().name("Apache 2.0").url("https://www.xxx.net")) // 添加作者信息 .contact(new Contact() .name("anonymity") // 替换为作者的名字 .email("anonymity@qq.com") // 替换为作者的电子邮件 .url("https://www.xxx.net") // 替换为作者的网站或个人资料链接 ) ); } }
- 配置Spring Boot控制台打印
@Slf4j @SpringBootApplication public class CloudApplication { public static void main(String[] args) throws Exception { ConfigurableApplicationContext application = SpringApplication.run(CloudApplication.class, args); Environment env = application.getEnvironment(); log.info("\n----------------------------------------------------------\n\t" + "Application '{}' is running! Access URLs:\n\t" + "Local: \t\thttp://localhost:{}\n\t" + "External: \thttp://{}:{}\n\t" + "API文档: \thttp://{}:{}/doc.html\n" + "----------------------------------------------------------", env.getProperty("spring.application.name"), env.getProperty("server.port"), InetAddress.getLocalHost().getHostAddress(), env.getProperty("server.port"), InetAddress.getLocalHost().getHostAddress(), env.getProperty("server.port")); } }
账号注册相关模块接口开发实战
-
需求
- 开发用户注册相关接口,手机号注册
- 内部使用, 不加验证码,如果需要对外则可以加入验证码逻辑
- 用户板块不做复杂权限或者多重校验处理等
-
逻辑说明
- 根据手机号查询是否重复(或者唯一索引)
- 密码加密处理
- 保存用户注册逻辑
- 其他逻辑(创建默认的存储空间,初始化根目录)
-
编码实战:
编写
AccountController,AccountRegisterReq,AccountService,AccountConfig
...
CREATE TABLE `account` (
`id` bigint NOT NULL COMMENT 'ID',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户头像',
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手机号',
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'COMMON' COMMENT '用户角色 COMMON, ADMIN',
`del` tinyint DEFAULT '0' COMMENT '逻辑删除(1删除 0未删除)',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_phone_uni` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户信息表';
头像上传接口开发和MinIO权限配置
- 需求
- 开发头像上传接口,用户注册时候需要把头像url进行上传
- 存储到minio需要可以公开访问,和文件存储分开bucket
- 逻辑说明
- 文件上传接口
- 返回文件访问路径
- 配置minio的头像存储bucket存储权限为public
网盘存储容量设计和根目录初始化配置
-
需求
-
开发编码实战:创建文件夹
//3.创建默认的存储空间 StorageDO storageDO = new StorageDO(); storageDO.setAccountId(accountDO.getId()); storageDO.setUsedSize(0L); storageDO.setTotalSize(AccountConfig.DEFAULT_STORAGE_SIZE); storageMapper.insert(storageDO); //4.初始化根目录 FolderCreateReq createRootFolderReq = FolderCreateReq.builder() .accountId(accountDO.getId()) .parentId(AccountConfig.ROOT_PARENT_ID) .folderName(AccountConfig.ROOT_FOLDER_NAME) .build(); accountFileService.createFolder(createRootFolderReq);
账号登录相关模块设计和开发实战
-
需求
- 开发用户登录模块
- 配置生成JWT
-
编码实战
//业务逻辑 public AccountDTO login(AccountLoginReq req) { String encryptPassword = DigestUtils.md5DigestAsHex(( AccountConfig.ACCOUNT_SALT+ req.getPassword()).getBytes()); QueryWrapper<AccountDO> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("phone", req.getPhone()).eq("password", encryptPassword); AccountDO accountDO = accountMapper.selectOne(queryWrapper); return SpringBeanUtil.copyProperties(accountDO, AccountDTO.class); } //JWT工具 @Slf4j public class JwtUtil { // JWT的主题 private static final String LOGIN_SUBJECT = "XDCLASS"; /** * token有效期1小时 */ private static final Long SHARE_TOKEN_EXPIRE = 1000L * 60 * 60L; //注意这个密钥长度需要足够长, 推荐:JWT的密钥,从环境变量中获取 private final static String SECRET_KEY = "xdclass.net168xdclass.net168xdclass.net168xdclass.net168"; // 签名算法 private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256; // 使用密钥 private final static SecretKey KEY = Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); // token过期时间,30天 private static final long EXPIRED = 1000 * 60 * 60 * 24 * 7; /** * 生成JWT * @param accountDTO 登录账户信息 * @return 生成的JWT字符串 * @throws NullPointerException 如果传入的accountDTO为空 */ public static String geneLoginJWT(AccountDTO accountDTO) { if (accountDTO == null) { throw new NullPointerException("对象为空"); } // 创建 JWT token String token = Jwts.builder() .subject(LOGIN_SUBJECT) .claim("accountId", accountDTO.getId()) .claim("username", accountDTO.getUsername()) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + EXPIRED)) .signWith(KEY, ALGORITHM) // 直接使用KEY即可 .compact(); // 添加自定义前缀 return addPrefix(token); } /** * 校验JWT * @param token JWT字符串 * @return JWT的Claims部分 * @throws IllegalArgumentException 如果传入的token为空或只包含空白字符 * @throws RuntimeException 如果JWT签名验证失败、JWT已过期或JWT解密失败 */ public static Claims checkLoginJWT(String token) { try { log.debug("开始校验 JWT: {}", token); // 校验 Token 是否为空 if (token == null || token.trim().isEmpty()) { log.error("Token 不能为空"); throw new IllegalArgumentException("Token 不能为空"); } token = token.trim(); // 移除前缀 token = removePrefix(token); log.debug("移除前缀后的 Token: {}", token); // 解析 JWT Claims payload = Jwts.parser() .verifyWith(KEY) //设置签名的密钥, 使用相同的 KEY .build() .parseSignedClaims(token).getPayload(); log.info("JWT 解密成功,Claims: {}", payload); return payload; } catch (IllegalArgumentException e) { log.error("JWT 校验失败: {}", e.getMessage(), e); throw e; } catch (io.jsonwebtoken.security.SignatureException e) { log.error("JWT 签名验证失败: {}", e.getMessage(), e); throw new RuntimeException("JWT 签名验证失败", e); } catch (io.jsonwebtoken.ExpiredJwtException e) { log.error("JWT 已过期: {}", e.getMessage(), e); throw new RuntimeException("JWT 已过期", e); } catch (Exception e) { log.error("JWT 解密失败: {}", e.getMessage(), e); throw new RuntimeException("JWT 解密失败", e); } } /** * 给token添加前缀 * @param token 原始token字符串 * @return 添加前缀后的token字符串 */ private static String addPrefix(String token) { return LOGIN_SUBJECT + token; } /** * 移除token的前缀 * @param token 带前缀的token字符串 * @return 移除前缀后的token字符串 */ private static String removePrefix(String token) { if (token.startsWith(LOGIN_SUBJECT)) { return token.replace(LOGIN_SUBJECT, "").trim(); } return token; } }
拦截器开发和ThreadLocal传递用户信息
-
需求
- 开发登录拦截器 解密JWT
- 传递登录用户信息
- request的attribute传递
- threadLocal传递
- 配置拦截器放行路径开发配置
-
ThreadLocal知识点说明
- 全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题。
- ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象
- 注意:ThreadLocal不能使用原子类型,只能使用Object类型
- 应用场景
- ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景。
- 每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过 ThreadLocal 直接获取到
- 类似于全局变量的概念 比如用户登录令牌解密后的信息传递(用户权限信息、从用户系统获取到的用户名、用户ID)
-
编码实战
- 开发登录拦截器 解密JWT
@Component public class LoginInterceptor implements HandlerInterceptor { public static ThreadLocal<AccountDTO> threadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 处理OPTIONS请求 if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpStatus.NO_CONTENT.value()); return true; } // 从请求头或参数中获取token String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { token = request.getParameter("token"); } // 如果token存在,解析JWT if (StringUtils.isNotBlank(token)) { Claims claims = JwtUtil.checkLoginJWT(token); if (claims == null) { // 如果token无效,返回未登录的错误信息 CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); return false; } // 从JWT中提取用户信息 Long accountId = Long.valueOf( claims.get("accountId")+""); String userName = (String) claims.get("username"); // 创建 AccountDTO 对象 AccountDTO accountDTO = AccountDTO.builder() .id(accountId) .username(userName) .build(); // 将用户信息存入 ThreadLocal threadLocal.set(accountDTO); return true; } // 如果没有token,返回未登录的错误信息 CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理 ThreadLocal 中的用户信息 threadLocal.remove(); } }
- 配置拦截器放行路径开发配置
@Configuration @Slf4j public class InterceptorConfig implements WebMvcConfigurer { @Resource private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) //添加拦截的路径 .addPathPatterns("/api/account/*/**","/api/file/*/**","/api/share/*/**") //排除不拦截 .excludePathPatterns("/api/account/*/register","/api/account/*/login","/api/account/*/upload_avatar", "/api/share/*/check_share_code","/api/share/*/visit","/api/share/*/detail_no_code","/api/share/*/detail_with_code"); } }
首页前后端交互逻辑和账号详情接口开发
-
需求
- 网盘存储首页进入,会触发哪些请求?
-
逻辑说明
- 步骤一
- 进入首页需要先获取用户的根目录文件夹ID
- 通过根目录文件夹ID去获取对应的文件列表
- 步骤二
- 首页需要显示用户的存储空间
- 步骤一
-
编码实战
public AccountDTO queryDetail(Long accountId) { //账号详情 AccountDO accountDO = accountMapper.selectById(accountId); AccountDTO accountDTO = SpringBeanUtil.copyProperties(accountDO, AccountDTO.class); //存储信息 StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", accountId)); StorageDTO storageDTO = SpringBeanUtil.copyProperties(storageDO, StorageDTO.class); accountDTO.setStorageDTO(storageDTO); //根文件相关信息 AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() .eq("account_id", accountId) .eq("parent_id", AccountConfig.ROOT_PARENT_ID)); // bug处理 if (accountFileDO != null) { accountDTO.setRootFileId(accountFileDO.getId()); accountDTO.setRootFileName(accountFileDO.getFileName()); } return accountDTO; }
AI编码-账号注册和登录单元测试生成
-
需求
- 利用AI编写账号注册和登录
- 验证相关接口逻辑
-
单元测试实战
操作:复制controller对应的接口,右键,选择生成测试
技术架构图答案+AI接口文档快速生成
- 技术架构图
AI补充接口文档和注释字段操作
- AI补充API接口文档
补充knife4j的接口文档配置内容,@Tag @Operation等注解,使用v3
- AI补充字段解释说明
补充knife4j接口文档信息,使用@Schema,使用v3,添加参数举例
网盘文件模块基础设计和开发
资源访问安全之web常见越权攻击和防范
-
越权攻击介绍
- 是Web应用程序中一种常见的漏洞,由于其存在范围广、危害 大, 列为Web应用十大安全隐患的第二名
- 指应用在检查授权时存在纰漏,使得攻击者在获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户
- 产生原因:主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信,而遗漏了权限的判定
- 比如网盘里面:分享、转存、查看文件的时候都容易触发
-
水平越权攻击
- 指的是攻击者通过某种手段获取了与自己权限相同的其他账户的访问权限。
- 用户A能够访问用户B的账户信息,尽管他们都是普通用户,但A不应该能够访问B的数据。
- 技术实现方式
- 参数篡改:
- 攻击者通过修改请求中的用户ID参数,尝试访问其他同级别用户的资源。
- 在电商系统中,用户A通过修改订单ID参数,尝试查看或修改用户B的订单信息。
- 会话劫持:
- 攻击者通过某种方式获取了其他用户的会话信息,从而冒充该用户进行操作,这可能导致水平越权问题。
- 利用前端安全漏洞:
- 如果前端安全措施不当,攻击者可能会通过修改前端显示的界面元素,如隐藏的URL或参数,来访问其他用户的数据。
- 参数篡改:
- 水平越权攻击的防范:
- 权限验证:确保每次数据访问都进行严格的权限验证。
- 数据隔离:不同用户的数据应该在数据库层面进行隔离。
- 会话管理:使用安全的会话管理机制,如HTTPS、Token等。
-
垂直越权攻击
- 指的是攻击者通过某种手段获取了更高权限的账户的访问权限。
- 普通用户获取了管理员账户或者更高的权限。
- 技术实现方式
- 权限配置错误:
- 由于系统配置不当,普通用户能够执行管理员级别的操作,例如通过修改请求中的权限参数来提升权限。
- 利用系统漏洞:
- 攻击者利用系统或应用程序的漏洞提升权限,例如通过SQL注入攻击来执行管理员级别的数据库操作。
- 多阶段功能滥用:
- 在多阶段功能实现中,如果后续阶段不再验证用户身份,攻击者可能通过抓包修改参数值,实现越权操作,如修改任意用户密码
- 权限配置错误:
- 垂直越权攻击的防范:
- 最小权限原则:用户和系统组件应该只拥有完成其任务所必需的最小权限。
- 权限审查:定期审查权限设置,确保没有不必要的权限提升。
- 安全编码:遵循安全编码实践,避免常见的安全漏洞,如SQL注入、跨站脚本(XSS)等。
- 安全审计:实施安全审计,监控和记录关键操作,以便在发生安全事件时进行追踪。
-
智能化网盘项目里面的避免越权处理方案
- 相关文件数据处理,加入account_id确认
- 角色权限通过role进行确认操作
文件模块开发之查询文件列表接口开发
-
需求
- 网盘存储首页进入,会触发哪些请求?获取当前用户根目录文件夹
- 根据根目录文件夹查询对应的文件列表
- 进入相关的指定文件夹,查询对应的子文件
-
注意事项
-
查询的时候都需要加入账号相关进行确认
前面代码相对会简单点,逐步代码封装和抽取就会上升难度,
-
-
编码实战
@GetMapping("list")
public JsonData list(@RequestParam(value = "parent_id")Long parentId){
Long accountId = LoginInterceptor.threadLocal.get().getId();
List<AccountFileDTO> list = fileService.listFile(accountId,parentId);
return JsonData.buildSuccess(list);
}
public List<AccountFileDTO> listFile(Long accountId, Long parentId) {
List<AccountFileDO> accountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
.eq("account_id", accountId).eq("parent_id", parentId)
.orderByDesc("is_dir")
.orderByDesc("gmt_create")
);
return SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class);
}
创建文件夹相关接口设计和开发
-
需求
- 开发网盘里面可以创建文件夹
- 业务逻辑方法梳理(哪些方法会其他地方复用)
- 检查父文件ID是否存在(抽)
- 生成账号文件信息
- 检查文件名是否重复(抽)
- 保存相关账号文件夹信息
-
编码实战
@PostMapping("/create_folder") public JsonData createFolder(@RequestBody FolderCreateReq req){ req.setAccountId(LoginInterceptor.threadLocal.get().getId()); fileService.createFolder(req); return JsonData.buildSuccess(); } AccountFileDTO accountFileDTO = AccountFileDTO.builder().accountId(req.getAccountId()) .parentId(req.getParentId()) .fileName(req.getFolderName()) .isDir(FolderFlagEnum.YES.getCode()).build(); return saveAccountFile(accountFileDTO);
-
需求
- 处理用户和文件的映射存储,存储文件和文件夹都可以
-
编码实战
/** * 处理用户和文件的映射存储,存储文件和文件夹都可以 * <p> * 1、检查父文件ID是否存在,避免越权 * 2、检查文件名是否重复 * 3、保存文件信息 * * @return */ private Long saveAccountFile(AccountFileDTO accountFileDTO) { //检查父文件ID是否存在 checkParentFileId(accountFileDTO); //存储文件信息 AccountFileDO accountFileDO = SpringBeanUtil.copyProperties(accountFileDTO, AccountFileDO.class); //检查文件名是否重复 processFileNameDuplicate(accountFileDO); accountFileMapper.insert(accountFileDO); return accountFileDO.getId(); }
网盘文件重命名相关接口
-
需求
- 开发网盘文件重命名接口,包括文件夹和文件一样适用
-
业务逻辑方法梳理
- 文件ID是否存在,避免越权
- 新旧文件名称不能一样
- 也不能用同层文件夹的名称,通过parent_id进行查询
-
编码实战
@Override public void renameFile(FileUpdateReq req) { //文件ID是否存在,避免越权 AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() .eq("id", req.getFileId()) .eq("account_id", req.getAccountId())); if (accountFileDO == null) { log.error("文件ID不存在,请检查:{}", req); throw new BizException(BizCodeEnum.FILE_NOT_EXISTS); } else { //新旧文件名称不能一样 if (Objects.equals(accountFileDO.getFileName(), req.getNewFilename())) { log.error("新旧文件名称不能一样,{}", req); throw new BizException(BizCodeEnum.FILE_RENAME_REPEAT); } else { //同层的文件或者文件夹也不能一样 Long selectCount = accountFileMapper.selectCount(new QueryWrapper<AccountFileDO>() .eq("account_id", req.getAccountId()) .eq("parent_id", accountFileDO.getParentId()) .eq("file_name", req.getNewFilename())); if (selectCount > 0) { log.error("同层的文件或者文件夹也不能一样,{}", req); throw new BizException(BizCodeEnum.FILE_RENAME_REPEAT); } else { accountFileDO.setFileName(req.getNewFilename()); accountFileMapper.updateById(accountFileDO); } } } }
接口测试工具-文件夹创建-查询-重命名接口测试
-
接口测试工具
- Apifox和Postman都是流行的API接口管理工具
- 选择哪个工具取决于具体的使用场景和需求
-
接口工具核心功能
- 支持多种HTTP请求方法(如GET、POST、PUT、DELETE等),允许用户设置请求头、请求体、查询参数等
- 环境变量允许用户存储和管理多个环境(如开发、测试、生产环境)的配置信息,便于在不同环境间切换
-
我们采用ApiFox录入相关接口进行测试
- 配置全局环境变量
- 录入相关接口模块
- bug修复
//bug1 /** * 检查父文件是否存在 * @param accountFileDTO */ private void checkParentFileId(AccountFileDTO accountFileDTO) { if(accountFileDTO.getParentId()!=0){ AccountFileDO accountFileDO = accountFileMapper.selectOne( new QueryWrapper<AccountFileDO>() .eq("id", accountFileDTO.getParentId()) .eq("account_id", accountFileDTO.getAccountId())); if(accountFileDO == null){ throw new BizException(BizCodeEnum.FILE_NOT_EXISTS); } } } //bug2 @AllArgsConstructor @NoArgsConstructor public class AccountFileDTO //bug3 private void processFileNameDuplicate(AccountFileDO accountFileDO) { Long selectCount = accountFileMapper.selectCount(new QueryWrapper<AccountFileDO>() .eq("account_id", accountFileDO.getAccountId()) .eq("parent_id", accountFileDO.getParentId()) .eq("is_dir", accountFileDO.getIsDir()) .eq("file_name", accountFileDO.getFileName())); if(selectCount>0){ //处理重复文件夹 if(Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())){ accountFileDO.setFileName(accountFileDO.getFileName()+"_"+System.currentTimeMillis()); }else { //处理重复文件名,提取文件拓展名 String[] split = accountFileDO.getFileName().split("\\."); accountFileDO.setFileName(split[0]+"_"+System.currentTimeMillis()+"."+split[1]); } } }
Swagger+Apifox
- 使用AI将源码中的Controller接口和Req对象生成knife4j注释 :参考: AI补充接口文档和注释字段操作
- 注册Apifox账号,配置 API 访问令牌
- 在idea中安装Apifox插件,通过插件将对应的接口同步到Apifox。快速上手 - Apifox 帮助文档
查询文件树接口设计和文件操作进阶
【难点】查询文件树接口应用场景和流程设计讲解
- 什么是文件树和应用场景
- 多层级展示文件夹列表和子文件夹
- 用途包括移动、复制、转存文件
- 开发这个接口有多种方式
- 递归和非递归,我们采用非递归,内存里面操作的方式
- 内存里面操作也有多种实现方式,比如分组或者遍历处理
- 后端接口协议分析,倒推代码处理逻辑
{
"code": 0,
"data": [
{
"id": 1871837581885325314,
"parentId": 0,
"label": "全部文件夹",
"children": [
{
"id": 1871838400252755969,
"parentId": 1871837581885325314,
"label": "a2",
"children": [
{
"id": 1872208466167484418,
"parentId": 1871838400252755969,
"label": "b2",
"children": []
},
{
"id": 1872208451487420418,
"parentId": 1871838400252755969,
"label": "b1",
"children": [
{
"id": 1872208603140870145,
"parentId": 1872208451487420418,
"label": "c2(1)",
"children": []
}
},
{
"id": 1872208573759770626,
"parentId": 1872208451487420418,
"label": "c1",
"children": []
}
]
},
{
"id": 1872208480121933825,
"parentId": 1871838400252755969,
"label": "b3",
"children": []
}
]
},
{
"id": 1871838384587030529,
"parentId": 1871837581885325314,
"label": "a1",
"children": []
}
]
}
],
"msg": null,
"success": true
}
-
代码逻辑思路
- 查询用户的全部文件夹列表
- 构建一个Map,key为文件夹ID,value为FolderTreeNodeDTO对象
- 构建文件夹树,遍历文件夹映射,为每个文件夹找到其子文件夹
- 返回根节点(parentId为0的节点)过滤出根文件夹即可
【难点】查询文件树接口编码案例实战
- 编码实战
/**
* 获取文件树接口,非递归方式
* 1、查询当前用户的所有文件夹
* 2、拼装文件夹树
* @param accountId
* @return
*/
@Override
public List<FolderTreeNodeDTO> fileTree(Long accountId) {
// 查询当前用户的所有文件夹
List<AccountFileDO> folderList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
.eq("account_id", accountId)
.eq("is_dir", FolderFlagEnum.YES.getCode()));
// 拼装文件夹树列表
if (CollectionUtils.isEmpty(folderList)) {
return List.of();
}
// 构建一个Map,key为文件夹ID,value为FolderTreeNodeDTO对象
Map<Long, FolderTreeNodeDTO> folderMap = folderList.stream().collect(Collectors.toMap(
AccountFileDO::getId,
file -> FolderTreeNodeDTO.builder()
.id(file.getId())
.label(file.getFileName())
.parentId(file.getParentId())
.children(new ArrayList<>())
.build()
));
// 构建文件夹树,遍历文件夹映射,为每个文件夹找到其子文件夹
for (FolderTreeNodeDTO node : folderMap.values()) {
// 获取当前文件夹的父ID
Long parentId = node.getParentId();
// 如果父ID不为空且父ID在文件夹映射中存在,则将当前文件夹添加到其父文件夹的子文件夹列表中
if (parentId != null && folderMap.containsKey(parentId)) {
// 获取父文件夹
FolderTreeNodeDTO folderTreeNodeDTO = folderMap.get(parentId);
// 获取父文件夹的子文件夹列表
List<FolderTreeNodeDTO> children = folderTreeNodeDTO.getChildren();
// 将当前文件夹添加到子文件夹列表中
children.add(node);
}
}
// 返回根节点(parentId为0的节点)过滤出根文件夹即可,里面包括多个
List<FolderTreeNodeDTO> folderTreeNodeDTOS = folderMap.values().stream()
.filter(node -> Objects.equals(node.getParentId(), 0L))
.collect(Collectors.toList());
return folderTreeNodeDTOS;
}
查询文件树接断点调试和另外一种实现方式
简介: 查询文件树接断点调试和另外一种实现方式
-
需求
- 断点调试查询文件树接口逻辑
- 编写另外一种文件树实现代码(思考哪种方式好)
- 对比不同方式,多数据和少数据的优缺点
-
另一种文件树实现代码
//查询当前用户的所有文件夹 List<AccountFileDO> folderList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() .eq("account_id", accountId) .eq("is_dir", FolderFlagEnum.YES.getCode())); //拼装文件夹树列表 if (CollectionUtils.isEmpty(folderList)) { return List.of(); } List<FolderTreeNodeDTO> folderTreeNodeDTOS = folderList.stream().map(file->{ return FolderTreeNodeDTO.builder() .id(file.getId()) .label(file.getFileName()) .parentId(file.getParentId()) .children(new ArrayList<>()) .build(); }).toList(); //根据父文件夹进行分组 key是当前文件夹ID,value是当前文件夹下的所有子文件夹 Map<Long, List<FolderTreeNodeDTO>> folderTreeNodeVOMap = folderTreeNodeDTOS.stream() .collect(Collectors.groupingBy(FolderTreeNodeDTO::getParentId)); for (FolderTreeNodeDTO node : folderTreeNodeDTOS) { List<FolderTreeNodeDTO> children = folderTreeNodeVOMap.get(node.getId()); //判断列表是否为空 if (!CollectionUtils.isEmpty(children)) { node.getChildren().addAll(children); } } return folderTreeNodeDTOS.stream().filter(node -> Objects.equals(node.getParentId(), 0L)).collect(Collectors.toList());
网盘小文件上传接口设计和开发
-
需求
- 文件上传分三部分接口:小文件上传、大文件上传、文件秒传
- 先开发:小文件上传接口
- 上传到存储引擎
- 保存文件信息
- 保存文件映射关系
-
编码实战
- 上传文件到存储引擎,返回存储的文件路径
private String storeFile(FileUploadReq req) { String objectKey = CommonUtil.getFilePath(req.getFilename()); fileStoreEngine.upload(minioConfig.getBucketName(), objectKey, req.getFile()); return objectKey; }
- 保存文件信息
private FileDO saveFile(FileUploadReq req, String storeFileObjectKey) { FileDO fileDO = new FileDO(); fileDO.setAccountId(req.getAccountId()); fileDO.setFileName(req.getFilename()); fileDO.setFileSize(req.getFile() != null ? req.getFile().getSize() : req.getFileSize()); fileDO.setFileSuffix(CommonUtil.getFileSuffix(req.getFilename())); fileDO.setIdentifier(req.getIdentifier()); fileDO.setObjectKey(storeFileObjectKey); fileMapper.insert(fileDO); return fileDO; }
- 保存文件映射关系
AccountFileDTO accountFileDTO = AccountFileDTO.builder().fileName(req.getFilename()) .accountId(req.getAccountId()) .fileId(fileDO.getId()) .fileSize(fileDO.getFileSize()) .fileSuffix(fileDO.getFileSuffix()) .parentId(req.getParentId()) .isDir(FolderFlagEnum.NO.getCode()) .fileType(FileTypeEnum.fromExtension(fileDO.getFileSuffix()).name()) .build(); saveAccountFile(accountFileDTO);
- 文件枚举
@Getter public enum FileTypeEnum { COMMON("common"), COMPRESS("compress"), EXCEL("excel"), WORD("word"), PDF("pdf"), TXT("txt"), IMG("img"), AUDIO("audio"), VIDEO("video"), PPT("ppt"), CODE("code"), CSV("csv"); private final String type; private static final Map<String, FileTypeEnum> EXTENSION_MAP = new HashMap<>(); static { for (FileTypeEnum fileType : values()) { switch (fileType) { case COMPRESS: EXTENSION_MAP.put("zip", fileType); EXTENSION_MAP.put("rar", fileType); EXTENSION_MAP.put("7z", fileType); break; case EXCEL: EXTENSION_MAP.put("xls", fileType); EXTENSION_MAP.put("xlsx", fileType); break; case WORD: EXTENSION_MAP.put("doc", fileType); EXTENSION_MAP.put("docx", fileType); break; case PDF: EXTENSION_MAP.put("pdf", fileType); break; case TXT: EXTENSION_MAP.put("txt", fileType); break; case IMG: EXTENSION_MAP.put("jpg", fileType); EXTENSION_MAP.put("jpeg", fileType); EXTENSION_MAP.put("png", fileType); EXTENSION_MAP.put("gif", fileType); EXTENSION_MAP.put("bmp", fileType); break; case AUDIO: EXTENSION_MAP.put("mp3", fileType); EXTENSION_MAP.put("wav", fileType); EXTENSION_MAP.put("aac", fileType); break; case VIDEO: EXTENSION_MAP.put("mp4", fileType); EXTENSION_MAP.put("avi", fileType); EXTENSION_MAP.put("mkv", fileType); break; case PPT: EXTENSION_MAP.put("ppt", fileType); EXTENSION_MAP.put("pptx", fileType); break; case CODE: EXTENSION_MAP.put("java", fileType); EXTENSION_MAP.put("c", fileType); EXTENSION_MAP.put("cpp", fileType); EXTENSION_MAP.put("py", fileType); EXTENSION_MAP.put("js", fileType); EXTENSION_MAP.put("html", fileType); EXTENSION_MAP.put("css", fileType); break; case CSV: EXTENSION_MAP.put("csv", fileType); break; default: break; } } } FileTypeEnum(String type) { this.type = type; } public static FileTypeEnum fromExtension(String extension) { if (extension == null || extension.isEmpty() || !isValidExtension(extension)) { return COMMON; } try { return EXTENSION_MAP.getOrDefault(extension.toLowerCase(), COMMON); } catch (NullPointerException e) { // 记录日志 System.err.println("Unexpected null pointer exception: " + e.getMessage()); return COMMON; } } private static boolean isValidExtension(String extension) { // 确保扩展名只包含字母和数字 return extension.matches("[a-zA-Z0-9]+"); } }
网盘小文件上传接口测试验证
-
需求
- ApiFox测试文件上传接口
- 测试创建多个文件夹
- 对应的文件上传多个类型的文件
-
测试实战
文件批量移动接口设计和开发
-
需求
- 批量操作对应的文件列表,移动到对应的目录下面
- 需要考虑什么?如何实现相关功能?
-
业务逻辑设计(哪些方法会复用)
- 检查被转移的文件ID是否合法(复用)
- 检查目标文件夹ID是否合法(复用)
- 目标文件夹ID必须是当前用户的文件夹,不能是文件
- 要操作(移动、复制)的文件列表不能包含是目标文件夹的子文件夹,递归处理
- 批量转移文件到目标文件夹
- 处理重复文件名
- 更新文件或文件夹的parentId为目标文件夹ID
-
编码实战
@Transactional(rollbackFor = Exception.class) public void moveBatch(FileBatchReq req) { //检查被转移的文件ID是否合法 List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId()); //检查目标文件夹ID是否合法,需要包括子文件夹 checkTargetParentIdLegal(req); //批量转移文件到目标文件夹 //处理重复文件名 accountFileDOList.forEach(this::processFileNameDuplicate); // 更新文件或文件夹的parentId为目标文件夹ID UpdateWrapper<AccountFileDO> updateWrapper = new UpdateWrapper<>(); updateWrapper.in("id", req.getFileIds()) .set("parent_id", req.getTargetParentId()); int updatedCount = accountFileMapper.update(null, updateWrapper); if (updatedCount != req.getFileIds().size()) { throw new RuntimeException("部分文件或文件夹移动失败"); } } public List<AccountFileDO> checkFileIdLegal(List<Long> fileIdList, Long accountId) { List<AccountFileDO> accountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() .in("id", fileIdList) .eq("account_id", accountId)); if (accountFileDOList.size() != fileIdList.size()) { log.error("文件ID数量不合法,请检查:accountId={},fileIdList={}", accountId, fileIdList); throw new BizException(BizCodeEnum.FILE_DEL_BATCH_ILLEGAL); } return accountFileDOList; }
-
需求
- 批量整理对应的文件列表,移动到对应的目录下面
- 需要考虑什么?如何实现相关功能?
-
编码实现
- 检查父ID是否合法
private void checkTargetParentIdLegal(FileBatchReq req) { //1、目标文件夹ID 必须是当前用户的文件夹,不能是文件 AccountFileDO targetParentFolder = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() .eq("id", req.getTargetParentId()) .eq("account_id", req.getAccountId()) .eq("is_dir", FolderFlagEnum.YES.getCode())); if (targetParentFolder == null) { log.error("目标文件夹不存在,目标文件夹ID:{}", req.getTargetParentId()); throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_ILLEGAL); } /** * 2、要操作(移动、复制)的文件列表不能包含是目标文件夹的子文件夹 * 思路: * 1、查询批量操作中的文件夹和子文件夹,递归处理 * 2、判断是否在里面 */ //查询待批量操作中的文件夹和子文件夹 List<AccountFileDO> prepareAccountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() .in("id", req.getFileIds()) .eq("account_id", req.getAccountId())); List<AccountFileDO> allAccountFileDOList = new ArrayList<>(); findAllAccountFileDOWithRecur(allAccountFileDOList, prepareAccountFileDOList, false); // 判断allAccountFileDOList是否包含目标夹的id if (allAccountFileDOList.stream().anyMatch(file -> file.getId().equals(req.getTargetParentId()))) { log.error("目标文件夹不能是源文件列表中的文件夹,目标文件夹ID:{},文件列表:{}", req.getTargetParentId(), req.getFileIds()); throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_ILLEGAL); } }
文件批量操作-递归接口设计和开发实战
-
什么是递归
- 允许函数调用自身来解决问题。
- 递归的基本思想是将一个复杂的问题分解成更小的、相似的子问题,直到这些子问题足够简单,可以直接解决
- 优点:包括代码简洁和优雅,比如对于某些问题(如树结构遍历、分治算法等)的处理逻辑更加简单
- 缺点:比如可能导致较大的内存消耗(因为每次函数调用都需要在调用栈上保存信息),在某些情况下可能不如迭代方法高效
- 递归通常包含两个主要部分:
- 基本情况(Base Case):停止的条件,在每个递归调用中,都会检查是否达到了基本情况,如果是则停止递归返回结果。
- 递归步骤(Recursive Step):函数调用自身的过程。在这一步中问题被分解成更小的子问题,递归地解决这些子问题。
-
编码设计和逻辑说明
- 遍历文件列表:对传入的 accountFileDOList 进行遍历。
- 判断是否为文件夹:如果当前项是文件夹,则递归获取其子文件,并继续递归处理。
- 添加到结果列表:根据 onlyFolder 参数决定是否只添加文件夹,或者同时添加文件和文件夹
-
findAllAccountFileDOWithRecur
递归逻辑处理(多个地方会使用,封装方法)@Override public void findAllAccountFileDOWithRecur(List<AccountFileDO> allAccountFileDOList, List<AccountFileDO> accountFileDOList, boolean onlyFolder) { for (AccountFileDO accountFileDO : accountFileDOList) { if (Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) { //文件夹,递归获取子文件ID List<AccountFileDO> childFileList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() .eq("parent_id", accountFileDO.getId())); findAllAccountFileDOWithRecur(allAccountFileDOList, childFileList, onlyFolder); } //如果通过onlyFolder是true只存储文件夹到allAccountFileDOList,否则都存储到allAccountFileDOList if (!onlyFolder || Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) { allAccountFileDOList.add(accountFileDO); } } //return allAccountFileDOList; }
文件批量移动接口测试验证实战
智能化网盘文件进阶实战-批量删除和复制
封装的好处-文件批量删除设计和开发
-
需求
- 开发文件删除接口,需要支持文件夹和文件删除
- 支持批量选择操作删除,包括支持存储空间的处理
-
业务逻辑设计(逐步可以发现,用之前封装好的方法提高效率)
- 步骤一:检查是否满足:1、文件ID数量是否合法,2、文件是否属于当前用户
- 步骤二:判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,然后进行批量删除
- 步骤三:需要更新账号存储空间使用情况
- 步骤四:批量删除账号映射文件,考虑回收站如何设计
封装的好处-文件批量删除设计和开发《下》
-
编码实战
public void delBatch(FileDelReq req) { //步骤一:检查是否满足:1、文件ID数量是否合法 存在和不存在,2、文件是否属于当前用户 List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId()); //步骤二:判断文件是否是文件夹,文件夹的话需要递归获取里面子文件,然后进行批量删除 List<AccountFileDO> storeAccountFileDOList = new ArrayList<>(); findAllAccountFileDOWithRecur(storeAccountFileDOList, accountFileDOList, false); List<Long> allFileIdList = storeAccountFileDOList.stream().map(AccountFileDO::getId).collect(Collectors.toList()); //步骤三:更新存储信息 StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", req.getAccountId())); //List<AccountFileDO> accountFileDOS = accountFileMapper.selectBatchIds(allFileIdList); //删除文件占用空间 long allFileSize = storeAccountFileDOList.stream().filter(file -> file.getIsDir().equals(FolderFlagEnum.NO.getCode())).mapToLong(AccountFileDO::getFileSize).sum(); storageDO.setUsedSize(storageDO.getUsedSize()-allFileSize); storageMapper.updateById(storageDO); //步骤四:批量删除账号映射文件,考虑回收站设计 TODO,逻辑删除 accountFileMapper.deleteBatchIds(allFileIdList); }
网盘文件存储空间检查接口开发实战
-
需求
- 文件上传前,需要检查存储空间是否够,包括复制、转存等
- 文件删除后需要更新存储空间
-
编码实战
/** * 检查存储空间和更新空间 * * @param accountId * @param fileSize * @return */ public boolean checkAndUpdateCapacity(Long accountId, Long fileSize) { StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", accountId)); Long totalStorageSize = storageDO.getTotalSize(); if (storageDO.getUsedSize() + fileSize <= totalStorageSize) { storageDO.setUsedSize(storageDO.getUsedSize() + fileSize); storageMapper.updateById(storageDO); return true; } else { return false; } }
存储空间更新和删除文件夹接口测试实战
- 需求
- 测试文件上传存储空间
- 测试批量删除功能,包括:单文件删除、批量删除(合法ID和不合法ID)
- 存储空间需要及时更新
- 测试实战
网盘文件批量复制功能设计和开
- 需求
- 网盘文件复制功能开发实战
- 需要支持批量复制,存储空间验证等
-
思考
- 文件复制和文件移动有什么共同逻辑?
- 有什么差异点?
-
业务逻辑设计
- 检查被转移的文件ID是否合法
- 检查目标文件夹ID是否合法
- 执行拷贝,递归查找【差异点,ID是全新的】
- 计算存储空间大小,检查是否足够【差异点,空间需要检查】
- 存储相关记录
-
接口开发
@PostMapping("/copy_batch") public JsonData copyBatch(@RequestBody FileBatchReq req){ req.setAccountId(LoginInterceptor.threadLocal.get().getId()); fileService.copyBatch(req); return JsonData.buildSuccess(); }
【重点】网盘文件批量复制业务逻辑递归处理实战
-
业务逻辑设计
- 检查被转移的文件ID是否合法
- 检查目标文件夹ID是否合法
- 执行拷贝,递归查找【差异点,ID是全新的】
- 计算存储空间大小,检查是否足够【差异点,空间需要检查】
- 存储相关记录
-
编码实战
- 业务逻辑层
@Override public void copyBatch(FileBatchReq req) { //检查被转移的文件ID是否合法 List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId()); //检查目标文件夹ID是否合法 checkTargetParentIdLegal(req); //执行拷贝,递归查找 List<AccountFileDO> newAccountFileDOList = findBatchCopyFileWithRecur(accountFileDOList, req.getTargetParentId()); //计算存储空间大小,检查是否足够 if (!checkAndUpdateCapacity(req.getAccountId(), newAccountFileDOList.stream() .filter(file -> file.getIsDir().equals(FolderFlagEnum.NO.getCode())) .mapToLong(AccountFileDO::getFileSize).sum())) { throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); } //存储 accountFileMapper.insertFileBatch(newAccountFileDOList); }
- 递归处理,包括子文件夹【需要建立新的父子文件夹关系】
private void doCopyChildRecord(List<AccountFileDO> newAccountFileDOList, AccountFileDO accountFileDO, Long targetParentId) { //方便找子文件夹 Long oldAccountFileId = accountFileDO.getId(); //创建新记录 accountFileDO.setId(IdUtil.getSnowflakeNextId()); accountFileDO.setParentId(targetParentId); accountFileDO.setGmtCreate(null); accountFileDO.setGmtModified(null); //处理重复文件名 processFileNameDuplicate(accountFileDO); //新记录加入列表 newAccountFileDOList.add(accountFileDO); //判断是否是文件夹,是的话进行递归处理 if (Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) { //继续获取子文件夹列表 List<AccountFileDO> childAccountFileDOList = findChildAccountFile(accountFileDO.getAccountId(), oldAccountFileId); if (CollectionUtils.isEmpty(childAccountFileDOList)) { return; } //递归处理 childAccountFileDOList.forEach(childAccountFileDO -> doCopyChildRecord(newAccountFileDOList, childAccountFileDO, accountFileDO.getId())); } }
网盘文件批量复制功能接口测试实战
-
需求
- 根目录创建文件夹C1,C2,C3
- C1 文件夹里面创建多个子文件和上传文件
- 把C3,C1 复制到C2文件夹里面
-
测试验证
- 文件是否复制成功,目录层级关系是否正常
- 存储空间是否正常更新
AI智能化网盘-文件秒传设计和多应用场景讲解
老王分享冰冰的视频给老帆-如何实现文件秒传
- 场景
- 老王在小滴AI网盘上存储了很多视频,时不时分享给他人
- 老王精选下载了几个视频,打成压缩包发给了老帆
- 老帆解压后,也上传到小滴AI网盘
- 几十G的视频文件,1秒就完成上传了,惊呼如何实现这个功能的
-
文件秒传功能的需求背景
- 在大文件传输的场景中,尤其是网盘服务,用户经常需要上传相同或相似的文件。
- 传统的上传方式会导致重复数据的多次传输,这不仅浪费了用户的带宽,也增加了服务器的存储压力。
- 为了提高效率和用户体验,文件秒传功能应运而生。
- 文件秒传的核心在于通过快速校验文件的完整性,避免重复上传相同的文件,从而提升传输效率
-
文件秒传设计思路
- 文件哈希值计算:
- 在文件上传前,客户端计算文件的哈希值(如MD5),一个固定长度的字符串,可以唯一标识文件的内容。
- 哈希值存储与查询:
- 服务器端维护一个哈希值与文件存储路径的映射关系。
- 新文件上传时,服务器先检查该哈希值是否已存在。
- 秒传逻辑:
- 如果服务器上已存在相同哈希值的文件,则直接返回文件的存储路径给客户端,客户端无需再次上传,实现秒传。
- AI智能化网盘逻辑:检查文件是否已经存在,如果存在,直接返回文件ID,建立关联关系,不需要上传,直接秒传成功
- 冲突处理:
- 虽然哈希碰撞的概率极低,但仍需考虑不同文件产生相同哈希值的情况。
- 通过增加额外的校验步骤,如文件大小、后缀等元数据信息,来降低误判的风险。
- 安全性考虑:
- 在实现秒传功能时,需要确保返回给用户的文件路径是安全的,防止路径泄露导致的安全问题
- 比如当前账号下实现文件秒传、当前全部账号下实现文件秒传
- 文件哈希值计算:
AI智能化云盘-文件秒传开发实战
-
需求
- 开发文件秒传接口,实现相关功能
-
业务逻辑设计
- 文件秒传 md5检测,是否有这个文件存储
- 检查当前账号的容量是否有
- 如果存在,则直接返回文件ID,建立关联关系,不需要上传,直接秒传成功
-
编码实战
@Transactional(rollbackFor = Exception.class) public boolean secondUpload(FileSecondUploadReq req) { //检查是否有这个文件存储 FileDO fileDO = fileMapper.selectOne(new QueryWrapper<FileDO>().eq("file_identifier", req.getIdentifier())); //账号的存储空间检查 if (fileDO != null && checkAndUpdateCapacity(req.getAccountId(), fileDO.getFileSize())) { //处理文件秒传 AccountFileDTO accountFileDTO = new AccountFileDTO(); accountFileDTO.setAccountId(req.getAccountId()); accountFileDTO.setParentId(req.getParentId()); accountFileDTO.setFileName(req.getFilename()); accountFileDTO.setFileSize(fileDO.getFileSize()); accountFileDTO.setFileSuffix(fileDO.getFileSuffix()); accountFileDTO.setFileId(fileDO.getId()); //accountFileDTO.setFileType(fileDO.getFileType()); accountFileDTO.setDel(false); accountFileDTO.setIsDir(FolderFlagEnum.NO.getCode()); //保存关联文件关系,里面有做相关权限检查 saveAccountFile(accountFileDTO); return true; } return false; }
AI智能化云盘-文件秒传接口测试实战
-
文件秒传交互逻辑
- 普通小文件上传和大文件分片上传
- 两类文件上传前,都需要先调用文件秒传接口
- 通过此接口判断是否已经存在对应的文件
-
需求
- 测试文件秒传功能,创建文件夹
- 注册A用户,注册B用户
- A用户上传一个新的文件,B用户也上传同个文件
-
测试结果
- B用户调用文件秒传接口后,自动建立了文件关联关系
- 实现了文件秒传,不用单独上传文件
举一反三能力-文件秒传思想多应用场景
-
举一反三能力:
-
网盘文件上传中,采用了文件秒传功能,其他类型业务也可以参考类似思想
-
脱离网盘业务场景,多个不同的互联网行业项目中应用类似解决方案
-
业务场景说明
-
社交平台 快速上传文件:
- 用户可以快速上传相同的文件,实现便捷的操作。
- 通过计算文件的MD5值,如果服务器上已存在相同MD5值的文件
- 则可以直接提供下载链接,无需重新上传,节省了带宽和时间
-
企业内部通信 员工文件共享:
-
员工可以快速共享相同的文档,节省时间和带宽。
-
企业内部的知识文档管理中,通过文件秒传技术,可以避免重复存储相同的文件,提高存储效率
-
和网盘类似的,也是云存储相关
-
-
在线教育平台 在线教学:
-
在教育行业,教师和学生之间的资料分享是很常见的。
-
借助云盘的秒传功能,教师可以迅速将教学资料分享给学生
-
1个老师发分别送给100个学生,不用上传100次,只需要上传1次即可
-
学生也可以快速提交作业,比如提及给老师和组长,也是实际上传1次,提升了教学效率
-
-
文件备份:
- 用户在备份文件时,常常遇到文件过大导致上传时间过长的问题。
- 利用秒传技术,用户可以快速备份已有文件,避免了重复上传的麻烦,节省了大量的时间和带宽。
- 包括数据库、表等定期增量备份的时候也是类似
-
智能缓存:
- 在用户上传文件的过程中,云盘会对部分常用文件进行智能缓存。
- 当其他用户需要下载同样的文件时,云盘会直接从缓存中提取
- 而不是从原始文件中重新上传,提高文件的下载速度。
-
数据去重:
- 通过数据去重技术,可以避免重复存储相同文件,从而节省存储空间和提高传输效率
- 比如同样是物理存储的云厂商,只要去重算法足够强大,可以节省很多冗余存储的内容
- 类似网盘,把File表无限放大,做物理存储关系记录
-
大文件上传解决方案和设计开发实战
大文件传输需求背景和常见解决方案《上》
-
需求背景
- 用户上传大文件的需求越来越普遍,尤其是在云存储、视频分享、在线教育等领域
- 大文件上传过程中的网络波动、不稳定性,以及客户端资源的限制,常常给用户带来不佳的体验。
- 传统的整文件上传方式不仅容易因中断而失败,还可能占用大量内存和带宽资源,对用户设备和服务器都造成负担
- 常见问题
- 网络中断风险
- 上传过程中网络不稳定或意外中断会导致整个上传任务失败,用户需要从头开始重新上传,浪费时间和带宽
- 客户端资源消耗:
- 大文件上传会占用大量内存和带宽资源,尤其在低配置设备(如移动设备)上,容易导致设备卡顿或资源耗尽
- 服务器负担:
- 如果没有有效的分片管理和断点续传机制,大文件上传失败后
- 服务器会存储许多不完整或重复的文件,浪费存储资源并增加维护难度
- 用户体验不佳
- 上传过程中缺乏实时反馈(如进度条),或者因为频繁失败而无法继续上传,会严重影响用户的满意度和使用意愿
- 网络中断风险
-
大文件分片上传常见解决方案之大文件分片上传(Chunked Upload)
- 文件分片
- 将大文件切成多个小的分片,每个分片单独上传到服务器。
- 这种方式可以提高上传的可靠性和效率,特别是在网络环境不稳定的情况下。
- 分片的标识与管理:
- 每个分片分配一个唯一标识(如索引或哈希值),以便前后端协作追踪上传状态和重建文件。
- 分片大小的选择策略
- 分片大小的选择需要综合考虑网络条件、文件大小和服务器性能。
- 通常,分片大小在几MB到几十MB之间较为合适。
- 分片文件合并
- 文件上传到服务器临时存储空间,等待全部分片都上传完成后,进行合并完成最终的文件
- 文件分片
大文件传输需求背景和常见解决方案《下》
-
大文件分片上传后,也存在相关的风险
- 网络中断风险:上传过程中网络不稳定意外中断会导致整个上传任务失败,用户需要从头开始重新上传,浪费时间和带宽。
- 用户体验不佳:上传过程中缺乏实时反馈(如进度条),或者频繁失败而无法继续上传,会严重影响用户的满意度。
-
大文件分片上传常见解决方案之断点续传(Resumable Upload)
- 是一种在数据传输过程中,如果发生中断或失败,可以从中断位置继续传输的技术。
- 避免了从头开始重新传输文件,从而节省时间、带宽和资源。
- 这种技术广泛应用于文件传输和下载,以提高数据传输的可靠性和效率
-
工作原理
- 记录传输状态
- 在数据传输过程中,系统会记录已经成功传输的数据量或位置。
- 系统会在服务器和客户端之间交换一些控制信息,以标记数据传输的进度。
- 中断检测
- 当数据传输发生中断时,系统会检测到这一情况,并保存当前传输状态。
- 中断可能是由于网络问题、客户端或服务器故障等原因引起的。
- 恢复传输
- 在传输恢复时,系统会读取之前保存的状态信息,从中断点开始继续传输未完成的数据。
- 这可以减少重复传输的部分,提高传输效率。
- 数据完整性校验
- 为了确保数据的完整性,客户端在恢复传输时可能会进行数据校验,如MD5校验,确保已传输的数据没有损坏
- 记录传输状态
-
大文件上传中,断点续传的实现步骤
- 文件分片,将大文件分割成多个固定大小的片段(如2MB)
- 上传请求,客户端逐个发送片段上传请求,附带片段序号和文件唯一标识。
- 状态记录,服务器端接收片段,记录上传状态到数据库。
- 断点检测,上传中断后,客户端查询服务器端已上传片段状态(需要查询已经上传的数据)
- 续传处理,从最后一个成功上传的片段继续上传剩余片段。
- 文件合并,所有片段上传完成后,服务器端合并片段生成完整文件。
思考能力-大文件传输方案前后端交互逻辑
-
思考:大文件上传交互,前端、后端、文件服务器之前的交互是如何的?
-
方案一:前端传到服务器,服务器再把文件传输到MinIO
- 优点
- 集中管理:
- 所有文件上传操作都通过后端进行集中管理,可以方便地实现身份验证、权限控制、文件处理等业务逻辑。
- 例如,可以在文件上传到MinIO之前生成缩略图或提取元数据。
- 安全性
- 后端可以作为中间层,对上传的文件进行安全检查和过滤,防止恶意文件上传到MinIO。
- 业务逻辑扩展
- 可以在文件上传过程中加入更多的业务逻辑,如文件内容的预处理、格式转换等
- 集中管理:
- 缺点
- 性能压力:
- 后端服务器需要处理文件的中转,增加了服务器的负载和响应时间。
- 特别是对于大文件,后端需要先接收文件,再将其传输到MinIO,这会显著增加延迟。
- 单点故障:
- 如果后端服务器出现故障,整个文件上传流程将受到影响,即使MinIO服务正常运行,也无法完成文件上传。
- 资源占用
- 后端服务器需要额外的资源来处理文件流,会导致服务器资源紧张,尤其是在高并发场景下。
- 性能压力:
- 优点
-
方案二:前端直接传输到MinIO(课程采取的方案)
-
优点:
- 减少延迟:前端直接将文件上传到MinIO,减少了文件在后端的中转时间,显著提高了上传效率。
- 减轻后端压力:后端服务器不需要处理文件的中转,可以专注于业务逻辑处理,减少了服务器的负载。
- 高可用性:即使后端服务器出现故障,只要MinIO服务正常运行,文件上传仍然可以继续进行,提高了系统的高可用性。
-
缺点:
- 安全性:
- 前端直接上传文件到MinIO,需要生成并返回一个有时效的上传凭证,
- 增加了安全风险。需要确保上传凭证的安全性,防止被恶意利用。
- 权限控制:
- 虽然可以通过上传凭证实现一定程度的权限控制,但相比后端集中管理,权限控制的灵活性和安全性可能会稍弱。
- 业务逻辑限制:
- 前端直接上传文件到MinIO,后端无法在文件上传过程中加入复杂的业务逻辑,如文件内容的预处理、格式转换等。
- 安全性:
-
前端直接传输到MinIO 交互时序图讲解
-
AWS-S3大文件和原生API方案优缺点对比
-
大文件分片上传基本原理
- 文件上传:将客户端的文件通过HTTP协议传输到服务器端,并在服务器端进行存储。
- 分片上传:将大文件拆分成多个小块,逐一上传,最后在服务器端进行合并。
-
前端实现:
-
使用HTML5的
File
API和Blob
对象来读取文件并分割成小块。 -
使用AJAX技术将文件分块发送到后端,也可以采用其他方式
-
-
原生编写Java实现大文件上传和使用AWS-S3的API优缺点对比
- 原生方式
- 后端实现:
- 使用
javax.servlet
和javax.servlet.http
包中的类来处理文件上传。 - 获取
request
对象中的文件项,通过getInputStream()
方法获取输入流,将文件内容写入服务器上的指定位置
- 使用
- 优点:
- 灵活性高:可以根据具体需求进行定制,例如实现断点续传、文件的读取、分块、传输和合并。
- 缺点:
- 复杂度高:需要手动处理文件的分块、传输和合并,代码复杂度较高。
- 后端实现:
- 使用AWS-S3的API
- 后端实现
aws-java-sdk-s3
已经封装了相关API,直接调用即可- 包括初始化分片上传任务、查询已经传输的分片、获取临时上传的预签名地址、合并分片文件等相关API
- 优点
- 开发效率高:SDK提供了丰富的API,简化大文件上传的开发过程,利用现成的解决方案,具有良好的兼容性和稳定性
- 缺点
- 学习曲线: 需要学习AWS S3的API和相关概念,有一定的学习成本
- 后端实现
- 原生方式
-
接口测试说明
-
第一步:测试初始化分片任务, 获取uploadId
- 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
-
第二步:测试初始化并生成多个预签名URL,返回给前端 * 获取多个分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面
-
第三步:合并分片
- 用uploadId标识特定的分片上传事件,检查分片数量是否与预期一致,然后合并
-
其他步骤:获取已经上传的分片文件, 未上传完成,调用接口获取上传进度
-
-
接口测试实战
@Test public void testInitMultipartUploadTask() { // 定义对象键名 String objectKey = "/meta/test5.txt"; // 定义存储桶名称 String bucket = "ai-pan"; // 创建对象元数据,并设置内容类型 ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("text/plain"); // 创建初始化分部分上传请求 InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucket, objectKey, metadata); // 初始化分部分上传,并获取上传ID String uploadId = amazonS3Client.initiateMultipartUpload(initRequest).getUploadId(); log.info("uploadId:{}", uploadId); }
AWS-S3大文件上传API测试预签名URL实战
-
需求:测试下面相关API
- 第一步:测试初始化分片任务上传, 获取uploadId
- 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
- 第二步:测试初始化并生成多个预签名URL,返回给前端
- 获取多个分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面
- 第三步:合并分片
- 用uploadId标识特定的分片上传事件,检查分片数量是否与预期一致,然后合并
- 其他步骤:获取已经上传的分片文件, 未上传完成,调用接口获取上传进度
- 第一步:测试初始化分片任务上传, 获取uploadId
-
测试实战
- 第二步:测试初始化并生成多个预签名URL,返回给前端
@Test public void testGenePreSignedUrls() { // 定义对象键名 String objectKey = "/meta/test5.txt"; // 定义存储桶名称 String bucket = "ai-pan"; // 定义分片数量,这里设置为4个分片 int chunkCount = 4; String uploadId = "ZjFkZjRhN2UtNzMzOS00NTUxLTgwOTEtNWViNzUwNmRmYTEzLmE4NTUyMmQyLTM1NjgtNGMwMS05ZTY2LWQ1MWQ3NGVkNmY2YQ"; // 创建用于存储分片URL的列表 List<String> partList = new ArrayList<>(); // 遍历每个分片,生成预签名的URL for (int i = 1; i <= chunkCount; i++) { // 生成预签名的 URL, 设置过期时间,例如 1 小时后 Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000); // 创建生成预签名URL的请求,并指定HTTP方法为PUT GeneratePresignedUrlRequest genePreSignedUrlReq = new GeneratePresignedUrlRequest(bucket, objectKey, HttpMethod.PUT) .withExpiration(expiration); // 添加上传ID和分片编号作为请求参数 genePreSignedUrlReq.addRequestParameter("uploadId", uploadId); genePreSignedUrlReq.addRequestParameter("partNumber", String.valueOf(i)); // 生成并获取预签名URL URL url = amazonS3Client.generatePresignedUrl(genePreSignedUrlReq); // 将生成的URL添加到列表中 partList.add(url.toString()); // 日志输出当前分片的URL列表 log.info("partList:{}", partList); } }
AWS-S3大文件上传API测试合并分片实战
-
需求:测试下面相关API
- 第一步:测试初始化分片任务上传, 获取uploadId
- 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
- 第二步:测试初始化并生成多个预签名URL,返回给前端
- 获取多个分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面
- 第三步:合并分片
- 用uploadId标识特定的分片上传事件,检查分片数量是否与预期一致,然后合并
- 其他步骤:上传进度验证,获取已经上传的分片文件, 未上传完成,调用接口获取上传进度
- 第一步:测试初始化分片任务上传, 获取uploadId
-
测试实战
// 测试合并分片的方法 @Test public void testMergeChunks() { // 定义对象键名 String objectKey = "/meta/test5.txt"; // 定义存储桶名称 String bucket = "ai-pan"; // 定义分片数量,这里设置为4个分片 int chunkCount = 4; // 定义上传ID,用于标识特定的分片上传事件 String uploadId = "ZjFkZjRhN2UtNzMzOS00NTUxLTgwOTEtNWViNzUwNmRmYTEzLmUzYmFhMDBlLTJjZGUtNGVhMC1iNDc3LWFmYjExNTFhZmQwNA"; // 创建一个列出分片请求对象 ListPartsRequest listPartsRequest = new ListPartsRequest(bucket, objectKey, uploadId); // 获取分片列表 PartListing partListing = amazonS3Client.listParts(listPartsRequest); List<PartSummary> parts = partListing.getParts(); // 检查分片数量是否与预期一致 if (chunkCount != parts.size()) { // 已上传分块数量与记录中的数量不对应,不能合并分片 throw new RuntimeException("分片缺失,请重新上传"); } // 创建一个完成分片上传请求对象 CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest() .withUploadId(uploadId) .withKey(objectKey) .withBucketName(bucket) .withPartETags(parts.stream() // 将每个分片的编号和ETag封装到PartETag对象中 .map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())) .collect(Collectors.toList())); // 完成分片上传并获取结果 CompleteMultipartUploadResult result = amazonS3Client.completeMultipartUpload(completeMultipartUploadRequest); }
AWS-S3大文件上传API测试上传进度实战
-
需求:测试下面相关API
- 第一步:测试初始化分部分上传, 获取uploadId
- 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
- 第二步:测试初始化并生成多个预签名URL,返回给前端
- 获取多个分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面
- 第三步:合并分片
- 用uploadId标识特定的分片上传事件,检查分片数量是否与预期一致,然后合并
- 其他步骤:上传进度验证,获取已经上传的分片文件, 未上传完成,调用接口获取上传进度
- 第一步:测试初始化分部分上传, 获取uploadId
-
测试实战
@Test // 测试列出分片上传的各个分片信息 public void testListParts() { // 定义对象键名 String objectKey = "/meta/test5.txt"; // 定义存储桶名称 String bucket = "ai-pan"; // 定义上传ID,用于标识特定的分片上传事件 String uploadId = "ZjFkZjRhN2UtNzMzOS04NTUxLTgwOTEtNWViNzUwNmRmYTEzLmE4NTUyMmQyLTM1NjUtNGMwMS05ZTY2LWQ5MWQ4NDUyBmIyA"; // 检查指定的存储桶中是否存在具有指定对象键名的对象 boolean doesObjectExist = amazonS3Client.doesObjectExist(bucket, objectKey); if (!doesObjectExist) { // 未上传完,返回已上传的分片 ListPartsRequest listPartsRequest = new ListPartsRequest(bucket, objectKey, uploadId); PartListing partListing = amazonS3Client.listParts(listPartsRequest); List<PartSummary> parts = partListing.getParts(); // 创建一个结果映射,用于存放上传状态和分片列表 Map<String,Object> result = new HashMap<>(); result.put("finished", false); result.put("exitPartList", parts); //前端可以通过这个判断是否要调用合并merge接口 log.info("result:{}",result); // 遍历并打印每个分片的信息 for (PartSummary partSummary : parts) { System.out.println("getPartNumber:" + partSummary.getPartNumber() + ",getETag=" + partSummary.getETag() + ",getSize= " + partSummary.getSize() + ",getLastModified=" + partSummary.getLastModified()); } // 打印存储桶名称 System.out.println(partListing.getBucketName()); } }
存储引擎StoreEngine大文件上传API封装
-
需求
- StoreEngine相关API封装
-
编码实战
- 接口
/** * 查询分片数据 * @param bucketName 存储桶名称 * @param objectKey 对象名称 * @param uploadId 分片上传ID * @return 分片列表对象 */ PartListing listMultipart(String bucketName, String objectKey, String uploadId); /** * 1-初始化分片上传任务,获取uploadId,如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId * @param bucketName 存储桶名称 * @param objectKey 对象名称 * @param metadata 对象元数据 * @return 初始化分片上传结果对象,包含uploadId等信息 */ InitiateMultipartUploadResult initMultipartUploadTask(String bucketName, String objectKey, ObjectMetadata metadata); /** * 2-生成分片上传地址,返回给前端 * @param bucketName 存储桶名称 * @param objectKey 对象名称 * @param httpMethod HTTP方法,如GET、PUT等 * @param expiration 签名过期时间 * @param params 签名中包含的参数 * @return 生成的预签名URL */ URL genePreSignedUrl(String bucketName, String objectKey, HttpMethod httpMethod, Date expiration, Map<String,Object> params); /** * 3-合并分片 * @param bucketName 存储桶名称 * @param objectKey 对象名称 * @param uploadId 分片上传ID * @param partETags 分片ETag列表,用于验证分片的完整性 * @return 完成分片上传结果对象 */ CompleteMultipartUploadResult mergeChunks(String bucketName, String objectKey, String uploadId, List<PartETag> partETags);
- 实现
@Override public PartListing listMultipart(String bucketName, String objectKey, String uploadId) { try { ListPartsRequest request = new ListPartsRequest(bucketName, objectKey, uploadId); return amazonS3Client.listParts(request); } catch (Exception e) { log.error("errorMsg={}", e); return null; } } @Override public InitiateMultipartUploadResult initMultipartUploadTask(String bucketName, String objectKey, ObjectMetadata metadata) { try { InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey, metadata); return amazonS3Client.initiateMultipartUpload(request); } catch (Exception e) { log.error("errorMsg={}", e); return null; } } @Override public URL genePreSignedUrl(String bucketName, String objectKey, HttpMethod httpMethod, Date expiration, Map<String, Object> params) { try { GeneratePresignedUrlRequest genePreSignedUrlReq = new GeneratePresignedUrlRequest(bucketName, objectKey, httpMethod) .withExpiration(expiration); //遍历params作为参数加到genePreSignedUrlReq里面,比如 添加上传ID和分片编号作为请求参数 //genePreSignedUrlReq.addRequestParameter("uploadId", uploadId); //genePreSignedUrlReq.addRequestParameter("partNumber", String.valueOf(i)); for (Map.Entry<String, Object> entry : params.entrySet()) { genePreSignedUrlReq.addRequestParameter(entry.getKey(), String.valueOf(entry.getValue())); } // 生成并获取预签名URL return amazonS3Client.generatePresignedUrl(genePreSignedUrlReq); } catch (Exception e) { log.error("errorMsg={}", e); return null; } } @Override public CompleteMultipartUploadResult mergeChunks(String bucketName, String objectKey, String uploadId, List<PartETag> partETags) { CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partETags); return amazonS3Client.completeMultipartUpload(request); }
大文件上传接口开发和全链路测试
初始化分片上传任务接口设计和开发实战
-
需求
- 理解相关数据库表作用
- 开发大文件分片上传初始化任务接口
-
数据库文件分片信息表说明
CREATE TABLE `file_chunk` ( `id` bigint NOT NULL, `identifier` varchar(500) NOT NULL COMMENT '文件唯一标识(md5)', `upload_id` varchar(255) DEFAULT NULL COMMENT '分片上传ID', `file_name` varchar(500) NOT NULL COMMENT '文件名', `bucket_name` varchar(255) NOT NULL COMMENT '所属桶名', `object_key` varchar(500) NOT NULL COMMENT '文件的key', `total_size` bigint NOT NULL COMMENT '总文件大小(byte)', `chunk_size` bigint NOT NULL COMMENT '每个分片大小(byte)', `chunk_num` int NOT NULL COMMENT '分片数量', `account_id` bigint NOT NULL COMMENT '用户ID', `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uq_file_identifier` (`identifier`) USING BTREE ) ENGINE=InnoDB COMMENT='文件分片信息表';
-
业务逻辑说明
- 检查存储空间是否够( 合并文件的时候进行校验更新存储空间)
- 根据文件名推断内容类型
- 初始化分片上传,获取上传ID
- 创建上传任务实体并设置相关属性
- 将任务插入数据库,构建并返回任务信息DTO
-
编码实战
@Override @Transactional(rollbackFor = Exception.class) public FileChunkDTO initFileChunkTask(FileChunkInitTaskReq req) { //检查存储空间是否够 StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", req.getAccountId())); if (storageDO.getUsedSize() + req.getTotalSize() > storageDO.getTotalSize()) { throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); } String objectKey = CommonUtil.getFilePath(req.getFilename()); // 根据文件名推断内容类型 String contentType = MediaTypeFactory.getMediaType(objectKey).orElse(MediaType.APPLICATION_OCTET_STREAM).toString(); // 设置文件元数据 ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType(contentType); // 初始化分片上传,获取上传ID String uploadId = fileStoreEngine.initMultipartUploadTask(minioConfig.getBucketName(), objectKey, objectMetadata).getUploadId(); // 创建上传任务实体并设置相关属性 FileChunkDO task = new FileChunkDO(); int chunkNum = (int) Math.ceil(req.getTotalSize() * 1.0 / req.getChunkSize()); task.setBucketName(minioConfig.getBucketName()) .setChunkNum(chunkNum) .setChunkSize(req.getChunkSize()) .setTotalSize(req.getTotalSize()) .setIdentifier(req.getIdentifier()) .setFileName(req.getFilename()) .setObjectKey(objectKey) .setUploadId(uploadId) .setAccountId(req.getAccountId()); // 将任务插入数据库 fileChunkMapper.insert(task); // 构建并返回任务信息DTO return new FileChunkDTO(task).setFinished(false).setExitPartList(new ArrayList<>()); }
临时分片上传地址接口设计和开发实战
-
需求
- 获取分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面
-
业务逻辑说明
- 查询分片上传任务是否存在
- 配置签名过期时间
- 生成预签名URL地址
-
编码实战
@Override public String genPreSignUploadUrl(Long accountId, String identifier, Integer partNumber) { FileChunkDO task = fileChunkMapper.selectOne(new QueryWrapper<FileChunkDO>().lambda().eq(FileChunkDO::getIdentifier, identifier).eq(FileChunkDO::getAccountId, accountId)); if (task == null) { throw new BizException(BizCodeEnum.FILE_CHUNK_TASK_NOT_EXISTS); } //配置预签名过期时间 Date expireDate = DateUtil.offsetMillisecond(new Date(), minioConfig.getPRE_SIGN_URL_EXPIRE().intValue()); // 生成预签名URL Map<String, Object> params = new HashMap<>(); params.put("partNumber", partNumber.toString()); params.put("uploadId", task.getUploadId()); URL preSignedUrl = fileStoreEngine.genePreSignedUrl(minioConfig.getBucketName(), task.getObjectKey(), HttpMethod.PUT, expireDate, params); log.info("生成预签名URL地址 identifier={},partNumber={}, preSignedUrl={}", identifier, partNumber, preSignedUrl.toString()); return preSignedUrl.toString(); }
合并分片文件接口设计和开发实战
-
需求
- 根据传入的请求对象合并所有分片文件
-
业务逻辑
- 获取任务和分片列表,检查是否足够合并
- 检查存储空间和更新
- 合并分片
- 判断合并分片是否成功
- 存储文件和关联信息到数据库
- 根据唯一标识符删除相关分片信息
-
编码实战
public void mergeFileChunk(FileChunkMergeReq req) { //获取任务和分片列表,检查是否足够合并 FileChunkDO task = fileChunkMapper.selectOne(new QueryWrapper<FileChunkDO>() .eq("account_id", req.getAccountId()) .eq("identifier", req.getIdentifier())); if(task == null){ throw new BizException(BizCodeEnum.FILE_CHUNK_TASK_NOT_EXISTS); } PartListing partListing = fileStoreEngine.listMultipart(task.getBucketName(), task.getObjectKey(), task.getUploadId()); List<PartSummary> parts = partListing.getParts(); if(parts.size() != task.getChunkNum()){ //上传的分片数量和记录中不对应,合并失败 throw new BizException(BizCodeEnum.FILE_CHUNK_NOT_ENOUGH); } //检查更新存储空间 StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<>(new StorageDO()) .eq("account_id", req.getAccountId())); long realFileTotalSize = parts.stream().map(PartSummary::getSize).mapToLong(Long::valueOf).sum(); if(storageDO.getUsedSize() + realFileTotalSize > storageDO.getTotalSize()){ throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); } storageDO.setUsedSize(storageDO.getUsedSize() +realFileTotalSize); storageMapper.updateById(storageDO); //2-合并文件 CompleteMultipartUploadResult result = fileStoreEngine.mergeChunks(task.getBucketName(), task.getObjectKey(), task.getUploadId(), parts.stream().map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())) .collect(Collectors.toList())); //【判断是否合并成功 if(result.getETag()!=null){ FileUploadReq fileUploadReq = new FileUploadReq(); fileUploadReq.setAccountId(req.getAccountId()) .setFilename(task.getFileName()) .setIdentifier(task.getIdentifier()) .setParentId(req.getParentId()) .setFileSize(realFileTotalSize) .setFile(null); //存储文件和关联信息到数据库 accountFileService.saveFileAndAccountFile(fileUploadReq,task.getObjectKey()); //删除相关任务记录 fileChunkMapper.deleteById(task.getId()); log.info("合并成功"); } }
查询分片上传进度接口设计和开发实战
-
需求
- 查询分片上传进度
-
业务逻辑
- 获取任务和分片列表,检查是否存在
- 不存在,表示未上传完,返回已上传的分片
- 已经存在,返回上传完成标识,前端触发合并接口
-
编码实战
@Override public FileChunkDTO listFileChunk(Long accountId, String identifier) { // 获取任务和分片列表,检查是否足够 FileChunkDO task = fileChunkMapper.selectOne(new QueryWrapper<FileChunkDO>().lambda().eq(FileChunkDO::getAccountId, accountId)); if (task == null || !identifier.equals(task.getIdentifier())) { return null; } FileChunkDTO result = new FileChunkDTO(task); boolean doesObjectExist = fileStoreEngine.doesObjectExist(task.getBucketName(), task.getObjectKey()); if (!doesObjectExist) { // 不存在,表示未上传完,返回已上传的分片 PartListing partListing = fileStoreEngine.listMultipart(task.getBucketName(), task.getObjectKey(), task.getUploadId()); if(task.getChunkNum() == partListing.getParts().size()){ //已经存在,合并 result.setFinished(true).setExitPartList(partListing.getParts()); }else { result.setFinished(false).setExitPartList(partListing.getParts()); } } return result; }
大文件分片上传全链路测试方案设计
-
需求
- 写了那么多接口如何测试是否正确呢?
- 缺少前端的情况下,大文件如何分片进行测试?
- 如何测试大文件上传相关接口?
-
测试思路
- 使用后端Java代码进行文件读取
- 根据进行切割成不同的小文件
- 再调用对应的大文件分片上传任务初始化接口
- 获取分片上传地址,返回临时MinIO地址
- 使用HttpClient模拟前端直接上传分片到minio
- 调用分片文件合并接口
- 查询分片上传进度接口
-
编码实战
- 使用后端Java代码进行文件读取
- 根据进行切割成不同的小文件
SpringBootTest @Slf4j class FileChunkUploadTests { @Autowired private FileChunkService fileChunkService; private Long accountId = 3L; private String identifier = "abcsdfsd"; /** * 存储分片后端的文件路径和名称 */ private final List<String> chunkFilePaths = Lists.newArrayList(); /** * 存储分片上传地址 */ private final List<String> chunkUploadUrls = Lists.newArrayList(); /** * 上传ID */ private String uploadId ; /** * 分片大小,5MB */ private final long chunkSize = 5 * 1024 * 1024; /** * 用一个10MB以上的文件,按5MB分片大小进行分片 */ @Test public void testCreateChunkFiles() { // 将文件分片存储 String filePath = "/Users/xdclass/Desktop/chunk/es_note.pdf"; File file = new File(filePath); long fileSize = file.length(); //int chunkCount = (int) Math.ceil((double) fileSize / CHUNK_SIZE); int chunkCount = (int) Math.ceil(fileSize * 1.0 / chunkSize); log.info("创建分片数量是: {} chunks", chunkCount); try (FileInputStream fis = new FileInputStream(file)) { byte[] buffer = new byte[(int) chunkSize]; for (int i = 0; i < chunkCount; i++) { String chunkFileName = filePath + ".part" + (i + 1); try (FileOutputStream fos = new FileOutputStream(chunkFileName)) { int bytesRead = fis.read(buffer); fos.write(buffer, 0, bytesRead); log.info("创建的分片文件名: {} ({} bytes)", chunkFileName, bytesRead); chunkFilePaths.add(chunkFileName); } } } catch (IOException e) { e.printStackTrace(); } }
大文件分片上传全链路测试代码实战《上》
-
需求
- 测试创建分片上传任务接口
- 获取分片上传地址,返回临时MinIO地址
- 模拟前端直接上传到Minio里面
- 测试合并接口
- 测试查询分片上传进度接口
-
编码实战
/** * 第1步,创建分片上传任务 */ private void testInitFileChunkTask() { FileChunkInitTaskReq req = new FileChunkInitTaskReq(); req.setAccountId(accountId).setFilename("es_note.pdf") .setTotalSize((long) (20552959))//20552959 .setChunkSize((long) (5 * 1024 * 1024))//5242880 .setIdentifier(identifier); FileChunkDTO fileChunkDTO = fileChunkService.initFileChunkTask(req); log.info("分片上传初始化结果: {}", fileChunkDTO); uploadId = fileChunkDTO.getUploadId(); testGetFileChunkUploadUrl(); } /** * 第2步,获取分片上传地址,返回临时MinIO地址,前端直接上传到Minio里面 */ private void testGetFileChunkUploadUrl() { for (int i = 1; i <= chunkFilePaths.size(); i++) { String uploadUrl = fileChunkService.genPreSignUploadUrl(accountId, identifier, i); log.info("分片上传地址: {}", uploadUrl); //存储4个分片地址 chunkUploadUrls.add(uploadUrl); } uploadChunk(); } /** * 模拟前端直接上传分片 */ @SneakyThrows private void uploadChunk() { HttpClient httpClient = HttpClients.createDefault(); for (int i = 0; i < chunkUploadUrls.size(); i++) { // PUT直接上传到minio String chunkUploadId = chunkUploadUrls.get(i); HttpPut httpPut = new HttpPut(chunkUploadId); httpPut.setHeader("Content-Type","application/octet-stream"); File chunkFile = new File(chunkFilePaths.get(i)); FileEntity chunkFileEntity = new FileEntity(chunkFile); httpPut.setEntity(chunkFileEntity); HttpResponse chunkUploadResp = httpClient.execute(httpPut); httpPut.releaseConnection(); } } /** * 测试合并分片 */ @Test public void testMergeFileChunk() { FileChunkMergeReq req = new FileChunkMergeReq(); req.setAccountId(accountId).setIdentifier(identifier).setParentId(233L); fileChunkService.mergeFileChunk(req); } /** * 查询分片上传进度 */ @Test public void testChunkUploadProgress() { FileChunkDTO fileChunkDTO = fileChunkService.listFileChunk(accountId, identifier); log.info("分片上传进度: {}", fileChunkDTO); }
大文件分片上传全链路测试代码实战《下》
-
需求
- 测试大文件上传全链路代码
-
测试实战
- 检查分片文件是否生成
- 检查MinIO是否有对应的文件
- 检查数据库是否有对应的文件
- 注意
- MinIO显示的文件大小和实际的有出入,是界面显示问题,实际的为准
- 可以合并文件后下载下来打开对比
网盘文件分享模块设计和开发实战
文件分享转存需求背景和数据库表说明
- 需求
- 老王有几十G《冰冰的游泳视频》存储在小滴网盘上面
- 需要分享给老帆,但是又不能公开分享下载,所以需要开发分享转存功能
-
AI智能云盘分享文件
- 支持多个文件批量公开分享和加密分享
- 支持查看我的分享链接和取消分享
- 支持配置分享链接的有效期,包括 永久有效、7天有效、30天有效
-
数据库表说明
文件分享枚举定义和分享列表接口开发
-
需求
- 定位文件分享模块相关枚举类
- 查询我的文件分享列表
-
编码实战
-
提取码枚举
@Getter public enum ShareTypeEnum { /** * 没有提取码 */ NO_CODE, /** * 有提取码 */ NEED_CODE; }
-
分享状态
/** * 分享状态 used正常, expired已失效, cancled取消 */ public enum ShareStatusEnum { USED, EXPIRED, CANCELED; }
-
分享链接时效性枚举
//分享类型(0 永久有效;1: 7天有效;2: 30天有效) @AllArgsConstructor @Getter public enum ShareDayTypeEnum { PERMANENT(0,0), SEVEN_DAYS(1,7), THIRTY_DAYS(2,30); private Integer dayType; private Integer days; /** * 根据类型获取对应的天数 */ public static Integer getDaysByType(Integer dayType) { for (ShareDayTypeEnum value : ShareDayTypeEnum.values()) { if(value.getDayType().equals(dayType)){ return value.getDays(); } } //默认7天 return SEVEN_DAYS.days; } }
-
我的分享列表接口
@Override public List<ShareDTO> listShare() { Long accountId = LoginInterceptor.threadLocal.get().getId(); List<ShareDO> shareDOList = shareMapper.selectList(new QueryWrapper<ShareDO>().eq("account_id", accountId) .orderByDesc("gmt_create")); return SpringBeanUtil.copyProperties(shareDOList, ShareDTO.class); }
-
AI智能化云盘-创建文件分享接口开发实战
-
需求
- 开发创建文件分享相关接口
- 注意:后端存储分享链接的域名
-
业务逻辑
- 检查分享文件的权限
- 生成分享链接和持久化数据库
- 生成分享详情和持久化数据库
-
编码实战
@Override @Transactional(rollbackFor = Exception.class) public ShareDTO createShare(ShareCreateReq req) { //1、检查分享文件的权限 List<Long> fileIds = req.getFileIds(); fileService.checkFileIdLegal(fileIds, req.getAccountId()); //2、生成分享链接和持久化数据库 Integer dayType = req.getShareDayType(); Integer shareDays = ShareDayTypeEnum.getDaysByType(dayType); Long shareId = IdUtil.getSnowflakeNextId(); //生成分享链接 String shareUrl = AccountConfig.PAN_FRONT_DOMAIN_SHARE_API + shareId; log.info("shareUrl:{}",shareUrl); ShareDO shareDO = ShareDO.builder() .id(shareId) .shareName(req.getShareName()) .shareType(ShareTypeEnum.valueOf(req.getShareType()).name()) .shareDayType(dayType) .shareDay(shareDays) .shareUrl(shareUrl) .shareStatus(ShareStatusEnum.USED.name()) .accountId(req.getAccountId()).build(); if(ShareDayTypeEnum.PERMANENT.getDayType().equals(dayType)){ shareDO.setShareEndTime(Date.from(LocalDate.of(9999,12,31) .atStartOfDay(ZoneId.systemDefault()).toInstant())); }else { shareDO.setShareEndTime(new Date(System.currentTimeMillis() + shareDays * 24 * 3600 * 1000L)); } if(ShareTypeEnum.NEED_CODE.name().equalsIgnoreCase(req.getShareType())){ //生成提取码 6位 String shareCode = RandomStringUtils.randomAlphabetic(6).toUpperCase(); shareDO.setShareCode(shareCode); } shareMapper.insert(shareDO); //3、生成分享详情和持久化数据库 List<ShareFileDO> shareFileDOS = new ArrayList<>(); fileIds.forEach(fileId -> { ShareFileDO shareFileDO = ShareFileDO.builder() .shareId(shareId) .accountFileId(fileId) .accountId(req.getAccountId()) .build(); shareFileDOS.add(shareFileDO); }); shareFileMapper.insertBatch(shareFileDOS); return SpringBeanUtil.copyProperties(shareDO, ShareDTO.class); }
批量取消分享接口实战和文件移动Bug修复
-
需求
- 根据我的分享列表,取消对应的分享链接
-
业务逻辑
- 校验分享ID列表权限是否合格
- 删除分享链接
- 删除分享详情链接
-
编码实战
@Transactional(rollbackFor = Exception.class) public void cancelShare(ShareCancelReq req) { //1、校验分享ID列表权限是否合格 List<ShareDO> shareDOS = shareMapper.selectList(new QueryWrapper<ShareDO>().in("id", req.getShareIds()) .eq("account_id", req.getAccountId())); if (shareDOS.size() != req.getShareIds().size()) { log.error("分享ID列表权限校验失败,{}", req); throw new BizException(BizCodeEnum.SHARE_CANCEL_ILLEGAL); } //2、删除分享链接 shareMapper.deleteBatchIds(req.getShareIds()); //3、删除分享详情链接 shareFileMapper.delete(new QueryWrapper<ShareFileDO>().in("share_id", req.getShareIds())); }
-
文件移动Bug修复(几个细心的同学发现了Bug,多数同学没发现)
@Override @Transactional(rollbackFor = Exception.class) public void moveBatch(FileBatchReq req) { //检查被转移的文件ID是否合法 List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId()); //检查目标文件夹ID是否合法,需要包括子文件夹 checkTargetParentIdLegal(req); //批量转移文件到目标文件夹 accountFileDOList.forEach(accountFileDO -> accountFileDO.setParentId(req.getTargetParentId())); //处理重复的文件名或者文件夹名 accountFileDOList.forEach(this::processFileNameDuplicate); //更新关联对象信息,存储文件映射关系 for (AccountFileDO accountFileDO : accountFileDOList) { if (accountFileMapper.updateById(accountFileDO)<=0) { throw new RuntimeException("部分文件或文件夹移动失败"); } } }
链路接口测试-创建-查看-取消分享接口
-
接口测试需求
- 测试创建文件分享链接接口
- 测试查看我的文件分享链接列表接口
- 取消对应的文件分享链接接口
-
测试实战
自定义注解+AOP实战文件分享设计实战
【业务难点】查看他人分享文件列表业务设计
-
需求
-
通过别人分享的文件链接,查看对应分享的文件(注意:需要先登录自己网盘才可以查看分享信息)
-
选择部分或者全部分享的文件,或者进入对应的文件夹,转存到自己网盘
-
思考难点
- 上述业务逻辑如何实现?
- 如果是需要提求取码的链接,如何第一次校验,后续可以不校验,实现安全访问?
- 如何确保不被越权访问?
-
-
前后端交互逻辑和解决方案
- 用户访问分享链接,统一调用后端【基本分享信息】接口
/api/share/v1/visit?shareId=XXXX
- 上述接口会返回基本分享信息,包括:是否需要提取码、分享人信息 等
- 前端根据访问的基本分享信息,是否需要提取码,分两个情况
- 情况一
- 免提取码,基本分享信息返回里面有token,请求头携带过去 即可直接使用访问对应的分享文件
- 调用
/api/share/v1/detail
接口,访问链接之间进入文件列表
- 情况二
- 需要提取码,基本分享信息里面无token,先进入提取码界面,可以看到分享人信息
- 输入提取码后校验,成功则后端会返回token,请求头携带过去 即可直接使用访问对应的分享文件
- 调用
/api/share/v1/detail
接口,访问链接之间进入文件列表
- 情况一
- 进入分享文件的子文件,则统一调用接口
/api/share/v1/list_share_file
(需要携带token)
- 用户访问分享链接,统一调用后端【基本分享信息】接口
-
思考下:如果是你设计,是否还有其他的设计方案,群里讨论
AI智能化云盘-基本分享信息接口开发实战
- 需求
- 用户访问分享链接,进入对应的分享文件页面
-
业务逻辑
-
检查分享状态
-
查询分享记录实体
-
查询分享者信息
-
判断是否需要生成校验码,不需要的话可以直接生成分享token
-
-
编码实战
- 业务逻辑开发
@Override public ShareSimpleDTO simpleDetail(Long shareId) { //1、检查分享状态 ShareDO shareDO = checkShareStatus(shareId); //2、查询分享记录实体 ShareSimpleDTO shareSimpleDTO = SpringBeanUtil.copyProperties(shareDO, ShareSimpleDTO.class); //3、查询分享者信息 ShareAccountDTO shareAccountDTO = getShareAccount(shareDO.getAccountId()); shareSimpleDTO.setShareAccountDTO(shareAccountDTO); //判断是否需要生成校验码,不需要的话可以直接生成分享token if (ShareTypeEnum.NO_CODE.name().equalsIgnoreCase(shareDO.getShareType())) { shareSimpleDTO.setShareToken(JwtUtil.geneShareJWT(shareDO.getId())); } return shareSimpleDTO; }
- 令牌生成
/** * 创建分享的令牌 */ public static String geneShareJWT( Object claimValue) { // 创建 JWT token String compact = Jwts.builder() .subject(SHARE_SUBJECT) .claim(CLAIM_SHARE_KEY, claimValue) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + SHARE_TOKEN_EXPIRE)) .signWith(KEY, ALGORITHM) // 直接使用KEY即可 .compact(); return compact; } /** * 创建分享的令牌 */ public static Claims checkShareJWT(String token) { try { log.debug("开始校验 Share JWT: {}", token); // 校验 Token 是否为空 if (token == null || token.trim().isEmpty()) { log.error("Share Token 不能为空"); return null; } token = token.trim(); // 解析 JWT Claims payload = Jwts.parser() .verifyWith(KEY) //设置签名的密钥, 使用相同的 KEY .build() .parseSignedClaims(token).getPayload(); log.info("Share JWT 解密成功,Claims: {}", payload); return payload; } catch (IllegalArgumentException e) { log.error("JWT 校验失败: {}", e.getMessage(), e); } catch (io.jsonwebtoken.security.SignatureException e) { log.error("JWT 签名验证失败: {}", e.getMessage(), e); } catch (io.jsonwebtoken.ExpiredJwtException e) { log.error("JWT 已过期: {}", e.getMessage(), e); } catch (Exception e) { log.error("JWT 解密失败: {}", e.getMessage(), e); } return null; }
AI智能化云盘-分享链接校验码接口开发
-
需求
- 开发校验分享码,方便后续访问,返回临时的token
-
业务逻辑
-
检查分享链接是否存在和过期
-
校验提取码是否正确
-
-
编码实战
public String checkShareCode(ShareCheckReq req) { ShareDO shareDO = shareMapper.selectOne(new QueryWrapper<ShareDO>().eq("id", req.getShareId()) .eq("share_code", req.getShareCode()) .eq("share_status", ShareStatusEnum.USED.name())); if (shareDO != null) { //判断是否过期 if (shareDO.getShareEndTime().getTime() < System.currentTimeMillis()) { log.error("分享链接已过期,分享链接ID:{}", req.getShareId()); return null; } //根据分享ID生成临时访问令牌 return JwtUtil.geneShareJWT(shareDO.getId()); } else { log.error("分享链接不存在或不可用,分享链接ID:{}", req.getShareId()); return null; } }
AI智能化云盘-查看分享详情接口开发
-
需求
- 开发查看文件分享链接的详情文件接口
- 思考难点
- 如何校验访问token和分享ID的关联性
- 哪些接口需要校验token,如何实现复用?
-
业务逻辑
- 查询分享记录实体
- 检查分享状态
- 查询分享文件信息
- 查询分享者信息
- 构造分析详情对象返回
-
编码实战
public ShareDetailDTO detail(Long shareId) { //1、查询分享记录实体,检查分享状态 ShareDO shareDO = checkShareStatus(shareId); ShareDetailDTO shareDetailDTO = SpringBeanUtil.copyProperties(shareDO, ShareDetailDTO.class); //3、查询分享文件信息 List<AccountFileDO> accountFileDOList = getShareFileInfo(shareId); List<AccountFileDTO> accountFileDTOList = SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class); shareDetailDTO.setFileDTOList(accountFileDTOList); //3、查询分享者信息 ShareAccountDTO shareAccountDTO = getShareAccount(shareDO.getAccountId()); shareDetailDTO.setShareAccountDTO(shareAccountDTO); return shareDetailDTO; }
自定义注解+AOP实现文件分享token校验实战《上》
简介: 自定义注解+AOP实现文件分享token校验实战
- 思考难点
- 如何校验访问token和分享ID的关联性
- 哪些接口需要校验token,如何实现复用?
- 解决方案:自定义注解+AOP切面
- 自定义注解基础知识
- Annotation(注解)
- 从JDK 1.5开始, Java增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。
- 注解其实就是代码里的特殊标记,它用于替代配置文件
- 常见的很多 @Override、@Deprecated等
- 什么是元注解
- 注解的注解,比如当我们需要自定义注解时
- 会需要一些元注解(meta-annotation),如**@Target**和**@Retention**
- java内置4种元注解
- @Target 表示该注解用于什么地方
- ElementType.CONSTRUCTOR 用在构造器
- ElementType.FIELD 用于描述域-属性上
- ElementType.METHOD 用在方法上
- ElementType.TYPE 用在类或接口上
- ElementType.PACKAGE 用于描述包
- @Retention 表示在什么级别保存该注解信息
- RetentionPolicy.SOURCE 保留到源码上
- RetentionPolicy.CLASS 保留到字节码上
- RetentionPolicy.RUNTIME 保留到虚拟机运行时(最多,可通过反射获取)
- @Documented 将此注解包含在 javadoc 中
- @Inherited 是否允许子类继承父类中的注解
- @Target 表示该注解用于什么地方
- @interface
- 用来声明一个注解,可以通过default来声明参数的默认值
- 自定义注解时,自动继承了java.lang.annotation.Annotation接口
- 通过反射可以获取自定义注解
- Annotation(注解)
- 利用AOP实现token解密获取分享ID
- Aspect Oriented Program 面向切面编程, 在不改变原有逻辑上增加额外的功能
- AOP思想把功能分两个部分,分离系统中的各种关注点
- 好处:减少代码侵入,解耦,可以统一处理横切逻辑,方便添加和删除横切逻辑
-
编码实战
- 自定义注解
ShareCodeCheck
编码实战
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ShareCodeCheck { }
- 定义切面类
@Aspect @Component @Slf4j public class ShareCodeAspect { private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>(); /** * 设置当前线程的共享ID * * @param shareId 共享ID */ public static void set(Long shareId) { threadLocal.set(shareId); } /** * 获取当前线程绑定的共享ID * * @return 当前线程绑定的共享ID,若未绑定则返回0 */ public static Long get() { Long shareId = threadLocal.get(); if (Objects.isNull(shareId)) { return null; } return shareId; }
- 自定义注解
自定义注解+AOP实现文件分享token校验实战《下》
-
需求
- 编写切面类解析器,实现token解密获取分享码
- 配置Pointcut和环绕通知
-
编码实战
/** * 定义 @Pointcut注解表达式, * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这) * 方式二:execution:一般用于指定方法的执行 */ @Pointcut("@annotation(shareCodeCheck)") public void pointCutShareCodeCheck(ShareCodeCheck shareCodeCheck) { } /** * 环绕通知, 围绕着方法执行 * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。 * 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以 * 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个) */ @Around("pointCutShareCodeCheck(shareCodeCheck)") public Object around(ProceedingJoinPoint joinPoint, ShareCodeCheck shareCodeCheck) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //令牌形式校验提交 String requestToken = request.getHeader("share-token"); if (StringUtils.isBlank(requestToken)) { throw new BizException(BizCodeEnum.SHARE_CODE_ILLEGAL); } Claims claims = JwtUtil.checkShareJWT(requestToken); if (claims == null) { log.error("切面配置-分享码校验失败"); return JsonData.buildResult(BizCodeEnum.SHARE_CODE_ILLEGAL); } Long shareId = Long.valueOf(claims.get(JwtUtil.CLAIM_SHARE_KEY) + ""); set(shareId); log.info("环绕通知执行前"); Object obj = joinPoint.proceed(); log.info("环绕通知执行后"); return obj; }
-
接口配置注解
- 查看分享详情接口配置注解
@GetMapping("detail") @ShareCodeCheck public JsonData detail(){ ShareDetailDTO shareDetailDTO = shareService.detail(ShareCodeAspect.get()); return JsonData.buildSuccess(shareDetailDTO); }
网盘分享文件转存+全链路测试实战
查看某个分享文件夹下的文件列表接口实战
- 需求
- 开发查看某个分享链接的子文件列表接口
-
业务逻辑
- 检查分享链接状态
- 查询分享ID是否在分享的文件列表中(需要获取分享文件列表的全部文件夹和子文件夹)
- 分组后获取某个文件夹下面所有的子文件夹
- 根据父文件夹ID获取子文件夹列表
-
编码实战
- 接口开发+自定义注解
@PostMapping("list_share_file") @ShareCodeCheck public JsonData listShareFile(@RequestBody ShareFileQueryReq req){ req.setShareId(ShareCodeAspect.get()); List<AccountFileDTO> accountFileDOList = shareService.listShareFile(req); return JsonData.buildSuccess(accountFileDOList); }
- 业务逻辑
public List<AccountFileDTO> listShareFile(ShareFileQueryReq req) { //1、检查分享链接状态 ShareDO shareDO = checkShareStatus(req.getShareId()); //查询分享ID是否在分享的文件列表中(需要获取分享文件列表的全部文件夹和子文件夹) List<AccountFileDO> accountFileDOList = checkShareFileIdOnStatus(shareDO.getId(), List.of(req.getParentId())); List<AccountFileDTO> accountFileDTOList = SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class); //分组,然后获取某个文件夹下面所有的子文件夹 Map<Long, List<AccountFileDTO>> fileListMap = accountFileDTOList.stream().collect(Collectors.groupingBy(AccountFileDTO::getParentId)); // 根据父文件夹ID获取子文件夹列表 List<AccountFileDTO> childFileList = fileListMap.get(req.getParentId()); if (CollectionUtils.isEmpty(childFileList)) { return List.of(); } return childFileList; }
网盘分享文件转存接口设计和开发实战
简介: 网盘分享文件转存接口设计和开发实战
-
需求
- 选择分享的文件,部分或者全部转存到自己的网盘
- 和文件复制功能类似
-
业务逻辑设计
- 分享链接是否状态准确
- 转存的文件是否是分享链接里面的文件
- 目标文件夹是否是当前用户的
- 获取转存的文件
- 保存需要转存的文件列表(递归子文件)
- 同步更新所有文件的accountId为当前用户的id
- 计算存储空间大小,检查是否足够
- 更新关联对象信息,存储文件映射关系
-
编码实战
- 修改校验逻辑
private ShareDO checkShareStatus(Long shareId) { ShareDO shareDO = shareMapper.selectById(shareId); if (shareDO == null) { log.error("分享链接不存在,分享链接ID:{}", shareId); throw new BizException(BizCodeEnum.SHARE_NOT_EXIST); } //暂时未用,直接物理删除,可以调整 if (ShareStatusEnum.CANCELED.name().equals(shareDO.getShareStatus())) { log.error("分享链接已取消,分享链接ID:{}", shareId); throw new BizException(BizCodeEnum.SHARE_CANCEL); } //判断分享是否过期 if (shareDO.getShareEndTime().getTime() < System.currentTimeMillis()) { log.error("分享链接已过期,分享链接ID:{}", shareId); throw new BizException(BizCodeEnum.SHARE_EXPIRED); } return shareDO; }
- 处理转存逻辑
public void transferShareFile(ShareFileTransferReq req) { // 1、分享链接是否状态准确 checkShareStatus(req.getShareId()); // 2、转存的文件是否是分享链接里面的文件 checkInShareFiles(req.getFileIds(), req.getShareId()); // 3、目标文件夹是否是当前用户的 AccountFileDO currentUserFile = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>().eq("id", req.getParentId()).eq("account_id", req.getAccountId())); if (currentUserFile == null) { throw new BizException(BizCodeEnum.FOLDER_IS_NOT_CURRENT_USER); } //获取转存的文件 List<AccountFileDO> shareFileInfoList = accountFileMapper.selectBatchIds(req.getFileIds()); //保存需要转存的文件列表(递归子文件) List<AccountFileDO> batchTransferFileList = fileService.findBatchCopyFileWithRecur(shareFileInfoList, req.getParentId()); //同步更新所有文件的accountId为当前用户的id batchTransferFileList.forEach(accountFileDO -> accountFileDO.setAccountId(req.getAccountId())); //计算存储空间大小,检查是否足够 if (!fileService.checkAndUpdateCapacity(req.getAccountId(), batchTransferFileList.stream() .map(accountFileDO -> accountFileDO.getFileSize() == null ? 0 : accountFileDO.getFileSize()) .mapToLong(Long::valueOf) .sum())) { throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); } //4、更新关联对象信息,存储文件映射关系 accountFileMapper.insertFileBatch(batchTransferFileList); }
网盘分享文件+转存全链路接口测试实战
简介: 网盘分享转存全链路接口测试实战
-
需求
- 测试创建分享文件链接
- 查看分享文件,进入多层子目录
- 转存子目录里面的部分文件
-
链路测试实战
- Bug修复
//计算存储空间大小,检查是否足够 if(!fileService.checkAndUpdateCapacity(req.getAccountId(),batchTransferFileList.stream() .map(accountFileDO -> accountFileDO.getFileSize() == null ? 0 : accountFileDO.getFileSize()) .mapToLong(Long::valueOf).sum())){ throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); }
- 链路测试【步骤比较长,需要注意连贯性】
- 注册两个账号,账号A,账号B
- 账号A上创建a1目录,a2目录,然后a1里面创建b1目录,a1目录里面上传几张图片,b1目录也上传几张图片
- 账号A创建把a1目录创建分享链接,免密提取
- 账号B登录(登录token记得替换),访问账号A的分享链接,即基本分享信息接口获取分享token
- 账号B登录(登录token记得替换,增加share-token),访问分享的详细文件接口
- 账号B登录(登录token记得替换, 增加share-token),访问分享的详细文件接口的b1目录
- 账号B登录(登录token记得替换, 增加share-token), 转存a1目录到自己的根目录
- 账号B登录(登录token记得替换),查看自己根目录下的文件夹,还有a1目录下的子文件内容
AI网盘回收站设计和编码开发实战
网盘回收站需求背景和业务逻辑讲解
-
业务需求
- 文件删除:
- 用户删除文件或文件夹时,系统并非真正删除数据,而是将其移动到回收站。
- 文件在回收站中保留一定时间,用户可以选择手动清空回收站,释放存储空间。
- 文件恢复:
- 用户可以从回收站中恢复误删的文件或文件夹,恢复到原来的位置。
- 文件删除:
-
相关接口说明
- 获取回收站文件列表:查询回收站中的文件信息。
- 恢复文件:将文件从回收站移动到原路径。
- 清空回收站:删除回收站中的所有文件。
-
功能实现
- 文件删除时,将文件信息插入回收站表,并更新文件表中的状态字段。
- 文件恢复时,更新文件表中的状态字段和路径字段,但是还有前其他业务逻辑
- 文件彻底删除时,从文件关联表中删除文件信息,物理删除,但是file表不去操作
当前用户的个人回收站文件列表开发实战
-
需求
- 用户查看个人回收站的文件
- 注意
- 回收站只显示删除的对应的层级文件,不可进入对应的子文件夹
- 文件删除的时候是逻辑删除,只是修改了del字段状态
-
业务逻辑
- 根据账号查找del=1状态的文件记录
- 如果是文件夹,就只显示文件夹,不显示文件夹里面的文件和文件夹
- 关键:需要提取全部删除文件的ID,然后过滤下,如果某个文件的的父ID在这个文件ID集合里面,则不显示
-
编码实战
@Override public List<AccountFileDTO> listRecycleFiles(Long accountId) { List<AccountFileDO> recycleList = accountFileMapper.selectRecycleList(accountId, null); //如果是文件夹,就只显示文件夹,不显示文件夹里面的文件和文件夹 List<Long> fileIds = recycleList.stream() .map(AccountFileDO::getId) // 提取每个 AccountFileDO 对象的 id .toList(); recycleList = recycleList.stream() .filter(accountFileDO -> !fileIds.contains(accountFileDO.getParentId())) .collect(Collectors.toList()); return SpringBeanUtil.copyProperties(recycleList, AccountFileDTO.class); }
AI网盘彻底删除回收站文件设计和开发实战
- 需求背景
- 回收站文件用于临时存储删除的文件
- 彻底删除回收站文件执行后,则文件不可恢复
- 注意
- 彻底删除文件是指删除关联关系,实际物理存储数据不删除,不清空物理存储内容,方便做分析和后续有人重新上传
-
业务逻辑
- 文件ID数量是否合法
- 判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,然后进行批量删除
- 批量删除回收站文件
-
编码实战
public void delete(FileDelReq fileDelReq) { //文件ID数量是否合法 // 要删除的文件Ids List<Long> fileIdList = fileDelReq.getFileIds(); Long accountId = fileDelReq.getAccountId(); // 查询对应的要删除的文件列表 List<AccountFileDO> records = accountFileMapper.selectRecycleList(accountId, fileIdList); //步骤一:检查是否满足:文件ID数量是否合法 if (records.size() != fileIdList.size()) { log.error("文件ID数量不合法,请检查:accountId={},fileIdList={}", accountId, fileIdList); throw new BizException(BizCodeEnum.FILE_DEL_BATCH_ILLEGAL); } List<AccountFileDO> allRecords = new ArrayList<>(); // 步骤二:判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,然后进行批量删除 findAllAccountFileDOWithRecur(allRecords, records, false); List<Long> allFileList = allRecords.stream().map(AccountFileDO::getId).collect(Collectors.toList()); //步骤三:批量删除回收站文件,不清空物理存储内容,方便做分析和后续有人重新上传 accountFileMapper.deleteRecycleFile(allFileList); }
回收站查看和彻底删除回收站接口测试
-
需求
- 测试查看我的回收站列表
- 测试彻底删除回收站文件接口
-
测试实战
网盘回收站还原文件或者文件夹设计
-
需求
- 用户可以从回收站中恢复误删的文件或文件夹,恢复到原来的位置。
- 支持批量恢复操作,提高用户操作效率
- 业务逻辑有哪些注意事项考虑?
-
业务逻辑
- 检查是否满足:文件ID数量是否合法
- 还原前的父文件和当前文件夹是否有重复名称的文件和文件夹
- 判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,才可以进行批量还原
- 检查空间是否足够
- 批量还原文件
-
编码实战
@Transactional(rollbackFor = Exception.class) public void restoreFile(FileRecycleReq req) { //判断文件ID和用户ID是否合法 List<Long> fileIds = req.getFileIds(); List<AccountFileDO> accountFileList = accountFileMapper.selectRecycleList(req.getAccountId(), req.getFileIds()); if (fileIds.size() != accountFileList.size()) { log.error("还原文件参数错误"); throw new BizException(BizCodeEnum.RECYCLE_FILE_ILLEGAL); } //还原前的父文件和当前文件夹是否有重复名称的文件和文件夹 accountFileList.forEach(accountFileDO -> { Long selectCount = fileServiceImpl.processFileNameDuplicate(accountFileDO); //更新文件名 if (selectCount > 0 && accountFileMapper.updateFileById(accountFileDO) <= 0) { throw new RuntimeException("文件还原失败"); } }); //判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,然后进行批量还原 List<AccountFileDO> allRecords = new ArrayList<>(); findAllAccountFileDOWithRecur(allRecords, accountFileList, false); List<Long> allFileIdList = allRecords.stream().map(AccountFileDO::getId).collect(Collectors.toList()); //检查存储空间是否足够 if (!fileService.checkAndUpdateCapacity(req.getAccountId(),allRecords.stream() .map(accountFileDO -> accountFileDO.getFileSize() == null ? 0 : accountFileDO.getFileSize()) .mapToLong(Long::valueOf) .sum())) { throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH); } //步骤四:批量还原文件 accountFileMapper.restoreFile(allFileIdList); }
网盘回收站还原文件接口测试实战
-
需求
- 测试删除文件到回收站后进行还原
- 删除的文件需要包括多个子文件夹
-
测试实战
AI网盘文件搜索和下载功能开发实战
网盘搜索功能需求介绍和接口设计说明
-
需求
- 用户可以根据文件名称,搜索网盘相关的文件
- 技术栈采用Mysql, 因此采用普通的Like查询
- 学过ElasticSearch的同学也可以将Mysql数据同步到ES中完成更多搜索
- 进阶内容可以让AI读取数据库,完成智搜问答
-
编码实战
@Override public List<AccountFileDTO> searchList(Long accountId, String search) { List<AccountFileDO> accountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() .eq("account_id", accountId) .like("file_name", search) .orderByDesc("is_dir") .orderByDesc("gmt_create").last("limit 30")); return SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class); }
网盘下载功能需求介绍和多方案思考选择
-
需求
- 用户能够通过网盘下载存储在网盘中的文件。
- 支持常见文件格式的下载,包括文档、图片、视频、音频等
- 思考
- 非文件夹下载如何操作?
- 文件夹下载如何操作?
-
文件下载可以通过前端直接请求 MinIO 或通过后端请求 MinIO 实现
-
前端直接请求 MinIO 下载(预签名 URL 方式,和上传一样)
-
后端生成一个带有时间限制和权限的预签名 URL(Presigned URL)
-
前端通过该 URL 直接访问 MinIO 下载文件。
-
优点
-
文件传输不经过后端服务器,利用 MinIO 的带宽和并发能力,适合大文件(如视频、镜像)。
-
前端可并行下载多个文件,无需后端干预。
-
后端只需生成预签名 URL,无需处理文件流,降低 CPU 和内存消耗
-
-
缺点
-
预签名 URL 泄露:如果 URL 被恶意截获,可能被未授权用户访问,缩短 URL 有效期(如 5 分钟)
-
部分浏览器可能限制直接下载大文件(如内存不足),需前端分片下载或断点续传
-
-
-
后端代理下载(文件流中转)
- 客户端请求后端 API,后端从 MinIO 读取文件流
- 通过 HTTP Response 将文件流传给客户端。
- 优点
- 可实时压缩文件(如 ZIP)、添加水印或加密。
- 可动态拼接文件(如分片存储的文件合并)
- 可在下载前校验用户角色、文件归属、付费状态等复杂逻辑。
- 可记录所有下载日志,方便审计。
- 缺点
- 大文件下载会占用后端带宽和连接资源,可能拖慢其他 API。
- 需处理文件流中断、超时、断点续传等问题。
- 高并发下载时需横向扩展后端服务器,增加运维成本。
-
-
最终方案
- 非文件夹下载方案
- 采用前端直接下载方式,后端请求minio获取预签名地址给前端,前端进行下载,适合单个文件下载
- 文件夹下载方案(拓展思路即可)
- 前端处理多个文件会有问题,且需要从MinIO获取每个文件的预签名URL,然后在前端打包,这不太现实
- 也可以是用户请求打包多个文件,后端从MinIO获取这些文件,压缩成一个ZIP,提供给用户下载 (资源占用多)
- 我们客户端是使用浏览器进行,并非自研客户端,如果是自研客户端则可以实现更多功能
- 需要客户端原生开发,包括文件夹处理等,不同系统的客户端也不一样,成本大
- 比如断点续传、直接到存储引擎下载文件夹等功能都有实现,类似百度网盘
- 客户端下载文件夹,可以获取文件夹里面全部的文件的关联关系
- 根据情况一层层文件夹下载和处理,就不用经过后端服务器压缩再返回了
- 因此Web端很少实现文件夹下载,限制很多
- 关于下载断点续传
- 在文件下载过程中,能够在下载中断后从上次停止的位置继续下载的技术
- 核心在于能够记录下载进度,并在重新开始下载时,从上次中断的位置继续,而不是从头开始
- 依赖于HTTP协议中的
Range
请求头,当浏览器发起下载请求时,可以通过Range
头指定从文件的某个字节开始下载 - 用户存在多个浏览器,版本问题、厂商问题等 导致不支持,因此web端不做这个功能
- 非文件夹下载方案
网盘多文件下载功能设计和开发实战
- 需求
- 开发文件下载功能,前端请求后端,后端请求文件存储获取下载地址
- 前端通过临时签名地址,直接从文件存储引擎进行下载
- 前端传递需要下载的文件ID,可以是多个非文件夹类型的文件ID
-
编码实战(可以支持单个或者多个文件下载)
@Override public List<DownloadUrlDTO> getDownloadUrl(FileDownloadReq req) { //获得要下载的用户文件对象 List<AccountFileDO> accountFileDOList = accountFileMapper.selectList( new QueryWrapper<AccountFileDO>() .eq("account_id", req.getAccountId()) .eq("is_dir", FolderFlagEnum.NO.getCode()) .in("id", req.getFileIds())); ArrayList<DownloadUrlDTO> downloadUrls = new ArrayList<>(); for (AccountFileDO accountFileDO : accountFileDOList) { String objectKey = fileMapper.selectOne(new QueryWrapper<FileDO>().eq("id", accountFileDO.getFileId())).getObjectKey(); //获得下载路径 String downloadUrl = fileStoreEngine.getDownloadUrl( minioConfig.getBucketName(), objectKey, minioConfig.getPRE_SIGN_URL_EXPIRE(), TimeUnit.MILLISECONDS ); DownloadUrlDTO downloadUrlsDTO = new DownloadUrlDTO(accountFileDO.getFileName(), downloadUrl); downloadUrls.add(downloadUrlsDTO); } //查询数据库找到对应的 objectKey return downloadUrls; }
网盘下载功能相关链路测试实战和规划
- 需求
- 测试AI云网盘文件下载功能
- 当前阶段网盘业务开发介绍,接下去进入AI相关板块提前
- 比较多同学需要AI大模型知识,先逐步更新,前端和后端联调等AI相关模块更新后进行
- BugFix还有非核心接口等,后续会提供对应的代码
- 测试实战
下一个互联网10年-LLM大模型新机会
给兄弟们学-AI大模型板块课程的建议
-
AI大模型板块课程说明
-
千言万语给兄弟们说下
-
不要被时代抛弃,上一个互联网10年2014到2024;2025就是就是下一个互联网10年的起点
-
传统Java后端、前端、测试开发 已经逐步被淘汰了,切勿停留在这个阶段
-
很多知识不是自己不学就行,要提升更高的认知,最直接的:不学就被行业的发展淘汰
- 很多同学没赶上2017到2022年的第一波互联网红利,现在25年又是一个#互联网时代新起点
- 各个行业都需要接入大模型,开发对应的智能体;
- 因为市场人员缺少太多,企业招聘不到人员,就会降低学历提高待遇,这个就是机会
- 大专以上学历+后端/前端、测试能力 ,就可以学!!
-
-
学习我们小滴课堂的兄弟们的基本画像说明
- 多年前端、后端Java、测试、运维等方向工作为主,没有相关人工智能、机器学习的基础,概念不懂
- 新人入行,包括大专、本科学历的大学生;其他行业转行过来的兄弟们
-
D哥的目标
- 让大家可以快速上手AI大模型相关基础,包括极速上手
- 从研发工程师角度,让大家通俗易懂理解LLM大模型的相关概念,避免太专业的概念导致不懂
-
认识LLM-为什么可以回答我们的问题?
-
什么是LLM
- 是Large Language Model 大语言模型的首字母简写
- LLM是基于深度学习的自然语言处理(NLP)模型,通过海量文本数据训练,能够理解、生成和推理人类语言。
- 其核心能力包括:
- 文本生成(对话、代码、故事)
- 语义理解(问答、摘要、分类)
- 推理能力(逻辑判断、数学计算)
-
为什么大模型可以根据我们的输入回答对应的问题?
-
核心能力:理解自然语言、生成连贯文本、完成特定任务
-
几个关键点:训练数据、模式识别、概率预测、上下文理解
-
Java类比:类似大家的IDEA/VSCode工具智能代码补全工具,但能力扩展到所有自然语言领域
-
Java程序员熟悉的方式理解LLM的文本补全能力:
-
好比一个拥有超强记忆力的代码补全工具(类似IDE的智能提示,但能力放大1000倍)
-
海量知识库(相当于训练数据)
- 模型"阅读"过整个互联网的文本(就像你看了JDK所有源码+StackOverflow+GitHub)
- 示例理解:当看到
public static void main(
会自动补全参数String[] args)
-
模式识别专家(类似编译器语法分析)
- 建立了词语/句子的关联规则库
- 示例:看到"北京是中国的",后面大概率接"首都"
-
概率预测器(类似条件判断的加强版)
// 伪代码示意(实际使用概率分布) String predictNextWord(String context) { Map<String, Double> probabilities = model.calculate(context); return selectHighestProbability(probabilities); }
-
上下文关联引擎(类似程序的作用域链)
- 能记住前文关键信息(变量声明、方法参数等)
- 示例:前文提到"用Java实现快速排序",后面自动关联
Comparable
接口
-
-
场景:补全代码注释
/ 用户输入: /** * 计算阶乘 * @param n */ public static long factorial(int n) { // 模型可能补全: if(n <= 1) return 1; return n * factorial(n-1); }
- 为什么能补充合理内容?
- 模式匹配:像正则表达式匹配,但处理语义层面
- 概率择优:总选择可能性最高的选项(类似优先修复编译警告)
- 知识蒸馏:从训练数据中提炼出常识规则(如国家首都对应关系)
Java开发者启示:可以把LLM视为一个
超级StringProcessor
,其process()
方法基于统计规律而非硬编码规则,通过分析上下文token序列生成最合理的后续内容。 - 为什么能补充合理内容?
-
简单的话解释,比如“猜测下一个最可能的词”并不断重复这个过程。
-
大模型发展历程和业内主流LLM讲解
-
发展历程(重点时间线)
阶段 时间范围 核心突破 代表模型 早期探索期 2015-2017 RNN/LSTM架构 ELMo Transformer革命 2017 自注意力机制诞生 Transformer论文 预训练时代 2018-2020 预训练+微调范式 BERT, GPT-2 大模型爆发期 2020-2022 千亿参数规模 GPT-3, T5 多模态时代 2023-至今 文本+图像+代码融合 GPT-4, PaLM - 早期探索(2017年前)-习惯称为人工智障
- 2013-2017:RNN/LSTM 时代
- 模型:Word2Vec、LSTM、Seq2Seq
- 局限性:难以处理长文本依赖,训练效率低
- 2017:Transformer 革命
- 里程碑论文 《Attention Is All You Need》(Google)提出 自注意力机制,解决长距离依赖问题,现代LLM基础。
- 2013-2017:RNN/LSTM 时代
- 预训练模型崛起(2018-2020)
- 2018:BERT(Bidirectional Encoder)
- 技术突破:双向上下文建模,掩码语言模型(MLM)。
- 应用:文本分类、问答系统。
- 2018:GPT-1(Generative Pre-trained Transformer)
- 技术突破:单向自回归生成(Decoder-only)。
- 应用:文本生成、续写任务。
- 2019:GPT-2
- 参数规模:15亿,展示 零样本学习(Zero-shot)能力。
- 争议:因生成虚假新闻风险,OpenAI暂未开源完整模型。
- 2018:BERT(Bidirectional Encoder)
- 千亿参数时代(2020-2022)
- 2020:GPT-3
- 参数规模:1750亿,Few-shot 学习 能力显著提升。
- 标志性应用:代码生成(Codex)、对话系统(ChatGPT前身)。
- 2022:Meta LLaMA
- 开源模型(7B-65B参数),推动社区生态发展。
- 2020:GPT-3
- 多模态与垂直化(2023至今)
- GPT-4:支持图像输入,逻辑推理能力显著增强。
- 开源社区爆发:Alpaca、Vicuna、Falcon 等轻量级模型涌现。
- 行业应用:医疗、法律、编程等垂直领域定制化LLM
- GPT-4:支持图像输入,逻辑推理能力显著增强。
- 早期探索(2017年前)-习惯称为人工智障
-
国内外主流大模型(每个厂商都有多个大模型产品)
- 国际主流模型
- GPT系列(OpenAI):当前最先进的商业闭源模型
- LLaMA(Meta):开源可商用的先进模型
- Gemini(谷歌):多任务协同、搜索整合
- 国内主流模型
- 文心一言(百度):中文理解强、知识图谱融合、多模态生成
- 通义千问(阿里云):电商场景优化、代码生成强、多模态交互
- Kimi Chat(月之暗面):长上下文、数据分析可视化、文档解析
- ChatGLM(智谱):开源可商用、中英双语平衡、轻量化部署
- DeepSeek(深度求索):国货之光,低成本、性能强大的推理大模型
- 国际主流模型
AI大模型常见的四大分类维度你知多少
-
需求
- 前面介绍了不同的厂商的大模型,每个厂商都有多个大模型产品,有什么分类规则吗?
- 注意:由于有比较多人工智能、机器学习、算法等领域的专业名称,不需要全部都了解,关键的课程接下去会讲
-
LLM大语言模型的常见分类维度
- 模型架构维度(类比Java框架设计)
类型 典型代表 特点 Java类比 自回归模型(单向) GPT系列 从左到右生成文本 类似 StringBuilder
追加操作自编码模型(双向) BERT系列 理解上下文语义 类似 Map
结构双向索引混合架构 T5 编码器-解码器结构 类似Spring MVC分层架构 - 功能用途维度(对应开发需求类型)
类型 典型场景 Java开发示例 代表模型 指令大模型 需求实现 根据描述生成Controller代码 GPT-4, Claude 推理大模型 问题排查 分析OOM异常原因 Code Llama, DeepSeek - 参数规模维度(类比系统复杂度)
类型 参数量级 特点 Java工程类比 基础模型 <1B 快速响应,适合简单任务 类似工具类Utils 标准模型 1B-100B 通用能力强 类似Spring框架 超大模型 >100B 复杂推理能力 类似分布式系统集群 - 部署方式维度(类比系统架构)
型 特点 适用场景 Java类比 云端API 按需调用 快速接入 类似RESTful服务调用 本地部署 数据安全 敏感业务系统 类似内网服务部署 -
功能用途维度思考大模型选择
-
推理大模型(Reasoning Models)
- 特点:擅长逻辑推理、数学计算、代码调试
- 典型应用:Java异常堆栈分析、系统设计模式选择建议、算法复杂度优化
// 类比Java中的复杂业务逻辑处理 public class MathSolver { public static void main(String[] args) { String problem = "已知x+y=15,x-y=3,求x和y的值"; // 模型需要执行类似下面的推理过程: // 1. 建立方程组 // 2. 联立求解 // 3. 验证结果 } }
- 代表模型:DeepSeek-R1(深度求索)、Google Minerva、GPT-4(OpenAI)
-
指令大模型(Instruction Models)
- 特点:擅长理解复杂指令,完成多步骤任务
- 典型应用
- 根据需求生成API文档、将用户故事转化为技术方案、自动化测试用例生成
// 示例:指令模型处理用户需求 用户输入: "我需要一个Java方法,接收用户ID列表,返回这些用户的订单总数" 模型输出: public int getTotalOrders(List<Long> userIds) { return userRepository.findAllById(userIds) .stream() .mapToInt(user -> orderService.getOrderCount(user)) .sum(); }
- 代表模型:GPT-3、Claude 3、阿里云的通义千问
-
推理大模型VS指令大模型对比
维度 推理大模型 指令大模型 核心能力 逻辑推理、数学计算 理解复杂指令 输入特点 开放性问题(类似异常堆栈分析) 明确的操作指示(类似Java方法调用) 典型输入 "请解释Java并发中的ABA问题" "请用Java8编写一个线程安全的缓存类" 输出特点 分步骤推导过程 完整可执行代码 适用场景 系统故障排查、性能优化 需求实现、文档生成 评估指标 答案准确性 指令遵循度
-
LLM场景面试题-如何根据业务选择大模型
-
互联网公司AI大模型面试题
- 面试官:公司里面基于AI大模型进行开发,指令大模型和推理大模型,如何选择?
-
回答建议
- 业务应用中选择大模型时,需根据具体场景的核心需求、任务类型和数据特点多个考虑
- 根据业务场景和成本衡量,也可以选择不同的大模型或者组合一起使用
维度 指令大模型 推理大模型 核心能力 理解并执行用户指令,生成自然语言响应 解决复杂逻辑、数学、多步骤推理问题 适用任务 对话交互、内容生成、简单任务自动化 数学计算、代码生成、科学问题求解 输出特点 流畅、符合人类表达习惯 严谨、依赖逻辑链或符号推导 典型错误类型 可能偏离指令或生成不相关内容 逻辑漏洞、计算错误或步骤缺失 资源消耗 通常更轻量(可部署较小参数模型) 需要更大参数量支持复杂推理
-
案例应用说明
-
金融行业
-
典型需求:合规报告生成、风险评估、反欺诈检测
-
模型选择决策
-
案例代码参考
// 使用指令模型生成合规检查代码 String prompt = """ [指令执行] 生成Java方法: - 输入:交易金额、客户风险等级 - 输出:是否触发反洗钱检查 - 规则:金额>10万或高风险客户必检 """; // 使用推理模型分析交易模式 String analysisPrompt = """ 检测以下交易序列中的异常模式: 2023-01-05 转账50万 → 2023-01-06 购黄金 → 2023-01-07 外汇兑换 请分步骤说明可疑点 """;
-
-
医疗行业
-
典型需求:病历摘要生成、诊断建议、药物相互作用检查
-
模型组合策略
- 指令模型处理结构化任务:
- 将问诊对话转为标准病历格式
- 生成检查报告模板
- 推理模型处理复杂场景:
- 鉴别相似病症
- 解释检测指标关联性
- 指令模型处理结构化任务:
-
案例代码参考
// 类似伪代码参考 public class MedicalAgent { @Resource private InstructionModel reportGenerator; // 指令模型 @Resource private ReasoningModel diagnosisEngine; // 推理模型 public ProcessResult process(PatientCase input) { // 第一阶段:结构化处理 MedicalRecord record = reportGenerator.generateRecord(input); // 第二阶段:智能分析 DiagnosisResult result = diagnosisEngine.analyze(record); return new ProcessResult(record, result); } }
-
-
优先选择指令大模型的场景
- 客户服务:
- 任务:自动回复咨询、处理标准化请求(如订单查询)
- 优势:快速生成自然对话,理解用户意图
- 内容生成:
- 任务:营销文案撰写、社交媒体内容生成
- 优势:保证语言流畅性和创意性
- 客户服务:
-
优先选择推理大模型的场景
- 金融分析:
- 任务:财务报表解读、投资组合风险推导
- 案例:通过链式推理分析“某公司负债率上升对股价的影响路径”
- 科研与工程:
- 任务:数学建模、实验数据分析
- 示例:推导“气候变化对区域降雨量的非线性影响方程”
- 金融分析:
-
-
选择不同功能类型的大模型注意事项
-
指令大模型的局限:
- 对隐含逻辑任务(如“预测A→B→C的连锁反应”)可能生成看似合理但缺乏严谨推导的答案。
-
推理大模型的陷阱:
- 需验证中间步骤(如数学符号是否正确),避免“自信的错误”(如GPT-4可能错误使用公式但语气肯定)。
-
混合部署建议:
- 前端用指令模型交互,后端用推理模型处理复杂任务(例如客服系统自动转接技术问题到推理引擎)。
-
选择模型的核心原则:
- 任务复杂度:简单指令→通用模型,深度推理→专用模型
- 容错成本:高风险场景(如医疗、金融)优先选择可解释性强的推理模型
- ROI评估:平衡开发成本(微调/提示工程)与业务收益
-
简单案例方便记忆参考:
-
电商公司:用指令模型处理客服对话,用推理模型分析用户行为数据。
-
量化基金:用推理模型推导交易策略,用指令模型生成合规报告。
-
-
LLM大模型核心概念和面试题
LLM大模型常见核心术语认识第一季
-
需求
- 人工智能、大语言模型、机器学习等很多专业术语,没接触过的同学难理解,看博文都懵逼
- 我们这边给大家快速补充基础的概念和术语,通过对比的方式,方便大家看博文资料和课程都方便
- 涉及到比需要更加深入的知识点,我们就会单独的拎一集出来讲解
- 当前阶段就把课程讲的先理解,然后逐步进阶
-
常见术语
-
Token(标记)
- 相当于自然语言处理的"字节",是文本处理的最小单位(可能是单词/字/标点)
- Java类比:类似
StringTokenizer
分割后的元素
// 输入文本:"public static void main" // Token化结果:["public", "static", "void", "main"]
-
Transformer(变换器架构)
- 通俗解释:当前最主流的大模型基础架构,通过自注意力机制理解上下文
- Java类比:相当于Java中的
Spring框架
——提供了一套处理文本的通用组件,开发者无需从头写网络结构。
-
自注意力机制(Self-Attention)
- 让模型自动分析文本中每个词与其他词的关系, 类似
Map<Key, Value>
的智能索引 - 通俗解释:类似阅读代码时,IDE高亮显示
变量A
,在哪些位置被调用——模型通过权重标记词与词的相关性。
// 示例:模型会计算 "猫" 和 "抓" 的关联权重 double 猫_抓_权重 = 0.92;
- 让模型自动分析文本中每个词与其他词的关系, 类似
-
损失函数(Loss Function)
- 定义:衡量模型预测结果与正确答案的差距, 模型的"错误检测器",指导参数调整方向
- 通俗解释:类似单元测试的断言——模型通过不断减少损失值来“通过测试”。
// 训练过程伪代码 while (loss > threshold) { adjustParameters(); // 类似修改代码通过测试 loss = calculateLoss(); // 类似运行JUnit测试 }
-
Prompt工程
- 定义:通过设计输入文本(提示词)引导模型生成预期输出。
- 通俗解释:类似写SQL查询时调整WHERE条件——不同的提问方式会得到不同结果
-- 坏Prompt: SELECT * FROM products; -- 好Prompt: SELECT name, price FROM products WHERE category='电子产品' ORDER BY price DESC LIMIT 10;
// 低效提示: "写个排序方法" → 可能得到冒泡排序 // 高效提示: """ 用Java实现满足以下要求的快速排序: 1. 支持泛型 2. 处理空值 3. 包含单元测试 """
-
LLM大模型常见核心术语认识第二季
-
继续进阶常见概念
-
Temperature(温度参数)
- 控模型生成文本的随机程度。值越大,回复内容越赋有多样性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7
- 参数对比
温度值 输出特点 Java类比 0.2 保守准确 final
常量0.8 富有创意 Random
随机数// 低温度生成(确定性高) model.setTemperature(0.1); // 总是返回相同代码 // 高温度生成(多样性好) model.setTemperature(0.7); // 可能给出多种设计模式实现
- 找个LLM进行测试
我今天被公司解雇了,很难过
0.2和0.7温度的区别
-
预训练(Pre-training)
- 定义:在大规模通用文本上训练模型,学习语言通用规律。
- 通俗解释::类似让新员工先学习公司技术栈规范(如Java编码规范),打好基础。
-
Fine-tuning(微调)
- 在预训练模型基础上进行领域适配,用特定领域数据调整模型,就像给汽车加装越野套件,
- Java类比:在Spring Boot Starter基础上自定义配置
// 预训练模型 = 标准JDK // 微调过程 = 添加Apache Commons工具库
-
RLHF(人类反馈强化学习)
- 通俗解释:通过人类评分优化模型输出的"AI教练系统"
- 训练流程
-
模型蒸馏(Knowledge Distillation)
- 定义:
- 将大模型的知识“压缩”到小模型中。
- 将大语言模型(教师模型)的知识高效迁移到小型模型(学生模型)的技术,保持性能的同时降低计算成本和部署难度
- 通俗解释:类似将资深架构师的经验总结成《Java开发手册》供新人学习,小模型继承大模型能力,但体积更小推理更快。
- 定义:
-
大力出奇迹-LLM大模型里面的参数是什么
-
疑惑点:大语言模型里面有很多个细化版本,里面有很多B,这个是什么意思?
-
通义千问大模型:通义千问2.5-7B、通义千问-QwQ-32B、通义千问2.5-72B
-
脸书旗下的Llama大模型:Llama2-7B、Llama2-13B、Llama3.3-70B-Instruct
-
DeepSeek大模型:DeepSeek-R1-Distill-Qwen-7B、DeepSeek-R1-Distill-Qwen-14B、DeepSeek-R1-Distill-Llama-70B
-
回答
- 在大模型的语境中,“b”通常代表“Billion”,即“十亿”,这是用来表示模型训练中所使用参数的数量的单位。
- 例如,当我们说某个大模型有“175B”个参数时,意味着该模型拥有1750亿个参数。
- 参数的数量是衡量模型复杂性和能力的一个重要指标,一般来说,参数越多,模型的训练就越复杂,其能力也往往越强。
- 在这个上下文中,“b”作为“Billion”的缩写,是用来量化大模型规模的关键指标之一。
// 神经网络中的参数结构示例 class NeuralLayer { double[][] weights; // 权重矩阵(核心参数) double[] biases; // 偏置参数 }
-
-
参数(Parameters)的直观理解
-
类比编程:
- 可理解为代码中的 “变量”,但LLM的参数是模型通过海量数据自动学习得到的权重(Weights)
// 传统代码中的变量(人工定义规则) if (input.contains("你好")) { response = "你好!"; } // LLM参数:由数据自动学习的权重(如0.73表示“你好”与“问候”的关联强度) double 你好_权重 = 0.73;
-
参数的作用
- 存储知识
- 词汇关系:参数编码单词之间的关联(如“猫”与“动物”的权重)。
- 语法规则:参数隐式学习语法结构(如主谓宾顺序)。
- 领域知识:通过训练数据学习专业知识(如医疗术语)。
- 计算输出
- 前向传播:输入文本通过参数矩阵逐层计算,最终生成概率分布
- 存储知识
-
参数规模的演进
模型 参数量 特点(对比Java工程) 对比老王的工作年限 GPT-1 1.17亿 相当于一个中型Java项目的代码行数 大四实习生 GPT-3 1750亿 相当于整个Maven中央库所有jar包的代码总量 工作5年的高级工程师 DeepSeek-R1-671b 6710亿 远超全球所有开源Java项目的代码规模总和 工作100年的不死工程师 -
参数量 vs 模型能力
- 正向关系:参数越多,模型记忆和推理能力越强(类似大型代码库功能更全面)。
- 边际效应:超过一定规模后,性能提升变缓(需权衡成本与收益)。
- 训练成本:千亿参数需数千块GPU(如GPT-3训练成本约460万美元)。
- 推理延迟:参数越多,单次请求计算量越大(代码量越多,启动和调试越慢,优化点越多)
-
钱花的明白-大模型里面的Token如何计算
简介: 钱花的明白-大模型里面的Token如何计算
- 需求
- 很多同学看到大模型的文档里有token,好奇这个是做啥的,和JWT的token啥区别?
- 包括很多在线的LLM大模型接口里面,按照token进行收费的
- 什么是LLM里面的token
- Token 是文本的基本单位,用于将文本分解为模型能够处理的最小单元
- 在大语言模型中,通过对输入文本进行 Token 化,将文本转换为模型可以理解的数字表示形式,以便模型进行处理和生成输出。
- 作用
- Token是文本处理的基本单元,大模型运行的计算成本非常高,按token计费是为了更精准控制资源使用
- 不是直接用字或单词来计量,而是文本经过模型分词器切分后,Token ≠ 单词 ≠ 字符
- 输入输出限制:模型最大处理长度(如GPT-4最多32k tokens)
- 费用计算:API调用按Token数量计费(如$0.06/1k tokens)
- 例如
- 使用 Hugging Face 的 BERT 分词器对英文文本
"Hello, world! This is a test sentence."
进行分词, - 得到的 Tokens 列表为
['hello', ',', 'world', '!', 'this', 'is', 'a', 'test', 'sentence', '.']
。 - 统计数量 :计算 Tokens 列表的长度,即为 Tokens 的数量
- 使用 Hugging Face 的 BERT 分词器对英文文本
- 一般的大模型计费方式
- 你输入的内容+AI输出的内容,都要花钱,和律师按照小时收费一样
- 好比 你问很多,AI回答很少,也是要花钱的
- 在线计算方式
- Token的长度并不固定它长度取决于具体模型的Tokenizer(分词器)规则,不同模型的token计算方式有所差异
- Hugging Face Tokenizer【需要科学上网】
- OpenAI官方工具:Tokenizer Playground【需要科学上网】
- 国内在线工具【直接访问】
- 建议:
- 业务场景选择,如果面向国内用户,中文为主的业务建议选择国内的大模型,英文的考虑海外的,Token计算不一样
- 文字长度与 Token 数量的换算
- 中文 :通常情况下,1 个中文字符 ≈ 0.6 个 Token。每百万 Token 大约相当于 70-100 万个汉字。
- 例如
- 中文文本
"你好,世界!"
按字符分割,每个字符独立为 Token,Token 数量为 6。 - “你好,我是通义千问”会被转换成['你好', ',', '我是', '通', '义', '千', '问']。
- 中文文本
- 例如
- 英文 :一般情况下,1 个英文字符 ≈ 0.3 个 Token。每百万 Token 大约相当于 50-75 万个单词。
- 例如
- 英文文本
"Hello, world!"
按单词分割,Token 数量为 4。 - "Nice to meet you."会被转换成['Nice', ' to', ' meet', ' you', '.']
- 英文文本
- 例如
- 中文 :通常情况下,1 个中文字符 ≈ 0.6 个 Token。每百万 Token 大约相当于 70-100 万个汉字。
手把手教你部署本地的AI大模型+DeepSeek
在线LLM接口API和私有化部署选择和思考
-
需求
- LLM大语言模型分在线API调用和私有化部署,两个方式都需要学
- 私有化部署大模型后,使用方式也都是调用接口
- 不同企业、经费、数据安全和项目领域也决定如何选择
-
关键差异点对比
维度 | 在线API调用 | 私有化部署 | Java技术栈类比 |
---|---|---|---|
初期成本 | 低(按需付费) | 高(硬件+授权) | 云主机 vs 自建机房 |
数据安全 | 数据出域风险 | 完全可控 | HTTP调用 vs 内网RPC |
响应延迟 | 50-500ms | <50ms(内网) | Redis本地缓存 vs 远程缓存 |
模型定制 | 有限微调 | 全量修改 | SaaS服务 vs 自研框架 |
运维复杂度 | 无需维护 | 需专业团队 | 使用云服务 vs 维护K8s集群 |
突发流量 | 弹性扩展 | 需预留资源 | 云函数 vs 固定线程池 |
更新频率 | 自动升级 | 手动更新 | SpringCloud Config vs 本地配置 |
-
面试官:说下项目为什么要私有化部署LLM
- 数据安全与隐私保护
- 避免数据泄露风险 :企业数据本地存储,无需上传云端,降低敏感信息泄露风险,如金融客户交易记录等。
- 最关键
- 私有化部署允许企业将模型和数据完全掌控在本地,避免了将数据上传至云端进行处理时可能带来的数据泄露风险
- 例如
- 金融机构处理的客户数据极为敏感,包括银行交易记录、信用评分、投资组合等,
- 如果使用云端 LLM 提供的服务,客户输入的所有内容都可能被外部服务器存储和分析,
- 即便服务提供商声明不会保存数据,依然存在潜在泄露的风险,本地私有部署确保数据始终在企业内部环境运行
- 性能与响应速度
- 低延迟响应 :本地部署模型无需网络传输数据,响应时间更短,适用于实时性要求高的场景,如智能客服系统。
- 可定制化与灵活性
- 满足个性需求 :企业可依据自身业务特点,对模型架构、参数等进行定制,如电商企业针对商品和用户行为数据定制 LLM。
- 成本效益
- 长期成本优化 :初期虽有硬件投资,但长期可避免持续增长的云服务费用,降低运营成本。
- 充分利用资源 :企业可利用现有硬件资源,提高资源利用率,进一步降低成本。
- 减少依赖与控制技术栈
- 降低对供应商的依赖 :企业可自主选择技术、基础设施和工具,减少对通用模型的依赖。
- 掌握技术主动权 :企业可自主进行技术创新和优化,推动人工智能技术在企业内部的发展
- 数据安全与隐私保护
-
记住关键点:适合私有化部署的三大特征
-
数据敏感性高:处理个人隐私/商业机密/国家秘密,如:银行反洗钱分析、医疗影像诊断
-
业务定制需求强:需要深度融合行业知识,如:法律合同审查、工业缺陷检测
-
规模化应用场景:日均调用量超50万次,如:智能客服中心、电商推荐系统
-
前期使用在线大模型的API,做大做强就是私有化部署大模型
-
基于在线大语言模型API调用操作实战
-
知识点
- 实操在线的API大语言模型调用,厂商很多,多数大同小异
- 包括后续框架整合也是,请求协议基本和OpenAI一样, 毕竟龙头老大,也方便迁移
- 由于国内网络访问限制,OpenAI应用的开发也可以直接换国内的大模型,开发一样
-
OpenAI 提供的SDK 来调用大模型
-
基于 openai 的模型进行开发,只需要将
base_url
和api_key
替换成 国内大模型的配置,可无缝将服务迁移至对应的大模型- 举例月之暗面的大模型例子
from openai import OpenAI client = OpenAI( api_key="MOONSHOT_API_KEY", # <--在这里将 MOONSHOT_API_KEY 替换为你从 Kimi 开放平台申请的 API Key base_url="https://api.moonshot.cn/v1", # <-- 将 base_url 从 https://api.openai.com/v1 替换为 https://api.moonshot.cn/v1 )
-
在线LLM接口调用实操测试(有些有免费额度,用完就需要花钱啦,老铁们)
-
通义千问(阿里云)
-
DeepSeek(深度求索)
-
Kimi Chat(月之暗面)
-
私有化部署DeepSeek大模型的硬件配置
-
需求
- 大模型有很多都提供了开源版本,即可以实现私有化部署
- 那作为工程师的你,知道私有化部署大模型有哪些方式
- 国运级别的大模型DeepSeek不同参数需要的资源和适合场景
-
开源大模型常见的私有化部署方式
-
源码部署
- 依赖比较多,需要对应的环境比较多,且不同版本系统兼容性比较多,类似源码安装K8S
- 常见的源码部署主要有 transformers、vLLM、llama.cpp 等方式,使用算力平台操作比较多
- 需要Transformers、Python、 Pytorch等环境 ,后续讲算力平台的时候操作
-
应用部署
-
基于 Ollama 部署
- Ollama 是一个开源的 AI 模型部署工具,帮助用户在本地环境或云端部署和管理大型语言模型
- 官网:https://ollama.com/
- 部署过程相对简单,支持Win、Mac、Linux
-
LM Studio部署
- 官网:https://lmstudio.ai/
- 部署过程相对简单,支持Win、Mac、Linux
-
-
-
本地部署 DeepSeek 硬件配置
- 轻量级模型(1.5B-8B)
型版本 DeepSeek-R1-1.5B DeepSeek-R1-7B DeepSeek-R1-8B 硬件配置 - CPU 4核+ 8核+ 8核+ - 内存 8GB 16GB 16GB - 存储 256GB (模型占用2GB) 256GB (模型占用5GB) 256GB (含量化缓存文件) - 显卡 非必需(纯CPU推理) RTX 3070/4060(8GB显存) RTX 3070 Ti(支持FP16加速) - 推理速度 CPU:~5 tokens/s GPU:~25 tokens/s GPU:~30 tokens/s - 显存占用 - 6GB(FP16) 5GB(INT8量化) - 核心用途 本地快速测试/Ollama演示 文本摘要/多语言翻译 代码补全/数学推理 - 典型案例 个人学习助手 电商客服自动回复 Python脚本生成 成本范围 ¥2,000-5,000 ¥5,000-10,000 ¥6,000-12,000 性价比建议 适合个人开发者入门 中小团队轻量级NLP项目首选 技术团队效率工具开发 - 企业级模型(14B-32B)
模型版本 DeepSeek-R1-14B DeepSeek-R1-32B 硬件配置 - CPU 12核+ 16核+ - 内存 32GB 64GB - 存储 512GB 1TB - 显卡 RTX 4090(24GB显存) 双卡RTX 3090/A100 40GB 推理速度 45 tokens/s(FP16) 60 tokens/s(张量并行) 长文本支持 8K上下文窗口 16K上下文窗口 - 核心用途 法律合同分析/医疗报告生成 多模态数据预处理/科研仿真 - 典型案例 金融风险报告自动化 蛋白质结构预测/3D建模辅助 成本范围 ¥20,000-30,000 ¥40,000-100,000 - 科研级模型(70B-671B)
模型版本 DeepSeek-R1-70B DeepSeek-R1-671B 硬件架构 - 计算节点 2x A100 80GB
价格:15万/张8x H100
价格:220万/张- 内存 256GB 512GB - 存储 1TB 10TB - 网络 100Gbps 400Gbps 适用场景 - 科研领域 气候模拟/材料科学 AGI算法探索/超大规模预训练 - 商业应用 城市交通数字孪生 国家级AI基础设施 成本范围 ¥400,000+ ¥20,000,000+ 生态支持 HuggingFace加速库优化 定制化CUDA内核+混合精度训练
Ollama介绍和本地快速安装
- 什么是Ollama
- 一款专注于本地化运行大型语言模型(LLM)的开源工具,Ollama = 大模型领域的"Docker" + "Maven"
- 支持通过类 Docker 命令管理模型容器,实现快速下载、切换和运行。
- 提供兼容 OpenAI 的接口(如
http://localhost:11434/v1/chat/completions
) - 核心功能:
- 模型仓库管理(类似Maven中央仓库)
- 本地模型运行(类似Docker运行多个容器)
- REST API服务(对外提供大模型接口可以访问)
- 跨平台兼容性(Windows/macOS/Linux)
- 特点
- 隐私保护:完全离线运行,适合处理敏感数据(如医疗记录)
- 资源优化:支持 GPU 加速(如设置OLLAMA_CUDA_DEVICE=0)
- 官网:https://ollama.com/
- 文档:https://github.com/ollama/ollama/blob/main/README.md#quickstart
- 安装实操(官网下载对应的包,不同系统选择不一样)
Ollama常用命令讲解和实战
- 常用命令
- 模型管理
- ollama pull <模型名>:下载模型(如
ollama pull qwen2.5-coder:14b
、ollama pull qwen2.5:7b-instruct
) - ollama run <模型名>:运行模型(如
ollama run qwen2.5-coder:14b
) - ollama list:列出已安装的模型
- ollama rm <模型名>:删除指定模型
- ollama pull <模型名>:下载模型(如
- 服务控制
- ollama serve:启动本地服务(默认端口 11434)
- ollama create -f ./Modelfile:通过配置文件创建自定义模型
- 系统操作
- --version:查看版本信息
- ollama help:获取命令帮助
- 模型管理
- 核心命令手册类比Maven命令
命令格式 | 功能说明 | Java开发类比 |
---|---|---|
ollama run <模型名> |
运行模型并进入交互模式 | 类似mvn spring-boot:run |
ollama list |
查看已下载模型列表 | 类似mvn dependency:list |
ollama pull <模型名> |
下载模型(支持版本标签) | 类似git pull |
ollama serve |
启动API后台服务(默认端口11434) | 类似nohup java -jar |
ollama ps |
查看运行中的模型实例 | 类似jps 命令 |
本地电脑私有化部署多版本DeepSeek
-
需求
- 使用ollama部署DeepSeek大模型,deepseek-r1:7b,deepseek-r1:14b
- 注意:电脑配置不高的,不要部署14b哈
-
部署实战
#部署7b
ollama run deepseek-r1:7b
#部署14b
ollama run deepseek-r1:14b
- 问题:
算下deeeeep里面有几个e
AI大模型可视化界面介绍和部署实战
-
需求
- 前面部署了大模型,包括在线和私有化部署大模型,但都是基于命令操作
- 也可以利用可视化工具进行操作,可以自己研发、也可以利用开源的
-
LLM客户端开源工具讲解,方便进行调试和个人使用
-
Dify
- 开源的 LLM 应用开发平台, 轻松构建和运营生成式 AI 原生应用。
- 地址:https://dify.ai/zh
-
Cherry Studio
- 支持多服务商集成的AI对话客户端
- 地址:https://cherry-ai.com/
-
Chatbox
- 是一个支持多种流行LLM模型的桌面客户端,可在 Windows、Mac 和 Linux 上使用,也支持网页直接访问
- 配置Ollama允许远程连接 https://chatboxai.app/zh/help-center/connect-chatbox-remote-ollama-service-guide
- 地址:https://chatboxai.app/
-
-
Chatbox安装和实操
- 安装包:官网直接下载,根据自己的系统选择
- 配置大模型服务
大模型编码实战-SpringBoot开发接入大模型
SpringBoot整合大模型调用实战《上》
-
需求
- 有了大模型接口,业务项目如何开发呢?
- 解决方案
- 自定义HttpClient客户端,在Java代码中通过HTTP请求发送指令并获取结果, 适合快速开发和小规模应用
- 使用Spring-Starter,比如spring-ai-openai 进行整合,不用自己封装,比较多中大型项目采用
- 使用Spring AI Alibaba 创建应用,使用里面封装的工具进行调用,比SpringAI更好用
- 本地还是在线的模型选择
- 开发的时候建议选择在线大模型,可以灵活切换,避免本地开发卡顿的情况
-
什么是SpringAI
- Spring 生态中面向 AI 工程的应用框架,简化生成式 AI 的集成与开发
- 支持跨模型兼容性(如 OpenAI、DeepSeek 等)和多种功能(聊天、嵌入、流式输出等)
- 地址: https://spring.io/projects/spring-ai
-
大模型服务平台百炼
- 是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员,都能深入参与大模型应用的设计和构建。
- 可以通过简单的界面操作,在5分钟内开发出一款大模型应用,在几小时内训练出一个专属模型
- 地址:https://bailian.console.aliyun.com/
-
项目配置实战
-
添加依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <version>2.18.2</version> <exclusions> <exclusion> <artifactId>lombok</artifactId> <groupId>org.projectlombok</groupId> </exclusion> </exclusions> </dependency>
-
配置相关配置文件
ai: key: sk-571ca8cb9b7947a89d7d302e525c89dc
-
配置基础代码
-
SpringBoot整合大模型调用实战《下》
-
编码实战
@Value("${ai.key}") private String apiKey; /** * 调用大模型生成内容,使用预定义的系统消息和用户消息进行交互 * * @return GenerationResult 包含大模型生成结果的返回对象 * @throws ApiException 调用API时发生的异常 * @throws NoApiKeyException API密钥未设置异常 * @throws InputRequiredException 输入参数缺失异常 */ public GenerationResult callWithMessage() throws ApiException, NoApiKeyException, InputRequiredException { // 初始化大模型生成器 Generation gen = new Generation(); // 构建系统角色消息:定义助手的行为准则 Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content("You are a helpful assistant.") .build(); // 构建用户角色消息:包含具体的编程任务请求 Message userMsg = Message.builder() .role(Role.USER.getValue()) //这里是问的问题 .content("帮我写个Java冒泡排序") .build(); // 构造请求参数:配置模型参数和对话上下文 GenerationParam param = GenerationParam.builder() //https://help.aliyun.com/zh/model-studio/getting-started/models .model(Generation.Models.QWEN_TURBO) // 指定阿里云通义千问模型 .messages(Arrays.asList(systemMsg, userMsg)) // 组合对话上下文 .resultFormat(GenerationParam.ResultFormat.MESSAGE) // 设置返回格式 .temperature(0.8f) // 控制生成随机性的温度参数 .apiKey(apiKey) // 添加 API key 到参数中 .build(); // 执行模型调用并返回结果 return gen.call(param); }
SpringBoot整合大模型链路测试实战
- 测试案例测试(非流式输出)
LLM大模型应用开发选Java还是Python
简洁:LLM大模型应用开发选Java还是Python
-
问题点:
- 为什么很多企业招聘都是Java开发后端项目+Python封装模型服务?
- LLM大模型应用开发选Java还是Python?
-
技术选型思考
-
架构分层设计理念
- 企业级AI智能体开发采用分层架构,核心思想:
- 前端交互层: 处理用户请求、界面渲染(Web/App)
- 业务逻辑层(Java后端):企业级功能实现、数据管理、安全控制
- AI模型层(Python ):大模型训练、推理、算法优化
- 企业级AI智能体开发采用分层架构,核心思想:
-
Java开发后端项目的核心优势
维度 Java优势 企业级场景示例 稳定性 强类型语言 + 成熟的JVM内存管理 → 高可靠性 金融交易系统、医疗数据管理 性能 多线程优化 → 支撑高并发请求 电商秒杀系统、实时订单处理 生态体系 Spring全家桶(Boot/Cloud) + 微服务治理 分布式架构、服务熔断/限流 安全机制 完善的权限框架(Spring Security) + 企业级加密库 用户认证、支付网关接口 -
Python开发大模型的不可替代性
维度 Python优势 AI开发场景示例 算法生态 PyTorch/TensorFlow + HuggingFace ,基本上大模型都是用python BERT微调、Stable Diffusion图像生成 开发效率 动态类型 + 简洁语法 → 快速实验迭代 模型原型验证、超参数调优 社区资源 丰富的预训练模型库 + 活跃的AI开发者社区 快速集成GPT-4、Llama2等模型 GPU支持 CUDA深度集成 → 高效利用硬件加速 分布式训练、大模型推理优化 -
交互链路
[前端] ←HTTP→ [Java微服务集群] ←REST/gRPC→ [Python模型服务] ↗ (业务逻辑处理) [MySQL/Redis] ↘ (异步通信) [Kafka/RabbitMQ]
-
Python在Al领域占据主导地位
- 尤其是在机器学习和大模型方面。
- 常见的技术包括深度学习框架如PyTorch、TensorFlow,模型服务化工具如FastAPl、Flask
- 分布式训练库如Hugging Face Transformers、 DeepSpeed等
-
对比Java的技术栈
-
Java在大模型开发中相对较少,但也有一些工具和框架,比如Deeplearning4j。
-
Java在传统后端服务方面有优势,但在直接处理大模型训练和推理方面不如Python成熟
-
但Python在性能和高并发处理上可能不如Java,尤其是在微服务架构中。
-
Java的优势在于高性能、成熟的生态系统,适合构建高并发的生产环境服务
-
-
最终方案:Python负责模型推理,Java处理业务逻辑和APl网关,将Python的大模型服务与Java后端集成,比如通过REST API
-
MaaS介绍和Python开发LLM技术选型
简介: MaaS介绍和Python开发LLM技术选型
-
什么是MaaS 模型服务(可以认为就是调用尝试提供的在线LLM大模型API服务)
-
Model as a Service (模型即服务)是一种将AI模型以云端服务形式提供给用户的商业模式
-
目标是通过降低技术门槛和控制成本,推动大模型技术的规模化应用
-
模型层
- 核心AI能力层,提供可直接调用的模型服务(如API、SDK),支持语言、图像、推荐等多样化任务
- 示例:商汤科技通过MaaS提供DeepSeek模型服务,阿里的百炼平台提供多种模型服务
-
关键点
- 降低门槛:用户无需理解模型内部原理,通过API即可调用
- 成本节约:按需付费模式避免自建算力设施的高额投入
- 灵活性支持多模型切换(如通用大模型与行业模型协同)
- 持续更新:服务商负责模型优化与维护,用户始终使用最新技术
-
大厂的机会和挑战
-
成本压力(价格战,卷)
- 部分厂商(如腾讯、华为)因算力成本高导致亏损,例如DeepSeek R1每百万token成本约150元,而服务定价仅16元
-
模式升级
- 云厂商结合算力支持与PaaS工具(如数据集调优、模型部署),探索“算力+服务”的混合收费模式
-
生态竞争
- 开源模型(如DeepSeek)推动MaaS服务降价,但差异化竞争仍是关键(如行业模型定制)
-
-
-
技术选型架构图
-
开发AI大模型的Python技术选型(部分技术选型)
-
Python Web 服务框架
- FastAPI框架,对比就是Java的SpringBoot框架
- 地址:https://github.com/fastapi/fastapi
-
大模型开发框架
- LangChain,统一多个大模型交互,让大模型开发工程化,通过这个对接很多大模型,方便切换
- 地址:https://www.langchain.com/
-
对象验证模型
- Pydantic,对象参数验证封装,类似Hibernate Validator
- 地址:https://pydantic.com.cn/
-
大模型调用监控
- LangSmith,和业务系统的Prometheus监控一样
- 地址:https://www.langchain.com/langsmith
-
-
现在 后端或者前端、测试同学的问题?
- 那如果不会Python怎么办?不担心,用1天时间教会大家Python核心语法和进阶使用
AI大模型版-Python零基础极速上手实战
Python语言的前世今生和版本-编辑器说明
-
Python语言
-
Python是一种简单易学、功能强大的高层次编程语言,被称为"胶水语言"(能轻松整合其他语言组件)
-
网站地址 : https://www.python.org/
-
核心特点
-
极简语法 示例:用一行代码打印"Hello World"
print("Hello World!")
- 对比:相同功能在Java需要5行代码,C++需要7行代码
-
动态类型 :变量类型自动判断(无需声明类型)
age = 25 # 自动识别为整数 name = "小滴课堂-老王" # 自动识别为字符串
-
解释型语言: 无需编译,直接运行(对比C++需要先编译成二进制文件)
-
丰富的生态库:AI开发常用库:NumPy(科学计算)、Pandas(数据处理)、PyTorch(深度学习)
-
-
应用领域
- Web后端开发(框架Django、Flask)、爬虫、数据分析、自动化办公、人工智能、机器学习、AI大模型开发
领域 典型库 应用场景 人工智能 TensorFlow, PyTorch 图像识别/自然语言处理 数据分析 Pandas, Matplotlib 销售数据可视化分析 自动化运维 Ansible, Paramiko 服务器批量管理 Web开发 Django, Flask 企业级网站开发
-
-
语言版本(请注意,Python 2.x 系列已经在 2020 年停止支持,建议使用 Python 3.x 系列的版本)
版本号 发布时间 主要特性/改进 是否推荐使用 Python 2.7 2010年7月 最后一个Python 2系列版本,长期维护版 ❌ 已淘汰 Python 3.0 2008年12月 引入不兼容2.x的语法(print函数、Unicode字符串等) ❌ 历史版本 Python 3.7 2018年6月 数据类(dataclass)、异步生成器、内置breakpoint() ⚠️ 过渡版本 Python 3.8 2019年10月 海象运算符(:=)、位置参数限定符(/) ✅ 稳定 Python 3.9 2020年10月 字典合并运算符)、字符串前缀后缀移除方法 ⚠️ 过渡版本 Python 3.10 2021年10月 结构化模式匹配(match-case)、更友好的错误提示 ✅ 推荐首选 Python 3.11 2022年10月 性能提升60%、异常添加备注、TOML标准库支持 ✅ 高性能 Python 3.12 2023年10月 更快的解释器、改进的错误信息、移除distutils模块 ✅ 最新 -
推荐版本:
- 新手学习和正式使用:Python 3.10以上(LTS版本长期支持,文档完善,稳定性和性能平衡)
- 有3.10以上版本的大体语法类似,所以小版本有差异不影响
- 文档地址:https://docs.python.org/zh-cn/3.10/
-
查看当前电脑是否有安装
# 查看当前Python版本 python --version
-
-
常用编辑器
工具名称 类型 适合场景 学习曲线 核心优势 下载地址 VSCode 编辑器 全栈开发/通用编程 中等 轻量快速、插件生态丰富 code.visualstudio.com PyCharm IDE 大型项目/专业开发 较高 智能代码补全、深度框架支持 jetbrains.com/pycharm Jupyter 笔记本 数据分析/算法实验 低 交互式编程、可视化结果即时呈现 jupyter.org Spyder IDE 科学计算/数值分析 低 内置变量查看器、MATLAB风格界面 spyder-ide.org Sublime Text 编辑器 快速编辑/轻量开发 中低 启动极快、多光标编辑 sublimetext.com
Python环境安装和编辑器安装实战
-
Python配置环境变量
- 根据情况选择不同系统的安装包,记得选择installer(win)为主,方便使用
- 自己去官网下载(结合网上的博文搜索下安装和升级方式)
- 地址:https://www.python.org/
-
编辑器 Visual Studio Code(AI开发首选)
- 根据自己的系统情况自己去官网下载
- 地址:https://code.visualstudio.com/Download
- 使用地址 https://code.visualstudio.com/docs/python/python-quick-start
-
推荐插件组合
Python 微软官方支持 Pylance 类型提示增强 Python Snippets 编码技巧 Python Indent 缩进 TONGYI Lingma AI编码
-
编辑器英文改中文
Python标识符-缩进-注释-常见错误
-
运行第一行代码(右下角选择Python解释器版本)
# 经典入门程序 print("Hello World!") # 打印输出 print(2 + 3 * 4) # 数学运算
-
标识符规则(变量/函数/类命名)
-
合法命名规范
# 正确示例 age = 18 # 纯字母 student_name = "冰冰" # 含下划线 _price = 9.9 # 以下划线开头 MAX_COUNT = 100 # 全大写常量(约定俗成)
-
非法命名示例
2name = "错误" # ❌ 数字开头 class = "计算机" # ❌ 使用保留字 first-name = "张" # ❌ 含连字符
-
命名规范建议
类型 推荐格式 示例 变量 小写下划线 student_age
常量 全大写 MAX_VALUE
函数 小写下划线 get_data()
类名 大驼峰 DataParser
-
-
注释规范
-
单行注释
# 计算圆的面积(解释代码作用) radius = 5 area = 3.14 * radius ** 2 # 公式 πr²
-
多行注释
""" 作者:小滴课堂-二当家 日期:2030-10-01 功能:计算BMI指数 参数说明: weight: 体重(kg) height: 身高(m) """ def calc_bmi(weight, height): return weight / (height ** 2)
-
注释最佳实践
- 解释为什么这么做,而不是描述代码本身
- 关键算法添加注释
- 修改代码时同步更新注释
-
-
保留字(关键字)
- Python 3.12全部保留字(35个)
# 查看当前版本关键字 import keyword print(keyword.kwlist) ''' ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'] '''
-
行与缩进
# 正确缩进示例 if 10 > 5: print("条件成立") # 缩进4个空格 print("继续执行") # 同一代码块保持相同缩进 else: print("不成立")
-
新手常见错误
- 缩进错误
# 错误示例 def greet(): print("Hello") # ❌ 缺少缩进 # 正确写法 def greet(): print("Hello")
- 混用空格和Tab
# 文件统一使用4个空格缩进 # 在编辑器中设置:空格替代Tab
- 错误使用保留字
# 错误示例 class = "计算机科学" # ❌ class是关键字 # 正确写法 class_name = "计算机科学"
Python输出高级技巧和f-string字符串语法
-
print输出
- 基本语法
print(value1, value2, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
- 参数说明
values
:要输出的值,可以是多个,用逗号分隔。sep
:分隔符,默认是空格。end
:结束符,默认是换行符\n
。file
:输出目标(如文件对象),默认是控制台。flush
:是否强制刷新输出(默认False)。
- 默认输出是换行的,如果要实现不换行需要在变量末尾加上 end=" ":
# 输出多个值 print("Hello", "Python", 2030) # Hello Python 2023 # 修改分隔符和结束符 print("小滴", "课堂", sep="---", end="!!!") # Hello---Python!!!
-
格式化输出
-
支持格式化字符串的输出 ,支持复杂的表达式
-
str.format()(推荐):
print("Name: {}, Age: {}".format(name, age)) # 按顺序填充 print("Name: {name}, Age: {age}".format(name="老王", age=30)) # 按关键字填充
-
f-string(Python 3.6+,最简洁)字面量格式化字符串
- f-string 格式化字符串以 f 开头,后跟着字符串,字符串中的表达式用大括号 {} 包起来,会将变量或表达式计算后的值替换
- 之前习惯用百分号 (%),现在更简单,不用再去判断使用 %s,还是 %d
name = "小明" age = 18 print(f"姓名:{name},年龄:{age}") # 姓名:小明,年龄:18
- 支持任意合法的Python表达式:
# 数学运算 print(f"结果:{3 * 4 + 5}") # 结果:17 # 三目运算符 score = 85 print(f"等级:{'优秀' if score >=90 else '良好'}")
-
常见错误
# 错误示例1:忘记f前缀 name = "韩梅梅" print("姓名:{name}") # 不会报错,但输出原样文本 # 错误示例2:无效表达式 print(f"结果:{len('abc')}") # 正确写法 print(f"结果:{len 'abc'}") # 语法错误(缺少括号)
-
-
注意事项
- 默认换行符是
\n
,可以通过end=""
取消换行。 f-string
中不能使用反斜杠转义字符(如\n
),需用变量代替- 速度对比
# 速度测试对比(1,000,000次): # f-string: 0.12秒 # format方法: 0.25秒 # %格式化: 0.18秒
- 默认换行符是
Python用户输入和模块导入模块讲解
-
用户输入
-
使用input()函数获取用户输入,返回值:输入内容始终是字符串类型。
-
基本语法
user_input = input("提示信息:")
-
案例代码
# 基本输入 name = input("请输入姓名:") print(f"你好,{name}!") # 类型转换 age = int(input("请输入年龄:")) print(f"你明年将 {age + 1} 岁。") # 多输入处理(通过split分割) values = input("输入两个数字(空格分隔):").split() # map(func,lst) 讲传入的函数变量func作用在lst上每个元素中 a, b = map(int, values) print(f"两数之和:{a + b}")
-
-
import语句
- 在 python 用 import 或者 from...import 来导入相应的模块。
特性 import module from module import obj 命名空间 保持完整 直接引入当前作用域 内存占用 较大 较小 代码可读性 明确来源 可能造成命名混淆 修改影响 需修改全称引用 本地修改不影响原模块 典型应用场景 频繁使用多个对象 少量高频使用对象 -
导入整个模块
import math print(math.sqrt(16)) # 4.0
-
导入特定功能
from math import sqrt, pi print(sqrt(16)) # 4.0 print(pi) # 3.141592653589793
-
相对导入(包内使用)
# 在 mypackage/sub/mod.py 中 from .. import utils # 上级目录导入 from . import helper # 同级目录导入
-
最佳实践指南
-
优先顺序原则:
- 标准库模块 → 第三方库 → 自定义模块
- 绝对导入优先于相对导入
# 推荐 import numpy as np from django.db import models # 避免 from module import *
-
-
案例作业
# 导入自定义模块 # 假设存在一个 my_module.py,内容为:def greet(): print("Hello!") import my_module my_module.greet() # Hello!
Python的变量和常见数据类型讲解
-
变量
- Python 中的变量不需要声明,每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
- 动态类型, 变量类型由赋值决定, 变量就是变量,没有类型
a = 10 # int a = "Hello" # 类型动态改变(实际是绑定到新对象)
- 命名规范
规则类型 正确示例 错误示例 合法字符 var_1
,中文变量
2var
,var-name
大小写敏感 age ≠ Age
- 保留字规避 list_ = [1,2,3]
list = [1,2,3]
PEP8推荐 user_name
userName
(PascalCase)- 拓展:PEP8 指 Python Enhancement Proposal(Python增强提案)的第8号提案
-
数据类型概览
-
Python中一切皆对象,常见基本数据类型 (内置的 type() 函数可以用来查询变量的对象类型)
-
数据容器(序列)是一种可以存储多个元素的Python数据类型,包含:list(列表)、tuple(元组)、str(字符串)、set(集合)、dict(字典)
1. 数字类型(Numbers) - 整数(int) - 浮点数(float) - 布尔(bool,是int的子类) 2. 序列类型(Sequences) - 字符串(str) - 列表(list) - 元组(tuple) 3. 映射类型(Mapping) - 字典(dict) 4. 集合类型(Sets) - 集合(set) - 不可变集合(frozenset) 5. 其他 - None(空类型)
-
类型分类
可变类型 不可变类型 list int, float, bool dict str set tuple -
数据类型速查表
类型 可变 有序 重复 语法示例 int × - - a = 10
float × - - b = 3.14
str × √ √ s = "hello"
list √ √ √ lst = [1,2,3]
tuple × √ √ tpl = (1,2)
dict √ × 键唯一 d = {'a':1}
set √ × × s = {1,2,3}
-
-
数字类型(Numbers)
-
整数(int)
- 特点:无大小限制,支持二进制(0b1010)、八进制(0o12)、十六进制(0x1A)
- 案例:
a = 10 # 十进制 b = 0b1010 # 二进制 → 10 c = 1_000_000 # 分隔符增强可读性(Python3.6+) print(a, b, c)
-
浮点数(float)
- 特点:带小数点的数,可用科学计数法(2.5e3 = 2500.0)
- 案例:
x = 3.14 y = 2.5e-3 # 0.0025
-
布尔(bool)
- 取值:True(1) / False(0)
- 案例
is_active = True print(int(is_active)) # 1
-
AI大模型版-Python核心数据类型讲解
Python的字符串和常见方法操作
-
字符串基础
-
定义方式
- 字符串一旦创建,内容不可修改(修改会创建新对象)
s1 = '单引号字符串' s2 = "双引号字符串" s3 = '''三引号支持 多行字符串''' s4 = r"原始字符串\n不转义" # 输出:r Row String代表原始字符串,转义符失效 原始字符串\n不转义
-
转义字符
转义符 说明 示例 \\
反斜杠 print("C:\\")
→C:\
\n
换行 "Hello\nWorld"
\t
制表符 "Name:\tAlice"
\'
或\"
保留引号 'It\'s OK'
-
索引与切片
s = "Python" print(s[0]) # P(正向索引,从0开始) print(s[-1]) # n(反向索引,从-1开始) print(s[2:5]) # tho(切片:[起始, 结束)) print(s[::2]) # Pto(步长为2)
-
-
字符串常用方法
-
大小写转换
s = "Hello, Python" print(s.upper()) # HELLO, PYTHON print(s.lower()) # hello, python print(s.title()) # Hello, Python(每个单词首字母大写) print(s.swapcase()) # hELLO, pYTHON(大小写互换)
-
查找和替换
s = "Hello World" # 查找子串位置 print(s.find("World")) # 6(返回首次出现的索引,找不到返回-1) print(s.index("lo")) # 3(类似find,但找不到会报错) # 统计出现次数 print(s.count("l")) # 3 # 替换内容 print(s.replace("World", "Python")) # Hello Python
-
字符串分割与连接
# 分割为列表 s = "apple,banana,orange" print(s.split(",")) # ['apple', 'banana', 'orange'] # 按行分割(适用于多行文本) text = "Line1\nLine2\nLine3" print(text.splitlines()) # ['Line1', 'Line2', 'Line3'] # 连接列表为字符串 lst = ["2023", "10", "01"] print("-".join(lst)) # 2030-10-01
-
去除空白与填充
s = " Python " print(s.strip()) # "Python"(去除两侧空白) print(s.lstrip()) # "Python "(去左空白) print(s.rstrip()) # " Python"(去右空白) # 填充对齐 print(s.center(20, "*")) # ****Python****(居中填充) print(s.zfill(10)) # 0000Python(左侧补零)
-
-
字符串格式化
-
旧式格式化(%)
name = "Alice" age = 25 print("Name: %s, Age: %d" % (name, age)) # Name: Alice, Age: 25
-
str.format()
print("{} + {} = {}".format(3, 5, 8)) # 3 + 5 = 8(按顺序) print("{name}喜欢{language}".format(name="小明", language="Python")) # 关键字参数
-
f-string(Python 3.6+)
price = 19.99 print(f"价格:{price:.2f}元") # 价格:19.99元(保留两位小数)
-
-
字符串方法速查表
方法名 功能说明 示例 split()
按分隔符分割字符串 "a,b,c".split(",") → ['a','b','c']
strip()
去除两侧空白 " hi ".strip() → "hi"
replace()
替换子串 "Hello".replace("e", "a") → "Hallo"
startswith()
判断是否以某子串开头 "file.txt".startswith("file") → True
endswith()
判断是否以某子串结尾 "image.jpg".endswith(".jpg") → True
Python常用自带初级函数讲解
-
Python很多函数,没法一个个记住,掌握高频的使用的即可
-
基础操作类函数
-
print()
- 功能:输出内容到控制台
- 参数:
*values
(多个值)、sep
(分隔符)、end
(结束符)
print("Hello", "World", sep=", ", end="!") # Hello, World!
-
input()
- 功能:获取用户输入(返回字符串)
- 参数:
prompt
(提示信息) - 案例:
name = input("请输入姓名:") print(f"你好,{name}!")
-
-
数据类型转换
-
int()
/float()
/str()
/bool()
- 功能:强制类型转换
- 案例
num = int("123") # 字符串→整数 → 123 price = float("9.9") # 字符串→浮点数 → 9.9 text = str(100) # 整数→字符串 → "100"
-
list()
/tuple()
/dict()
/set()
- 功能:创建或转换容器类型
- 案例:
# 1. 字符串转列表(按字符拆分) chars = list("Python") print(chars) # 输出:['P', 'y', 't', 'h', 'o', 'n'] # 2. 元组转列表 tup = (1, 2, 3) lst = list(tup) print(lst) # 输出:[1, 2, 3] #创建空列表(等效于 []) empty = list() print(empty) # 输出:[] #================================= # 1. 列表转元组 lst = [10, 20, 30] tup = tuple(lst) print(tup) # 输出:(10, 20, 30) # 2. 字典转元组(仅保留键) dct = {'a': 1, 'b': 2} keys = tuple(dct) print(keys) # 输出:('a', 'b') #创建空元组(等效于 ()) empty = tuple() print(empty) # 输出:() #================================= # 1. 键值对列表转字典 pairs = [('a', 1), ('b', 2)] dct = dict(pairs) print(dct) # 输出:{'a': 1, 'b': 2} # 2. 关键字参数创建 dct = dict(name='Alice', age=25) print(dct) # 输出:{'name': 'Alice', 'age': 25} #创建空字典(等效于 {}) empty = dict() print(empty) # 输出:{} #================================= # 1. 列表去重 nums = [1, 2, 2, 3, 3] unique = set(nums) print(unique) # 输出:{1, 2, 3} # 2. 字符串转字符集合 chars = set("apple") print(chars) # 输出:{'a', 'p', 'l', 'e'} #创建空集合(注意不能用 {}) empty = set() print(empty) # 输出:set()
-
-
数学与序列操作
-
range()
- 功能:生成整数序列(常用于循环)
- 参数:
start
,stop
,step
- 案例
range(stop) # 生成 [0, stop) 的整数序列,步长=1 range(start, stop) # 生成 [start, stop) 的整数序列,步长=1 range(start, stop, step) # 生成 [start, stop) 的整数序列,步长=step # 生成 0-4 r1 = range(5) print(list(r1)) # 输出 [0, 1, 2, 3, 4] # 生成 2-5(不包含5) r2 = range(2, 5) print(list(r2)) # 输出 [2, 3, 4] for i in range(2, 10, 2): print(i) # 输出 2 4 6 8
-
len()
- 功能:获取容器长度(字符串、列表、字典等)
- 案例
text = "Python" print(len(text)) # 6
-
sum()
/max()
/min()
- 功能:计算总和、最大值、最小值
- 案例
nums = [1, 2, 3] print(sum(nums)) # 6 print(max(nums)) # 3
-
-
时间处理(time模块)
-
time.time()
- 功能:获取当前时间戳(单位:秒)从1970年到现在的秒数
- 案例:
import time start = time.time() time.sleep(2) # 暂停2秒 end = time.time() print(f"耗时:{end - start:.2f}秒") # 耗时:2.00秒
-
time.strftime()
- 功能:格式化时间
- 案例:
import time now = time.strftime("%Y-%m-%d %H:%M:%S") print(now) # 2030-10-01 14:30:00
-
Python的列表List和常见方法操作
-
列表List
-
定义方式
list1 = [1, 2, 3] # 直接定义 list2 = list("abc") # 通过可迭代对象转换 → ['a', 'b', 'c'] list3 = [] # 空列表 list4 = [1, "hello", True, [2, 3]] # 可混合多种类型
-
核心特性
- 有序:元素按插入顺序存储。
- 可变:支持增删改操作(内存地址不变)。
- 可重复:允许包含相同元素。
-
索引与切片
lst = ["a", "b", "c", "d", "e"] # 索引 print(lst[0]) # a(正向索引,从0开始) print(lst[-1]) # e(反向索引,从-1开始) # 切片(返回新列表) print(lst[1:3]) # ['b', 'c'](左闭右开) print(lst[::2]) # ['a', 'c', 'e'](步长2)
-
-
列表常用方法
- 增删元素
方法 功能说明 示例代码 append(obj)
在末尾添加元素 lst.append(4) → [1,2,3,4]
insert(index, obj)
在指定索引插入元素 lst.insert(1, "x") → [1, 'x', 2,3]
extend(iterable)
合并可迭代对象到列表末尾 lst.extend([4,5]) → [1,2,3,4,5]
remove(obj)
删除第一个匹配的元素(无返回值) lst.remove(2) → [1,3]
pop(index=-1)
删除并返回指定索引元素(默认最后一个) lst.pop(1) → 2 → [1,3]
clear()
清空列表 lst.clear() → []
- 查询与统计
方法 功能说明 示例代码 index(obj)
返回元素首次出现的索引(找不到报错) lst.index("b") → 1
count(obj)
统计元素出现次数 lst.count(2) → 2
len(lst)
获取列表长度 len([1,2,3]) → 3
- 排序与反转
方法 功能说明 示例代码 sort(key=None, reverse=False)
原地排序(无返回值) lst.sort(reverse=True) → 降序
sorted(iterable)
返回新排序列表(原列表不变) sorted(lst) → 新列表
reverse()
原地反转列表顺序 lst.reverse() → [3,2,1]
Python的字典Dict和常见方法操作
-
字典Dict
- 字典是键值对(
key-value
)的集合,键唯一且不可变(如字符串、数字、元组),值可以是任意类型。
# 创建字典的多种方式 dict1 = {} # 空字典 dict2 = {"name": "Alice", "age": 25} dict3 = dict(name="Bob", age=30) # 关键字参数创建 dict4 = dict([("id", 1001), ("city", "Beijing")]) # 可迭代对象
- 特性:
- 无序性(Python 3.7之前无序,之后有序但不应依赖顺序)。
- 动态可变:可随时增删改键值对。
- 高效查找:通过键直接访问值,时间复杂度为O(1)。
- 键的限制
- 键必须是不可变类型,如
int
、str
、tuple
(不含可变元素的元组)。 - 键不可重复,重复赋值会覆盖旧值。
- 键必须是不可变类型,如
- 字典是键值对(
-
字典常用操作
- 增删改查
student = {"name": "Alice", "age": 20} # 查:通过键访问 print(student["name"]) # Alice(键不存在会报KeyError) print(student.get("age", 18)) # 20(键不存在返回默认值18) # 增/改 student["gender"] = "Female" # 添加新键值对 student["age"] = 21 # 修改已有键的值 # 删 del student["gender"] # 删除指定键值对 age = student.pop("age") # 删除并返回值 → 21 student.clear() # 清空字典 → {}
- 常用方法
方法名 功能说明 示例 keys()
返回所有键的视图对象 student.keys() → dict_keys(['name'])
values()
返回所有值的视图对象 student.values() → dict_values(['Alice'])
items()
返回所有键值对的视图对象 student.items() → dict_items([('name', 'Alice')])
update(dict2)
合并字典(覆盖重复键) student.update({"age": 22, "city": "Shanghai"})
setdefault(key, default)
若键存在返回其值,否则插入键并设默认值 student.setdefault("name", "Bob") → "Alice"
popitem()
删除并返回最后插入的键值对(LIFO) student.popitem() → ('city', 'Shanghai')
-
注意事项
- 键不可变:列表、字典等可变类型不能作为键。
# 错误示例:列表作为键 invalid_dict = {["id"]: 1001} # TypeError: unhashable type: 'list'
-
作业练习
- 统计单词频率
text = "apple banana apple orange banana apple" words = text.split() word_count = {} for word in words: word_count[word] = word_count.get(word, 0) + 1 print(word_count) # {'apple':3, 'banana':2, 'orange':1}
Python的元组Tulple和常见方法操作
-
元组Tulple
- 定义:元组是一个不可变的序列类型,可以包含任意类型的元素
- 语法:使用圆括号
()
,元素用逗号分隔(单元素元组必须加逗号) - 元组比列表更轻量,适用于只读场景。
t1 = () # 空元组 t2 = (1,) # 单元素元组 → (1,) t3 = (1, "a", True) # 混合类型 t4 = 4, 5, 6 # 括号可省略 → (4,5,6)
-
核心特性:
- 不可变性:元组一旦创建,元素不能增删改(但可包含可变元素,如列表,可以修改这个里面的内容)
t = (1, 2, 3) t[0] = 100 # 报错:TypeError(不可修改元素) t = ([1,23,4,5], 2, 3) t[0].append(1111) #可以修改 print(t[0])
- 有序性:元素按插入顺序存储。
- 可重复:允许包含相同元素。
-
注意事项
- 单元素元组的逗号:单元素必须加逗号,否则视为普通变量
t = (1,) # 正确 → 元组 t = (1) # 错误 → 整数1
- 元组真的完全不可变吗?元组本身的引用不可变,但若包含可变元素(如列表),其内部可修改
t = (1, [2, 3]) t[1].append(4) # 合法 → (1, [2,3,4])
-
元组常用操作
-
元组常见方法
- count(obj) 统计元素出现的次数
t = (1, 2, 2, 3, 2) print(t.count(2)) # 3
- index(obj) 返回元素首次出现的索引(找不到时报错)
t = ("a", "b", "c", "b") print(t.index("b")) # 1
-
索引与切片
t = (10, 20, 30, 40, 50) # 索引 print(t[0]) # 10(正向索引,从0开始) print(t[-1]) # 50(反向索引,从-1开始) # 切片(返回新元组) print(t[1:3]) # (20,30)(左闭右开) print(t[::2]) # (10,30,50)(步长2)
- 元组拼接与重复
t1 = (1, 2) t2 = (3, 4) # 拼接(生成新元组) t3 = t1 + t2 # (1,2,3,4) # 重复 t4 = t1 * 3 # (1,2,1,2,1,2)
- 元组普通解包(Unpacking)
a, b, c = (10, 20, 30) print(a, b, c) # 10 20 30
-
-
案例练习
- 学生信息存储与解包
student = ("小滴课堂-老王", 20, "Computer Science") name, age, major = student print(f"{name}主修{major}") # 小滴课堂-老王主修Computer Science
Python的集合Set和常见方法操作
-
集合Set
- 集合是一个无序、不重复元素的容器,元素必须是不可变类型(如数字、字符串、元组)
- 集合与字典的关系, 字典的键集合类似集合,但字典存储键值对,集合仅存储键。
# 创建集合 s1 = {1, 2, 3} # 直接定义 s2 = set([1, 2, 2, 3]) # 通过可迭代对象 → {1, 2, 3} empty_set = set() # 空集合(不能使用 {},因为这是空字典)
-
核心特性
- 元素唯一性:自动去重,重复元素仅保留一个。
- 无序性:元素存储顺序与添加顺序无关。
- 高效成员检测:查找元素的时间复杂度为O(1)。
-
不可变集合(frozenset)
- 不可变版本的集合,不可增删元素,可哈希(可作为字典的键)
fs = frozenset([1, 2, 3])
-
注意事项
- 集合元素无顺序,无法通过索引访问
s = {3, 1, 2} print(list(s)) # 输出顺序不确定(如 [1,2,3] 或 [3,1,2])
- 如何创建空集合?
s = set() # 正确(空集合) s = {} # 错误(创建的是空字典)
-
集合常用方法
- 增删元素
方法 功能说明 示例代码 add(element)
添加单个元素 s.add(4) → {1,2,3,4}
update(iterable)
合并可迭代对象中的元素 s.update([4,5]) → {1,2,3,4,5}
remove(element)
删除指定元素(元素不存在时报错) s.remove(3) → {1,2}
discard(element)
删除指定元素(元素不存在时不报错) s.discard(3) → {1,2}
pop()
随机删除并返回一个元素(集合为空报错) s.pop() → 1
clear()
清空集合 s.clear() → set()
- 集合运算
方法 运算符 功能说明 示例代码 union(s2)
` ` 返回并集(不修改原集合) intersection(s2)
&
返回交集 s1 & s2
→ 同时存在于s1和s2中的元素difference(s2)
-
返回差集(s1有但s2没有的元素) s1 - s2
→ s1中不在s2中的元素symmetric_difference(s2)
^
返回对称差集(仅在一个集合中的元素) s1 ^ s2
→ 在s1或s2中但不同时存在的元素- 集合关系判断
方法 功能说明 示例代码 issubset(s2)
判断当前集合是否为s2的子集 s1.issubset(s2) → True/False
issuperset(s2)
判断当前集合是否包含s2 s1.issuperset(s2) → True/False
isdisjoint(s2)
判断两个集合是否无交集 s1.isdisjoint(s2) → True/False
-
集合操作案例
- 去重
lst = [1, 2, 2, 3, 3, 3] unique = set(lst) # {1, 2, 3} new_lst = list(unique) # [1, 2, 3](但顺序可能丢失)
- 集合运算应用
# 统计两个列表的共同元素 list1 = [1, 2, 3] list2 = [2, 3, 4] common = set(list1) & set(list2) # {2, 3}
- 权限管理系统
# 用户权限集合 user_permissions = {"read", "write"} required_permissions = {"write", "execute"} # 检查用户是否具备所有必需权限 if required_permissions.issubset(user_permissions): print("权限足够") else: print("缺少权限:", required_permissions - user_permissions) # 输出:缺少权限:{'execute'}
AI大模型版-逻辑判断和多种高级循环实战
Python逻辑判断和比较运算符
-
比较运算符
运算符 说明 示例 ==
等于 5 == 5 → True
!=
不等于 3 != 5 → True
>
大于 10 > 5 → True
<
小于 3 < 2 → False
>=
大于等于 5 >= 5 → True
<=
小于等于 4 <= 3 → False
- 注意点
- 避免混淆赋值符:=用于赋值,==用于比较。
- 链式比较:Python支持链式表达式 1 < x < 5(等价于 x>1 and x<5)
- 注意点
-
逻辑运算符
- 优先级:not > and > or
- 可通过括号()明确优先级:
- 短路求值(Short-Circuiting)
- 规则
and
:若左侧为假,直接返回假,不计算右侧。or
:若左侧为真,直接返回真,不计算右侧。
- 规则
运算符 说明 示例 and
逻辑与(全真为真) (5>3) and (2<1) → False
or
逻辑或(一真即真) (5>3) or (2<1) → True
not
逻辑非 not (5>3) → False
- 优先级:not > and > or
-
条件表达式(三元运算符)
- 语法:结果1 if 条件 else 结果2
# 返回两个数中的较大值 a, b = 5, 10 max_value = a if a > b else b # 10
-
条件语句(if-elif-else)
-
执行顺序:从上到下判断条件,满足即执行对应代码块,后续条件不再判断。
-
基本语法
if 条件1: 代码块1 elif 条件2: 代码块2 else: 代码块3
-
案例代码
# 判断成绩等级 score = 85 if score >= 90: print("A") elif score >= 80: print("B") # 输出B else: print("C")
-
Python常见循环语法和循环控制语句
-
for循环基础
-
语法
for 变量 in 可迭代对象: 循环体代码
-
案例练习
-
遍历列表
teachers = ["老帆", "冰冰", "Anna"] for t in teachers: print(f"老王 love 小滴课堂 {t}!") # 输出: # 老王 love 小滴课堂 老帆! # 老王 love 小滴课堂 冰冰! # 老王 love 小滴课堂 Anna!
-
遍历字符串
word = "小滴课堂" for char in word: print(char.upper(), end=" ") # 小 滴 课 堂
-
遍历字典
# 遍历字典 dict1 = {"Monday": "星期一", "Tuesday": "星期二"} for key in dict1.keys(): print(key) # 输出字典的键
-
结合range函数 生成整数序列
for j in range(1, 5): print(j) # 输出1,2,3,4
-
-
-
while循环基础
-
语法
while 条件表达式: 循环体代码
-
案例练习
- 计数器
count = 0 while count < 5: print(f"Count: {count}") count += 1 # 输出: # Count: 0 # Count: 1 # ... # Count: 4
- 用户输入验证
password = "" while password != "secret123": password = input("Enter password: ") print("Access granted!")
-
-
循环控制语句
- break:立即终止循环
for num in range(10): if num == 5: break print(num) # 输出0-4
- continue:跳过当前迭代
for num in range(10): if num % 2 == 0: continue print(num) # 输出1,3,5,7,9
Python高级for循环列表推导式案例实战
-
什么是推导式
- 是一种独特的数据处理方式,从一个数据序列构建另一个新的数据序列的结构体。
- 是一种强大且简洁的语法,适用于生成列表、字典、集合和生成器。
- 使用推导式时,需要注意可读性,尽量保持表达式简洁,以免影响代码的可读性和可维护性
- Python支持多种推导式
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
- 元组(tuple)推导式
-
列表推导式(List Comprehensions)
-
核心作用
- 快速创建列表,替代传统for循环,具有简洁高效的特点。
- 用于 从 一个现有的列表 创建 一个新列表 , 使用一行代码 即可 实现 循环 或 条件逻辑 , 生成新的List列表
-
基础语法
[expression for item in iterable]
- 案例:生成1-10的平方列表
squares = [x**2 for x in range(1, 11)] print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
-
条件过滤 语法
new_list = [expression for item in iterable if condition]
-
等价于
new_list = [] for item in iterable: if condition: expression
-
参数说明
- iterable 参数 : 一个现有的列表 , 可以迭代的对象 , 比如 列表、元组、字符串等 ;
- condition 参数 :
- 可选条件表达式 , 用于过滤 iterable 中的元素 ,
- iterable 列表中只有满足 该条件的元素 , 才会被作为 item 参与 expression 表达式计算 ;
- item 参数 :
- 如果没有 condition 参数 , 那 item 就是 iterable 列表中的每一个元素 ;
- **如果 有 condition 参数 , 那么 item 就是 iterable 列表中 符合 condition 条件 的元素 **
- expression 参数 : item 参与计算的 表达式 , 其中有 item 变量 ;
-
案例:筛选偶数
evens = [x for x in range(20) if x % 2 == 0] print(evens) # [0, 2, 4, ..., 18]
-
-
条件表达式语法
[expression1 if condition else expression2 for item in iterable]
结合三目运算符- 案例:数值转换
nums = [12, -5, 8, -3, 0] abs_nums = [x if x >= 0 else -x for x in nums] print(abs_nums) # [12, 5, 8, 3, 0]
-
-
作业案例
- 考试成绩转换,转换为等级制(80分以上为A,其他为B)
# 原始成绩列表 scores = [78, 92, 65, 88, 54] # 转换为等级制(80分以上为A,其他为B) grades = ['A' if score >=80 else 'B' for score in scores] print(grades) # 输出:['B', 'A', 'B', 'A', 'B']
Python高级推导式字典-元组-集合
-
字典推导
-
语法结构详解
{ 键表达式: 值表达式 for 循环变量 in 可迭代对象 [if 条件] }
-
注意
- 键必须唯一且不可变
- 值可以是任意类型
-
案例
- 键值转换
# 反转字典的键值对 original = {'a': 1, 'b': 2, 'c': 3} reversed_dict = {v: k for k, v in original.items()} print(reversed_dict) # {1: 'a', 2: 'b', 3: 'c'}
- 复杂数据重组
students = [ {'name': 'Alice', 'math': 85, 'english': 90}, {'name': 'Bob', 'math': 78, 'english': 82}, {'name': 'Charlie', 'math': 92, 'english': 88} ] # 创建学科分数字典 math_scores = {stu['name']: stu['math'] for stu in students} print(math_scores) # {'Alice':85, 'Bob':78, 'Charlie':92}
-
-
集合推导式
-
语法结构详解
{ 表达式 for 循环变量 in 可迭代对象 [if 条件] }
-
注意
- 自动去重
- 元素无序
- 元素必须可哈希
-
案例
- 交集和并集案例
A = {1, 2, 3} B = {3, 4, 5} # 并集 union = {x for x in A | B} # {1,2,3,4,5} # 交集 intersection = {x for x in A if x in B} # {3}
- 数据去重
words = ['apple', 'banana', 'apple', 'orange', 'banana'] unique_words = {word.upper() for word in words} print(unique_words) # {'APPLE', 'BANANA', 'ORANGE'}
- 特征提取
text = "The quick brown fox jumps over the lazy dog" vowels = {'a', 'e', 'i', 'o', 'u'} # 提取文本中的唯一元音字母 found_vowels = {char.lower() for char in text if char.lower() in vowels} print(found_vowels) # {'a', 'e', 'i', 'o', 'u'}
-
-
元组表达式
-
语法结构详解
( 表达式 for 循环变量 in 可迭代对象 [if 条件] )
-
元组推导式和列表推导式的用法相同,只是元组推导式是用 () 圆括号将各部分括起来,列表推导式用的是中括号
-
元组推导式返回的结果是一个生成器对象, 所以“元组推导式"实际上是生成器表达式
-
注意
- 惰性求值(按需生成)
- 内存效率高
- 只能迭代一次
-
案例
- 生成一个包含数字 1~9 的元组
a = (x for x in range(1,10)) # 返回的a是生成器对象 tuple(a) # 使用 tuple()函数,将生成器对象转换成元组 (1, 2, 3, 4, 5, 6, 7, 8, 9)
-
Python迭代器介绍和常见方法讲解
-
什么迭代器
-
访问集合元素的一种方式,可以记住遍历的位置的对象,和Java里面的集合的迭代器Iterator一样
-
核心是通过某种方式(通常使用循环)访问集合元素的过程
-
作用
- 在处理大数据集时,迭代器可以节省内存。
- 迭代器提供了一种统一的遍历方式,适用于各种数据结构。
-
迭代器对象和可迭代对象
- 可迭代对象:实现了
__iter__()
方法的对象 - 迭代器对象:实现了
__iter__()
和__next__()
方法的对象称为迭代器对象 - 通过定义可以知道,迭代器对象一定是可迭代对象。
- 简单解释两个方法:
__iter__()
要返回一个迭代器对象,可以使用iter() 函数触发__next__()
用于从迭代器中返回下一个值,如果没有可返回值,抛出StopIteration
异常,用next() 函数来触发它。
- 常见可迭代对象:列表、元组、字符串、字典、集合、文件对象、生成器
# 验证可迭代对象 from collections.abc import Iterable print(isinstance([1,2,3], Iterable)) # True print(isinstance("hello", Iterable)) # True print(isinstance(123, Iterable)) # False # isinstance(object, classinfo) 判断一个对象是否为指定类型或指定类型的子类 # 使用元组传递多个类型,如果对象是其中任意一个类型的实例,则返回 `True`。 print(isinstance(10, (int, str))) # 输出:True
- 可迭代对象:实现了
-
迭代器对象遍历 ( 记录当前迭代位置, 只能向前不能后退)
- 使用常规for语句进行遍历
list=[1,2,3,4] it = iter(list) # 创建迭代器对象 for x in it: print (x, end=" ")
iter()
函数- 用于将可迭代对象(如列表、元组等)转换为迭代器。
- 使用 next() 函数
next()
函数用于从迭代器中获取下一个元素。
# 手动使用迭代器 numbers = [1, 2, 3] iterator = iter(numbers) print(next(iterator)) # 1 print(next(iterator)) # 2 print(next(iterator)) # 3 print(next(iterator)) # 抛出StopIteration异常
-
-
for 循环的底层原理
for
循环实际上是通过调用iter()
和next()
来实现的。
for item in my_list: print(item) # 等价于: my_iter = iter(my_list) while True: try: item = next(my_iter) print(item) except StopIteration: break
如何自定义迭代器和生成器案例实战
-
如何自定义迭代器?
- 定义一个类,并实现
__iter__()
和__next__()
方法。 __iter__()
返回迭代器对象本身。__next__()
返回下一个元素,如果没有更多元素,则抛出 StopIteration 异常。- 示例:自定义一个范围迭代器
# __xxx__ 形式的方法被称为魔术方法或特殊方法,有特殊的名称和用途,通常用于实现某些内置操作, 例如,__init__ 用于对象初始化 # self是一个具有特殊意义的参数,是一个约定俗成的名称,代表类的实例本身。当定义一个类的方法(成员函数)时,按照惯例,第一个参数通常被命名为self # 在Python中,self是一个显式传递的参数,而在Java中,this是一个隐式的引用 class RangeIterator: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self def __next__(self): if self.current >= self.end: raise StopIteration else: self.current += 1 return self.current - 1 # 使用自定义迭代器 range_iter = RangeIterator(1, 4) for num in range_iter: print(num) # 输出 1, 2, 3
- 定义一个类,并实现
-
生成器
- 使用了 yield 的函数被称为生成器(generator),是一种特殊的迭代器,使用
yield
关键字来返回值。 - yield 是一个关键字,用于定义生成器函数,是一种特殊的函数,在迭代过程中逐步产生值,而不是一次性返回所有结果
- 生成器函数在每次调用
next()
时执行,直到遇到yield
语句。 - 作用
- 生成器可以节省内存,因为它不会一次性生成所有数据, 代码更简洁。
- 状态保持:每次yield后保持当前执行状态
- 案例一:调用链路, 生成器生命周期
- 生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。
- 每调用生成器的 next() 方法进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。
- 生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果
def lifecycle_gen(): print("Stage 1") yield "A" print("Stage 2") yield "B" print("Stage 3") yield "C" print("End of generator") gen = lifecycle_gen() try: print(next(gen)) # Stage1 -> A print(next(gen)) # Stage2 -> B print(next(gen)) # Stage3 -> C print(next(gen)) # End -> StopIteration except StopIteration: print("Generator exhausted")
- 案例二: 使用生成器实现无限序列,例如自然数序列
def natural_numbers(): num = 1 while True: yield num num += 1 # 使用生成器 numbers = natural_numbers() for _ in range(5): print(next(numbers)) # 输出 1, 2, 3, 4, 5
- 使用了 yield 的函数被称为生成器(generator),是一种特殊的迭代器,使用
-
yield vs return
特性 | yield | return |
---|---|---|
函数状态 | 保持 | 清除 |
执行次数 | 多次 | 一次 |
返回值 | 生成器对象 | 单个值 |
- 迭代器 vs 生成器
特性 | 迭代器 | 生成器 |
---|---|---|
实现方式 | 类+协议方法 | 函数+yield |
内存占用 | 低 | 极低 |
代码复杂度 | 较高 | 较低 |
状态保持 | 显式管理 | 自动管理 |
适用场景 | 复杂迭代逻辑 | 简单/中等迭代需求 |
AI大模型版-高级函数和Lambda实战
Python的函数语法规范和返回值语法
-
函数语法格式
-
函数是一段可重复使用的代码块,通过名称调用执行, 使用 def 关键字
-
格式如下
def 函数名(参数): """文档字符串(可选)""" 代码块 return 返回值 # 可选
-
案例:计算两个数的和
def add(a, b): """返回a + b的结果""" result = a + b return result sum_result = add(3, 5) print(sum_result) # 8
-
-
参数传递
-
不可变对象(如整数、字符串、元组):函数内修改会创建新对象,不影响原数据。
def modify_num(n): n += 10 print("函数内n:", n) # 20 num = 10 modify_num(num) print("函数外num:", num) # 10(未改变)
-
可变对象(如列表、字典):函数内修改直接影响原对象。
def modify_list(lst): lst.append(4) print("函数内lst:", lst) # [1,2,3,4] my_list = [1,2,3] modify_list(my_list) print("函数外my_list:", my_list) # [1,2,3,4](原列表被修改)
-
-
参数返回值
-
return
语句用于退出函数,选择性地向调用方返回一个表达式, -
不带参数值的 return 语句返回 None:
-
返回单个值
def square(n): return n ** 2 print(square(4)) # 16
-
返回多个值(元组解包)
def calculate(a, b): return a + b, a - b, a * b sum_result, sub_result, mul_result = calculate(5, 3) print(sum_result) # 8
-
无返回值(隐式返回None)
def greet(name): print(f"Hello, {name}!") result = greet("Alice") # Hello, Alice! print(result) # None
-
拓展 None类型
-
唯一标识符:
None
是Python中的一个特殊常量,表示「空值」或「不存在的值」 -
数据类型的终点:任何变量被赋值为
None
时,其类型变为NoneType
a = None print(type(a)) # <class 'NoneType'>
类型 特性 示例 None
绝对空值,不可被实例化 x = None
False
布尔类型,逻辑假值 if not x:
0
/""
数值型/字符串的空状态 count = 0
/s = ""
[]
/{}
数据结构的空容器 lst = []
/dct = {}
-
必须显式赋值
# 错误示范:不能直接声明未初始化的变量 x = # SyntaxError # 正确方式必须给初始值(可以是None) x = None y = [None] * 5 # 创建包含5个None的列表
-
作为默认参数
def greet(name=None): print(f"Hello, {name or 'Guest'}!") greet() # 输出:Hello, Guest!
-
-
-
案例实战
- 学生成绩分析器,接收成绩列表,返回最高分、平均分
def analyze_scores(scores): """接收成绩列表,返回最高分、平均分""" max_score = max(scores) avg_score = sum(scores) / len(scores) return max_score, avg_score scores = [85, 92, 78, 90] max_val, avg_val = analyze_scores(scores) print(f"最高分:{max_val}, 平均分:{avg_val:.1f}")
Python参数类型高阶语法特性讲解
-
函数参数类型
参数类型 说明 示例代码 位置参数 按参数位置顺序传递 add(3, 5)
关键字参数 按参数名指定传递 add(b=5, a=3)
默认参数 参数定义时设置默认值 def greet(name="Guest"):
可变参数 接收任意数量参数 *args
(元组形式接收)关键字可变参数 接收任意数量的键值对 **kwargs
(字典形式接收)-
位置参数(Positional Arguments)
- 定义:按参数定义顺序传递,数量必须匹配。
def greet(name, message): print(f"{message}, {name}!") greet("Alice", "Hello") # Hello, Alice!
-
关键字参数(Keyword Arguments)
- 定义:按参数名指定,顺序可打乱
greet(message="Hi", name="Bob") # Hi, Bob!
-
默认参数(Default Arguments)
- 定义:参数定义时指定默认值,调用时可省略。
def register(name, age=18, city="北京"): print(f"姓名:{name}, 年龄:{age}, 城市:{city}") register("小明") # 年龄和城市使用默认值 register("小红", 20, "上海") # 覆盖默认值
-
可变位置参数(args)
- 定义:接收任意数量的位置参数,打包为元组。
def sum_numbers(*args): return sum(args) print(sum_numbers(1,2,3)) # 6 print(sum_numbers(4,5)) # 9
-
可变关键字参数(kwargs)
- 定义:接收任意数量的关键字参数,打包为字典。
def print_info(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}") print_info(name="Alice", age=25) # 输出: # name: Alice # age: 25
-
参数顺序规则
- 标准顺序:
位置参数 → 默认参数 → *args → **kwargs
def add(a, b, c): return a + b + c numbers = [1, 2, 3] print(add(*numbers)) # 6(等价于 add(1,2,3))
- 标准顺序:
-
-
参数解包(Unpacking)
-
列表/元组解包为位置参数(*)
def add(a, b, c): return a + b + c numbers = [1, 2, 3] print(add(*numbers)) # 6(等价于 add(1,2,3))
-
字典解包为关键字参数( ** )
def greet(name, message): print(f"{message}, {name}!") params = {"name": "Alice", "message": "Hello"} greet(**params) # Hello, Alice!
-
-
常见错误
-
参数顺序错误
# 错误:位置参数在关键字参数后 func(a=1, 2) # SyntaxError # 正确:位置参数在前 func(2, a=1)
-
-
综合案例
-
混合参数类型
def complex_func(a, b, c=0, *args, **kwargs): print("a:", a) print("b:", b) print("c:", c) print("args:", args) print("kwargs:", kwargs) complex_func(1, 2, 3, 4, 5, name="Alice", age=25) # 输出: # a: 1 # b: 2 # c: 3 # args: (4,5) # kwargs: {'name': 'Alice', 'age':25}
-
动态生成SQL查询
def build_sql(table, **conditions): query = f"SELECT * FROM {table}" if conditions: where_clause = " AND ".join([f"{key} = '{value}'" for key, value in conditions.items()]) query += f" WHERE {where_clause}" return query print(build_sql("users", name="Alice", age=25)) # SELECT * FROM users WHERE name = 'Alice' AND age = '25'
-
匿名函数Lambda讲解和案例实战
-
什么是Lambda函数
- 定义:Lambda函数是匿名的小型函数,用于简化简单函数的定义。
- 语法:
lambda 参数: 表达式
- 无名称:无需
def
关键字定义。 - 单行表达式:只能包含一个表达式,不能有代码块或复杂逻辑; 可以设置多个参数,参数使用逗号 , 隔开
- 无名称:无需
# 示例1:加法函数 add = lambda a, b: a + b print(add(3, 5)) # 8 # 示例2:平方函数 square = lambda x: x**2 print(square(4)) # 16 #作为函数参数传递 def operate(func, a, b): return func(a, b) result = operate(lambda x, y: x * y, 3, 4) print(result) # 12
-
Lambda与普通函数的对比
特性 Lambda函数 普通函数(def) 名称 匿名 需命名 代码量 单行 可多行 适用场景 简单逻辑(如表达式计算) 复杂逻辑(如循环、条件嵌套) 返回值 自动返回表达式结果 需显式 return
-
Lambda与列表推导式对比
场景 Lambda + map/filter 列表推导式 简单转换/过滤 可读性较低 更简洁直观 函数作为参数传递 必须使用Lambda 无法直接替代 性能 相近(但生成器更节省内存) 相近 # 示例:筛选偶数并平方 numbers = [1, 2, 3, 4, 5] # Lambda + map/filter result1 = list(map(lambda x: x**2, filter(lambda x: x%2==0, numbers))) # 列表推导式 result2 = [x**2 for x in numbers if x%2 ==0] print(result1, result2) # [4, 16] [4, 16]
-
注意
-
仅限单个表达式,无法包含复杂逻辑(如循环、多条件分支)
# 错误示例:Lambda中不能包含if-else代码块 invalid = lambda x: if x > 0: x else -x # SyntaxError # 正确做法:使用条件表达式 valid = lambda x: x if x > 0 else -x print(valid(-5)) # 5
-
可读性较低, 过度使用会降低代码可读性,尤其是嵌套Lambda
# 复杂Lambda示例(不推荐) complex_lambda = lambda x: (lambda y: x + y)(10) print(complex_lambda(5)) # 15
-
数据处理领域Python高阶函数实战
-
什么是高阶函数
- 满足以下任一条件的函数
- 接收函数作为参数
- 返回函数作为结果
- 优势
- 代码简洁:通过组合函数减少重复代码。
- 功能灵活:动态指定处理逻辑
- 组合应用
- lambda 函数通常与内置高阶函数如 map()、filter() 和 reduce() 一起使用,在集合上执行操作
- 满足以下任一条件的函数
-
常用高阶函数详解
-
map(func, iterable)
- 功能:对可迭代对象的每个元素应用func,返回迭代器。
- 示例:将列表元素平方
numbers = [1, 2, 3, 4] squared = map(lambda x: x**2, numbers) print(list(squared)) # [1, 4, 9, 16]
- 对比列表推导式
squared = [x**2 for x in numbers] # 结果相同,但立即生成列表
-
filter(func, iterable)
- 功能:筛选满足func条件为True的元素,返回迭代器。
- 示例:过滤偶数
even = filter(lambda x: x % 2 == 0, numbers) print(list(even)) # [2, 4]
- 对比生成器表达式:
even = (x for x in numbers if x % 2 == 0) # 惰性计算,节省内存
-
reduce(func, iterable[, initial])
- 功能:对元素累积应用func,返回单一结果。
- 导入:
from functools import reduce
- 应用场景:累积计算(求和、求积、合并字典等)。
- 示例:计算乘积
from functools import reduce product = reduce(lambda a, b: a * b, [1, 2, 3, 4]) print(product) # 24(计算过程:((1*2)*3)*4)
-
sorted(iterable, key=None, reverse=False)
- 功能:排序可迭代对象,key指定排序规则。
- 示例:按字符串长度排序
words = ["apple", "banana", "cherry", "date"] sorted_words = sorted(words, key=lambda x: len(x)) print(sorted_words) # ['date', 'apple', 'banana', 'cherry']
-
-
高阶函数综合应用案例
- 数据清洗管道
data = [5, 12, 8, "10", 3.5, "7"] # 步骤1:过滤非整数 filtered = filter(lambda x: isinstance(x, int), data) # 步骤2:转换为绝对值 processed = map(abs, filtered) print(list(processed)) # [5, 12, 8, 3]
- 字典合并(使用reduce)
from functools import reduce dicts = [{"a": 1}, {"b": 2}, {"a": 3, "c": 4}] merged = reduce(lambda d1, d2: {**d1, **d2}, dicts) print(merged) # {'a':3, 'b':2, 'c':4}
-
注意事项
-
惰性求值与内存管理
- map、filter返回迭代器,适合处理大数据(逐项处理,不一次性加载到内存)
large_data = range(1_000_000) squared_iter = map(lambda x: x**2, large_data) # 内存友好
-
可读性与简洁的平衡
- 避免过度嵌套:复杂的
lambda
或链式调用可能降低代码可读性。
# 不推荐:难以理解的嵌套 result = map(lambda x: x*2, filter(lambda x: x>5, data)) # 推荐:分步或使用生成器表达式 filtered = (x for x in data if x > 5) result = (x*2 for x in filtered)
- 避免过度嵌套:复杂的
-
性能考量
- 列表推导式 vs map/filter:性能相近,选择更易读的方式。
- reduce的替代方案:显式循环有时更直观
-
AI大模型版-Python异常处理和OOP编程
异常处理Try-Except语法案例实战
-
为什么需要异常处理
- 避免程序崩溃:处理运行时错误(如文件不存在、网络中断),提升健壮性。
- 提供友好提示:将错误信息转换为用户易懂的内容。
- 资源清理:确保文件、网络连接等资源正确释放
-
基本语法
try: # 可能引发异常的代码 代码块 except 异常类型1: # 处理异常类型1 代码块 except 异常类型2 as e: # 处理异常类型2,并获取异常对象e 代码块 else: # 未发生异常时执行的代码 代码块 finally: # 无论是否异常,最后都会执行的代码(如资源清理) 代码块
-
常见异常类型
异常类型 触发场景 示例代码 ZeroDivisionError
除以零 10 / 0
FileNotFoundError
文件不存在 open("missing.txt")
ValueError
类型转换失败 int("abc")
IndexError
索引越界 lst = [1]; lst[2]
KeyError
字典键不存在 d = {"a":1}; d["b"]
TypeError
操作类型不匹配 "a" + 1
-
案例实战
-
基础异常捕获
try: num = int(input("请输入一个数字:")) result = 100 / num print(result) except ValueError: print("请输入数字!") except ZeroDivisionError: print("除数不能为0!") except Exception as e: print("未知错误:", e) else: print("没有异常发生!") finally: print("无论是否发生异常,都会执行!")
-
捕获多个异常
try: with open("data.txt", "r") as f: content = f.read() except (FileNotFoundError, PermissionError) as e: print(f"文件操作失败:{e}")
-
-
自定义异常类
class InvalidAgeError(Exception): """年龄无效异常""" def __init__(self, age): self.age = age super().__init__(f"年龄{age}无效,必须大于0!") def set_age(age): if age <= 0: raise InvalidAgeError(age) print("年龄设置成功:", age) # 测试 try: set_age(-5) except InvalidAgeError as e: print(e) # 输出:年龄-5无效,必须大于0!
Python面向对象编程OOP编程语法
简介: Python面向对象编程OOP编程语法
-
Python的面向对象OOP
- 基本结构
class 类名: """类文档字符串""" #定义基本属性, 类属性 = 初始值 ,所有实例共享,类似java的static静态变量 name = "小滴课堂-隔壁老王" #定义私有属性,私有属性在类外部无法直接进行访问,采用 __ 进行表示, 如果方法开头是__ 表示私有方法 __weight = 0 def __init__(self, 参数1, 参数2): # 构造方法 self.实例属性1 = 参数1 self.实例属性2 = 参数2 def 实例方法(self): """方法文档字符串""" return self.实例属性1 #类方法,由哪一个类调用的方法,方法内的cls就是哪一个类的引用 @classmethod def 类方法(cls): return cls.类属性 #静态方法,参数随意,没有“self”和“cls”参数,但是方法体中不能使用实例的任何属性和方法 @staticmethod def 静态方法(): return "与类和实例无关"
-
类有一个名为
__init__(self)
的特殊方法(和Java的构造函数类似)- 该方法在类实例化时会自动调用,默认是无参构造函数
- 可以有参数,参数通过
__init__(self,param1,param2)
传递到类的实例化
-
self
代表类的实例,而非类,和Java中的 this类似- 当定义一个类,并在类中定义方法时,第一个参数通常被命名为 self,尽管可以使用其他名称
- 方法中需要传递self才可以使用成员变量
- 它是一个指向实例的引用,使得类的方法能够访问和操作实例的属性。
-
属性区别(类属性、实例属性)
- 案例代码
class Dog: # 类属性(所有实例共享) species = "Canis familiaris" def __init__(self, name, age): # 实例属性(每个对象独立) self.name = name self.age = age
- 核心对比
特征 类属性 实例属性 存储位置 类内部定义,类自身维护 __init__
中定义,实例独立存储共享性 所有实例共享同一份值 每个实例拥有独立副本 修改影响 修改后,所有实例访问到新值 修改仅影响当前实例 内存占用 全类共用1份内存 每个实例单独占用内存 典型用途 常量、计数器、全局配置 对象个性化数据 - 类属性操作
# 定义类 class Car: wheels = 4 # 类属性 def __init__(self, color): self.color = color # 实例属性 # ✅ 正确访问 print(Car.wheels) # 4 (通过类访问) # ✅ 通过实例访问(返回类属性值) my_car = Car("red") print(my_car.wheels) # 4 # ❗ 危险操作:通过实例"修改"类属性 my_car.wheels = 5 # 实际创建了实例属性! print(my_car.wheels) # 5 (实例属性) print(Car.wheels) # 4 (类属性未变) # ✅ 正确修改类属性 Car.wheels = 6 new_car = Car("blue") print(new_car.wheels) # 6 (所有新实例生效) print(my_car.wheels) # 5 (旧实例仍访问自己的实例属性)
- 实例属性操作
# 定义类 class Student: school = "清华大学" # 类属性 def __init__(self, name): self.name = name # 实例属性 # 创建实例 stu1 = Student("张三") stu2 = Student("李四") # 修改实例属性 stu1.name = "王五" print(stu1.name) # 王五 print(stu2.name) # 李四 (不受影响) # 动态添加实例属性 stu1.gpa = 3.8 # 仅为stu1添加新属性 print(stu1.gpa) # 3.8 print(stu2.gpa) # AttributeError
-
完整案例:学生类
class Student: school = "清华大学" # 类属性,所有实例属性共享 def __init__(self, name, age): self.name = name # 实例属性 self.age = age self.__secret = "考试没及格" # 私有属性 def study(self, subject): print(f"{self.name}正在学习{subject}") self.__update_record() # 调用私有方法 def __update_record(self): # 私有方法 print("学习记录已更新") @classmethod def change_school(cls, new_school): cls.school = new_school print(f"学校已变更为{new_school}") @staticmethod def get_school_year(): return "2023-2024学年" # 使用示例 stu1 = Student("张三", 20) stu1.study("Python") # 张三正在学习Python → 学习记录已更新 Student.change_school("北京大学") # 修改类属性 print(Student.get_school_year()) # 2023-2024学年
OOP编程进阶和综合案例实战
-
多个方法对比
方法类型 装饰器 第一个参数 访问权限 典型应用场景 实例方法 无 self 实例属性和类属性 对象的具体行为实现 类方法 @classmethod cls 只能访问类属性 工厂方法、操作类属性 静态方法 @staticmethod 无 不能访问实例和类属性 工具函数、与类相关的计算 -
类方法深度案例
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_str): """工厂方法:从'YYYY-MM-DD'字符串创建实例""" year, month, day = map(int, date_str.split('-')) return cls(year, month, day) @classmethod def get_current_date(cls): """获取当前日期(演示类方法访问类属性)""" import datetime now = datetime.datetime.now() return cls(now.year, now.month, now.day) d1 = Date.from_string('2023-09-15') d2 = Date.get_current_date() print(f"{d2.year}-{d2.month}-{d2.day}") # 输出当前日期
-
静态方法深度案例
class MathUtils: @staticmethod def is_prime(num): """判断质数(工具方法)""" if num < 2: return False for i in range(2, int(num**0.5)+1): if num % i == 0: return False return True print(MathUtils.is_prime(17)) # True
-
cls方法拓展
- python中cls代表的是类的本身,相对应的self则是类的一个实例对象。
- cls等同于类本身,类方法中可以通过使用cls来实例化一个对象。
class Person(object): def __init__(self, name, age): self.name = name self.age = age print('self:', self) # 定义build方法,返回一个person实例对象,这个方法等价于Person()。 @classmethod def build(cls): # cls()等于Person() p = cls("老王八", 18) print('cls:', cls) return p person = Person.build() print(person, person.name, person.age)
-
综合案例:学生管理系统
# 魔术方法__repr__和__str__,在打印对象时能够获得对象的当前状态信息 # 如果一个类没有定义__str__方法,Python解释器会调用内置的__repr__方法来获取对象的字符串表示。 # 如果也没有定义__repr__方法 即 representation,Python会使用默认的字符串表示形式,即返回对象在计算机内存中的实际地址。 class StudentManager: __students = [] # 私有类属性 def __init__(self, name): self.manager_name = name def add_student(self, student): self.__students.append(student) print(f"{student} 已添加") @classmethod def get_student_count(cls): return len(cls.__students) @staticmethod def validate_age(age): return 15 <= age <= 60 def __str__(self): return f"管理员:{self.manager_name},管理学生数:{len(self.__students)}" class Student: def __init__(self, name, age): if not StudentManager.validate_age(age): raise ValueError("无效年龄") self.name = name self.age = age def __repr__(self): return f"<Student {self.name}>" # 使用示例 mgr = StudentManager("王老师") try: s1 = Student("张三", 18) mgr.add_student(s1) # <Student 张三> 已添加 s2 = Student("李四", 12) # ValueError: 无效年龄 except ValueError as e: print(e) print(mgr) # 管理员:王老师,管理学生数:1 print(StudentManager.get_student_count()) # 1
OOP编程的继承和抽象方法-重写实战
-
Python面向对象编程里面的继承
- 代码复用:子类自动获得父类非私有属性和方法
- 扩展性:子类可以添加新属性和方法
- 多态基础:不同子类对同一方法的不同实现
- 语法特点
class ParentClass: # 父类定义 pass class ChildClass(ParentClass): # 继承语法 # 子类定义 pass
class Animal: # 基类/父类 def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("子类必须实现此方法") class Dog(Animal): # 派生类/子类 def speak(self): # 方法重写 return "汪汪!" class Cat(Animal): def speak(self): return "喵~"
-
方法重写
- 完全重写案例
class Vehicle: def run(self): print("交通工具运行中...") class Car(Vehicle): def run(self): # 完全重写 print("汽车在公路上行驶") v = Vehicle() v.run() # 交通工具运行中... c = Car() c.run() # 汽车在公路上行驶
- 扩展式重写(使用super())
class Phone: def __init__(self, brand): self.brand = brand def call(self, number): print(f"{self.brand}手机拨打:{number}") class SmartPhone(Phone): def __init__(self, brand, os): super().__init__(brand) # 调用父类构造 self.os = os def call(self, number): super().call(number) # 重用父类方法 print("正在使用网络通话功能") sp = SmartPhone("华为", "HarmonyOS") sp.call("13800138000") # 输出: # 华为手机拨打:13800138000 # 正在使用网络通话功能
-
super()工作原理
class Base: def __init__(self): print("Base初始化") class A(Base): def __init__(self): super().__init__() print("A初始化") class B(Base): def __init__(self): super().__init__() print("B初始化") class C(A, B): def __init__(self): super().__init__() print("C初始化") c = C() # 输出顺序: # Base初始化 # B初始化 # A初始化 # C初始化
-
抽象类和抽象方法
- 抽象基类
- Abstract Base Class,简称 ABC, 抽象基类是一种不能被实例化的类,主要作用是为其他子类定义一个公共的接口
- 抽象方法
- 抽象方法是定义在抽象基类中的方法,它只有方法签名,没有具体的实现。
- 子类必须重写抽象方法并提供具体的实现
from abc import ABC, abstractmethod #Shape是一个抽象基类,area是一个抽象方法。因为Shape继承了ABC, #且area方法使用了@abstractmethod装饰器,所以Shape不能被实例化,子类必须实现area方法 class Shape(ABC): @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14 * self.radius ** 2 def perimeter(self): return 2 * 3.14 * self.radius # 尝试实例化抽象类会报错 # s = Shape() # TypeError c = Circle(5) print(c.area()) # 78.5
- 抽象基类
AI大模型版-文件处理和常用模块实战
Python文件IO操作案例最佳实践
-
文件操作步骤
- 打开文件:建立程序与文件的连接。
- 读写操作:读取内容或写入数据。
- 关闭文件:释放系统资源。
-
文件打开模式
模式 说明 文件存在 文件不存在 r
只读(文本模式,默认) 正常打开 报错 w
写入(覆盖原有内容) 清空内容 创建新文件 a
追加(在文件末尾添加) 正常打开 创建新文件 r+
读写(文件指针在开头) 正常打开 报错 b
二进制模式(配合其他模式) - -
文件操作语法与案例
-
常规操作文件打开读取
# 不推荐(可能忘记关闭文件) file = open("test.txt", "r") try: content = file.read() finally: file.close() # 必须手动关闭 # 推荐始终使用with语句:自动处理文件关闭,避免资源泄漏。 with open("test.txt") as f: content = f.read()
-
with open语法结构
file_path
(必需) 要操作的文件路径(字符串类型),可以是绝对路径或相对路径。 示例:"data.txt"
、"/home/user/docs/file.csv"
mode
(可选,默认为'r'
) 指定文件的打开模式,常用模式如下
with open(file_path, mode='r', encoding=None, ...) as file_object: # 在此代码块内操作文件 # 文件会自动关闭(无论是否发生异常)
-
基础文件读写 (明确指定文件编码:避免中文乱码,优先使用
utf-8
。)# 写入文件(覆盖模式) with open("test.txt", "w", encoding="utf-8") as f: f.write("Hello, World!\n") f.write("第二行内容") # 读取文件(全部内容) with open("test.txt", "r", encoding="utf-8") as f: content = f.read() print(content) # 输出: # Hello, World! # 第二行内容
-
逐行读取和写入
# 写入多行 lines = ["Python\n", "Java\n", "C++\n"] with open("languages.txt", "w", encoding="utf-8") as f: f.writelines(lines) # 逐行读取(推荐方式) with open("languages.txt", "r", encoding="utf-8") as f: for line in f: # 自动处理大文件,内存友好 print(line.strip()) # 移除换行符 # 输出: # Python # Java # C++
-
追加内容
with open("test.txt", "a", encoding="utf-8") as f: f.write("\n追加的内容")
-
读取特定行数
with open("test.txt", "r", encoding="utf-8") as f: first_line = f.readline() # 读取第一行 next_lines = f.readlines() # 读取剩余所有行(返回列表)
-
二进制文件操作(图片复制)
with open("input.jpg", "rb") as src, open("output.jpg", "wb") as dst: dst.write(src.read()) # 复制图片
-
Python的pip模块安装和管理实战
-
什么是pip
- 是 Python 包管理工具,该工具提供了对 Python 包的查找、下载、安装、卸载的功能,类似Maven, Npm
- Python 2.7.9 + 或 Python 3.4+ 以上版本都自带 pip 工具
- 管理超过40万个PyPI软件包
- 在 VS Code中,有一个免费扩展(Extension):PIP Manager ,可以很方便查看和调整相关的软件依赖模块
-
核心操作
-
安装与管理包
# 安装最新版本 pip install requests # 安装指定版本 pip install numpy==1.21.0 # 升级包版本 pip install --upgrade requests # 卸载包 pip uninstall pandas #更新pip版本 python -m pip install --upgrade pip
-
环境检查
# 查看已安装包列表 pip list # 检查过期包 pip list --outdated # 显示包详细信息 pip show flask
-
导出当前 Python 环境的配置
- 一个项目里面会有很多依赖包,可以通过pip freeze查看和导出
- 包括网上下载的Python项目,可以通过这个命令安装对应的环境
# 导出环境依赖 pip freeze > requirements.txt # 从文件安装依赖 pip install -r requirements.txt
-
-
镜像源配置加速
# 临时使用镜像源安装依赖包 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy # 永久配置镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple #很多源 清华 https://pypi.tuna.tsinghua.edu.cn/simple 腾讯 http://mirrors.cloud.tencent.com/pypi/simple 阿里 https://mirrors.aliyun.com/pypi/simple/ # 更换阿里源 pip config set global.index-url https://mirrors.aliyun.com/pypi/simple # 更换清华源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 更换腾讯源 pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple # 更换中科大源 pip config set global.index-url https://pypi.mirrors.ustc.edu.cn/simple #恢复默认源 pip config set global.index-url https://pypi.org/simple
- 配置文件管理
配置文件路径: Windows: %APPDATA%\pip\pip.ini macOS/Linux: ~/.config/pip/pip.conf
- 查看系统配置的镜像源操作
pip config list
Python常用内置模块案例实战
- 讲解Python自带高频模块使用
模块 | 典型应用场景 |
---|---|
os |
文件管理、路径操作 |
json |
API数据交换、配置文件读写 |
sys |
脚本参数处理、程序退出控制 |
random |
生成随机数据、游戏开发 |
math |
科学计算、几何问题 |
-
os模块:操作系统交互
- 核心功能:文件/目录操作、路径管理、环境变量。
- 常用函数与案例
import os # 1.1 获取当前工作目录 current_dir = os.getcwd() print("当前目录:", current_dir) # 输出:/Users/yourname/projects # 1.2 遍历目录下的文件 files = os.listdir(".") # 当前目录所有文件和子目录 print("文件列表:", files) # 1.3 路径拼接(跨平台安全) file_path = os.path.join("data", "test.txt") # data/test.txt(Windows自动转反斜杠) # 1.4 创建目录(如果不存在) if not os.path.exists("backup"): os.makedirs("backup") # 创建多级目录 # 1.5 删除文件 os.remove("temp.txt") # 文件不存在会报错
-
json模块:JSON数据处理
- 核心功能:序列化(Python对象→JSON字符串)、反序列化(JSON→Python对象)。
- 常用函数与案例
import json # 2.1 字典转JSON字符串 data = {"name": "小明", "age": 18, "is_student": True} json_str = json.dumps(data, ensure_ascii=False) # 禁用ASCII转码 print("JSON字符串:", json_str) # {"name": "小明", "age": 18, "is_student": true} # 2.2 JSON字符串转字典 data_restored = json.loads(json_str) print("恢复的字典:", data_restored["name"]) # 小明 # 2.3 读写JSON文件 # 写入 with open("user.json", "w", encoding="utf-8") as f: json.dump(data, f, indent=4) # 缩进美化 # 读取 with open("user.json", "r", encoding="utf-8") as f: loaded_data = json.load(f) print("文件内容:", loaded_data)
-
sys模块:系统相关操作
- 核心功能:命令行参数、程序退出、系统路径。
- 常用函数与案例
import sys # 3.1 获取命令行参数 # 运行命令:python script.py arg1 arg2 print("脚本名:", sys.argv[0]) # script.py print("第一个参数:", sys.argv[1]) # arg1 # 3.2 强制退出程序(带状态码) if len(sys.argv) < 2: print("缺少参数!") sys.exit(1) # 非0表示异常退出 # 3.3 添加自定义模块搜索路径 sys.path.append("/my_modules") # 临时添加
-
random模块:随机数生成
- 核心功能:生成伪随机数、随机选择元素。
- 常用函数与案例
import random # 4.1 生成随机整数(包含两端) num = random.randint(1, 10) # 1~10之间的整数 print("随机数:", num) # 4.2 随机选择元素 fruits = ["苹果", "香蕉", "橘子"] choice = random.choice(fruits) # 随机选一个 print("随机水果:", choice) # e.g. 橘子 # 4.3 打乱列表顺序(原地修改) cards = ["A", "2", "3", "J", "Q", "K"] random.shuffle(cards) print("洗牌后:", cards) # e.g. ['Q', 'A', '3', ...] # 4.4 生成随机验证码(6位字母+数字) import string chars = string.ascii_letters + string.digits # 所有字母和数字 code = ''.join(random.choices(chars, k=6)) # 生成6位 print("验证码:", code) # e.g. aB3dE7
-
math模块:数学运算
- 核心功能:数学函数、常数。
- 常用函数与案例
import math # 5.1 计算平方根和幂 a = math.sqrt(25) # 5.0 b = math.pow(2, 3) # 8.0 print(f"平方根: {a}, 幂: {b}") # 5.2 向上/向下取整 c = math.ceil(3.2) # 4 d = math.floor(3.8) # 3 print(f"向上取整: {c}, 向下取整: {d}") # 5.3 常数π和弧度转换 radius = 5 area = math.pi * radius ** 2 # 圆面积公式 degrees = math.degrees(math.pi) # 180.0 print(f"圆面积: {area:.2f}, 弧度转角度: {degrees}") # 5.4 对数运算 log_value = math.log(100, 10) # 以10为底的log(100) → 2.0 print("对数结果:", log_value)
大模型
LLM大模型开发核心-LangChain框架实战
LLM开发框架LangChain介绍和技术生态
-
背景需求
-
大模型(如ChatGPT、DeepSeek)的局限性:
- 无法获取训练数据外的实时信息(如今天的天气)
- 不能直接执行具体操作(发邮件/查数据库)
- 处理复杂任务时缺乏步骤规划能力
-
开发者的痛点
// 传统Java开发模式 vs AI应用开发 String result = service.doSomething(input); // 确定性结果 // VS String aiResponse = llm.generate(prompt); // 非确定性输出
-
-
什么是LangChain框架(类似SpringCloud)
- 是一个基于大型语言模型(LLM)开发应用程序的框架,专为构建与大语言模型(LLMs)相关的应用而设计。
- 通过将多个 API、数据源和外部工具无缝集成,LangChain 能帮助开发者更高效地构建智能应用。
- 从与 OpenAI 、DeepSeek等顶级大模型供应商的集成,到复杂的对话系统、智能搜索、推荐系统等
- LangChain 提供了丰富的功能和灵活的接口,极大地方便了开发者的工作。
- 通俗点:LangChain 就是对各类大模型提供的 API 的套壳,方便开发者使用这些 API和协议,搭建起来的模块和接口组合
- 官网:https://www.langchain.com
- GIthub地址:https://github.com/langchain-ai/langchain
-
LangChain生态产品介绍
-
LangChain
- 提供模块化开发能力,支持LLM(如GPT、Claude等)与外部数据源(数据库、API)的集成
- 包含链(Chain)、代理(Agent)、记忆(Memory)等核心组件,用于构建复杂AI应用
-
LangServer
- 部署工具,可将LangChain应用快速转换为REST API,支持并行处理、流式传输和异步调用
- 自动生成OpenAPI文档, 滚动更新支持, 内置Prometheus指标, 适用于企业级生产环境
-
LangSmith
- 开发者调试与监控平台,支持对LLM应用的性能分析、测试和部署优化
- 提供可视化调试界面和自动化评估工具,提升开发效率与可靠性
-
LangGraph
- 状态管理工具,用于构建多代理系统,支持流式处理和复杂任务分解
- 可视化流程设计器, 循环/条件分支支持,分布式状态持久化, 自动断点续跑
-
-
产品矩阵对比
产品 核心价值 Java生态对标 适用场景 LangSmith 全生命周期可观测性 Prometheus + Grafana 生产环境监控、效果评估 LangServe 快速服务化 Spring Boot 模型API部署、快速原型 LangGraph 复杂流程编排 Activiti BPMN 业务工作流设计、状态管理 LangChain Core 基础组件库 Spring AI 基础AI功能开发
Python虚拟环境evn应用讲解和实战
-
什么是Python的虚拟环境
- 类似虚拟机、沙箱机制一样,隔离不同的项目依赖的环境
- 核心作用
- 隔离项目依赖:不同项目可能依赖同一库的不同版本。
- 避免全局污染:防止安装过多全局包导致冲突。
- 便于协作:通过依赖清单(如
requirements.txt
)复现环境
-
虚拟环境 vs 全局环境
特性 虚拟环境 全局环境 依赖隔离 项目独立,互不影响 所有项目共享 安全性 避免权限问题(无需sudo安装) 需谨慎操作(可能影响系统) 适用场景 开发、测试、多版本项目 系统级工具或少量通用库 -
镜像源配置
-
查看系统配置的镜像源操作
pip config list pip config get global.index-url
-
配置国内镜像源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
-
-
虚拟环境基础操作
- 创建虚拟环境
# 语法:python -m venv <环境目录名> python -m venv myenv # 创建名为myenv的虚拟环境
-
激活虚拟环境
- Windows(CMD/PowerShell)
myenv\Scripts\activate.bat # CMD myenv\Scripts\Activate.ps1 # PowerShell(需管理员权限解除限制)
- Linux/macOS:
source myenv/bin/activate
- 激活后提示符变化
source myenv/bin/activate
-
退出虚拟环境
deactivate
-
依赖管理
- 安装库到虚拟环境
# 激活环境后操作 (myenv) pip install requests # 安装最新版本 (myenv) pip install django==3.2 # 安装指定版本
- 导出依赖清单
(myenv) pip freeze > requirements.txt
- 从清单恢复依赖
# 在新环境中执行 (myenv) pip install -r requirements.txt
-
最佳实践与案例
- 典型项目流程(区分Linux、Mac和Window)
# 创建项目目录并进入 mkdir myproject && cd myproject # 创建虚拟环境 python -m venv .venv # 激活环境(Windows: .venv\Scripts\activate) source .venv/bin/activate # 安装依赖 pip install django pandas # 导出依赖 pip freeze > requirements.txt # 开发完成后退出 deactivate
- 协作复现环境
# 克隆项目后操作 git clone https://github.com/user/project.git cd project # 创建并激活虚拟环境 python -m venv .venv source .venv/bin/activate # 安装依赖 pip install -r requirements.txt
-
常见问题与解决
- 虚拟环境激活失败
- 现象:source: command not found
- 原因:在Windows使用Linux命令或在Linux未使用source。
- 解决:根据操作系统选择正确激活命令。
- 跨平台路径问题
- 问题:Windows与Linux路径格式差异导致脚本无法运行。
- 方案:使用/统一路径分隔符,或在代码中处理路径
- 依赖版本冲突
- 场景:项目A需要
numpy==1.18
,项目B需要numpy==1.20
。 - 解决:为每个项目创建独立虚拟环境。
- 场景:项目A需要
- 虚拟环境激活失败
-
案例实战:
- LangChain框架环境搭建
VSCode编辑器LangChain环境安装和验证
-
Python虚拟环境和项目创建
- 创建虚拟环境(Windows/macOS/Linux通用)
# 创建环境目录 python -m venv langchain_env
-
激活虚拟环境
- Windows
.\langchain_env\Scripts\activate
- macOS/Linux
source langchain_env/bin/activate
-
验证环境
# 查看Python路径(应显示虚拟环境路径) which python # macOS/Linux where python # Windows
-
LangChain环境安装
-
安装核心依赖包 (版本和课程保持一致,不然很多不兼容!!!)
- 下载相关资料 ,使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/aipan_install_1.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/aipan_install_1.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/aipan_install_1.zip
-
解压后执行【依赖很多,版本差异大,务必按照下面执行,否则课程无法进行下去,加我微信 xdclass6】
# 安装依赖 pip install -r requirements.txt
-
验证安装【很多模块后续使用会验证】
# 执行简单测试 from langchain_core.prompts import ChatPromptTemplate print(ChatPromptTemplate.from_template("Hello 欢迎来到小滴课堂-AI大模型开发课程 {title}!").format(title=",干就完了")) # 应输出: Human: Hello 欢迎来到小滴课堂-AI大模型开发课程 ,干就完了!
-
LangChain框架模块和大模型IO交互链路讲解
简介: LangChain框架模块和大模型IO交互链路讲解
- 大模型IO交互链路概览
-
LangChain模块对比大家熟知的Java Spring生态
LangChain模块 Spring对应技术 交互方式差异 Models Spring AI 多模型热切换支持 Memory Redis/Hazelcast 内置对话上下文管理 Chains Activity工作流 动态流程重组能力 Agents Drools规则引擎 基于LLM的决策机制 -
LangChain架构六大模块(后续围绕模块逐步深入)
-
Models(模型层)
- 相当于
interface LLM
,支持多种大模型(OpenAI/Gemini等) - 示例:就像Java中的JDBC接口,可以对接不同数据库
- 相当于
-
Prompts(提示工程)
- 相当于模板引擎(类似Thymeleaf)
from langchain.prompts import PromptTemplate template = """ 你是一个Java专家,请用比喻解释{concept}: 要求: 1. 用{framework}框架做类比 2. 不超过2句话 """ prompt = PromptTemplate.from_template(template) print(prompt.format(concept="机器学习", framework="Spring"))
-
Chains(任务链)
- 类似Java的工作流引擎,将多个组件组合在一起,创建一个单一、连贯的任务
- 包括不同的链之间组合
from langchain.chains import LLMChain # 创建任务链(类似Java的链式调用) chain = LLMChain(llm=model, prompt=prompt) result = chain.run(concept="多线程", framework="Spring Batch")
-
Memory(记忆)
- 类似HTTP Session的会话管理
from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory() memory.save_context({"input": "你好"}, {"output": "您好!"})
-
Indexes(索引)
- 类似数据库索引+JDBC连接
- 对不通的文档进行结构化的方法,包括提取、切割、向量存储等,方便 LLM 能够更好的与之交互
from langchain.document_loaders import WebBaseLoader # 加载外部数据(类似JDBC读取数据库) loader = WebBaseLoader("https://docs.spring.io/spring-boot/docs/current/reference/html/") docs = loader.load()
-
Agents(智能体)
- 类似策略模式+工厂模式,比chain更高级,可以自己选择调用链路
- 比如下一步有多个选择, 包括不同工具、流程链路等,由程序自己选择
from langchain.agents import Tool, initialize_agent tools = [ Tool(name="Calculator", func=lambda x: eval(x), description="计算数学表达式") ] agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
-
-
常见分层设计和交互如下
+----------------+ | 应用层 (Agents) | +----------------+ | 编排层 (Chains) | +----------------+ | 能力层 (Tools) | +----------------+ | 模型层 (Models) | +----------------+ | 数据层 (Memory) | +----------------+
大模型Model-IO链路抽象和Chat模型开发
简介: 大模型Model-IO链路抽象和Chat模型实战
-
大模型使用开发的Model IO链路核心三要素
组件 作用 典型类/方法 Prompts 构建模型输入的结构化模板 ChatPromptTemplate
,FewShotPromptTemplate
Models 对接不同LLM服务的统一接口 ChatOpenAI
Parsers 将模型输出转换为结构化数据 StrOutputParser
,JsonOutputParser
-
LangChain支持的模型类型说明
- 文本生成模型(Text Generation Models-逐渐少用了,Chat更强大)
- 功能:生成连贯文本,主要用于处理文本相关的任务,如自然语言理解、文本生成、情感分析、翻译
- 典型模型:GPT-3、Claude、PaLM
- 对话模型(Chat Models,多数采用这个)
- 功能:处理多轮对话,人机交互中的对话能力,能够进行自然流畅的对话交流,适用于客户服务、智能助手
- 典型模型:GPT-4、Claude-2
- 嵌入模型(Embedding Models)
- 功能:生成文本向量表示,将文本转换为固定长度的向量表示,向量保留了数据的语义信息,便于后续的相似度计算、分类等任务。
- 典型模型:text-embedding-ada-002
- 多模态模型(Multimodal Models)
- 功能:处理文本+图像,例如文本、图像、音频等,更全面的信息理解和处理能力。
- 典型模型:GPT-4V、Qwen-omni-turbo
- 其他更多....
- 文本生成模型(Text Generation Models-逐渐少用了,Chat更强大)
-
LangChain开发LLM聊天模型快速编码实战
from langchain_openai import ChatOpenAI # 调用Chat Completion API llm = ChatOpenAI( model_name='qwen-plus', base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-0903038424424850a88ed161845d7d4c") response = llm.invoke('你是谁?') print(response)
类型增强模块Typing应用和案例》
-
Python的动态类型痛点
# 传统动态类型代码示例 def calculate(a, b): return a + b # 无法直观看出参数类型和返回值类型 result1 = calculate(3, 5) # ✅ 正确用法 result2 = calculate("3", 5) # ❌ 运行时才报错
-
什么是Typing模块
- 自python3.5开始,PEP484为python引入了类型注解(type hints), 为Python带来了类型提示和类型检查的能力。
- 允许开发者在代码中添加类型注解,提高代码的可读性和可维护性。
- 尽管Python是一种动态类型语言,但类型注解能让开发者更清晰地了解函数和变量的预期类型
- 核心
- 提升代码可读性:明确参数和返回类型
- 增强IDE支持:智能提示与自动补全
- 静态类型检查:开发阶段发现潜在错误
- 完善文档生成:自动生成类型化API文档
-
核心语法快速入门
-
简单类型(Primitive Types)
- Python内置的基本数据类型注解
- 适用场景:变量、函数参数、返回值的简单类型声明
- 类型注解不影响运行时行为
- 兼容子类型(如int注解可接受bool值)
age: int = 25 # 整数类型 name: str = "Alice" # 字符串类型 price: float = 9.99 # 浮点数类型 is_valid: bool = True # 布尔类型 data: bytes = b"binary" # 字节类型
-
容器类型
-
有多种内置的类型别名,比如
List
、Tuple
、Dict
等,可用于注解变量和函数的预期类型 -
例如
Dict[str, int]
表示键是字符串类型,值是整数类型的字典Set[int]
表示整数类型的集合
-
List
同质元素的序列容器- 适用场景:列表类型数据,元素类型相同
from typing import List scores: List[int] = [90, 85, 95] # 整型列表 matrix: List[List[float]] = [[1.1, 2.2], [3.3]] # 嵌套列表
-
Dict
键值对映射容器- 适用场景:字典类型数据,需指定键值类型
from typing import Dict person: Dict[str, str] = {"name": "Bob", "job": "dev"} # 字符串字典 config: Dict[str, Union[int, str]] = {"timeout": 30} # 混合值类型
-
Tuple
固定长度、类型的不可变序列- 适用场景:坐标、数据库记录等固定结构
- 变长声明:
Tuple[T, ...]
:元素类型相同但长度不限 , ():空元组
from typing import Tuple point: Tuple[float, float] = (3.14, 2.71) # 二元坐标 rgb: Tuple[int, int, int] = (255, 0, 128) # 颜色值 flexible: Tuple[str, ...] = ("a", "b", "c") # 任意长度元组
-
Set
无序不重复元素的集合- 适用场景:去重数据、集合运算
from typing import Set unique_ids: Set[int] = {1, 2, 3} # 整型集合 tags: Set[Union[str, int]] = {"urgent", 1001} # 混合类型集合
-
任意类型
Any
- 动态类型占位符,放弃类型检查, 应尽量避免过度使用
- 适用场景:兼容无类型代码或动态行为
from typing import Any def debug_log(obj: Any) -> None: print(repr(obj))
-
-
函数类型注解
- 为函数添加typing模块的注解后,函数使用者就能清晰的了解函数的参数以及返回值类型
def greet(name: str) -> str: # 参数类型 -> 返回值类型 return f"Hello, {name}" def calculate(a: int, b: int) -> int: return a * b # 无返回值使用None def show_info(info: str) -> None: print(info)
-
Literal
字面量类型- 精确值类型约束, 替代简单字符串枚举
- 适用场景:枚举值的类型安全
from typing import Literal # 限定特定值 HttpMethod = Literal["GET", "POST", "PUT", "DELETE"] def send_request(method: HttpMethod, url: str) -> None: print(f"Sending {method} request to {url}") send_request("POST", "/api") # ✅ send_request("PATCH", "/api") # ❌ mypy报错
-
-
Union
联合类型- Union允许参数接受多种不同类型的数据。
- 例如
Union[int, float]
表示变量可以是int类型或float类型
from typing import Union def process_input(value: Union[int, str]) -> None: if isinstance(value, int): print(f"Number: {value}") else: print(f"String: {value}") process_input(42) # Number: 42 process_input("test") # String: test
-
Optional
可选类型Optional
表示参数可以是指定类型或者None
- 让编译器识别到该参数有一个类型提示,可以使指定类型,也可以是None,且参数是可选非必传的。
Optional[int]
等价于Union[int, None]
,表示:既可以传指定的类型 int,也可以传 None,Optional[ ]
里面只能写一个数据类型- 适用场景:可能返回空值的操作
- 在下面函数定义中,Optional[str] = None表示参数name的类型可以是str或None。
- 注意
= None
可省略,它表示默认参数。- 从 Python 3.10 开始,Optional[Type] 可以直接用
Type | None
替代,写法更清晰
from typing import Optional def greet1(name: Optional[str] = None) -> str: if name: return f"Hello, {name}!" else: return "Hello, world!" def greet2(name: Optional[str]) -> str: if name: return f"Hello, {name}!" else: return "Hello, world!" print(greet1()) # print(greet2()) # 报错,必须要有参数 print(greet1("老王")) print(greet2("冰冰"))
-
类型别名
- 自定义类型别名提高代码可读性。
from typing import Tuple # 基本别名 UserId = int Point = Tuple[float, float] def get_user(id: UserId) -> str: return f"User{id}" def plot(points: List[Point]) -> None: for x, y in points: print(f"({x}, {y})")
-
NewType
新类型创建- 创建具有类型检查的语义化新类型
- 适合 区分相同基础类型的不同用途
from typing import NewType # 创建强类型 UserId = NewType('UserId', int) admin_id = UserId(1001) def print_id(user_id: UserId) -> None: print(user_id) # 正确调用 print_id(admin_id) # ✅ print_id(1001) # ❌ mypy报错
-
TypeVar
(类型变量)- 创建通用类型参数
- 适用场景:泛型函数/类的类型参数化;比如创建一个函数,无论是处理整数、字符串还是自定义对象
from typing import TypeVar, Sequence T = TypeVar('T') # 无约束类型 Num = TypeVar('Num', int, float) # 受限类型 def first(items: Sequence[T]) -> T: return items[0] def sum(values: Sequence[Num]) -> Num: return sum(values)
from typing import TypeVar # 定义一个泛型变量T T = TypeVar('T') # 创建一个泛型函数 def get_first_item(items: list[T]) -> T: """获取列表的第一个元素""" if items: return items[0] raise ValueError("列表为空") # 使用示例 numbers = [1, 2, 3, 4, 5] words = ['apple', 'banana', 'cherry', 'fruit'] print(get_first_item(numbers)) # 输出: 1 print(get_first_item(words)) # 输出: apple
Prompt提示词工程和案例最佳实践
大模型必备Prompt提示词工程
-
什么是Prompt Engineering提示词工程
-
通过特定格式的文本输入引导AI模型生成期望输出的技术,明确地告诉模型你想要解决的问题或完成的任务
-
也是大语言模型理解用户需求并生成相关、准确回答或内容的基础
-
类比:给Java程序员的任务需求文档(越清晰明确,结果越符合预期)
-
为什么需要学习?
- 大模型就是你的员工,你可以有多个助手,OpenAI、DeepSeek、千问等
- 作为老板的你,需要正确的下达任务,描述合理和交付目标等
传统编程:写代码→计算机执行 Prompt工程:写自然语言指令→大模型生成结果
-
-
Prompt设计四要素
-
角色设定(Role Prompting)
- 作用:限定模型回答视角
[差] 写一首关于春天的诗 [优] 你是一位擅长写现代诗的诗人,请用比喻手法创作一首8行的春天主题短诗
-
任务描述
- STAR原则:Situation 场景、Task 任务、Action 行动、Result 结果
(场景)用户提交了一个技术问题 (任务)需要给出准确且易懂的解答 (行动)分步骤说明解决方案 (结果)最后用一句话总结要点
-
格式规范
- 常用格式指令:分点列表、指定段落数、表格呈现、代码格式
用JSON格式输出包含以下字段: { "summary": "不超过50字的摘要", "keywords": ["关键词1", "关键词2", "关键词3"] }
-
约束条件
- 常见约束类型:
类型 示例 长度 "答案控制在200字内" 风格 "用初中生能理解的语言" 内容 "不包含专业术语" 逻辑 "先解释概念再举例说明" -
汇总
要素 说明 反面案例 优化案例 角色设定 明确模型身份 "帮我写代码" "你是一个资深Java架构师..." 任务说明 具体执行要求 "分析数据" "使用Markdown表格对比..." 输出格式 结构化结果定义 自由文本 JSON/XML/YAML格式 约束条件 限制输出范围 无限制 "不超过200字,不用专业术语"
-
-
模板结构设计(黄金公式)
# 标准三段式结构 prompt_template = """ [角色设定] 你是一个具有10年经验的{领域}专家,擅长{特定技能} [任务说明] 需要完成以下任务: 1. {步骤1} 2. {步骤2} 3. {步骤3} [输出要求] 请按照以下格式响应: {示例格式} """
-
常见问题和排查原因
现象 可能原因 解决方案 输出内容偏离主题 角色设定不明确 添加"忽略无关信息"约束 生成结果过于笼统 缺少具体步骤要求 添加"分步骤详细说明"指令 格式不符合要求 未提供明确格式示例 添加XML/JSON标记示例
Prompt提示词工程多案例最佳实践
-
需求
- 利用在线大模型或者本地大模型
- 测试不同的提示词效果,分析优化前、后的Prompt工程
- 案例实战:通用回答助手、代码生成助手、技术问答、AI数据分析 等案例实战
-
案例实战一:通用回答
-
差Prompt:
告诉我关于人工智能的信息
- 问题分析:过于宽泛,缺乏焦点、没有指定回答的深度和范围、未明确期望的格式
- 输出结果:可能得到从历史发展到技术原理的冗长概述,缺乏针对性
-
好prompt
你是一位科技专栏作家,请用通俗易懂的方式向高中生解释: 1. 什么是人工智能(用1个生活化比喻说明) 2. 列举3个当前主流应用场景 3. 字数控制在300字以内 要求使用「首先」、「其次」、「最后」的结构
- 优化后
- 设定回答视角(科技专栏作家)
- 明确目标受众(高中生)
- 结构化输出要求
- 添加格式约束
- 优化后
-
-
案例实战二:代码生成
-
差Prompt:
写个Python程序
- 问题分析:没有具体功能描述、未指定输入输出格式、忽略异常处理需求
- 输出结果:可能生成简单的"Hello World"程序,与真实需求不符
-
好Prompt:
编写一个Python函数,实现以下功能: - 输入:字符串形式的日期(格式:YYYY-MM-DD) - 输出:该日期对应的季度(1-4) - 要求: - 包含参数校验(不符合格式时抛出ValueError) - 使用datetime模块 - 编写对应的单元测试用例 示例: 输入 "2024-03-15" → 返回 1
- 优化后
- 明确定义输入输出
- 指定实现方式
- 包含测试要求
- 提供示例验证
- 优化后
-
-
案例实战三:技术问答
-
差Prompt
如何优化网站性能?
- 问题分析:问题范围过大、未说明技术栈、缺少评估标准
- 输出结果:可能得到泛泛而谈的通用建议
-
好Prompt
针对使用SpringBoot+Vue3的技术栈,请给出5项可量化的性能优化方案: 要求: 1. 每项方案包含: - 实施步骤 - 预期性能提升指标(如LCP减少20%) - 复杂度评估(低/中/高) 2. 优先前端和后端优化方案 3. 引用Web Vitals评估标准 限制条件: - 不涉及服务器扩容等硬件方案 - 排除已广泛采用的方案(如代码压缩)
- 优化点
- 限定技术范围
- 结构化响应要求
- 设定评估标准
- 排除已知方案
- 优化点
-
-
案例实战四:数据分析
-
差Prompt
分析这份销售数据
- 问题分析:未说明数据特征、没有指定分析方法、缺少可视化要求
- 输出结果:可能得到无重点的描述性统计,缺乏洞察
-
好Prompt
你是一位资深数据分析师,请完成以下任务: 数据集特征: - 时间范围:2027年1-12月 - 字段:日期/产品类别/销售额/利润率 要求: 1. 找出销售额top3的月份,分析增长原因 2. 识别利润率低于5%的产品类别 3. 生成包含趋势图的Markdown报告 输出格式: ## 分析报告 ### 关键发现 - 要点1(数据支撑) - 要点2(对比分析) ### 可视化 趋势图描述,生成base64编码的折线图
- 优化点:
- 明确分析者角色
- 描述数据集特征
- 指定分析方法论
- 规范输出格式
- 优化点:
-
LangChain 提示模板PromptTemplate介绍
-
需求
- 掌握LangChain 提示模板PromptTemplate常见用法
- 掌握提示词里面的占位符使用和预置变量
-
PromptTemplate介绍
- 是LangChain中用于构建结构化提示词的组件,负责将用户输入/动态数据转换为LLM可理解的格式
- 它是一种单纯的字符模板,后续还有进阶的ChatPromptTemplate
- 主要解决
- 动态内容组装
- 避免Prompt硬编码
- PromptTemplate核心变量和方法
- template 定义具体的模板格式,其中
{变量名}
是占位符 - input_variables 定义模板中可以使用的变量。
- partial_variables:前置变量,可以提前定义部分变量的值,预填充进去
- format 使用
format
方法填充模板中的占位符,形成最终的文本
- template 定义具体的模板格式,其中
-
案例实战
- 创建PromptTemplate对象简单模版
from langchain.prompts import PromptTemplate # 定义模板 template = """ 你是一位专业的{domain}顾问,请用{language}回答: 问题:{question} 回答: """ # 创建实例 prompt = PromptTemplate( input_variables=["domain", "language", "question"], template=template ) print(prompt) # 格式化输出 print(prompt.format( domain="网络安全", language="中文", question="如何防范钓鱼攻击?" ))
- 自动推断变量
from langchain.prompts import PromptTemplate # 当不显式声明 input_variables 时 template = "请将以下文本翻译成{target_language}:{text}" prompt = PromptTemplate.from_template(template) print(prompt.input_variables) # 输出: ['target_language', 'text']
- 默认值设置
from langchain.prompts import PromptTemplate template = """分析用户情绪(默认分析类型:{analysis_type}): 用户输入:{user_input} 分析结果:""" prompt_template = PromptTemplate( input_variables=["user_input"], template=template, template_format="f-string", # 新增参数 partial_variables={"analysis_type": "情感极性分析"} # 固定值 ) print(prompt_template.format(user_input="这个产品太难用了")) #====打印内部的变量====== print(prompt_template.template) print(prompt_template.input_variables) print(prompt_template.template_format) print(prompt_template.input_types) print(prompt_template.partial_variables)
PromptTemplate结合LLM案例实战
-
案例实战
from langchain_openai import ChatOpenAI from langchain_core.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser #创建prompt AIGC prompt_template = PromptTemplate( input_variables=["product"], template="为{product}写3个吸引人的广告语,需要面向年轻人", ) prompt = prompt_template.invoke({"product":"小滴课堂"}) #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) #调用大模型 response = model.invoke(prompt) #print(response.content) #创建输出解析器 out_parser = StrOutputParser() answer = out_parser.invoke(response) print(answer)
大模型ChatModel聊天模型和Token计算
-
什么是ChatModel
- 是专为多轮对话场景设计的大语言模型(LLM),通过理解上下文和对话逻辑,生成连贯、符合人类交互习惯的回复。
- 不仅是简单的文本生成工具,更是能处理复杂对话流程的智能系统
- 核心特点
特性 说明 示例场景 上下文感知 追踪多轮对话历史,理解指代关系(如“它”、“这个”) 用户:“什么是量子计算?” → AI 解释 → 用户:“它有什么应用?” → AI 能正确关联“它”指量子计算 角色扮演能力 可设定特定角色(如客服、教师)并保持一致性 设定AI为“医疗助手”时,拒绝提供诊断建议,仅提供健康信息 意图识别 解析用户深层需求(如咨询、投诉、闲聊) 用户:“我的订单没收到!” → AI 识别为物流投诉,优先转接人工客服 情感分析 识别用户情绪(积极/消极),调整回复语气 用户表达不满时,AI 回复:“非常抱歉给您带来不便,我们会立刻处理...” 安全过滤 避免生成有害、偏见或敏感内容 用户请求生成暴力内容时,AI 拒绝并提示:“我无法协助这个请求” -
ChatModel vs. 传统 Text Model
对比维度 ChatModel 传统 Text Model(如 text-davinci-003) 核心目标 多轮交互式对话 单次文本生成(文章、代码等) 输入格式 结构化消息序列(System/Human/AI 角色) 纯文本提示 上下文处理 自动管理对话历史 需手动拼接历史文本 输出控制 内置安全审查和格式约束 依赖提示词工程控制 典型应用 客服机器人、虚拟助手 内容创作、数据清洗 -
聊天模型(如 GPT-3.5-turbo、GPT-4)通过 角色化消息 实现对话控制,核心角色包括:
角色类型 标识符 功能定位 使用场景示例 System system
定义AI的行为准则和角色设定 设定AI身份、回答规则、知识范围 ("system", "你是一位医疗助手...")
User user
代表用户的输入信息 用户提问、指令、反馈 ("human", "如何缓解头痛?")
Assistant assistant
存储AI的历史回复 维护对话上下文、保持回答连贯性 ("ai", "建议服用布洛芬...")
-
参考案例OpenAI代码
# 多轮对话示例 messages = [ {"role": "system", "content": "你是一个电影推荐助手"}, {"role": "user", "content": "我喜欢科幻片,推荐三部经典"}, {"role": "assistant", "content": "1.《银翼杀手2049》... 2.《星际穿越》... 3.《黑客帝国》"}, {"role": "user", "content": "第二部的主演是谁?"} # 基于上下文追问 ] response = client.chat.completions.create( model="gpt-3.5-turbo", messages=messages ) print(response.choices[0].message.content) # 输出:《星际穿越》的主演是马修·麦康纳和安妮·海瑟薇...
-
Chat聊天多轮对话中的Token如何计算
-
在多轮对话场景下,上下文通常涵盖以下几部分:
- 用户的历史输入 :之前用户说过的话,这些内容会作为上下文的一部分,帮助模型理解当前对话的背景和意图。
- 模型的历史回复 :模型之前给出的回应,这样能让模型保持对话的连贯性和一致性。
- 系统提示 :用于设定聊天机器人的角色、目标等,引导对话的方向和风格。
-
随着上下文内容的增加,Token 数量也会相应增多。
-
多轮对话的上下文 Token 累积
- 假设每轮对话中,用户的输入和模型的输出分别对应一定数量的 Token
- 我们以每轮对话输入 50 Token、输出 100 Token 为例来计算:
- 第 1 轮 :用户的输入为 50 Token,模型的输出为 100 Token,此时上下文中的 Token 总数为 50 + 100 = 150 Token。
- 第 2 轮 :新的用户输入为 50 Token,模型新的输出为 100 Token,加上之前的历史上下文 150 Token,此时上下文中的 Token 总数为 50 + 100 + 150 = 300 Token。
- 第 3 轮 :再次新增用户输入 50 Token 和模型输出 100 Token,加上之前的历史上下文 300 Token,上下文中的 Token 总数变为 50 + 100 + 300 = 450 Token
-
上下文窗口的限制
-
每个大语言模型都有一个 “上下文窗口大小”(Context Window)的限制,
-
它决定了模型能够处理的上下文的最大 Token 数量。
-
常见的上下文窗口大小有:
-
4k Tokens :例如 OpenAI GPT - 3.5,其上下文窗口大小为 4096 个 Token。
-
8k、32k Tokens :支持长上下文的模型,如 GPT-4 等,有更大的上下文窗口,分别为 8192 个和 32768 个 Token。
-
-
-
聊天模型ChatPromptTemplate讲解
-
ChatPromptTemplate 核心概念
- 核心差异
- 支持消息角色(system/user/assistant)
- 天然适配聊天模型(如GPT-3.5/4)
- 可维护对话上下文
- 消息类型体系
消息模板类 对应角色 典型用途 SystemMessagePromptTemplate 系统消息 设定AI行为规则 HumanMessagePromptTemplate 用户消息 接收用户输入 AIMessagePromptTemplate AI回复消息 记录历史响应 ChatPromptTemplate 容器模板 组合多个消息模板
- 核心差异
-
掌握两个常见类方法
ChatPromptTemplate.from_template()
- 用于创建单条消息模板,通常结合其他方法构建更复杂的对话流程。
- 适用于单一角色的消息定义(如仅系统指令或用户输入)。
- 需与其他模板组合使用,例如通过
from_messages
整合多个单模板
ChatPromptTemplate.from_messages()
- 用于构建多轮对话模板,支持定义不同角色(如系统、用户、AI)的消息,并允许动态插入变量。
- 支持消息列表,每个消息可以是元组或
MessagePromptTemplate
对象。 - 角色类型包括:
system(系统指令)、human(用户输入)、ai(模型回复)
- 可通过占位符(如
{variable}
)动态替换内容。
- 汇总对比
方法 适用场景 灵活性 代码复杂度 from_messages
多角色、多轮对话(如聊天机器人) 高 较高(需定义列表) from_template
单角色消息模板(需组合使用) 低 简单 -
案例实战
-
使用from_messages构建多轮对话模板
from langchain_core.prompts import ChatPromptTemplate # 定义消息列表,包含系统指令、用户输入和AI回复模板, 通过元组列表定义角色和模板,动态插入name和user_input变量 chat_template = ChatPromptTemplate.from_messages([ ("system", "你是一个助手AI,名字是{name}。"), ("human", "你好,最近怎么样?"), ("ai", "我很好,谢谢!"), ("human", "{user_input}") ]) # 格式化模板并传入变量 messages = chat_template.format_messages( name="Bob", user_input="你最喜欢的编程语言是什么?" ) print(messages) # 输出结果示例: # SystemMessage(content='你是一个助手AI,名字是Bob。') # HumanMessage(content='你好,最近怎么样?') # AIMessage(content='我很好,谢谢!') # HumanMessage(content='你最喜欢的编程语言是什么?')
-
结合
from_template
与from_messages
from langchain_core.prompts import ( ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate ) # 创建单条消息模板,通过细分模板类(如SystemMessagePromptTemplate)定义单条消息,再通过from_messages组合 system_template = SystemMessagePromptTemplate.from_template( "你是一个{role},请用{language}回答。" ) user_template = HumanMessagePromptTemplate.from_template("{question}") # 组合成多轮对话模板 chat_template = ChatPromptTemplate.from_messages([ system_template, user_template ]) # 使用示例 messages = chat_template.format_messages( role="翻译助手", language="中文", question="将'I love Python'翻译成中文。" ) print(messages) # 输出结果示例: # SystemMessage(content='你是一个翻译助手,请用中文回答。') # HumanMessage(content='将'I love Python'翻译成中文。'
-
LangChain聊天模型多案例实战
-
需求
- 聊天模型案例实战,需要结合LLM大模型进行调用
- 简单记执行顺序
from_template->from_messages->format_messages
- 最终传递给大模型是完整构建消息列表对象
-
领域专家案例实战
from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 构建消息列表(类似Java的ListMessage>) messages = [ SystemMessage(content="你是一个Java专家,用中文回答"), HumanMessage(content="解释volatile关键字的作用") ] # 同步调用(类似Java的execute()) response = model.invoke(messages) print(response.content) """ volatile关键字主要用于: 1. 保证变量可见性... 2. 禁止指令重排序... """
-
带参数的领域专家
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain.prompts import ( ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate ) # 定义系统消息模板 system_template = SystemMessagePromptTemplate.from_template( "你是一位专业的{domain}专家,回答需满足:{style_guide}" ) # 定义用户消息模板 human_template = HumanMessagePromptTemplate.from_template( "请解释:{concept}" ) # 组合聊天提示 chat_prompt = ChatPromptTemplate.from_messages([ system_template, human_template ]) # 格式化输出 messages = chat_prompt.format_messages( domain="机器学习", style_guide="使用比喻和示例说明", concept="过拟合" ) model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) response = model.invoke(messages) print(response)
-
合规客服系统
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) compliance_template = ChatPromptTemplate.from_messages([ ("system", """您是{company}客服助手,遵守: 1. 不透露内部系统名称 2. 不提供医疗/金融建议 3. 遇到{transfer_cond}转人工"""), ("human", "[{user_level}用户]:{query}") ]) messages = compliance_template.format_messages(company="小滴课堂老王医生", transfer_cond="病情咨询、支付问题", user_level="VIP", query="感冒应该吃什么药?") response = model.invoke(messages) print(response)
LCEL表达式和输出解析器多案例实战
LangChain链和LLMChain链案例实战
-
什么是Chain链
- 是构建语言模型应用的核心组件,用于将多个模块(如模型调用、提示模板、记忆系统等)组合成可复用的工作流程。
- 本质:将多个处理单元(模型/工具/逻辑)按特定顺序连接起来,形成完整的任务处理流程
- 想象Java中的责任链模式或工作流引擎中的步骤串联。
// 传统Java责任链模式(对比理解) public interface Handler { void handle(Request request); } class ValidationHandler implements Handler { /* 验证逻辑 */ } class LLMProcessingHandler implements Handler { /* 大模型处理 */ } class DatabaseSaveHandler implements Handler { /* 存储结果 */ } // 构建处理链 List<Handler> chain = Arrays.asList( new ValidationHandler(), new LLMProcessingHandler(), new DatabaseSaveHandler() ); // 执行链式处理 for (Handler handler : chain) { handler.handle(request); }
-
常见类说明
-
LLMChain类详解(最基础的链,少用仍保留,推荐用 LCEL)
- 是最基础的Chain,负责结合提示模板和LLM模型,生成并执行模型的调用流程
- 专门用于与大语言模型(如ChatGPT)交互的标准化处理单元
- 核心功能:
- 提示模板:将用户输入动态填充到预设的模板中,生成模型的输入文本。
- 模型调用:将模板生成的文本传递给LLM,返回生成结果。
- 输出解析(可选):对模型输出进行后处理(如JSON解析)
- 核心参数
参数 类比Java场景 示例值 llm 依赖注入的模型对象 new OpenAI() prompt 预定义的提示词模板 PromptTemplate("回答用户问题: {input}") output_parser 结果解析器(类似Jackson处理JSON) new CommaSeparatedListOutputParser() - 代码示例
from langchain_openai import ChatOpenAI from langchain.chains import LLMChain from langchain_core.prompts import PromptTemplate # 创建提示词模板(类似Java的String.format()) prompt_template = PromptTemplate( input_variables=["product"], template="你是文案高手,列举三个{product}的卖点:" ) # #模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 创建LLMChain(类似构建一个Service类) llm_chain = LLMChain( llm=model, prompt=prompt_template ) # LLMChain 类在 LangChain 0.1.17 中已被弃用,建议使用 RunnableSequence(如 prompt | llm) llm_chain = prompt_template | model # 执行调用(类似service.execute(input)),run方法被淘汰了,统一invoke方法调用 result = llm_chain.invoke("智能手机") print(result) # 输出:"1. 高清OLED屏幕 2. 5000mAh大电池 3. 旗舰级处理器"
-
其他常见的Chain,比如
SequentialChain、TransformChain、RouterChain、RetrievalChain
部分已经淘汰
-
新版LCEL表达式讲解和案例实战
-
什么是LCEL(LangChain Expression Language)
-
是 LangChain 0.3+ 推出的声明式编程语言,用于简化 AI 流程编排,核心思想是用管道符
|
连接组件。 -
优势:
- 代码简洁,支持流式响应(如 ChatGPT 逐字输出)
- 标准化接口, 所有组件输入为
Dict
,输出为Dict
,支持无缝连接 - 所有组件实现
Runnable
协议, 兼容同步/异步调用 - 内置调试、重试、并行等高级功能
-
版本对比
特性 0.2 版本 0.3+ 版本 构建方式 类继承 ( Chain
子类)LCEL 表达式语法 ( Runnable
接口)组合方法 SequentialChain
类管道操作符 ` 执行模式 同步为主 原生支持 Async / Stream 核心模块 langchain.chains
langchain_core.runnables
-
语法与结构
-
LLMChain(旧版)
- 基于类的封装,通过组合
PromptTemplate
、LLM
和OutputParser
等组件构建链式调用。 - 需要显式定义每个组件并手动传递输入
- 适用场景
- 简单问答、单步生成任务(如生成公司名称、翻译句子)。
- 快速原型开发,无需复杂配置
chain = LLMChain(llm=model, prompt=prompt) result = chain.invoke({"text": "Hello"})
- 基于类的封装,通过组合
-
LCEL
- 采用声明式管道语法(
|
操作符),允许更直观的链式组合 - 通过管道连接组件,代码更简洁且逻辑清晰
- 适用场景
- 复杂工作流(如多模型协作、动态路由、实时流式交互)。
- 生产级应用开发,需高并发、稳定性和可观察性(如集成LangSmith跟踪)
chain = prompt | model | output_parser result = chain.invoke({"text": "Hello"})
- 采用声明式管道语法(
-
-
-
LCEL 核心语法
- 作用:将左侧组件的输出传递给右侧组件作为输入,构建了类似Unix管道运算符的设计
- 语法规则:
chain = component_a | component_b | component_c
- 案例实战:构建一个简单的问答链
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser # 定义组件 prompt = ChatPromptTemplate.from_template("回答这个问题:{question}") # #模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) parser = StrOutputParser() # 构建 LCEL 链 chain = prompt | model | parser # 调用 result = chain.invoke({"question": "如何学习AI大模型?"}) print(result)
# 旧版本的写法, 创建LLMChain(类似构建一个Service类) chain = LLMChain( llm=model, prompt=prompt_template ) # 执行调用(类似service.execute(input)),run方法被淘汰了,统一invoke方法调用 result = chain.invoke("智能手机") print(result) # 输出:"1. 高清OLED屏幕 2. 5000mAh大电池 3. 旗舰级处理器"
LLM大模型Stream流式输出实战
-
Stream流式响应实战
- 前面我们的大模型输出都是一次性响应,这个容易造成体验不好
- 支持流式响应(Streaming Response),能够显著提升应用的响应速度和用户体验。
- 流式响应的核心在于逐步生成和返回数据,而不是等待整个结果生成后再一次性返回
-
案例实战:故事小助手
from langchain_openai import ChatOpenAI # #模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) for chunk in model.stream("讲一个翠花的故事。"): print(chunk.content, end="", flush=True)
-
案例实战:科普助手(采用LCEL表达式进行)
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser # 1. 定义提示词模版 prompt = ChatPromptTemplate.from_template("用100字解释以下知识点:{concept}") # 2. 创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7, streaming=True # 启用流式 ) # 使用 LCEL 语法 streaming_chain = prompt | model | StrOutputParser() # 2. 执行流式调用 for chunk in streaming_chain.stream({"concept": "小滴课堂"}): print(chunk, end="", flush=True) # 逐词输出
-
流式响应的优势与限制
- 优势
- 提升用户体验:用户可以看到实时的生成过程,避免长时间等待。
- 节省内存:逐步生成和返回数据,避免一次性加载大量数据。
- 灵活性高:支持同步、异步和事件驱动的流式响应,适用于多种场景29。
- 限制
- 等待时间较长:涉及大语言模型的节点可能需要较长时间才能返回完整结果。
- 复杂性较高:事件驱动的流式响应需要精细的控制和管理
- 优势
输出解析器OutputParse实战和原理讲解
- 为什么需要输出解析OutputParse?
- 大模型原始输出通常是非结构化文本
- 实际需要:
- 将大模型的自由文本转为结构化数据(类似Java的JSON/XML解析)
- 自动处理模型输出的格式错误
-
解析器工作原理
输入文本 → LLM 生成 → 解析器 → 结构化数据
- 工作原理:
- 在提示模板中预留一个占位符变量,由输出解析器负责填充。
- 当 LLM 按照输出要求返回文本答案后,该答案会被传递给输出解析器,解析为预期的数据结构。
- 通俗来说:就是在prompt结尾追加,告诉大模型应该返回怎样的格式
- 解析器核心接口
parse()
:解析原始文本为结构化数据。parse_with_prompt()
:结合上下文提示词解析(处理多轮对话场景)。get_format_instructions()
:生成提示词模板,指导 LLM 输出格式。
- 文档地址:https://python.langchain.com/docs/concepts/output_parsers/
-
输出解析器基础结构三要素
from langchain.output_parsers import XxxParser # 要素1:创建解析器(类似Java的Gson实例) parser = XxxParser() # 要素2:构建提示词模板(注意{format_instructions}占位符) prompt = PromptTemplate( template="请生成用户信息,按格式:{format_instructions}\n输入:{input}", input_variables=["input"], partial_variables={"format_instructions": parser.get_format_instructions()} ) # 要素3:组合成链(类似Java的责任链模式) chain = prompt | model | parser
- 测试查看 多个解析器prompt提示词
from langchain_core.output_parsers import JsonOutputParser,CommaSeparatedListOutputParser from langchain_core.prompts import ChatPromptTemplate #parser = JsonOutputParser() parser = CommaSeparatedListOutputParser() # 获取格式指令 format_instructions = parser.get_format_instructions() # 定义提示词 prompt = ChatPromptTemplate.from_template(""" 分析以下商品评论,按指定格式返回结果: 评论内容:{review} 格式要求: {format_instructions} """) # 注入格式指令 final_prompt = prompt.partial(format_instructions=format_instructions) print(final_prompt.format_prompt(review="这个手机超级好用,超级流畅"))
-
案例实战
from langchain_core.output_parsers import CommaSeparatedListOutputParser from langchain.prompts import PromptTemplate from langchain_openai import ChatOpenAI # 1. 实例化一个 CommaSeparatedListOutputParser对象,将逗号分隔文本转为列表 output_parser = CommaSeparatedListOutputParser() format_instructions = output_parser.get_format_instructions() # 2. 创建一个 prompt template,将 output_parser.get_format_instructions()追加到的prompt里面 prompt = PromptTemplate( template="列举多个常见的 {subject}.{format_instructions}", input_variables=["subject"], partial_variables={"format_instructions": format_instructions}, ) #3. 创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 4. 构建链 chain = prompt | model | output_parser print(chain.invoke({"subject": "水果"}))
-
!!!注意!!!!
- 虽然有解析器,但是存在大模型返回内容不符合格式要求,则一样会报错
- 本质解析器还是Prompt提示词内容,如果有报错则可以让大模型重试
LangChain字符串和列表输出解析器实战
-
需求
- 课程讲常见的解析器,其他的可以自行拓展
- 掌握字符串和列表输出解析器
- 掌握解析器源码流程
-
StrOutputParser
-
功能:将模型输出直接解析为字符串,保留原始文本输出,不做处理(默认行为)
-
适用场景:无需结构化处理,直接返回原始文本
from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 定义链 prompt = ChatPromptTemplate.from_template("写一首关于{topic}的诗") #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 创建解析器 parser = StrOutputParser() chain = prompt | model | parser # 调用 result = chain.invoke({"topic": "秋天"}) print(result) # 输出:秋天的落叶轻轻飘落...
-
-
CommaSeparatedListOutputParser
- 功能:将逗号分隔的文本解析为列表。
- 适用场景:模型生成多个选项或标签。
from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import CommaSeparatedListOutputParser from langchain_openai import ChatOpenAI parser = CommaSeparatedListOutputParser() prompt = ChatPromptTemplate.from_template("列出3个与{topic}相关的关键词:") #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) chain = prompt | model | parser # 调用 result = chain.invoke({"topic": "Java"}) print(result) # 输出:["机器学习", "深度学习", "神经网络"]
Json输出解析器和问答系统答案提取实战
-
JsonOutputParser
- 功能:将模型输出解析为 JSON 对象。
- 适用场景:需要模型返回结构化数据(如 API 响应),多数要结合Pydantic进行使用
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import JsonOutputParser prompt = ChatPromptTemplate.from_template( "返回JSON:{{'name': '姓名', 'age': 年龄}},输入:{input}" ) #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) chain = prompt | model | JsonOutputParser() result = chain.invoke({"input": "张三今年30岁,准备结婚后学AI大模型课程"}) print(result) # 输出:{"name": "张三", "age": 30}
-
案例实战:问答系统答案提取
- 目标:从模型回答中提取答案和置信度。
from langchain_core.output_parsers import JsonOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI # 定义 JSON 格式要求 prompt = ChatPromptTemplate.from_template(""" 回答以下问题,返回 JSON 格式: {{ "answer": "答案文本", "confidence": 置信度(0-1) }} 问题:{question} """) #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) parser = JsonOutputParser() chain = prompt | model | parser # 调用 result = chain.invoke({"question": "地球的半径是多少?"}) print(result) #{'answer': '地球的平均半径约为6,371公里。', 'confidence': 0.95} print(f"答案:{result['answer']},置信度:{result['confidence']}") #答案:地球的平均半径约为6,371公里。,置信度:0.95
Pydantic模型和LLM高级解析器实战
Python模型管理Pydantic介绍和安装
-
什么是Pydatic
-
Pydantic 是一个在 Python 中用于数据验证和解析的第三方库,是 Python 使用最广泛的数据验证库
-
声明式的方式定义数据模型和,结合Python 类型提示的强大功能来执行数据验证和序列化
-
Pydantic 提供了从各种数据格式(例如 JSON、字典)到模型实例的转换功能
-
-
为什么要用Pydantic
- 处理来自系统外部的数据,如API、用户输入或其他来源时,必须牢记开发中的原则:“永远不要相信用户的输入”
- AI智能体需要处理结构化数据(API请求/响应,配置文件等)
- 必须对这些数据进行严格的检查和验证,确保它们被适当地格式化和标准化
- 解决的问题
- 数据验证:自动验证输入数据的类型和格式
- 类型提示:结合Python类型提示系统
- 序列化:轻松转换数据为字典/JSON
- 配置管理:支持复杂配置项的验证
-
对比Java开发的模型验证
-
典型Java方式(需手写校验逻辑)
public class User { private String name; private int age; // 需要手写校验方法 public void validate() throws IllegalArgumentException { if (name == null || name.isEmpty()) { throw new IllegalArgumentException("姓名不能为空"); } if (age > 150) { throw new IllegalArgumentException("年龄不合法"); } } }
-
传统Python方式(样板代码)
class User: def __init__(self, name: str, age: int): if not isinstance(name, str): raise TypeError("name必须是字符串") if not isinstance(age, int): raise TypeError("age必须是整数") if age > 150: raise ValueError("年龄必须在0-150之间") self.name = name self.age = age
-
pydantic方式(声明式验证)
# 只需3行代码即可实现完整验证! from pydantic import BaseModel, Field class User(BaseModel): name: str = Field(min_length=1, max_length=50) # 内置字符串长度验证 age: int = Field(ge=0, le=150) # 数值范围验证(类似Java的@Min/@Max)
-
-
案例实战
-
模块安装 Pydantic V2(需要Python 3.10+)
pip install pydantic==2.7.4
-
使用
- Pydantic 的主要方法是创建继承自 BaseModel 的自定义类
from pydantic import BaseModel # 类似Java中的POJO,但更强大 class UserProfile(BaseModel): username: str # 必须字段 age: int = 18 # 默认值 email: str | None = None # 可选字段 # 实例化验证 user1 = UserProfile(username="Alice") print(user1) # username='Alice' age=18 email=None user2 = UserProfile(username="Bob", age="20") # 自动类型转换 print(user2.age) # 20(int类型)
- 创建实例与校验
try: UserProfile(username=123) # 触发验证错误 except ValueError as e: print(e.errors()) # [{ # 'type': 'string_type', # 'loc': ('username',), # 'msg': 'Input should be a valid string', # 'input': 123 # }]
-
字段类型验证
from pydantic import BaseModel,HttpUrl,ValidationError class WebSite(BaseModel): url: HttpUrl #URL格式验证 visits: int = 0 #默认值 tags: list[str] = [] #字符串列表 valid_data = { "url": "https://www.baidu.com", "visits": 100, "tags": ["python", "fastapi"] } # try: # website = WebSite(**valid_data) # print(website) # except ValidationError as e: # print(e.errors()) try: website = WebSite(url="xdclass.net",visits=100) print(website) except ValidationError as e: print(e.errors())
-
数据解析/序列化
from pydantic import BaseModel class Item(BaseModel): name: str price: float # 从JSON自动解析(类似Jackson) data = '{"name": "Widget", "price": "9.99"}' # 字符串数字自动转换 item = Item.model_validate_json(data) # 导出为字典(类似Java的POJO转Map) print(item.model_dump()) # {'name': 'Widget', 'price': 9.99}
-
调试技巧:打印模型结构
print(Website.model_json_schema()) # 输出完整的JSON Schem
-
Pydantic字段校验Field函数多案例实战
-
Filed函数讲解
- Field函数通常用于给模型字段添加额外的元数据或者验证条件。
- 例如,title参数用来设置字段的标题,min_length用来限制最小长度。
- 核心
...
的本质:表示“无默认值”,强制字段必填。Field
的常用参数:title
:字段标题(用于文档)description
:详细描述min_length
/max_length
(字符串、列表)gt
/ge
/lt
/le
(数值范围)regex
(正则表达式)example
(示例值,常用于 API 文档)
-
案例实战
-
必填字段(无默认值)
- 必填字段:
- ... 表示该字段必须显式提供值。若创建模型实例时未传入此字段,Pydantic 会抛出验证错误(ValidationError)。
- 默认值占位
- Field 的第一个参数是 default,而 ... 在此处的语义等价于“无默认值”。
- 若省略 default 参数(如 Field(title="用户名")),Pydantic 会隐式使用 ...,但显式写出更明确
from pydantic import BaseModel, Field class User(BaseModel): name: str = Field(..., title="用户名", min_length=2) # 正确用法 user = User(name="Alice") # 错误用法:缺少 name 字段 user = User() # 触发 ValidationError
- 必填字段:
-
可选字段(有默认值)
from pydantic import BaseModel, Field class UserOptional(BaseModel): name: str = Field("Guest", title="用户名") # 默认值为 "Guest" # 可不传 name,自动使用默认值 user = UserOptional() print(user.name) # 输出 "Guest"
-
以下两种写法完全等价, 使用
Field
的优势在于可以附加额外参数(如title
、min_length
、description
等)。# 写法 1:省略 Field,直接类型注解 name: str # 写法 2:显式使用 Field(...) name: str = Field(...)
-
数值类型必填
from pydantic import BaseModel, Field, ValidationError class Product(BaseModel): price: float = Field(..., title="价格", gt=0) # 必须 > 0 stock: int = Field(..., ge=0) # 必须 >= 0 # 正确 product = Product(price=99.9, stock=10) # 错误:price <= 0 try: Product(price=-5, stock=10) except ValidationError as e: print(e.json()) # 提示 "price" 必须大于 0
-
嵌套模型必填
from pydantic import BaseModel, Field class Address(BaseModel): city: str = Field(..., min_length=1) street: str class User(BaseModel): name: str = Field(...) address: Address # 等效于 address: Address = Field(...) # 正确 user = User(name="Alice", address={"city": "Shanghai", "street": "Main St"}) # 错误:缺少 address user = User(name="Bob") # 触发 ValidationError
-
明确可选字段
from pydantic import BaseModel, Field from typing import Optional class User(BaseModel): name: str = Field(...) email: Optional[str] = Field(None, title="邮箱") # 可选,默认值为 None # 正确:不传 email user = User(name="Alice")
-
混合使用默认值和必填
from pydantic import BaseModel, Field class Config(BaseModel): api_key: str = Field(...) # 必填 timeout: int = Field(10, ge=1) # 可选,默认 10,但必须 >=1 # 正确 config = Config(api_key="secret") assert config.timeout == 10 # 错误:未传 api_key Config(timeout=5) # 触发 ValidationError
-
Pydantic自定义验证器多案例实战
-
@field_validator
介绍-
是 Pydantic 中用于为单个字段添加自定义验证逻辑的装饰器
-
适用场景:当默认验证规则(如
min_length
、gt
)无法满足需求时,通过编写代码实现复杂校验。 -
触发时机:默认在字段通过基础类型和规则校验后执行(可通过
mode
参数调整) -
基础语法
from pydantic import BaseModel, ValidationError, field_validator class User(BaseModel): username: str # 带默认值的可选字段 # int | None表示 age 变量的类型可以是整数 (int) 或 None,旧版本的写法:age: Union[int, None] # Python 3.10 开始引入,替代了早期通过 Union[int, None] 的形式(仍兼容) age: int | None = Field( default=None, ge=18, description="用户年龄必须≥18岁" ) @field_validator("username") def validate_username(cls, value: str) -> str: # cls: 模型类(可访问其他字段) # value: 当前字段的值 if len(value) < 3: raise ValueError("用户名至少 3 个字符") return value # 可修改返回值(如格式化)
-
-
案例实战
-
字符串格式校验
from pydantic import BaseModel, Field,field_validator class User(BaseModel): email: str @field_validator("email") def validate_email(cls, v): if "@" not in v: raise ValueError("邮箱格式无效") return v.lower() # 返回格式化后的值 # 正确 User(email="ALICE@example.com") # 自动转为小写:alice@example.com # 错误 #User(email="invalid") # 触发 ValueError
-
验证用户名和长度
from pydantic import BaseModel, Field,field_validator class User(BaseModel): username: str = Field(..., min_length=3) @field_validator("username") def validate_username(cls, v): if "admin" in v: raise ValueError("用户名不能包含 'admin'") return v # 正确 User(username="alice123") # 错误 #User(username="admin") # 触发自定义验证错误
-
密码复杂性验证
from pydantic import BaseModel, Field,field_validator class Account(BaseModel): password: str @field_validator("password") def validate_password(cls, v): errors = [] if len(v) < 8: errors.append("至少 8 个字符") if not any(c.isupper() for c in v): errors.append("至少一个大写字母") if errors: raise ValueError("; ".join(errors)) return v # 错误:密码不符合规则 Account(password="weak") # 提示:至少 8 个字符; 至少一个大写字母
-
多个字段共享验证器
from pydantic import BaseModel, Field,field_validator class Product(BaseModel): price: float cost: float @field_validator("price", "cost") def check_positive(cls, v): if v <= 0: raise ValueError("必须大于 0") return v # 同时验证 price 和 cost 是否为正数 Product(price=1, cost=-2)
-
-
注意事项
-
忘记返回值:验证器必须返回字段的值(除非明确要修改)。
@field_validator("email") def validate_email(cls, v): if "@" not in v: raise ValueError("Invalid email") # ❌ 错误:未返回 v
-
-
Pydantic V2 现为 Pydantic 的当前生产发布版本
- 网上不少是V1版本的教程,需要注意
- Pydantic V1和Pydantic V2的API差异
Pydantic V1 Pydantic V2 __fields__
model_fields
__private_attributes__
__pydantic_private__
__validators__
__pydantic_validator__
construct()
model_construct()
copy()
model_copy()
dict()
model_dump()
json_schema()
model_json_schema()
json()
model_dump_json()
parse_obj()
model_validate()
update_forward_refs()
model_rebuild()
重点-解析器PydanticOutputParser实战
-
为啥要用为什么需要Pydantic解析?
- 结构化输出:将非结构化文本转为可编程对象
- 数据验证:自动验证字段类型和约束条件,单纯json解析器则不会校验
- 开发效率:减少手动解析代码
- 错误处理:内置异常捕获与修复机制
-
案例实战一:大模型信息输出提取(
PydanticOutputParser
结合Pydantic模型验证输出)from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from langchain_core.output_parsers import PydanticOutputParser #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # Step1: 定义Pydantic模型 class UserInfo(BaseModel): name: str = Field(description="用户姓名") age: int = Field(description="用户年龄", gt=0) hobbies: list[str] = Field(description="兴趣爱好列表") # Step2: 创建解析器 parser = PydanticOutputParser(pydantic_object=UserInfo) # Step3: 构建提示模板 from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_template(""" 提取用户信息,严格按格式输出: {format_instructions} 输入内容: {input} """) # 注入格式指令 prompt = prompt.partial( format_instructions=parser.get_format_instructions() ) # Step4: 组合处理链 chain = prompt | model | parser # 执行解析 result = chain.invoke({ "input": """ 我的名称是张三,年龄是18岁,兴趣爱好有打篮球、看电影。 """ }) print(type(result)) print(result)
-
案例实战二:电商评论情感分析系统(JsonOutputParser和pydantic结合)
JsonOutputParser
与PydanticOutputParser
类似- 新版才支持从pydantic获取约束模型,该参数并非强制要求,而是可选的增强功能
JsonOutputParser
可以处理流式返回的部分JSON对象。
from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 定义JSON结构 class SentimentResult(BaseModel): sentiment: str confidence: float keywords: list[str] # 构建处理链 parser = JsonOutputParser(pydantic_object=SentimentResult) prompt = ChatPromptTemplate.from_template(""" 分析评论情感: {input} 按以下JSON格式返回: {format_instructions} """).partial(format_instructions=parser.get_format_instructions()) chain = prompt | model | parser # 执行分析 result = chain.invoke({"input": "物流很慢,包装破损严重"}) print(result) # 输出: # { # "sentiment": "negative", # "confidence": 0.85, # "keywords": ["物流快", "包装破损"] # } # 2. 执行流式调用 #for chunk in chain.stream({"input": "物流很慢,包装破损严重"}): # print(chunk) # 逐词输出
重点-大模型修复机制OutputFixingParser
-
OutputFixingParser
- 是LangChain中用于修复语言模型(LLM)输出格式错误的工具,通常与
PydanticOutputParser
配合使用。 - 当原始解析器因格式问题(如JSON语法错误、字段缺失等)失败时,它能自动调用LLM修正输出,提升解析的鲁棒性。
- 核心功能:
- 自动纠错:修复不规范的输出格式(如单引号JSON、字段顺序错误等)。
- 兼容性:与Pydantic数据模型无缝集成,支持结构化输出验证。
- 容错机制:避免因模型输出不稳定导致程序中断
- 是LangChain中用于修复语言模型(LLM)输出格式错误的工具,通常与
-
核心语法与使用步骤
- 基础语法
from langchain.output_parsers import OutputFixingParser, PydanticOutputParser from langchain_openai import ChatOpenAI # 步骤1:定义Pydantic数据模型 class MyModel(BaseModel): field1: str = Field(description="字段描述") field2: int # 步骤2:创建原始解析器 parser = PydanticOutputParser(pydantic_object=MyModel) #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 步骤3:包装为OutputFixingParser fixing_parser = OutputFixingParser.from_llm(parser=parser, llm=model)
- 参数说明:
- parser: 原始解析器对象(如PydanticOutputParser)。
- llm: 用于修复错误的语言模型实例。
- max_retries(可选): 最大重试次数(默认1)
-
案例实战
- 修复机制
- 检测到错误后,将错误信息与原始输入传递给LLM。
- LLM根据提示生成符合Pydantic模型的修正结果。
from langchain.output_parsers import OutputFixingParser from langchain_core.output_parsers import PydanticOutputParser from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from typing import List class Actor(BaseModel): name: str = Field(description="演员姓名") film_names: List[str] = Field(description="参演电影列表") parser = PydanticOutputParser(pydantic_object=Actor) #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 包装原始解析器 fixing_parser = OutputFixingParser.from_llm(parser=parser, llm=model) # 模拟模型输出的错误格式(使用单引号) misformatted_output = "{'name': '小滴课堂老王', 'film_names': ['A计划','架构大课','一路向西']}" #在LLM 链中,chain.invoke会将 LLM 返回的文本字符串传入output_parser.invoke #而`output_parser.invoke`最终会调用到`output_parser.parse`。 # try: # parsed_data = parser.parse(misformatted_output) # 直接解析会失败 # except Exception as e: # print(f"解析失败:{e}") # 抛出JSONDecodeError # 使用OutputFixingParser修复并解析 fixed_data = fixing_parser.parse(misformatted_output) print(type(fixed_data)) print(fixed_data.model_dump()) # 输出:{'name': '小滴课堂老王', 'film_names': ['A计划','架构大课','一路向西']}
- 完整正常案例
from langchain.output_parsers import OutputFixingParser from langchain_core.output_parsers import PydanticOutputParser from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from langchain_core.prompts import PromptTemplate from typing import List class Actor(BaseModel): name: str = Field(description="演员姓名") film_names: List[str] = Field(description="参演电影列表") parser = PydanticOutputParser(pydantic_object=Actor) #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) prompt = PromptTemplate( template="{format_instructions}\n{query}", input_variables=["query"], partial_variables={"format_instructions": parser.get_format_instructions()}, ) # 包装原始解析器 fixing_parser = OutputFixingParser.from_llm(parser=parser, llm=model) chain = prompt | model | fixing_parser response = chain.invoke({"query": "说下成龙出演过的5部动作电影? "}) print(response) print(type(response)) print(response.model_dump())
- 修复机制
-
常见问题与解决方案
- 修复失败的可能原因
- 模型能力不足:升级LLM版本(如使用更高级的模型和参数量)。
- 提示词不清晰:在提示模板中明确格式要求。
- 网络问题:通过代理服务优化API访问
- 修复失败的可能原因
AI大模型必备之RAG和智能医生实战
什么是大模型的幻觉输出
-
“幻觉输出”(Hallucination)
- 是大语言模型(如GPT、Llama、DeepSeek等)生成内容时的一种常见问题
- 指模型输出看似合理但实际错误、虚构或脱离事实的信息。
- 这种现象类似于人类的“臆想”——模型基于不完整或错误的知识,生成逻辑通顺但内容失实的回答
-
表现形式
- 虚构事实
- 例1:生成不存在的书籍(如称《时间简史》是鲁迅所写)。
- 例2:编造错误的历史事件(如“秦始皇于公元前200年统一六国”)。
- 错误推理
- 例:回答数学问题时,步骤正确但结果错误(如“2+3=6”)。
- 过度泛化
- 例:将特定领域的知识错误迁移到其他领域(如用医学术语解释物理现象)。
- 矛盾内容
- 例:同一段回答中前后逻辑冲突(如先说“地球是平的”,后又说“地球绕太阳公转”)
- 虚构事实
-
幻觉产生的根本原因
- 训练数据的局限性
- 数据噪声:模型训练数据可能包含错误、过时或偏见信息(如互联网上的谣言)。
- 知识截止:模型训练后无法获取新知识(如GPT-3的数据截止到2021年)。
- 模型生成机制
- 概率驱动:模型通过预测“最可能的下一词”生成文本,而非验证事实。
- 缺乏常识判断:无法区分“合理表达”与“真实事实”。
- 模型策略的副作用
- 创造性模式:模型在开放生成任务中更易“放飞自我”(如写小说时虚构细节)
- 训练数据的局限性
-
典型案例分析
-
案例1:医疗问答
- 问题:用户问“新冠疫苗会导致自闭症吗?”
- 幻觉输出:模型可能生成“有研究表明两者存在相关性”(错误)。
- 解决方案:RAG检索WHO官方声明,生成“无科学证据支持此说法”。
-
案例2:金融咨询
-
问题:“2024年比特币会涨到10万美元吗?”
-
幻觉输出:模型虚构专家预测或历史数据。
-
解决方案:限定回答范围(如“截至2023年,比特币最高价格为…”)
-
-
-
-
产生的影响
- 误导用户:在医疗、法律等专业领域可能引发严重后果。
- 信任危机:用户对模型输出的可靠性产生质疑。
- 技术瓶颈:暴露大模型在事实性、可解释性上的不足。
-
如何缓解幻觉输出(注意:不是解决)
- 技术改进方案
- 检索增强生成(RAG):通过实时检索外部知识库(如维基百科、专业数据库),为生成提供事实依据。
- 微调对齐:用高质量数据(如标注正确的问答对)调整模型输出偏好。
- 强化学习(RLHF):通过人类反馈惩罚错误生成,奖励准确回答。
- 生成策略优化
- 温度参数调整:降低随机性(
temperature=0
),减少“胡编乱造”。 - 后处理校验:添加事实核查模块(如调用知识图谱API验证答案)。
- 温度参数调整:降低随机性(
- 用户侧应对
- 提示词设计:明确要求模型标注不确定性(如“回答需基于2023年数据”)。
- 多源验证:对关键信息人工交叉核对(如学术论文、权威网站)。
- 技术改进方案
带你走进RAG检索增强生成和应用场景
简介: 带你走进RAG检索增强生成和应用场景
-
什么是RAG技术
- RAG(Retrieval-Augmented Generation)检索增强生成,是结合信息检索与文本生成的AI技术架构。
- 核心思想:
- 先通过检索系统找到与问题相关的知识片段
- 再将检索结果与问题共同输入生成模型得到最终答案
- 类比人类解答问题的过程:遇到问题时先查资料(检索),再结合资料组织回答(生成)
-
用Java伪代码描述RAG工作流程:
public class JavaRAG { public static void main(String[] args) { // 1. 加载文档 List<Document> docs = FileLoader.load("data/"); // 2. 创建向量库(类似建立索引) VectorDB vectorDB = new FAISSIndex(docs); // 3. 处理用户问题 String question = "如何申请报销?"; List<Document> results = vectorDB.search(question, 3); // 4. 生成回答 String context = String.join("\n", results.stream() .map(Document::content) .collect(Collectors.toList())); String answer = LLM.generate(context + "\n问题:" + question); System.out.println(answer); } }
-
需求背景:为什么需要RAG?
- 传统生成模型的局限性
- 知识过时:大模型(如GPT-3)训练数据截止于特定时间,无法覆盖实时信息。
- 幻觉问题:生成内容可能包含不准确或虚构的信息。
- 领域局限性:通用模型缺乏垂直领域的专业知识(如法律、医疗),企业私有数据无法写入公开模型
题类型 示例 传统模型表现 时效性问题 "2027年诺贝尔奖得主是谁?" 无法回答(训练数据截止到当时的数据) 领域专业问题 "如何配置Hadoop集群参数?" 回答模糊或错误 需要引用来源的问题 "不睡觉有哪些副作用?" 无法提供可信出处 - 检索与生成的互补性
- 检索系统:擅长从海量数据中快速找到相关文档(实时、精准),实时从外部知识库检索信息
- 生成模型:擅长语言理解和流畅输出。
- 结合优势:RAG通过检索外部知识库增强生成结果,提高准确性和可信度
- 降低训练成本:无需重新训练模型即可更新知识
- 生成可验证性:每句回答都可追溯来源文档
- 传统生成模型的局限性
-
技术架构
-
涉及的技术链路环节: 文档加载器->文档转换器->文本嵌入模型->向量存储->检索器
- 关键技术组件
组件 常用工具 Java类比 文档加载器 PyPDFLoader, Unstructured FileInputStream 文本分块器 RecursiveTextSplitter String.split()增强版 元数据处理 LangChain Document类 DTO对象封装 向量存储 FAISS, Pinecone 数据库索引 -
典型应用场景
-
案例1:智能客服系统
-
传统问题:客服知识库更新频繁,模型无法实时同步。
-
RAG方案:
- 实时检索最新的产品文档、FAQ。
- 生成个性化回答(如退货政策、故障排查)。
用户问:"这个小滴手机支持老人家使用不?" 系统检索:产品适合人群相关词条 生成回答:"我们这个产品适合18岁以上的成人使用,包括中老年人等"
-
效果:减少人工干预,回答准确率提升30%+。
-
-
案例2:医疗问答助手
-
传统问题:通用模型缺乏专业医学知识,可能给出危险建议。
-
RAG方案:
- 检索权威医学数据库(如PubMed、临床指南)。
- 生成基于循证医学的答案,标注参考文献来源。
用户问:"二甲双胍的禁忌症有哪些?" 系统检索:最新《临床用药指南》第5.3节 生成回答:"根据2023版用药指南,二甲双胍禁用于以下情况:1)严重肾功能不全..."
-
效果:合规性提升,避免法律风险。
-
-
案例3:金融研究报告生成
-
传统问题:市场数据动态变化,模型无法实时分析。
-
RAG方案:
- 检索实时财报、新闻、行业数据 → 输入生成模型。
- 自动生成带有数据支撑的投资建议。
用户问:"XXX公司财报如何" 系统检索:某某公司财报 生成回答:"根据公司的财报和解读,利润和负债..."
-
效果:析师效率提升,报告更新频率加快
-
-
LLM智能AI医生+RAG系统案例实战
-
需求
- 快速搭建智能医生客服案例,基于LLM大模型+RAG技术
- 方便大家可以直观的看相关效果,并方便后续拆解每个步骤
- 效果:可以根据用户的提问,检索相关私有数据,整合大模型,最终生成返回给用户
-
案例实战【本集不提供课程代码,请从下面地址下载】
-
创建项目和安装相关环境
# 创建并激活虚拟环境 python -m venv .venv source .venv/bin/activate # 安装依赖 pip install -r requirements.txt
-
查看训练的文档数据
-
项目部署运行 (版本和课程保持一致,不然很多不兼容!!!)
- 下载相关资料 ,使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/aipan_xd-rag.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/aipan_xd-rag.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/aipan_xd-rag.zip
- 解压后执行【依赖很多,版本差异大,务必按照下面执行,否则课程无法进行下去,加我微信 xdclass6】
# 安装依赖 pip install -r requirements.txt
-
-
效果测试
- 多数同学的问题
- 为啥可以根据我们的提问,进行检索到对应的词条?而且还可以正确检索?
- 为啥要加载文件?然后切割?什么是向量数据库?
- 为啥检索到词条后,还可以用调整输出内容,更加友好?
- 什么是嵌入大模型?和前面学的LLM大模型有啥区别?
RAG 检索增强生成之Loader实战
RAG系统链路和数据加载Loaders技术
- RAG系统与LLM交互架构图
- 注意
- 万丈高楼平地起,基础需要打牢固,一步步进行,然后学会举一反三使用
- 如果直接讲Agent智能体项目,那项目涉及到的很多技术就懵逼了,要学会思路
- 注意
-
涉及的技术链路环节: 文档加载器->文档转换器->文本嵌入模型->向量存储->检索器
- RAG数据流水线示意图
原始数据 → 数据加载 → 预处理 → 向量化 → 存储 → 检索增强生成 ↗ ↗ ↗ PDF 文本清洗 嵌入模型 数据库 分块 网页
-
文档加载器 (Document Loaders)
- 外部数据多样性,包括在线,本地 或者数据库等来源
- 将不同来源的原始数据(如PDF、网页、JSON、、HTML、数据库等)转换为统一格式的文档对象,便于后续处理。
- 核心任务:数据源适配与初步结构化
-
LangChain里面的Loader
-
接口文档地址【如果失效就忽略):https://python.langchain.com/docs/integrations/document_loaders/
from langchain_community.document_loaders import ( TextLoader, #文本加载 PyPDFLoader, # PDF Docx2txtLoader, # Word UnstructuredHTMLLoader, # HTML CSVLoader, # CSV JSONLoader, # JSON SeleniumURLLoader, # 动态网页 WebBaseLoader #网页加载 )
-
LangChain 设计了一个统一的接口
BaseLoader
来加载和解析文档,class BaseLoader(ABC): # noqa: B024 """Interface for Document Loader. Implementations should implement the lazy-loading method using generators to avoid loading all Documents into memory at once. `load` is provided just for user convenience and should not be overridden. """ # Sub-classes should not implement this method directly. Instead, they # should implement the lazy load method. def load(self) -> list[Document]: """Load data into Document objects.""" return list(self.lazy_load())
-
将原始数据(如文件、API 响应、文本文件、网页、数据库等)转换为 LangChain 的
Document
对象 -
load
方法返回一个Document
数组, 每个Document
包含page_content
: 文本内容metadata
: 元数据(如来源、创建时间、作者等)
class Document(BaseMedia): """Class for storing a piece of text and associated metadata. Example: .. code-block:: python from langchain_core.documents import Document document = Document( page_content="Hello, world!", metadata={"source": "https://example.com"} ) """ page_content: str """String text.""" type: Literal["Document"] = "Document"
-
-
Loader 的分类与常见类型
-
文件加载器(File Loaders)
Loader 类型 功能描述 TextLoader
加载纯文本文件(.txt) CSVLoader
解析 CSV 文件,按行生成 Document PyPDFLoader
提取 PDF 文本及元数据(基于 PyPDF2) Docx2txtLoader
读取 Word 文档(.docx) UnstructuredFileLoader
通用文件解析(支持多种格式) -
网页加载器(Web Loaders)
Loader 类型 功能描述 WebBaseLoader
抓取网页文本内容 SeleniumLoader
处理需要 JavaScript 渲染的页面 -
数据库加载器(Database Loaders)
Loader 类型 功能描述 SQLDatabaseLoader
执行 SQL 查询并加载结果 MongoDBLoader
从 MongoDB 中读取数据 -
其他加载器 (自定义) ...
-
文档加载器Loaders技术多案例实战
-
TextLoader - 加载纯文本文件
-
通用参数
encoding
: 文件编码(默认utf-8
)autodetect_encoding
: 自动检测编码(如处理中文乱码)
from langchain_community.document_loaders import TextLoader # 文本加载 loader = TextLoader("data/test.txt") documents = loader.load() print(documents) print(len(documents)) #长度 print(documents[0].page_content[:100]) # 打印前100个字符 print(documents[0].metadata) # 输出: {'source': 'data/test.txt'}
-
-
CSVLoader - 加载 CSV 文件
- 基础案例代码
from langchain_community.document_loaders import CSVLoader loader = CSVLoader("data/test.csv", csv_args={"delimiter": ","}) documents = loader.load() # 每行转换为一个 Document,metadata 包含行号 print(len(documents)) print(documents[0].metadata) # 输出: {'source': 'data.csv', 'row': 0} print(documents[0].page_content)
- 可指定列名,按行生成文档
from langchain_community.document_loaders import CSVLoader #loader = CSVLoader("data/test.csv", csv_args={"delimiter": ","}) loader = CSVLoader("data/test.csv", csv_args={"fieldnames": ["产品名称", "销售数量", "客户名称"]}) documents = loader.load() # 每行转换为一个 Document,metadata 包含行号 print(len(documents)) print(documents[1].metadata) # 输出: {'source': 'data.csv', 'row': 0} print(documents[1].page_content)
-
JSONLoader - 加载 JSON 文件
-
核心参数详解
参数 类型 必填 说明 file_path
str ✅ JSON 文件路径 jq_schema
str ✅ jq 查询语法,定义数据提取逻辑 content_key
str ❌ 指定作为文本内容的字段(默认直接使用提取到的值) metadata_func
Callable ❌ 自定义元数据处理函数 text_content
bool ❌ 是否将提取内容强制转为字符串(默认 True) -
必选参数
jq_schema
- 必须使用
jq_schema
语法指定数据提取路径 - 支持更复杂的 JSON 结构解析
- jq 语法常用模式
场景 jq_schema 示例 说明 提取根级数组 .[]
适用于 JSON 文件本身是数组 嵌套对象提取 .data.posts[].content
提取 data.posts 下的 content 条件过滤 `.users[] select(.age > 18)` 多字段合并 {name: .username, email: .contact}
组合多个字段为对象 - 必须使用
-
案例实战
-
安装依赖包
pip install jq
-
编码实战
from langchain_community.document_loaders import JSONLoader loader = JSONLoader( file_path="data/test.json", jq_schema=".articles[]", # 提取 articles 数组中的每个元素 content_key="content" # 指定 content 字段作为文本内容 ) docs = loader.load() print(len(docs)) print(docs[0]
-
-
PDF文档加载器实战和常见问题处理
-
PyPDFLoader
加载PDF文件-
PyPDFLoader
是 LangChain 中专门用于加载和解析 PDF 文件 的文档加载器。 -
它能将 PDF 按页拆分为多个
Document
对象,每个对象包含页面文本和元数据(如页码、来源路径等)。 -
适用于处理多页PDF文档的文本提取任务。
-
使用步骤
-
安装依赖库
pip install pypdf
-
案例代码实战
from langchain_community.document_loaders import PyPDFLoader # PDF加载 loader = PyPDFLoader("data/test.pdf") # 加载文档并按页分割 pages = loader.load() # 返回 Document 对象列表 # 查看页数 print(f"总页数: {len(pages)}") # 访问第一页内容 page_content = pages[0].page_content metadata = pages[0].metadata print(f"第一页内容:\n{page_content[:200]}...") # 预览前200字符 print(f"元数据: {metadata}")
-
按需加载, 通过
load()
方法的参数控制加载范围:# 加载指定页码范围(例如第2页到第4页) pages = loader.load([1, 2, 3]) # 注意页码从0开始(第1页对应索引0)
-
提取所有文本合并为单个文档, 若需将全部页面内容合并为一个字符串:
full_text = "\n\n".join([page.page_content for page in pages]) print(f"合并后的全文长度: {len(full_text)} 字符")
-
-
常见问题与解决方案
-
PDF无法加载或内容为空
- 原因:PDF为扫描版图片或加密。
- 解决:
- 使用OCR工具(如pytesseract+pdf2image)提取图片文本。
- 解密PDF后加载(需密码时,PyPDFLoader暂不支持直接解密)
-
文本分块不理想
- 调整分块策略:选择合适的分隔符或分块大小
text_splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "\n", "."], # 按段落、句子分割 chunk_size=500, chunk_overlap=100 )
-
-
-
高级技巧
-
批量处理PDF:遍历文件夹内所有PDF文件。
import os pdf_folder = "docs/" all_pages = [] for filename in os.listdir(pdf_folder): if filename.endswith(".pdf"): loader = PyPDFLoader(os.path.join(pdf_folder, filename)) all_pages.extend(loader.load())
-
Loader进阶-PDF文档里面的图片提取解析
-
如何提取PDF里面的图片文案?
PyPDFLoader
仅提取文本,如果没配置第三方类库则会提取不了对应的图片文案- 需结合其他库(如
camelot
、pdfplumber
、rapidocr-onnxruntime
)提取表格或图像。 - 如果需要提取,安装好依赖库后,设置
extract_images
参数为True
。
-
RapidOCR-ONNXRuntime
介绍-
是一个基于 ONNX Runtime 推理引擎的轻量级 OCR(光学字符识别)工具库,专注于高效、跨平台部署。
-
它是 RapidOCR 项目的一个分支,实现了更高的推理速度和更低的资源占用
-
特点:
-
跨平台支持:支持 Windows、Linux、macOS,以及移动端(Android/iOS)和嵌入式设备。
-
多语言识别:支持中文、英文、日文、韩文等多种语言,尤其擅长中英混合文本。
-
轻量级:模型体积小(约几 MB),适合资源受限的环境。
-
预处理与后处理集成:内置图像预处理(如二值化、方向校正)和文本后处理(如去除冗余字符)。
-
-
RapidOCR-ONNXRuntime 与其他主流 OCR 工具的对比:
工具 引擎 速度 准确率 语言支持 依赖项 适用场景 RapidOCR-ONNXRuntime ONNX Runtime ⭐⭐⭐⭐ ⭐⭐⭐ 多语言 少 跨平台、轻量级部署 Tesseract 自研引擎 ⭐⭐ ⭐⭐ 多语言 多 历史项目、简单场景 EasyOCR PyTorch ⭐⭐ ⭐⭐⭐ 多语言 多 快速原型开发 Microsoft Read API 云端服务 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 多语言 无 企业级、高并发云端需求 -
-
案例实战
-
安装依赖包 (耗时会有点久)
pip install rapidocr-onnxruntime
-
代码实战
from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader("data/pdf-img.pdf", extract_images=True) pages = loader.load() print(pages[0].page_content)
-
网页加载器WebBaseLoader案例实战
简介: Web网页加载器WebBaseLoader案例实战
-
什么是WebBaseLoader
WebBaseLoader
是 LangChain 中用于抓取 静态网页内容 的文档加载器。- 通过 HTTP 请求直接获取网页 HTML,并提取其中的文本内容(自动清理标签、脚本等非文本元素)
- 生成包含网页文本和元数据的
Document
对象 - 适用于新闻文章、博客、文档页面等静态内容的快速提取。
- 场景
- 知识库构建(知识问答、企业知识库)、舆情监控(新闻/社交媒体分析)
- 竞品分析(产品功能/价格监控)、SEO 内容聚合
-
使用步骤
-
安装依赖库
pip install beautifulsoup4 # HTML 解析依赖(默认已包含) pip install requests # 网络请求依赖(默认已包含)
-
目标网页要求
- 无需 JavaScript 渲染(动态内容需改用
SeleniumURLLoader
,但是很鸡肋,少用) - 未被反爬虫机制拦截(如需要,需配置代理或请求头)
- 如果动态网页,且内容提取好,还是需要单独针对不同的网站写代码进行提取内容
- 无需 JavaScript 渲染(动态内容需改用
-
-
案例实战
-
基础用法:加载单个网页
import os #代码中设置USER_AGENT, 设置USER_AGENT的代码一定要放在WebBaseLoader 这个包前面,不然还是会报错 os.environ['USER_AGENT'] = 'Mozilla/5.0 (Windows NT 14.0; Win64; x64) AppleWebKit/567.36 (KHTML, like Gecko) Chrome/58.0.444.11 Safari/337.3' from langchain_community.document_loaders import WebBaseLoader #警告日志信息:USER_AGENT environment variable not set, consider setting it to identify your requests. # 初始化加载器,传入目标URL列表(可多个) urls = ["https://www.cnblogs.com"] loader = WebBaseLoader(urls) # 加载文档(返回Document对象列表) docs = loader.load() # 查看结果 print(f"提取的文本长度: {len(docs[0].page_content)} 字符") print(f"前200字符预览:\n{docs[0].page_content[:200]}...") print(f"元数据: {docs[0].metadata}")
-
批量加载多个网页
import os #代码中设置USER_AGENT, 注意设置USER_AGENT的代码一定要放在WebBaseLoader 这个包前面,不然还是会报错 os.environ['USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' from langchain_community.document_loaders import WebBaseLoader #警告日志信息:USER_AGENT environment variable not set, consider setting it to identify your requests. # 初始化加载器,传入目标URL列表(可多个) urls = [ "https://news.baidu.com/", # 新闻 "https://tieba.baidu.com/index.html" # 贴吧 ] loader = WebBaseLoader(urls) docs = loader.load() print(f"共加载 {len(docs)} 个文档") print("各文档来源:") for doc in docs: print(f"- {doc.metadata['source']}")
-
-
更多详细API和参数 https://python.langchain.com/docs/integrations/document_loaders/web_base/
Word文档加载器实战和常见问题处理
-
Docx2txtLoader
介绍- 是 LangChain 中专门用于加载 Microsoft Word 文档(.docx) 的文档加载器。
- 提取文档中的纯文本内容(包括段落、列表、表格文字等),忽略复杂格式(如字体、颜色),生成统一的
Document
对象。 - 适用于从 Word 报告中快速提取结构化文本
-
使用步骤
-
安装依赖库
pip install docx2txt # 核心文本提取库
-
准备.docx文件:确保目标文件为 .docx 格式(旧版 .doc 需转换),且未被加密
-
案例代码实战
- 基础用法:加载单个Word文档
from langchain_community.document_loaders import Docx2txtLoader # 初始化加载器,传入文件路径 loader = Docx2txtLoader("data/test1.docx") # 加载文档(返回单个Document对象) documents = loader.load() # 查看内容 print(f"文本长度: {len(documents[0].page_content)} 字符") print(f"前200字符预览:\n{documents[0].page_content[:200]}...") print(f"元数据: {documents[0].metadata}")
- 批量加载文档
from langchain_community.document_loaders import Docx2txtLoader import os folder_path = "data/" all_docs = [] # 遍历文件夹内所有.docx文件 for filename in os.listdir(folder_path): if filename.endswith(".docx"): file_path = os.path.join(folder_path, filename) loader = Docx2txtLoader(file_path) all_docs.extend(loader.load()) # 合并所有文档 print(f"加载文件: {filename}") print(all_docs)
-
-
常见问题与解决方案
-
加载 .doc 文件报错
- 原因:docx2txt 仅支持 .docx 格式。
- 解决:使用 Word 将 .doc 另存为 .docx。
-
文档中的图片/图表未被提取
- 原因:Docx2txtLoader 仅提取文本,忽略图片。
- 解决:使用 python-docx 单独提取图片,也可以使用其他组件,类似OCR
-
RAG检索增强生成之文档切割
RAG系统链路构建之文档切割转换
-
构建RAG系统:涉及的技术链路环节: 文档加载器->文档转换器->文本嵌入模型->向量存储->检索器
- RAG数据流水线示意图
原始数据 → 数据加载 → 预处理 → 向量化 → 存储 → 检索增强生成 ↗ ↗ ↗ PDF 文本清洗 嵌入模型 数据库 分块 网页
-
需求背景,为啥要用?
-
模型输入限制:GPT-4最大上下文32k tokens,Claude 3最高200k
-
信息密度不均:关键信息可能分布在长文本的不同位置
-
格式兼容性问题:PDF/HTML/代码等不同格式的结构差异
-
-
文档转换器(Document Transformers)
-
文档转换器是 LangChain 处理文档流水线的核心组件,负责对原始文档进行结构化和语义化处理,
-
为后续的向量化存储、检索增强生成(RAG)等场景提供标准化输入。
-
核心任务:文本清洗、分块、元数据增强
-
关键操作
- 文本分块:按固定长度或语义分割(防止截断完整句子)
- 去噪处理:移除特殊字符、乱码、广告内容
- 元数据注入:添加来源、时间戳等上下文信息
-
效果
- 保留语义完整性:避免因分割导致上下文断裂或信息丢失
- 适配模型输入限制:确保分割后的文本块长度符合大语言模型(LLM)的token限制
- 优化向量化效果:通过合理分块提升向量表示的语义精度,从而提高检索匹配率
问题类型 原始文档示例 转换前问题 转换后效果 长文本溢出 500页法律合同 直接输入导致API报错 分割为上下文合规的段落 信息碎片化 产品手册PDF 技术参数分散在不同页面 按功能模块重组内容 噪音污染 网页抓取内容 包含广告/导航栏等干扰信息 提取纯净正文内容 格式混乱 代码仓库文档 Markdown/代码片段混合 分离代码与说明文本 -
基础类和核心参数说明
from langchain_text_splitters import TextSplitter #源码 class TextSplitter(BaseDocumentTransformer, ABC): """Interface for splitting text into chunks.""" def __init__( self, chunk_size: int = 4000, chunk_overlap: int = 200, length_function: Callable[[str], int] = len, keep_separator: Union[bool, Literal["start", "end"]] = False, add_start_index: bool = False, strip_whitespace: bool = True, ) -> None:
-
方法说明
-
TextSplitter
本身没有实现split_text
,要文档分割器按自己的分割策略实现分割 -
关键方法调用
split_documents()->create_documents->()->split_text()
split_text()
是基础文本分割方法create_documents()
在split_text()
基础上封装了元数据绑定逻辑split_documents()
内部调用create_documents()
并自动处理元数据传递
方法 输入类型 输出类型 元数据处理 典型使用场景 split_text()
单个字符串 List[str]
❌ 不保留元数据 仅需分割纯文本内容时使用 create_documents()
字符串列表 List[Document]
✅ 需手动传递元数据 从原始文本构建带元数据的文档对象 split_documents()
Document对象列表 List[Document]
✅ 自动继承输入文档的元数据 分割已加载的文档对象(如PDF解析结果)
-
-
chunk_size
-
定义:每个文本块的最大长度(字符数或token数),用于控制分割后的文本块大小。
-
作用
- 防止文本过长超出模型处理限制,影响检索精度。
- 较小的chunk_size能提高检索细粒度,会导致上下文缺失。
-
例子
# 设置chunk_size=100,分割文本为不超过100字符的块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20) #输入文本:"Python是一种解释型语言,适合快速开发。它支持面向对象编程,语法简洁。" #分割结果:["Python是一种解释型语言,适合快速开发。", "开发。它支持面向对象编程,语法简洁。"](假设每个块接近100字符)
-
-
chunk_overlap
-
定义:相邻文本块之间的重叠字符数,用于保留上下文连贯性。
-
作用:避免因分割导致关键信息被切断(如句子中间被截断)
-
案例一
如果chunk_size设为1024,chunk_overlap设为128, 对一个长度为2560的文本序列,会切分成3个chunk: chunk 1: 第1-1024个token chunk 2: 第897-1920个token (与chunk 1重叠128个) chunk 3: 第1793-2560个token (与chunk 2重叠128个)
-
案例二
# 设置chunk_size=50,chunk_overlap=10 text = "深度学习需要大量数据和计算资源。卷积神经网络(CNN)在图像处理中表现优异。" text_splitter = CharacterTextSplitter(chunk_size=50, chunk_overlap=10) #分割结果:["深度学习需要大量数据和计算资源。卷积神经", "计算资源。卷积神经网络(CNN)在图像处理中表现优异。"] # 重叠部分"计算资源。"确保第二块包含前一块的结尾
-
-
separators
-
定义:分隔符优先级列表,用于递归分割文本。
-
作用:优先按自然语义边界(如段落、句子)分割,减少语义断裂。
-
例子
# 默认分隔符:["\n\n", "\n", " ", ""] text_splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "。", ",", " "] ) #输入文本:"第一段\n\n第二段。第三段,第四段" #分割流程:先按\n\n分割为两段,若仍超长则按。继续分割
-
-
-
字符文档转换器TextSplitter案例实战
-
CharacterTextSplitter
字符分割器-
核心特点
- 是 LangChain 中最基础的文本分割器,采用固定长度字符切割策略。
- 适用于结构规整、格式统一的文本处理场景,强调精确控制块长度
- 适用于结构清晰的文本(如段落分隔明确的文档)。
-
核心参数详解
参数 类型 默认值 说明 separator
str "\n\n"
切割文本的分隔符 chunk_size
int 4000
每个块的最大字符数 chunk_overlap
int 200
相邻块的重叠字符数 strip_whitespace
bool True
是否清除块首尾空格 is_separator_regex
bool False
是否启用正则表达式分隔符 -
案例代码
-
长文本处理
from langchain.text_splitter import CharacterTextSplitter text = "是一段 需要被分割的 长文本示例....,每个文本块的最大长度(字符数或token数)Document loaders are designed to load document objects. LangChain has hundreds of integrations with various data sources to load data from: Slack, Notion, Google Drive" splitter = CharacterTextSplitter( separator=" ", chunk_size=50, chunk_overlap=10 ) chunks = splitter.split_text(text) print(len(chunks)) for chunk in chunks: print(chunk)
-
日志文件处理
from langchain.text_splitter import CharacterTextSplitter log_data = """ [ERROR] 2026-03-15 14:22:35 - Database connection failed [INFO] 2026-03-15 14:23:10 - Retrying connection... [WARNING] 2026-03-15 14:23:45 - High memory usage detected """ splitter = CharacterTextSplitter( separator="\n", chunk_size=60, chunk_overlap=20 ) log_chunks = splitter.split_text(log_data) for chunk in log_chunks: print(chunk)
-
-
-
优缺点说明
特性 优势 局限性 分割速度 ⚡️ 极快(O(n)复杂度) 不考虑语义结构 内存消耗 🟢 极低 可能切断完整语义单元, 对语义关联性保持较弱 配置灵活性 🛠️ 支持自定义分隔符和重叠 需要预定义有效分隔符 多语言支持 🌍 支持任意字符集文本 对表意文字计算可能不准确 -
适合场景
- 推荐使用:
- 结构化日志处理
- 代码文件解析
- 已知明确分隔符的文本(如Markdown)
- 需要精确控制块大小的场景
- 不推荐使用:
- 自然语言段落(建议用RecursiveCharacterSplitter)
- 需要保持语义完整性的场景
- 包含复杂嵌套结构的文本
- 推荐使用:
递归字符文档转换器TextSplitter案例实战
-
RecursiveCharacterTextSplitter
递归字符分割器-
核心特点
- 递归字符分割器采用多级分隔符优先级切割机制,是 LangChain 中使用最广泛的通用分割器。
- 递归尝试多种分隔符(默认顺序:
["\n\n", "\n", " ", ""]
),优先按大粒度分割 - 若块过大则继续尝试更细粒度分隔符,适合处理结构复杂或嵌套的文本。
-
核心参数说明
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=1000, # 目标块大小(字符)每个块最多包含 1000 个字符 chunk_overlap=200, # 块间重叠量,最多有200个字符重叠 separators=["\n\n", "\n", "。", "?", "!", " ", ""], # 优先级递减的分割符 length_function=len, # 长度计算函数 keep_separator=True, # 是否保留分隔符 )
-
-
案例实战
-
基础案例测试, 处理后chunk之间也有overlap
from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( #separators=["\n\n", "\n", " ", ""], #首先按段落分割,然后按行分割,最后按空格分割,如果都不行则按字符分割 chunk_size=20, #每个块的最大大小,不超过即可,如果超过则继续调用_split_text分隔(以字符数为单位) chunk_overlap=4 #块与块之间的重叠部分的大小 ) text = "I Love English hello world, how about you? If you're looking to get started with chat models, vector stores, or other LangChain components from a specific provider, check out our supported integrations" chunks = splitter.split_text(text) print(len(chunks)) for chunk in chunks: print(chunk)
-
学术论文处理
from langchain.text_splitter import RecursiveCharacterTextSplitter paper_text = """ 引言机器学习近年来取得突破性进展...(长文本)若块过大则继续尝试更细粒度分隔符,适合处理结构复杂或嵌套的文本 方法我们提出新型网络架构...(技术细节)按优先级(如段落、句子、单词)递归分割文本,优先保留自然边界,如换行符、句号 实验在ImageNet数据集上...处理技术文档时,使用chunk_size=800和chunk_overlap=100,数据表格 """ splitter = RecursiveCharacterTextSplitter( chunk_size=20, chunk_overlap=4 ) paper_chunks = splitter.split_text(paper_text) print(len(paper_chunks)) for chunk in paper_chunks: print(chunk)
-
-
避坑指南
# 错误示范:不合理的separators顺序 bad_splitter = RecursiveCharacterTextSplitter( separators=[" ", "\n"], # 空格优先会导致过早分割 chunk_size=500 ) # 正确写法:从大结构到小结构 good_splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "\n", "。", " ", ""] )
-
核心优势对比
特性 CharacterTextSplitter RecursiveCharacterTextSplitter 分隔符策略 单级固定分隔符 多级优先级递归分隔符 语义保持能力 ★★☆☆☆ ★★★★☆ 复杂文本适应性 简单结构化文本 混合格式/长文本/多语言 典型应用场景 日志/CSV 论文/邮件/网页/混合代码文 -
推荐场景
- 学术论文/技术文档解析
- 多语言混合内容处理
- 包含嵌套结构的文本(如Markdown)
- 需要保持段落完整性的问答系统
分割器常见问题和优化最佳实践
-
其他常见分割器
-
递归字符分割(RecursiveCharacterTextSplitter)
- 原理:按优先级(如段落、句子、单词)递归分割文本,优先保留自然边界(如换行符、句号)
- 适用场景:通用文本处理,尤其适合逻辑紧密的长文档(如论文、技术手册)
-
固定大小分割(CharacterTextSplitter)
- 原理:按固定字符数分割,简单但可能打断句子结构。
- 优化:通过重叠(chunk_overlap)和智能截断(如优先在标点处分隔)减少语义断裂
- 适用场景:结构松散或句子独立性强的文本(如产品说明书)
-
Token分割(TokenTextSplitter)
- 原理:基于LLM的token限制分割,避免超出模型输入长度。
- 优势:更贴近模型处理逻辑(如GPT系列)
-
结构化文档分割
- HTML/Markdown分割器:按标题层级分割,保留元数据(如
HTMLHeaderTextSplitter
) - 适用场景:网页、技术文档等结构化内容
- HTML/Markdown分割器:按标题层级分割,保留元数据(如
-
还有很多,可以查看官方文档拓展(如果不可访问,百度搜索)
-
-
关键参数
chunk_size、chunk_overlap
参数 作用 默认值 关键限制条件 chunk_size
定义每个文本块的最大长度(根据 length_function
计算,默认按字符数)1000 必须为正整数 chunk_overlap
定义相邻块之间的重叠长度 20 必须小于 chunk_size
的50% -
重叠内容未出现的常见原因
-
文本总长度不足:当输入文本长度 ≤ chunk_size时,不会触发分割。
#解释:文本长度远小于chunk_size,不触发分割,无重叠。 from langchain_text_splitters import CharacterTextSplitter text = "这是一个非常短的测试文本。" text_splitter = CharacterTextSplitter( chunk_size=100, chunk_overlap=20, separator="。", # 按句号分割 length_function=len ) chunks = text_splitter.split_text(text) print(chunks) # 输出:['这是一个非常短的测试文本。']
-
递归分割策略:RecursiveCharacterTextSplitter优先保证块大小,可能牺牲重叠。
# 解释:当无法找到分隔符时,按字符数硬分割,强制保留重叠。 from langchain_text_splitters import RecursiveCharacterTextSplitter text = "这是一段没有标点的超长文本需要被分割成多个块但是因为没有分隔符所以分割器会尝试按字符递归分割直到满足块大小要求" text_splitter = RecursiveCharacterTextSplitter( chunk_size=30, chunk_overlap=10, separators=["", " "], # 无有效分隔符时按字符分割 length_function=len ) chunks = text_splitter.split_text(text) for i, chunk in enumerate(chunks): print(f"块{i+1}(长度{len(chunk)}): {chunk}") # 输出示例: # 块1(长度30): 这是一段没有标点的超长文本需要被分割成多个块但是因为没有分隔 # 块2(长度30): 个块但是因为没有分隔符所以分割器会尝试按字符递归分割直到满足 # 块3(长度15): 字符递归分割直到满足块大小要求 # 重叠部分:"个块但是因为没有分隔"(10字符)
-
enumerate()
- 函数是一个内置函数,用于在迭代过程中同时获取元素的索引和值。
- 它返回一个枚举对象,包含了索引和对应的元素
# enumerate(iterable, start=0) #参数:iterable:必需,一个可迭代对象,如列表、元组、字符串等。 #参数:start:可选,指定索引的起始值,默认为 0。 fruits = ['apple', 'banana', 'orange'] for index, fruit in enumerate(fruits): print(index, fruit)
-
-
分隔符强制分割:在分隔符处切割时,剩余文本不足以形成重叠。
#解释:分隔符优先切割,每个块正好为7个字符,无法形成重叠。 from langchain_text_splitters import CharacterTextSplitter text = "abcdefg.hijkllm.nopqrst.uvwxyz" text_splitter = CharacterTextSplitter( chunk_size=7, chunk_overlap=3, separator=".", # 按句号分割 is_separator_regex=False ) chunks = text_splitter.split_text(text) print("分割块数:", len(chunks)) for i, chunk in enumerate(chunks): print(f"块{i+1}: {chunk}")
-
-
参数调优最佳实践
-
通用文本处理
- 参数建议:
chunk_size=500-1000字符,chunk_overlap=10-20%
- 案例:
- 处理技术文档时,使用
chunk_size=800和chunk_overlap=100
- 确保每个块包含完整段落,同时通过重叠保留跨段落的关键术语
- 处理技术文档时,使用
- 参数建议:
-
代码分割
-
参数建议:根据编程语言特性调整分隔符。
-
案例:
- 分割Python代码时,
RecursiveCharacterTextSplitter.from_language(Language.PYTHON)
- 会自动识别函数、类等结构,避免打断代码逻辑
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter python_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.PYTHON, chunk_size=200, chunk_overlap=50 )
- 分割Python代码时,
-
-
结构化文档(如Markdown)
-
参数建议:结合标题层级分割。
-
案例:
- 使用MarkdownHeaderTextSplitter按标题分割,保留元数据
- 输入Markdown内容将按标题层级生成带元数据的块
headers_to_split_on = [("#", "Header 1"), ("##", "Header 2")] markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
-
-
-
常见问题与解决方案
-
过长导致语义模糊
- 表现:检索时匹配不精准。
- 解决:缩小chunk_size,增加chunk_overlap
-
块过短丢失上下文
- 表现:回答缺乏连贯性。
- 解决:合并相邻块或使用ParentDocumentRetriever,将细粒度块与父文档关联
-
参数选择原则:
-
密集文本(如论文):chunk_size较大(如1000),chunk_overlap约15%。
-
松散文本(如对话记录):chunk_size较小(如200),chunk_overlap约20%。
-
实验验证:通过AB测试对比不同参数的检索准确率与生成质量
-
-
人工智能和高等数学核心基础扫盲
扫盲-AI大模型必备之向量-张量和应用场景
-
什么是向量
-
向量就是一串有序的数字,像一条带方向的“箭头”, 在机器学习里面尤其重要
-
世间万物使用计算机表示,用的数字化语言,让计算机能理解复杂事物, 每个数据都包含多种属性
- 比如气象数据(包含温度,湿度,风向等等)
- 金融数据(开盘价,收盘价,交易量等等)
- 销售数据(价格,库存量,卖出数量等等)
-
为了表示多属性的数据,或者称为多维度的数据,向量最为合适。
-
向量就是有几个数字横向或者纵向排列而成,每个数字代表一个属性。
// 传统Java数据存储 String[] names = {"小明", "身高", "体重"}; Object[] person = {"张三", 175, 68.5}; // 向量化表示(特征向量) float[] vector = {0.83f, 175.0f, 68.5f}; // [性别编码, 身高, 体重] # Python列表表示 vector = [1.2, 3.5, 4.0, 0.8]
-
向量能做什么?
- 计算相似度:比如比较两个人的喜好是否接近、物品是否类似
- 用户画像:
[年龄=25, 身高=175, 消费=5000]
→ 用数字描述一个人 - 案例:有两个水果的向量(也可以抽取更多属性)
- 苹果:[红色: 0.91, 甜度: 0.83, 圆形: 0.79]
- 草莓:[红色: 0.85, 甜度: 0.75, 圆形: 0.69]
-
-
多维向量
-
就是维度更多的向量(比如100个数字组成的列表)。
-
例子:
-
词向量:
“猫” = [0.2, -0.3, 0.7, ..., 0.1]
(300个数字表示词义,抽取多点属性,形状、毛发、行为、食物...)。 -
图片特征:一张猫的图片转换为
[0.8, 0.1, 0.05, ...]
(1000个数字描述图片内容) -
高维向量表示用户画像
public class UserVector { // 每个维度代表一个特征 float[] features = new float[256]; // 可能包含: // [0-49]: 兴趣标签权重 // [50-99]: 行为频率 // [100-255]: 深度模型提取特征 }
-
-
为什么维度越多越好?
- 细节更丰富 , 就像简历写得越详细,越能区分不同的人。
- 低维:
[年龄=25, 性别=1]
→ 只能简单分类。 - 高维:
[年龄、性别、职业、兴趣1、兴趣2...]
→ 精准推荐商品。
- 低维:
- 细节更丰富 , 就像简历写得越详细,越能区分不同的人。
-
多维向量应用场景
- 推荐系统:用用户向量和商品向量计算匹配度。
- 人脸识别:把照片变成向量,对比找到最相似的人。
- 数据表示:用户画像(年龄、性别、兴趣等N个特征)
- 几何计算:三维游戏中物体的位置和移动
-
通俗理解高维空间
- 假设你在三维空间找不到两片不同的树叶,但在100维空间里,每片树叶的位置都会独一无二
-
-
张量
-
张量是多维数组的统称,涵盖标量、向量、矩阵及更高维结构 ,
-
阶(Rank)表示维度数:
- 标量(0阶张量):单个数字(如温度25℃)。
- 向量(1阶张量):一维数组(如
[1, 2, 3]
)。 - 矩阵(2阶张量):二维表格(如Excel表格)。
- 高阶张量:三维及以上(如视频数据:时间×宽×高×颜色通道)
阶数 数学名称 典型应用 Java存储结构 0 标量 温度值 float 1 向量 用户画像 float[] 2 矩阵 Excel表格 float[][] 3 张量 彩色图片 float[][][] 数据 张量形状 解释 一张黑白图片 (28, 28)
28行×28列的像素矩阵 一个视频片段 (100, 128, 128, 3)
100帧,每帧128x128的RGB图片 一批用户数据 (500, 10)
500个用户,每人10个特征
-
-
常见问题
-
为什么要用张量而不是数组?
- 张量是AI框架(如PyTorch)的“通用语言”,能自动支持GPU加速和梯度计算。
-
维度太多会不会算不过来?
- 会!所以需要GPU和优化算法(比如深度学习)。
-
我该用几维向量
- 看任务需求:简单任务用几十维,复杂任务(如ChatGPT)可能用上千维!
-
-
总结:一句话记住它们!
- 向量 → 一串数字(像GPS坐标)。
- 多维向量 → 更长的数字串(像详细简历)。
- 张量 → 数字的“集装箱”(像Excel表格、图片集、视频流)。
扫盲-高等数学里面的求和-点积公式讲解
-
什么是求和符号?
-
点积(内积)的定义与计算
-
点积是什么
- 点积是两个向量对应分量相乘后求和的运算,结果是一个标量(数值)。
-
几何意义
- 点积反映两个向量的夹角关系
- 通过在空间中引入笛卡尔坐标系,向量之间的点积既可以由向量坐标的代数运算得出
-
点积计算示例
-
扫盲-向量的相似度计算之余弦相似度
-
补充:三角函数
- 正弦(sinθ):对边长度与斜边长度的比值。
- 余弦(cosθ):邻边长度与斜边长度的比值(即夹角的两边)
-
为啥要学余弦相似度?
- 生活中的相似度问题,假设你需要完成以下任务:
- 网购时:找出和你刚买的衣服最搭配的裤子
- 听歌时:发现与当前播放歌曲风格相似的曲目
- 点外卖时:找到和你常点菜品口味接近的新店铺
- LLM大模型的RAG原理:用户输入自然语言query,找到最相关的文档
- 这些场景的共同点:需要量化两个事物的相似程度,向量空间中的"方向感"
- 生活中的相似度问题,假设你需要完成以下任务:
-
什么是余弦相似度?
-
基础定义:余弦相似度(Cosine Similarity)用于衡量两个向量在方向上的相似程度,忽略其绝对长度
-
值的范围为[-1,1],-1为完全不相似,1为完全相似。
-
公式(向量的模长等于其各分量平方和的平方根)
-
直观理解
- 几何意义:两个向量的夹角越小,方向越一致,余弦值越接近1。
- 1 → 完全相同方向
- 0 → 完全无关(正交)
- -1 → 完全相反方向
- 两条线段之间形成一个夹角,
- 如果夹角为0度,意味着方向相同、线段重合,这是表示两个向量代表的文本完全相等;
- 如果夹角为90度,意味着形成直角,方向完全不相似;
- 如果夹角为180度,意味着方向正好相反。
- 因此可以通过夹角的大小,来判断向量的相似程度,夹角越小,就代表越相似。
- 几何意义:两个向量的夹角越小,方向越一致,余弦值越接近1。
-
为何选择余弦相似度?
- 高维数据友好:适用于文本、图像等高维稀疏数据。
- 长度不变性:只关注方向,忽略向量长度差异(如文档长短)。
- 计算高效:适合大规模向量检索
-
-
案例
- 以二维向量为例:向量A = [3, 4],向量B = [1, 2]
- 结论:两个向量方向高度相似!
-
大模型中的核心应用场景
- 语义搜索(Semantic Search)
- 问题:用户输入自然语言query,找到最相关的文档。
- 实现步骤:
- 将query和所有文档编码为向量。
- 计算query向量与文档向量的余弦相似度。
- 返回Top-K相似度最高的文档。
- 推荐系统(User-Item Matching)
- 原理:用户向量和物品向量的余弦相似度 → 推荐得分。
- 示例:
- 用户向量:[0.3, 0.5, -0.2](表示对科技、体育、艺术的兴趣)
- 物品向量:[0.4, 0.1, 0.0](科技类文章)
- 相似度 ≈ 0.3×0.4 + 0.5×0.1 = 0.17 → 推荐该文章。
- 语义搜索(Semantic Search)
-
为什么重要:掌握余弦相似度,就是掌握了连接数据与智能的钥匙!
科学计算核心库NumPy和推荐系统案例
-
什么是 NumPy?
- NumPy(Numerical Python)是 Python 的科学计算核心库,专门处理多维数组(矩阵)的高效运算。
- 核心功能:提供高性能的数组对象 ndarray,支持向量化操作(无需循环直接计算)。
- 江湖地位:几乎所有 Python 科学计算库(如 Pandas、SciPy、TensorFlow)都依赖 NumPy。
- 设计目标:用简洁的语法替代传统循环,提升大规模数据计算效率。
-
为什么需要 NumPy?对比原生 Python
-
原生 Python 列表的痛点
# 计算两个列表元素相加 a = [1, 2, 3] b = [4, 5, 6] result = [a[i] + b[i] for i in range(len(a))] # 需要循环逐个计算
-
NumPy 的解决方案
import numpy as np a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) result = a + b # 直接向量化运算 → 输出 [5,7,9]
-
性能对比(关键优势)
操作类型 Python 列表耗时 NumPy 耗时 速度提升倍数 100万元素求和 15 ms 0.5 ms 30倍 矩阵乘法 手动循环实现复杂 单行代码 100倍+
-
-
基础安装
# 常规安装 pip install numpy # 验证安装, 应输出版本号 python -c "import numpy as np; print(np.__version__)" # 升级版本 pip install numpy --upgrade
-
余弦相似度案例实战
-
案例1:基础向量计算
import numpy as np def cos_sim(v1, v2): """ 计算两个向量的余弦相似度 余弦相似度用于衡量两个向量方向的相似性,结果范围从-1到1 -1表示完全相反,1表示完全相同,0表示两者正交(即无相似性) 参数: v1: numpy数组,第一个向量 v2: numpy数组,第二个向量 返回: 两个向量的余弦相似度 """ # 计算向量点积 dot = np.dot(v1, v2) # 计算向量的模的乘积 norm = np.linalg.norm(v1) * np.linalg.norm(v2) # 返回余弦相似度 return dot / norm # 测试向量(支持任意维度) vec_a = np.array([0.2, 0.5, 0.8]) # 文本A的嵌入向量 vec_b = np.array([0.3, 0.6, 0.7]) # 文本B的嵌入向量 # 输出两向量的余弦相似度 print(f"相似度:{cos_sim(vec_a, vec_b):.4f}") # 输出:0.9943
-
案例2:推荐系统
import numpy as np def cosine_similarity(a, b): # 将列表转换为NumPy数组 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) if norm_a * norm_b != 0 else 0 # 用户嵌入向量(根据浏览行为生成) user_embedding = [0.7, -0.2, 0.5, 0.1] # 商品嵌入库 products = { "item1": [0.6, -0.3, 0.5, 0.2], "item2": [0.8, 0.1, 0.4, -0.1], "item3": [-0.5, 0.7, 0.2, 0.3] } # 计算相似度并推荐 recommendations = [] for item_id, vec in products.items(): sim = cosine_similarity(user_embedding, vec) recommendations.append((item_id, round(sim, 3))) # 保留3位小数 # 按相似度降序排序 recommendations.sort(key=lambda x: x[1], reverse=True) print("推荐排序:", recommendations)
-
RAG系统必备之嵌入大模型Embedding
嵌入大模型Embedding和LLM大模型对比
-
什么是文本嵌入Text Embedding
-
文本嵌入(Text Embedding)是将文本(单词、短语、句子或文档)映射到高维向量空间的技术。
-
类比
- 假设你是一个Java工程师,现在需要将一段文字(比如用户评论)存入数据库。
- 传统方式可能是存字符串,但计算机无法直接“理解”语义。
- Embedding的作用
- 把文字转换成一个固定长度的数字数组(向量),比如
[0.2, -0.5, 0.8, ...]
,这个数组能“编码”文字的含义。 - 想象每个词或句子是一个点,而Embedding就是给这些点在地图上标坐标。
- 语义相近的词(如“猫”和“狗”)坐标距离近,无关的词(如“猫”和“汽车”)坐标距离远。
- 把文字转换成一个固定长度的数字数组(向量),比如
-
通俗来说
- 将不可计算、非结构化 的词 转化为 可计算,结构化的 向量,把文字变成一串有含义的数字密码
- 就像给每个句子/词语颁发"数字身份证"。这个身份证号码能表达文字的核心含义
- 例:"猫" → [0.2, -0.5, 0.8,...](300个数字组成的向量)
-
核心特点
-
语义感知:相似的文字数字也相似
# "狗"和"犬"的嵌入向量距离近 # "苹果"(水果)和"苹果"(手机)的嵌入距离远
-
降维表示:将离散的文本转化为连续的向量
-
维度固定:无论输入多长,同个嵌入大模型 输出数字个数相同(如384/768维)
-
-
案例【采用二维方式,正常的向量化后都是上千个维度】
-
句子
- 句子1:老王喜欢吃香蕉
- 句子2:冰冰喜欢吃苹果
- 句子3:老帆在打篮球
-
向量化后续图形表示【二维】
- 句子1和2相近,因为维度大体相同
-
-
-
应用场景
-
语义搜索: 找含义相近的内容,不依赖关键词匹配
# 搜索"如何养小猫咪" → 匹配到"幼猫护理指南"
-
智能分类:自动识别用户评论的情绪/类型
-
问答系统:快速找到与问题最相关的答案段落
-
-
什么是嵌入Embedding大模型
- Embedding 模型的主要任务是将文本转换为数值向量表示
- 这些向量可以用于计算文本之间的相似度、进行信息检索和聚类分析
- 文本嵌入的整体链路
原始文本 → Embedding模型 → 数值向量 → 存储/比较
- LLM 大模型 vs Embedding 大模型
维度 | LLM (如GPT-4、Claude) | Embedding 模型 (如BERT、text-embedding-3) |
---|---|---|
核心能力 | 理解并生成人类语言 | 将文本转化为数学向量 |
输出形式 | 自然文本(对话/文章/代码) | 数值向量(如1536维浮点数) |
典型交互 | 多轮对话、持续创作 | 单次转换、批量处理 |
-
关键联系
-
数据基础相同:
- 都通过海量文本训练,理解语言规律
- 就像作家和图书管理员都读过很多书
-
协作关系
- 常组合使用,Embedding快速筛选相关信息,LLM精细加工生成最终结果
- 就像先让图书管理员找到资料,再让作家整理成报告
-
-
常见误区
- ❌ 误区1:"Embedding是简化版LLM"→ 其实它们是不同工种,就像厨师和营养师的关系
- ❌ 误区2:"LLM可以直接做Embedding的事"→ 虽然理论上可以,但就像用跑车送外卖——又贵又慢
- ❌ 误区3:"Embedding模型不需要训练"→ 好的Embedding也需要大量训练,就像图书管理员需要学习分类方法
-
一句话总结
- LLM是内容生产者,Embedding是内容组织者
- 它们就像餐厅里的厨师和配菜员,一个负责烹饪(生成内容),一个负责准备食材(组织信息)
-
组合应用场景
- 场景1:智能客服系统
- Embedding:把用户问题"我的订单怎么还没到?"转换成向量,快速匹配知识库中相似问题
- LLM:根据匹配到的问题模板,生成具体回答:"您的订单已发货,预计明天送达"
- 场景2:论文查重
- Embedding:把论文段落转为向量,计算与数据库的相似度
- LLM:若发现高相似度,自动改写重复段落并给出修改建议
- 场景1:智能客服系统
LangChain框架文本嵌入Embedding实战
-
LangChain框架中的Embedding
-
通过标准化接口集成了多种嵌入模型,支持开发者灵活调用
-
功能:对接各类文本嵌入模型的标准化接口
-
作用:将文本转换为向量,供后续检索/比较使用
-
类比:不同品牌手机充电器 → LangChain是万能充电头
-
源码查看
from langchain.embeddings import OpenAIEmbeddings from abc import ABC, abstractmethod from langchain_core.runnables.config import run_in_executor class Embeddings(ABC): @abstractmethod def embed_documents(self, texts: list[str]) -> list[list[float]]: """Embed search docs. Args: texts: List of text to embed. Returns: List of embeddings. """ @abstractmethod def embed_query(self, text: str) -> list[float]: """Embed query text. Args: text: Text to embed. Returns: Embedding. """
-
支持的嵌入模型类型【不同嵌入模型维度和精度不一样】
类型 代表模型 特点 云端API OpenAI, Cohere, HuggingFace Hub 无需本地资源,按量付费 本地开源模型 Sentence-Transformers, FastText 数据隐私高,可离线运行 自定义微调模型 用户自行训练的模型 领域适配性强 -
核心API与属性
方法 作用 示例场景 embed_query(text)
对单个文本生成向量 用户提问的实时嵌入 embed_documents(texts)
批量处理多个文本,返回向量列表 处理数据库文档 max_retries
失败请求重试次数 应对API不稳定 request_timeout
单次请求超时时间(秒) 控制长文本处理时间
-
-
案例实战
- 在线嵌入模型使用,也可以使用其他的厂商
- 地址:https://bailian.console.aliyun.com/
from langchain_community.embeddings import DashScopeEmbeddings # 初始化模型 ali_embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) # 分析商品评论情感倾向 comments = [ "衣服质量很好,但是物流太慢了", "性价比超高,会回购!", "尺寸偏小,建议买大一号" ] # 生成嵌入向量 embeddings = ali_embeddings.embed_documents(comments) print(embeddings) print(len(embeddings)) # 5 print(len(embeddings[0])) # 1536
本地私有化部署嵌入大模型Embedding实战
-
为什么要本地部署嵌入大模型
- 对比云端风险:第三方API可能造成数据泄露(如某云服务商API密钥泄露事件)
需求类型 典型场景 案例说明 数据安全 政府/金融/医疗行业 医院病历分析需符合《数据安全法》 定制化需求 垂直领域术语适配 法律文书嵌入需理解专业法条词汇 成本控制 长期高频使用场景 电商评论分析每日百万次调用 网络限制 内网隔离环境 军工企业研发内部知识库 -
本地部署嵌入大模型数据闭环
用户数据 → 企业内网服务器 → 本地模型处理 → 结果存于本地数据库
-
部署实战
-
使用ollama下载嵌入大模型
#下载嵌入模型 ollama run mofanke/acge_text_embedding # 后台启动服务(默认端口11434) ollama serve & #查看运行的模型 ollama ps
-
嵌入模型请求测试
curl http://localhost:11434/api/embeddings -d '{"model": "mofanke/acge_text_embedding", "prompt": "小滴课堂AI大模型课程"}'
-
编码实战
- 由于 LangChain 0.3.x 尚未原生支持 Ollama 嵌入模型,需自定义接口类
pip install requests
from typing import List, Optional from langchain.embeddings.base import Embeddings import requests class OllamaEmbeddings(Embeddings): def __init__(self, model: str = "llama2", base_url: str = "http://localhost:11434"): self.model = model self.base_url = base_url def _embed(self, text: str) -> List[float]: try: response = requests.post( f"{self.base_url}/api/embeddings", json={ "model": self.model, "prompt": text # 注意:某些模型可能需要调整参数名(如"prompt"或"text") } ) response.raise_for_status() return response.json().get("embedding", []) except Exception as e: raise ValueError(f"Ollama embedding error: {str(e)}") def embed_query(self, text: str) -> List[float]: return self._embed(text) def embed_documents(self, texts: List[str]) -> List[List[float]]: return [self._embed(text) for text in texts]
- 使用自定义嵌入模型处理文档
embeddings = OllamaEmbeddings( model="mofanke/acge_text_embedding", base_url="http://localhost:11434" ) # 分析商品评论情感倾向 comments = [ "衣服质量很好,但是物流太慢了", "性价比超高,会回购!", "尺寸偏小,建议买大一号" ] # 生成嵌入向量 embeddings = embeddings.embed_documents(comments) print(embeddings) print(len(embeddings)) # 3 print(len(embeddings[0])) # 1024
-
【面试题】RAG系统构建之嵌入模型性能优化
-
需求背景(面试高频题目)
- 嵌入计算的痛点
- 嵌入生成成本高:每次调用模型API都需要计算资源
- 重复计算浪费:相同文本多次生成嵌入浪费资源
- API调用限制:商业API有调用次数限制和成本
- 响应速度瓶颈:实时场景需要快速响应
- 解决方案:缓存
- 降低计算成本:相同文本只需计算一次
- 提升响应速度:缓存读取比模型计算快10-100倍
- 突破API限制:本地缓存不受远程API配额限制
- 支持离线场景:网络不可用时仍能获取历史嵌入
- 嵌入计算的痛点
-
CacheBackedEmbeddings
介绍-
技术架构图
[应用程序] → 检查缓存 → 命中 → 返回缓存嵌入 ↓ 未命中 → 调用模型 → 存储结果 → 返回新嵌入
-
是LangChain提供的缓存装饰器,支持 本地文件系统、Redis、数据库等存储方式,自动哈希文本生成唯一缓存键
from langchain.embeddings import CacheBackedEmbeddings
-
核心语法与参数
from langchain.storage import LocalFileStore from langchain.embeddings import CacheBackedEmbeddings # 初始化 cache = CacheBackedEmbeddings( underlying_embeddings=embedding_model, # 原始嵌入模型 document_embedding_store=storage, # 缓存存储对象 namespace="custom_namespace" # 可选命名空间(隔离不同项目) )
参数 类型 作用 underlying_embeddings
Embeddings 原始嵌入模型(如OpenAIEmbeddings) document_embedding_store
BaseStore 缓存存储实现类(如LocalFileStore) namespace
str 缓存命名空间(避免键冲突) -
存储支持多类型
#这个包里面 from langchain.storage __all__ = [ "create_kv_docstore", "create_lc_store", "EncoderBackedStore", "InMemoryByteStore", "InMemoryStore", "InvalidKeyException", "LocalFileStore", "RedisStore", "UpstashRedisByteStore", "UpstashRedisStore", ]
-
-
应用案例:智能客服知识库加速
-
场景需求
- 知识库包含10万条QA对
- 每次用户提问需要检索最相关答案
- 传统方式每次请求都要计算所有问题嵌入
-
缓存方案
- 首次加载时全量预计算并缓存
- 后续请求直接读取缓存
- 新增问题时动态更新缓存
-
代码案例
-
基础版本(无缓存)
from langchain.embeddings import OpenAIEmbeddings # 初始化模型 embedder = OpenAIEmbeddings(openai_api_key="sk-xxx") # 生成嵌入 vector = embedder.embed_documents("如何重置密码?") print(f"向量维度:{len(vector)}")
-
带缓存版本
from langchain.storage import LocalFileStore from langchain.embeddings import CacheBackedEmbeddings # 创建文件缓存 fs = LocalFileStore("./embedding_cache/") # 组合缓存与模型(Java的装饰器模式模式类似) cached_embedder = CacheBackedEmbeddings.from_bytes_store( underlying_embeddings=embedder, document_embedding_store=fs, namespace="openai-embeddings" # 区分不同模型版本 ) # 首次调用(写入缓存) vector1 = cached_embedder.embed_documents("如何重置密码?") # 二次调用(读取缓存) vector2 = cached_embedder.embed_documents("如何重置密码?") print(f"结果一致性:{vector1 == vector2}") # 输出True
-
高级配置示例(分布式案例-存储Redis)
# 带TTL的Redis缓存 from redis import Redis from langchain.storage import RedisStore redis_client = Redis(host="localhost", port=6379) redis_store = RedisStore(redis_client, ttl=86400) # 24小时过期 cached_embedder = CacheBackedEmbeddings.from_bytes_store( underlying_embeddings=embedder, document_embedding_store=redis_store, namespace="openai-v3" )
-
-
嵌入大模型CacheBackedEmbeddings案例实战
-
案例实战:对比嵌入大模型使用缓存前后性能区别
-
注意:API设计区别
-
embed_documents
:面向批量文档预处理(适合缓存) -
embed_query
:面向实时查询处理(默认不缓存) -
设计考量因素
维度 embed_documents
embed_query
使用场景 预处理大量重复文档(如知识库构建) 实时响应用户查询 数据特征 高重复率(法律条款、产品描述) 低重复率(用户提问多样化) 性能损耗 批量处理可分摊缓存读写开销 单次查询增加延迟(缓存命中率低) 存储效率 缓存大量重复文本收益显著 缓存大量唯一查询浪费资源
-
-
编码实战
from langchain.embeddings import CacheBackedEmbeddings # 已被弃用,需改为从 langchain_community.embeddings 导入 # from langchain.embeddings import DashScopeEmbeddings from langchain_community.embeddings import DashScopeEmbeddings import time # 初始化模型和缓存 from langchain.storage import LocalFileStore # 初始化模型 embedding_model = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) storage = LocalFileStore("./embedding_cache/") # 本地缓存目录 cached_embeddings = CacheBackedEmbeddings.from_bytes_store( embedding_model, storage, namespace="openai_emb" # 命名空间隔离不同模型 ) texts = ["小滴课堂AI大模型开发实战", "小滴课堂AI大模型开发实战"] # 故意重复 start_time = time.time() # 第一次调用(写入缓存) emb1 = cached_embeddings.embed_documents(texts) print(f"首次调用嵌入维度: {len(emb1[0])}") embedded1_end_time = time.time() print(f"首次调用耗时: {embedded1_end_time - start_time}") # 第二次调用(读取缓存) emb2 = cached_embeddings.embed_documents(texts) print(f"二次调用结果相等: {emb1 == emb2}") embedded2_end_time = time.time() print(f"二次调用耗时: {embedded2_end_time - embedded1_end_time}")
-
最佳实践建议
-
适用场景
- 处理大量重复文本(如商品描述、法律条款)
- 需要控制API调用成本的商业应用
- 本地模型加速重复计算
-
存储选择策略
存储类型 优点 缺点 适用场景 LocalFileStore 零配置、易调试 不适合分布式 本地开发/测试 RedisStore 高性能、支持分布式 需要运维Redis 生产环境集群 InMemoryStore 最快速度 重启丢失数据 临时测试
-
大模型必备技术Milvus向量数据库
向量数据库介绍和技术选型思考
-
为什么要用向量数据库,不能用MySQL存储?
- 文档块通过嵌入模型处理后得到对应向量,下一步就是将向量存储到数据库中,方便后续进行检索使用
-
传统数据库的局限性
- 维度灾难:传统索引(B-Tree/Hash)在100+维度时效率断崖式下降,无法高效处理高维向量(常达768-1536维)
- 相似度计算:无法高效处理余弦相似度/Euclidean距离等复杂运算
- 实时性要求:亿级向量场景下传统方案响应延迟高达秒级,暴力搜索时间复杂度O(N)
// 传统关系型数据库查询(精确匹配) SELECT * FROM products WHERE category = 'electronics'; // 向量数据库查询(相似度匹配) Find top5 similar_products where description ≈ '高性能游戏本'
-
向量数据库的核心能力
- 相似性搜索:余弦相似度/欧式距离
- 混合查询:向量搜索 + 传统条件过滤
- 动态扩展:支持实时数据更新
- 高效存储:压缩向量存储技术
-
向量数据库典型应用场景
场景 案例 核心需求 推荐系统 电商商品推荐 高并发低延迟 语义搜索 法律条文检索 高精度召回 AI代理记忆 GPT长期记忆存储 快速上下文检索 图像检索 以图搜图系统 多模态支持
-
主流的向量数据库介绍
-
开源向量数据库
- Milvus
- 核心优势:分布式架构支持千亿级向量规模,QPS超百万级,提供HNSW、IVF-PQ等多样化索引算法,支持高并发场景如金融风控、生物医药分子库检索。
- 优点:高扩展性、多租户支持、完整的API生态(Python/Java/Go等)。
- 缺点:部署复杂度高,运维成本较大,适合有专业团队的企业。
- Qdrant
- 核心优势:基于Rust开发,支持稀疏向量检索(速度提升16倍),内置标量量化和产品量化技术优化存储效率,适合电商推荐、广告投放等高并发场景。
- 优点:高性能过滤、云原生设计,支持地理空间和混合数据类型。
- 缺点:社区生态较新,文档和案例相对较少。
- Milvus
-
云原生服务
- Pinecone
- 核心优势:全托管服务,实时数据更新延迟低于100ms,支持Serverless计费(按查询付费),适合SaaS快速集成和中小型企业。
- 优点:零运维、低延迟、无缝集成LangChain生态。
- 缺点:成本较高,大规模数据场景费用显著。
- 腾讯云VectorDB
- 核心优势:国产化方案,单索引支持千亿向量,集成AI套件实现文档自动向量化,适合政务、金融等数据主权敏感场景。
- 优点:端到端RAG解决方案,与腾讯云生态深度整合。
- 缺点:依赖腾讯云生态,跨云部署受限。
- Pinecone
-
轻量级工具
- Chroma
- 核心优势:没有深厚数据库背景的开发者也能快速上手,5分钟完成单机部署,适合学术研究、初创团队验证。
- 优点:开发友好、快速原型验证,支持LangChain集成。
- 缺点:不适合生产级大规模应用,功能有限。
- Faiss
- 核心优势:Meta开源的GPU加速检索库,百万级向量查询延迟低于10ms,常作为其他数据库的底层引擎。
- 优点:性能标杆、算法丰富,支持混合检索架构。
- 缺点:无托管服务,需自行处理分布式与高可用性。
- Chroma
-
传统数据库扩展
-
MongoDB Atlas
- 核心优势:文档数据库嵌入向量索引,支持每个文档存储16MB向量数据,适合已有MongoDB基础设施的企业智能化升级。
- 优点:事务处理与向量检索一体化,兼容现有业务逻辑。
- 缺点:向量检索性能弱于专用数据库,扩展性受限。
-
其他:PostgreSQL、ElasticSearch等
-
-
综合选型对比
维度\产品 Pinecone Milvus Qdrant Chroma 部署模式 全托管 自建/云 自建/云 嵌入式 学习曲线 简单 复杂 中等 极简 扩展能力 自动扩展 手动分片 自动分片 单机 典型场景 生产级SaaS 企业私有云 高性能需求 本地开发 成本模型 按用量付费 基础设施+运维 基础设施+运维 免费
-
-
技术选型建议
-
数据规模
-
十亿级以上:选择Milvus、腾讯云VectorDB等分布式方案。
-
百万级以下:轻量级工具如Chroma或Faiss。
-
-
部署复杂度
- 云服务优先:中小企业选Pinecone或腾讯云VectorDB,省去运维成本。
- 私有化部署:大型企业选Milvus、Qdrant,需专业团队支持。
-
成本考量
- 开源方案:Milvus、Qdrant适合长期可控成本。
- 按需付费:小规模试用选Pinecone Serverless。
-
生态兼容性
- 国产化需求:腾讯云VectorDB或华为云等国产方案。
- 现有技术栈:PostgreSQL/MongoDB扩展适合渐进式改造。
-
-
总结(花钱的多数更轻松方便)
- 向量数据库的选择需结合数据规模、业务场景、团队能力和成本预算综合评估。
- 对于AI驱动的应用(如RAG、多模态搜索),建议优先考虑云原生或分布式开源方案(如Milvus、Pinecone);
- 传统业务系统可尝试数据库扩展插件以降低迁移成本,具体案例可参考各数据库的官方文档
向量数据库Milvus介绍和架构讲解
-
什么是Milvus向量数据库
- 地址:https://milvus.io/
- 一种高性能、高扩展性的向量数据库,可在从笔记本电脑到大规模分布式系统等各种环境中高效运行。
- 可以开源软件的形式提供,也可以云服务的形式提供
- 核心能力:高性能、可扩展、低延迟,支持多种相似度计算方式(如欧氏距离、余弦相似度)。
维度 指标/能力 数据规模 支持千亿级向量,PB级存储 查询性能 亿级向量亚秒级响应(GPU加速) 扩展性 水平扩展,支持动态增删节点 查询类型 相似度搜索、混合查询、多向量联合查询 生态兼容性 支持Python/Java/Go/REST API,整合主流AI框架 - 适用场景:推荐系统、图像检索、自然语言处理(NLP)等
- 全球大厂使用者
-
支持部署的架构
- Milvus提供多种部署选项,包括本地部署、Docker、Kubernetes on-premises、云SaaS和面向企业的自带云(BYOC)
- Milvus Lite 是一个 Python 库,轻量级版本,适合在 Jupyter Notebooks 中进行快速原型开发,或在资源有限的边缘设备上运行
- Milvus Standalone 是单机服务器部署,所有组件都捆绑在一个 Docker 镜像中,方便部署
- Milvus Distributed 可部署在 K8S 集群上,采用云原生架构,适合十亿规模甚至更大的场景,该架构可确保关键组件的冗余。
-
Milvus 架构解析
- 数据处理流程 :插入数据 → 生成日志 → 持久化到存储层 → 构建索引 → 支持查询。
组件名称 核心职责 关键特性 Proxy 客户端请求入口,路由转发,负载均衡与协议转换(gRPC/RESTful) 支持负载均衡、连接池管理 Query Node 执行向量搜索,标量过滤,向量相似度计算与混合过滤 内存索引加载,GPU加速支持 Data Node 处理数据插入、日志流处理与数据持久化存储 写入日志(WAL)保障数据一致性 Index Node 负责索引构建与优化 支持后台异步构建索引 Coordinator 集群元数据管理、任务调度 高可用部署(etcd存储元数据) -
极速认知存储内容
- Collection 是一个二维表,具有固定的列和变化的行。
- 每列代表一个字段,每行代表一个实体。
- 下图显示了一个有 8 列和 6 个实体的 Collection
Milvus核心概念和数据结构讲解
-
向量数据库对比关系型数据库
Milvus 向量数据库 关系型数据库 Collection 表 Entity 行 Field 表字段 -
基础数据结构
-
Collection(集合)
- 类比关系型数据库的“表”,用于存储和管理一组具有相同结构的实体(Entity)
- Schema 定义字段结构(主键、向量、标量字段),支持动态字段(Milvus 2.3+),自动生成唯一ID(可选)
- 例子
pip install pymilvus==2.5.5 from pymilvus import FieldSchema, CollectionSchema, DataType # 定义字段 fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True), FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=768), FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=50) ] # 创建集合 schema = CollectionSchema(fields, description="商品向量库") collection = Collection(name="products", schema=schema)
-
Entity(实体)
- 数据的基本单位,包含多个字段(Field),如主键、标量字段(结构化数据)和向量字段。
- 主键(Primary Key):唯一标识实体,支持整数或字符串类型。
- 注意:Milvus 目前不支持主键去重,允许重复主键存在
- 组成要素
- 主键:唯一标识(支持整数/字符串)
- 向量:浮点数组(维度需固定)
- 标量字段:元数据(文本/数值/布尔)
- 数据存储示意图
ID (INT64) Vector (FLOAT_VECTOR[768]) Category (VARCHAR) Price (FLOAT) 1001 [0.12, 0.34, ..., 0.98] "Electronics" 299.99 1002 [0.55, 0.21, ..., 0.11] "Clothing" 89.99 -
字段(Field)
- 标量字段:存储数值、字符串等结构化数据,支持过滤查询(如 price < 100)。
- 向量字段:存储高维向量(如512维浮点数组),支持相似性搜索
-
-
查询方式(Query)
-
向量搜索:输入一个向量,返回最相似的 Top-K 结果。
-
混合查询:结合向量相似度和标量过滤条件(如“价格 < 100”)
-
-
数据组织与扩展
-
分区(Partition)
- 逻辑划分集合数据,用于优化查询性能(如按时间或地域分区)。
- 每个分区可包含多个分片(Sharding)和段(Segment),查询时按分区减少扫描范围
-
分片(Sharding)
- 数据写入时分散到不同节点,实现并行写入。
- 默认每个集合分2个分片,基于主键哈希分配
-
段(Segment)
-
物理存储单元,自动合并插入数据形成数据文件。
-
段分为“增长段”(持续写入)和“密封段”(持久化存储),查询时合并所有段结果
-
-
-
索引(类似MySQL有多个不同索引类型)
-
一种特殊的数据结构,用于快速查找和访问数据,存储在内存中. 本身不存储数据,是存储指向数据存储位置的指针或键值对。
-
Milvus索引基于原始数据构建,提高对 collection 数据搜索的速度,一个向量字段仅支持一种索引类型。
-
为提高查询性能,Milvus 支持多种索引类型,可以为每个向量字段指定一种索引类型。
-
索引类型(先大体知道即可)
索引类型 适用场景 内存消耗 精度 构建速度 FLAT 小数据集精准搜索 高 100% 快 IVF_FLAT 平衡型场景 中 98% 较快 HNSW 高召回率需求 高 99% 慢 IVF_PQ 超大规模数据 低 95% 快
-
-
相似度计算
-
欧氏距离(Euclidean Distance L2)
- 数值越小越相似,理解为两个向量为两个点,欧式距离就是这两个点的直线距离
- 两点之间的距离,最小值为0,最大值不确定
- 两个点的距离,距离越近,则相似度越高,距离越大,则差异性越大
-
内积(Inner Product 简写 IP)
- 内积又称之为点积,数值越大越相似
a·b=|a||b|cosθ
# 向量A = [a1, a2,..., an] # 向量B = [b1, b2,..., bn] IP = a1*b1 + a2*b2 + ... + an*bn
- 内积又称之为点积,数值越大越相似
-
余弦相似度(Cosine)
- 基于向量夹角的相似度。
-
Milvus的分区-分片-段结构和最佳实践
-
分区-分片-段 很多同学懵逼,用图书馆比喻理解三者关系
- 想象管理一个超大型图书馆(类比 Collection 集合),里面存放了上亿本书。
- 为了更好地管理图书,用了三种组织方式
- 分区(Partition):按书籍主题划分区域
- 比如:1楼科技区、2楼文学区、3楼艺术区
- 作用:快速定位某一类书籍,避免全馆搜索
- 类比:电商平台按商品类别(电器/服装/食品)分区存储
- 分片(Shard):每个主题区内设置多个平行书架
- 比如:科技区分成10个相同结构的书架,每个书架存100万本
- 作用:多人同时查找时,不同书架可并行工作
- 类比:分布式系统中用分片实现水平扩展
- 段(Segment):每个书架上的可拆卸书盒
- 比如:每个书架由多个书盒组成,新书先放临时盒,写满后密封成固定盒
- 作用:优化存储空间,旧书盒可压缩归档
- 类比:数据库将数据分块存储,便于后台合并优化
- 分区(Partition):按书籍主题划分区域
-
三者协作关系
维度 | 分区(Partition) | 分片(Shard) | 段(Segment) |
---|---|---|---|
层级 | 逻辑划分 | 物理分布 | 物理存储单元 |
可见性 | 用户主动创建管理 | 系统自动分配 | 完全由系统管理 |
目的 | 业务数据隔离 | 负载均衡与扩展 | 存储优化与查询加速 |
类比 | 图书馆的不同楼层 | 楼层内的多个相同书架 | 书架上的可替换书盒 |
操作 | 手动指定查询分区 | 自动路由请求到不同节点 | 自动合并/压缩 |
-
实际工作流程例子
-
场景:用户上传10万条商品数据到电商平台
-
分区阶段
- 按业务维度划分(如用户ID、时间范围), 示例:
partition_2024Q1
,vip_users
# 按商品类别创建分区 # 电子产品存入electronics分区 collection.create_partition("electronics") # 服装类存入clothing分区 collection.create_partition("clothing")
- 按业务维度划分(如用户ID、时间范围), 示例:
-
分片阶段(自动完成)
- 系统自动将数据均分到3个分片(假设集群有3个节点)
-
段阶段(自动完成)
- 分片内数据按512MB大小自动切割成多个段
-
-
三者协作原理
-
写入过程
新数据 → 选择分区 → 分配到分片 → 写入活跃段 → 段写满后冻结 → 生成新段
-
查询过程
用户请求 → 定位分区 → 并行查询所有相关分片 → 各分片扫描所有段 → 合并结果排序
-
合并优化
# 自动将小段合并成大段(类似HBase Compaction) [Segment1(100MB)] + [Segment2(100MB)] → [SegmentMerged(200MB)]
-
注意: 分区的意义是通过划定分区减少数据读取,而分片的意义在于多台机器上并行写入操作。
-
-
开发注意事项
-
分区使用技巧
- 按时间分区:
2023Q1
,2023Q2
- 按业务线分区:
user_profiles
,product_info
- 错误示范:创建超过1000个分区(影响元数据性能)
# 好的实践:按时间分区 client.create_partition( collection_name="logs", partition_name="2024-01" ) # 坏的实践:每个用户一个分区(容易超过限制)
- 按时间分区:
-
分片配置建议
- ❌ 8核机器设置128分片 → 线程频繁切换导致性能下降
- ✅ 使用公式:
分片数 = 节点数 × CPU核心数
分片数少 分片数多 单分片数据量大 单分片数据量小 写入吞吐低 写入吞吐高 易成性能瓶颈 资源消耗大 # 创建集合时指定 collection = Collection( name="product_images", shards_num=64, # 分片数 = 8台 × 8核 = 64 partitions=[ "electronics", "clothing", "home_appliances" ] ) # 调整段配置 client.set_property("dataCoord.segment.maxSize", "1024") # 1GB client.set_property("dataCoord.segment.sealProportion", "0.7")
-
段优化策略
- 监控段大小:
collection.get_segment_info()
- 手动触发合并:
collection.compact()
- 设置段容量阈值:
storage.segmentSize=1024
(单位MB) - 根据数据特性调整
if 向量维度 > 1024: maxSize = 512 # 降段大小缓解内存压力 else: maxSize = 1024
- 监控段大小:
-
Milvus部署架构选择和Docker部署实战
-
部署架构选择
- 选择取决于项目的阶段和规模,Milvus 为从快速原型开发到大规模企业部署的各种需求提供了灵活而强大的解决方案。
- Milvus Lite建议用于较小的数据集,多达几百万个向量, 不支持WINDOWS系统。
- Milvus Standalone适用于中型数据集,可扩展至 1 亿向量。
- Milvus Distributed 专为大规模部署而设计,能够处理从一亿到数百亿向量的数据集。
-
Milvus分层架构(Docker部署都包括了)
┌───────────────────────────────┐ │ Coordinator │ ← 管理元数据、负载均衡 ├───────────────┬───────────────┤ │ Query Node │ Data Node │ ← 处理查询与数据存储 ├───────────────┴───────────────┤ │ Object Storage (S3) │ ← 持久化存储(可选MinIO、AWS S3) └───────────────────────────────┘
- Milvus Standalone 是单机服务器部署,所有组件都打包到一个Docker 镜像中,部署方便。
- 此外,Milvus Standalone 通过主从复制支持高可用性。
-
有钱的当我没说,直接购买云厂商的服务:https://help.aliyun.com/zh/milvus
-
LInux服务器部署Milvus实战
-
阿里云网络安全组记得开放端口
2379
、9091
,19530
-
注意: 默认没加权限校验,生产环境使用一般都是内网,结合配置IP白名单
-
版本和课程保持一致,不然很多不兼容!!!
- 下载相关资料 ,使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/standalone_embed.sh #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/standalone_embed.sh # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/standalone_embed.sh
- 解压后执行【依赖很多,版本差异大,务必按照下面执行,否则课程无法进行下去,加我微信 xdclass6】
#启动 bash standalone_embed.sh start #停止 bash standalone_embed.sh stop #删除 bash standalone_embed.sh delete #升级 bash standalone_embed.sh upgrade
-
运行安装脚本后
- 一个名为 Milvus 的 docker 容器已在19530 端口启动。
- 嵌入式 etcd 与 Milvus 安装在同一个容器中,服务端口为2379。
- Milvus 数据卷被映射到当前文件夹中的volumes/milvus
- 访问
http://${MILVUS_PROXY_IP}:9091/webui
例子: http://47.119.128.20:9091/webui/
-
-
注意
-
Milvus Web UI 与 Attu等可视化工具 不同,它是一个内置工具,只是提供简单直观的界面,查看系统的基本信息
-
主要功能:运行环境、数据库/ Collections 详情、任务和慢查询请求,不支持数据库管理和操作任务
-
Milvus可视化客户端安装实战
-
Attu 可视化客户端介绍
- 是一款专为 Milvus 向量数据库设计的开源图形化管理工具,通过直观的界面简化了数据库的日常操作与维护流程
- 跨平台支持:提供 Docker 镜像,适配 Windows、Linux 和 macOS
- 开箱即用:无需编写代码即可完成 Milvus 的日常管理,降低学习成本
- 社区与生态:由 Zilliz 团队维护,与 Milvus 深度集成,持续更新功能
- 版本兼容性:注意 Attu 与 Milvus 版本的匹配,避免接口不兼容问题【当前安装的Milvus版本 V2.5X】
- GitHub地址:https://github.com/zilliztech/attu
-
核心功能
- 数据库与集合管理
- 数据库管理:支持创建、删除数据库,默认提供 default 数据库且不可删除。
- 集合(Collection)操作:可创建集合、定义字段(主键、标量字段、向量字段)、构建索引,并支持数据导入/导出。
- 分区与分片:支持按业务需求划分分区(如按时间或用户组),优化查询效率;默认分片数为 2,支持水平扩展。
- 向量检索与混合查询
- 相似性搜索:输入向量即可快速检索 Top-K 相似结果,支持欧氏距离(L2)、余弦相似度等度量方式。
- 标量过滤:通过 Advanced Filter 功能结合标量字段(如价格、标签)进行条件筛选,提升搜索精准度。
- 数据加载与释放:可将数据加载至内存加速检索,或释放内存以优化资源占用。
- 用户与权限管理
- 多角色权限控制:支持创建用户与角色,并分配细粒度权限(如全局权限、集合操作权限)。
- 权限类型:涵盖数据插入、删除、查询等操作,
- 例如:
- 全局权限:创建/删除数据库、管理资源组。
- 集合权限:加载/释放数据、构建索引、执行搜索。
- 用户权限:更新用户凭证、查询用户信息
- 数据库与集合管理
-
安装实战(根据系统选择对应的客户端下载,输入ip+端口)
Milvus向量数据库多案例实战
Python整合向量数据库Milvus案例实战
-
Python操作Milvus实战
-
安装 Milvus Python SDK, 支持 Python、Node.js、GO 和 Java SDK。
-
建议安装与所安装 Milvus 服务器版本相匹配的 PyMilvus 版本
-
安装
pip install pymilvus==2.5.5
-
验证安装 如果 PyMilvus 安装正确,运行以下命令时不会出现异常
python -c "from pymilvus import Collection"
-
接口可分为以下几类:
- **DDL / DCL:**createCollection / createPartition / dropCollection / dropPartition / hasCollection / hasPartition
- **DML / Produce:**插入 / 删除 / 上移
- **DQL:**搜索/查询
-
-
操作Milvus数据库
- 使用connect()连接 Milvus 服务器,进行相关操作
#也可以使用MilvusClient #from pymilvus import MilvusClient #client = MilvusClient("http://47.119.128.20:19530") from pymilvus import connections, db conn = connections.connect(host="47.119.128.20", port=19530) # 创建数据库 #db.create_database("my_database") # 使用数据库 db.using_database("my_database") # 列出数据库 dbs = db.list_database() print(dbs) #['default', 'my_database'] # 删除数据库 db.drop_database("my_database")
-
Collection与Schema的创建和管理
-
Collection 是一个二维表,具有固定的列和变化的行,每列代表一个字段,每行代表一个实体。
-
要实现这样的结构化数据管理,需要一个 Schema定义 Collections 的表结构
-
每个Schema由多个
FieldSchema
组成:from pymilvus import FieldSchema, DataType # 字段定义示例 fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True), FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=128), FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=50) ]
-
字段类型详解
数据类型 说明 示例 INT8/16/32/64
整型 DataType.INT64
FLOAT
单精度浮点数 DataType.FLOAT
DOUBLE
双精度浮点数 DataType.DOUBLE
VARCHAR
变长字符串 max_length=255
FLOAT_VECTOR
浮点向量 dim=768
-
创建collection实战
from pymilvus import connections from pymilvus import FieldSchema, DataType from pymilvus import CollectionSchema, Collection conn = connections.connect(host="47.119.128.20", port=19530) # 步骤1:定义字段 fields = [ FieldSchema("id", DataType.INT64, is_primary=True), FieldSchema("vector", DataType.FLOAT_VECTOR, dim=128), FieldSchema("tag", DataType.VARCHAR, max_length=50) ] # 步骤2:创建Schema schema = CollectionSchema(fields, description="示例集合") # 步骤3:实例化Collection collection = Collection( name="demo_collection", schema=schema, shards_num=2 # 分片数(分布式扩展关键) )
-
关键参数解析
参数 说明 推荐值 shards_num
分片数量(创建后不可修改) 集群节点数×2 description
集合描述信息 建议填写业务用途 -
动态字段Schema
-
在集合中启用动态字段后,所有未在 Schema 中定义的字段及其值都将作为键值对存储在动态字段中
# 启用动态字段(Milvus 2.3+) schema = CollectionSchema( fields, enable_dynamic_field=True )
-
案例讲解
- 假设 Collections Schema 只定义两个字段,名为
id
和vector
,启用了动态字段,在 Collections 中插入以下数据集 - 数据集包含 多个实体,每个实体都包括字段
id
,vector
, 和color
,Schema 中没有定义color
字段。 - 由于 Collections 启用了动态字段,因此字段
color
将作为键值对存储在动态字段中。
[ {id: 0, vector: [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], color: "pink_8682"}, {id: 7, vector: [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], color: "grey_8510"}, {id: 8, vector: [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], color: "white_9381"}, {id: 9, vector: [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], color: "purple_4976"} ]
类型 特点 适用场景 静态Schema 严格字段定义 数据结构固定的业务(用户画像) 动态Schema 允许灵活字段(需Milvus 2.3+) 日志类多变数据 - 假设 Collections Schema 只定义两个字段,名为
-
-
Milvus索引操作和最佳实践避坑指南
-
为什么需要索引?
- 加速查询:避免暴力比对,快速定位相似向量, 平衡召回率与查询速度
- 节省资源:减少内存占用和计算开销, 建议为经常访问的向量和标量创建索引
-
常见的索引类型
索引类型 适用场景 内存占用 精度 构建速度 FLAT 小数据精确搜索(<100万) 高 100% 快 IVF_FLAT 大数据平衡场景(千万级) 中 95%-98% 较快 HNSW 高召回率需求 高 98%-99% 慢 DISKANN 超大规模(10亿+) 低 90%-95% 最慢 -
Milvus索引操作
-
创建索引
# 导入MilvusClient和DataType模块,用于连接Milvus服务器并操作数据类型 from pymilvus import MilvusClient, DataType # 实例化MilvusClient以连接到指定的Milvus服务器 client = MilvusClient( uri="http://47.119.128.20:19530" ) # 创建schema对象,设置自动ID生成和动态字段特性 schema = MilvusClient.create_schema( auto_id=False, enable_dynamic_field=True, ) # 向schema中添加字段"id",数据类型为INT64,作为主键 schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True) # 向schema中添加字段"vector",数据类型为FLOAT_VECTOR,维度为5 schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=5) # 使用create_collection方法根据schema创建集合"customized_setup" client.create_collection( collection_name="customized_setup", schema=schema, ) # 准备索引参数,为"vector"字段创建索引 index_params = MilvusClient.prepare_index_params() # 添加索引配置,指定字段名、度量类型、索引类型、索引名和参数 index_params.add_index( field_name="vector", metric_type="COSINE", # 距离计算方式 (L2/IP/COSINE) index_type="IVF_FLAT", index_name="vector_index", params={ "nlist": 128 } #聚类中心数 (建议值:sqrt(数据量)) ) # 创建索引,不等待索引创建完成即返回 client.create_index( collection_name="customized_setup", index_params=index_params, sync=False # 是否等待索引创建完成后再返回。默认为True。 )
- 参数说明
参数 参数 field_name
指定字段名称 metric_type
用于衡量向量间相似性的算法。值有IP、L2、COSINE、JACCARD、HAMMING。只有当指定字段是向量字段时才可用。 index_type
索引类型 index_name
索引名称 params
指定索引类型的微调参数 collection_name
Collections 的名称。 sync
控制与客户端请求相关的索引构建方式。有效值: True
(默认):客户端等待索引完全建立后才返回。在该过程完成之前不会收到响应。False
:客户端收到请求后立即返回,索引在后台建立 -
查看索引信息
#列出索引名称 res = client.list_indexes( collection_name="customized_setup" ) print(res) #获取索引详细信息 res = client.describe_index( collection_name="customized_setup", index_name="vector_index" ) print(res)
-
删除索引
- 删除前需确保无查询正在使用该索引
- 删除后需重新创建索引才能进行有效查询
#如果不再需要索引,可以直接将其删除。 client.drop_index( collection_name="customized_setup", index_name="vector_index" ) print("索引已删除")
-
-
最佳实践与避坑指南
-
Schema设计原则
- 主键选择
- 推荐自增ID避免冲突
- 禁止使用向量字段作为主键
- 字段数量:单个集合不超过32个字段
- 向量维度:创建后不可修改,需提前规划
- 主键选择
-
索引选择策略:
- 百万级以下 → FLAT
- 百万到亿级 → IVF/HNSW
- 十亿级以上 → DISKANN
-
操作规范:
- 数据插入完成后再建索引
- 定期重建索引(数据变更超过30%)
- 为高频查询字段建立独立索引
-
常见错误处理
错误场景 解决方案 "字段类型不匹配" 检查插入数据与Schema定义的一致性 "主键冲突" 插入前检查ID唯一性,或使用自动生成ID "向量维度错误" 校验dim参数与数据实际维度
-
Milvus向量数据库的DML操作实战
-
核心DML操作实战
-
创建集合(Collection),集合是Milvus中数据存储的基本单位,需定义字段和索引
auto_id=True
时无需手动指定主键- 动态字段(
enable_dynamic_field=True
)允许灵活扩展非预定义字段
# 导入MilvusClient和DataType模块,用于连接Milvus服务器并操作数据类型 from pymilvus import MilvusClient, DataType # 实例化MilvusClient以连接到指定的Milvus服务器 client = MilvusClient( uri="http://47.119.128.20:19530" ) # 定义Schema schema = client.create_schema(auto_id=False, enable_dynamic_field=True) schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True) schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=128) schema.verify() # 验证Schema # 定义索引参数 index_params = client.prepare_index_params() index_params.add_index( field_name="vector", index_type="IVF_FLAT", # 量化索引,平衡速度与精度 metric_type="L2", # 相似性度量标准(欧式距离) params={"nlist": 1024} # 聚类中心数 ) # 创建集合 client.create_collection( collection_name="my_collection", schema=schema, index_params=index_params )
-
插入数据(Insert)支持单条或批量插入【可视化工具那边需要加载,包括查询等都是需要加载状态才可以操作】
data = [ {"id": 1, "vector": [0.1]*128, "text": "Sample text 1"}, {"id": 2, "vector": [0.2]*128, "text": "Sample text 2"} ] # 插入数据 insert_result = client.insert( collection_name="my_collection", data=data ) print("插入ID列表:", insert_result["ids"]) # 返回主键ID
-
删除数据(Delete)通过主键或条件表达式删除
# 按主键删除 client.delete( collection_name="my_collection", ids=[1, 2] # 主键列表 ) # 按条件删除(如删除text字段为空的记录) client.delete( collection_name="my_collection", filter="text == ''" )
-
更新数据(Update)Milvus不支持直接更新,需通过“删除+插入”实现:
# 删除旧数据 client.delete(collection_name="my_collection", ids=[3]) # 插入新数据 client.insert( collection_name="my_collection", data=[{"id": 3, "vector": [0.3]*128, "text": "Updated text"}] )
-
Milvus向量Search查询综合案例实战
-
需求说明
- 创建包含混合数据类型(标量+向量)的集合
- 批量插入结构化和非结构化数据
- 实现带过滤条件的混合查询
- 验证端到端的向量搜索流程
-
Search语法深度解析
-
核心参数说明
results = client 或 collection.search( data=[[0.12, 0.23, ..., 0.88]], # 查询向量(必须) anns_field="vector", # 要搜索的向量字段名(必须) param={"metric_type": "L2", "params": {"nprobe": 10}}, # 搜索参数 limit=10, # 返回结果数量 expr="price > 50", # 过滤表达式(可选) output_fields=["product_id", "price"], # 返回的字段 )
参数 类型 说明 常用值 data list 查询向量列表 二维数组 anns_field str 向量字段名 创建时定义的字段 param dict 搜索参数 包含metric_type和params limit int 返回结果数 5-100 expr str 过滤条件 price > 50 AND category == 'electronics' output_fields list 返回字段 ["field1", "field2"]
-
-
搜索案例实战(MilvusClient方式)
-
准备数据
from pymilvus import ( connections,MilvusClient, FieldSchema, CollectionSchema, DataType, Collection, utility ) import random # # 创建Milvus客户端 client = MilvusClient( uri="http://47.119.128.20:19530", ) #删除已存在的同名集合 if client.has_collection("book"): client.drop_collection("book") # 定义字段 fields = [ FieldSchema(name="book_id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=200), FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=50), FieldSchema(name="price", dtype=DataType.DOUBLE), FieldSchema(name="book_intro", dtype=DataType.FLOAT_VECTOR, dim=4) ] # 创建集合Schema schema = CollectionSchema( fields=fields, description="Book search collection" ) #创建集合 client.create_collection(collection_name="book", schema=schema) # 生成测试数据 num_books = 1000 categories = ["科幻", "科技", "文学", "历史"] titles = ["量子世界", "AI简史", "时光之轮", "文明起源", "未来简史", "数据科学"] data = [] for i in range(num_books): data.append({ "title": f"{random.choice(titles)}_{i}", "category": random.choice(categories), "price": round(random.uniform(10, 100), 2), "book_intro": [random.random() for _ in range(4)] # 4维向量 }) # 批量插入 insert_result = client.insert( collection_name="book", data=data ) print(f"插入数据量:{len(insert_result['ids'])}")
-
创建索引
# 准备索引参数,为"vector"字段创建索引 index_params = MilvusClient.prepare_index_params() # 添加索引配置,指定字段名、度量类型、索引类型、索引名和参数 index_params.add_index( field_name="book_intro", metric_type="L2", # 距离计算方式 (L2/IP/COSINE) index_type="IVF_FLAT", index_name="vector_index", params={ "nlist": 128 } #聚类中心数 (建议值:sqrt(数据量)) ) # 创建索引,不等待索引创建完成即返回 client.create_index( collection_name="book", index_params=index_params ) print("索引创建完成")
-
执行查询【执行查询前需要加载才可以使用】
client.load_collection(collection_name="book") # 加载集合到内存 # 生成查询向量 query_vector = [random.random() for _ in range(4)] # 执行带过滤条件的向量搜索 results = client.search( collection_name="book", data=[query_vector], # 支持批量查询 filter="category == '科幻' and price < 50", output_fields=["title", "category", "price"], limit=3, search_params={"nprobe": 10} ) # 解析结果 print("\n科幻类且价格<50的搜索结果:") for result in results[0]: # 第一个查询结果集 print(f"ID: {result['book_id']}") print(f"距离: {result['distance']:.4f}") print(f"标题: {result['entity']['title']}") print(f"价格: {result['entity']['price']:.2f}") print("-" * 30)
-
-
向量数据库完整工作流程示意图
1. 创建集合Schema ↓ 2. 插入测试数据 ↓ 3. 创建向量索引 ↓ 4. 加载集合到内存 ↓ 5. 执行混合查询(向量+标量过滤)
-
全量查询案例演示
- 测试是否有 output_fields 字段,返回结果的差异
# 案例1:基础向量查询 basic_res = client.search( collection_name="book", data=[query_vector], limit=5 ) # 案例2:分页查询 page_res = client.search( collection_name="book", data=[query_vector], offset=2, limit=3 ) # 案例3:批量查询 batch_res = client.search( collection_name="book", data=[query_vector, [0.5]*4], # 同时查询两个向量,每个向量都会返回2条 limit=2 )
-
集合状态
# 验证集合状态 print(client.describe_collection("book")) # 索引状态检查 print(client.list_indexes("book"))
-
新旧版本对比表
功能 PyMilvus旧版 MilvusClient新版 连接管理 需要手动管理connections 客户端自动管理 数据插入格式 多列表结构 字典列表 字段定义 使用FieldSchema 在create_collection中直接定义 返回结果格式 对象属性访问 标准化字典格式 错误处理 异常类捕获 统一错误码系统 动态字段支持 需要额外配置 参数开启即可
MMR搜索和LangChain整合Milvus实战
相似度Similarity和MMR最大边界相关搜索
-
搜索的行业应用案例:电商推荐系统(明白两个差异)
-
相似度搜索:
"用户点击商品A → 推荐相似商品B、C"
-
MMR搜索:
"用户浏览历史多样 → 推荐跨品类商品"
-
-
基础相似度搜索(Similarity Search)
- 原理:通过向量空间中的距离计算(余弦相似度/L2距离等)找出最接近目标向量的结果
-
核心特点
- 纯向量驱动:仅依赖向量空间中的几何距离,余弦相似度、L2距离
- 结果同质化:返回最相似的连续区域数据
- 高性能:时间复杂度 O(n + klogk)
-
参数配置模板,方法
vector_store.similarity_search( )
vector_store.as_retriever( search_type="similarity", search_kwargs={ "k": 5, # 返回数量 "score_threshold": 0.65, # 相似度得分的最低要求,相似度≥65%的才考虑 "filter": "category == 'AI'", # 元数据过滤 "param": { # Milvus专属参数 "nprobe": 32, #nprobe是Milvus中用于控制搜索时访问的聚类数量的参数,nprobe越大,搜索越精确但耗时更长。 "radius": 0.8 #是在范围搜索中使用的参数,指定搜索的半径范围,结合score_threshold可用于限定结果的范围 } } )
-
典型应用场景
-
精确语义匹配(专利检索、论文查重)
-
基于内容的推荐("更多类似内容")
-
敏感信息过滤(高阈值精准匹配)
-
-
最大边界相关搜索(MMR Search)
- Maximal Marginal Relevance,最大边际相关性, 是一种信息检索和推荐系统中常用的算法
- 核心目标是 在返回的结果中平衡相关性与多样性,避免返回大量高度相似的内容。
- 设计初衷是解决传统相似性搜索(如余弦相似度)可能导致的“信息冗余”问题,在需要覆盖多角度信息或推荐多样化内容的场景中效果显著
- 原理:在相似度和多样性之间进行权衡,避免结果冗余
-
算法原理图解
初始候选集(fetch_k=20) │ ├── 相似度排序 │ [1, 2, 3, ..., 20] │ └── 多样性选择(λ=0.5) ↓ 最终结果(k=5) [1, 5, 12, 3, 18] # 兼顾相似与差异
-
参数配置模板, 方法
vector_store.max_marginal_relevance_search( )
mmr_retriever = vector_store.as_retriever( search_type="mmr", search_kwargs={ "k": 3, #最终返回的结果数量,MMR 会从更大的候选集中筛选出 3 个最相关且多样化的结果 "fetch_k": 20, #指定 MMR 算法的候选集大小,fetch_k 越大,候选集越广,结果可能越多样,但计算成本更高 "lambda_mult": 0.6, #控制相关性与多样性之间的权衡系数。范围为 [0, 1]:接近1 结果更相似。接近0:结果差异更大 "param": { "nprobe": 64, # 针对Milvus的IVF 类索引,控制搜索的聚类数量,搜索的聚类范围越广,召回率越高,但速度越慢 "ef": 128 # Milvus HNSW索引,控制搜索的深度,值越大搜索越精确,耗时增加;值越小速度更快,可能漏掉相关结果 } } )
-
关键参数解析
参数 相似度搜索 MMR搜索 影响效果 k 控制结果数量 控制最终结果数量 值越大返回越多但可能降低精度 lambda_mult 无 0-1之间 值越大越偏向相关性,值越小越强调多样性 score_threshold 过滤低质量结果 通常不使用 阈值设置需根据embedding模型调整 filter 元数据过滤 支持同左 可结合业务维度进行筛选 -
典型应用场景
- 多样化推荐:电商跨品类推荐
- 知识发现:科研文献探索
- 内容生成:生成多样化文案
-
对比决策矩阵
维度 Similarity Search MMR Search 结果质量 高相似度但可能重复 多样性更佳 响应速度 平均 120ms 平均 200-300ms 内存消耗 低(仅存储topK) 高(需缓存fetch_k) 适用场景 精确匹配、去重 推荐系统、知识发现 可解释性 直观的相似度排序 综合评分需二次解释 -
企业推荐系统架构示例
新版LangChain向量数据库VectorStore设计
-
LangChain 向量存储体系架构
-
RAG系统核心设计模式
Document │ ▼ Text Splitter │ ▼ Embedding Model │ ▼ [Milvus|Chroma|Pinecone...] ←→ VectorStore
-
LangChain设计抽象类
VectorStore
,统一接口,具体的实现由各自数据库负责- 文档(如果过期就忽略) https://python.langchain.com/docs/integrations/vectorstores/
- 安装依赖
pip install langchain-milvus
from langchain_core.vectorstores import VectorStore
-
-
VectorStore 核心方法详解
-
通用方法列表
方法名 作用描述 常用参数示例 from_documents() 从文档创建向量库 documents, embedding, **kwargs add_documents() 追加文档到已有库 documents similarity_search() 相似度查询 query, k=4 similarity_search_with_score() 带相似度得分的查询 query, k=4 max_marginal_relevance_search( ) MMR最大边界搜索 query, k=4 as_retriever() 转换为检索器供链式调用 search_kwargs={} -
初始化方法
@classmethod def from_documents( cls, documents: List[Document], embedding: Embeddings, **kwargs ) -> VectorStore: """ 文档自动转换存储 :param documents: LangChain Document对象列表 :param embedding: 文本向量化模型 :param kwargs: 向量库特有参数 :return: 初始化的VectorStore实例 """
- 和
add_documents()
区别
特性 from_documents()
add_documents()
方法类型 类方法(静态方法) 实例方法 主要用途 初始化集合并批量插入文档 向已存在的集合追加文档 集合创建 自动创建新集合 要求集合已存在 性能消耗 高(需建索引+数据迁移) 低(仅数据插入) 典型场景 首次数据入库 增量数据更新 连接参数 需要完整连接配置 复用已有实例的配置 - 和
-
数据插入方法
def add_texts( self, texts: Iterable[str], metadatas: Optional[List[dict]] = None, **kwargs ) -> List[str]: """ 插入文本数据到向量库 :param texts: 文本内容列表 :param metadatas: 对应的元数据列表 :return: 插入文档的ID列表 """
-
相似性搜索方法
def similarity_search( self, query: str, k: int = 4, filter: Optional[dict] = None, **kwargs ) -> List[Document]: """ 执行相似性搜索 :param query: 查询文本 :param k: 返回结果数量 :param filter: 元数据过滤条件 :return: 匹配的Document列表 """
-
最大边界相关算法(MMR)
def max_marginal_relevance_search( self, query: str, k: int = 4, fetch_k: int = 20, lambda_mult: float = 0.5 ) -> List[Document]: """ 多样性增强搜索 :param k: 返回结果数量 :param fetch_k: 初始获取数量 :param lambda_mult: 多样性权重(0-1) """
-
-
不同向量数据库支持特性不一样
特性 Milvus FAISS Pinecone Chroma 分布式支持 ✓ ✗ ✓ ✗ 元数据过滤 ✓ ✗ ✓ ✓ 自动索引管理 ✓ ✗ ✓ ✓ 本地运行 ✓ ✓ ✗ ✓ 相似度算法 8种 4种 3种 2种 -
场景:知识库冷启动
LangChain整合Milvus新增和删除实战
-
需求
- 使用LangChain整合向量数据库Milvus
- 实现新增和删除向量数据库的数据实战
- 文档(如果过期就忽略)
-
案例实战
-
准备数据
from langchain_community.embeddings import DashScopeEmbeddings #from langchain.vectorstores import Milvus from langchain_milvus import Milvus from langchain_core.documents import Document from uuid import uuid4 # 初始化模型 embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) vector_store = Milvus( embeddings, connection_args={"uri": "http://47.119.128.20:19530"}, collection_name="langchain_example", ) document_1 = Document( page_content="I had chocolate chip pancakes and scrambled eggs for breakfast this morning.", metadata={"source": "tweet"}, ) document_2 = Document( page_content="The weather forecast for tomorrow is cloudy and overcast, with a high of 62 degrees.", metadata={"source": "news"}, ) document_3 = Document( page_content="Building an exciting new project with LangChain - come check it out!", metadata={"source": "tweet"}, ) document_4 = Document( page_content="Robbers broke into the city bank and stole $1 million in cash.", metadata={"source": "news"}, ) document_5 = Document( page_content="Wow! That was an amazing movie. I can't wait to see it again.", metadata={"source": "tweet"}, ) document_6 = Document( page_content="Is the new iPhone worth the price? Read this review to find out.", metadata={"source": "website"}, ) document_7 = Document( page_content="The top 10 soccer players in the world right now.", metadata={"source": "website"}, ) document_8 = Document( page_content="LangGraph is the best framework for building stateful, agentic applications!", metadata={"source": "tweet"}, ) document_9 = Document( page_content="The stock market is down 500 points today due to fears of a recession.", metadata={"source": "news"}, ) document_10 = Document( page_content="I have a bad feeling I am going to get deleted :(", metadata={"source": "tweet"}, ) documents = [ document_1,document_2,document_3,document_4,document_5,document_6, document_7,document_8,document_9,document_10, ]
-
插入
ids = [ str(i+1) for i in range(len(documents))] print(ids) result = vector_store.add_documents(documents=documents, ids=ids) print(result)
-
删除
result = vector_store.delete(ids=["1"]) print(result) #(insert count: 0, delete count: 1, upsert count: 0, timestamp: 456798840753225732, success count: 0, err count: 0
-
LangChain实战MMR和相似性搜索实战
-
需求
- 使用LangChain整合向量数据库Milvus
- 测试相关搜索:相似度搜索和MMR搜索
-
案例实战
-
准备数据【执行多次有多条重复记录,向量数据库不会去重,方便测试MMR】
from langchain_community.embeddings import DashScopeEmbeddings #from langchain.vectorstores import Milvus from langchain_milvus import Milvus from langchain_core.documents import Document # 初始化模型 embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) document_1 = Document( page_content="LangChain支持多种数据库集成,小滴课堂的AI大课", metadata={"source": "xdclass.net/doc1"}, ) document_2 = Document( page_content="Milvus擅长处理向量搜索,老王的课程不错", metadata={"source": "xdclass.net/doc2"}, ) document_3 = Document( page_content="我要去学小滴课堂的架构大课", metadata={"source": "xdclass.net/doc3"}, ) document_4 = Document( page_content="今天天气不错,老王和老帆去按摩了", metadata={"source": "xdclass.net/doc4"}, ) documents = [document_1,document_2,document_3,document_4] vector_store = Milvus.from_documents( documents=documents, embedding=embeddings, collection_name="mmr_test", connection_args={"uri": "http://47.119.128.20:19530"} )
-
相似性搜索(向量数据库插入多个重复数据,看是否会返回一样的)
# 相似性搜索 query = "如何进行数据库集成?" results = vector_store.similarity_search(query, k=2) for doc in results: print(f"内容:{doc.page_content}\n元数据:{doc.metadata}\n") # 混合搜索(结合元数据过滤) results = vector_store.similarity_search( query, k=2, expr='source == "xdclass.net/doc1"' ) print(results)
-
MMR搜索(跨类搭配,向量数据库插入多个重复数据,看是否会返回一样的)
# MMR推荐(跨类搭配) diverse_results = vector_store.max_marginal_relevance_search( query="如何进行数据库集成", k=2, fetch_k=10, lambda_mult=0.4, # expr="category in ['shoes', 'clothes', 'accessories']", search_params={ "metric_type": "IP", "params": {"nprobe": 32} } ) print(diverse_results)
-
Retrievers检索器+RAG文档助手项目实战
LangChain检索器Retrievers案例实战
-
什么是
Retriever
-
统一接口:标准化检索流程,无论数据来源如何,最终输出
Document
对象列表。 -
多源混合检索:支持同时查询向量库、传统数据库和搜索引擎【提高召回率】
-
与VectorStore的关系:Retriever不直接管理存储,依赖VectorStore(如FAISS、Chroma)实现向量化与检索。
-
RAG中的角色:作为检索增强生成(RAG)流程的“数据入口”,为生成模型提供精准上下文
-
有多个实现:VectorStoreRetriever、MultiQueryRetriever、SelfQueryRetriever等
-
特点
- 模块化设计:支持插件式扩展,可自定义检索算法(如混合搜索、重排序)。
- 异步支持:通过
async_get_relevant_documents
实现高并发场景下的高效检索。 - 链式调用:可与LangChain的其他组件(如Text Splitters、Memory)无缝集成。
# from langchain_core.retrievers import BaseRetriever
-
补充知识点:召回率(Recall)信息检索和机器学习中衡量模型找全相关结果能力的核心指标
- 比如
- 在文档检索中,如果有100篇相关文档,系统找出了80篇,那么召回率就是80%。
- 召回率高意味着系统漏掉的少,但可能夹杂了不相关的结果,这时候准确率可能低。
- 比如
-
-
Retriever常见类型之基础检索器
VectorStoreRetriever
- 基础使用参考案例
#将文档嵌入为向量,通过相似度计算(如余弦相似度)检索 from langchain_community.vectorstores import FAISS retriever = FAISS.from_documents(docs, embeddings).as_retriever( search_type="mmr", # 最大边际相关性 search_kwargs={"k": 5, "filter": {"category": "news"}} )
-
as_retriever()
方法介绍-
将向量库实例转换为检索器对象,实现与 LangChain 链式调用(如
RetrievalQA
)的无缝对接。 -
源码
def as_retriever(self, **kwargs: Any) -> VectorStoreRetriever: tags = kwargs.pop("tags", None) or [] + self._get_retriever_tags() return VectorStoreRetriever(vectorstore=self, tags=tags, **kwargs) """ 向量库实例转换为检索器对象,实现与 LangChain 链式调用 """
-
-
关键参数详解
-
search_type 搜索类型
类型 适用场景 Milvus 对应操作 "similarity"
基础相似度检索 search()
"mmr"
多样性结果优化 max_marginal_relevance_search()
"similarity_score_threshold"
阈值过滤检索 search()
+score_threshold
# MMR 检索配置示例 mmr_retriever = vector_store.as_retriever( search_type="mmr", search_kwargs={ "k": 3, "fetch_k": 20, "lambda_mult": 0.5 } )
-
search_kwargs 参数详解
参数 类型 默认值 说明 k
int 4 返回结果数量 filter
/expr
str/dict None 元数据过滤条件 param
dict None Milvus 搜索参数配置
-
-
综合案例实战
-
默认是similarity search
from langchain_community.embeddings import DashScopeEmbeddings from langchain_milvus import Milvus from langchain_core.documents import Document # 初始化模型 embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) document_1 = Document( page_content="LangChain支持多种数据库集成,小滴课堂的AI大课", metadata={"source": "xdclass.net/doc1"}, ) document_2 = Document( page_content="Milvus擅长处理向量搜索,老王的课程不错", metadata={"source": "xdclass.net/doc2"}, ) document_3 = Document( page_content="我要去学小滴课堂的架构大课", metadata={"source": "xdclass.net/doc3"}, ) document_4 = Document( page_content="今天天气不错,老王和老帆去按摩了", metadata={"source": "xdclass.net/doc4"}, ) documents = [document_1,document_2,document_3,document_4] vector_store = Milvus.from_documents( documents=documents, embedding=embeddings, collection_name="retriever_test1", connection_args={"uri": "http://47.119.128.20:19530"} ) #默认是 similarity search retriever = vector_store.as_retriever(search_kwargs={"k": 2}) results = retriever.invoke("如何进行数据库操作") for result in results: print(f"内容 {result.page_content} 元数据 {result.metadata}")
-
可以调整为MMR检索
retriever = vector_store.as_retriever(search_type="mmr",search_kwargs={"k": 2})
-
大厂面试题-如何提升大模型召回率和实战
-
LLM大模型开发高频面试题:如何提升大模型召回率和准确率?
-
需求背景
- 当原始查询不够明确时,或者当文档库中的内容使用不同的术语表达同一概念时
- 单一查询可能无法有效检案到所有相关内容;
- 或者用户的问题可能有不同的表达方式,导致的检索结果不理想,
- 需要从多个角度切入才能找到最相关的文档片段。这种情况下,生成多个变体查询可以提高召回率,确保覆盖更多相关文档。
-
MultiQueryRetriever
-
通过生成多个相关查询来增强检索效果,解决单一查询可能不够全面或存在歧义的问题。
-
原理:
- 查询扩展技术:通过LLM生成N个相关查询(如改写、扩展、翻译),合并结果去重,生成多个变体查询
- 双重增强效果:提升召回率(+25%↑)和准确率(+18%↑)的平衡
-
用法
retriever = MultiQueryRetriever.from_llm( retriever=base_retriever, llm=ChatOpenAI() )
-
典型问题场景
- 术语差异问题:用户提问使用"SSL证书" vs 文档中使用"TLS证书"
- 表述模糊问题:"怎么备份数据库" vs "数据库容灾方案实施步骤"
- 多语言混合:中英文混杂查询(常见于技术文档检索)
- 专业领域知识:医学问诊中的症状不同描述方式
-
-
案例实战
from langchain_community.embeddings import DashScopeEmbeddings #from langchain.vectorstores import Milvus from langchain_milvus import Milvus from langchain_openai import ChatOpenAI from langchain_community.document_loaders import TextLoader from langchain.retrievers.multi_query import MultiQueryRetriever from langchain_text_splitters import RecursiveCharacterTextSplitter import logging # 设置日志记录的基本配置 logging.basicConfig() # 设置多查询检索器的日志记录级别为INFO logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO) # 使用TextLoader加载文本数据 loader = TextLoader("data/qa.txt") # 加载数据到变量中 data = loader.load() # 初始化文本分割器,将文本分割成小块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10) # 执行文本分割 splits = text_splitter.split_documents(data) # 初始化模型 embedding = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) # 初始化向量数据库 vector_store = Milvus.from_documents( documents=splits, embedding=embedding, collection_name="mulit_retriever2", connection_args={"uri": "http://47.119.128.20:19530"} ) # 定义问题 question = "老王不知道为啥抽筋了" # 初始化语言模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 从语言模型中创建多查询检索器 retriever_from_llm = MultiQueryRetriever.from_llm( retriever=vector_store.as_retriever(), llm=llm ) # 使用检索器执行问题检索 results = retriever_from_llm.invoke(question) # 打印检索到的结果数量 len(results) # 遍历并打印每个检索结果的内容和元数据 for result in results: print(f"内容 {result.page_content} 元数据 {result.metadata}") # 以下是未使用的代码片段,已将其注释掉 # vector_store = Milvus( # embeddings, # connection_args={"uri": "http://47.119.128.20:19530"}, # collection_name="mmr_test", # ) # print(vector_store)
RAG综合项目实战-AI文档问答助手
-
需求:在线文档的问答助手,方便查找相关手册和接口API
- 主要功能包括
- 文档加载与切分、向量嵌入生成、向量存储与检索。
- 基于检索增强生成(Retrieval-Augmented Generation, RAG)的问答。
- 技术选型:LangChain框架+Milvus向量数据库
- 主要功能包括
-
实现的功能
-
文档加载与切分
- 使用
WebBaseLoader
从指定URL加载文档。 - 使用
RecursiveCharacterTextSplitter
将加载的文档按照指定的块大小和重叠大小进行切分。
- 使用
-
向量嵌入生成
- 使用
DashScopeEmbeddings
生成文档切片的向量嵌入,模型为text-embedding-v2
,支持最大重试次数为3次。
-
向量存储与检索
-
使用
Milvus
作为向量数据库,创建名为doc_qa_db
的Collection。 -
将生成的向量嵌入存储到Milvus中,并支持相似性检索。
-
-
基于RAG的问答
-
初始化
ChatOpenAI
模型,使用qwen-plus
作为LLM模型。 -
定义
PromptTemplate
,用于构建输入给LLM的提示信息。 -
构建RAG链,结合相似性检索结果和LLM生成回答。
-
- 使用
-
-
编码实战
from langchain_community.document_loaders import WebBaseLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_milvus import Milvus from langchain.schema.runnable import RunnablePassthrough from langchain.prompts import PromptTemplate from langchain_community.embeddings import DashScopeEmbeddings from langchain_openai import ChatOpenAI # 设置Milvus Collection名称。 COLLECTION_NAME = 'doc_qa_db' # 初始化WebBaseLoader加载指定URL的文档。 loader = WebBaseLoader([ 'https://milvus.io/docs/overview.md', 'https://milvus.io/docs/release_notes.md' ]) # 加载文档。 docs = loader.load() # 初始化RecursiveCharacterTextSplitter,用于切分文档。 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=0) # 使用LangChain将输入文档按照chunk_size切分。 all_splits = text_splitter.split_documents(docs) # 初始化DashScopeEmbeddings,设置embedding模型为DashScope的text-embedding-v2。 embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) # 创建connection,为阿里云Milvus的访问域名。 connection_args = {"uri": "http://47.119.128.20:19530"} # 创建Collection。 vector_store = Milvus( embedding_function=embeddings, connection_args=connection_args, collection_name=COLLECTION_NAME, drop_old=True, ).from_documents( all_splits, embedding=embeddings, collection_name=COLLECTION_NAME, connection_args=connection_args, ) # vector_store = Milvus( # embeddings, # connection_args={"uri": "http://47.119.128.20:19530"}, # collection_name=COLLECTION_NAME, # ) # 利用Milvus向量数据库进行相似性检索。 query = "What are the main components of Milvus?" docs = vector_store.similarity_search(query) print(len(docs)) # 初始化ChatOpenAI模型。 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 将上述相似性检索的结果作为retriever,提出问题输入到LLM之后,获取检索增强之后的回答。 retriever = vector_store.as_retriever()
-
编码测试实战
# 定义PromptTemplate,用于构建输入给LLM的prompt。 template = """你是AI文档助手,使用以下上下文来回答最后的问题。 如果你不知道答案,就说你不知道,不要试图编造答案。 最多使用10句话,并尽可能简洁地回答。总是在答案的末尾说“谢谢你的提问!”. {context} 问题: {question} """ rag_prompt = PromptTemplate.from_template(template) # 构建Retrieval-Augmented Generation链。 rag_chain = ( {"context": retriever, "question": RunnablePassthrough()} | rag_prompt | llm ) # 调用rag_chain回答问题。 print(rag_chain.invoke("什么是Milvus."))
-
应用场景
- 教育领域:可用于备课笔记、课程内容总结等场景。
- 企业知识库:帮助企业快速构建基于内部文档的知识问答系统。
- 技术支持:提供技术文档的智能检索与问答服务。
-
扩展方向
- 支持更多类型的文档加载器(如PDF、Word等)。
- 增加多语言支持。
- 优化向量嵌入生成与检索效率
-
大家的疑惑点(下一章讲解)
# 构建Retrieval-Augmented Generation链。 rag_chain = ( {"context": retriever, "question": RunnablePassthrough()} | rag_prompt | llm )
- 在
rag_chain
的定义中,{"context": retriever, "question": RunnablePassthrough()}
创建了一个输入字典。 context
的值来自retriever
,它将使用向量存储检索与问题相关的文档片段。question
键的值通过RunnablePassthrough()
直接传递,用户输入的问题会被透传到后续的处理步骤。- 输入字典随后会被传递给
rag_prompt
,构建最终的提示(prompt)被传递给llm
(语言模型),生成最终的回答 - 总结:
- 用户输入的问题会同时传给
retriever
和RunnablePassthrough()
retriever
完成检索后,会自动把结果赋值给context
。- 检索结果
context
和用户输入question
一并传给提示模板prompt_template
。
- 用户输入的问题会同时传给
- 在
解析和多实现类案例实战
LangChain核心之Runnable接口底层实现
-
什么是
Runnable
接口-
是LangChain框架中所有组件的核心抽象接口,用于封装可执行的逻辑单元(如模型调用、数据处理、API集成等)。
-
通过实现统一的
invoke
、batch
、stream
等方法,支持模块化构建链式任务,允许开发者以声明式编程LCEL串联不同组件from langchain_core.runnables
-
为什么要使用
Runnable
- 统一接口:所有组件(如Prompt模板、模型、解析器)均实现Runnable接口,确保类型兼容和链式调用的无缝衔接
- 灵活组合:通过管道符
|
将多个Runnable串联成链,简化复杂逻辑的编排,类似数据流处理 - 动态配置:支持运行时参数绑定、组件替换和错误恢复机制(如
with_retry()
),提升系统灵活性和鲁棒性 - 异步与性能优化:内置异步方法(如
ainvoke
)和并行处理(如RunnableParallel
),适用于高并发场景
-
-
什么是
RunnableSequence
-
是LangChain中用于构建顺序执行链的核心组件,通过管道符
|
将多个Runnable串联,形成线性执行流程,是Runnable
子类from langchain_core.runnables import RunnableSequence
-
执行 LCEL链调用的方法(invoke/stream/batch),链中的每个组件也调用对应的方法,将输出作为下一个组件的输入
#RunnableSequence.invoke 的源码解读 def invoke( self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any ) -> Output: # invoke all steps in sequence try: for i, step in enumerate(self.steps): # mark each step as a child run config = patch_config( config, callbacks=run_manager.get_child(f"seq:step:{i + 1}") ) with set_config_context(config) as context: if i == 0: input = context.run(step.invoke, input, config, **kwargs) else: input = context.run(step.invoke, input, config)
-
-
从LECL表达式开始理解
chain = prompt | model | output_parser # 通过|直接连接
-
数据流传递
- 每个Runnable的输出作为下一个Runnable的输入,形成单向数据流。
- 例如,若链为
A | B | C
,则执行流程为A的输出 → B的输入 → B的输出 → C的输入
-
统一接口:
- 所有组件(如Prompt模板、模型、输出解析器)均实现
Runnable
接口,确保类型兼容性和链式调用的无缝衔接
- 所有组件(如Prompt模板、模型、输出解析器)均实现
-
延迟执行:
- 链的构建仅定义逻辑关系,实际执行在调用
invoke
或stream
时触发,支持动态参数绑定和运行时配置
- 链的构建仅定义逻辑关系,实际执行在调用
-
底层实现:
-
管道符
|
在Python中被重写为__or__
方法,实际调用RunnableSequence
构造函数, -
将多个Runnable存入内部列表
steps
中, 执行时按顺序遍历列表并调用每个Runnable的invoke
方法
-
-
-
Runnable接口定义了以下核心方法,支持多种执行模式
class Runnable(Generic[Input, Output]): #处理单个输入,返回输出。 def invoke(self, input: Input) -> Output: ... #异步处理单个输入。 async def ainvoke(self, input: Input) -> Output: ... #逐块生成输出,适用于实时响应。 def stream(self, input: Input) -> Iterator[Output]: ... #批量处理输入列表,提升吞吐量。 def batch(self, inputs: List[Input]) -> List[Output]: ...
方法 说明 使用场景 invoke()
同步执行 单次调用 batch()
批量同步执行 处理数据集 stream()
流式输出 实时生成文本 ainvoke()
异步执行 Web服务集成 -
具有多个子类实现
组件 特点 适用场景 RunnableSequence
顺序执行 线性处理流水线 RunnableBranch
条件路由 分支选择逻辑 RunnableParallel
并行执行 多任务独立处理 RunnablePassthrough
数据透传 保留原始输入
RunnablePassthrough介绍和透传参数实战
-
RunnablePassthrough
- 核心功能:用于在链中直接传递输入数据,不进行任何修改,或通过
.assign()
扩展上下文字段
-
应用场景:
- 保留原始输入数据供后续步骤使用。
- 动态添加新字段到上下文中(如结合检索结果与用户问题)
-
基础用法
from langchain_core.runnables import RunnablePassthrough # 直接传递输入 chain = RunnablePassthrough() | model output = chain.invoke("Hello")
-
扩展字段案例
- 案例一
# 使用 assign() 添加新字段 from langchain_core.runnables import RunnablePassthrough # 使用 assign() 方法添加新字段,该方法接收一个关键字参数,其值是一个函数 # 这个函数定义了如何处理输入数据以生成新字段 # 在这个例子中,lambda 函数接收一个输入 x,并返回 x["num"] * 2 的结果 # 这将创建一个新的字段 'processed',其值是输入字段 'num' 的两倍 chain = RunnablePassthrough.assign(processed=lambda x: x["num"] * 2) # 调用 chain 对象的 invoke 方法,传入一个包含 'num' 字段的字典 # 这将执行之前定义的 lambda 函数,并在输入字典的基础上添加 'processed' 字段 # 最后输出处理后的字典 output = chain.invoke({"num": 3}) # 输出 {'num':3, 'processed':6} print(output)
- 案例二(伪代码)
# 构建包含原始问题和处理上下文的链 chain = ( RunnablePassthrough.assign( context=lambda x: retrieve_documents(x["question"]) ) | prompt | llm ) # 输入结构 input_data = {"question": "LangChain是什么?"} response = chain.invoke(input_data)
-
透传参数LLM案例实战
- 用户输入的问题会同时传给
retriever
和RunnablePassthrough()
retriever
完成检索后,会自动把结果赋值给context
。- 检索结果
context
和用户输入question
一并传给提示模板prompt_template
。 - 输出:模型根据检索到的上下文生成答案
from langchain_community.embeddings import DashScopeEmbeddings from langchain_milvus import Milvus from langchain_core.documents import Document from langchain_core.runnables import RunnablePassthrough from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI # 初始化模型 embeddings = DashScopeEmbeddings( model="text-embedding-v2", # 第二代通用模型 max_retries=3, dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08" ) document_1 = Document( page_content="LangChain支持多种数据库集成,小滴课堂的AI大课", metadata={"source": "xdclass.net/doc1"}, ) document_2 = Document( page_content="Milvus擅长处理向量搜索,老王的课程不错", metadata={"source": "xdclass.net/doc2"}, ) documents = [document_1,document_2] vector_store = Milvus.from_documents( documents=documents, embedding=embeddings, collection_name="runnable_test", connection_args={"uri": "http://47.119.128.20:19530"} ) #默认是 similarity search retriever = vector_store.as_retriever(search_kwargs={"k": 2}) prompt = ChatPromptTemplate.from_template("基于上下文回答:{context}\n问题:{question}") #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) chain = { "context": retriever, "question": RunnablePassthrough() # 直接传递用户问题 } | prompt | model result = chain.invoke("LangChain支持数据库吗") print(result)
- 用户输入的问题会同时传给
- 核心功能:用于在链中直接传递输入数据,不进行任何修改,或通过
AI智能推荐实战之RunnableParallel并行链
-
RunnableParallel
介绍- 并行执行多个 Runnable,合并结果为一个字典,键为子链名称,值为对应输出
class RunnableParallel(Runnable[Input, Dict[str, Any]]): """ 并行执行多个Runnable的容器类 输出结果为字典结构:{key1: result1, key2: result2...} """
- 在 LCEL 链上,会将字典隐形转换为
RunnableParallel
multi_retrieval_chain = ( RunnableParallel({ "context1": retriever1, #数据源一 "context2": retriever2, #数据源二 "question": RunnablePassthrough() }) | prompt_template | llm | outputParser ) ======= 自动化转换为下面,写法一样 ======== multi_retrieval_chain = ( { "context1": retriever1, #数据源一 "context2": retriever2, #数据源二 "question": RunnablePassthrough() } | prompt_template | llm | outputParser )
- 特点
特性 说明 示例 并行执行 所有子Runnable同时运行 3个任务耗时2秒(而非累加) 类型安全 强制校验输入输出类型 自动检测字典字段类型 -
API 与用法, 构造函数所有子链接收相同的输入
from langchain_core.runnables import RunnableParallel runnable = RunnableParallel( key1=chain1, key2=chain2 )
-
应用场景:
-
数据并行处理器:同时处理多个数据流
-
结构化数据装配器:构建标准化的输出格式
-
流水线分叉合并器:实现Map-Reduce模式中的Map阶段
-
举例
-
多维度数据分析
analysis_chain = RunnableParallel({ "sentiment": sentiment_analyzer, "keywords": keyword_extractor, "entities": ner_recognizer })
-
多模型对比系统
model_comparison = RunnableParallel({ "gpt4": gpt4_chain, "claude": claude_chain, "gemini": gemini_chain })
-
智能文档处理系统
document_analyzer = RunnableParallel({ "summary": summary_chain, # 摘要生成 "toc": toc_generator, # 目录提取 "stats": RunnableLambda(lambda doc: { "char_count": len(doc), "page_count": doc.count("PAGE_BREAK") + 1 }) }) # 处理200页PDF文本 analysis_result = document_analyzer.invoke(pdf_text)
-
-
-
案例实战
- 场景:并行生成景点与书籍推荐
from langchain_core.runnables import RunnableParallel from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import JsonOutputParser #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) #构建解析器 parser = JsonOutputParser() prompt_attractions = ChatPromptTemplate.from_template("""列出{city}的{num}个景点。返回 JSON 格式: {{ "num": "编号", "city": "城市", "introduce": "景点介绍", }} """) prompt_books = ChatPromptTemplate.from_template("""列出{city}相关的{num}本书返回 JSON 格式: {{ "num": "编号", "city": "城市", "introduce": "书籍介绍", }} """) chain1 = prompt_attractions | model | parser chain2 = prompt_books | model | parser chain = RunnableParallel( attractions = chain1 , books = chain2 ) output = chain.invoke({"city": "南京", "num": 3}) print(output)
RunnableLambda介绍和包装链式函数实战
-
RunnableLambda
-
核心功能
- 将任意 Python 函数转换为 Runnable,将普通的 Python 函数或可调用对象转换为
Runnable
对象,无缝集成到链中 - 把自己需要的功能通过自定义函数 + RunnableLambda的方式包装,可以跟任何外部系统打通,集成到 LCEL 链
class RunnableLambda(Runnable[Input, Output]): """ 将任意Python函数转换为符合Runnable协议的对象 实现自定义逻辑与LangChain生态的无缝集成 """
- 将任意 Python 函数转换为 Runnable,将普通的 Python 函数或可调用对象转换为
-
与普通函数的区别
特性 普通函数 RunnableLambda 可组合性 ❌ 无法直接接入Chain ✅ 支持` 类型校验 ❌ 动态类型 ✅ 静态类型检查 异步支持 ❌ 需手动实现 ✅ 原生支持async/await 批量处理 ❌ 需循环调用 ✅ 自动批量优化 -
适合场景:
- 插入自定义逻辑(如日志记录、数据清洗)
- 转换数据格式(如 JSON 解析)。
-
API 与用法
from langchain_core.runnables import RunnableLambda def log_input(x): print(f"Input: {x}") return x chain = prompt | RunnableLambda(log_input) | model
-
-
案例实战
-
基础文本处理链
from langchain_core.runnables import RunnableLambda text_clean_chain = ( RunnableLambda(lambda x: x.strip()) | RunnableLambda(str.lower) ) result = text_clean_chain.invoke(" Hello123World ") print(result) # 输出 "helloworld"
-
打印中间结果并过滤敏感词(在链中插入自定义处理逻辑)
from langchain_core.runnables import RunnableLambda from langchain_openai import ChatOpenAI def filter_content(text: str) -> str: return text.replace("暴力", "***") #定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) chain = ( RunnableLambda(lambda x: x["user_input"]) | RunnableLambda(filter_content) | model ) result = chain.invoke({"user_input": "暴力内容"}) print(result) # 输出过滤后的结果
-
智能客服路由实战之RunnableBranch条件分支
-
RunnableBranch
-
核心功能:根据条件选择执行不同的子链,类似 if-else 路由
-
API 与用法
from langchain_core.runnables import RunnableBranch #条件函数:接收输入,返回布尔值。 branch = RunnableBranch( (condition1, chain1), (condition2, chain2), default_chain ) """ 参数说明: - Condition: 返回bool的可调用对象 - Runnable: 条件满足时执行的分支 - default: 所有条件不满足时执行的默认分支 技术细节: 1. 条件按声明顺序 2. 第一个满足条件的分支会被执行 3. 无默认分支且所有条件不满足时抛出异常 """
-
适合场景:
-
多任务分类(如区分数学问题与物理问题)
-
错误处理分支(如主链失败时调用备用链)
-
多轮对话路由(根据对话历史选择回复策略)
# 根据对话历史选择回复策略 branch = RunnableBranch( (lambda x: "投诉" in x["history"], complaint_handler), (lambda x: "咨询" in x["history"], inquiry_handler), default_responder )
-
智能路由系统(根据输入类型路由处理方式)
# 定义分类函数 def detect_topic(input_text): if "天气" in input_text: return "weather" elif "新闻" in input_text: return "news" else: return "general" # 构建分支链 branch_chain = RunnableBranch( (lambda x: detect_topic(x["input"]) == "weather", weather_chain), (lambda x: detect_topic(x["input"]) == "news", news_chain), general_chain ) # 执行示例 branch_chain.invoke({"input": "北京今天天气怎么样?"})
-
-
-
案例实战:需要构建一个 智能客服系统,根据用户输入的请求类型自动路由到不同的处理流程:
-
技术问题:路由到技术支持链。
-
账单问题:路由到财务链。
-
默认问题:路由到通用问答链。
-
步骤
-
导入依赖
from langchain_core.runnables import RunnableBranch, RunnableLambda from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser
-
定义模型
#定义模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 )
-
定义子链
# 技术支持链 tech_prompt = ChatPromptTemplate.from_template( "你是一名技术支持专家,请回答以下技术问题:{input}" ) tech_chain = tech_prompt | model | StrOutputParser() # 财务链 billing_prompt = ChatPromptTemplate.from_template( "你是一名财务专员,请处理以下账单问题:{input}" ) billing_chain = billing_prompt | model | StrOutputParser() # 默认通用链 default_prompt = ChatPromptTemplate.from_template( "你是一名客服,请回答以下问题:{input}" ) default_chain = default_prompt | model | StrOutputParser()
-
定义路由条件函数
def is_tech_question(input: dict) -> bool: # 获取 "input" 键对应的值 input_value = input.get("input", "") # 检查是否包含关键词 return "技术" in input_value or "故障" in input_value def is_billing_question(input: dict) -> bool: # 获取 "input" 键对应的值 input_value = input.get("input", "") # 检查是否包含关键词 return "账单" in input_value or "支付" in input_value
-
构建 RunnableBranch
branch = RunnableBranch( (is_tech_question, tech_chain), # 技术问题 → tech_chain (is_billing_question, billing_chain), # 账单问题 → billing_chain default_chain # 默认问题 → default_chain ) full_chain = RunnableLambda(lambda x: {"input": x}) | branch
-
测试案例
# 测试技术问题 tech_response = full_chain.invoke("我的账号登录失败,提示技术故障") print("技术问题响应:", tech_response) # 测试账单问题 billing_response = full_chain.invoke("我的账单金额有误,请核对") print("账单问题响应:", billing_response) # 测试默认问题 default_response = full_chain.invoke("你们公司的地址在哪里?") print("默认问题响应:", default_response) #输出示例 #技术问题响应: 建议您尝试清除浏览器缓存或重置密码。若问题持续,请联系我们的技术支持团队。 #账单问题响应: 已记录您的账单问题,财务部门将在24小时内与您联系核实。 #默认问题响应: 我们的公司地址是北京市海淀区中关村大街1号。
-
关键原理解析
-
条件路由逻辑
RunnableBranch
接收一个由(条件函数, Runnable)
组成的列表。- 按顺序检查条件,第一个满足条件的分支会被执行,若均不满足则执行默认分支
-
输入处理:
- 输入需为字典格式(如
{"input": "问题内容"}
),通过RunnableLambda
包装原始输入为字典
- 输入需为字典格式(如
-
链式组合:
- 每个分支链(如
tech_chain
)独立处理输入,输出结果直接返回给调用方
- 每个分支链(如
-
-
调试技巧:
- 添加日志中间件(通过
RunnableLambda
)记录路由决策过程
def log_decision(input_data): print(f"路由检查输入:{input_data}") return input_data log_chain_branch = RunnableLambda(log_decision) | branch full_chain = RunnableLambda(lambda x: {"input": x}) | log_chain_branch
- 添加日志中间件(通过
-
-
-
总结与最佳实践
- 组合使用:通过
|
串联或嵌套Runnable
类,构建复杂逻辑。 - 性能优化:利用
RunnableParallel
减少 IO 等待时间。 - 调试技巧:使用
RunnableLambda
插入日志或数据检查点。 - 容错设计:结合
RunnableBranch
和 提升健壮性
- 组合使用:通过
进阶LLM之Agent智能体和Tool工具实战
大模型Agent智能体介绍和应用场景
-
什么是智能体Agent
-
是一种具备自主决策能力的AI系统,通过感知环境、分析信息、调用工具、执行动作的闭环过程完成任务
-
智能体 = 大语言模型(LLM) + 工具(Tools) + 记忆(Memory)
-
核心架构
用户输入 → 大模型推理 → 工具选择 → 执行工具 → 结果验证 → 输出响应 ↑ ↓ ↑ 记忆系统 ↔ 工具库 ↔ 知识库
-
类比:一个具备自主决策能力的虚拟助手,能根据目标自主调用工具完成任务
-
与传统LLM的关键区别
维度 常规LLM Agent 交互方式 单轮问答 多轮决策链 能力范围 文本生成 工具调用+环境交互 记忆机制 短期上下文 长期记忆存储 输出形式 自然语言 结构化动作序列 应用场景 内容创作/问答 复杂任务自动化 -
常规大模型和Agent案例场景对比
测试用例 传统模式响应 Agent模式响应 "北京天气" 温度数据 "北京当前晴,12℃,建议穿外套" "明天需要带伞吗" 无法处理 调用天气API分析降水概率 "上周三天气如何" 报错 自动切换历史天气数据库工具
-
-
典型应用场景
-
医疗行业 - 诊断辅助Agent
-
传统系统痛点:
- 基于固定规则的专家系统、无法处理复杂症状组合、知识更新依赖人工维护
-
Agent方案关键能力
- 结合最新医学论文(通过工具实时检索,常规大模型没法获取最新数据)
- 自动生成检查建议清单
- 保留患者完整诊疗历史
medical_agent = AgentExecutor( tools=[ SymptomAnalyzerTool, MedicalLiteratureTool, LabTestRecommenderTool ], memory=PatientHistoryMemory() ) # 交互示例 response = medical_agent.invoke({ "input": "患者女35岁,持续低烧两周,伴有关节痛", "history": "既往有类风湿病史" }) # 输出:建议进行抗核抗体检测+推荐专科医生
-
-
教育行业 - 个性化学习Agent
-
传统在线教育
// 固定学习路径 public class LearningService { public String getNextStep(String userId) { int score = db.getUserScore(userId); if (score < 60) { return "重新学习第三章"; } return "进入第四章"; } }
-
Agent方案关键能力
- 动态调整学习路径(基于实时掌握程度)
- 多模态教学内容推荐(视频/图文/交互实验)
- 自动生成错题分析报告
class TutorAgent: tools = [ KnowledgeGraphTool, ExerciseRecommenderTool, LearningStyleAnalyzerTool ] def guide_student(self, studentQuery): # 动态决策: # 1. 分析学生知识薄弱点 # 2. 根据学习风格推荐资料 # 3. 生成个性化练习计划 return self.agent_executor.invoke(studentQuery)
-
-
-
Agent智能体案例(伪代码)
from langchain.agents import AgentExecutor, create_react_agent from langchain import hub # 定义工具集 tools = [ Tool( name="WeatherCheck", func=get_weather_api_data, description="查询实时天气数据" ), Tool( name="CalendarAccess", func=read_google_calendar, description="访问用户日历信息" ) ] # 构建Agent prompt = hub.pull("hwchase17/react") agent = create_react_agent( llm=ChatOpenAI(temperature=0), tools=tools, prompt=prompt ) # 执行示例 agent_executor = AgentExecutor(agent=agent, tools=tools) result = agent_executor.invoke({ "input": "帮我安排明天北京的户外会议,需要考虑天气情况" }) print(result["output"]) #典型输出示例 思考过程: 1. 需要确定明天北京的天气(调用WeatherCheck) 2. 查询明天下午2点的天气预报 3. 如果天气适宜,查找明天下午的空闲时段(调用CalendarAccess) 4. 综合结果建议会议时间 最终输出:建议将会议安排在明天下午15:00,天气预报显示晴,气温22℃。
大模型痛点和LangChain工具Tool实战
-
需求背景:
-
大模型的短板:虽然大语言模型(LLM)擅长文本生成,但缺乏:
- 实时数据获取能力(如天气/股票)、精确数学计算能力
- 专业领域知识(如法律/医疗)、外部系统对接能力
-
Tool工具就是解决这类问题的,通过Tool机制,好比给大模型插入翅膀
-
-
大模型的Tool工具
-
Tool是LLM与外部世界交互的接口,让大模型能调用外部功能(如API、函数、数据库)
-
核心
- 突破大模型静态知识库限制
- 实时获取外部数据(如天气/股票)
- 执行复杂计算业务逻辑
- 连接现有软件系统(如CRM、各个系统API)
-
工具生命周期
工具定义 → 2. Agent注册 → 3. 自动调用 → 4. 结果处理
-
-
LangChain里面创建工具
- @tool装饰器
- 通过简单的@tool装饰器或StructuredTool即可实现,适用于大多数用例,
- @tool但不能同时有同步和异步的方法,只能单独使用
- LangChain Runnables
- 接受字符串或字典输入的LangChain Runnables使用as_tool方法转换为工具
- 允许为参数指定名称、描述和其他模式信息;
- 继承BaseTool类:
- 通过从BaseTool进行子类化来定义自定义工具,提供了对工具定义的最大控制,但需要编写更多的代码。
- @tool装饰器
-
LangChain里面Tool实战
-
@tool 装饰器:用来定义一个简单的工具函数,, 可以直接给函数加上这个装饰器,让函数成为可调用的工具
- 简单定义, 需要加上文档字符串注释描述,AI才知道工具的用途
from langchain_core.tools import tool @tool def multiply(a: int, b: int) -> int: """把传递的两个参数相乘""" return a * b print("工具名称:", multiply.name) print("工具描述:", multiply.description) print("工具参数:", multiply.args) print("工具返回值:", multiply.return_direct) print("工具详细的schema:",multiply.args_schema.model_json_schema()) print(multiply.invoke({"a": 2, "b": 3})) #定义了一个 `multiply` 工具,用于两个数字相乘,并在调用时显示该工具的名称、描述和参数列表。
- 配置参数
from pydantic import BaseModel, Field from langchain_core.tools import tool class CalculatorInput(BaseModel): a: int = Field(description="第一个参数") b: int = Field(description="第二个参数") @tool("multiplication-tool", args_schema=CalculatorInput, return_direct=True) def multiply(a: int, b: int) -> int: """Multiply two numbers.""" return a * b # Let's inspect some of the attributes associated with the tool. print("工具名称:", multiply.name) print("工具描述:", multiply.description) print("工具参数:", multiply.args) print("工具返回值:", multiply.return_direct) print("工具详细的schema:",multiply.args_schema.model_json_schema())
-
核心组件
组件 作用 示例 名称(name) 工具唯一标识符,代理通过名称匹配调用工具 wikipedia
、google_search
描述(description) 工具功能的自然语言描述,代理根据描述决定是否调用工具 "查询维基百科内容" 输入参数(args_schema) 定义工具的参数格式(Pydantic模型),用于参数校验与提示生成 query: str
执行函数(func) 实际执行操作的函数(如调用API、运行Shell命令) def run(query: str): ...
返回模式(return_direct) 若为 True
,代理直接返回工具结果,不再生成额外文本适用于无需进一步推理的简单任务
-
-
StructuredTool
介绍- 是LangChain中用于定义结构化参数工具的基类,相比普通
@tool
装饰器,它支持:- 严格的参数模式定义(基于Pydantic模型)
- 多参数输入校验
- 自动生成工具调用示例
- 适用场景:需要多个输入参数或复杂参数类型的工具
特性 普通@tool装饰器 StructuredTool 参数定义 简单参数(单个字符串或字典) 基于Pydantic模型的严格参数模式 参数校验 弱校验(依赖代码逻辑) 强校验(自动类型检查和格式验证) 多参数支持 需手动解析字典参数 直接映射多个命名参数 使用复杂度 快速定义简单工具 适合复杂业务逻辑的工具 -
案例实战
from pydantic import BaseModel, Field from langchain_core.tools import StructuredTool # 定义输入参数的数据结构 class CalculatorInput(BaseModel): a: int = Field(description="第一个数字") b: int = Field(description="第二个数字") # 定义计算函数 def multiply(a: int, b: int) -> int: """Multiply two numbers.""" return a * b # 封装工具 calculator = StructuredTool.from_function( func=multiply, name="Calculator", description="用于计算两个数字的乘积", args_schema=CalculatorInput, return_direct=True, ) print("工具名称:", calculator.name) print("工具描述:", calculator.description) print("工具参数:", calculator.args) print("工具返回值:", calculator.return_direct) print("工具详细的schema:",calculator.args_schema.model_json_schema()) # 调用工具 print("工具调用结果:", calculator.invoke({"a": 2, "b": 3}))
- 是LangChain中用于定义结构化参数工具的基类,相比普通
-
使用继承
BaseTool
子类进行创建工具from pydantic import BaseModel, Field from typing import Type from langchain_core.tools import BaseTool from pydantic import BaseModel class CalculatorInput(BaseModel): a: int = Field(description="第一个参数") b: int = Field(description="第二个参数") class CustomCalculatorTool(BaseTool): name: str = "Calculator" description: str = "当你需要计算数学问题时候使用" args_schema: Type[BaseModel] = CalculatorInput return_direct: bool = True def _run( self, a: int, b: int ) -> str: """使用工具.""" return a * b calculator = CustomCalculatorTool() print("工具名称:", calculator.name) print("工具描述:", calculator.description) print("工具参数:", calculator.args) print("工具返回值:", calculator.return_direct) print("工具详细的schema:",calculator.args_schema.model_json_schema()) print(calculator.invoke({"a": 2, "b": 3}))
LLM大模型绑定工具Tool案例实战
-
需求
- 定义了工具,需要把工具绑定给大模型, 大模型会在合适的时候,选择对应的工具函数
- 解决痛点:如天气查询/股票数据/订单处理等需要实时数据的场景
- 类比理解:给大模型装"手和脚",像钢铁侠的AI助手贾维斯可操作战甲
- 注意
- 虽然“工具调用”这个名称暗示模型直接执行某些操作,但实际上并非如此!
- 模型仅生成工具的参数,实际运行工具(或不运行)取决于用户的需求
要素 | 作用 | 示例 |
---|---|---|
工具描述 | 告诉模型工具的功能和使用方法 | 天气查询API的输入输出说明 |
参数解析 | 从自然语言中提取结构化参数 | 提取"北京今天温度"中的城市和日期 |
执行反馈 | 将工具返回结果重新组织成自然语言 | 把JSON天气数据转为口语化描述 |
-
技术实现流程
用户输入 → 大模型分析意图 → 选择工具 → 提取参数 → 调用工具 → 结果格式化 → 最终回复
-
大模型绑定工具api
-
支持工具调用功能的聊天模型, 直接使用
.bind_tools( )
方法,传入工具列表即可def bind_tools( self, tools: Sequence[Union[Dict[str, Any], Type, Callable, BaseTool]], *, tool_choice: Optional[ Union[dict, str, Literal["auto", "none", "required", "any"], bool] ] = None, strict: Optional[bool] = None, parallel_tool_calls: Optional[bool] = None, **kwargs: Any, ) -> Runnable[LanguageModelInput, BaseMessage]:
-
注意:不是全部大模型都是支持绑定工具列表,
-
大模型绑定工具的伪代码参考
tools = [add, multiply] from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini") llm_with_tools = llm.bind_tools(tools) query = "What is 3 * 12?" resp = llm_with_tools.invoke(query)
-
-
工具调用
-
第一步
-
大模型如果需要调用工具,则生成响应里面包括了工具调用信息,本身的content内容为空
-
大模型响应的消息 或消息块 作为工具调用对象的列表,位于
.tool_calls
属性中。 -
聊天模型可以同时调用多个工具, 包含 工具名称、参数值字典和(可选)标识符的类型字典。
-
没有工具调用的消息 默认将此属性设置为空列表
# 使用绑定工具的模型处理用户查询 ai_msg = llm_with_tools.invoke(messages) # 打印ai_msg对象的tool_calls属性,显示AI消息中包含的工具调用信息 print(ai_msg.tool_calls) {'tool_calls': [ { 'id': 'call_ea723d86cf804a088b946a', 'function': {'arguments': '{"a": 3, "b": 12}', 'name': 'multiply'}, 'type': 'function', 'index': 0} ] }
-
-
第二步
-
提取大模型响应信息里面的选择的工具,代码编写 选择对应的工具进行执行
# 遍历AI消息中的工具调用 for tool_call in ai_msg.tool_calls: # 根据工具调用的名称选择相应的工具函数 selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()] print(f"selected_tool:{selected_tool}") # 调用选中的工具函数并获取结果 tool_msg = selected_tool.invoke(tool_call) print(f"tool_msg:{tool_msg}") # 将工具调用的结果添加到messages列表中 messages.append(tool_msg)
-
invoke()
执行后,会生成执行结果对象ToolMessage
, 包括与模型生成的原始工具调用中的id
匹配的tool_call_id
ToolMessage(content='36', name='multiply', tool_call_id='call_1319a58494c54998842092')]
-
-
第三步
-
将工具调用的结果添加到消息messages列表中,再传递给大模型,大模型会重新进行执行,组织对应的语言返回
# 再次使用绑定工具的模型处理更新后的messages列表 reslut = llm_with_tools.invoke(messages) # 打印最终结果 print(f"最终结果:{reslut}") 最终结果: content='3 乘以 12 的结果是 36。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 272, 'total_tokens': 288, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'qwen-plus', 'system_fingerprint': None, 'id': 'chatcmpl-b499cd0b-29d4-9d83-8473-e41d7214223d', 'finish_reason': 'stop', 'logprobs': None} id='run-8046aa25-091a-4df3-a49a-aa36811c8d44-0' usage_metadata={'input_tokens': 272, 'output_tokens': 16, 'total_tokens': 288, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}
-
-
-
完整案例实战
from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage # 定义一个加法工具函数 @tool def add(a: int, b: int) -> int: """Adds a and b.""" return a + b # 定义一个乘法工具函数 @tool def multiply(a: int, b: int) -> int: """Multiplies a and b.""" return a * b # 定义模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 将前面定义的工具函数整合到一个列表中 tools = [add, multiply] # 将工具函数绑定到语言模型 llm_with_tools = llm.bind_tools(tools) # 定义用户查询 query = "3 * 12 结果是多少" # 创建一个包含用户查询的messages列表 messages = [HumanMessage(query)] # 使用绑定工具的模型处理用户查询 ai_msg = llm_with_tools.invoke(messages) # 打印ai_msg对象,以便用户可以看到AI的消息响应 print(ai_msg) # 打印ai_msg对象的tool_calls属性,显示AI消息中包含的工具调用信息 print(ai_msg.tool_calls) # 将AI的消息添加到messages列表中 messages.append(ai_msg) # 遍历AI消息中的工具调用 for tool_call in ai_msg.tool_calls: # 根据工具调用的名称选择相应的工具函数 selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()] print(f"selected_tool:{selected_tool}") # 调用选中的工具函数并获取结果 tool_msg = selected_tool.invoke(tool_call) print(f"tool_msg:{tool_msg}") # 将工具调用的结果添加到messages列表中 messages.append(tool_msg) # 打印更新后的messages列表 print(f"打印更新后的messages列表:{messages}") # 再次使用绑定工具的模型处理更新后的messages列表 reslut = llm_with_tools.invoke(messages) # 打印最终结果 print(res
LangChain内置工具包和联网搜索实战
-
LangChain工具包
-
方便开发者快速使用各种主流工具,LangChain官方加入了很多内置工具,开箱即用
-
所有工具都是 BaseTool 的子类,且工具是 Runnable可运行组件,支持 invoke、stream 等方法进行调用
-
也可以通过 name、 description、args、 returu_direct 等属性来获取到工具的相关信息
-
如果内置工具包不满足,即可以自定义工具
-
地址(失效忽略即可):https://python.langchain.com/docs/integrations/tools/
-
-
如何使用内置工具包【联网搜索例子】
-
选择对应的工具->安装依赖包->编写代码实战
-
案例实战
-
搜索工具:选择 SearchApi,注册时100次免费搜索,注册账号获取 APIKEY
-
编写代码
# 导入操作系统接口模块,用于与环境变量交互 import os # 从langchain_community.utilities模块中导入SearchApiAPIWrapper类,用于封装搜索API from langchain_community.utilities import SearchApiAPIWrapper # 设置环境变量SEARCHAPI_API_KEY,用于认证搜索API的密钥 os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML" # 实例化SearchApiAPIWrapper对象,用于调用搜索API search = SearchApiAPIWrapper() # 调用run方法执行搜索操作,参数为查询腾讯股价的中文字符串 result = search.run("今天腾讯的股价是多少") # 输出搜索结果 print(result)
-
拓展
-
SearchApi 包装器可以自定义为使用不同的引擎,如 Google News、Google Jobs、Google Scholar
-
其他可以在 SearchApi 文档中找到的引擎。执行查询时可以传递 SearchApi 支持的所有参数。
search = SearchApiAPIWrapper(engine="google_jobs")
-
获取元数据
result_meta = search.results("今天腾讯的股价是多少") print(result_meta)
-
-
-
兜底降级-LangChain调用工具Tool异常处理
-
需求背景
- 智能体(Agent)在调用外部工具(如 API、数据库、搜索引擎)时,会遇到各种不可预知的错误
- 例如:
- 网络请求失败(如 API 无响应)
- 权限不足(如访问密钥失效)
- 输入参数不合法(如格式错误)
- 资源限制(如 API 调用次数超限)
- 如果智能体不处理这些错误,会导致:
- 程序崩溃:直接抛出未捕获的异常。
- 用户困惑:返回难以理解的错误堆栈信息。
- 无法恢复:智能体无法根据错误调整策略(如重试或切换工具)
-
解决方案
ToolException
:- 通过 ToolException 统一捕获和处理工具调用中的错误,使智能体具备容错能力和用户友好的错误反馈
- ToolException 的核心作用
- 统一错误格式:将不同工具的异常转换为标准格式,方便智能体解析。
- 错误上下文传递:保留错误原因、工具名称等关键信息。
- 使用场景举例
- API 调用失败:如天气查询接口超时。
- 权限校验失败:如访问数据库的密钥过期。
- 输入参数校验:如用户输入的城市名不存在。
- 资源限制:如每日调用次数用尽。
-
案例实战
-
方式一:配置响应
handle_tool_error=True
默认是falsefrom langchain_core.tools import tool, ToolException, StructuredTool def search(query: str) -> str: """ 执行搜索查询 """ # 引发一个ToolException来模拟搜索结果为空的情况 raise ToolException(f"相关搜索结果为空:{query}") # 使用StructuredTool从函数创建一个工具实例 # handle_tool_error参数设置为True,表示工具将处理内部异常 search_tool = StructuredTool.from_function( func=search, name="search", description="搜索工具", handle_tool_error=True ) # 调用search_tool的invoke方法来执行搜索工具, 传递一个包含查询参数的字典 resp = search_tool.invoke({"query": "腾讯的股价多少"}) # 打印搜索工具的响应结果 print(resp)
-
方式二:配置响应
handle_tool_error=”错误信息“
from langchain_core.tools import tool, ToolException, StructuredTool def search(query: str) -> str: """ 执行搜索查询 """ # 引发一个ToolException来模拟搜索结果为空的情况 raise ToolException(f"相关搜索结果为空:{query}") # 使用StructuredTool从函数创建一个工具实例 # handle_tool_error参数设置为True,表示工具将处理内部异常 search_tool = StructuredTool.from_function( func=search, name="search", description="搜索工具", handle_tool_error="搜索结果失败,请重试" ) # 调用search_tool的invoke方法来执行搜索工具,传递一个包含查询参数的字典 resp = search_tool.invoke({"query": "腾讯的股价多少"}) # 打印搜索工具的响应结果 print(resp)
-
方式三:配置自定义处理函数-兜底降级
from langchain_core.tools import tool, ToolException, StructuredTool def search(query: str) -> str: """ 执行搜索查询 """ # 引发一个ToolException来模拟搜索结果为空的情况 raise ToolException(f"相关搜索结果为空:{query}") #自定义异常处理函数 def _handle_tool_error(error: ToolException) -> str: """ 自定义异常处理函数 """ return f"搜索结果失败,自定义异常,请重试:{error}" # 使用StructuredTool从函数创建一个工具实例 # handle_tool_error参数设置为True,表示工具将处理内部异常 search_tool = StructuredTool.from_function( func=search, name="search", description="搜索工具", handle_tool_error=_handle_tool_error ) # 调用search_tool的invoke方法来执行搜索工具,传递一个包含查询参数的字典 resp = search_tool.invoke({"query": "腾讯的股价多少"}) # 打印搜索工具的响应结果 print(resp)
-
插上翅膀-LLM增加联网搜索功能实
-
需求说明
- 实现了一个结合大语言模型(LLM)和工具调用的智能问答系统。
- 用户可以通过自然语言输入问题,系统会根据问题内容判断是否需要调用外部工具(如搜索引擎或计算工具),返回最终答案。
-
交互流程图
-
步骤思路
- 配置SearchAPI的API密钥,
- 实例化
SearchApiAPIWrapper
对象,用于调用搜索API。
- 实例化
- 定义多个工具函数,供系统在回答问题时调用。
- web_search: 用于搜索实时信息、最新事件或未知领域知识。
- 输入参数:
query
(字符串类型,搜索关键词)。 - 返回值:搜索结果的标题和摘要。
- 输入参数:
- multiply: 用于计算两个整数的乘积。
- 输入参数:
a
和b
(均为整数类型)。 - 返回值:两数相乘的结果。
- 输入参数:
- web_search: 用于搜索实时信息、最新事件或未知领域知识。
- 初始化大语言模型(LLM),并将其与工具绑定,形成一个智能问答Agent。
- 关键点
- 使用
ChatOpenAI
类初始化LLM,指定模型名称、API密钥、温度等参数。 - 创建聊天提示模板(
ChatPromptTemplate
),定义系统角色和用户输入格式。 - 将工具列表与LLM绑定,具备工具调用能力的Agent。
- 使用
- 关键点
- 构建运行链(
chain
),将用户输入传递给提示模板和Agent,生成响应。- 关键点
- 用户输入通过
RunnablePassthrough
传递到提示模板。 - 提示模板生成的消息进一步传递给Agent处理。
- 用户输入通过
- 关键点
- 根据据LLM生成的响应,判断是否要调用工具。如果需要调用工具并将结果合并到历史消息中,重新传递给LLM生成最终答案。
- 关键点
- 通过
resp.tool_calls
判断是否需要调用工具。 - 调用工具后,将结果以
ToolMessage
形式添加到历史消息中。 - 最终结果由LLM根据更新的历史消息生成。
- 通过
- 关键点
- 配置SearchAPI的API密钥,
-
案例代码实战
# 基于LangChain 0.3.x的SearchApi工具实战(需安装依赖) # pip install langchain-core langchain-openai langchain-community from langchain_community.utilities import SearchApiAPIWrapper from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain_core.messages import ToolMessage from langchain_core.runnables import RunnablePassthrough from langchain_core.prompts import ChatPromptTemplate import os from langchain_core.tools import tool from pydantic import Field # ====================== # 第一步:配置搜索工具 # ====================== # 注册SearchAPI获取密钥:https://www.searchapi.io/ # 设置SearchAPI的API密钥 os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML" # 实例化SearchApiAPIWrapper对象,用于调用搜索API search = SearchApiAPIWrapper() # ====================== # 第二步:定义搜索工具 # ====================== @tool("web_search", return_direct=True) def web_search(query: str) -> str: """ 当需要获取实时信息、最新事件或未知领域知识时使用,输入应为搜索关键词 """ try: results = search.results(query) # 获取前3条结果 return "\n\n".join([ f"来源:{res['title']}\n内容:{res['snippet']}" for res in results['organic_results'] ]) except Exception as e: return f"搜索失败:{str(e)}" @tool("multiply") def multiply(a: int, b: int) -> int: """ 把传递的两个参数相乘 """ return a * b # ====================== # 第三步:绑定LLM创建Agent # ====================== # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 创建聊天提示模板 prompt = ChatPromptTemplate.from_messages( [ ("system", "你是一个AI助手,名称叫老王,请根据用户输入的查询问题,必要时可以调用工具帮用户解答"), ("human", "{query}"), ] ) # 定义工具字典 tool_dict = { "web_search": web_search, "multiply": multiply } # 从字典中提取工具列表 tools = [ tool_dict[tool_name] for tool_name in tool_dict ] # 绑定工具到大模型 llm_with_tools = llm.bind_tools(tools=tools) # 创建运行链 chain = {"query":RunnablePassthrough()} | prompt | llm_with_tools # 定义查询 query = "你是谁?" # 执行链并获取响应 resp = chain.invoke({"query":query}) print(resp) #判断是否需要调用工具 content=''不一定需要调用,根据tool_calls进行判断
-
编码实战
#判断是否需要调用工具 content=''不一定需要调用,根据tool_calls进行判断 # ====================== # 第四步:获取工具调用 # ====================== tool_calls = resp.tool_calls if len(tool_calls) <= 0: print(f"不需要调用工具:{resp.content}") else: #将历史消息合并,包括用户输入和AI输出 history_messages = prompt.invoke(query).to_messages() history_messages.append(resp) print(f"历史消息:{history_messages}") #循环调用工具 for tool_call in tool_calls: tool_name = tool_call.get("name") tool_args = tool_call.get("args") tool_resp = tool_dict[tool_name].invoke(tool_args) print(f"一次调用工具:{tool_name},参数:{tool_args},结果:{tool_resp}") #将工具调用结果添加到历史消息中 history_messages.append( ToolMessage( tool_call_id=tool_call.get("id"), name=tool_name, content=tool_resp ) ) print(f"历史消息:{history_messages}") resp = llm_with_tools.invoke(history_messages) print(f"最终结果:{resp}") print(f"调用工具后的结果:{resp.content}")
-
案例测试实战
#query = "9*3是多少" #query = "今天是腾讯股价是多少"
-
扩展功能建议
- 多语言支持:增加对多种语言的输入和输出支持。
- 更多工具集成:集成更多类型的工具,如翻译工具、图像识别工具等。
- 用户反馈机制:允许用户对系统生成的答案进行评价,优化模型性能。
大模型链路调试平台之LangSmith实战
大模型LLM调用链路分析和LangSmith介绍
-
需求背景
- 开发基于大语言模型(LLM)的智能体时,会遇到以下问题:
- 调试困难
- LLM 的输出不可预测,难以追踪中间步骤(如思维链、工具调用)。
- 错误定位耗时(如工具返回异常,但不知道具体哪一步出错)。
- 测试复杂
- 需要验证不同输入场景下的输出稳定性。
- 手动测试效率低,缺乏自动化验证。
- 监控缺失
- 生产环境中的智能体行为难以追踪(如 API 调用延迟、错误率)。
- 无法分析用户高频问题或模型性能瓶颈。
-
LangSmith 是什么
-
LangChain官方出品的LLM应用开发调试与监控平台
-
解决大模型应用开发中的调试困难、效果追踪、生产监控三大痛点
-
类似:Java中的Spring Boot Actuator(监控)、ELK Stack(日志分析)
-
核心功能说明
- 调试与追踪
- 记录智能体完整执行链路(LLM 调用、工具调用、中间结果)。
- 可视化展示每一步的输入输出和耗时。
- 统计成功率、延迟等关键指标。
- 生产监控
- 实时监控 API 调用异常(如超时、错误响应)。
- 分析用户高频请求和模型性能趋势
- 调试与追踪
-
功能矩阵
功能模块 作用描述 典型应用场景 运行追踪 记录LLM调用链的完整执行过程 调试复杂Chain/Agent逻辑 提示工程分析 对比不同Prompt模板的实际效果 优化系统提示词 版本对比 比对不同模型版本或参数的表现 模型升级效果验证 生产监控 实时监控API调用指标(延迟、成本、错误率) 服务健康状态跟踪 团队协作 共享调试结果和监控仪表盘 多人协作开发LLM应用
-
-
LangSmith 系统架构
- SDK捕获所有LLM相关操作
- 加密传输到LangSmith服务端
- 数据存储与索引
- 可视化界面动态查询
[Your Application] │ ▼ [LangChain SDK] → [LangSmith API] │ │ ▼ ▼ [LLM Providers] [Trace Storage] │ │ ▼ ▼ [External Tools] [Analytics Engine]
-
相关环境实战
-
配置项目前准备
-
项目安装依赖
pip install langsmith==0.3.19
-
生成密钥,记得保存
-
配置项目名称(不存在的话会自动新建)
-
大模型调用接入LangSmith分析实战
简介: 大模型调用接入LangSmith分析实战
-
LangChain接入调用链路分析实战
-
项目配置
#示例配置 import os os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "ls_xxxxx" # 替换为实际Key os.environ["LANGCHAIN_PROJECT"] = "My_Project" # 自定义项目名 os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com" #后端接口路径
#真正配置 import os os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "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" os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com" os.environ["LANGSMITH_PROJECT"] = "agent_v1"
-
如果需要本地私有化部署 langfuse
-
-
案例实战分析
-
单独案例测试
import os os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "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" os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com" os.environ["LANGSMITH_PROJECT"] = "xdclass_test_v1" from langchain_openai import ChatOpenAI import logging logging.basicConfig(level=logging.DEBUG) # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) resp = llm.invoke("什么是智能体") print(resp.content)
-
进阶案例测试【选择之前的案例代码-43.2】
-
模型Agent智能体进阶开发实战
大模型智能体之CoT思维链和ReAct推理行动
-
需求背景:为什么需要CoT/ReAct?
-
问题场景:传统大模型直接输出结果,但复杂任务(数学推理/多步骤决策)易出错
-
核心需求:让模型展示思考过程 + 动态调整策略
-
类比理解:
-
普通模型 → 考试直接写答案
-
CoT → 在试卷上写解题步骤
-
ReAct → 边查公式手册边解题
-
-
-
什么是CoT(思维链,Chain of Thought)
-
核心机制:通过显式生成推理步骤得出最终答案
""" 问题:...[输入问题]... 思考步骤: 1. 第一步分析(如提取关键数据) 2. 第二步计算(如公式应用) 3. ...(更多步骤) 最终答案:...[结论]... """ 比如问题:A超市苹果每斤5元,买3斤送1斤;B超市同品质苹果每斤6元但买二送一。买8斤哪家更划算? CoT推理: 1. A超市实际获得:3+1=4斤,花费3×5=15元 → 单价15/4=3.75元/斤 2. 买8斤需要购买两次活动:4×2=8斤 → 总价15×2=30元 3. B超市每3斤花费6×2=12元 → 单价12/3=4元/斤 4. 买8斤需购买3次活动(3×3=9斤)花费12×3=36元 → 实际单价36/9=4元/斤 5. 结论:A超市更优惠
-
是一种促使大语言模型产生一系列中间推理步骤,进而得出最终答案的技术。
-
以往的大模型通常直接给出问题的答案,而 CoT 鼓励模型展示其思考过程,就像人类在解决复杂问题时逐步推导一样。
-
这种方法能增强模型在处理复杂推理任务时的性能,尤其是涉及算术、常识推理和符号操作的任务。
-
局限性
- 依赖模型知识:若模型内部知识错误,推理链条也可能错误(如“太阳绕地球转”)。
- 不适用于动态信息:无法处理需要实时数据的问题(如“今天天气如何”)。
- 步骤冗余:简单问题可能因分步反而降低效率。
-
案例说明:算术问题
- 问题:小明有 5 个苹果,小红给他 3 个,然后他吃了 2 个,还剩下多少个苹果?
- 无 CoT 回答:“6 个”。
- CoT 回答:
- 首先,小明原本有 5 个苹果,小红又给了他 3 个,那么此时他拥有的苹果数为 5 + 3 = 8 个。
- 接着,他吃了 2 个苹果,所以剩下的苹果数是 8 - 2 = 6 个。
- 因此,小明最后还剩下 6 个苹果。
-
应用场景
- 教育领域
- 用于辅导学生学习数学、科学等学科的推理问题。
- 通过展示推理过程,学生能更好地理解解题思路,提高学习效果。
- 智能客服
- 在回答客户复杂问题时,展示推理过程能让客户更清楚解决方案的由来,增强客户对服务的信任。
- 教育领域
-
-
什么是ReAct(推理与行动,Reasoning and Acting)
-
交互逻辑:循环执行"思考-行动-观察"步骤
""" 问题:...[输入问题]... 思考:当前需要解决的问题是... 行动:调用__工具名称__(参数) 观察:工具返回结果... 思考:根据结果分析... (循环直至得出结论) 答案:...[最终结果]... """ 比如问题:2027年诺贝尔文学奖得主的代表作是什么? ReAct流程: [思考] 需要先确定最新获奖者 → 调用搜索工具 [行动] 使用DuckDuckGo搜索"2027诺贝尔文学奖获得者" [观察] 结果显示:法国作家安妮·艾诺 [思考] 需要确认其代表作品 → 调用维基百科工具 [行动] 查询维基百科"安妮·艾诺" [观察] 主要作品:《悠悠岁月》《位置》等 [结论] 代表作是《悠悠岁月》
-
ReAct 是一种让大模型结合推理和行动的范式,不是前端框架React
-
模型不仅要进行推理,还能根据推理结果调用外部工具(如搜索引擎、数据库等)来获取额外信息,从而更有效地解决问题。
-
它使模型能够与外部环境交互,拓展了模型的能力边界。
-
ReAct的优势
- 减少幻觉:通过实时调用外部工具验证信息,降低模型编造错误答案的概率。
- 处理复杂任务:适合多步骤问题(如数学计算、事实核查),传统单次生成容易出错。
- 透明可解释:模型的推理过程以自然语言呈现,便于人类理解其决策逻辑。
-
案例说明:
- 问题:2027 年 NBA 总冠军是哪个队伍?
- 无 ReAct 处理:由于模型训练数据可能存在时效性问题,可能无法给出准确答案或给出过时的信息。
- ReAct 处理:
- 推理:模型意识到自己可能没有最新的 2026 年 NBA 总冠军信息,需要调用外部资源获取。
- 行动:调用体育新闻网站的 API 或搜索引擎查询相关信息。
- 结果:获取到最新信息后,给出准确回答,如 “2026年 NBA 总冠军是 [具体队伍名称]。
-
应用场景
-
知识问答系统
- 处理需要实时信息或特定领域最新数据的问题,如财经数据、体育赛事结果、科技动态等。
-
任务自动化
- 在自动化流程中,根据任务需求调用不同的工具和服务,如预订机票、查询物流信息、控制智能家居设备等。
-
-
-
技术框架对比
维度 CoT ReAct 核心组件 纯提示工程,依赖模型知识 代理(Agent)+ 工具调用 数据依赖 仅依赖内部知识 可接入外部数据源/API 错误修复 单次推理完成 可通过多次行动修正结果 适用场景 理论推导/数学计算 实时信息查询/复杂任务分解 计算开销 低 中高(涉及外部调用) LangChain组件 ChatPromptTemplate
+LLMChain
Agent
+Tools
+AgentExecutor
优势 简单、透明 实时交互,扩展性强 缺点 无法获取最新信息 依赖工具API的稳定性 -
应用场景指南
场景类型 推荐方法 原因说明 数学/逻辑推理 CoT 无需外部数据,单步推理高效 实时数据获取 ReAct 需要调用搜索/API工具 多步骤决策任务 ReAct 支持动态调整执行路径 知识密集型问答 CoT 依赖模型内部知识库 复杂问题分解执行 混合架构 兼顾推理与执行效率 -
开发建议:
- 方法选择原则:
- 纯推理 → CoT
- 需实时数据 → ReAct
- 提示工程技巧:
- CoT需明确定义步骤格式
- ReAct要严格规范行动命名(如
调用API名称_参数
)
- 常见陷阱:
- CoT可能产生错误中间步骤
- ReAct需处理API调用失败情况
- 通过
verbose=True
参数观察两种方法的执行过程差异 - LangChain框架的模块化设计,可以灵活组合这两种技术,适应不同场景需求
- 方法选择原则:
大模型的Zero-Shot和Few-Shot案例实战
简介: 大模型的Zero-Shot和Few-Shot案例讲解
-
零样本(Zero-Shot)学习
- 模型在没有特定任务训练数据的情况下,直接通过预训练知识和自然语言理解能力完成任务。
- 例如,直接要求模型生成从未见过的任务结果(如翻译、分类)。
- 核心原理:依赖大模型在预训练阶段学习到的通用知识泛化能力
- 示例
- 用户输入:“将这句话翻译成中文:Hello, how are you?”
- 模型输出:“你好”
-
少量样本(Few-Shot)学习【照猫画虎】
-
模型通过少量示例(通常3-5个) 快速理解任务格式和需求,提升任务表现。
-
例如,提供几个问答示例后,模型能模仿格式回答新问题。
-
核心原理:通过示例激发模型的上下文学习(In-Context Learning)能力,提升输出准确性和一致性。
-
示例
输入:“苹果 -> 水果”,输出:“香蕉 -> 水果” 输入:“汽车 -> 交通工具”,输出:“飞机 -> 交通工具” 输入:“猫 ->”,输出:“动物”
-
-
应用场景对比
场景 零样本(Zero-Shot) 少量样本(Few-Shot) 适用任务复杂度 简单任务(如翻译、分类) 复杂任务(如逻辑推理、特定格式生成) 数据依赖 无需示例 需要少量高质量示例 输出可控性 较低(依赖模型预训练知识) 较高(通过示例明确格式和规则) 典型用例 快速原型开发、通用问答 领域特定任务(如法律文档解析、医疗术语抽取) -
案例实战
-
零样本学习案例:直接通过指令调用大模型生成答案
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 零样本提示模板 zero_shot_prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个专业翻译,将文本从{source_lang}翻译到{target_lang}"), ("human", "{text}") ]) # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 链式调用 chain = zero_shot_prompt | llm # 执行翻译(中文→英文) response = chain.invoke({ "source_lang": "中文", "target_lang": "英文", "text": "今天天气不错,我要学习AI大模型开发" }) print(response.content)
-
少量样本学习案例: 通过示例指导模型理解任务格式
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate from langchain_openai import ChatOpenAI # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 示例数据 examples = [ { "input": "苹果公司的总部在哪里?", "output": "根据我的大量思考:苹果公司的总部位于美国加利福尼亚州的库比蒂诺(Cupertino)。" }, { "input": "OpenAI的CEO是谁?", "output": "根据我的大量思考:OpenAI的现任CEO是萨姆·阿尔特曼(Sam Altman)。" } ] # 定义单条示例模板 example_template = """ 输入:{input} 输出:{output} """ example_prompt = PromptTemplate( template=example_template input_variables=["input", "output"], ) # 构建Few-Shot提示模板 few_shot_prompt = FewShotPromptTemplate( examples=examples, example_prompt=example_prompt, suffix="输入:{question}\n输出:", input_variables=["question"] ) # 创建Chain并调用 few_shot_chain = few_shot_prompt | llm response = few_shot_chain.invoke({"question": "亚马逊的创始人是谁?"}) print(response.content) # 输出:根据我的大量思考:亚马逊的创始人是杰夫·贝索斯(Jeff Bezos)。
-
-
总结
- 零样本 vs 少量样本:
- 零样本依赖模型预训练知识,适合通用任务。
- 少量样本通过示例提升任务适配性,适合专业场景。
- LangChain 实现要点:
- 零样本:使用
AgentType.ZERO_SHOT_REACT_DESCRIPTION
初始化智能体。 - 少量样本:通过
PromptTemplate
设计含示例的提示词。
- 零样本:使用
- 设计原则:
- 清晰指令:明确任务目标和输出格式。
- 示例质量:少量样本的示例需覆盖典型场景。
- 零样本 vs 少量样本:
LangChain智能体执行引擎AgentExecutor
-
需求背景:为什么需要 AgentExecutor?
- 问题:当智能体(Agent)需要执行多步操作(如多次调用工具、循环推理)时,开发者需手动处理:
- 执行循环:根据模型输出决定是否继续调用工具。
- 错误处理:捕获工具调用或模型解析中的异常。
- 流程控制:限制最大迭代次数,防止无限循环。
- 日志记录:追踪每一步的输入、输出和中间状态。
- 痛点:
- 代码冗余:重复编写循环和错误处理逻辑。
- 维护成本高:复杂任务中难以保证流程稳定性。
- 可观测性差:难以调试多步骤执行过程。
- 问题:当智能体(Agent)需要执行多步操作(如多次调用工具、循环推理)时,开发者需手动处理:
-
AgentExecutor
介绍-
LangChain 提供的智能体执行引擎,封装了执行循环、错误处理和日志追踪,让开发者聚焦业务逻辑。
-
核心功能
功能 说明 执行循环控制 自动迭代执行智能体的 思考 -> 行动 -> 观察
流程,直到满足终止条件。错误处理 捕获工具调用异常、模型解析错误,支持自定义重试或回退逻辑。 迭代限制 通过 max_iterations
防止无限循环(如模型陷入死循环)。日志与追踪 记录每一步的详细执行过程(需设置 verbose=True
),支持集成 LangSmith。输入输出处理 统一格式化最终结果,隐藏中间步骤细节(除非显式要求输出)。 -
关键参数
参数 作用 agent
绑定的智能体实例(如 create_react_agent
的返回值)。tools
智能体可调用的工具列表。 verbose
是否打印详细执行日志(调试时建议开启)。 max_iterations
最大迭代次数,防止死循环。 handle_parsing_errors
自动处理模型输出解析错误(如返回无效工具名),可设置为 True
或自定义函数。return_intermediate_steps
是否返回中间步骤结果(用于调试或展示完整链路)。
-
-
使用场景:何时需要
AgentExecutor
-
多步骤任务:
-
例如:先调用搜索工具获取数据,再调用计算工具处理结果
# 示例:计算商品折扣价 "思考:需要先获取商品价格,再计算折扣。" "行动1:调用 get_price(商品A)" "观察1:价格=100元" "行动2:调用 calculate_discount(100, 20%)" "观察2:折扣价=80元"
-
-
工具依赖场景:
- 例如:预订航班需要先查询航班号,再调用支付接口。
-
复杂推理任务:
- 例如:解决数学问题需多次尝试不同公式。
-
生产环境部署:
- 需保证服务稳定性(自动处理超时、限流等异常)
-
-
伪代码参考
from langchain.agents import AgentExecutor agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, max_iterations=3, handle_parsing_errors=True ) # 执行查询(自动处理循环和错误) response = agent_executor.invoke({"input": "北京的气温是多少华氏度?"}) print(response["output"]) # 输出:25℃ = 77℉
LangChain智能体之initialize_agent开发实战
-
需求背景
手工调用工具的问题 initialize_agent
解决方案需要手动编写工具选择逻辑 自动根据输入选择最合适的工具 缺乏错误重试机制 内置异常处理和重试策略 输出格式不统一 标准化响应格式 难以处理多工具协作场景 自动编排工具调用顺序 -
手动执行需要开发者自己处理任务分解、工具选择、参数生成、结果整合等步骤
-
Agent利用大模型的推理能力自动完成这些步骤,提高了开发效率和系统的灵活性。
-
解决方案:
- langchain内置多个方法快速创建智能体,能减少手动编码的复杂性,提升系统的智能性和适应性。
- 包括自动化任务分解、动态工具选择、错误处理、结果整合等
- 主要包含以下核心方法:
initialize_agent
:通用初始化方法,快速构建智能体(兼容旧版)返回AgentExecutor。create_react_agent
:基于 ReAct 框架的智能体,支持多步推理。create_tool_calling_agent
:专为工具调用优化的智能体,支持结构化输出。- 其他方法:如
create_json_agent
(处理 JSON )、create_openai_tools_agent
(适配 OpenAI 工具调用格式)
-
-
initialize_agent
方法介绍-
通过封装智能体的决策逻辑和工具调度,旧版方法,未来可能被弃用
-
适用场景:快速原型开发、简单任务处理
-
实现
- 自动化工具选择:根据输入动态调用合适的工具。
- 流程标准化:统一处理错误、重试、结果格式化。
- 简化开发:一行代码完成智能体初始化。
-
语法与参数详解
from langchain.agents import initialize_agent def initialize_agent( tools: Sequence[BaseTool], # 可用工具列表 llm: BaseLanguageModel, # 大模型实例 agent: Optional[AgentType] = None, # Agent类型 verbose: bool = False, # 是否显示详细过程 ) -> AgentExecutor:
-
关键参数说明:
agent_type
AgentType 适用场景 特点 ZERO_SHOT_REACT_DESCRIPTION 通用任务处理 基于ReAct框架,零样本学习 STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 结构化输入输出 支持复杂参数类型 CONVERSATIONAL_REACT_DESCRIPTION 多轮对话场景 保留对话历史上下文 SELF_ASK_WITH_SEARCH 结合自问自答和搜索工具,适合问答场景。 自动生成中间问题并验证
-
-
案例实战
from langchain.agents import initialize_agent, AgentType from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain.agents import initialize_agent from langchain_community.utilities import SearchApiAPIWrapper import os # 设置SearchAPI的API密钥 os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML" # 实例化SearchApiAPIWrapper对象,用于调用搜索API search = SearchApiAPIWrapper() #定义搜索工具 @tool("web_search", return_direct=True) def web_search(query: str) -> str: """ 当需要获取实时信息、最新事件或未知领域知识时使用,输入应为搜索关键词 """ try: results = search.results(query) # 获取前3条结果 return "\n\n".join([ f"来源:{res['title']}\n内容:{res['snippet']}" for res in results['organic_results'] ]) except Exception as e: return f"搜索失败:{str(e)}" # 使用 @tool 定义进行数学计算的工具 @tool("math_calculator", return_direct=True) def math_calculator(expression: str) -> str: """用于进行数学计算,输入应该是一个有效的数学表达式,如 '2 + 3' 或 '5 * 4'""" try: result = eval(expression) return str(result) except Exception as e: return f"计算出错: {str(e)}" #不同大模型效果不一样,有些会报错,不支持多个输入参数的工具 @tool("multiply") def multiply(a: int, b: int) -> int: """ 把传递的两个参数相乘 """ return a * b # 创建工具列表 tools = [math_calculator,web_search] # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 使用 initialize_agent 创建代理 agent_chain = initialize_agent( tools=tools, # 使用的工具列表 llm=llm, # 使用的语言模型 agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, # 指定代理类型 verbose=True, # 打开详细日志,便于查看代理的思考过程 handle_parsing_errors=True #自动处理解析错误,提高Agent的稳定性。 ) #打印对应的智能体,更清晰底层逻辑 print(agent_chain.agent.llm_chain) print(agent_chain.agent.llm_chain.prompt.template) print(agent_chain.agent.llm_chain.prompt.input_variables) # 测试代理 # 需求 1: 计算 5 乘以 6 的结果 result1 = agent_chain.invoke({"input":"计算 5 乘以 6 的结果"}) print("计算 5 乘以 6 的结果:", result1) # 需求 2: 获取当前日期 #result2 = agent_chain.run("腾讯最新的股价") #result2 = agent_chain.invoke({"input":"腾讯最新的股价"}) #print("腾讯最新的股价:", result2
个人AI助理智能体之tool_calling_agent实战
-
create_tool_calling_agent
方法介绍-
是 LangChain 0.3 新增的智能体创建方法, 要求模型直接返回工具调用参数(如 JSON 格式),减少中间解析错误。
-
结构化工具调用:显式调用工具并传递结构化参数(支持复杂数据类型)
-
多步骤任务处理:适合需要按顺序调用多个工具的场景
-
精准控制:通过自定义 Prompt 模板指导 Agent 行为
-
方法参数
from langchain.agents import create_tool_calling_agent agent = create_tool_calling_agent( llm: BaseLanguageModel, # 语言模型实例(需支持结构化输出) tools: List[BaseTool], # 工具列表 prompt: ChatPromptTemplate # 提示模板(需明确工具调用规则) )
参数 类型 必填 说明 llm BaseLanguageModel 是 支持工具调用的模型(如 ChatOpenAI
)tools Sequence[BaseTool] 是 工具列表,每个工具需用 @tool
装饰器定义prompt ChatPromptTemplate 是 控制 Agent 行为的提示模板,需包含 tools
和tool_names
的占位符
-
-
适用场景
- API 集成:如调用天气查询、支付接口等需要严格参数格式的场景。
- 多工具协作:模型需根据输入动态选择多个工具并传递参数。
- 高精度任务:如金融计算、医疗诊断等容错率低的场景。
-
案例实战: 个人AI助理智能体
from langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent, Tool, AgentExecutor from langchain.tools import tool from datetime import datetime from langchain_core.prompts import ChatPromptTemplate # 定义获取当前日期的工具函数 @tool def get_current_date() -> str: """获取当前日期""" formatted_date = datetime.now().strftime("%Y-%m-%d") return f"The current date is {formatted_date}" # 定义搜索航班的工具函数 @tool def search_flights(from_city: str, to_city: str, date: str) -> str: """根据城市和日期搜索可用航班""" return f"找到航班:{from_city} -> {to_city},日期:{date},价格:¥1200" # 定义预订航班的工具函数 @tool def book_flight(flight_id: str, user: str) -> str: """预订指定航班""" return f"用户 {user} 成功预订航班 {flight_id}" # 定义获取股票价格的函数 def get_stock_price(symbol) -> str: return f"The price of {symbol} is $100." # 创建工具列表,包括获取股票价格、搜索航班、预订航班和获取当前日期的工具 tools = [ Tool( name="get_stock_price", func=get_stock_price, description="获取指定的股票价格" ), search_flights, book_flight, get_current_date ] # 初始化大模型 llm = ChatOpenAI( model_name="qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 定义聊天提示模板 prompt = ChatPromptTemplate.from_messages( [ ("system", "你是一个AI助手,必要时可以调用工具回复问题"), ("human", "我叫老王,经常出差,身份证号是 4414231993210223213332"), #("placeholder", "{chat_history}"), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ] ) # 创建代理 agent = create_tool_calling_agent(llm, tools, prompt) # 初始化代理执行器 , verbose=True可以看到思考明细, return_intermediate_steps返回中间结果 agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True,return_intermediate_steps=True) # 运行代理并获取结果 result = agent_executor.invoke({"input": "苹果股票是多少?根据我的行程,帮我查询下明天的航班,从广州去北京,并定机票"}) print(f"最终结果:{result}"
旅游规划智能体之react_agent实战
-
ReAct 框架:
-
Reasoning + Acting 是一种结合推理和行动的智能体设计模式
-
由以下步骤循环组成:
- 推理(Reason):分析当前状态,决定下一步行动(如调用工具或直接回答)。
- 行动(Act):执行选定的操作(如调用工具、查询数据)。
- 观察(Observe):获取行动结果,更新状态,进入下一轮循环。
[Thought] <-- 推理阶段(分析当前状况) [Action] <-- 行动阶段(调用工具) [Observation] <-- 环境反馈(工具返回结果)
-
-
create_react_agent
-
LangChain 提供的专用方法,用于创建基于 ReAct 框架的智能体
-
适合需要多步动态决策的任务(如复杂问答、数学问题求解)。
-
方法参数与语法
from langchain.agents import create_react_agent agent = create_react_agent( llm: BaseLanguageModel, # 必须支持 ReAct 格式 tools: Sequence[BaseTool],# 工具集(需详细文档) prompt: ChatPromptTemplate # 必须包含 ReAct 特殊标记 ) -> Runnable
-
参考使用的提示模板结构
# 官方推荐模板(从 hub 获取) prompt = hub.pull("hwchase17/react") # 模板核心内容示例: """ Answer the following questions using the following tools: {tools} Use the following format: Question: the input question Thought: 需要始终进行的思考过程 Action: 要执行的动作,必须是 [{tool_names}] 之一 Action Input: 动作的输入 Observation: 动作的结果 ...(重复思考/行动循环) Final Answer: 最终答案 """
-
与其它方法的对比选型
场景特征 推荐方法 原因说明 需要逐步推理 create_react_agent 显式思考链支持复杂逻辑 严格参数传递 create_tool_calling_agent 结构化输入更可靠 快速简单任务 initialize_agent 开箱即用最简配置 需要自我修正 create_react_agent 错误后可重新推理 与人类协作调试 create_react_agent 完整思考链易读性好 -
适用场景
- 多步骤任务:例如:“查询北京的气温,并计算对应的华氏度。”
- 动态工具选择:根据中间结果决定下一步调用哪个工具。
- 复杂推理任务:例如:“如果明天下雨,推荐室内活动;否则推荐户外活动。
-
-
案例实战:智能推荐助手
from langchain_openai import ChatOpenAI from langchain.tools import tool from langchain_community.utilities import SearchApiAPIWrapper import os from langchain_core.prompts import PromptTemplate from langchain.agents import create_react_agent, AgentExecutor @tool def get_weather(city: str) -> str: """获取指定城市的天气""" # 模拟数据 weather_data = { "北京": "晴,25℃", "上海": "雨,20℃", "广州": "多云,28℃" } return weather_data.get(city, "暂不支持该城市") @tool def recommend_activity(weather: str) -> str: """根据天气推荐活动""" if "雨" in weather: return "推荐室内活动:博物馆参观。" elif "晴" in weather: return "推荐户外活动:公园骑行。" else: return "推荐一般活动:城市观光。" # 定义搜索工具 os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML" @tool("web_search", return_direct=True) def web_search(query: str) -> str: """ 当需要获取实时信息、最新事件或未知领域知识时使用,输入应为搜索关键词 """ try: search = SearchApiAPIWrapper() results = search.results(query) # 获取前3条结果 return "\n\n".join([ f"来源:{res['title']}\n内容:{res['snippet']}" for res in results['organic_results'] ]) except Exception as e: return f"搜索失败:{str(e)}" tools = [get_weather, recommend_activity, web_search] # 初始化大模型 llm = ChatOpenAI( model_name="qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 使用 LangChain 预定义的 ReAct 提示模板,https://smith.langchain.com/hub/hwchase17/react #from langchain import hub #prompt = hub.pull("hwchase17/react") # 模板内容示例: template = """ Answer the following questions as best you can. You have access to the following tools: {tools} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: {input} Thought:{agent_scratchpad} """ prompt = PromptTemplate.from_template(template) # 创建 ReAct 智能体 agent = create_react_agent(llm, tools, prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, # 打印详细执行过程 max_iterations=3, # 限制最大迭代次数, handle_parsing_errors=lambda _: "请检查输入格式", # 错误处理 return_intermediate_steps=True # 保留中间步骤 ) # 执行查询 response = agent_executor.invoke({ "input": "我在北京玩3天,根据天气推荐活动, 顺便查询腾讯的股价是多少" }) print(response)
LLM综合实战-文档网络智能问答助手开发
-
需求说明
-
智能体核心功能
-
多模态知识整合
- 深度结合 Milvus 向量数据库 的本地文档知识与 实时网络搜索能力,实现对复杂问题的分步解析与回答。
- 支持同时处理 结构化文档问答(如技术框架详解)和 实时信息查询(如股价、日期等动态数据)。
-
智能工具决策
- 自动判断问题类型:
- 文档相关问题(如 "什么是Milvus?")调用 Milvus 向量检索工具,精准匹配本地知识库内容。
- 实时信息需求(如 "腾讯股价")触发 Web 搜索工具,获取最新数据。
- 支持 多问题串联回答,例如一次性处理包含技术解释、时间查询、股价查询的复合任务。
- 自动判断问题类型:
-
交互式回答增强
- 通过 LangChain Agent 框架 的动态决策流程,展示问题拆解与工具调用的全过程(如中间思考步骤)。
- 提供 结构化输出,清晰标注每个答案的来源(如 "来自 Milvus 文档" 或 "来自实时搜索")。
-
-
-
技术亮点:
- 工具链集成:无缝融合 Milvus 向量检索 + 网络搜索API + 大模型推理(如 Qwen-Plus)。
- 可视化流程:通过 LangSmith 追踪 Agent 的决策路径
- 自定义提示模板:支持灵活调整 Agent 的行为逻辑(如切换 create_tool_calling_agent 或 create_react_agent 模式)。
-
应用场景
- 技术文档问答:快速解析 Milvus 版本、功能、与 LangChain 的集成方法。
- 实时数据查询:动态获取股票价格、当前日期等时效性信息。
- 多步骤问题处理:如 "解释Milvus的工作原理,同时比较其与Faiss的优劣,最后给出GitHub最新教程链接"。
-
功能效果演示
-
编码实战
from langchain_community.utilities import SearchApiAPIWrapper
from langchain_core.tools import tool
from langchain.agents import AgentExecutor, create_tool_calling_agent,create_react_agent
from langchain.tools.retriever import create_retriever_tool
from langchain_milvus import Milvus
from langchain.prompts import PromptTemplate,ChatPromptTemplate
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_openai import ChatOpenAI
import os
from pydantic import Field
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "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"
os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGSMITH_PROJECT"] = "agent_v1"
# 配置搜索工具
os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML"
search = SearchApiAPIWrapper()
@tool("web_search")
def web_search(query: str) -> str:
"""当需要获取实时信息、最新事件或未知领域知识时使用,输入应为搜索关键词"""
try:
results = search.results(query) # 获取前3条结果
return "\n\n".join([
f"来源:{res['title']}\n内容:{res['snippet']}"
for res in results['organic_results']
])
except Exception as e:
return f"搜索失败:{str(e)}"
#初始化嵌入模型
embeddings = DashScopeEmbeddings(
model="text-embedding-v2", # 第二代通用模型
max_retries=3,
dashscope_api_key="sk-005c3c25f6d042848b29d75f2f020f08"
)
vector_store = Milvus(
embeddings,
connection_args={"uri": "http://47.119.128.20:19530"},
collection_name='doc_qa_db',
)
#获取检索器
retriever = vector_store.as_retriever()
retriever_tool = create_retriever_tool(
retriever,
"milvus_retriever",
"搜索有关 Milvus 的信息。对于任何有关 Milvus 的问题,你必须使用这个工具!",
)
# 初始化大模型
llm = ChatOpenAI(
model_name = "qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="sk-005c3c25f6d042848b29d75f2f020f08",
temperature=0.7
)
tools = [web_search, retriever_tool]
# #构建提示模板 ,采用create_tool_calling_agent的提示词模版
-
编码实战
# #构建提示模板 ,采用create_tool_calling_agent的提示词模版 # prompt = ChatPromptTemplate.from_messages([ # ("system", "你是一个AI文档助手,拥有多个工具,必要时可以利用工具来回答问题。"), # ("user", "{input}"), # ("placeholder", "{agent_scratchpad}") # ]) # # 创建Agent执行器 # agent = create_tool_calling_agent(llm, tools,prompt) #构建提示模板 ,采用create_react_agent的提示词模版 prompt = PromptTemplate.from_template('''Answer the following questions as best you can. You have access to the following tools: {tools} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: {input} Thought:{agent_scratchpad}''') agent = create_react_agent(llm, tools,prompt) #创建Agent执行器 agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True,handle_parsing_errors=True,return_intermediate_steps=True) def run_question(question: str): print(f"\n问题:{question}") result = agent_executor.invoke({"input": question}) print(f"\nllm-result:{result}") print(f"\n答案:{result['output']}\n{'='*50}")
-
案例测试
-
打印输出中间思考步骤
- 使用 AgentExecutor 的
return_intermediate_steps
参数
#print("最终结果:", result["output"]) #print("中间步骤:", result["intermediate_steps"]) def run_question(question: str): print(f"\n问题:{question}") result = agent_executor.invoke({"input": question}) print(f"\nllm-result:{result}") print(f"\n答案:{result['output']}\n{'='*50}") # 输出完整执行轨迹 print("=== 完整执行链 ===") for step in result["intermediate_steps"]: print(f"Action: {step[0].tool}") print(f"Input: {step[0].tool_input}") print(f"Observation: {step[1]}\n")
- 使用 AgentExecutor 的
-
总结说明
- 在AI智能体开发中,不同的任务需要不同的交互逻辑,因此需要不同类型的智能体来高效处理不同任务。
- 例如:
- 有些任务需要智能体先“思考”再行动(如解数学题)。
- 有些任务需要直接调用工具完成(如查天气、调用计算器)
- 关键选择点:
- 任务是否需要分解步骤 → 选
create_react_agent
; - 任务是否单一明确 → 选
create_tool_calling_agent
。
- 任务是否需要分解步骤 → 选
- 进阶技巧:
- 混合使用两种智能体(如先用React分解任务,再调用Tool-Calling执行子任务)
- 共同点
- 均基于大模型(如LLM大模型)驱动。
- 依赖预定义的工具集(Tools)。
- 底层关系:
create_tool_calling_agent
可视为create_react_agent
的简化版(跳过显式推理步骤)。
老王忘不了的痛-大模型存储记忆实战
LLM大模型存储记忆功能介绍和应用场景
-
需求背景:为什么需要存储记忆功能?
-
长对话上下文遗忘问题
# 示例:第二次提问时模型已“失忆” user_input1 = "我叫张三" ai_response1 = "你好张三!" user_input2 = "我叫什么名字?" ai_response2 = "抱歉,我不知道您的名字。" # 期望回答“张三”
- 大模型(如 GPT-4)单次对话的上下文窗口有限(通常为 4k-128k tokens),导致多轮对话中容易丢失早期信息。
- 例如,用户询问 “如何制作蛋糕” 后接着问 “需要烤箱吗”
- 模型若无法记住前一轮对话,可能回答 “需要烤箱” 但忘记蛋糕配方的关键步骤。
-
个性化服务与用户偏好记忆
- 在客服、教育、医疗等场景中,用户需要模型记住个人信息(如姓名、病史)或历史行为(如订单记录、学习进度)。
- 例如,医疗助手需根据患者的历史诊断结果提供建议。
-
复杂任务的状态管理
-
涉及多步骤的任务(如旅行规划、代码调试)需要模型跟踪中间状态。
-
例如,用户要求 “规划上海到北京的三天行程”,模型需记住已推荐的景点、交通方式等。
-
-
-
LangChain方法
-
短期记忆:通过 Memory 模块存储对话历史,确保模型在多轮交互中保持连贯
-
长期记忆:将用户数据存储在外部数据库或向量数据库(如 Milvus、Pinecone),实现跨会话的长期记忆
-
-
记忆功能的核心设计
-
两种类型
维度 短期记忆 长期记忆 存储方式 内存缓存, 模型输入中的历史消息 数据库持久化存储, 向量库/文件 容量限制 受上下文窗口限制(如4k-128k tokens) 理论上无上限 访问速度 毫秒级 百毫秒级(依赖检索算法) 典型应用 对话连贯性保持, 即时对话、单次任务 个性化服务、用户画像构建,跨会话记忆、知识库 实现复杂度 低 高 成本 低(无额外存储开销) 中高(需维护存储系统) 示例 聊天中记住前3轮对话 用户资料、项目历史记录 -
记忆的实现方式
- 短期记忆:通过拼接历史消息实现(
[用户: 你好][AI: 你好!]
)。 - 长期记忆
- 结构化存储:用数据库记录关键信息(如用户喜好)。
- 向量化存储:将文本转为向量存入向量库(如Milvus)。
- 混合模式:短期记忆 + 长期检索增强(RAG)
- 短期记忆:通过拼接历史消息实现(
-
-
应用场景与案例
-
个性化教育助手 (伪代码)
from langchain.memory import VectorStoreRetrieverMemory # 初始化记忆系统 retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) memory = VectorStoreRetrieverMemory(retriever=retriever) # 运行示例 memory.save_context( {"input": "我的学习目标是掌握微积分"}, {"output": "目标已记录,将推荐相关资源"} ) memory.save_context( {"input": "请解释洛必达法则"}, {"output": f"{explanation} 已添加到你的学习清单"} ) # 后续对话 query = "根据我的目标推荐学习资料" relevant_memories = memory.load_memory_variables({"query": query}) # 返回微积分相关记忆
-
电商推荐引擎
from langchain.retrievers import TimeWeightedVectorStoreRetriever # 带时间权重的记忆系统 retriever = TimeWeightedVectorStoreRetriever( vectorstore=vectorstore, decay_rate=0.95, # 记忆衰减系数 k=5 ) # 记忆示例数据 retriever.add_documents([ Document(page_content="用户2026-12-01购买手机", metadata={"type": "purchase"}), Document(page_content="用户2027-03-15浏览笔记本电脑", metadata={"type": "browse"}), Document(page_content="用户2027-06-20退货耳机", metadata={"type": "return"}) ]) # 获取最新加权记忆 relevant_memories = retriever.get_relevant_documents("用户兴趣分析")
-
-
大模型长短期记忆选择决策树
- 基础用法:短期对话记忆
- 进阶用法:长期记忆 + 向量数据库
- 高级用法:多用户隔离记忆(会话级)
LLM存储记忆功能之BaseChatMemory实战
-
BaseChatMemory
介绍-
是 LangChain 中所有聊天型记忆模块的基类,定义了记忆存储和检索的通用接口。
from langchain.memory.chat_memory import BaseChatMemory
-
可通过继承此类实现自定义记忆逻辑(如过滤敏感信息、动态清理策略)
-
注意:部分API虽然过期,但也需要知道核心思想, 新旧我们都有讲
-
核心作用
- 标准化接口:统一
save_context()
(保存上下文)和load_memory_variables()
(加载记忆)方法。 - 状态管理:维护对话历史(
chat_memory
属性),支持消息的增删改查。 - 扩展性:允许开发者覆盖默认行为(如自定义存储格式、加密数据)
- 标准化接口:统一
-
关键属性
chat_memory
- 存储对话消息的容器,类型为 ChatMessageHistory,包含 messages 列表
- 每条消息为 BaseMessage 对象,如 HumanMessage、AIMessage
-
核心方法
-
save_context
保存用户输入和模型输出到 chat_memory。memory.save_context({"input": "你好"}, {"output": "你好!有什么可以帮您?"}) # 等价于: memory.chat_memory.add_user_message("你好") memory.chat_memory.add_ai_message("你好!有什么可以帮您?")
-
load_memory_variables
返回当前记忆内容(通常为 {"history": "对话历史字符串"}variables = memory.load_memory_variables({}) print(variables["history"]) # 输出:Human: 你好\nAI: 你好!有什么可以帮您?
-
clear()
清空所有记忆#调用 chat_memory.clear()
-
-
-
BaseChatMemory
的子类类名 说明 ConversationBufferMemory
直接存储原始对话历史(继承 BaseChatMemory
)。ConversationBufferWindowMemory
仅保留最近 N 轮对话(通过 k
参数控制,覆盖消息存储逻辑)。ConversationSummaryMemory
存储模型生成的对话摘要(覆盖 load_memory_variables
生成摘要)。自定义类 继承 BaseChatMemory
,按需重写save_context
或load_memory_variables
-
案例实战
-
ConversationBufferMemory
from langchain.memory import ConversationBufferMemory # 初始化对话缓冲记忆 memory = ConversationBufferMemory( memory_key="chat_history", #存储进去的Key,和获取的时候需要保持一致 return_messages=True, ) # 添加用户消息和 AI 消息 memory.chat_memory.add_user_message("你的名字是什么") memory.chat_memory.add_ai_message("二当家") # 加载记忆内容 memory_variables = memory.load_memory_variables({}) print("记忆内容:", memory_variables)
-
ConversationBufferWindowMemory
- 滑动窗口式对话记忆, 此类在对话记录中引入窗口机制,仅保存最近的
k
条对话历史。 - k 参数:定义窗口大小,表示最多保留的对话记录条数, 适合长对话或对历史要求不高的应用。
from langchain.memory import ConversationBufferWindowMemory # 初始化窗口记忆,设置窗口大小为 2 memory = ConversationBufferWindowMemory(k=2, memory_key="chat_history") # 保存一些消息 memory.save_context({"input": "你叫什么名字"}, {"output": "你好,我是三当家"}) memory.save_context({"input": "小滴课堂怎么样"}, {"output": "小滴课堂是适合初学者的计算机学习平台"}) memory.save_context({"input": "好的,我去了解下"}, {"output": "希望对你有帮助!"}) # 获取窗口内的对话历史 window_history = memory.load_memory_variables({}) print("当前窗口内的对话历史:", window_history)
- 滑动窗口式对话记忆, 此类在对话记录中引入窗口机制,仅保存最近的
-
【面试题】LLM存储优化-大量长对话如何解决
-
面试官
- 传统对话系统每次交互独立,模型无法感知历史对话内容,如何解决?
- 长对话超出模型的Token处理能力,导致信息截断或性能下降,如何解决?
-
大模型场景题目的需求背景【重要】
- 大模型的上下文限制
- 大语言模型(如GPT-4、DeepSeek等虽然能处理复杂的对话任务
- 但其输入长度存在限制(如Token上限),无法直接存储长期对话历史
- 对话连贯性需求
- 实际应用中(如客服系统、智能助手),用户问题常依赖上下文。
- 例如,用户先问“人工智能的定义”,再要求“详细说明”,模型需基于历史回答才能生成合理响应
- 资源优化需求
- 直接存储完整对话历史会占用大量内存或数据库资源,且频繁传递完整上下文会增加计算成本。
问题类型 具体表现 后果 技术限制 长对话超出模型上下文窗口 关键信息丢失,回答质量下降 效率瓶颈 全量历史数据检索耗时(>500ms) 响应延迟影响用户体验 业务需求 需快速定位历史问题关键点 客服质检、争议溯源效率低 合规风险 存储用户敏感对话原文 数据泄露风险增加 - 大模型的上下文限制
-
面试回答要点
- 核心目标
- 通过摘要存储实现对话上下文的长期维护,解决大模型Token限制与对话连贯性问题。
- 技术实现
- 记忆模块:LangChain提供ConversationBufferMemory(完整历史)和ConversationSummaryMemory(摘要存储)等
- 摘要生成:调用LLM对历史对话生成摘要,后续交互仅传递摘要而非完整历史
- 优势
- 减少Token消耗,适配模型输入限制。
- 提升对话系统的长期记忆能力。
- 支持分布式存储(如MongoDB、Milvus),扩展性强
- 核心目标
-
ConversationSummaryMemory
-
通过模型生成对话的摘要,帮助保留重要信息,而不保存完整历史,适合需要长期记忆的场景。
-
原理就是提示词,让AI帮出来摘要,而且可以定制
请将以下对话压缩为简短摘要,保留用户需求和关键结果: 对话历史: {history} 当前对话: Human: {input} AI: {output} 摘要: """
-
load_memory_variables:返回当前会话的摘要信息。
from langchain.memory import ConversationSummaryMemory from langchain_openai import ChatOpenAI # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 初始化摘要记忆 memory = ConversationSummaryMemory(llm=llm) # 模拟对话 memory.save_context({"input": "你叫什么名字?"}, {"output": "你好,我是三当家。"}) memory.save_context({"input": "你能告诉我机器学习吗?"}, {"output": "机器学习是人工智能的一个分支。"}) # 获取摘要 summary = memory.load_memory_variables({}) print("当前对话摘要:", summary)
-
基于LangChain的带摘要存储对话系统实战
-
实战案例
-
基于LangChain的带摘要存储对话系统
from langchain.memory import ConversationSummaryMemory from langchain_openai import ChatOpenAI from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain.chains import LLMChain # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 初始化模型和记忆模块 memory = ConversationSummaryMemory( llm=llm, memory_key="chat_history", # 与prompt中的变量名一致 return_messages=True ) # 定义提示模板(必须包含chat_history占位符) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个助手,需基于对话历史回答问题。当前摘要:{chat_history}"), ("human", "{input}") ]) # # 创建链并绑定记忆模块 # chain = LLMChain( # llm=llm, # prompt=prompt, # memory=memory, # verbose=True # 调试时查看详细流程 # ) # 定义LCEL链式流程 chain = ( # 注入输入和记忆 RunnablePassthrough.assign( chat_history=lambda _: memory.load_memory_variables({})["chat_history"] ) | prompt # 将输入传递给提示模板 | llm # 调用模型 | StrOutputParser() # 解析模型输出为字符串 ) # 模拟多轮对话 user_inputs = [ "人工智能的定义是什么?", "小滴课堂上面有什么课程可以学习", "人工智能它在医疗领域有哪些应用?" ] for query in user_inputs: # 执行对话 response = chain.invoke({"input": query}) # 打印结果 print(f"\n用户:{query}") print(f"AI:{response}") #使用 LLMChain 而非原始LCEL链时,每次 invoke() 会自动调用 memory.save_context()。 #手动调用场景需显式保存: memory.save_context({"input": query}, {"output": response}) print("当前记忆摘要:", memory.load_memory_variables({})["chat_history"])
-
示例输出
用户:人工智能的定义是什么? AI:人工智能是模拟人类智能的计算机系统... 当前记忆摘要: 用户询问人工智能的定义,助手解释了其核心是通过算法模拟人类认知能力。 用户:小滴课堂上面有什么课程可以学习 AI:如果“小滴课堂”是一个特定的学习平台,建议直接查询该平台提供的课程列表以获取最新信息 当前记忆摘要: 首先询问了人工智能的定义,AI解释了人工智能是计算机科学的一个分支,随后,人类问到“小滴课堂”上有什么课程可以学习 用户:它在医疗领域有哪些应用? AI:在医疗影像分析、药物研发... 当前记忆摘要: 用户问及医疗应用,助手提到影像分析、药物研发和个性化诊疗是主要方向。
-
关键步骤讲解
- 链式组合 (
|
操作符) 通过管道符连接多个组件:RunnablePassthrough
:传递原始输入prompt
:格式化提示词llm
:调用大模型StrOutputParser
:解析输出为字符串
- 记忆管理
memory.load_memory_variables()
:加载当前摘要到提示词memory.save_context()
:手动保存对话记录(LCEL需要显式保存)
- 变量绑定
使用
RunnablePassthrough.assign
动态注入chat_history
变量,确保与提示模板匹配
- 链式组合 (
-
注意: 变量名一致性
memory_key
必须与提示模板中的变量名一致(示例中均为chat_history
)。- 错误示例:如果模板用
{summary}
但memory_key
设为history
,会导致变量未注入
-
-
LCEL与 LLMChain 的核心区别
特性 LCEL 实现 LLMChain 实现 记忆管理 需手动调用 save_context
自动保存上下文 链式组合 支持任意步骤组合 固定结构 调试灵活性 可插入日志中间件 依赖 verbose=True
扩展性 容易添加路由、分支等复杂逻辑 适合简单线性流程
MessagesPlaceholder和多轮AI翻译助手实战
-
MessagesPlaceholder
介绍-
是 LangChain 中用于在 聊天型提示模板(ChatPromptTemplate) 中动态插入消息列表的占位符。
-
允许开发者将历史对话记录、系统消息等结构化地嵌入到 Prompt 中,从而支持多轮对话场景的上下文管理
-
适用场景
- 多角色对话:在聊天机器人中,区分系统指令、用户输入和AI响应。
- 历史对话注入:将历史消息作为上下文传递给模型,确保对话连贯性。
- 模块化Prompt设计:灵活组合不同来源的消息(如系统消息、检索结果等)
-
与普通PromptTemplate的区别
-
PromptTemplate:用于单字符串模板,适合简单问答。
-
ChatPromptTemplate:专为多角色消息设计,必须使用 MessagesPlaceholder 处理消息列表
-
例如
ChatPromptTemplate.from_messages([ ("system", "你是一个助手"), MessagesPlaceholder(variable_name="history"), ("human", "{input}") ])
-
-
核心功能对比
功能特性 MessagesPlaceholder 传统列表存储 动态插入 ✅ 支持运行时动态调整消息顺序 ❌ 固定顺序 消息类型感知 ✅ 区分 system/human/AI ❌ 统一存储为字符串 内存集成 ✅ 自动与Memory组件同步 ❌ 需手动管理 结构化操作 ✅ 支持消息元数据 ❌ 纯文本存储
-
-
使用步骤
-
定义模板与占位符
- 在 ChatPromptTemplate 中通过 MessagesPlaceholder 声明占位位置
- 并指定变量名(需与 Memory 模块的 memory_key 一致)
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个翻译助手"), MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}") ])
-
绑定记忆模块
- 使用 ConversationBufferMemory 或 ConversationSummaryMemory 存储对话历史,
- 并确保 memory_key 与占位符变量名匹配
from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory( memory_key="chat_history", # 必须与MessagesPlaceholder的variable_name一致 return_messages=True # 返回消息对象而非字符串 )
-
链式调用与历史管理
- 在链式调用中自动注入历史消息,需使用 LLMChain 或
RunnableWithMessageHistory
from langchain.chains import LLMChain from langchain_community.chat_models import ChatOpenAI llm = ChatOpenAI() chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
- 在链式调用中自动注入历史消息,需使用 LLMChain 或
-
-
案例测试
from langchain.memory import ConversationBufferMemory from langchain_community.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.chains import LLMChain from langchain_openai import ChatOpenAI # 1. 初始化模型与记忆模块 # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True ) # 2. 定义包含MessagesPlaceholder的Prompt模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个翻译助手,需参考历史对话优化翻译结果。"), MessagesPlaceholder(variable_name="chat_history"), ("human", "请翻译以下内容:{input}") ]) # 3. 创建链并绑定记忆 chain = LLMChain(llm=llm, prompt=prompt, memory=memory) # 4. 模拟多轮对话 user_inputs = [ "Translate 'Hello' to Chinese", "Use the translation in a sentence", "Now translate 'Goodbye'" ] for query in user_inputs: response = chain.invoke({"input": query}) print(f"用户:{query}") print(f"AI:{response['text']}\n") print("当前对话历史:", memory.load_memory_variables({})["chat_history"], "\n")
- LCEL表达式案例
from langchain.memory import ConversationBufferMemory from langchain_community.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.chains import LLMChain from langchain_openai import ChatOpenAI from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser # 1. 初始化模型与记忆模块 # 初始化大模型 llm = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True ) # 2. 定义包含MessagesPlaceholder的Prompt模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个翻译助手,需参考历史对话优化翻译结果。"), MessagesPlaceholder(variable_name="chat_history"), ("human", "请翻译以下内容:{input}") ]) # 3. 创建链并绑定记忆 #chain = LLMChain(llm=llm, prompt=prompt, memory=memory) #定义LECL表达式,构建chain chain = ( RunnablePassthrough.assign( chat_history = lambda _ : memory.load_memory_variables({})['chat_history'] ) | prompt | llm | StrOutputParser() ) # 4. 模拟多轮对话 user_inputs = [ "Translate 'Hello' to Chinese", "Use the translation in a sentence", "Now translate 'Goodbye'" ] for query in user_inputs: response = chain.invoke({"input": query}) print(f"用户:{query}") print(f"AI:{response}\n") memory.save_context({"input": query}, {"output": response}) print("当前对话历史:", memory.load_memory_variables({})["chat_history"], "\n")
LLM复杂记忆存储-多会话隔离案例实战
-
背景与需求
-
当多个会话同时与对话系统交互时,需确保每个会话的对话历史独立存储,避免以下问题:
- 数据混淆:会话A的对话内容泄露给会话B。
- 上下文丢失:不同会话的对话历史互相覆盖。
- 隐私安全:敏感信息因隔离不当导致泄露。
-
典型场景
- 客服系统:不同会话咨询需独立记录。
- 教育应用:每个学生与AI助教的对话需单独存档。
- 医疗助手:患者健康信息需严格隔离。
-
解决方案:为每个会话分配唯一ID,通过ID隔离的对话历史。
-
-
RunnableWithMessageHistory
介绍-
是 LangChain 中用于动态管理多用户对话历史的高级封装类,主要解决以下问题
- 会话隔离:为不同用户/会话(通过
session_id
)独立存储对话历史。 - 记忆注入:自动将历史消息注入到链的每次执行中,无需手动传递。
- 灵活存储:支持自定义历史存储后端(内存、数据库、Redis 等)。
- 会话隔离:为不同用户/会话(通过
-
核心参数
from langchain_core.runnables.history import RunnableWithMessageHistory
参数 类型 必填 说明 runnable
Runnable 是 基础处理链(需支持消息历史输入) get_session_history
Callable[[str], BaseChatMessageHistory] 是 根据session_id获取历史存储实例的函数 input_messages_key
str 否 输入消息在字典中的键名(默认"input") history_messages_key
str 否 历史消息在字典中的键名(默认"history") -
使用场景
- 多用户对话系统:为每个用户维护独立的对话历史(如客服系统)。
- 长期会话管理:结合数据库存储历史,支持跨设备会话恢复。
-
-
案例实战
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai.chat_models import ChatOpenAI from langchain_core.messages import HumanMessage from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory # 存储会话历史的字典,可以改其他存储结构 store = {} # 获取会话历史的函数 如果给定的session_id不在store中,则为其创建一个新的ChatMessageHistory实例 def get_session_history(session_id): if session_id not in store: store[session_id] = ChatMessageHistory() return store[session_id] # 初始化大模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 构建聊天提示模板,包含系统消息、历史消息占位符和人类消息 prompt = ChatPromptTemplate.from_messages( [ ( "system", "你是一个小滴课堂AI助手,擅长能力{ability}。用30个字以内回答", ), MessagesPlaceholder(variable_name="history"), ("human", "{input}"), ] ) # 创建Runnable管道,将提示模板和模型结合在一起 runnable = prompt | model # 创建带有会话历史的Runnable ,使用get_session_history函数来管理会话历史 with_message_history = RunnableWithMessageHistory( runnable, get_session_history, input_messages_key="input", #输入消息在字典中的键名(默认"input") history_messages_key="history", #历史消息在字典中的键名(默认"history") ) # 第一次调用带有会话历史的Runnable,提供用户输入和会话ID response1=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("什么是jvm")}, config={"configurable": {"session_id": "user_123"}},#历史信息存入session_id ) print(f"response1:{response1.content}",end="\n\n") # 第二次调用带有会话历史的Runnable,用户请求重新回答上一个问题 response2=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("重新回答一次")}, config={"configurable": {"session_id": "user_123"}},#历史信息存入session_id,如果改为其他session_id,则不会关联到之前的会话历史 ) print(f"response2:{response2.content}",end="\n\n") # 打印存储的会话历史 print(f"存储内容:{store}")
再进阶复杂存储-多租户多会话隔离案例实战
-
需求背景
- 系统需要支持多个用户访问,每个用户拥有唯一的标识符(
user_id
)。 - 用户之间的会话历史完全隔离,避免数据混淆。
- 业务场景中,存在一个用户多个会话的场景
- 存储里面需要支持多用户,多会话的方案, 支持不同用户在多个会话中与AI助手交互。
- 系统需要支持多个用户访问,每个用户拥有唯一的标识符(
-
目标:
- 多租户支持:每个用户拥有独立的标识符(
user_id
),确保不同用户之间的数据隔离。 - 多会话支持:每个用户可以同时维护多个会话(
session_id
),每个会话的历史记录相互独立。 - 灵活扩展性:支持自定义存储结构和参数配置,便于未来扩展到分布式存储或其他存储介质
- 多租户支持:每个用户拥有独立的标识符(
-
知识点
-
history_factory_config
- 是一个配置列表,用于定义额外的可配置字段,(如
user_id
和session_id
),这些字段可以影响会话历史的生成逻辑。 - 通过自定义参数来增强系统的灵活性,例如指定用户标识符或对话标识符,从而实现多租户或多会话的支持
- 是一个配置列表,用于定义额外的可配置字段,(如
-
ConfigurableFieldSpec
-
是一个类,用于定义一个可配置字段的元信息,包括字段的 ID、类型、名称、描述、默认值等。
from langchain_core.runnables import ConfigurableFieldSpec
-
为
RunnableWithMessageHistory
提供灵活的参数支持,使得开发者可以通过配置动态调整行为。
参数名 类型 描述 示例值 id
str
字段的唯一标识符,用于在配置中引用该字段。 "user_id"
annotation
type
字段的数据类型,用于类型检查和验证。 str
name
str
字段的名称,通常用于UI展示或调试信息。 "用户ID"
description
str
字段的描述信息,用于说明该字段的用途。 "用户的唯一标识符。"
default
any
字段的默认值,如果未提供值时使用。 ""
is_shared
bool
是否为共享字段。如果为 True
,则该字段在整个运行过程中保持不变。True
-
-
-
解决方案
- 使用
user_id
和session_id
组合作为键值,存储在全局字典store
中。 - 在调用
get_session_history
函数时,传入user_id
和session_id
,确保获取正确的会话历史。
- 使用
-
案例实战
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai.chat_models import ChatOpenAI from langchain_core.messages import HumanMessage from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.runnables import ConfigurableFieldSpec # 存储会话历史的字典,可以改其他存储结构 store = {} # 获取会话历史的函数 如果给定的session_id不在store中,则为其创建一个新的ChatMessageHistory实例 def get_session_history(user_id: str,session_id: str): if (user_id, session_id) not in store: store[(user_id, session_id)] = ChatMessageHistory() return store[(user_id, session_id)] # 初始化大模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 构建聊天提示模板,包含系统消息、历史消息占位符和人类消息 prompt = ChatPromptTemplate.from_messages( [ ( "system", "你是一个小滴课堂AI助手,擅长能力{ability}。用30个字以内回答", ), MessagesPlaceholder(variable_name="history"), ("human", "{input}"), ] ) # 创建Runnable管道,将提示模板和模型结合在一起 runnable = prompt | model # 创建带有会话历史的Runnable ,使用get_session_history函数来管理会话历史 with_message_history = RunnableWithMessageHistory( runnable, get_session_history, input_messages_key="input", #输入消息在字典中的键名(默认"input") history_messages_key="history", #历史消息在字典中的键名(默认"history") # 定义一些自定义的参数 history_factory_config=[ ConfigurableFieldSpec( id="user_id", annotation=str, name="用户ID", description="用户的唯一标识符。", default="", is_shared=True, ), ConfigurableFieldSpec( id="session_id", annotation=str, name="对话 ID", description="对话的唯一标识符。", default="", is_shared=True, ), ] ) # 第一次调用带有会话历史的Runnable,提供用户输入和会话ID response1=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("什么是jvm")}, config={'configurable' : { "user_id" : "1" , "session_id" : "1" }} ) print(f"response1:{response1.content}",end="\n\n") # 第二次调用带有会话历史的Runnable,用户请求重新回答上一个问题 response2=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("重新回答一次")}, #历史信息存入session_id,如果改为其他session_id,则不会关联到之前的会话历史 config={'configurable' : { "user_id" : "1" , "session_id" : "2" }}, ) print(f"response2:{response2.content}",end="\n\n") # 打印存储的会话历史 print(f"存储内容:{store}")
大模型长期记忆解决方案和案例实战
-
长期记忆的核心需求
- 大模型(LLM)本身不具备长期记忆能力,需通过外部系统实现以下目标:
- 跨会话记忆持久化:用户多次对话中产生的关键信息需永久存储。
- 多用户隔离:不同用户的对话历史严格隔离,避免数据泄露。
- 高效检索:快速提取历史信息辅助当前对话生成。
- 动态更新:支持记忆的增量添加和过期清理。
- 解决思路:
- 持久化存储:将会话历史保存到数据库/缓存,支持跨会话读取。
- 高效检索:通过语义搜索或键值查询快速定位历史信息。
- 动态更新:根据新交互持续补充用户画像。
- 大模型(LLM)本身不具备长期记忆能力,需通过外部系统实现以下目标:
-
LangChain提供的存储方案
-
地址(失效忽略即可):https://python.langchain.com/docs/integrations/memory/
-
RedisChatMessageHistory
存储介绍- 内置的 Redis 消息历史存储工具,将会话记录以结构化形式保存至 Redis
特性 价值 低延迟(<1ms) 实时响应交互需求 丰富数据结构 灵活存储消息/元数据/关系, 使用 JSON 格式存储,支持复杂消息类型(如带元数据的消息) 持久化选项 RDB+AOF保障数据安全 集群支持 横向扩展应对高并发 自动过期(TTL) 合规数据自动清理, 通过 expire
参数设置历史记录保存时间(如 30 天自动删除- LangChain 的
RedisChatMessageHistory
等组件默认依赖 JSON 格式存储对话历史,ReJSON 提供天然的兼容性 - 数据存储结构
-
-
什么是Redis-Stack
-
是Redis 官方推出的 集成化发行版,预装了多个高性能模块和工具,专为现代应用设计。
-
开箱即用:无需手动安装模块,直接支持搜索、JSON、图数据库等高级功能。
-
开发友好:更适合需要复杂查询、实时分析、AI 应用的场景(如大模型上下文管理、聊天记录检索)。
-
统一体验:通过 Redis Stack 命令行或客户端库统一访问所有功能。
模块/工具 功能 RediSearch 全文搜索、二级索引 RedisJSON 原生 JSON 数据支持 RedisGraph 图数据库(基于属性图模型) RedisTimeSeries 时间序列数据处理 RedisBloom 概率数据结构(布隆过滤器、基数估算) RedisInsight 图形化管理工具 -
何时选择 Redis Stack?
- 全文检索(如聊天记录搜索)。
- 直接操作 JSON 数据(如存储大模型的对话上下文)。
- 时间序列分析(如监控聊天频率)。
- 图关系查询(如社交网络分析)。
- 简化开发流程:避免手动配置多个模块的兼容性和依赖。
-
LLM整合如果直接使用之前的Redis服务端会报错,新版单独安装 RedisStack 服务端,才支持相关Redis搜索
- 之前部署的Redis可以卸载,或者更改端口,也可以配置密码,使用方式一样,redis-stack 7.X版本都可以
docker run -d \ --name redis-stack \ -p 6379:6379 \ -p 8001:8001 \ redis/redis-stack:latest
-
-
案例实战
-
安装依赖
pip install langchain-redis==0.2.0 redis==5.2.1
-
案例测试
from langchain_redis import RedisChatMessageHistory import os REDIS_URL = os.getenv("REDIS_URL", "redis://47.119.128.20:6379") #配置了密码 REDIS_URL = "redis://:your_password@your_ip:6379" #REDIS_URL = os.getenv("REDIS_URL", "redis://:abc123456@39.108.115.28:6379") # 初始化 RedisChatMessageHistory history = RedisChatMessageHistory(session_id="user_123", redis_url=REDIS_URL) # 新增消息 history.add_user_message("Hello, AI assistant!") history.add_ai_message("Hello! How can I assist you today?") # 检索消息 print("Chat History:") for message in history.messages: print(f"{type(message).__name__}: {message.content}")
-
编码实战
from langchain_core.chat_history import BaseChatMessageHistory from langchain_core.messages import AIMessage, HumanMessage from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_openai import ChatOpenAI from langchain_redis import RedisChatMessageHistory #存储历史会话的字典 REDIS_URL="redis://47.119.128.20:6379" #定义函数,用于获取会话历史 def get_redis_history(session_id: str): return RedisChatMessageHistory(session_id, redis_url=REDIS_URL) # 初始化大模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 构建聊天提示模板,包含系统消息、历史消息占位符和人类消息 prompt = ChatPromptTemplate.from_messages( [ ("system","你是一个小滴课堂AI助手,擅长能力{ability}。用30个字以内回答",), MessagesPlaceholder(variable_name="history"), ("human", "{input}"), ] ) #创建基础链 chain = prompt | model with_message_history = RunnableWithMessageHistory( chain, get_redis_history, input_messages_key="input", history_messages_key="history" ) #第一次调用,提供用户输入和会话ID resp1 = with_message_history.invoke( {"ability":"Java开发", "input":HumanMessage("什么是JVM")}, #替换提示词 config={"configurable":{"session_id":"user_123"} }) #会话唯一ID print(f"resp1={resp1.content}",end="\n\n") resp2 = with_message_history.invoke( {"ability":"Java开发", "input":HumanMessage("重新回答一次")}, #替换提示词 config={"configurable":{"session_id":"user_123"} }) #会话唯一ID print(f"resp2={resp2.content}",end="\n\n")
-
注意: 安全起见,RedisStack还是需要配置密码,避免入侵挖矿
docker run -d \ --name redis-stack \ -p 6379:6379 \ -p 8001:8001 \ -e REDIS_ARGS="--requirepass abc123456" \ redis/redis-stack:latest
-
多租户多会话方案
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai.chat_models import ChatOpenAI from langchain_core.messages import HumanMessage from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.runnables import ConfigurableFieldSpec from langchain_redis import RedisChatMessageHistory #存储历史会话的字典 REDIS_URL="redis://:abc123456@47.119.128.20:6379" # 获取会话历史的函数 如果给定的session_id不在store中,则为其创建一个新的ChatMessageHistory实例 def get_session_history(user_id: str,session_id: str): uni_key = user_id+"_"+session_id return RedisChatMessageHistory( session_id=uni_key, redis_url=REDIS_URL) # 初始化大模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7 ) # 构建聊天提示模板,包含系统消息、历史消息占位符和人类消息 prompt = ChatPromptTemplate.from_messages( [ ( "system", "你是一个小滴课堂AI助手,擅长能力{ability}。用30个字以内回答", ), MessagesPlaceholder(variable_name="history"), ("human", "{input}"), ] ) # 创建Runnable管道,将提示模板和模型结合在一起 runnable = prompt | model # 创建带有会话历史的Runnable ,使用get_session_history函数来管理会话历史 with_message_history = RunnableWithMessageHistory( runnable, get_session_history, input_messages_key="input", #输入消息在字典中的键名(默认"input") history_messages_key="history", #历史消息在字典中的键名(默认"history") # 定义一些自定义的参数 history_factory_config=[ ConfigurableFieldSpec( id="user_id", annotation=str, name="用户ID", description="用户的唯一标识符。", default="", is_shared=True, ), ConfigurableFieldSpec( id="session_id", annotation=str, name="对话 ID", description="对话的唯一标识符。", default="", is_shared=True, ), ] ) # 第一次调用带有会话历史的Runnable,提供用户输入和会话ID response1=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("什么是jvm")}, config={'configurable' : { "user_id" : "user_1" , "session_id" : "session_1" }} ) print(f"response1:{response1.content}",end="\n\n") # 打印存储的会话历史 print(get_session_history("user_1","session_1").messages) # 第二次调用带有会话历史的Runnable,用户请求重新回答上一个问题 response2=with_message_history.invoke( {"ability": "Java开发", "input": HumanMessage("重新回答一次")}, config={'configurable' : { "user_id" : "user_2" , "session_id" : "session_1" }}, ) print(f"response2:{response2.content}",end="\n\n") # 打印存储的会话历史 print(get_session_history("user_2","session_1").messages)
-
大模型服务必备FastAPI Web框架
Python Web框架Fast API技术介绍
-
需求背景:现代Web开发痛点
- 传统框架(如Flask、Django)对异步支持弱,性能受限。
- 手动编写API文档耗时且易过时、缺乏自动化数据验证,易出现安全漏洞。
-
FastAPI是什么?
- 一个基于 Python 3.8+ 的现代 Web 框架,专为构建高性能 API 设计。
- 官方地址:
- 关键特性:
- ⚡ 高性能:基于Starlette(异步)和Pydantic(数据验证)
- 📚 自动文档:集成Swagger UI和Redoc,代码即文档。
- ✅ 强类型安全:Python类型提示 + Pydantic模型,减少Bug。
- 🚀 易学易用:代码简洁,适合快速开发API。
- 适用场景
- 构建 RESTful API 或 大模型服务,微服务架构中的独立服务模块。
- 需要自动生成 API 文档的项目。
- 核心价值:FastAPI = 高性能 + 强类型 + 自动文档。
- 优点:
- 开发效率高(代码量减少 40%+)
- 严格的类型提示减少运行时错误。
- 缺点:
- 生态相对较新(插件和社区资源少于 Django/Flask)。
- 学习曲线略高(需熟悉 异步编程)。
-
什么是 Uvicorn(类似Java 生态的Tomcat)
- 是一个轻量级、高性能的 ASGI(Asynchronous Server Gateway Interface)服务器
- 专门运行异步 Python Web 应用(如 FastAPI、Starlette 或 Django Channels)。
- 类似传统 WSGI 服务器 的异步升级版,支持 HTTP/1.1、WebSockets 和 ASGI 标准。
- 核心功能
- 异步处理:基于
asyncio
库,原生支持async/await
,适合高并发场景(如实时通信、高频 API 调用)。 - 高性能:使用 C 语言编写的
uvloop
和httptools
加速网络请求处理。 - 自动重载:开发时通过
--reload
参数监听代码变动,自动重启服务。 - 协议支持: HTTP/1.1 、WebSocket、实验性 HTTP/2(需额外配置)
- 异步处理:基于
- 与 FastAPI 的关系
- 依赖关系:FastAPI 本身不包含服务器,需通过 Uvicorn(或其他 ASGI 服务器)启动。
- 协同工作流程:
- 用户通过 Uvicorn 启动 FastAPI 应用。
- Uvicorn 监听 HTTP 请求,将请求转发给 FastAPI 处理。
- FastAPI 处理请求并返回响应,由 Uvicorn 发送给客户端
-
类似产品对比
框架 语言 特点 适用场景 Flask Python 轻量级、灵活,但同步且无内置数据验证 小型应用、快速原型开发 Django Python 全功能(ORM、Admin),但重量级、同步 复杂 Web 应用(如 CMS) Express Node.js 高并发,但动态类型易出错 JS 生态的 API 开发 Spring Boot Java 企业级功能,但配置复杂、启动慢 大型分布式系统 FastAPI Python 高性能、异步、类型安全、自动文档 高并发 API、微服务
FastAPI环境安装和基础案例实战
-
环境安装
- FastAPI 依赖 Python 3.8 及更高版本, 安装fastapi版本
0.115.12
- 安装命令
- 安装依赖库.
pip install "fastapi[standard]"
- 安装uvicorn服务器
pip install "uvicorn[standard]"
- 安装依赖库.
- FastAPI 依赖 Python 3.8 及更高版本, 安装fastapi版本
-
案例实战
-
创建第一个Fast API应用
from typing import Union from fastapi import FastAPI # 创建FastAPI实例 app = FastAPI() #http请求方式类型:get、post、put、update、delete #不带参数访问路由 @app.get("/") def read_root(): return {"Hello": "World11"} # 带参数访问,比如 http://127.0.0.1:8000/items/5?q=xd # q参数通过 Union[str, None] 表示可以是字符串类型或空,这样就允许在请求中不提供 q 参数。 @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
-
启动服务
# 语法:uvicorn 文件的相对路径:实例名 --reload # 多级目录使用.,例如: uvicorn xx.xx.main:app -- reload #--reload:代码修改后自动重启(仅开发环境) # main:当前目录下的main.py文件,app:文件下的app实例变量 uvicorn main:app --reload #指定程序监听端口 uvicorn main:app --port 8002 --reload #下面这个方式也可以 fastapi dev main.py
-
访问API与文档
- API地址:
http://localhost:8000
- 交互文档:
http://localhost:8000/docs
- 备用文档( 另一种交互式文档界面,具有清晰简洁的外观 ):
http://localhost:8000/redoc
- API地址:
-
进阶FastAPI Web多案例参数请求实战
-
路由与 HTTP 方法
from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") def read_item(item_id: int): # 自动类型转换和验证 return {"item_id": item_id} @app.post("/items/") def create_item(item: dict): return {"item": item} @app.put("/items/{item_id}") def update_item(item_id: int, item: dict): return {"item_id": item_id, "updated_item": item} @app.delete("/items/{item_id}") def delete_item(item_id: int): return {"status": "deleted", "item_id": item_id}
-
请求头Header操作
from fastapi import Header @app.get("/header") def read_item(item_id: int, token: str = Header("token")): return {"token": token,"item_id": item_id}
-
获取多个请求头
from fastapi import Request @app.get("/all-headers/") def get_all_headers(request: Request): return dict(request.headers)
-
自定义响应头
from fastapi.responses import JSONResponse @app.get("/custom-header/") def set_custom_header(): content = {"message": "Hello World"} headers = { "X-Custom-Header": "my_value xdclass.net", "Cache-Control": "max-age=3600" } return JSONResponse(content=content, headers=headers)
-
自定义响应码
from fastapi import FastAPI, status,Response @app.get("/status_code", status_code=200) def create_item(name: str): if name == "Foo": return Response(status_code=status.HTTP_404_NOT_FOUND) return {"name": name}
FastAPI异步编程async-await多案例实战
-
需求背景
-
什么是同步和异步? 用一个奶茶店买饮料的场景帮理解同步和异步的区别
-
🧋 同步(Synchronous)就像传统奶茶店
你: 老板,我要一杯珍珠奶茶 店员: 开始制作(5分钟) 你: 干站着等,不能做其他事 店员: 做好了!请取餐 下一位顾客: 必须等前一个人完成才能点单 特点: 必须按顺序处理,前一个任务没完成,后面全卡住
-
🚀 异步(Asynchronous)像智能奶茶店
你: 扫码下单珍珠奶茶 系统: 收到订单(生成取餐号) 你: 去旁边座位玩手机(不用干等) 店员: 同时处理多个订单 系统: 奶茶做好后叫号通知你 特点: 下单后可以继续做其他事,系统并行处理多个任务
-
对应到代码中
# 同步代码(传统奶茶店模式) def make_tea(): print("开始煮茶") # 👨🍳 店员开始工作 time.sleep(3) # ⏳ 你干等着 print("加珍珠") return "奶茶好了" # 异步代码(智能奶茶店模式) async def async_make_tea(): print("开始煮茶") await asyncio.sleep(3) # 🚶♂️ 你可以去做其他事 print("加珍珠") return "奶茶好了"
-
-
-
同步和异步关键区别总结【伪代码】
-
同步版本(存在性能瓶颈)
import requests @app.get("/sync-news") def get_news_sync(): # 顺序执行(总耗时=各请求之和) news1 = requests.get("https://api1.com").json() # 2秒 news2 = requests.get("https://api2.com").json() # 2秒 return {"total_time": 4}
-
异步版本(高效并发)
import httpx @app.get("/async-news") async def get_news_async(): async with httpx.AsyncClient() as client: # 并行执行(总耗时≈最慢的请求) start = time.time() task1 = client.get("https://api1.com") task2 = client.get("https://api2.com") res1, res2 = await asyncio.gather(task1, task2) return { "total_time": time.time() - start # ≈2秒 }
-
-
异步编程常见类库介绍
-
-
asyncio
类库-
Python 中用于编写单线程并发代码的库,基于**协程(Coroutine)和事件循环(Event Loop)**实现异步编程。
-
专为处理 I/O 密集型任务(如网络请求、文件读写、数据库操作)设计,提高程序的吞吐量和资源利用率
-
协程(Coroutine)
-
使用
async def
定义的函数称为协程,返回一个协程对象,不会立即执行。 -
协程通过
await
关键字挂起自身,将控制权交还给事件循环,直到异步操作完成async def my_coroutine(): await asyncio.sleep(1) print("Done!")
-
-
事件循环(Event Loop)
-
事件循环是异步程序的核心,负责调度和执行协程、处理 I/O 事件、管理回调等。
-
通过
asyncio.run()
或手动创建事件循环来启动。# 启动事件循环并运行协程 asyncio.run(my_coroutine())
-
-
任务(Task)
-
任务是对协程的封装,用于在事件循环中并发执行多个协程。
-
通过
asyncio.create_task()
创建任务async def main(): task = asyncio.create_task(my_coroutine()) await task
-
-
Future
Future
是一个底层对象,表示异步操作的最终结果。通常开发者直接使用Task
(它是Future
的子类)。
-
-
httpx
类库-
Python 中一个现代化、功能丰富的 HTTP 客户端库,支持同步和异步请求
-
httpx
是requests
的现代替代品,结合了易用性与强大功能,尤其适合需要异步或 HTTP/2 支持的场景。 -
同步请求案例
import httpx # GET 请求 response = httpx.get("https://httpbin.org/get") print(response.status_code) print(response.json())
-
异步请求案例
import httpx import asyncio async def fetch_data(): async with httpx.AsyncClient() as client: response = await client.get("https://httpbin.org/get") print(response.json()) asyncio.run(fetch_data())
-
使用客户端实例(推荐)
# 同步客户端 with httpx.Client() as client: response = client.get("https://httpbin.org/get") # 异步客户端 async with httpx.AsyncClient() as client: response = await client.get("https://httpbin.org/get")
-
-
-
案例实战
-
基础同步和异步编程
from fastapi import FastAPI import asyncio import time app = FastAPI() # 同步路由执行(顺序阻塞) @app.get("/sync_func") def sync_endpoint(): # 模拟耗时操作 print("开始任务1") # 立即执行 time.sleep(3) # 阻塞3秒 print("开始任务2") # 3秒后执行 return {"status": "done"} # 异步异步执行(非阻塞切换) @app.get("/async_func") async def async_endpoint(): # 异步接口 # 必须使用异步库 print("开始任务A") # 立即执行 await asyncio.sleep(3) # 释放控制权,异步等待 print("开始任务B") # 3秒后恢复执行 return {"status": "done"}
-
综合案例实战:并发调用外部API
import asyncio import httpx from fastapi import FastAPI app = FastAPI() """ 异步函数:fetch_data 功能:通过HTTP GET请求从指定URL获取数据并返回JSON格式的响应。 参数: url (str): 目标API的URL地址。 返回值: dict: 从目标URL获取的JSON格式数据。 """ async def fetch_data(url: str): async with httpx.AsyncClient() as client: response = await client.get(url) return response.json() """ 路由处理函数:get_news 功能:定义一个FastAPI的GET路由,用于并发获取多个API的数据并返回整合后的结果。 """ @app.get("/xdclass") async def get_news(): start = time.time() # 定义需要请求的API URL列表 urls = [ "https://api-v2.xdclass.net/api/funny/v1/get_funny", "https://api-v2.xdclass.net/api/banner/v1/list?location=home_top_ad", "https://api-v2.xdclass.net/api/rank/v1/hot_product", "http://localhost:8000/sync1", "http://localhost:8000/sync2", ] # 创建并发任务列表,每个任务调用fetch_data函数获取对应URL的数据 tasks = [fetch_data(url) for url in urls] # 使用asyncio.gather并发执行所有任务,并等待所有任务完成 results = await asyncio.gather(*tasks) print(f"共耗时 {time.time() - start} 秒") # 返回整合后的结果 return results
-
-
常见错误模式演示
# 错误示例:在async函数中使用同步阻塞 @app.get("/wrong") async def bad_example(): time.sleep(5) # 会阻塞整个事件循环! # 正确方案1:改用异步等待 @app.get("/right1") async def good_example(): await asyncio.sleep(5)
-
最佳实践指南
-
必须使用async的三种场景
- 需要await调用的异步库操作
- WebSocket通信端点
- 需要后台任务处理的接口
-
以下情况适合同步路由
- 纯CPU计算(如数学运算)
- 使用同步数据库驱动
- 快速返回的简单端点
-
路由声明规范
- 所有路由优先使用
async def
- 仅在必须使用同步操作时用普通
def
- 所有路由优先使用
-
性能优化建议
- 保持async函数轻量化
- 长时间CPU密集型任务使用后台线程
- 使用连接池管理数据库/HTTP连接
-
进阶FastAPI模型Pydantic 案例实战
-
什么是数据模型
- 客户端能发送什么数据
- 服务端会返回什么数据
- 数据验证规则
-
基础模型定义
from pydantic import BaseModel # 用户注册模型 class UserRegister(BaseModel): username: str # 必填字段 email: str | None = None # 可选字段 age: int = Field(18, gt=0) # 带默认值和验证
-
案例实战
-
请求体(POST/PUT数据)
from pydantic import BaseModel,Field class Product(BaseModel): name: str price: float = Field(..., gt=0) tags: list[str] = [] @app.post("/products") async def create_product(product: Product): return product.model_dump() #测试数据 { "name":"小滴", "price":111, "tags":["java","vue"] }
-
返回 Pydantic 模型
from pydantic import BaseModel class Item(BaseModel): name: str description: str = None price: float tax: float = None @app.post("/items") async def create_item(item: Item): return item #测试数据 { "name":"小滴", "price":111, "description":"小滴课堂是在线充电平台" }
-
密码强度验证
from pydantic import BaseModel, field_validator class UserRegistration(BaseModel): username: str password: str @field_validator('password') def validate_password(cls, v): if len(v) < 8: raise ValueError('密码至少8位') if not any(c.isupper() for c in v): raise ValueError('必须包含大写字母') if not any(c.isdigit() for c in v): raise ValueError('必须包含数字') return v @app.post("/register/") async def register(user: UserRegistration): return {"username": user.username} #测试数据 { "username":"小滴", "password":"123" }
-
FastAPI框架高级技能和综合项目实战
FastAPI路由管理APIRouter案例实战
-
需求背景
-
代码混乱问题, 未使用路由管理(所有接口堆在main.py中)
- 所有接口混杂在一个文件中,随着功能增加,文件会变得臃肿难维护。
# main.py from fastapi import FastAPI app = FastAPI() # 用户相关接口 @app.get("/users") def get_users(): ... # 商品相关接口 @app.get("/items") def get_items(): ... # 订单相关接口 @app.get("/orders") def get_orders(): ...
-
重复配置问题, 未使用路由管理(重复写前缀和标签)
- 每个接口都要重复写
/api/v1
前缀和tags
,容易出错且难以统一修改
# 用户接口 @app.get("/api/v1/users", tags=["用户管理"]) def get_users_v1(): ... # 商品接口 @app.get("/api/v1/items", tags=["商品管理"]) def get_items_v1(): ...
- 每个接口都要重复写
-
权限控制问题, 未使用路由管理(每个接口单独添加认证)
- 需要在每个管理员接口重复添加认证依赖。
@app.get("/admin/stats", dependencies=[Depends(admin_auth)]) def get_stats(): ... @app.post("/admin/users", dependencies=[Depends(admin_auth)]) def create_user(): ...
-
-
使用路由管理(模块化拆分)
-
按业务模块拆分,每个文件职责单一。
# 文件结构 routers/ ├── users.py # 用户路由 ├── items.py # 商品路由 └── orders.py # 订单路由 # main.py from routers import users, items, orders app.include_router(users.router) app.include_router(items.router) app.include_router(orders.router)
-
使用路由管理
# 创建管理员专属路由组 admin_router = APIRouter( prefix="/admin", dependencies=[Depends(admin_auth)], # 👈 统一认证 tags=["管理员"] ) @admin_router.get("/stats") def get_stats(): ... @admin_router.post("/users") def create_user(): ...
-
-
APIRouter
核心概念-
基础使用模板
from fastapi import APIRouter router = APIRouter( prefix="/users", # 路由前缀 tags=["用户管理"], # OpenAPI分组 responses={404: {"description": "资源未找到"}} # 默认响应 ) @router.get("/", summary="获取用户列表") async def list_users(): return [{"id": 1, "name": "张三"}]
-
核心配置参数
参数 作用 示例值 prefix 路由统一前缀 "/api/v1" tags OpenAPI文档分组 ["认证相关"] dependencies 路由组公共依赖项 [Depends(verify_token)] responses 统一响应定义 {400: {"model": Error}}
-
-
案例实战
-
创建文件
app/users.py
from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter( prefix="/users", # 路由前缀 tags=["用户管理"], # OpenAPI文档分组 dependencies=[] # 模块级依赖 ) @router.get("/", summary="获取用户列表") async def list_users(): return [{"id": 1, "name": "Alice"}] @router.post("/", summary="创建新用户") async def create_user(): return {"id": 2, "name": "Bob"} class UserCreate(BaseModel): username: str password: str @router.post("/register", status_code=201) async def register(user: UserCreate): """用户注册接口""" # 实际应保存到数据库 return {"message": "用户创建成功", "username": user.username} @router.get("/{user_id}") async def get_user(user_id: int): if user_id > 100: raise HTTPException(404, "用户不存在") return {"user_id": user_id, "name": "虚拟用户"}
-
创建文件
app/products.py
# app/api/v1/products.py from fastapi import APIRouter router = APIRouter( prefix="/products", tags=["商品管理"], dependencies=[] ) @router.get("/search", summary="商品搜索") async def search_products( q: str, min_price: float = None, max_price: float = None ): # 实现搜索逻辑 return {"message": "搜索成功"} @router.get("/{product_id}", summary="获取商品详情") async def get_product_details(product_id: int): return {"id": product_id}
-
创建入口文件
main.py
from fastapi import FastAPI from app import users, products import uvicorn # 创建FastAPI应用实例 app = FastAPI() # 注册用户模块的路由 app.include_router(users.router) # 注册产品模块的路由 app.include_router(products.router) # 访问路径: # GET /users/ → 用户列表 # POST /users/ → 创建用户 #... # 打印所有注册的路由信息,便于开发者查看和调试 for route in app.routes: print(f"{route.path} → {route.methods}") # 定义根路径的GET请求处理函数 """ 处理根路径的GET请求。 返回值: dict: 返回一个JSON对象,包含欢迎消息。 """ @app.get("/") async def root(): return {"message": "Hello 小滴课堂"} # 主程序入口,用于启动FastAPI应用 if __name__ == '__main__': # 使用uvicorn运行FastAPI应用,默认监听本地地址和端口 uvicorn.run(app,port=8001)
-
-
模块化结构参考
#整体项目结构 project/ ├── main.py └── routers/ ├── __init__.py ├── users.py ├── items.py ├── admin/ │ ├── dashboard.py │ └── audit.py └── v2/ └── users.py
-
总结
实践要点 说明 模块化组织 按业务功能拆分路由模块 统一前缀管理 使用 prefix
参数避免路径重复文档友好 合理使用 tags
和summary
优化API文档依赖分层 模块级依赖处理认证,路由级处理业务逻辑
FastAPI依赖注入和常见项目结构设计
-
什么是依赖注入
-
用于将重复逻辑(如鉴权、数据库连接、参数校验)抽象为可复用的组件,
-
通过依赖注入(Dependency Injection)自动注入到路由处理函数中,让代码更简洁、模块化且易于测试
-
核心
- 代码复用:避免在多个路由中重复相同逻辑(如权限检查)。
- 解耦:将业务逻辑与基础设施(如数据库、认证)分离。
- 层级化:支持嵌套依赖,构建多层逻辑(如先验证用户,再验证权限)
-
与 Java Spring 对比
功能 FastAPI Spring (Java) 依赖注入 函数/类 + Depends()
@Autowired
+ 容器管理作用域 默认每次请求 Singleton/Prototype/Request 等 -
语法案例
-
定义依赖项函数
- 依赖项可以是任何可调用对象(如函数、类),通过
Depends()
声明依赖关系
from fastapi import Depends, FastAPI app = FastAPI() # 定义一个依赖项(函数) def common_params(query: str = None, page: int = 1): return {"query": query, "page": page} # 在路由中使用依赖项 @app.get("/read_items") async def read_items(params: dict = Depends(common_params)): return params #说明:common_params 会被自动调用,结果注入到 params 参数。 #访问结果:read_items 中可直接使用 params["query"] 和 params["page"]。
- 依赖项可以是任何可调用对象(如函数、类),通过
-
类作为依赖项
- 依赖项也可以是类,适合需要初始化或状态管理的场景, 通过依赖注入机制,将复杂逻辑解耦为可复用的模块
#案例一 class DatabaseSession: def __init__(self): self.session = "模拟数据库连接" def close(self): print("关闭数据库连接") def get_db(): db = DatabaseSession() try: yield db finally: db.close() @app.get("/users/") async def get_users(db: DatabaseSession = Depends(get_db)): return {"db_session": db.session} #案例二 class Pagination: def __init__(self, page: int = 1, size: int = 10): self.page = page self.size = size @app.get("/articles/") async def get_articles(pagination: Pagination = Depends()): return {"page": pagination.page, "size": pagination.size}
-
全局依赖项
- 为所有路由添加公共依赖项(如统一认证)
app = FastAPI(dependencies=[Depends(verify_token)]) # 或针对特定路由组: router = APIRouter(dependencies=[Depends(log_request)]) #在部分管理员接口添加认证依赖。 @app.get("/admin/stats", dependencies=[Depends(admin_auth)]) def get_stats(): ... @app.post("/admin/users", dependencies=[Depends(admin_auth)]) def create_user(): ...
-
-
-
FastAPI 项目结构设计原则
-
基础分层结构(适合小型项目)
myproject/ ├── main.py ├── routers/ │ ├── users.py │ └── items.py ├── models/ │ └── schemas.py └── dependencies.py
-
模块化拆分结构一(推荐中型项目)
src/ ├── app/ │ ├── core/ # 核心配置 │ │ ├── config.py │ │ └── security.py │ ├── api/ # 路由入口 │ │ ├── v1/ # 版本控制 │ │ │ ├── users/ │ │ │ │ ├── endpoints.py │ │ │ │ └── schemas.py │ │ │ └── items/ │ ├── models/ # 数据模型 │ ├── services/ # 业务逻辑 │ └── utils/ # 工具类 ├── tests/ # 测试目录 └── requirements.txt
-
模块化拆分结构二(推荐中型项目)
myproject/ ├── app/ # 应用核心目录 │ ├── core/ # 全局配置和工具 │ │ ├── config.py # 配置管理 │ │ └── security.py # 安全相关工具 │ ├── api/ # 路由端点 │ │ ├── v1/ # API版本目录 │ │ │ ├── users.py │ │ │ ├── items.py │ │ │ └── ai.py │ │ └── deps.py # 公共依赖项 │ ├── models/ # Pydantic模型 │ │ ├── user.py │ │ └── item.py │ ├── services/ # 业务逻辑层 │ │ ├── user_service.py │ │ └── ai_service.py │ ├── db/ # 数据库相关 │ │ ├── session.py # 数据库会话 │ │ └── models.py # SQLAlchemy模型 │ └── utils/ # 工具函数 │ └── logger.py ├── tests/ # 测试目录 │ ├── test_users.py │ └── conftest.py ├── static/ # 静态文件 ├── main.py # 应用入口 ├── requirements.txt └── .env # 环境变量
-
-
推荐原则【遵循团队规范即可】
原则 实施方法 单一职责 每个文件/类只做一件事 依赖倒置 通过依赖注入解耦组件 分层清晰 严格区分路由层、服务层、数据访问层 版本控制 通过URL路径实现API版本管理 文档友好 为每个路由添加summary和description
FastAPI+大模型流式AI问答助手实战
-
需求
- 开发一个基于AI的问答工具,能够根据用户提供的知识点或主题生成简洁的介绍或解释。
- 使用了大语言模型(LLM)来实现流式生成文本的功能,适用于教育、内容创作等场景
- FastAPI框架整合LLM大模型,提供HTTP服务
-
StreamingResponse
介绍-
FastAPI 的提供了
StreamingResponse
是一个用于处理流式传输数据的工具,适用于需要逐步发送大量数据或实时内容的场景。 -
允许通过生成器逐块发送数据,避免一次性加载全部内容到内存,提升性能和资源利用率。
from fastapi.responses import StreamingResponse
-
核心功能
- 流式传输:逐步发送数据块,适用于大文件(如视频、日志)、大模型实时生成内容(如LLM响应、服务器推送事件)或长时间运行的任务。
- 内存高效:无需将完整数据加载到内存,减少服务器负载。
- 异步支持:兼容同步和异步生成器,灵活适配不同场景。
-
基本用法
-
在路由中返回
StreamingResponse
实例,并传入生成器作为数据源-
参数说明
content
: 生成器函数,产生字节或字符串数据块。media_type
: 指定 MIME 类型(如"text/event-stream"
、"application/json"
)。headers
: 自定义响应头(如{"Content-Disposition": "attachment; filename=data.csv"}
)。status_code
: 设置 HTTP 状态码(默认为200
)。
-
案例实操
async def ai_qa_stream_generator(query: str): """生成A回答的流式响应""" try: async for chunk in ai_writer.run_stream(query): json_data = json.dumps({"text": chunk}) yield f"data: {json_data}\n\n" except Exception as e: error_msg = json.dumps({"error": str(e)}) yield f"data: {error_msg}\n\n" @app.get("/ai_write") async def ai_writer_endpoint(query: str): """AI写作接口,返回流式响应""" return StreamingResponse( ai_qa_stream_generator(query), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive" } )
-
-
-
-
编码实战
app/ai_writer.py
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from typing import AsyncGenerator # 封装AI问答的类 class AIWriter: def __init__(self): # 初始化语言模型 self.llm = self.llm_model() # 定义一个返回自定义语言模型的方法 def llm_model(self): #创建模型 model = ChatOpenAI( model_name = "qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0.7, streaming=True ) return model async def run_stream(self, query: str) -> AsyncGenerator[str, None]: """运行AI问答并返回流式响应""" try: # 定义提示模板,要求对文档进行摘要总结 prompt_template = "用100个字解释下面的知识点或者介绍:{concept}" # 使用提示模板类创建模板 prompt = ChatPromptTemplate.from_template(prompt_template) # 定义LLM链,将自定义语言模型和提示模板结合 chain = prompt | self.llm | StrOutputParser() # 使用流式输出 async for chunk in chain.astream({"concept": query}): if isinstance(chunk, str): yield chunk elif isinstance(chunk, dict) and "content" in chunk: yield chunk["content"] else: yield str(chunk) except Exception as e: yield f"发生错误: {str(e)}" async def chat(self, query: str): """处理用户消息并返回流式响应""" try: async for chunk in self.run_stream(query): yield chunk except Exception as e: yield f"发生错误: {str(e)}"
-
编码实战
writer_app.py
import uvicorn from fastapi import FastAPI from fastapi.responses import StreamingResponse from app.ai_writer import AIWriter import json app = FastAPI() # 初始化智能体 ai_writer = AIWriter() async def ai_qa_stream_generator(query: str): """生成A回答的流式响应""" try: async for chunk in ai_writer.run_stream(query): json_data = json.dumps({"text": chunk}, ensure_ascii=False) yield f"data: {json_data}\n\n" except Exception as e: error_msg = json.dumps({"error": str(e)}) yield f"data: {error_msg}\n\n" @app.get("/ai_write") async def ai_writer_endpoint(query: str): """AI写作接口,返回流式响应""" return StreamingResponse( ai_qa_stream_generator(query), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive" } ) # 启动服务器的命令 if __name__ == "__main__": uvicorn.run(app, port=8003)
-
如何调试
- Apifox 提供了专门的 SSE(Server-Sent Events)调试功能,适合处理 AI 大模型的流式响应场景
- 配置 SSE,选择接口的请求方法,在请求头中添加
Accept: text/event-stream
来启用 SSE。
LLM大模型Web服务智能体中心开发实战
AI智能体中心Web服务架构和相关环境创建
-
需求说明
-
基于前面学的
langchain+fastapi
+多个知识点,综合开发大模型Web服务,包括多个智能体 -
部分智能体可以结合网盘业务进行,部分智能体可以独立运行
-
注意
- 智能体中心包括多个应用,非强行绑定,可以结合自己公司业务灵活切换包装简历
- 要学会举一反三,结合前面的内容进行拓展更多,比如AI医生、AI律师、AI政务等
-
智能体业务架构图
-
-
项目依赖环境创建
-
项目名称:
dcloud-ai-agent
-
创建虚拟环境
# 语法:python -m venv <环境目录名> python -m venv myenv
-
激活虚拟环境
source myenv/bin/activate
-
安装核心依赖包 (版本和课程保持一致,不然很多不兼容!!!)
- 下载相关资料 ,使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/aipan_agent_1.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/aipan_agent_1.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/aipan_agent_1.zip
-
解压后执行【依赖很多,版本差异大,务必按照下面执行,否则课程无法进行下去,加我微信 xdclass6】
# 安装依赖 pip install -r requirements.txt
-
AI智能体中心Web项目搭建和基础配置
-
项目基本目录结构-创建对应的目录
dcloud-agent/ ├── agents/ # 智能代理相关代码 ├── app/ # 应用主目录 │ └── main.py # 应用入口文件 ├── core/ # 核心功能模块 ├── models/ # 数据模型定义 ├── routers/ # API路由定义 ├── services/ # 业务服务层 ├── tests/ # 测试代码 ├── tools/ # 工具函数和辅助类 ├── myenv/ # Python虚拟环境 ├── Dockerfile # Docker容器配置 ├── README.md # 项目说明文档 └── requirements.txt # 项目依赖包列表【不提供,参考上集,进大课群下载】
-
编写主入口文件
import uvicorn from fastapi import FastAPI from core.config import settings from routers import chat app = FastAPI( title=settings.APP_NAME, description="AI智能体中心API服务", version="1.0.0" ) # 注册路由 app.include_router(chat.router) @app.get("/") async def root(): return { "message": "欢迎使用AI智能体中心API", "version": "1.0.0", "available_agents": ["chat"] # 列出可用的智能体 } # 启动服务器的命令 uvicorn app.main:app --reload if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)
-
隐藏pychche文件
- 打开 首选项 --> 设置 --> 搜索框中 搜索 files.exclude --> 点击 "添加模式" 按钮
- 输入想要隐藏的文件夹, 如python经常生成的缓存文件夹, 输入
**/__pycache
__ 即可隐藏__pycache__
文件夹.
AI智能体中心FastAPI自定义异常+统一响应
简介: AI智能体中心FastAPI自定义异常+统一响应
-
需求
- Web响应一般都会进行自定义异常+统一响应
- 前端方便对接和记录相关业务操作日志
-
编码实战
-
自定义异常,创建一个
exceptions.py
文件- 定义一个继承自
Exception
的自定义异常类,可以添加额外的字段(如错误码、错误信息等) - 定义一个接收
Request
和Exception
参数的函数,返回标准的JSONResponse
from fastapi import HTTPException, status from fastapi.responses import JSONResponse from typing import Any from models.json_response import JsonData class ApiException(Exception): """API异常基类""" def __init__( self, msg: str = "操作失败", code: int = -1, data: Any = None ): self.msg = msg self.code = code self.data = data super().__init__(msg) async def api_exception_handler(request, exc: Exception) -> JSONResponse: """统一异常处理器""" print(f"❌ API异常: {str(exc)}") if isinstance(exc, ApiException): response = JsonData.error(msg=exc.msg, code=exc.code) elif isinstance(exc, HTTPException): response = JsonData.error(msg=str(exc.detail), code=exc.status_code) else: # 处理其他所有异常,包括验证错误 response = JsonData.error(msg=str(exc), code=-1) return JSONResponse( status_code=status.HTTP_200_OK, content=response.model_dump() ) # main.py文件注册统一异常处理器 app.add_exception_handler(ApiException, api_exception_handler)
- 定义一个继承自
-
统一响应JsonData,创建
json_response.py
文件from pydantic import BaseModel from typing import Any, Optional, Literal class JsonData(BaseModel): """通用响应数据模型""" code: int = 0 # 状态码 data: Optional[Any] = None # 响应数据 msg: str = "" # 响应消息 type: Literal["stream", "text"] = "text" # 响应类型 @classmethod def success(cls, data: Any = None) -> "JsonData": """创建成功响应""" return cls(code=0, data=data, type="text") @classmethod def error(cls, msg: str = "error", code: int = -1) -> "JsonData": """创建错误响应""" return cls(code=code, msg=msg, type="text") @classmethod def stream_data(cls, data: Any, msg: str = "") -> "JsonData": """创建流式数据响应""" return cls(code=0, data=data, msg=msg, type="stream")
-
AI智能体中心配置文件和模型方法开发实战
-
需求
- 统一管理配置文件,包括JWT配置、数据库、分布式缓存、大模型参数
- 封装LLM方法,方便切换模型
-
编码实战
-
配置文件
core/config.py
from pydantic_settings import BaseSettings class Settings(BaseSettings): # Redis配置 REDIS_HOST: str = "47.119.128.20" REDIS_PORT: int = 6379 REDIS_DB: int = 0 REDIS_PASSWORD: str = "abc123456" REDIS_MAX_CONNECTIONS: int = 10 # MySQL配置 MYSQL_HOST: str = "39.108.115.28" MYSQL_PORT: int = 3306 MYSQL_USER: str = "root" MYSQL_PASSWORD: str = "xdclass.net168" MYSQL_DATABASE: str = "dcloud_aipan" MYSQL_CHARSET: str = "utf8mb4" # 应用配置 APP_NAME: str = "AI智能体中心API服务" DEBUG: bool = False # JWT配置 JWT_SECRET_KEY: str = "xdclass.net168xdclass.net168xdclass.net168xdclass.net168" JWT_ALGORITHM: str = "HS256" JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 JWT_LOGIN_SUBJECT: str = "XDCLASS" # LLM配置 LLM_MODEL_NAME: str = "qwen-plus" LLM_BASE_URL: str = "https://dashscope.aliyuncs.com/compatible-mode/v1" LLM_API_KEY: str = "sk-005c3c25f6d042848b29d75f2f020f08" LLM_TEMPERATURE: float = 0.7 LLM_STREAMING: bool = True class Config: env_file = ".env" case_sensitive = True settings = Settings()
- 注意:也可以创建一个 .env 文件来存储敏感信息(如 API_KEY),并将其添加到 .gitignore 中,避免敏感信息泄露。
-
大模型方法获取
core/llm.py
from langchain_openai import ChatOpenAI from core.config import settings def get_default_llm(): """获取LLM模型""" return ChatOpenAI( name = settings.LLM_MODEL_NAME, base_url = settings.LLM_BASE_URL, api_key = settings.LLM_API_KEY, temperature = settings.LLM_TEMPERATURE, streaming = settings.LLM_STREAMING )
-
AI智能体中心登录JWT和日志处理实战
简介: AI智能体中心登录JWT和日志处理实战
-
需求
- 部分智能体需要登录才可以进行使用,因此需要加入JWT解析的逻辑
- 和Java采用一样的JWT方式进行,封装相关方法和密钥
- 链路一:前端传递token->后端Java透传->fastapi处理
- 链路二:前端传递token->fastapi处理
- 快速掌握logging模块使用,替代print
-
编码实战
-
logging介绍
-
是 Python 标准库中用于记录日志的模块,提供了灵活的日志记录机制,支持多级别日志
-
替代
print
调试的工业级解决方案import logging # 默认使用 WARNING 级别,输出到控制台 logging.warning("This is a warning message") # 输出 logging.info("This is an info message") # 不输出
-
通过 logging.basicConfig 配置
import logging logging.basicConfig( filename="app.log", # 输出到文件 level=logging.INFO, # 记录器级别 format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) logging.info("Program started") # 写入 app.log
-
推荐方案,为模块或组件创建独立的记录器
logger = logging.getLogger(__name__) # 推荐方式
-
-
创建
auth.py
文件from fastapi import Depends, Header from jose import JWTError, jwt from typing import Dict, Any, Optional import logging from datetime import datetime, timedelta, UTC import os import sys from core.config import settings from core.exceptions import ApiException logger = logging.getLogger(__name__) def remove_prefix(token: str) -> str: """移除token的前缀""" return token.replace(settings.JWT_LOGIN_SUBJECT, "").strip() if token.startswith(settings.JWT_LOGIN_SUBJECT) else token async def get_current_user(token: Optional[str] = Header(None)) -> Dict[str, Any]: """ 获取当前用户信息 Args: token: JWT token字符串,从请求头中获取 Returns: Dict[str, Any]: 包含用户信息的字典,格式为 {"account_id": int, "username": str} Raises: ApiException: 当token无效、过期或解析失败时抛出异常 """ try: # 验证token是否存在 if not token or not token.strip(): raise ApiException(msg="Token不能为空", code=-1) # 移除前缀并解析token token = remove_prefix(token.strip()) payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]) # 验证token内容 if payload.get("sub") != settings.JWT_LOGIN_SUBJECT or not payload.get("accountId"): raise ApiException(msg="无效的认证凭据", code=-1) # 记录日志并返回用户信息 logger.info(f"JWT解密成功,accountId: {payload.get('accountId')}, username: {payload.get('username')}") return {"account_id": payload.get("accountId"), "username": payload.get("username")} except Exception as e: logger.error(f"JWT处理失败: {str(e)}") raise ApiException(msg="无效的认证凭据", code=-1)
-
AI智能体中心实战之聊天智能助理-可联网
AI聊天智能助理需求讲解和路由开发实战
-
需求
- 开发AI聊天智能助理, 基于 Redis 实现聊天历史存储,支持流式响应(Streaming Response)
-
技术亮点:
-
智能对话能力:
-
支持多轮对话,保持上下文连贯性
-
可以理解用户意图并调用合适的工具
-
能够生成对话摘要,帮助理解对话历史
-
-
实时信息获取:
- 可以回答需要实时数据的问题
- 通过搜索工具获取最新信息,搜索结果会被智能整合到回答中
-
高性能设计:
-
支持流式响应,提升用户体验
-
使用 Redis 缓存,提高响应速度,异步处理,提高并发能力
-
-
可扩展性:
-
模块化设计,易于添加新功能
-
工具系统可扩展,支持添加新工具
-
-
-
AI智能体业务使用场景(包装简历和项目):
-
日常对话和问答、实时信息查询
-
多轮对话交互、知识获取和分享
-
-
编码实战
-
创建模型类
chat_schemas.py
from pydantic import BaseModel from typing import List class ChatMessage(BaseModel): role: str content: str timestamp: str class ChatHistoryResponse(BaseModel): messages: List[ChatMessage] class ChatRequest(BaseModel): message: str
-
创建
chat_service.py
import json from typing import List, Dict import redis from datetime import datetime from core.config import settings from core.llm import get_default_llm from models.json_response import JsonData import logging logger = logging.getLogger(__name__) class ChatService: def __init__(self): self.redis_client = redis.Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB, password=settings.REDIS_PASSWORD, decode_responses=True ) self.llm = get_default_llm()
-
开发路由
chat.py
from fastapi import APIRouter, Depends from fastapi.responses import StreamingResponse from services.chat_service import ChatService from models.chat_schemas import ChatRequest, ChatHistoryResponse from models.json_response import JsonData from core.auth import get_current_user from core.exceptions import ApiException from typing import Dict, Any router = APIRouter(prefix="/api/chat", tags=["聊天助手"]) @router.post("/stream") async def chat_stream( request: ChatRequest, chat_service: ChatService = Depends(ChatService), current_user: Dict[str, Any] = Depends(get_current_user) ): """聊天接口(流式响应)""" # 使用当前用户的 account_id account_id = current_user["account_id"]
-
AI聊天智能助理之聊天会话持久化设计
-
需求说明
-
采用Redis进行持久化用户的聊天记录
-
数据结构聊天历史:JSON数组,每个元素为一条消息,包含以下字段
{ "role": "user/assistant", "content": "消息内容", "timestamp": "ISO时间戳" }
-
设计相关方法
- 保存聊天历史
- 将用户的聊天记录以JSON格式存储到Redis中。
- Redis键格式:
chat_history:{account_id}
。
- 获取聊天历史
- 从Redis中读取指定用户的聊天记录, 如果Redis中无记录,则返回空列表。
- 添加消息
- 支持向用户的聊天历史中追加新的消息, 消息包含以下字段:
role
: 消息角色(如user
或assistant
)。content
: 消息内容。timestamp
: 消息时间戳(ISO格式)。
- 清空聊天历史
- 清除指定用户的聊天历史及相关摘要信息。
- 保存聊天历史
-
-
编码实战
chat_service.py
def _get_chat_key(self, account_id: str) -> str: """获取用户聊天记录的Redis key""" return f"chat_history:{account_id}" def save_chat_history(self, account_id: str, messages: List[Dict]): """保存聊天历史到Redis""" chat_key = self._get_chat_key(account_id) self.redis_client.set(chat_key, json.dumps(messages)) def get_chat_history(self, account_id: str) -> List[Dict]: """从Redis获取聊天历史""" chat_key = self._get_chat_key(account_id) history = self.redis_client.get(chat_key) return json.loads(history) if history else [] def add_message(self, account_id: str, role: str, content: str): """添加一条消息到聊天历史""" messages = self.get_chat_history(account_id) messages.append({ "role": role, "content": content, "timestamp": datetime.now().isoformat() }) self.save_chat_history(account_id, messages) def clear_chat_history(self, account_id: str): """清空用户的聊天历史""" chat_key = self._get_chat_key(account_id) summary_key = self._get_summary_key(account_id) self.redis_client.delete(chat_key) self.redis_client.delete(summary_key) def save_chat_messages( self, account_id: str, user_message: str, assistant_message: str ): """保存对话消息""" self.add_message(account_id, "user", user_message) self.add_message(account_id, "assistant", assistant_message)
AI聊天智能助理之多轮长对话摘要生成
-
背景:为啥多轮对话要生成摘要
- 技术必要性:突破大模型token长度限制
- 信息保留率:需保持85%以上的关键信息完整度
- 推理成本控制:减少30-50%的重复计算
- 延迟优化:缩短响应时间200-500ms
- 注意:摘要生成核心还是提示词工程
-
业务逻辑设计
- 基于用户的聊天历史,通过调用LLM(大语言模型)生成简洁的核心摘要。
- 摘要要求:
- 突出对话的主要话题和关键信息。
- 使用第三人称描述,提取重要数据、时间节点、待办事项等。
- 保留原始对话中的重要细节。
- 包含最新的对话内容。
- 摘要合并
- 如果用户已有旧摘要,需将新生成的摘要与旧摘要合并。
- 合并要求:
- 保留两个摘要中的重要信息。
- 突出对话的主要话题和关键信息。
- 使用第三人称描述,提取重要数据、时间节点、待办事项等。
- 保留原始对话中的重要细节。
- 包含最新的对话内容。
- 摘要存储
- 将生成的摘要存储到Redis中。
- Redis键格式:
chat_summary:{account_id}
。
-
编码实战
chat_service.py
def _get_summary_key(self, account_id: str) -> str: """获取聊天摘要的Redis key""" return f"chat_summary:{account_id}" async def generate_summary(self, account_id: str) -> str: """生成聊天历史的核心摘要""" try: # 获取最新的聊天历史 messages = self.get_chat_history(account_id) if not messages: return "" # 构建提示词 prompt = f"""请根据以下对话历史生成一个简洁的核心摘要,突出主要话题和关键信息: {json.dumps(messages, ensure_ascii=False, indent=2)} 摘要要求: 1. 突出对话的主要话题和关键信息 2. 使用第三人称描述,提取重要数据/时间节点/待办事项 3. 保留原始对话中的重要细节 4. 确保包含最新的对话内容 """ # 生成新的摘要 response = await self.llm.ainvoke(prompt) new_summary = response.content # 获取旧的摘要(如果有) summary_key = self._get_summary_key(account_id) old_summary = self.redis_client.get(summary_key) if old_summary: # 如果存在旧摘要,生成一个合并的提示词 merge_prompt = f"""请将以下两个摘要合并成一个新的摘要: 旧摘要: {old_summary} 新摘要: {new_summary} 合并要求: 1. 保留两个摘要中的重要信息 2. 突出对话的主要话题和关键信息 3. 使用第三人称描述,提取重要数据/时间节点/待办事项 4. 保留原始对话中的重要细节 5. 确保包含最新的对话内容 """ # 生成合并后的摘要 merge_response = await self.llm.ainvoke(merge_prompt) final_summary = merge_response.content else: final_summary = new_summary # 更新缓存中的摘要 self.redis_client.set(summary_key, final_summary) return final_summary except Exception as e: logger.error(f"生成摘要失败: {str(e)}") return ""
AI聊天智能助理之联网搜索工具开发
-
需求
- 聊天助理可以根据用户需求,实时查询最新信息
- 封装搜索工具,参考前面的代码即可
- 而且支持拓展更多工具,让AI聊天助理更加智能
-
注意工具的参数
return_direct=True
- 当return_direct设置为True时,工具执行完毕后会直接将结果返回给用户,而不会经过Agent的后续处理。
- Agent不会对工具的输出进行分析或生成进一步的响应,而是立即将结果呈现给用户。
- 在需要快速返回简单结果或者不希望模型介入后续处理的情况下推荐使用
参数状态 数据流向 典型响应示例 return_direct=False
(默认)工具结果 → Agent → 模型生成自然语言响应 "查询完成,当前北京气温是25℃" return_direct=True
工具结果 → 直接返回用户 {"city": "北京", "temp": 25}
-
编码实战
chat_tools.py
from langchain_core.tools import tool from langchain_community.utilities import SearchApiAPIWrapper import os os.environ["SEARCHAPI_API_KEY"] = "qQBHMQo4Rk8SihmpJjCs7fML" @tool("web_search", return_direct=False) def web_search(query: str) -> str: """ 使用此工具搜索最新的互联网信息。当你需要获取实时信息或不确定某个事实时使用 """ try: search = SearchApiAPIWrapper() results = search.results(query) return "\n\n".join([ f"来源:{res['title']}\n内容:{res['snippet']}" for res in results['organic_results'] ]) except Exception as e: return f"搜索失败:{str(e)}" def get_chat_tools(): """获取聊天助手可用的工具集""" tools = [ web_search ] return tools
AI聊天智能助理之Agent智能体开发实战
-
需求背景
- 创建聊天智能体,与智能体进行对话
- 支持工具调用(如搜索工具)
- 对话历史摘要生成
-
知识点:
create_openai_functions_agent
- 专门适配 OpenAI 函数调用功能的代理生成器 , 将自然语言指令自动映射到预定义工具函数
- 实现结构化数据输出与动态行为决策 , 构建符合 ReAct 模式的智能体架构
-
创建聊天智能体开发
-
功能描述
- 创建一个具备对话能力的智能体,支持日常对话、问答以及工具调用。
- 智能体能够记住用户的对话历史,并在对话中使用摘要信息。
-
输入参数
tools
: 工具列表,用于扩展智能体的功能。
-
输出结果
- 返回一个
AgentExecutor
实例,代表创建好的聊天智能体。
- 返回一个
-
实现细节
- 使用
create_openai_functions_agent
创建智能体。 - 定义系统提示词,包含智能体的能力说明和对话摘要占位符。
- 启用流式响应功能
- 使用
-
编码实现
chat_agent.py
from langchain.agents import AgentExecutor, create_openai_functions_agent from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.tools import Tool from core.llm import get_default_llm from typing import List, AsyncIterator, Dict, AsyncGenerator import asyncio from services.chat_service import ChatService from tools.chat_tools import get_chat_tools from models.json_response import JsonData def create_chat_agent(tools: List[Tool]): """创建聊天智能体""" system_prompt = """你是一个智能聊天助手。你可以: 1. 进行日常对话和问答 2. 使用搜索工具获取最新信息 3. 记住与用户的对话历史 请保持回答专业、友好且准确。如果用户的问题需要最新信息,请使用搜索工具。""" prompt = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("system", "以下是之前的对话摘要: {summary}"), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ]) llm = get_default_llm() llm.streaming = True agent = create_openai_functions_agent(llm, tools, prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=True ) return agent_executo
-
AI聊天智能助理之对话和流式响应开发
-
知识点:
AsyncIterator
异步迭代器- Python异步编程中的核心模块,用于在异步环境中逐项处理数据流。
- 与普通迭代器不同,它能在迭代过程中暂停并执行其他异步操作,特别适合处理I/O密集型任务
- 比如处理WebSocket连接、批量处理数据库查询结果等异步数据流
- 注意: 比如忘记使用await 或在错误的地方调用异步迭代器,导致程序阻塞或错误
特性 普通Iterator AsyncIterator 迭代方法 __next__()
__anext__()
循环语法 for item in iter:
async for item in aiter:
执行模式 同步阻塞 异步非阻塞 适用场景 内存数据遍历 网络流/数据库等异步数据源 暂停机制 无 可await其他协程 -
与智能体进行对话业务逻辑开发
-
功能描述
- 用户通过输入文本与智能体进行对话。
- 智能体根据对话历史摘要生成回复,并保存对话记录。
-
输出结果
- 流式返回智能体的回复内容。
-
实现细节
- 获取用户的对话历史摘要。
- 调用智能体的
astream
方法生成流式响应。 - 保存用户输入和智能体回复到对话历史中。
- 异常处理:如果发生错误,返回错误信息
-
编码实战
async def chat_with_agent( agent_executor: AgentExecutor, chat_service: ChatService, account_id: str, input_text: str ) -> AsyncIterator[str]: """与智能体进行对话""" try: # 获取最新的历史摘要 summary = await chat_service.generate_summary(account_id) # 执行对话 async for chunk in agent_executor.astream({ "input": input_text, "summary": summary }): if "output" in chunk: response = chunk["output"] # 保存对话消息(用户输入和助手回复) await chat_service.save_chat_messages(account_id, input_text, response) # 流式返回响应 for token in response: yield token await asyncio.sleep(0.01) except Exception as e: yield f"抱歉,处理您的请求时出现错误: {str(e)}"
-
-
流式生成响应
-
功能描述
- 将智能体的回复内容以流式方式发送给用户。
- 支持按标点符号或固定长度分割响应内容。
-
输入参数
agent
: 聊天智能体实例。chat_service
: 聊天服务实例。account_id
: 用户标识。message
: 用户输入的文本。
-
输出结果
- 以SSE(Server-Sent Events)格式返回流式响应。
-
实现细节
- 逐字符接收智能体的回复内容。
- 当遇到标点符号或字符长度达到20时,发送当前分块内容。
- 发送剩余内容后,添加结束标记
[DONE]
。
-
编码实战
async def generate_stream_response( agent: AgentExecutor, chat_service: ChatService, account_id: str, message: str ) -> AsyncGenerator[str, None]: """生成流式响应""" current_chunk = "" async for token in chat_with_agent(agent, chat_service, account_id, message): current_chunk += token # 当遇到标点符号或空格时,发送当前chunk if token in "。,!?、;:,.!?;: " or len(current_chunk) >= 20: response = JsonData.stream_data(data=current_chunk) yield f"data: {response.model_dump_json()}\n\n" current_chunk = "" await asyncio.sleep(0.01) # 发送剩余的chunk if current_chunk: response = JsonData.stream_data(data=current_chunk) yield f"data: {response.model_dump_json()}\n\n" # 发送结束标记 yield "data: [DONE]\n\n"
-
路由层面处理
chat.py
@router.post("/stream") async def chat_stream( request: ChatRequest, chat_service: ChatService = Depends(ChatService), current_user: Dict[str, Any] = Depends(get_current_user) ): """聊天接口(流式响应)""" # 使用当前用户的 account_id account_id = current_user["account_id"] # 获取智能体 agent = await get_chat_agent(chat_service) return StreamingResponse( generate_stream_response(agent, chat_service, account_id, request.message), media_type="text/event-stream" )
-
AI聊天智能助理之全链路测试综合实战
简介: AI聊天智能助理之全链路测试综合实战
-
回顾需求
- 开发AI聊天智能助理, 基于 Redis 实现聊天历史存储,支持流式响应(Streaming Response)
-
智能对话能力:
-
支持多轮对话,保持上下文连贯性
-
可以理解用户意图并调用合适的工具
-
能够生成对话摘要,帮助理解对话历史
-
-
实时信息获取:
- 可以回答需要实时数据的问题,
- 通过搜索工具获取最新信息,搜索结果会被智能整合到回答中
-
全链路测试实战
- 启动项目
uvicorn app.main:app --reload
- 准备token ,并且配置到HTTP请求头里面
- APIFOX增加请求头
Accept : text/event-stream
- 测试案例一
- 告诉基本当前人员信息(进阶也可以初始化导入用户的历史画像数据)
- 进行基本信息问答,看智能体助理是否有记忆功能
- 测试案例二
- 询问当前当前需要联网信息内容,看大模型是否有调用工具进行处理
- 查看Redis的数据存储:聊天记录和摘要
- 拓展:不用每次都生成摘要,可以配置进阶策略,比如对话轮次阈值(默认5轮)、关键决策点标记、上下文长度超过XXX
- 启动项目
AI智能体心实战之AI在线文档助手
AI在线文档助手需求文档和路由开发实战
-
需求
-
AI在线文档助手,提供文档内容获取、解析和智能总结功能
-
支持多种文档格式(HTML、PDF等),并能够根据用户需求生成不同形式的文档总结。
-
大体流程和聊天助理类似,接下去就会加快速度了哈
-
技术亮点
- 多格式文档支持:HTML、PDF、纯文本等
- 智能文档处理:基于LLM的智能总结和关键点提取
- 流式处理:支持大文件分块处理和流式响应
- 多语言支持:支持多种语言的文档处理和总结
- 备注
- 是否需要鉴权登录,取决文档的来源类型
- 选择网盘里面的相关资源,则需要登录;选择好文件后调用存储接口获取临时访问地址
- 智能体接受的是在线可以访问的地址,即可实现对应的在线文档解析
-
-
编码实战
-
创建 请求文件
doc_schemas.py
from pydantic import BaseModel, HttpUrl from typing import Optional class DocumentRequest(BaseModel): """文档处理请求""" url: HttpUrl # 文档URL summary_type: str = "brief" # 总结类型:brief(简要), detailed(详细), key_points(要点) language: str = "zh" # 输出语言 length: Optional[str] = None # 长度限制 additional_instructions: Optional[str] = None # 额外要求
-
创建业务逻辑处理文件
doc_service.py
from typing import Any, AsyncIterator from agents.doc_agent import create_document_agent, process_document from models.doc_schemas import DocumentRequest from tools.document_tools import DocumentTools import gc class DocumentService:
-
创建路由文件
doc.py
from fastapi import APIRouter, Depends from models.doc_schemas import DocumentRequest from models.json_response import JsonData from services.doc_service import DocumentService from fastapi.responses import StreamingResponse import asyncio router = APIRouter(prefix="/api/document", tags=["文档助手"]) #可以放到service层,也可以放到路由层,或者抽取统一方法 async def generate_stream_response(request: DocumentRequest, doc_service: DocumentService): """生成流式响应数据""" async for chunk in doc_service.process_document_stream(request): response = JsonData.stream_data(data=chunk) yield f"data: {response.model_dump_json()}\n\n" await asyncio.sleep(0.01) yield "data: [DONE]\n\n" @router.post("/stream") async def process_document_stream( request: DocumentRequest, doc_service: DocumentService = Depends(DocumentService) ): """处理文档请求(流式响应)""" return StreamingResponse( generate_stream_response(request, doc_service), media_type="text/event-stream" )
-
AI在线文档助手之文档处理工具集开发
-
需求背景
-
根据用户的需求,智能体提取的文档类型,选择合适的工具进行提取
-
实现文档处理服务,支持多种文档格式的处理(HTML、PDF、文本),提供高效的文档内容提取和清理功能
-
目前开发PDF和HTML类型,可以根据业务需求自己拓展更多
-
-
拓展知识点
-
BeautifulSoup
库-
主要是解析 HTML/XML 内容(
html_content
),生成一个结构化的BeautifulSoup 对象
-
解析器
html.parser
- 是 Python 自带的 HTML 解析器,轻量、无需依赖第三方库。
-
方便地提取和操作 HTML 中的元素
from bs4 import BeautifulSoup # 假设有一段 HTML 内容 html_content = "<html><body><h1>你好,小滴课堂老王</h1></body></html>" # 解析 HTML soup = BeautifulSoup(html_content, 'html.parser') # 提取数据 title = soup.h1.text # 输出:"你好,小滴课堂老王"
-
-
-
编码实战
document_tools.py
from typing import Optional, Dict, Any, Generator import requests from bs4 import BeautifulSoup from langchain.tools import Tool import re from urllib.parse import urlparse import time import io from PyPDF2 import PdfReader from tqdm import tqdm import gc class DocumentTools: """文档处理工具集""" @staticmethod def fetch_document(url: str) -> Dict[str, Any]: """获取文档内容""" try: # 使用流式下载 response = requests.get(url, stream=True, timeout=30) response.raise_for_status() # 解析文档类型 content_type = response.headers.get('content-type', '') print(f"---Content-Type---: {content_type}") if 'text/html' in content_type: return DocumentTools._parse_html(response.text) elif 'application/pdf' in content_type: return DocumentTools._parse_pdf_stream(response) else: return { "title": urlparse(url).path.split('/')[-1], "content": response.text, "type": "text" } except Exception as e: raise Exception(f"获取文档失败: {str(e)}") @staticmethod def _parse_html(html_content: str) -> Dict[str, Any]: """解析HTML文档""" soup = BeautifulSoup(html_content, 'html.parser') # 提取标题 title = soup.title.string if soup.title else "未命名文档" # 提取正文内容 content = "" for p in soup.find_all(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']): content += p.get_text() + "\n" return { "title": title, "content": content.strip(), "type": "html" } @staticmethod def _parse_pdf_stream(response: requests.Response) -> Dict[str, Any]: """流式解析PDF文档""" pass
-
知识点拓展
-
tqdm
-
是 Python 中用于在循环或长时间运行的任务中显示进度条的库
-
主要功能
- 实时进度条:显示当前进度百分比、已用时间、剩余时间、迭代速度等。
- 低开销:对代码性能影响极小。
-
使用
#前面一开始创建项目的时候已经安装了这个依赖 from tqdm import tqdm import time # 在 for 循环外包裹 tqdm() for i in tqdm(range(100)): time.sleep(0.1) # 模拟耗时操作
-
-
PyPDF2
- 是 Python 中一个专门用于处理 PDF 文件的第三方库,支持 PDF 文件的读取、写入、合并、拆分、加密、解密等操作。
- 功能相对基础,但在处理简单 PDF 任务时非常高效。
- 建议
- 对于复杂需求(如表格提取、内容编辑),可以结合其他库(如
pdfplumber
+PyPDF2
)等进行选择 pdfplumber
: 更适合提取文本、表格和可视化元素。- **
PyMuPDF
**: 高性能,支持复杂操作(如 OCR 集成)。
- 对于复杂需求(如表格提取、内容编辑),可以结合其他库(如
-
-
编码实战
@staticmethod def _parse_pdf_stream(response: requests.Response) -> Dict[str, Any]: """流式解析PDF文档 Args: response (requests.Response): 包含PDF文档的响应对象 Returns: Dict[str, Any]: 包含文档标题、内容和类型的字典 """ # 创建内存缓冲区 buffer = io.BytesIO() # 获取文件大小 total_size = int(response.headers.get('content-length', 0)) # 使用tqdm显示下载进度 with tqdm(total=total_size, unit='B', unit_scale=True, desc="下载PDF") as pbar: for chunk in response.iter_content(chunk_size=8192): if chunk: buffer.write(chunk) pbar.update(len(chunk)) # 重置缓冲区位置 buffer.seek(0) # 分块读取PDF内容 content = "" try: # 使用PdfReader替代PdfFileReader pdf_reader = PdfReader(buffer) # 获取文档信息 info = pdf_reader.metadata title = info.title if info and info.title else "PDF文档" # 分块处理PDF页面 total_pages = len(pdf_reader.pages) with tqdm(total=total_pages, desc="处理PDF页面") as pbar: for page_num in range(total_pages): page = pdf_reader.pages[page_num] content += page.extract_text() + "\n" # 每处理10页就清理一次内存 if (page_num + 1) % 10 == 0: content = DocumentTools._clean_text(content) gc.collect() pbar.update(1) except Exception as e: raise ApiException(f"解析PDF失败: {str(e)}") finally: buffer.close() return { "title": title, "content": DocumentTools._clean_text(content), "type": "pdf" } @staticmethod def _clean_text(text: str) -> str: """清理文本内容 Args: text (str): 待清理的文本内容 Returns: str: 清理后的文本内容 """ # 移除多余的空行 text = re.sub(r'\n\s*\n', '\n\n', text) # 移除多余的空格 text = re.sub(r' +', ' ', text) return text.strip() @staticmethod def create_tools() -> list[Tool]: """创建文档处理工具集 Returns: list[Tool]: 文档处理工具列表 """ return [ Tool( name="fetch_document", func=DocumentTools.fetch_document, description="获取并解析在线文档内容" ) ]
AI在线文档助手之文档Agent智能体开发实战
-
业务需求
- 智能体能够分析用户提供的文档,并根据用户需求生成高质量的总结。
- 核心功能:
- 分析文档内容并提取关键要点,根据用户指定的总结类型(简要、详细或要点)生成相应的总结。
- 支持流式输出,确保每个 token 实时返回给用户。
- 也支持处理用户提出的额外要求。
-
编码实战
from langchain.agents import AgentExecutor, create_openai_functions_agent from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.tools import Tool from core.llm import get_default_llm from typing import List, AsyncIterator import asyncio from datetime import datetime def create_document_agent(tools: List[Tool]): """创建文档处理智能体""" # 定义系统提示词 system_prompt = """你是一个专业的文档处理助手。你的任务是分析用户提供的文档,生成高质量的总结。 你需要: 1. 仔细阅读并理解文档内容 2. 根据用户要求的总结类型(简要/详细/要点)生成相应的总结 3. 提取文档的关键要点 4. 确保总结准确、全面、易读 如果用户提供了额外的要求,请尽量满足这些要求。""" # 创建提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ]) # 获取LLM实例,并配置流式输出 llm = get_default_llm() # 创建智能体 agent = create_openai_functions_agent(llm, tools, prompt) # 创建执行器 agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=True ) return agent_executor async def process_document(agent_executor: AgentExecutor, input_text: str) -> AsyncIterator[str]: """处理文档并生成总结""" print(f"正在处理 input_text: {input_text}") async for chunk in agent_executor.astream({"input": input_text}): if "output" in chunk: # 确保每个token都能实时返回 for token in chunk["output"]: yield token await asyncio.sleep(0.01) # 控制输出速度
AI在线文档助手之文档Service层处理实战
-
需求
- 文档处理服务,能够接收用户请求并调用智能体对文档进行分析和总结。
- 核心功能:
- 接收文档请求并获取文档内容。
- 根据文档大小决定是否进行分块处理。
- 使用智能体生成高质量的文档总结,并支持流式输出。
- 处理大文本时,通过内存管理优化性能。
-
编码实战
doc_service.py
from typing import Any, AsyncIterator from agents.doc_agent import create_document_agent, process_document from models.doc_schemas import DocumentRequest from tools.document_tools import DocumentTools import gc class DocumentService: """ 文档处理服务类,负责初始化文档处理所需的工具和智能体,并提供文档处理的接口 """ def __init__(self): # 初始化工具 self.tools = DocumentTools.create_tools() # 创建智能体 self.agent_excutor = create_document_agent(self.tools) async def process_document_stream(self, request: DocumentRequest) -> AsyncIterator[str]: """流式处理文档请求""" try: # 获取文档内容 doc_content = DocumentTools.fetch_document(str(request.url)) # 根据文档内容长度决定是否分割处理 content = doc_content['content'] chunks = [] # 检查内容长度是否超过1,000,000个字符 print(f"--------------len(content): {len(content)}") if len(content) > 1000: # 初始化当前块变量,用于累积不足1,000,000字符的内容 current_chunk = "" # 遍历内容中的每个段落,使用'\n\n'作为分隔符进行分割 for paragraph in content.split('\n\n'): # 如果当前块加上新段落后超过1,000,000字符,且当前块非空 if len(current_chunk) + len(paragraph) > 1000000 and current_chunk: # 将当前块添加到块列表中,并开始新的块 chunks.append(current_chunk) current_chunk = paragraph else: # 否则,将当前段落添加到当前块中 current_chunk = f"{current_chunk}\n\n{paragraph}" if current_chunk else paragraph # 如果循环结束后当前块非空,将其添加到块列表中 if current_chunk: chunks.append(current_chunk) else: # 如果内容长度不超过1,000,000字符,直接将内容作为唯一一个块 chunks = [content] # 处理每个文档片段 for i, chunk in enumerate(chunks): # 构建输入文本 input_text = self._build_input_text( doc_content['title'], chunk, request.summary_type, request.language, request.length or '无限制', request.additional_instructions or '无' ) # 异步处理文档并生成响应 async for response_chunk in process_document(self.agent_excutor, input_text): yield response_chunk # 如果不是最后一个片段,插入分隔符 if i < len(chunks) - 1: yield "\n\n--- 下一部分 ---\n\n" # 清理内存 gc.collect() except Exception as e: # 错误处理 yield f"处理文档时发生错误: {str(e)}" def _build_input_text(self, title: str, content: str, summary_type: str, language: str, length: str, additional_instructions: str) -> str: """构建输入文本""" return f""" 文档标题: {title} 文档内容: {content} 总结类型: {summary_type} 输出语言: {language} 最大长度: {length} 额外要求: {additional_instructions} """
AI在线文档助手之全链路测试综合实战
-
全链路测试,回顾需求
- AI在线文档助手,提供文档内容获取、解析和智能总结功能
- 支持多种文档格式(HTML、PDF等),并能够根据用户需求生成不同形式的文档总结。
- 注意
- 不同智能体之间,实现方式也有多样,企业中开发遵循当前团队即可
- 不用局限一定的格式,智能体工程化这个不像传统的后端和前端,新技术刚出来,还在持续发展中
- 代码有一定的优化空间和逻辑修复,大家可以进一步跟进自己的需求进行拓展,比如doc文档、其他类型提取等
- 异常打印堆栈信息:
logger.exception("获取文档失败")
-
链路测试一
- 选择网盘的文件,可以调用下载接口,拿到临时文件访问地址
- 也可以直接通过minio获取临时的访问地址
-
链路测试二
- 选择在线网站,非动态网站即可,HTML结尾的静态网站才可以提取
- 比如
AI智能体中心实战之AI网盘查询助手
LLM大模型如何访问MySQL业务数据库
-
需求背景: 为什么需要 SQL Agent?
-
LangChain如何整合大模型操作业务数据库
-
未来场景下,用户更倾向于用自然语言提问(如“销售额最高的产品是什么?”),而非编写复杂 SQL。
-
非技术人员(如产品经理、业务人员)无需学习 SQL 即可查询数据库。
-
包括开发对应的业务智能体,实现数据库的查询和操作
-
-
什么是
create_sql_agent
-
创建能通过自然语言与SQL数据库交互的AI智能体,自动生成/执行SQL查询并解析结果
-
核心能力
功能 说明 自然语言转 SQL 将用户问题(如“统计每个地区的销量”)转化为 SQL 查询语句。 安全执行查询 连接数据库执行 SQL,默认只读模式防止数据误修改。 结果解析与自然语言 将数据库返回的原始数据(如 [1500, 2000]
)转换为用户友好的回答(如“总销售额为 $3500”)。错误处理与重试 自动修正 SQL 语法错误或逻辑问题(如字段名拼写错误)。 -
应用场景
- 非技术人员通过对话查询数据库
- 数据分析,自动生成复杂报表
- 结合业务系统的问答机器人
-
核心语法
- 相关包
from langchain_community.agent_toolkits.sql.base import create_sql_agent
agent = create_sql_agent( llm=ChatOpenAI(temperature=0, model="gpt-5"), # 必需:大模型 toolkit=SQLDatabaseToolkit(db=db, llm=llm), # 必需:数据库工具包 agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION, # Agent类型 verbose=True, # 显示详细执行过程 prefix="""你是一个专业的MySQL专家...""", # 自定义提示前缀 suffix="""请始终检查你的查询结果...""" # 自定义提示后缀 )
- 相关包
-
-
什么是
SQLDatabaseToolkit
- 是 LangChain 中专门用于 连接 SQL 数据库并集成相关操作工具 的模块包。
- 本质上是一个容器,将数据库连接与多个预构建的 SQL 操作工具打包整合
-
核心语法
- 相关包
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
#使用 SQLDatabase.from_uri 连接数据库,自动读取表结构。 db = SQLDatabase.from_uri( database_uri="数据库连接信息", include_tables=['account_file', 'storage'], custom_table_info={"account": "查询账号相关的表"}) #SQLDatabaseToolkit 封装了查询执行、表结构查看等底层操作。 toolkit = SQLDatabaseToolkit(db=db, llm=ChatOpenAI())
- 相关包
-
案例讲解(伪代码)
# 1. 连接数据库 db = SQLDatabase.from_uri("mysql+pymysql://root:xdclass.net168@39.108.115.128:3306/dcloud_aipan") # 2. 初始化大模型 llm = ChatOpenAI( temperature=0, model="gpt-4", openai_api_key="sk-your-key" ) # 3. 创建工具包 toolkit = SQLDatabaseToolkit(db=db, llm=llm) # 4. 创建SQL Agent agent = create_sql_agent( llm=llm, toolkit=toolkit, verbose=True, agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION, handle_parsing_errors=True # 自动处理解析错误 ) # 5. 执行自然语言查询 questions = [ "有多少位客户?", "显示销售额前5的产品及其总销售额" ] for question in questions: print(f"\n问题:{question}") response = agent.invoke({"input": question}) print(f"答案:{response['output']}")
SQL Agent智能体操作MySQL数据库实战
-
需求
- 实现了一个基于SQL Agent的智能数据库查询系统
- 用户可以通过自然语言提问的方式,获取网盘数据库中的相关信息。
- 系统能够自动解析用户的自然语言问题,生成对应的SQL查询语句,并返回查询结果
-
编码实战
import os from langchain_community.agent_toolkits.sql.base import create_sql_agent from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit from langchain_community.utilities import SQLDatabase from langchain_openai import ChatOpenAI from langchain.agents.agent_types import AgentType # 设置OpenAI API密钥 llm = ChatOpenAI( model_name="qwen-plus", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key="sk-005c3c25f6d042848b29d75f2f020f08", temperature=0 ) # 1. 连接数据库 db = SQLDatabase.from_uri( f"mysql+pymysql://root:xdclass.net168@39.108.115.28:3306/dcloud_aipan", #include_tables=['account_file', 'storage','account'], custom_table_info={"account": "查询账号相关的表","account_file": "查询文件夹和文件内容相关的表","storage": "查询存储相关就查询这个表"}, # 自定义表描述 ) # 2. 初始化工具包 toolkit = SQLDatabaseToolkit(db=db, llm=llm) print(f"包含工具数量:{len(toolkit.get_tools())}") print("工具列表:", [tool.name for tool in toolkit.get_tools()]) # 3. 创建SQL Agent agent = create_sql_agent( llm=llm, toolkit=toolkit, prefix="""你是一个专业的网盘数据库查询助手。请遵循以下规则: 1. 所有回答必须使用中文 2. 回答要简洁明了,避免技术术语 3. 如果遇到查询错误,请尝试用更简单的方式重新组织查询 4. 对于数据查询结果,请用通俗易懂的语言解释 5. 如果用户的问题不明确,请主动询问更多细节""", verbose=True, agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION, handle_parsing_errors=True ) # 4. 定义查询函数 def ask_question(question): try: print(f"\n正在处理您的问题:{question}") response = agent.invoke({"input": question}) return response["output"] except Exception as e: print(f"系统提示:处理查询时遇到技术问题") return "抱歉,系统暂时无法处理您的请求。请尝试用更简单的方式描述您的问题,或者稍后再试。" # 5. 测试不同问题 questions = [ "数据库有多少个表", "有多少个账号", "有多少个文件夹" ] for q in questions: answer = ask_question(q) print(f"\n问题:{q}") print(f"回答:{answer}") print("-" * 50)
-
create_sql_agent
相关使用注意事项-
提升生成准确率
- 添加表注释:在数据库表中添加字段描述(如通过 COMMENT),帮助 LLM 理解字段含义
-
安全限制, 只读模式
db = SQLDatabase.from_uri( "sqlite:///sales.db", include_tables=["sales", "products"], # 限制可访问的表 custom_table_info={"sales": "只允许查询,禁止修改"}, # 自定义表描述 view_support=False # 禁止访问视图 )
-
生成的 SQL 包含不存在的字段
- 解决方案:检查 SQLDatabaseToolkit 是否包含最新表结构,在提示中明确字段范围
CUSTOM_PROMPT = """你只能使用以下字段: - products: product_id, product_name, category, price - sales: sale_id, product_id, sale_date, quantity 问题:{question}"""
-
复杂 JOIN 查询失败
- 解决方案:提供few_shot案例
examples = [ ("如何查询产品对应的销售记录?", "SELECT * FROM sales JOIN products ON sales.product_id = products.product_id") ]
-
其他
- 数据库权限控制:生产环境务必限制为只读。
- 提示工程优化:通过添加表结构描述、示例数据提升准确率。
- 错误防御:处理 SQL 注入、无效查询等边界情况
-
AI网盘智答Agent需求说明和开发实战
-
需求说明
-
实现的智能网盘查询系统,通过自然语言处理技术,让用户能够使用自然语言查询网盘中的文件信息。
-
核心功能
- 文件查询功能
- 查看文件列表、查看文件详细信息、查看文件夹内容
- 文件统计功能
- 统计文件数量、统计文件类型分布、统计存储空间使用情况
- 存储空间管理
- 查看已使用空间、查看剩余空间、查看空间使用率
- 文件查询功能
-
备注
- 前面多个智能体采用流式响应输出,大家基本掌握了
- AI网盘智答智能体采用一次性输出的方式进行,
- 个人网盘数据库读取需要鉴权,和聊天助理智能体类似
- SQL Agent智能体的回复准确性,和大模型参数、能力、提示词工程强相关,也包括用户的提问内容
-
业务流程说明:
- 用户请求: 用户发送自然语言查询、身份验证、获取用户ID
- 参数处理:处理查询参数、注入用户ID
- 创建代理:初始化SQL代理、准备查询环境
- 执行查询:执行数据库查询、获取查询结果
- 返回结果:返回JSON格式响应
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ 用户请求 │────▶│ 身份验证 │────▶│ 参数处理 │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ 返回结果 │◀────│ 执行查询 │◀────│ 创建代理 │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘
-
-
编码实战
-
创建
pan_schemas.py
from pydantic import BaseModel from typing import Optional, Dict, Any, List from datetime import datetime class PanQueryRequest(BaseModel): """网盘查询请求模型""" account_id: Optional[int] = None # 从token中获取,请求中可选 query: str class FileInfo(BaseModel): """文件信息模型""" id: int # account_file表的ID file_id: int # 实际存储的文件ID file_name: str file_type: str file_suffix: str file_size: int gmt_create: datetime gmt_modified: datetime class StorageInfo(BaseModel): """存储空间信息""" used_size: int total_size: int used_percentage: float class FileStatistics(BaseModel): """文件统计信息模型""" total_files: int total_size: int file_types: Dict[str, int] recent_files: List[FileInfo] class PanQueryResponse(BaseModel): """网盘查询响应模型""" type: str data: Dict[str, Any]
-
创建路由文件
from fastapi import APIRouter, Depends from models.pan_schemas import PanQueryRequest from agents.pan_agent import process_pan_query from core.auth import get_current_user # 创建路由 router = APIRouter( prefix="/api/pan", tags=["网盘查询"], ) @router.post("/query") async def query_pan( request: PanQueryRequest, current_user: dict = Depends(get_current_user) ): """网盘查询接口""" # 设置用户ID request.account_id = current_user["account_id"] # 调用代理处理请求 return await process_pan_query(request)
-
AI网盘智答Agent之MySQL数据安全隔离实战
-
需求
- 问题一:大模型操作Mysql数据库如何做到个人数据隔离,不会查询到别人的数据?
- 问题二:大模型响应给调用方的内容格式如何限制?
- 问题三:大模型如何更精确的执行用户的查询需求?
-
解决方案:【!!!!提示词工程!!!】
你是一个智能网盘助手,专门用于查询用户的网盘文件信息。你只能执行查询操作,不能执行任何修改数据的操作。 重要警告: 1. 你绝对不能生成或编造任何数据 2. 你只能返回实际查询到的数据 3. 如果查询没有结果,必须返回空结果 4. 任何生成或编造数据的行为都是严重错误 5. 你只能使用数据库中的实际数据 6. 不能对查询结果进行任何修改或补充 7. 不能生成示例数据或占位数据 8. 不能假设或推测数据 9. 不能使用模板或示例数据 10. 不能对数据进行任何形式的加工或美化 数据库表结构说明: - account_file: 用户文件表 - id: 文件ID(account_file表的主键) - account_id: 用户ID - is_dir: 是否为文件夹(0不是,1是) - parent_id: 上层文件夹ID(顶层为0) - file_id: 实际存储的文件ID - file_name: 文件名称 - file_type: 文件类型(common/compress/excel/word/pdf/txt/img/audio/video/ppt/code/csv) - file_suffix: 文件后缀名 - file_size: 文件大小(字节) - del: 是否删除(0未删除,1已删除) - del_time: 删除时间 - gmt_create: 创建时间 - gmt_modified: 更新时间 - storage: 存储信息表 - id: 记录ID - account_id: 用户ID - used_size: 已使用空间(字节) - total_size: 总空间(字节) - gmt_create: 创建时间 - gmt_modified: 更新时间 你可以处理以下类型的查询请求: 1. 文件查询 - 查看我的文件列表 - 搜索特定文件 - 查看文件详细信息 - 查看文件夹内容(参考语法:select ) - 查看最近修改的文件 2. 文件统计 - 统计文件数量 - 统计文件类型分布 - 统计存储空间使用情况 - 查看最近上传的文件 3. 存储空间 - 查看已使用空间 - 查看剩余空间 - 查看空间使用率 重要限制: 1. 你只能执行 SELECT 查询,不能执行任何修改数据的操作 2. 所有查询必须包含 account_id 条件,确保数据安全 3. 不能执行以下操作: - 删除文件 - 修改文件 - 创建文件 - 移动文件 - 重命名文件 - 清空回收站 - 修改存储空间 4. 如果用户请求执行任何修改操作,请礼貌地拒绝并说明原因 5. 如果查询没有结果,必须返回空结果,不能生成示例数据 6. 绝对不能生成或编造任何数据 7. 只能返回实际查询到的数据 8. 不能对数据进行任何形式的加工或美化 处理请求时请注意: 1. 必须使用 account_id 过滤用户数据,确保数据安全 2. 对于文件夹查询,使用 is_dir=1 和 parent_id 3. 对于文件类型查询,使用 file_type 字段 4. 对于模糊查询,使用 LIKE 和通配符 5. 对于时间相关的查询,使用 gmt_create 和 gmt_modified 6. 对于空间统计,使用 storage 表 7. 结果要简洁明了,突出重点 8. 所有查询必须包含 account_id 条件 9. 查询文件信息时,必须返回 account_file 表的 id 和 file_id 10. 所有响应必须返回 JSON 格式的数据,包含完整的文件信息 11. 如果查询没有结果,返回空数组或空对象,不要生成示例数据 12. 绝对不能生成或编造任何数据 响应格式必须符合以下模型结构: 1. 文件列表响应: {{ "type": "file_list", "data": List[FileInfo] # FileInfo包含id, file_id, file_name, file_type, file_suffix, file_size, gmt_create, gmt_modified }} 2. 存储空间信息响应: {{ "type": "storage_info", "data": StorageInfo # StorageInfo包含used_size, total_size, used_percentage }} 3. 文件统计信息响应: {{ "type": "file_statistics", "data": FileStatistics # FileStatistics包含total_files, total_size, file_types, recent_files }} 请根据用户的问题,使用 SQL 查询来获取信息,并返回符合上述格式的 JSON 数据。 重要警告:你绝对不能生成或编造任何数据,只能返回实际查询到的数据。任何生成或编造数据的行为都是严重错误
-
调用接口响应示例说明
-
存储相关
{ "code": 0, "data": { "type": "storage_info", "data": { "used_size": 2000, "total_size": 104857600, "used_percentage": 0.0019073486328125 } }, "msg": "", "type": "text" }
-
文件统计相关
{ "code": 0, "data": { "type": "file_statistics", "data": { "total_files": 2, "total_size": 2000 } }, "msg": "", "type": "text" }
-
文件查询相关
{ "code": 0, "data": { "type": "file_list", "data": [ { "id": 1889313223002312705, "file_id": 1889313222733877250, "file_name": "344144.png", "file_type": "IMG", "file_suffix": "png", "file_size": 1000, "gmt_create": "2025-02-11T13:59:08", "gmt_modified": "2025-02-11T14:02:15" }, { "id": 1889313269047382017, "file_id": 1889313268749586433, "file_name": "344144_1739282359741.png", "file_type": "IMG", "file_suffix": "png", "file_size": 1000, "gmt_create": "2025-02-11T13:59:19", "gmt_modified": "2025-02-11T14:02:15" } ] }, "msg": "", "type": "text" }
-
AI网盘智答Agent之SQLAgent智能体开发
-
需求
-
创建智能网盘查询agent的函数,它将自然语言查询转换为SQL查询,并执行数据库操作
-
核心功能
-
创建数据库连接
-
初始化LLM(大语言模型)
-
配置SQL工具包
-
设置代理提示模板
-
创建SQL代理实例
-
-
-
编码实战(部分对象和代码可以抽取出来-作业自己完成)
from langchain_community.agent_toolkits.sql.base import create_sql_agent from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit from langchain_community.utilities import SQLDatabase from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from typing import Dict, Any, List, Union import json from core.llm import get_default_llm from core.config import settings from models.json_response import JsonData from models.pan_schemas import PanQueryRequest import logging from langchain.agents.agent_types import AgentType logger = logging.getLogger(__name__) def create_pan_agent() -> Any: """创建网盘查询代理""" # 创建数据库连接,只读模式 db = SQLDatabase.from_uri( f"mysql+pymysql://{settings.MYSQL_USER}:{settings.MYSQL_PASSWORD}@{settings.MYSQL_HOST}:{settings.MYSQL_PORT}/{settings.MYSQL_DATABASE}", include_tables=['account_file', 'storage'] ) # 创建 LLM llm = get_default_llm() # 创建 SQL 工具包,只包含查询工具 toolkit = SQLDatabaseToolkit(db=db, llm=llm) # 创建提示模板 prompt = ChatPromptTemplate.from_messages([ ("system", """你是一个智能网盘助手,专门用于查询用户的网盘文件信息。你只能执行查询操作,不能执行任何修改数据的操作。 重要警告: 1. 你绝对不能生成或编造任何数据 2. 你只能返回实际查询到的数据 3. 如果查询没有结果,必须返回空结果 4. 任何生成或编造数据的行为都是严重错误 5. 你只能使用数据库中的实际数据 6. 不能对查询结果进行任何修改或补充 7. 不能生成示例数据或占位数据 8. 不能假设或推测数据 9. 不能使用模板或示例数据 10. 不能对数据进行任何形式的加工或美化 数据库表结构说明: - account_file: 用户文件表 - id: 文件ID(account_file表的主键) - account_id: 用户ID - is_dir: 是否为文件夹(0不是,1是) - parent_id: 上层文件夹ID(顶层为0) - file_id: 实际存储的文件ID - file_name: 文件名称 - file_type: 文件类型(common/compress/excel/word/pdf/txt/img/audio/video/ppt/code/csv) - file_suffix: 文件后缀名 - file_size: 文件大小(字节) - del: 是否删除(0未删除,1已删除) - del_time: 删除时间 - gmt_create: 创建时间 - gmt_modified: 更新时间 - storage: 存储信息表 - id: 记录ID - account_id: 用户ID - used_size: 已使用空间(字节) - total_size: 总空间(字节) - gmt_create: 创建时间 - gmt_modified: 更新时间 你可以处理以下类型的查询请求: 1. 文件查询 - 查看我的文件列表 - 搜索特定文件 - 查看文件详细信息 - 查看文件夹内容 - 查看最近修改的文件 2. 文件统计 - 统计文件数量 - 统计文件类型分布 - 统计存储空间使用情况 - 查看最近上传的文件 3. 存储空间 - 查看已使用空间 - 查看剩余空间 - 查看空间使用率 重要限制: 1. 你只能执行 SELECT 查询,不能执行任何修改数据的操作 2. 所有查询必须包含 account_id 条件,确保数据安全 3. 不能执行以下操作: - 删除文件 - 修改文件 - 创建文件 - 移动文件 - 重命名文件 - 清空回收站 - 修改存储空间 4. 如果用户请求执行任何修改操作,请礼貌地拒绝并说明原因 5. 如果查询没有结果,必须返回空结果,不能生成示例数据 6. 绝对不能生成或编造任何数据 7. 只能返回实际查询到的数据 8. 不能对数据进行任何形式的加工或美化 处理请求时请注意: 1. 必须使用 account_id 过滤用户数据,确保数据安全 2. 对于文件夹查询,使用 is_dir=1 和 parent_id 3. 对于文件类型查询,使用 file_type 字段 4. 对于模糊查询,使用 LIKE 和通配符 5. 对于时间相关的查询,使用 gmt_create 和 gmt_modified 6. 对于空间统计,使用 storage 表 7. 结果要简洁明了,突出重点 8. 所有查询必须包含 account_id 条件 9. 查询文件信息时,必须返回 account_file 表的 id 和 file_id 10. 所有响应必须返回 JSON 格式的数据,包含完整的文件信息 11. 如果查询没有结果,返回空数组或空对象,不要生成示例数据 12. 绝对不能生成或编造任何数据 响应格式必须符合以下模型结构: 1. 文件列表响应: {{ "type": "file_list", "data": List[FileInfo] # FileInfo包含id, file_id, file_name, file_type, file_suffix, file_size, gmt_create, gmt_modified }} 2. 存储空间信息响应: {{ "type": "storage_info", "data": StorageInfo # StorageInfo包含used_size, total_size, used_percentage }} 3. 文件统计信息响应: {{ "type": "file_statistics", "data": FileStatistics # FileStatistics包含total_files, total_size, file_types, recent_files }} 请根据用户的问题,使用 SQL 查询来获取信息,并返回符合上述格式的 JSON 数据。 重要警告:你绝对不能生成或编造任何数据,只能返回实际查询到的数据。任何生成或编造数据的行为都是严重错误。"""), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ]) # 创建 SQL 代理 agent = create_sql_agent( llm=llm, toolkit=toolkit, verbose=True, agent_type="openai-tools", prompt=prompt, handle_parsing_errors=True, max_iterations=10 ) return agent
AI网盘智答Agent之非流式响应和数据提取
-
需求
-
开发处理网盘查询请求的核心函数,负责协调整个查询流程,从接收请求到返回结果。
-
解析代理输出,处理不同类型的结果,返回标准化的 JsonData 响应
-
核心功能
-
接收查询请求
-
创建查询代理
-
执行查询处理
-
格式化响应结果
-
错误处理
-
-
-
编码实战
async def process_pan_query(request: PanQueryRequest) -> JsonData: """处理网盘查询请求 Args: request: 查询请求对象,包含用户ID和查询内容 Returns: JsonData: 查询结果,包含文件列表、存储信息等 """ agent = create_pan_agent() # 构建查询输入 query_input = f"用户ID为 {request.account_id} 的 {request.query}" # 获取代理输出 response = await agent.ainvoke({"input": query_input}) if "output" not in response: return JsonData.error("未获取到有效响应") output = response["output"] # 解析输出数据 try: data = json.loads(output) if isinstance(output, str) else output if isinstance(data, list): return JsonData.success({ "type": "file_list", "data": data }) return JsonData.success({ "type": data.get("type", "text"), "data": data.get("data", data) }) except json.JSONDecodeError: return JsonData.success({ "type": "text", "data": {"content": str(output)} })
-
主路由配置
app.include_router(pan.router)
AI网盘智答Agent之全链路测试实战+面试题
-
需求回顾
- 实现的智能网盘查询系统,通过自然语言处理技术,让用户能够使用自然语言查询网盘中的文件信息。
- 核心功能
- 文件查询功能
- 查看文件列表、查看文件详细信息、查看文件夹内容
- 文件统计功能
- 统计文件数量、统计文件类型分布、统计存储空间使用情况
- 存储空间管理
- 查看已使用空间、查看剩余空间、查看空间使用率
- 文件查询功能
- bug修复
- PanQueryRequest入参的AccountID
- create_pan_agent入参
- agent_type="openai-tools"
- JsonData.success({"type": data.get("type"),"data": data.get("data")})
-
全链路测试
-
问题一: 我的图片文件有哪些
-
问题二:图片文件有多少,大小是多少
-
问题三:存储空间使用情况
-
-
注意【面试题】
-
SQL Agent智能体的回复准确性,和大模型参数、温度、能力、提示词工程强相关,也包括用户的提问内容
-
比如更详细的表结构Schema说明,提问改写,提供FewShot样例等,都是可以提高准确度
-
增强 Schema 理解
# 在提示词中添加增强元数据描述 prefix = """ 你连接的数据库包含以下关键表: [Customers] 客户表(重要字段:CustomerId, FirstName, LastName, Country) [Invoices] 发票表(与 Customers 通过 CustomerId 关联) 优先使用 JOIN 代替子查询,注意 Country 字段存储的是国家全称 """
-
Few-Shot Learning 通过示例引导生成模式
examples = [ { "input": "法国客户数量是多少?", "query": "SELECT COUNT(*) FROM Customers WHERE Country = 'France'" }, { "input": "显示最新的5张发票", "query": "SELECT * FROM Invoices ORDER BY InvoiceDate DESC LIMIT 5" } ]
-
-
Java业务系统整合Python大模型服务
Java业务系统整合Python大模型服务讲解
-
需求
- 企业在业务+AI项目中采用多语言技术栈,多数是 Java+Python
- 主要是基于技术生态、业务需求和技术特性的考虑,不同编程语言的技术优势互补
- Java特性:
- 企业级开发:Spring生态(Spring Boot/Cloud)的成熟解决方案(事务管理、分布式架构)
- 具有良好的面向对象设计、丰富的类库、强大的并发处理能力
- 成熟的开发工具链,适合处理复杂的业务逻辑、事务管理、数据持久化等任务。
- Python特性:
- 在数据科学、机器学习、人工智能等领域具有明显的优势。
- Python 拥有丰富的机器学习和深度学习库,如 TensorFlow、PyTorch 等
- 能够方便地进行数据处理、模型训练和算法实现
- Java特性:
-
参考常见系统架构设计
-
采用前后端分离模式,Java系统作为主业务处理平台
-
Python服务作为AI能力提供方,通过REST API进行跨语言通信
-
在两个系统之间传输的格式,通常使用 JSON 格式
+-------------------+ HTTP/REST +-------------------+ | SpringBoot业务系统 | <--------------> | Python大模型服务 | | (Java/Kotlin) | JSON数据交互 | (FastAPI) | +-------------------+ +-------------------+
-
SpringBoot 业务系统可以作为客户端,向 Python 大模型服务发送 HTTP 请求,传递相关的业务数据,如用户输入的文本等。
-
Python 大模型服务接收到请求后,进行相处理,利用大模型的能力生成结果,将结果通过 HTTP 响应给 SpringBoot 业务系统。
-
在 SpringBoot 中,可以使用 RestTemplate 或 WebClient 等工具来实现 HTTP 请求的发送和接收。
-
在 Python 端使用合适的 Web 框架,如 Flask 或 FastAPI 等来搭建大模型服务接口。
-
-
AI智能化网盘业务架构图
- 方式一:可以前端请求到Python模型服务
- 方式二:可以前端经过Java后端,再由Java请求到模型服务
- 具体哪个方式都可以,看公司业务需求
- 本章是主要讲解方式二的技术知识点,课程最终采用方式一的进行,
Java调用大模型服务问题和响应式编程
-
需求背景:Java调用大模型服务的典型特征
- 调用链路
[Java Web 前端] --> [Spring Boot API] --> [Python 大模型服务(FastAPI)]
- 高延迟
- 大模型推理耗时较长(如 GPT生成文本可能需要 2~10 秒)
- 同步阻塞调用会占用线程资源,导致系统吞吐量下降
- 流式输出
- 部分大模型支持流式返回结果(逐 Token 生成)
- 传统 HTTP 请求-响应模式无法实时获取中间结果
- 调用链路
-
解决方案:Java响应式编程
-
非阻塞调用优化资源利用
- 1 个事件循环线程可处理 数千个并发请求
- 线程资源消耗减少 80%+(对比线程池模式)
// 传统同步调用(阻塞线程) @GetMapping("/chat/sync") public String generateChatSync(String content) { // 线程在此阻塞等待 Python 响应 return restTemplate.postForObject(pythonUrl, content, String.class); } // 响应式调用(非阻塞) @GetMapping("/chat/async") public Mono<String> generateChatAsync(String content) { return webClient.post() .uri(pythonUrl) .bodyValue(content) .retrieve() .bodyToMono(String.class); // 不阻塞线程 }
-
流式处理大模型输出
- 当 Python 服务返回逐段生成的文本时,前端可以实时显示生成过程。
// 处理流式响应(如 SSE) @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamNotes(String prompt) { return webClient.post() .uri("/python/stream-generate") .bodyValue(prompt) .retrieve() .bodyToFlux(String.class) // 接收流式数据 .map(chunk -> "部分结果: " + chunk) .onBackpressureBuffer(50); // 控制处理速度 }
-
性能对比数据:在 4 核 8GB 云服务器上测试(Python 服务部署独立实例)
场景 同步阻塞方式 Mono/Flux 方式 提升效果 1000 并发请求 QPS 82 460 5.6x 平均响应时间 3200ms 180ms 94%↓ CPU 使用率 95% 40% 58%↓ 内存占用 2.1GB 860MB 59%↓
-
-
响应式编程概念:什么是背压(Backpressure)
-
背压是响应式编程中的一个概念,用于处理数据生产者和消费者速度不匹配的问题。
-
当生产者产生数据的速度超过消费者处理的速度时,会导致数据积压,最终可能引起内存溢出或系统崩溃。
-
背压机制就是让消费者能够通知生产者调整数据发送的速率,从而避免这种情况。
-
生活中的例子
- 比如水龙头和桶,如果水开得太大,桶装不下,水就会溢出。
- 这时候如果桶能告诉水龙头关小一点,就是背压的作用
-
背压就像给数据流装了一个智能阀门,根据系统的消化能力动态调节流量,支持多个策略
策略 现实类比 适用场景 缓冲(Buffer) 用更大的桶暂存 允许短暂突发流量 丢弃最新(Drop Latest) 丢掉新出水 实时性要求高的场景 丢弃最旧(Drop Oldest) 替换掉旧的水 需要最新数据的场景 限速(Rate Limit) 让水管慢点出水 稳定处理能力场景
-
-
什么是Flux 和 Mono
- Flux 和 Mono 是 Reactor 响应式编程框架 中的核心类(Spring WebFlux 基于 Reactor 实现),用于表示异步数据流。
- 它们是响应式编程的基础,专为处理非阻塞、背压(Backpressure) 数据流设计。
特性 Flux Mono 数据元素数量 0~N 个元素(多值流) 0~1 个元素(单值流) 典型场景 分页查询、实时数据流、集合处理
多个元素(列表、流式数据)HTTP 请求响应、数据库单条记录查询
单个元素(对象、Optional)操作符支持 支持所有流操作符 支持单值操作符(如 map
,flatMap
)订阅行为 持续推送数据直到完成或错误 推送单个数据或立即完成 背压处理 必须处理背压 无需显式处理背压 -
何时不该使用 Flux/Mono?
- 简单 CRUD 应用:低并发场景下,同步代码更易维护
- CPU 密集型任务:响应式模型无法提升计算性能
- 已有阻塞代码库:混用阻塞/非阻塞可能导致线程饥饿
-
案例语法说明
- 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
- 直接创建
// 创建 Flux Flux<String> flux1 = Flux.just("A", "B", "C"); // 固定元素 Flux<Integer> flux2 = Flux.range(1, 5); // 数字序列 Flux<Long> flux3 = Flux.interval(Duration.ofSeconds(1)); // 定时生成 Flux<Integer> flux = Flux.range(1, 10); //.filter(n -> n % 2 == 0) //.map(n -> n * 10); //测试 flux.subscribe(System.out::println); // 创建 Mono Mono<String> mono1 = Mono.just("Hello"); // 单值 Mono<Throwable> mono3 = Mono.error(new RuntimeException("Error")); // 错误信号
- 从集合/数组转换
/** * 使用Reactor的Flux从不同数据源创建响应式流 * 本方法展示两种创建Flux的常用方式: * 1. 从List集合创建Flux流 * 2. 从数组创建Flux流 * 无参数 * 无返回值 */ public void test(){ // 从List集合创建Flux并订阅消费 List<String> list = Arrays.asList("Java", "Python"); Flux<String> fluxFromList = Flux.fromIterable(list); fluxFromList.subscribe(System.out::println); // 从数组创建Flux并订阅消费 String[] array = {"Apple", "Banana"}; Flux<String> fluxFromArray = Flux.fromArray(array); fluxFromArray.subscribe(System.out::println); }
- 整合WebClient
Mono<User> userMono = webClient.get() .uri("/users/{id}", 123) .accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus(HttpStatusCode::is4xxClientError, response -> Mono.error(new ClientException("Client error"))) .bodyToMono(User.class);
Java响应式编程核心技术之WebClient
-
需求背景
-
传统同步请求的痛点
// 传统RestTemplate示例(同步阻塞) RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); // 阻塞当前线程
-
存在的问题:
- 线程资源浪费(等待IO时线程被阻塞)
- 扩展性差(高并发场景性能骤降)
- 响应式编程需求(实时数据流处理)
-
-
WebClient 响应式 HTTP 客户端
-
核心特性
- 非阻塞 I/O:基于 Reactor Netty 实现异步通信
- 函数式 API:链式调用风格
- 流式处理:支持 Server-Sent Events (SSE) 和 Streaming JSON
- 与 Spring 生态集成:自动配置、指标采集、异常处理
-
使用场景
- 微服务间通信,第三方API调用
- 响应式数据聚合,大文件流式传输
-
与 RestTemplate 对比
特性 WebClient RestTemplate 编程模型 响应式(Reactive) 同步阻塞 并发处理 基于事件循环(Event Loop) 线程池阻塞 资源消耗 低(少量固定线程) 高(线程池扩容) 适用场景 高并发、低延迟 传统 CRUD 应用
-
-
案例实战
- 基础语法
// 创建实例 WebClient client = WebClient.builder() .baseUrl("https://api.example.com") .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .build(); // GET 请求 Mono<User> userMono = client.get() .uri("/users/{id}", 1) .retrieve() .bodyToMono(User.class); // POST 请求 Mono<ResponseEntity<Void>> response = client.post() .uri("/users") .bodyValue(new User("Alice", 30)) .retrieve() .toBodilessEntity(); // 处理流式响应 Flux<StockPrice> prices = client.get() .uri("/stocks/stream") .accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(StockPrice.class);
- 综合案例实战
@Test public void testWebClient() { // 创建请求体 Map<String, Object> body = new HashMap<>(); body.put("url", "https://www.runoob.com/python3/python3-function.html"); body.put("summary_type", "简洁点"); body.put("language", "中文"); String requestBodyJson = JsonUtil.obj2Json(body); log.info("请求参数: {}", requestBodyJson); // 创建WebClient WebClient webClient = WebClient.builder() .baseUrl("http://localhost:8000") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE) .build(); // 发送POST请求并处理流式响应 Flux<String> response = webClient.post() // 设置目标API端点 .uri("/api/document/stream") // 设置JSON格式请求体(自动序列化) .bodyValue(requestBodyJson) // 发送请求并获取响应 .retrieve() // 将响应体转换为持续接收字符串的流式对象 .bodyToFlux(String.class); // 处理流式响应 response.subscribe( chunk -> { log.info("收到数据块: {}", chunk); }, error -> { log.error("发生错误: {}", error.getMessage()); }, () -> { log.info("流式响应完成"); } ); // 等待响应完成,设置较长的超时时间 response.blockLast(Duration.ofSeconds(60)); }
网盘SpringBoot项目整合模型服务实战
-
需求
- AI智能化网盘项目整合WebClient,流式调用Python模型服务
- 使用方式和上述案例场景一样,直接转移代码过来即可
- 最终前端调用Spring Boot,再调用Python 实现全链路流式响应
- 测试接口:AI聊天智能体(需要JWT校验)
-
编码实战
-
文件增加配置
stream: base-url: http://localhost:8000 chat-stream-path: /api/chat/stream timeout: connect: 30s response: 5m read: 5m write: 5m pool: max-connections: 100 max-idle-time: 5m max-life-time: 10m pending-acquire-timeout: 30s evict-in-background: 120s
-
新增
WebClientConfig
配置类@Data @Configuration @ConfigurationProperties(prefix = "stream") public class WebClientConfig { private String baseUrl; private String chatStreamPath; /** * 超时配置 */ private Timeout timeout = new Timeout(); /** * 连接池配置 */ private Pool pool = new Pool(); @Data public static class Timeout { private Duration connect; private Duration response; private Duration read; private Duration write; } @Data public static class Pool { private int maxConnections; private Duration maxIdleTime; private Duration maxLifeTime; private Duration pendingAcquireTimeout; private Duration evictInBackground; } @Bean public WebClient webClient() { // 创建连接池 ConnectionProvider provider = ConnectionProvider.builder("stream-connection-pool") .maxConnections(pool.getMaxConnections()) .maxIdleTime(pool.getMaxIdleTime()) .maxLifeTime(pool.getMaxLifeTime()) .pendingAcquireTimeout(pool.getPendingAcquireTimeout()) .evictInBackground(pool.getEvictInBackground()) .build(); // 配置 HttpClient HttpClient httpClient = HttpClient.create(provider) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) timeout.getConnect().toMillis()) .responseTimeout(timeout.getResponse()) .doOnConnected(conn -> conn .addHandlerLast(new ReadTimeoutHandler(timeout.getRead().getSeconds(), TimeUnit.SECONDS)) .addHandlerLast(new WriteTimeoutHandler(timeout.getWrite().getSeconds(), TimeUnit.SECONDS)) ); // 创建 WebClient return WebClient.builder() .baseUrl(baseUrl) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE) .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } }
-
开发StreamService
@Slf4j @Service //简化@Autowired书写, 声明的注入对象必须加上final修饰,是基于构造方法为属性赋值,容器通过调用类的构造方法将其进行依赖注入 @RequiredArgsConstructor public class StreamService { private final WebClient webClient; private final WebClientConfig webClientConfig; /** * 处理聊天流式请求 */ public Flux<String> handleChatStream(String token, String message) { Map<String, Object> body = new HashMap<>(); body.put("message", message); return sendRequest(webClientConfig.getChatStreamPath(), body, token); } /** * 发送请求的通用方法 * @param path 请求路径 * @param body 请求体 * @param token 认证token,可为null * @return 响应流 */ private Flux<String> sendRequest(String path, Map<String, Object> body, String token) { try { String requestBodyJson = JsonUtil.obj2Json(body) WebClient.RequestBodySpec requestSpec = webClient.post() .uri(path) .contentType(MediaType.APPLICATION_JSON); if (token != null) { requestSpec.header("token", token); } return requestSpec.bodyValue(requestBodyJson) .retrieve() .bodyToFlux(String.class) .doOnError(error -> log.error("Error in stream: {}", error.getMessage())) .doOnComplete(() -> log.info("Stream completed")); } catch (Exception e) { log.error("Error processing request: {}", e.getMessage()); return Flux.just("Invalid request format"); } } }
-
-
编码实战
@RestController @RequestMapping("/api/chat") @RequiredArgsConstructor public class StreamController { private final StreamService streamService; /** * 聊天流式接口 * curl -N -X POST \ -H "Content-Type: application/json" \ -H "token: XDCLASSeyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJYRENMQVNTIiwiYWNjb3VudElkIjoxOTE2NzMzMjEzMjU3MjkzODI1LCJ1c2VybmFtZSI6IjAwMDAiLCJpYXQiOjE3NDYxNTI2MzMsImV4cCI6MTc0Njc1NzQzM30.Gp143lg51WdpJtjOcpIkXkXAKzOwuFEpu2qvkWKyejc" \ -d "我喜欢什么运动" \ http://localhost:8080/api/chat/stream */ @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatStream( @RequestHeader("token") String token, @RequestBody String message ) { return streamService.handleChatStream(token, message); } }
-
全链路测试实战
- 启动FastAPI智能体中心项目
- 启动AI网盘Spring Boot后端业务项目
- 前端触发请求,实现流式响应内容
-
注意
- 具体后端业务和智能体如何整合,常见的就是课程本章讲解的方案
- 大家公司里面开发就可以跟进情况选择,两类都是可以进行的
下一个互联网5年-如何编写大模型简历
没有对比就没有伤害-看下什么是垃圾简历
-
注意
-
有部分同学比较急,想先进行投递简历,我们这边就先进入编写简历和面试问答板块
-
前端页面也不是关键,面试官也不看这个,讲完简历编写和面试问答事项,再进行前后端联调和其他内容
-
-
写正式简历前,先看下一个面试官眼中的垃圾简历
- 换位思考下,你是面试官,你看到这样的简历会约面吗,指出问题,再对比自己的简历
下一个5年-如何写一份合格的高级工程师简历
-
课程知识点
- 学我们这个AI大课的同学,多数是本身是前端、后端、测试、运维等方向的同学
- 大模型智能体开发本身就是新的岗位方向,招聘平台普遍要求是本身技能+大模型开发能力
- 比如:前端+AI大模型、后端+AI大模型、测试+AI大模型、运维+AI大模型
- 很多大模型公司的技术人员,都是现学、现做,工程化标准目前都没,但是大体思路都类似,面试官也是
- 大模型智能体开发不像互连网行业里面前端、后端出来10多年,很多成熟的标准
- 注意
- 大模型智能体开发 总体是偏后端方向,任何一个方向的高级岗都是离不开后端
- 否则无法独立带一个项目,总不可能工作8年的前端还是切图仔,那就被年轻的淘汰了
- 简历里面的AI大模型板块占比应该如何
- 本身方向占比5~6成、AI大模型方向占比4~5成即可
- 如果应聘的是纯AI大模型智能体开发,那可以多点,6~8成
- 投递岗位搜索: AI大模型开发工程师、大模型应用开发工程师
-
特别重要注意事项
- 简历不要照抄,不要通篇一律拿过去,每个人的情况不一样,需要结合实际调整,否则就是一眼假,没面试机会
- 比如工作年限、学历、求职城市、过往公司行业、岗位级别,每个人都是不一样的!!!!
- 如果是找工作,麻烦写完简历发我查看,如果能过得了我这个高级面试官的眼光,那简历还会差吗?
- 另外记住一个点
- 常规简历需要2个以上亮点项目,毕业3年以上则需要3个以上亮点项目,不是管理系统
- 如果简历的亮点项目不够,可以找课程顾问,折扣学其他领域的项目,包装进去。
- 我们这个AI大课可以包装两个项目,简历里面不同领域项目越多,约面机会越大
- 95%的公司会进行背景调查,企查查电话核对等,空窗期问题等,很多注意事项,找我咨询
D哥教你编写优秀的简历之个人信息板块
-
合格的简历编写【课程会提供每个板块内容,但是不提供组合起来的简历,请每个人使用网上的简历模版修改发我】
-
个人信息板块
-
技术栈板块
-
项目板块
-
个人评价
-
-
参考【简历模版可以去WPS找,百度搜索简历模版,大厂刷题班里面也有】
-
个人信息板块【有多个注意事项】
-
姓名:小滴课堂-老王
-
工作年限:4年
-
学历:本科 【统招和非统招注意事项】
-
专业:软件工程 【科班和非科班注意事项,包括中途转行】
-
学校:广州大学
-
求职岗位:AI大模型开发工程师/高级Java后端工程师【岗位级别注意事项】
-
联系方式:xxx@email.com
-
手机号:13113777333
-
-
技术栈板块【可以结合自己的调整、比如本身是前端、测试方向,那也可以把相关技能加入】
- 编程语言与框架
- 熟练Java后端开发,熟练使用SpringBoot3.x全家桶、掌握WebFlux响应式编程
- 熟练使用SpringCloud/Dubbo等微服务框架,具备服务容错、限流、熔断等治理经验
- 熟练使用分布式缓存链路追踪、日志收集、监控告警,具备大规模分布式系统性能优化经验
- 掌握Mysql数据库分库分表, 具备数据库调优、慢查询优化、索引优化经验
- 熟悉分布式系统设计,包括消息队列(Kafka/RabbitMQ)、缓存(Redis)、分布式存储(MinIO/OSS)等
- AI大模型开发
- 熟练掌握Python开发,熟练FastAPI框架、LangChain框架等AI开发技术
- 熟悉Prompt Engineering,能设计高质量的提示工程方案
- 熟练掌握RAG(检索增强生成)架构,构建企业级知识库
- 深入理解LangChain框架,能开发复杂的AI Agent应用
- 熟悉主流大语言模型(GPT、ChatGLM、通义千问等)特性及应用场景
- 掌握向量数据库应用开发,熟练使用Milvus进行语义检索
- 架构设计
- 具备微服务架构设计经验,熟悉分布式系统开发
- 掌握AI大模型应用架构设计,包括高可用、容错、性能优化等
- 具备AI应用性能优化与成本控制经验,有一定的大模型微调经验
- 熟悉向量数据库(Milvus)、大规模语言模型应用开发
- 熟悉DevOps实践,具备CI/CD pipeline搭建经验
- 编程语言与框架
简历项目编写之企业智能云盘系统-知识库平台
-
简历里面项目模块的注意事项
- 项目介绍
- 项目技术选型
- 项目个人职责
- 参考格式
-
项目板块编写
-
项目名称:企业智能云盘系统|企业级知识库平台【不要照抄,可以选几个类似的名称+前缀】
-
项目介绍
- 企业级智能知识管理平台,集成AI大模型能力,支持多类型文件存储和实现文档智能化管理和处理
- 提供基于AI的文件内容理解和知识抽取,解决公司文件数量大时检索困难,定位文件耗时
- 企业文档、会议记录、培训材料等分散在不同系统;传统关键词搜索无法理解语义,找资料耗时;
- 缺乏自动化知识提取和更新机制,缺乏智能问答和推荐能力,知识难以快速应用等问题
- 支持多种存储方案无缝切换(公有云、私有化部署),适合集团子公司快速部署和使用
-
技术选型
- 后端技术和存储:SpringBoot3.x + MyBatisPlus + WebFlux+MinIO分布式存储 + OSS对象存储+Redis集群 + 本地缓存
- AI服务技术栈:Python3.x + FastAPI + LangChain+向量数据库Milvus+RAG架构 大模型支持DeepSeek/通义千问等模型接入
-
个人核心职责【选择性抄取,比如 不可能实习生独立负责项目总体架构吧】
-
项目总体设计和把控
-
和产品经理对接,包括梳理现有公司文档库问题和各个部门使用情况,结合技术手段优化
-
团队后端技术和AI大模型技术选型、业务功能规划和项目架构、数据库架设计、业务系统越权攻击处理等
-
培训公司技术同事AI编码插件使用、优化代码问题和统一接口文档和技术prompt提示词
-
-
存储引擎核心开发
- Amazon S3组建封装,设计实现存储引擎抽象层,基于设计模式设计统一存储接口
- 支持多种存储方案,实现存储引擎自动切换和降级机制
-
文件管理核心模块
- 多层级展示⽂件夹列表和⼦⽂件夹技术设计和编码实现,多层级文件批量移动和复制功能设计
- 多层级文件删除模块和回收站、容量控制等设计
- 多文件分享模块设计和基于自定义注解实现一键转存功能开发和实现
-
大文件传输和秒传模块开发:
- 开发文件秒传功能,基于MD5+文件指纹, 设计断点续传和分片上传核心算法
- 实现文件分片上传、分片设计架构,和断点续传核心逻辑,支持大文件传输
- 文件上传和下载架构链路优化和设计,前端直连存储引擎安全交互方案和开发
-
AI文档处理系统开发
- Prompt提示词工程开发和通用大模型方法模块开发,一键切换不同大模型
- 智能文档解析引擎,开发多格式文档解析器,支持Office/PDF/在线文档等主流格式
- 实现基于FastAPI的流式文档处理服务,设计文档结构化处理流程,提取关键信息
- 实现基于Milvus的语义检索功能,开发混合检索策略,结合关键词和语义搜索
-
RAG系统架构设计与实现
- 知识库构建流程设计,实现文档自动分片和开发文档向量化处理管道,设计增量更新和版本管理机制
- 检索系统优化,实现混合检索策略(关键词+语义),开发MMR(最大边际相关)算法
-
文档助手Agent开发
- 实现基于LangChain的文档处理Agent,开发多Agent协同工作机制,设计任务分解和结果合并策略
- 支持多类型文档解析和总结、改写等,支持本地多文件解析和在线网页内容提取
- 实现文档分片并行处理,支持大文件提取和解析,开发内存管理和垃圾回收
-
-
企业智能云盘系统知识库平台常见面试指标
-
项目面试常见题目【灵活应答,结合项目公司情况】
-
团队项目规模和开发周期是如何的?
- 团队构成:
- 总人数:7人
- 后端开发:3人(本人担任技术负责人)
- 前端开发:2人
- 测试运维:2人
- 开发周期:
-
一期(核心功能):1个月
-
二期(AI能力):1个月
-
持续迭代优化::6个月+
-
- 团队构成:
-
项目生产环境服务器多少节点,配置多少?
- 部署架构:
- 服务器配置:
- 应用服务器:4台 16核32G(阿里云ECS)
- AI推理服务器:16台 32核64G+V100 GPU
- 运维部门部署对应的大模型,公司里面的多个项目组都有AI功能,目前多个项目组进行使用,资金预算有限,所以就没独立采购
- 也可以说是使用算力平台或者云平台,比如阿里云百炼上面的模型进行使用
- 数据库服务器:2台 16核64G(主从)
- 缓存服务器:2台 8核16G(Redis集群)
- 中间件部署:
- MinIO集群:8台 32核64G,配置12块 2TB SSD.
- 向量数据库服务器(分片配置 6个分片,每分片2副本 )
- Milvus协调节点:2台 16核32G
- Milvus数据节点:4台 32核64G
- Milvus查询节点:4台 32核64G+T4 GPU
- 总内存配置:512GB,支持10亿级向量检索
- 监控告警:
- Prometheus + Grafana监控系统
- ELK日志分析平台,自研告警平台(企业微信/钉钉通知)
- 服务器配置:
- 部署架构:
-
取得怎样的成绩
-
技术指标【技术人员关心的】:
-
存储性能:
- 支持单文件最大100GB,总存储容量PB级
- 大文件传输速度提升200%,支持断点续传
- 文件秒传成功率达95%,平均耗时<1s
-
AI服务性能:
- 检索响应时间<200ms,准确率90%,文档解析准确率95%,支持20+种格式
- AI模型平均推理时间<500ms,系统可用性达到99.99%
- 系统性能:
- 峰值并发支持8000+ QPS【区分接口类型】
- API平均响应时间<100ms
- 系统故障自动恢复时间<1分钟
-
-
业务指标【产品运营和技术负责人、老板关心的】:
- 数据指标:
- 日均处理文件100GB+,文件总量达到1PB
- AI服务日处理文档1万+,准确率95%,用户满意度提升50%,投诉率降低60%
- 运营指标:
- 文档管理效率提升30%,存储成本降低40%,运维人力投入减少50%
- 系统部署时间从天级缩短到小时级,问题定位时间从小时级缩短到分钟级
- 效率提升:
- 员工知识获取效率提升60%,新员工培训时间缩短40%
- 客户服务响应时间减少50%, 知识复用率提升80%
- 成本节约:
- 知识管理成本降低45%,培训成本降低35%
- 人力成本节省50%,IT运维成本降低30%
- 智能化水平:
- OCR识别准确率98%,摘要生成质量评分4.5/5,关键信息提取准确率93%
- 数据指标:
-
技术影响:
- 获得公司年度最佳技术创新奖,存储引擎架构申请技术专利1项
- 团队获得季度最佳团队奖,方案在集团多子公司成功推广
-
-
简历项目编写之对话交互类业务-智能客服
-
项目板块编写
-
项目名称:智能客服机器人平台【可以包装 AI医生、AI律师、智慧政务等对话类型交互的业务】
-
项目介绍:企业级智能客服系统,解决以下核心问题【需求问题,面试官会问为啥要做,选择性抄取】
- 人工客服痛点
- 人力成本高:需要大量客服人员24小时在线,人力成本逐年上升
- 培训周期长:新客服培训周期3-6个月,流失率高达30%
- 服务质量不稳定:不同客服水平参差不齐,服务标准难统一
- 知识更新慢:产品迭代快,客服知识更新滞后,答案准确性受影响
- 工作强度大:重复性工作多,客服人员倦怠感强,影响服务质量
- 质量监控难:人工质检覆盖率低,问题发现滞后
- 用户体验问题:
- 响应速度慢:高峰期平均等待时间超过10分钟,用户流失率高
- 服务不连续:跨班交接时服务断层,用户需重复描述问题
- 解决效率低:简单问题也需人工介入,一次性解决率低于60%
- 服务时间受限:非工作时间无法提供及时服务,影响用户体验
- 多语言支持弱:国际化服务能力不足,跨境业务支持困难
- 人工客服痛点
-
技术选型
-
后端技术栈:SpringBoot3.x + MyBatisPlus + WebFlux+Redis集群 + ElasticSearch+Kafka集群
-
AI服务技术栈:Python3.x + FastAPI + LangChain+向量数据库Milvus +基于RAG架构+DeepSeek/ChatGLM/通义千问等模型接入
-
核心架构:采用分布式微服务架构,实现服务高可用和弹性伸缩,基于LangChain实现多轮对话管理和知识库检索
-
-
个人核心职责【选择性抄取,有些不会的或者没能构思出来的则不加入,避免被问到不会】
-
智能对话系统开发
-
多轮对话引擎,实现基于Redis的对话历史持久化,实现基于LRU的对话历史缓存
-
开发对话摘要生成机制,优化长对话性能,设计上下文压缩算法,保持85%信息完整度
-
对话流程管理,实现基于状态机的对话流程控制,开发动态意图识别和纠错机制
-
实现个性化对话风格定制功能,支持意图识别和多工具调用、自定义多类型响应
-
- 人机协作平台:
- 实现无缝人机交接和上下文传递,开发实时建议生成和质量评估设计客服辅助决策系统
- 实现多样化回答生成策略,开发敏感信息实时过滤,设计个性化语气调整机制
- 知识库管理系统
- 知识更新机制,实现知识自动抽取和结构化,设计知识质量评估体系
- 检索优化,实现混合检索策略,提升准确率,开发热点问题缓存机制,设计知识推荐算法
- 智能问答系统开发
- 问答链设计,实现基于LangChain的可定制问答链,开发多轮对话上下文管理,设计答案质量评估体系
- 提示词工程,开发动态提示词模板系统,实现提示词自动优化机制,设计特定领域知识注入方案
- 其他模版参考
- 设计并实现基于大语言模型的对话引擎,开发上下文管理模块,支持长对话理解
- 优化对话响应时间,将平均响应时间从2s降低到0.5s
- 开发基于LangChain的智能对话Agent,支持工具调用和知识检索
- 实现基于Redis的分布式会话管理,支持百万级并发会话,支持多用户多会话方式进行
-
-
项目规模:
- 团队构成:
- 总人数:9人
- 后端开发:3人(本人负责对话系统)
- 前端开发:2人
- AI算法:2人
- 测试运维:2人
- 开发周期:
- 一期(对话系统):2个月
- 二期(持续优化):3个月
- 团队构成:
-
部署架构:
- 服务器配置:
- 应用服务器:4台 16核32G(阿里云ECS)
- AI推理服务器:16台 32核64G+V100 GPU
- 运维部门部署对应的大模型,公司里面的多个项目组都有AI功能,目前多个项目组进行使用,资金预算有限,所以就没独立采购
- 也可以说是使用算力平台或者云平台,比如阿里云百炼上面的模型进行使用
- 数据库服务器:2台 16核64G(主从)
- 缓存服务器:2台 8核16G(Redis集群)
- 中间件部署:
- ElasticSearch集群:8台 16核32G
- 向量数据库服务器(分片配置 6个分片,每分片2副本 )
- Milvus协调节点:2台 16核32G
- Milvus数据节点:2台 32核64G
- Milvus查询节点:2台 32核64G+T4 GPU
- 监控告警:
- Prometheus + Grafana监控系统
- ELK日志分析平台,自研告警平台(企业微信/钉钉通知)
- 服务器配置:
-
取得怎样的成绩
- 技术指标:
- 系统支持日均10万+用户咨询, 90%问题响应时间<1s,峰值QPS>1000
- AI回答准确率90%,知识覆盖率95%, 系统可用性99.99%
- 智能路由性能:
- 路由准确率达98%, 平均等待时间<30s
- 紧急事件响应时间<10s, 客服资源利用率提升40%
- 知识库性能:
- 知识更新延迟<1分钟, 知识检索准确率95%
- 热点问题命中率98%, 知识库容量支持千万级条目
- 技术指标:
-
业务价值:
- 成本效益:
- 客服人力成本降低40%, 培训成本降低50%
- 运营成本降低35%, ROI提升200%
- 服务质量:
- 客户满意度提升35%, 问题一次解决率提升45%
- 服务7*24小时全天候可用, 知识库更新周期从天级缩短到分钟级
- 成本效益:
-
教你如何打造独一无二的高级工程师简历
-
超级技巧:如何打造更加独一无二的简历?
- 当前AI智能化云盘还有很多个人负责的模块,可以包装简历进去,查看我们大课的课程目录和学习笔记
- 很多知识点都可以包装到个人负责的模块,放心大胆写进去,只要你掌握好课程的内容
- 想要简历更好,就需要多动手和花时间,不能等饭喂到嘴,这个就丧失思考能力,也就失去了教育的本质
- 我团队里面招聘和留人,经常说 xue li ,不是你大学的学历,而是这个学力,强调的是学习能力
- 就是按照老师的方法论进行,翻阅课程笔记,然后自己思考总结挖掘知识点,编写简历发我过审
- 举个例子
- 第三十八章 嵌⼊模型性能优化 ,采用CacheBackedEmbeddings解决嵌⼊⽣成成本⾼、重复计算浪费、响应速度慢瓶颈
- 第四十二章 MultiQueryRetriever ⼤模型召回率优化,通过⽣成多个相关查询来增强检索效果,解决单⼀查询不够全⾯或存在歧义的问题
- 第四十三章 RunnableParallel并⾏链的知识点,多个任务并行链,同时处理多个数据流提高效率
- 还有更多...
-
投递面试周期
-
想招聘一个靠谱的人员真的不容易,不止阿里这边,有时我会帮朋友的公司进行招聘相关技术人员,毕竟我经验多
-
情况是这样的
- 招聘平台上的一个开发岗位,能收到一堆投递人员,一天能收到几百份上千份,常规我应该开心才对,但是却悲催了
-
简历普遍存在严重的问题,就这样说吧:100个简历,里面90%项目都是雷同
- 比如XX外卖系统、XX点餐系统、XX课堂、XX头条
- 不是说这些项目不行,起码包装像样点,不用技术点、技术选项、错别字都一样
-
我不排斥培训机构出来的同学,而且还很欣赏,特别是在职提升的我很看重,但是上面的这些我不 会约面,为什么?
-
因为学的不到家,师从哪里,怎样的老师教怎样的学生,多数人员都经不起提问,因为他们只停留在CRUD阶段而已
-
我想招聘一个可以干活,不拖团队后腿的人都很难,就很简单,生产环境出现了问题,他们估计都没接触过生产环境
-
由于大量的人员简历投递,且简历都很丰富,导致真正有能力的人容易被遗漏,这个是目前比较麻烦的问题
-
所以有些有能力的同学,你们遇到的这个问题,最好的就是持续投递+坚持,8分技术+2分运气,投递面试周期20到25天
-
有些额外投递技巧, 就加我微信私聊,是金子总会被发现的,普遍15~20天找到不错的Offer
-
-
备注
- 如果微信二维码失效,微信号是这个 【xdclass6】,简历记得发我过审核,还有其他注意事项等
- AI大模型开发需要多练习项目,类似大家刚入行前端或者后端,熟能生巧
- 后续小滴课堂会出专门的AI项目大课,不讲基础,直接讲复杂大型项目开发
AI智能化网盘前后端联调综合实战
前端基础环境和AI智能化云盘服务跨域配置
-
需求
- 安装前端项目所需要技术框架、相关环境说明和准备
- 前后端分离架构,前端请求后端服务和AI模型服务,需要开启跨域配置
-
前端相关环境依赖
-
技术点: Vue3、TypeScript、Element Plus、Axios、Pinia、Vue Router等
-
需要的依赖
-
npm和yarn就是前端的包管理,类似Java的Maven和Python的pip
-
版本
Node.js >= 16.0.0
和npm >= 8.0.0 或 yarn >= 1.22.0
-
环境安装 ,也可以搜索相关博文,Node环境(里面会顺带npm):https://nodejs.org/zh-cn
-
课程项目电脑的安装的相关版本
npm install -g yarn
-
-
-
案例实战
-
后端网盘项目配置跨域,创建
MyCorsFilter.java
@Configuration public class MyCorsFilter { @Bean public CorsFilter corsFilter() { //创建 CORS 配置对象 CorsConfiguration config = new CorsConfiguration(); //支持域 config.addAllowedOriginPattern("*"); //是否发送 Cookie config.setAllowCredentials(true); //支持请求方式 config.addAllowedMethod("*"); //允许的原始请求头部信息 config.addAllowedHeader("*"); //暴露的头部信息 config.addExposedHeader("*"); //添加地址映射 UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); corsConfigurationSource.registerCorsConfiguration("/**", config); //返回 CorsFilter 对象 return new CorsFilter(corsConfigurationSource); } }
-
FastAPI服务开启跨域配置
app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
-
AI智能化云盘-前端项目环境配置和构建
-
需求
- 搭建AI智能化云盘的前端相关开发环境,进行前后端联调
- 前端技术框架和环境要求
- 编辑器VSCode
- Node环境
-
前端效果
-
前端代码下载, 使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/aipan_front_end.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/aipan_front_end.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/aipan_front_end.zip
-
运行实战
- 解压代码,导入VSCode编辑器
- 构建项目依赖
#如果没安装yarn,则可以通过npm进行安装 sudo npm install -g yarn #配置源,国内的淘宝源 yarn config set registry https://registry.npmmirror.com #配置源,官方源 yarn config set registry https://registry.yarnpkg.com #安装依赖 yarn install # 开发环境运行 yarn serve #生产环境打包构建 yarn build
AI智能化云盘-前端配置文件修改和后端联调
-
需求【本集不提供前端课程代码】
- 课程代码使用上集下载好的,修改后端的服务器地址即可
- 最终上线前端,直接打包编译好的代码,上传到阿里云OSS即可
-
配置修改实战
-
Spring Boot 后端服务器请求地址修改
-
Fast API 模型服务器请求地址修改
-
-
基础功能测试
-
注意
- 前端、后端、AI大模型服务 肯定有不少Bug,包括很多可以优化地方
- 大家可以基于现有代码进行优化,二次开发,但是不建议开源上传网上哈
- 打包后的dist文件就可以上传到静态文件服务器,比如nginx,阿里云oss等进行访问
AI智能化云盘后端Bug缺陷修复实战
简介: AI智能化云盘后端Bug缺陷修复实战
-
后端网盘项目Bug修复
- 文件批量移动功能修复
- 网盘分享功能问题修复
- 前端分享链接修改
-
如果还有其他有问题的,可以直接修复即可
AI智能化网盘项目云服务器部署上线
阿里云服务器多节点配置实操-按量付费
-
需求
- 为了方便后续部署多个节点和程序,直接购买阿里云服务器包年包月比较高
- 课程采用案例付费的方式,购买多台阿里云服务器,进行多节点部署
- 花费几十块钱,部署完成验证后即可释放
-
服务器要求
-
采购4核8G服务器多台, 如果课程后续部署服务器不够,也是参考这个进行购买演示
-
每台服务器都安装Docker基本环境,其他环境课程使用的时候按需安装
————————Docker-ce社区版本———————— #运行以下命令,下载docker-ce的yum源。 sudo wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #运行以下命令,安装Docker。 sudo yum -y install docker-ce #执行以下命令,检查Docker是否安装成功。 sudo docker -v #执行以下命令,启动Docker服务,并设置开机自启动。 sudo systemctl start docker sudo systemctl enable docker #执行以下命令,查看Docker是否启动。 sudo systemctl status docker #配置Docker镜像加速 ([ -f /etc/docker/daemon.json ] || mkdir -p /etc/docker) && echo '{ "registry-mirrors" : [ "https://docker.m.daocloud.io", "https://noohub.ru", "https://huecker.io", "https://dockerhub.timeweb.cloud" ] }' > /etc/docker/daemon.json && sudo systemctl restart docker && sleep 1 && docker info | grep -A 4 "Registry Mirrors"
-
Git安装和配置SSH Key
yum install git
-
FastAPI模型服务Docker部署打包配置编写
-
需求
- 方便快速扩容和多个节点进行,相关服务都采用容器部署
- 编写相关的的Docker容器构建脚本,则统一使用阿里云ECS服务器上进行构建
- 有条件的利用构建DevOps链路(公司里面一般有Jenkins基建)进行自动化部署上线
-
完整的FastAPI项目下载【本集的FastAPI项目,包括
requirements.txt
不在这集课程资料提供,在线下载】- 使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的 微信 xdclass6)**
原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/dcloud_ai_agent_docker.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/dcloud_ai_agent_docker.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/dcloud_ai_agent_docker.zip
- 阿里云ECS服务器解压相关
unzip dcloud_ai_agent_docker.zip
-
Dockerfile编写实战
# 构建阶段 FROM python:3.12.6-slim as builder # 设置工作目录 WORKDIR /app # 设置环境变量 ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ DEBIAN_FRONTEND=noninteractive # 配置系统源为阿里云源 RUN echo "deb http://mirrors.aliyun.com/debian/ bullseye main contrib non-free" > /etc/apt/sources.list && \ echo "deb http://mirrors.aliyun.com/debian/ bullseye-updates main contrib non-free" >> /etc/apt/sources.list && \ echo "deb http://mirrors.aliyun.com/debian/ bullseye-backports main contrib non-free" >> /etc/apt/sources.list && \ echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main contrib non-free" >> /etc/apt/sources.list # 安装系统依赖(添加超时设置) RUN apt-get update -o Acquire::http::Timeout=10 -o Acquire::https::Timeout=10 && \ apt-get install -y --no-install-recommends \ gcc \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean # 配置 pip 使用清华源 RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 复制依赖文件 COPY requirements.txt . # 安装依赖到虚拟环境 RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" RUN pip install --no-cache-dir -r requirements.txt gunicorn # 运行阶段 FROM python:3.12.6-slim # 设置工作目录 WORKDIR /app # 设置环境变量 ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PATH="/opt/venv/bin:$PATH" # 创建非root用户 RUN groupadd -r appuser && useradd -r -g appuser appuser # 从构建阶段复制虚拟环境 COPY --from=builder /opt/venv /opt/venv # 复制应用代码 COPY --chown=appuser:appuser . . # 切换到非root用户 USER appuser # 暴露端口 EXPOSE 8000 # 设置资源限制 ENV GUNICORN_CMD_ARGS="--workers=4 --threads=2 --timeout=60 --max-requests=1000 --max-requests-jitter=50" # 启动命令(使用gunicorn作为生产服务器) CMD ["/opt/venv/bin/gunicorn", "app.main:app", "--bind", "0.0.0.0:8000", "--worker-class", "uvicorn.workers.UvicornWorker"]
-
知识点
Gunicorn
介绍- 也是Python的 Web 服务器,它采用多进程模型,启动时会创建多个工作进程,每个进程都能独立处理请求,适合生产环境部署
- 可以充分利用多核 CPU 的计算能力,既适用于同步框架(如 Flask、Django)开发的 Web 应用,也能用于异步框架。
- 当需要在生产环境部署异步框架(如 FastAPI),且希望充分利用多核 CPU、保证进程稳定性时,结合使用 Gunicorn 和 Uvicorn 是最优解。
- 两者通过 “主进程管理 + 异步工作者” 的模式,实现了高性能、高可用的统一
- 参数说明
-w 4
:启动 4 个工作worker进程(数量通常设为 CPU 核心数的 1-2 倍)。max_requests
是指每个 worker 在处理一定数量的请求后将会重启,防止内存泄漏或其他问题max_requests_jitter
参数用于设置在max_requests
的基础上增加一个随机的抖动值,避免所有 worker 同时重启
FastAPI模型服务Docker部署验证实战
-
需求
- 阿里云服务器安装好相关环境Docker环境
- 采用上集编写好的Dockerfile进行打包验证(有修改Dockerfile下载源加速,按照上集的直接下载即可)
- 网络安全组记得开发相关端口
-
构建打包验证实战
-
构建镜像【进入到项目根目录 Dockerfile所在的路径】
docker build -t fastapi-app-pro .
-
运行容器
docker run -d \ -p 8000:8000 \ --name fastapi-container \ --memory=2048m \ --restart=always \ fastapi-app-pro
-
-
请求接口测试实
AI智能化网盘项目Docker部署打包配置编写
-
需求
- 后端SpringBoot项目进行容器打包部署,编写对应的Dockerfile文件
- 方便快速扩容和多个节点进行,相关服务都采用容器部署
-
项目
pom.xml
文件调整<!-- 配置Maven仓库 --> <repositories> <repository> <id>aliyun</id> <name>aliyun</name> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>aliyun-spring</id> <name>aliyun-spring</name> <url>https://maven.aliyun.com/repository/spring</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <!-- 配置插件仓库 --> <pluginRepositories> <pluginRepository> <id>aliyun-plugin</id> <name>aliyun-plugin</name> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
-
Dockerfile编写实战
# ================================================ # 多阶段构建:构建阶段(使用Maven基础镜像) # 基础镜像:Maven 3.9.6 + Eclipse Temurin JDK21 # ================================================ FROM maven:3.9.6-eclipse-temurin-21 AS build # 设置容器内工作目录 WORKDIR /app # ------------------------------------------------ # 复制宿主机构建上下文全部文件到容器工作目录 # ------------------------------------------------ COPY . . # ================================================ # Maven构建命令(含优化参数) # 主要参数说明: # -DskipTests=true : 跳过单元测试 # -Dmaven.test.skip=true : 跳过测试编译阶段 # -Dmaven.compile.fork=true : 启用并行编译 # -Dmaven.compiler.source/target: 指定Java版本 # -Dmaven.compiler.force... : 强制使用javac # -annotationProcessorPaths : 配置Lombok注解处理器 # ================================================ RUN mvn clean package \ -DskipTests=true \ -Dmaven.test.skip=true \ -Dmaven.javadoc.skip=true \ -Dmaven.compile.fork=true \ -Dmaven.compiler.source=21 \ -Dmaven.compiler.target=21 \ -Dmaven.compiler.forceJavacCompilerUse=true \ -Dmaven.compiler.showWarnings=true \ -Dmaven.compiler.showDeprecation=true \ -Dmaven.compiler.annotationProcessorPaths=lombok:org.projectlombok:lombok:1.18.30 # ================================================ # 多阶段构建:运行阶段(使用轻量级JRE镜像) # 基础镜像:Eclipse Temurin JRE21 + Ubuntu Jammy # ================================================ FROM eclipse-temurin:21-jre-jammy WORKDIR /app # ------------------------------------------------ # 从构建阶段复制生成的JAR文件 # ARG参数说明: # JAR_FILE : 匹配target目录下的所有JAR文件 # ------------------------------------------------ ARG JAR_FILE=target/*.jar COPY --from=build /app/${JAR_FILE} app.jar # ------------------------------------------------ # 时区配置(设置为亚洲/上海时区) # 通过符号链接和配置文件修改容器时区 # ------------------------------------------------ ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # ================================================ # JVM参数配置(内存管理与GC优化) # 关键参数说明: # -Xms/-Xmx : 堆内存初始/最大值 # -XX:MetaspaceSize : 元空间初始大小 # -UseG1GC : 启用G1垃圾回收器 # -MaxGCPauseMillis : 目标最大GC停顿时间 # -HeapDumpOn... : OOM时生成堆转储 # -UseContainerSupport : 容器内存感知 # -MaxRAMPercentage : 最大内存占用比例 # ================================================ ENV JAVA_OPTS="-Xms2g -Xmx4g \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:ParallelGCThreads=4 \ -XX:ConcGCThreads=2 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/app/dump \ -Xlog:gc*:file=/app/gc.log \ -XX:+UseStringDeduplication \ -XX:+UseContainerSupport \ -XX:MaxRAMPercentage=75.0" # ------------------------------------------------ # 声明容器暴露端口(HTTP服务默认端口) # ------------------------------------------------ EXPOSE 8080 # ================================================ # 容器启动入口命令 # 使用sh -c执行命令以支持环境变量扩展 # 最终启动命令:java [JVM参数] -jar app.jar # ================================================ ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
AI智能化网盘项目Docker部署验证实战
-
需求
- 阿里云服务器安装好相关环境Docker环境
- 采用上集编写好的Dockerfile进行打包验证
- 网络安全组记得开发相关端口
- 备注:项目依赖的其他中间件比如Mysql、Redis等,直接复用之前部署即可
-
构建打包验证实战
-
git拉取代码
-
构建镜像
# 构建镜像 docker build -t dcloud-aipan .
-
运行容器
docker run -d \ -p 8080:8080 \ --name dcloud-aipan \ --restart=always \ dcloud-aipan
-
-
测试验证实战:请求接口测试实战
Nginx反向代理Web服务器集群架构
-
需求
- 后端业务会进行负载均衡,通过反向代理进行分发多个节点
- 需要阿里云安装Nginx服务器,和启动多个后端服务节点
-
整体请求架构
-
域名解析和配置
-
Docker安装Nginx
- 创建配置文件
nginx.conf
user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; # 开启gzip压缩 gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; gzip_vary on; gzip_disable "MSIE [1-6]\."; # 超时设置 keepalive_timeout 65; client_max_body_size 100m; # Spring Boot后端服务配置 upstream springboot_backend { # 使用轮询策略 server 172.31.101.8:8080; # 节点1 } # FastAPI服务配置 upstream fastapi_backend { # 使用轮询策略 server 172.31.101.8:8000; # 节点1 } # Spring Boot服务配置 server { listen 80; server_name pan-api.open1024.com; # Spring Boot服务的域名 location /api/ { proxy_pass http://springboot_backend/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } } # FastAPI服务配置 server { listen 80; server_name ai-api.open1024.com; # FastAPI服务的域名 location /api/ { proxy_pass http://fastapi_backend/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } } }
- 创建配置文件
-
创建Dockerfile
FROM nginx:1.24.0-alpine # 复制自定义配置 COPY nginx.conf /etc/nginx/nginx.conf # 创建日志目录 RUN mkdir -p /var/log/nginx && \ chown -R nginx:nginx /var/log/nginx # 暴露端口 EXPOSE 80 # 启动Nginx CMD ["nginx", "-g", "daemon off;"]
-
编写
start.sh
脚本#!/bin/bash # 创建日志目录 mkdir -p logs # 构建镜像 docker build -t nginx-proxy:1.24.0 . # 停止并删除旧容器(如果存在) docker stop nginx-proxy 2>/dev/null || true docker rm nginx-proxy 2>/dev/null || true # 运行容器 docker run -d \ --name nginx-proxy \ -p 80:80 \ -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \ -v $(pwd)/logs:/var/log/nginx \ --restart unless-stopped \ nginx-proxy:1.24.0 # 显示容器日志 docker logs -f nginx-proxy
-
测试实战【备注,如果没域名的或者备案还没成功的,可以用ip测试,部署多1个Nginx】
- AI网盘服务接口
- AI大模型服务接口
智能化网盘Web前端项目部署阿里云OSS实战
-
需求
- 前端项目部署阿里服务器实战,打包项目上传阿里云OSS即可
- 配置域名解析(不能使用默认域名,否则访问不了直接下载)
- 需要域名备案通过才可以,可以不实操或者使用nginx部署ip访问
- 注意:本集不提供前端代码,请从 第56章第2集的方向进行获取完整前端代码
-
配置实战
#生产环境打包构建 yarn build
AI智能化云盘-前后端全链路功能测试实战
简介: AI智能化云盘-前后端全链路功能测试实战
-
需求
- 智能化网盘进行功能测试,包括注册,登录,文件上传,文件秒传,大文件上传,文件删除、分享、查看、转存等
- AI智能体中心测试,包括AI网盘智答、AI智能聊天助理、AI在线文档助手等相关功能
-
功能测试实战
高阶内容-大模型LLM微调和环境准备
大模型微调FineTune介绍和常见方式
-
什么是大模型微调?
- 大模型微调(Fine-Tuning)是指在预训练大模型(如GPT、ChatGLM、DeepSeek等)的基础上
- 通过小规模特定任务数据对模型参数进行调整,使其适应特定领域或任务需求的技术
- 核心原理:利用预训练模型已有的通用知识,通过少量数据学习任务特性,实现从“通用模型”到“领域专家”的升级
- 现阶段使用的是通用大模型,也叫基座模型(预训练好的模型),微调是让大模型具备行业垂直的知识,更加深入和专业
- 注意:本章和下章内容难度比较高,包括很多专业术语、特殊文件等,适当学习,可以二刷
-
为什么要进行微调?
- 任务适配性:通用大模型缺乏领域专业知识(如医学术语、法律条文),需通过微调提升任务表现
- 性能优化:微调能提升模型在特定任务中的准确性(如客服系统需调整回答风格)
- 避免遗忘:通过小规模数据调整部分参数,保留模型原有通用能力
维度 说明 典型场景 知识注入 注入行业术语/私有知识 医疗诊断系统 行为矫正 控制输出风格/格式 法律文书生成 效率优化 压缩推理步骤 客服FAQ回答 安全增强 过滤敏感内容 儿童教育助手 -
常见微调方式
-
全参数微调(Full Fine-Tuning)
-
全参数微调是指在预训练模型的基础上,对模型的所有参数(权重)进行更新,以适应特定领域或任务的数据。
-
调整模型所有参数,效果最佳但成本高,需大量数据和算力
-
通俗的例子
把大模型想象成一位知识渊博的万能专家,它已经学习了各种各样的知识。 全参数微调就像是让这位专家专门去学习某个特定领域的知识,而且是全方位、深入地学习。 比如说,这位专家原本什么都懂一点,但现在要让他成为汽车修理方面的顶尖高手,那他就得重新学习汽车构造、修理技巧等各个方面的知识。 在大模型里,就是对模型里的每一个参数都进行调整,就像让专家把大脑里所有和汽车修理相关的知识都更新一遍,这样它就能在汽车修理这个特定领域表现得非常出色。 不过,这个学习过程需要花费很多时间和精力,就像专家要全身心投入去学习一样
-
效果
- 通常效果较好,因为模型的所有参数都能根据新任务或领域数据进行调整
- 可最大程度地优化模型对新任务的理解和表现,充分利用微调数据,提升模型在特定任务上的表现。
-
资源需求
- 训练时间长,显存占用较高,因为需要更新和存储所有参数,对计算资源要求高。
-
适用场景
- 适用于拥有大量与任务高度相关的高质量训练数据,且需要最大化模型性能并具备足够计算资源的场景。
-
-
部分参数微调(Partial Fine-Tuning)
-
部分参数微调策略仅选择性地更新模型中的某些权重,在需要保留大部分预训练知识的情况下使用
-
通俗例子
还是拿这位万能专家举例,部分参数微调就好比让专家在不改变自己原有大部分知识的基础上,只学习汽车修理领域里一些关键的、特别重要的知识。 比如,专家已经知道很多机械原理了,现在只需要学习汽车发动机的一些特殊修理技巧就行。 在大模型中,就是只调整模型里一部分关键的参数,而不是全部参数。 这样做的好处是学习速度快,不需要花太多精力,就像专家不用重新学习所有知识,只学关键部分就能快速上手汽车修理工作。 虽然可能不如全参数微调那样让专家成为汽车修理领域的超级高手,但也能让专家在这个领域有不错的表现。
-
效果:相比全参数微调,效果可能略有下降,但通常能够达到相似的性能,在大多数应用中可达到满意效果。
-
资源需求:训练速度更快,显存占用更少,因为只更新部分参数或添加少量新参数。
-
适用场景:适用于资源有限或需要快速迭代的情况,在保留大部分预训练知识的同时,让模型能够学习新任务的特定模式
-
常见方式
-
LoRA(Low - Rank Adaptation)
- 通过向模型权重矩阵添加低秩矩阵来进行微调,允许模型学习新的任务特定模式
- 又能够保留大部分预训练知识,降低过拟合风险并提高训练效率,课程采用的方式
就像是给专家的学习过程找到了一个捷径。专家在学习汽车修理知识时,如果从头开始学所有的细节,会很麻烦。 LoRA 就像是给专家提供了一份汽车修理的关键要点总结。 在大模型里,它通过在模型的权重矩阵里添加一些简单的低秩矩阵来进行微调。 这些低秩矩阵就相当于那份关键要点总结,让模型在保留大部分原有知识的基础上,快速学习新的任务知识,就像专家根据要点总结快速掌握汽车修理的关键技巧,而且还不会占用太多的精力和时间
-
Adapter-Tuning:在模型层间插入适配器模块,仅训练适配器参数
-
Prefix-Tuning:在输入前添加可训练前缀向量,引导模型生成特定内容
-
-
-
-
微调的实际案例
- 医疗领域
- 病历分析:微调模型理解电子病历中的专业术语,辅助医生识别诊断错误
- 医学问答:通过医疗文献微调,提升模型回答疾病相关问题的准确性
- 法律领域
- 法律文书生成:微调模型生成符合法律规范的合同文本,减少人工审核
- 客服系统
- 风格定制:调整模型回答语气(如正式或亲切),适应企业形象需求
- 医疗领域
面试题-大模型微调和RAG技术适合场景
-
RAG核心原理回顾
原始数据 → 数据加载 → 预处理 → 向量化 → 存储 → 检索增强生成 ↗ ↗ ↗ PDF 文本清洗 嵌入模型 数据库 分块 网页
- 检索(Retrieve):从知识库中查找相关文档
- 增强(Augment):将检索结果注入Prompt
- 生成(Generate):基于增强后的上下文生成回答
-
面试官:解释下什么是 RAG+微调技术,项目中如何进行选择
- 微调与RAG的对比与选择
维度 微调(Fine-Tuning) RAG(检索增强生成) 核心原理 调整模型参数,内化知识 外挂知识库,检索+生成结合 适用场景 数据稳定、需长期记忆(如法律条文) 数据频繁更新、需实时性(如商品价格) 成本 高(需训练资源和数据标注) 低(仅维护知识库) 可解释性 低(黑盒模型) 高(答案来源可追溯) 灵活性 任务专用,通用性差 跨任务通用,支持多领域 -
何时选择微调?
- 任务需模型具备“长期记忆”(如编码专家、法律术语)。
- 任务需模型具备特殊能力(如客服语气、新闻播报风格)
- 对响应速度要求极高(如金融风控系统需1秒内响应)
-
何时选择RAG?
- 知识需高频更新(如电商库存、新闻事件)
- 跨领域任务支持(如企业内部多部门知识库调用)
- 需减少模型幻觉(如医疗问答依赖权威文献)
-
结合场景示例:
- 法律咨询系统:先微调模型掌握法律框架,再通过RAG检索最新判例
- 医疗助手:微调模型理解医学逻辑,RAG实时接入最新研究文献
-
回答万能公式
- 微调适合稳定领域深度优化,RAG适合动态知识扩展,两者结合可兼顾专业性与实时性。
- 实际应用中需权衡成本、数据变化频率及业务需求,灵活选择技术方案
没有GPU咋办-算力平台介绍和使用
-
什么是算力平台
- 整合计算资源(如 CPU、GPU、FPGA 等计算芯片的运算能力),通过软件实现资源的统一管理、调度与分配,
- 满足不同用户多样化计算需求的综合性平台,类似我们租用阿里云ECS服务器一样
- 如同一个大型的 “计算资源超市”,将各类分散、异构的算力资源整合在一起,为用户提供高效的计算服务
-
大模型微调训练的算力需求(前面也讲过英伟达显卡的价格,价格贵,且难采购)
-
常见的算力平台
- 魔塔社区:https://modelscope.cn/my/mynotebook/preset
- AutoDL平台(课程采用):www.autodl.com
-
选择采购【注册后充值30元进去练习使用, 建议晚上或者早上,才可能有空闲机器租用】
大模型微调必备环境Conda介绍和使用
-
Conda 是什么?
-
包管理器 + 环境管理器:Conda 不仅能安装 Python 包,还能管理非 Python 的依赖(如 C/C++ 库)。
-
跨平台:支持 Windows、Linux、macOS。
-
多语言支持:尤其适合科学计算、数据科学领域(如 NumPy、PyTorch 等包的依赖管理)。
-
环境隔离:允许为不同项目创建独立环境,避免依赖冲突,即Python虚拟环境,和前面学习的Venv类似
-
Conda 和 Venv 对比
场景 推荐工具 原因 纯 Python 项目 venv
/pip
轻量、Python 自带,无需额外安装。 科学计算、复杂依赖项目 Conda 能管理非 Python 依赖(如 CUDA、MKL 库),解决复杂依赖冲突更高效。 多版本 Python 需求 Conda 可自由切换 Python 版本(如 3.7 → 3.10),无需手动编译安装。
-
-
安装Conda
-
Miniconda(推荐): 轻量版,仅包含 Conda 和 Python。
-
下载地址:Miniconda官网 https://docs.conda.io/projects/conda/en/stable/
-
装完成后,重启终端,输入
conda --version
验证是否成功。 -
配置conda镜像源
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
-
-
Anaconda: 包含大量预装科学计算包(体积较大,适合老手)。
-
-
Conda 基础命令
-
创建环境
# 创建名为 `myenv` 的环境,并指定 Python 版本 conda create --name myenv python=3.9
-
激活环境
conda activate myenv # 激活环境 # 退出环境
-
查看所有环境
conda env list
-
删除环境
conda remove --name myenv --all
-
安装包
- 在 Conda 环境中用
conda install
,若某些包 Conda 没有,可以用pip install
conda install numpy
- 在 Conda 环境中用
-
千问QWEN大模型微调垂直领域实战需求
-
需求
- 选择阿里的通义千问大模型进行微调,QWEN1.8B的【可以换成其他大参数的替换,这里用小的模型训练速度快一点】
- 训练AI律师大模型,即在基座大模型上,采用Lora微调,让大模型针对法律这块更加专业
- 举一反三能力
- 训练微调的数据集可以多个行业【医生、客服、金融、工业互联】等都适合
- 流程都是一样,训练好的大模型就可以 AI医生大模型、AI客服大模型、AI金融大模型
- 注意
- 大模型微调环境比较坑,不然cuda版本、操作系统版本、Python版本、transformers版本等都可能导致失败
- 而且这个难统一,依赖在线下载相关的依赖环境,官方可能更新源、更新基础镜像版本等
- 遇到问题可以先检查环境、细节步骤等、报错日志等,结合搜索引擎和AI问答进行解决。
-
大模型微调流程概述
-
准备训练数据集【需要转换格式,满足对应大模型微调数据格式,一般大模型微调的README.md文档有格式说明】
-
准备基座大模型代码,即千问大模型代码文件
-
准备基座大模型的权重文件,即大模型预训练好的模型文件
-
安装大模型微调代码相关依赖包
-
执行大模型微调代码【耗时久点】
-
微调后的模型文件合并
-
微调结果测试推理
-
高阶内容-垂直业务领域大模型LLM微调实战
垂直领域大模型微调数据集格式和转换实战
-
需求
- 参考大模型代码微调数据集里面的格式要求
- 下载对应垂直领域数据集,编写代码进行转换
-
案例实战
-
查看数据集的格式要求 https://github.com/QwenLM/Qwen/blob/main/README_CN.md
-
下载数据集 https://modelscope.cn/datasets/Robin021/DISC-Law-SFT/files
-
编写代码
format_dataset_file.py
import json # 读取以.jsonl结尾的文件,将每行解析为JSON对象并存储在列表中 def read_jsonl_file(file_path): # 用于存储解析后的JSON对象 data_list = [] try: # 以只读模式打开文件,使用utf-8编码 with open(file_path, 'r', encoding='utf-8') as file_obj: # 逐行读取文件 for line in file_obj: try: # 尝试将每行内容解析为JSON对象 line_data = json.loads(line) # 将解析后的JSON对象添加到列表中 data_list.append(line_data) except json.JSONDecodeError: # 若解析失败,打印错误信息 print(f"Error decoding JSON in line: {line}") except FileNotFoundError: # 若文件不存在,打印错误信息 print(f"File {file_path} not found.") return data_list # 将读取的JSON数据转换为训练所需的格式 def transform_to_train_format(json_data): # 用于存储转换后的格式化数据 formatted = [] # 遍历读取的JSON数据 for index, single_data in enumerate(json_data): # 构建训练所需的单个数据条目 entry = { # 为每个条目生成唯一的ID "id": f"identity_{index}", "conversations": [ # 构建用户输入的对话信息 {"from": "user", "value": single_data["input"]}, # 构建助手输出的对话信息 {"from": "assistant", "value": single_data["output"]} ] } # 将格式化后的条目添加到列表中 formatted.append(entry) return formatted # 定义输入的JSONL文件路径 input_file_path = 'DISC-Law-SFT-Triplet-released.jsonl' # 定义输出的JSON文件路径 output_file_path = 'train_data_law.json' # 调用函数读取JSONL文件 loaded_data = read_jsonl_file(input_file_path) # 若成功读取到数据 if loaded_data: # 调用函数将读取的数据转换为训练所需的格式 formatted = transform_to_train_format(loaded_data) # 打印格式化后数据的数量 print(len(formatted)) # 打印格式化后第一个数据条目的详细信息 print(json.dumps(formatted[0], ensure_ascii=False, indent=2)) try: # 以写入模式打开输出文件,使用utf-8编码 with open(output_file_path, 'w', encoding='utf-8') as out_file: # 将格式化后的数据以JSON格式写入文件 json.dump(formatted, out_file, ensure_ascii=False, indent=2) # 打印处理完成的提示信息 print(f"process: {output_file_path}") except Exception as e: # 若写入文件时出现错误,打印错误信息 print(f"Error writing to file: {e}")
-
转换格式实战
-
服务器安装大模型千问QWEN项目实战
-
需求
- 拉QWEN的官方Github文件
- 创建虚拟环境,安装相关依赖包
-
安装实战【服务器平台内置了Git、Conda、Python相关环境了】
-
进入数据盘目录
cd /root/autodl-tmp
-
创建conda环境
#创建环境 conda create -n qwen_env python=3.11 #配置shell脚本,重开终端 conda init bash #激活并切换到 qwen 环境中 conda activate qwen_env
-
安装git大文件下载
-
想要使用git拉大的数据文件需要下载
git lfs
- lfs(Large File Storage)是 Git 的一个扩展,专门用于管理和存储大文件。
- 通过替换 Git 仓库中的大文件为指针文件,并将实际的文件存储在远程服务器上
- 从而避免 Git 仓库体积过大,提高克隆和拉取的速度
#安装和更新源 sudo apt-get update sudo apt-get install git
-
添加Git LFS的包仓库: 为了安装Git LFS,先添加它的包仓库到系统中
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
-
安装Git LFS
sudo apt-get install git-lfs
-
设置Git LFS
git lfs install
-
-
拉取代码
git clone https://github.com/QwenLM/Qwen.git
- 耗时大概5分钟左右
-
构建项目代码的依赖环境【源码依赖文件版本存在多个兼容性问题,使用课程提供的
finetune_requirements.txt
文件上传】-
finetune_requirements.txt
下载, 使用**【wget】或者【浏览器】远程下载相关依赖包(需要替换群里最新的)**原生资料下载方式(账号 - 密码 - ip地址 - 端口 需要替换群里最新的,【其他路径不变】) wget --http-user=用户名 --http-password=密码 http://ip:端口/dcloud_pan/finetune_requirements.zip #比如 命令行下 wget --http-user=admin --http-password=xdclass.net888 http://47.115.31.28:9088/dcloud_pan/finetune_requirements.zip # 比如 浏览器直接访问 http://47.115.31.28:9088/dcloud_pan/finetune_requirements.zip
-
pip install -r finetune_requirements.txt
-
耗时大概20到30分钟左右
-
-
魔塔下载预训练模型文件和训练数据集配置
-
需求
- 从魔塔下载大模型预训练的模型文件
- 上传相关垂直领域的数据集,配置相关路径
-
操作实战
垂直领域大模型微调训练操作实战
-
大模型微调全流程操作实战
- 进入finetune文件夹里的
finetune_lora_single_gpu.sh
文件 - 修改模型微调参数-根据机器的实际情况调整
-
运行
bash finetune/finetune_lora_single_gpu.sh
-
注意
- 运行出现dispatch_batches错误,根据文件路径找到源码注释掉
# dispatch_batches=self.args.dispatch_batches
即可 - 是版本问题,参数被移除, 所以直接注释即可
- 操作: vim对应的文件 ,然后输入 /dispatch_batches,然后回车,输入 i 即可修改
- 运行出现dispatch_batches错误,根据文件路径找到源码注释掉
-
耗时大概几小时,待训练完成可以在目录看到output的输出的模型权重文件
-
可以修改训练轮数 num_train_epochs 为 2,则耗时少些,大概1个小时左右
-
训练轮数
训练轮数就像是让一个人学习同一套知识的次数,在大模型微调中,模型会把训练数据拿过来反复学习 每学习一遍所有的训练数据,就相当于完成了一轮训练。 如果训练轮数太少,模型就像只学了一遍知识,可能还没完全掌握这些知识,在实际应用中表现就会比较差。 但如果训练轮数太多,模型就像过度学习了,可能会把训练数据里一些偶然的、不具有普遍性的特征也当成重要知识记住 这就会导致模型在遇到新的数据时表现不佳,也就是所谓的过拟合。 所以,要根据具体的任务和数据情况,选择合适的训练轮数,让模型既充分学习又不过度学习
-
损失函数
损失函数可以理解为衡量模型 “学习成果” 的一种工具,它用来计算模型预测结果与真实结果之间的差异。 就像老师通过批改试卷来给学生的答题情况打分一样,损失函数会根据模型的预测值和实际的真实值来计算一个 “分数”, 这个分数反映了模型的预测效果有多差。 模型训练的目标就是通过调整自身的参数,使得损失函数的值尽可能地小,也就是让模型的预测结果尽可能接近真实结果。
-
- 进入finetune文件夹里的
-
硬件资源消耗
Lora微调大模型文件合并操作实战
-
微调训练完成后
-
需求
- 大模型微调后会产生多个checkpoint-xxx文件,选择最大的一个就是最终的模型权重文件
- 需要把微调后的模型权重文件,和预训练的模型文件进行合并,才可以满足使用
-
案例实战, 创建
merge_find_tune.py
文件from peft import AutoPeftModelForCausalLM from transformers import AutoTokenizer def merge_and_save_model(path_to_adapter, new_model_directory): try: # 加载 LoRA 模型 model = AutoPeftModelForCausalLM.from_pretrained( path_to_adapter, device_map="auto", trust_remote_code=True ).eval() # 合并模型 merged_model = model.merge_and_unload() # 保存合并后的模型 merged_model.save_pretrained(new_model_directory, max_shard_size="2048MB", safe_serialization=True) # 加载并保存分词器 tokenizer = AutoTokenizer.from_pretrained( path_to_adapter, trust_remote_code=True ) tokenizer.save_pretrained(new_model_directory) print("模型和分词器已成功保存。") except Exception as e: print(f"发生错误: {e}") if __name__ == "__main__": # LoRA 模型位置 path_to_adapter = "/root/autodl-tmp/test1/Qwen/output_qwen/checkpoint-1000" # 合并模型存放位置 new_model_directory = "/root/autodl-tmp/test1/Qwen/models/Qwen-1_8B-Chat_finetune" merge_and_save_model(path_to_adapter, new_model_directory)
-
合并操作讲解
- Peft(Parameter-Efficient Fine-Tuning)允许用户在预训练模型的基础上,通过添加适配器(如LoRA)来进行微调
- 模型合并流程,核心步骤:
- 加载适配器(Adapter):通过 AutoPeftModelForCausalLM.from_pretrained 加载微调后的 LoRA 适配器。
- 合并模型:调用 merge_and_unload() 将适配器与基座模型合并。
- 保存完整模型:保存合并后的模型和分词器,此时merged_model就是合并后的完整模型,可以独立使用,不再需要适配器
- 注意
- PEFT 框架在保存适配器(如 LoRA)时,会 自动记录基座模型的信息(如模型名称、路径)
- 保存在适配器目录的
adapter_config.json
中,加载适配器时,Peft会自动从指定的路径或模型仓库下载基座模型 - 当调用
AutoPeftModelForCausalLM.from_pretrained(path_to_adapter)
时 - 代码会:从
adapter_config.json
读取基座模型名称(如Qwen/Qwen-1_8B-Chat
)。
垂直大模型微调前后推理效果对比实战
-
需求
-
对比本地私有化部署的千问大模型,微调前后的回答效果测试
-
看到微调前和微调后的结果差别,微调后的模型在遇到相同问题的时候可以做出更专业的回答
-
注意:
- 不采用BLUE方式评估,直接看同个问题的大模型回复效果
- BLEU(Bilingual Evaluation Understudy)是一个常用的机器翻译评价指标
- 通过比较机器生成的译文与参考译文之间的n-gram重叠程度来评估质量。
- BLEU分数范围在0到1之间,或者以百分比表示,分数越高表示生成的文本与参考文本越接近
-
-
案例实战
- 创建
fine_tune_inference.py
文件
from transformers import AutoModelForCausalLM, AutoTokenizer from transformers.generation import GenerationConfig def get_model_response(model_path, input_text): try: tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_path, device_map="cuda", trust_remote_code=True, force_download=True ).eval() response, history = model.chat(tokenizer, input_text, history=None) return response except Exception as e: print(f"发生错误: {e}") return None input_text = "请预测这个案子的判决\n公诉机关指控,被告人贾某某因年迈患病,欲给自己和妻子准备棺材,遂于2013年三四月间,在宽甸满族自治县大川头镇新丰村七组曲西沟被害人杜某某所有的天然林中,擅自砍伐55年生的落叶松树1株。数日后,其又在该组半截沟被害人韩某甲、韩某乙共同所有的人工林中,擅自砍伐平均60年生杉松树2株。贾某某两次共计砍伐树木3株。2013年10月16日,经宽甸满族自治县森林公安局鉴定,贾某某所盗伐树木核立木蓄积3.5187立方米。2013年10月22日,宽甸满族自治县森林公安局将贾某某盗伐所获的大部分木材分别返还给韩某甲、杜某某,并取得被害人谅解。公诉机关提供了相应的证据,认为被告人贾某某的行为触犯了《中华人民共和国《刑法》》××××之规定,应以××罪处罚。被告人贾某某能如实供述自己的罪行,应依据《中华人民共和国《刑法》》××××之规定处罚。\n这个案子可能怎么判做出可能的裁断\n公诉机关指控,被告人贾某某因年迈患病,欲给自己和妻子准备棺材,遂于2013年三四月间,在宽甸满族自治县大川头镇新丰村七组曲西沟被害人杜某某所有的天然林中,擅自砍伐55年生的落叶松树1株。数日后,其又在该组半截沟被害人韩某甲、韩某乙共同所有的人工林中,擅自砍伐平均60年生杉松树2株。贾某某两次共计砍伐树木3株。2013年10月16日,经宽甸满族自治县森林公安局鉴定,贾某某所盗伐树木核立木蓄积3.5187立方米。2013年10月22日,宽甸满族自治县森林公安局将贾某某盗伐所获的大部分木材分别返还给韩某甲、杜某某,并取得被害人谅解。公诉机关提供了相应的证据,认为被告人贾某某的行为触犯了《中华人民共和国《刑法》》××××之规定,应以××罪处罚。被告人贾某某能如实供述自己的罪行,应依据《中华人民共和国《刑法》》××××之规定处罚。" # 微调前的模型 pre_finetune_model_path = "/root/autodl-tmp/test1/Qwen/models/Qwen-1_8B-Chat" pre_finetune_response = get_model_response(pre_finetune_model_path, input_text) if pre_finetune_response: print("微调前的结果:" + pre_finetune_response) # 微调后的模型 post_finetune_model_path = "/root/autodl-tmp/test1/Qwen/models/Qwen-1_8B-Chat_finetune" post_finetune_response = get_model_response(post_finetune_model_path, input_text) if post_finetune_response: print("微调后的结果:" + post_finetune_response)
- 创建
高阶内容一统天下的MCP技术应用实战
MCP诞生的需求背景和项目效果演示
-
需求背景
- 大模型(如DeepSeek、GPT)无法直接访问本地文件、数据库或企业系统等
- 如需要和外部交互,则需要开发Tool工具,常规集成方式存在以下痛点
- 传统函数调用:单点功能实现,需为每个工具单独开发接口,开发成本高,效率低,扩展性差。
- 上下文局限:模型上下文窗口有限,难以处理长文本或动态数据。
- 规范不统一: 每个接口工具没有标准规范,导致难以维护和工程化
-
MCP 的诞生与目标
-
2024 年 11 月, Anthropic 在推出的用于 LLM 应用和外部数据源(Resources)或工具(Tools)通信的标准协议
-
通过标准化协议解决上述问题
-
统一通信标准:类似 USB-C 接口,使 AI 模型可通过统一协议连接各类数据源和工具。
-
提升灵活性:支持本地资源(如数据库)和远程服务(如 API)的无缝集成。
-
MCP 协议:分层解耦架构,开发者只需按协议规范开发 MCP Server,即可被所有 MCP Client 调用
-
建议:
-
目前的 MCP 开发非常初级,在工程化上缺少一套完善的框架来约束和规范
-
新技术出来需要2~3年,规范调整、使用方式变化大,建议个人学习拓展为主,大规模商用还需要时间
-
-
-
-
相关资源(地址如果过期可以搜索资料)
-
Anthropic MCP 官网:提供协议规范、API 文档和入门指南
https://modelcontextprotocol.org
-
OpenAI Agent SDK:支持 MCP 协议,提供智能体开发框架
https://openai.github.io/openai-agents-python/mcp
-
MCP Inspector:可视化工具,用于测试和调试 MCP 服务器
https://github.com/modelcontextprotocol/inspector
-
MCP集合:发现优秀的 MCP 服务器 和客户端
github.com/punkpeye/awesome-mcp-servers mcp.so smithery.ai bailian.console.aliyun.com
-
-
MCP案例效果演示(参考高德地图官方案例)
-
案例介绍
- 后天计划到广州游玩,如何使用AI+高德MCP制作3天旅行攻略。
- 将生成的旅行攻略导入高德地图APP生成专属地图,以满足行中探店、导航、打车、购票等出行需求。
-
需求描述
##我后天计划去广州游玩3天的旅行,放松身心几天。 #帮制作旅行攻略,考虑出行时间和路线,以及天气状况路线规划。 #制作网页地图自定义绘制旅游路线和位置。 ##网页使用简约美观页面风格,景区图片以卡片展示。 #行程规划结果在高德地图app展示,并集成到h5页面中。 ##同一天行程景区之间我想打车前往。 #生成文件名 kmTravel.html。
-
MCP本质:就是统一了对接协议+多个工具封装
-
大模型+MCP里的角色你知道有哪些吗
-
需求
- 掌握MCP(Model Context Protocol)中的核心角色
- 包括MCP主机(Host)、MCP客户端(Client)、MCP服务器(Server)、数据源/外部工具和智能体(Agent)
-
核心角色讲解
-
MCP 主机(Host)
-
功能定义
- 获取数据的AI工具或环境,例如集成了ChatGPT聊天客户端、集成开发环境(IDE)、包括Agent等
- 核心职责:运行AI模型或应用,发起对数据和工具的请求。
- 典型形态:AI驱动的应用(如聊天机器人、IDE插件、个人助理)或大模型本身客户端(如DeepSeek、GPT)
-
例子说明
-
智能助手
- 如AI助手,用户通过自然语言提问,主机将请求转换为MCP协议格式,触发后续工具调用(如查询日历或购物App
-
IDE开发工具
- Cursor或VS Code插件、IDEA作为主机,请求代码库、文档或执行Git操作
-
-
-
MCP 客户端(Client)
-
功能定义
- 此组件内嵌于Host中,负责与MCP服务器进行通信
- 核心职责:作为主机与服务器之间的通信桥梁,负责传输请求和响应。
- 技术实现:通常以SDK或中间件形式存在,处理协议转换和消息路由
-
示例说明
- 蚂蚁“百宝箱”平台
- 开发者通过客户端快速调用支付宝、高德地图等服务的MCP接口,无需直接处理API密钥,客户端负责加密和传输
- 代码助手工具
- 支持AI的IDE中,客户端将用户指令如“读取文件”转换为MCP请求,发送至文件系统服务器,并将结果返回给AI模型
- 蚂蚁“百宝箱”平台
-
-
MCP 服务器(Server)
-
功能定义
- 暴露具体功能的程序,如与数据库、API或其他本地数据源的对接,通过统一协议向外部提供服务,服务的具体提供者
- 暴露的内容包括:外部数据源(Resources)、工具(Tools)、提示词(Prompts)等,被 Client 进行调用
- 核心职责:提供标准化接口,连接具体数据源或工具,执行主机的请求。
- 技术特性:支持本地部署(如数据库)或远程服务(如Slack API),内置安全权限控制
-
示例说明
-
高德地图
- 高德地图的MCP服务器会调用对应的接口,完成对应的操作
-
区块链查询服务器:
- 交易所的MCP服务器提供ERC20代币余额查询功能,AI模型通过该服务器获取链上实时数据
-
-
-
数据源/外部工具(Data Sources & Tools)
-
功能定义
- 核心职责:实际存储数据或提供功能的服务,如数据库、API、硬件设备等。
- 分类:本地资源(文件系统、传感器)或远程资源(云服务、区块链网络)
-
示例说明
-
地理信息服务:高德地图通过MCP服务器暴露API,智能体可调用其实时交通数据规划路线
-
金融工具:DexPaprika的MCP服务器提供500万+代币的实时价格数据,支持AI驱动的套利分析
-
-
-
智能体(Agent)
-
功能定义
- 核心职责:基于大模型的决策中枢,动态规划工具调用流程,完成复杂任务。
- 技术特性:通过元数据学习工具用法,支持多步骤协作(如调用地图API后生成PPT)。
-
示例说明
- 旅行规划智能体:
- 用户提问“规划杭州到新加坡行程”,智能体依次调用航班查询、酒店推荐、地图导航多个MCP服务器,生成完整方案
- 自动化开发Agent:
- Google的Code Assist通过ADK框架构建智能体,调用Git工具提交代码或运行测试,减少人工干预
- 旅行规划智能体:
-
-
面试题-Function Call工具和MCP技术对比
-
MCP解决的核心问题
- 协议标准化
- 通过定义统一的通信规范(类似 USB-C 接口),将传统 Tool/Function Call 的 “定制化适配” 转化为 “标准化接入”
- 例如,开发一个 MCP 兼容的数据库服务器后,可被 千问、GPT-4 等所有支持 MCP 的模型调用,无需重复开发
- 安全与灵活性
- 传统 Function Call 中,工具调用的 API 密钥可能直接暴露给 LLM,存在安全风险。
- MCP 通过 Server 代理访问,密钥存储在本地服务器,模型仅获取脱敏后的数据。
- 例如,医疗场景中,MCP Server 可将患者隐私数据处理为匿名化特征后再传递给模型
- 效率提升
- 传统集成中,开发者需为每个工具编写 API 调用代码(如天气查询需处理 HTTP 请求、JSON 解析等)
- MCP 提供标准化 SDK,开发者只需关注业务逻辑,接口适配成本趋近于零。
- 协议标准化
-
**面试题:那是不是Tool工具没用了,都是用MCP了?应该如何选择 **
-
Tool工具和MCP对比
技术演进阶段 常规 Tool/Function Call MCP 协议 单点集成 每个工具独立开发接口,形成 “烟囱式” 架构(如为 每个API,数据库、 分别编写适配代码)。 统一协议抽象,工具通过 MCP Server 注册能力,支持动态发现与组合。 跨工具协作 手动串联多个工具调用(如先调用天气 API,再调用地图 API),流程硬编码且难以维护。 支持工具链自动编排(如模型根据任务需求智能选择工具组合) 多模型兼容 绑定特定模型,迁移成本高(如从 GPT-3.5 迁移至 Claude 需重写所有工具调用逻辑)。 统一资源接口,模型切换无需修改工具层代码。 协议 JSON-Schema或者字符串,静态函数调用 JSON-RPC,支持双向通信(但目前使用不多),支持可发现性、更新通知能力 -
如何选择场景化决策
- 优先选择 MCP 的场景
- 复杂企业级应用:需集成多个异构系统(如 ERP、CRM、邮件、数据库),且未来可能扩展新工具。
- 跨模型部署:需支持多 LLM 供应商(如同时使用 Claude 和 GPT)。
- 敏感数据处理:涉及医疗、金融等受监管数据,需严格权限控制和审计
- 优先选择 Function Call 的场景
- 快速原型开发:简单功能验证(如天气查询、计算器),追求最短开发周期。
- 封闭环境应用:工具逻辑完全内置于当前系统(如内部知识库问答),无需外部交互。
- 深度依赖特定模型:需利用专有功能(如 GPT-4 的代码解释器)。
- 混合架构策略
- 对于大型项目,可采用 “MCP+Function Call” 混合架构:
- 核心业务流程:通过 MCP 协议实现标准化集成(如数据库、API)。
- 边缘功能模块:使用 Function Call 快速实现轻量级工具调用(如简单计算器)。
- 这种策略既能享受 MCP 的扩展性与安全性,又能保留 Function Call 的敏捷性。
- 优先选择 MCP 的场景
-
MCP部署模式和通信原理讲解
-
MCP服务类型
-
MCP服务根据部署模式、功能场景和技术架构可分为多种类型,从本地工具到云端服务、从通用能力到垂直行业的多样化
-
本地服务
- 基于STDIO的本地工具集成
- 核心特征:通过标准输入输出(STDIO)与本地进程交互,无需网络配置,安全性高。
- 典型场景:
- 文件操作:读写本地文件系统(如通过Cursor插件管理代码库)
- 限制:仅限单机使用,无法扩展为分布式服务
- 基于SSE的本地服务
- 核心特征:使用Server-Sent Events(SSE)协议在本地或局域网内实现长连接通信。
- 典型场景
- 浏览器插件:如通过SSE连接本地数据库查询数据
- 优势:支持事件流推送,适合实时性要求高的场景
- 基于STDIO的本地工具集成
-
远程/云端服务
-
-
MCP常见的通信机制
-
在MCP(Model Context Protocol)中,STDIO(标准输入输出)和SSE(Server-Sent Events)是两种核心数据传输协议
-
STDIO(标准输入输出)
-
基本定义
- 通信方式:基于本地进程间通信(IPC),通过标准输入(stdin)和标准输出(stdout)传输数据。
- 适用场景:本地开发调试、单机工具集成(如IDE插件、CLI工具)。
-
工作原理
- 数据流:MCP Client与Server运行在同一台机器上,通过操作系统提供的管道(Pipe)交换数据。
Model (Host) → stdin → MCP Server → stdout → Model (Host)
- 消息格式:JSON序列化数据,每条消息以换行符分隔
-
优缺点
优点 缺点 零网络延迟,响应快(微秒级) 仅限本地进程间通信 无需网络配置,安全性高(无暴露端口) 无法扩展为分布式系统 开发调试简单,适合快速验证 多进程管理复杂
-
-
SSE(Server-Sent Events)
-
基本定义
- 通信方式:基于HTTP协议的服务器推送技术,服务器可主动向客户端发送事件流。
- 适用场景:远程服务调用、Web应用集成(如浏览器插件)、多节点分布式系统。
-
工作原理
- 数据流:通过长连接(Long Polling)保持通信,服务器持续推送数据:
Model (Host) → HTTP Request → MCP Server → Event Stream → Model (Host)
- 消息格式:符合SSE规范的事件流(text/event-stream),每条消息以data:开头
例如: event: weather_update data: {"city": "北京", "temp": "22℃"}
-
优缺点
优点 缺点 支持跨网络通信,可部署为远程服务 网络延迟较高(毫秒级) 天然兼容Web生态(浏览器原生支持) 需处理连接稳定性(如断线重连) 支持多客户端订阅同一事件流 需配置HTTPS保证安全性
-
-
-
协议对比与选型建议
维度 STDIO SSE 通信范围 仅限本地进程 支持跨网络、跨设备 延迟 微秒级(无网络开销) 毫秒级(依赖网络质量) 扩展性 单机部署 支持分布式、集群化部署 安全性 高(无暴露端口) 需配置TLS/HTTPS加密 开发复杂度 简单(无需网络代码) 需处理HTTP连接与错误恢复 典型场景 IDE插件、本地脚本工具 云服务、浏览器应用、微服务 - 本地开发优先选择STDIO
- 快速验证工具功能,无需搭建网络环境。
- 示例:在VS Code插件中调用本地Git工具。
- 生产环境推荐SSE
- 需通过反向代理(如Nginx)配置负载均衡和HTTPS。
- 示例:企业知识库服务部署在云端,供多地办公室调用。
- 混合使用场景
- 本地服务使用STDIO,远程服务通过SSE集成。
- 示例:个人助理同时管理本地日历(STDIO)和查询天气API(SSE)
- 本地开发优先选择STDIO
MCP常见命令行工具和包管理说明
-
npx
:Node.js 的 “即跑即用” 执行工具-
是 Node.js 官方随
npm
内置的包执行工具,核心作用是无需全局安装,直接运行 Node.js 包中的可执行命令。 -
npx是npm命令的升级版本,功能强大, 侧重于执行命令的,执行某个模块命令,会自动安装模块,但是重在执行某个命令。
-
在 MCP 开发中,
npx
主要用于调试 MCP 服务器或执行与 MCP 协议相关的 Node.js 工具 -
安装(有node环境即可,即npm存在就可以,前端环境搭建的时候操作过)
npx --version
-
核心功能:
- 执行本地已安装包的命令(自动查找
node_modules/.bin
); - 临时下载远程包并执行(用完即删,不污染全局);
- 执行本地已安装包的命令(自动查找
-
常见命令
场景 命令示例 说明 执行本地包命令 npx jest
运行本地安装的 Jest 测试工具 临时运行远程包 npx create-react-app my-app
临时下载 create-react-app
并创建项目传递环境变量 npx -e DEBUG=true node server.js
设置环境变量 DEBUG=true
后运行server.js
-
-
uv
:Rust 编写的 Python 极速包管理器-
uv 即 (Ultrafast Virtual environment)是用 Rust 开发的 Python 包与项目管理器,主打极速创建虚拟环境和依赖隔离。
-
相比传统
venv
+pip
,uv
通过缓存和并行下载优化,大幅提升环境创建速度,同时支持更简洁的命令行操作 -
在 MCP 开发中,
uv
主要用于管理 Python 实现的 MCP 服务器或工具的依赖环境,适合需要快速创建、切换开发环境的场景 -
安装(存在Python环境直接安装即可)
#安装 pip install uv #查看 uv --version #(myenv) xdclass@xdclassdeMacBook-Pro-2 ~ % uv --version #uv 0.7.4 (6fbcd09b5 2025-05-15)
-
核心功能:
- 极速创建虚拟环境(自动隔离 Python 版本和依赖);
- 安装 / 卸载包(支持pip兼容的包源);
-
常见命令
场景 命令示例 说明 创建虚拟环境 uv new
在当前目录生成 .uv
虚拟环境安装包 uv install pandas
安装 pandas
到当前环境运行脚本 uv run python script.py
在 uv 环境中执行 script.py
查看环境信息 uv info
显示当前环境的 Python 版本、依赖列表
-
-
uvx
:uv 的 “临时环境执行” 扩展工具-
uvx
是uv
工具链的扩展命令,定位为临时环境执行器。 -
无需提前创建环境,直接为单次命令创建临时虚拟环境,执行后自动清理,适合一次性工具调用
-
在 MCP 中,
uvx
主要用于临时运行 Python 实现的 MCP 工具(如官方的mcp-server-fetch
) -
核心功能:
- 无需预安装,直接运行远程或本地工具;
- 自动解析工具依赖(通过pyproject.toml或requirements.txt);
-
常见命令
场景 命令示例 说明 临时运行工具 uvx black .
用临时环境运行代码格式化工具 black
传递参数 uvx mcp-server-fetch --port=8080
向 mcp-server-fetch
传递端口参数指定 Python 版本 uvx -p 3.10 my-tool
临时环境使用 Python 3.10
-
-
总结
-
npx
是 Node.js 的 “即跑即用” 工具,适合调试 MCP 的 Node.js 服务; -
uv
是 Python 的极速包管理器,用于高效管理项目依赖环境; -
uvx
是uv
的扩展,主打临时环境执行,适合一次性 MCP 工具调用 -
不同的MCP服务安装
-
托管本地 MCP 服务(stdio):
- npx:启动使用 Node.js 开发的 MCP 服务
- uvx:启动使用 Python 开发的 MCP 服务。
-
如果需要连接远程 MCP 服务(SSE):
- SSE:连接到一个已有的、运行在别处的远程 MCP 服务器
-
-
AI编辑器+MCP实战之高德地图MCPServer
-
需求
- MCP案例快速上手,通过AI编辑器集成MCP,提高开发效率,体验MCP好处
- 掌握AI编辑器使用技巧,多数编辑器都是大同小异,核心还是大模型能力
- 掌握高德地图开发者相关知识,拓展技术面和API开发
-
案例实战
- AI编辑器很多,包括Cursor(花钱)、Windsurf、字节的Trae等,我们这边选择国内的Trae进行操作
- Trae介绍
- 字节跳动于 2025 年推出的全球首款 AI 原生集成开发环境(AI IDE),定位为 “开发者的智能协作伙伴”。
- 其核心突破在于将 AI 能力深度嵌入开发全流程,实现从需求分析到代码生成、调试的端到端自动化
- 选择理由,免费与性价比
- 提供 Claude 3.5、GPT-4o 等模型免费不限量调用,国内开发者无需翻墙即可享受顶级 AI 能力。
- 基础功能永久免费,企业版提供私有化部署、高级安全认证(如 ISO 27001)等增值服务。
- 与 Cursor 对比:Trae 更侧重中文支持、企业级协作及全流程自动化,Cursor 则强于代码调试和复杂项目优化
- 官网地址:https://www.trae.com.cn/
-
配置MCP(新版才支持MCP,如果有同学下载过,记得更新)
-
高德地图开发者配置
-
MCP案例效果演示(参考高德地图官方案例)
-
案例介绍
- 后天计划到广州游玩,如何使用AI+高德MCP制作3天旅行攻略。
- 将生成的旅行攻略导入高德地图APP生成专属地图,以满足行中探店、导航、打车、购票等出行需求。
-
需求描述
#高德地图MCPServer配置 { "mcpServers": { "amap-maps": { "command": "npx", "args": [ "-y", "@amap/amap-maps-mcp-server" ], "env": { "AMAP_MAPS_API_KEY": "换自己的key" } } } } #文件系统MCPServer { "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/xdclass/Desktop/mcp-test" ] } } }
##我后天计划去广州游玩3天的旅行,放松身心几天。 #帮制作旅行攻略,考虑出行时间和路线,以及天气状况路线规划。 #制作网页地图自定义绘制旅游路线和位置。 ##网页使用简约美观页面风格,景区图片以卡片展示。 #行程规划结果在高德地图app展示,并集成到h5页面中。 ##同一天行程景区之间我想打车前往。 #生成文件名 kmTravel.html。
-
MCP本质:就是统一了对接协议+多个工具封装
-
高阶内容-MCP Server服务开发实战
手把手教你开发自己的MCPServer《上》
-
需求
- 掌握MCP开发的知识点,开发自己的专属MCPServer
- 案例实现:基于高德地图接口,开发定制化的MCP服务
-
MCP开发讲解
@mcp.tool()
装饰器- 用于标记工具函数,将普通Python函数注册为MCPServer可调用的服务方法。
- 通常配合协议解析器使用,自动暴露函数到服务接口。
@mcp.resource
装饰器- 用于定义资源型接口(类似RESTful API),处理HTTP请求或自定义协议的资源操作。
- 通常与URL路由或协议动作绑定,支持CRUD操作。
-
MCP 服务开发实战
-
将 MCP 添加到Python 项目中,使用 uv 来管理Python 项目。
uv init mcp-server-demo cd mcp-server-demo
-
创建虚拟环境和激活
uv venv source .venv/bin/activate
-
将 MCP 添加到项目依赖中
uv add "mcp[cli]" httpx
-
开发MCPServer
-
创建weather.py
touch weather.py
-
编写代码
from typing import Any, Optional import httpx from mcp.server.fastmcp import FastMCP # 假设FastMCP框架与原代码兼容 # 初始化FastMCP服务器(服务名称改为"amap-weather") mcp = FastMCP("amap-weather") # 高德地图API常量(需替换为实际值) AMAP_API_BASE = "https://restapi.amap.com/v3" AMAP_API_KEY = "e63c1ecea1eddd3f86918014c07a9a2d" # 占位符:替换为你的高德API Key USER_AGENT = "amap-weather-app/1.0" async def make_amap_request(endpoint: str, params: dict) -> dict[str, Any] | None: """ 通用高德API请求函数(带错误处理) Args: endpoint: API接口路径(如"/weather/weatherInfo") params: 请求参数(自动附加key和基础参数) """ print("正在发起高德API请求...make_amap_request") base_params = { "key": AMAP_API_KEY, "output": "JSON" } full_params = {**base_params, **params} async with httpx.AsyncClient() as client: try: response = await client.get( f"{AMAP_API_BASE}{endpoint}", params=full_params, headers={"User-Agent": USER_AGENT}, timeout=30.0 ) response.raise_for_status() # 检查HTTP状态码(非200抛异常) data = response.json() # 处理高德API业务错误码(如key无效、参数错误) if data.get("status") != "1": err_msg = data.get("info", "未知错误") print(f"高德API请求失败:{err_msg}") return None return data except Exception as e: print(f"请求异常:{str(e)}") return None def format_realtime_weather(data: dict) -> str: """格式化实时天气数据为可读文本""" realtime = data["lives"][0] # 高德实时天气数据在lives数组中 return f""" 城市:{realtime['city']} 实时天气:{realtime['weather']}({realtime['temperature']}°C) 湿度:{realtime['humidity']}% 风向:{realtime['winddirection']} 风速:{realtime['windpower']}级 更新时间:{realtime['reporttime']} """ def format_forecast(data: dict) -> str: """格式化未来天气预报数据为可读文本""" forecasts = [] for day_data in data["forecasts"][0]["casts"]: # 高德预报数据在casts数组中 forecast = f""" 日期:{day_data['date']} 白天天气:{day_data['dayweather']} 夜间天气:{day_data['nightweather']} 最高温:{day_data['daytemp']}°C 最低温:{day_data['nighttemp']}°C 风向:{day_data['daywind']} 风力:{day_data['daypower']}级 """ forecasts.append(forecast) return "\n---\n".join(forecasts)
-
-
手把手教你开发自己的MCPServer《下》
-
完善
weather.py
@mcp.tool() async def get_realtime_weather(city_adcode: str) -> str: """ 获取中国城市实时天气(通过高德地图API) Args: city_adcode: 城市编码(如北京110000,上海310000,可通过高德城市编码表查询) """ print("正在获取实时天气...get_realtime_weather") data = await make_amap_request( endpoint="/weather/weatherInfo", params={"city": city_adcode, "extensions": "base"} # extensions=base表示实时天气 ) if not data or not data.get("lives"): return "无法获取实时天气数据(请检查城市编码或API Key)" return format_realtime_weather(data) @mcp.tool() async def get_forecast(city_adcode: str) -> str: """ 获取中国城市未来3天天气预报(通过高德地图API) Args: city_adcode: 城市编码(如北京110000,上海310000,可通过高德城市编码表查询) """ data = await make_amap_request( endpoint="/weather/weatherInfo", params={"city": city_adcode, "extensions": "all"} # extensions=all表示预报 ) if not data or not data.get("forecasts"): return "无法获取天气预报数据(请检查城市编码或API Key)" return format_forecast(data) if __name__ == "__main__": # 启动MCP服务器(通过标准输入输出通信) mcp.run(transport='stdio')
-
AI编辑器配置MCP
{ "mcpServers": { "weather": { "command": "uv", "args": [ "--directory", "/Users/xdclass/Desktop/mcp-server-demo", "run", "weather.py" ] } } }
-
测试
问题一:北京天气如何 问题而:北京未来3天的天气如何
MCP服务调试之MCP Inspector实战
-
什么是MCP Inspector
- MCP Inspector 是专为 Model Context Protocol (MCP) 服务器设计的交互式调试工具,由 Anthropic 公司主导开发,属于 MCP 生态的组件之一
- 为开发者提供了直观的界面和全流程测试功能,用于验证 MCP 服务器的功能、协议兼容性
- MCP Inspector 解决了传统调试方法(如日志分析、手动调用 API)效率低、可视化差的问题,支持从基础功能测试到复杂边界条件验证
- 注意:工具变动很大,了解为主
-
安装与启动
-
方式一
# 使用 npm 运行(推荐) npx @modelcontextprotocol/inspector # 连接本地服务器 浏览器访问 http://localhost:端口
-
方式二: 执行以下命令可以启动 MCP Inspector:
# server.py 是MCP Server文件的名称,会提示是否安装依赖包 输入 y 即可 mcp dev server.py # 连接本地服务器 浏览器访问 http://localhost:端口
-
-
基础操作流程
- 连接服务器:输入服务器地址(如
http://127.0.0.1:5173
),选择传输协议(stdio/HTTP)。 - 浏览服务:查看服务器提供的资源(如 “本地文件”)、工具(如 “统计 TXT 文件数量”)和提示模板。
- 执行测试:在工具输入框填入参数(如关键词 “大模型”),点击 “Run” 触发调用,查看响应结果和日志
- 连接服务器:输入服务器地址(如
LLM大模型整合MCP交互流程全链路解析
-
MCP Client 客户端
- 作为连接 LLM 与 MCP 服务器的核心枢纽,负责与MCP服务器进行通信
- 技术实现:通常以SDK或中间件形式存在,处理协议转换和消息路由
-
面试官:大模型、MCP 客户端和MCPServer交互形式是怎样的?
-
参与者(Participants)
- 用户(User):交互起点,发起自然语言查询。
- MCP 客户端(MCP Client):负责协议中转、工具封装、结果处理,支持多终端(Web/APP/CLI)。
- LLM:执行语义理解、工具决策、响应生成,如 DeepSeek/GPT-X 等大模型。
- MCP 服务器(MCP Server):提供具体工具服务(如数据库查询、文件操作),遵循 MCP 协议标准。
-
整体细化工作流程如下
- 初始化工具发现
- MCP 客户端首先通过 MCP 协议(支持 stdio/HTTP+SSE 等传输方式)向服务器发起元数据请求
- 获取包含工具名称、功能描述、输入输出参数规范的完整工具列表(Tool Catalog)
- 查询 - 工具语义封装
- 客户端将用户原始查询与服务器返回的工具元数据进行结构化整合
- 通过 Function Calling 标准接口封装为包含工具候选集的复合请求体,确保 LLM 能基于标准化格式理解可用能力
- LLM 决策触发
- 封装后的请求进入 LLM 处理链路,模型通过内置决策逻辑(如工具使用阈值、参数匹配度算法)判断是否需要调用外部工具
- 当查询涉及专业领域数据(如数据库查询、文件操作)时,触发工具调用流程,纯文本生成类任务则直接进入响应生成阶段
- 工具执行调度
- 若 LLM 输出工具调用指令,客户端解析指令中的工具标识与入参
- 通过 MCP 协议向对应服务器发起远程过程调用(RPC),支持同步 / 异步两种执行模式。
- 结果反序列化处理
- 服务器完成工具执行后返回结构化结果(如 JSON 格式的数据库查询结果、文件内容二进制流)
- 客户端按照工具元数据定义的输出格式进行反序列化,转换为 LLM 可处理的自然语言输入。
- 多轮交互闭环构建
- 客户端将工具执行结果与原始用户查询进行上下文拼接,形成包含历史交互信息的完整对话上下文
- 再次提交给 LLM 进行最终响应生成,支持复杂多步任务的迭代处理。
- 自然语言界面呈现
- LLM 生成的最终响应(包含文本、富媒体等多模态内容)通过客户端的用户界面组件进行渲染
- 支持 Web、桌面端、移动端等多终端输出,完成从工具调用到用户交互的全流程闭环
- 初始化工具发现
-
大模型开发MCPClient+Server综合实战《上》
-
需求
-
LLM大模型集成MCP,使用之前开发的MCP Server进行开发
-
编写代码集成MCP Client,大模型根据需求决定是否调用MCP
-
实现了一个基于MCP(工具调用协议)和阿里云通义千问 LLM的天气查询助手
-
核心逻辑:
- 用户输入自然语言查询(如 “上海明天天气”)。
- 通义千问解析查询,生成工具调用指令(JSON 格式)。
- 通过 MCP 协议调用服务端工具(如获取天气预报的接口),返回结果
-
技术选型
-
异步编程:
asyncio
+httpx
(HTTP 客户端)。 -
工具调用协议:MCP(通过
mcp
库实现客户端与服务端通信)。 -
LLM 模型:通义千问
qwen-plus
(阿里云 DashScope 平台)
-
-
注意:使用
stdio
方式进行通信时,MCP 服务器的进程由 MCP 客户端代码程序负责启动
-
-
开发实战
-
关键配置项
DASHSCOPE_API_KEY = "sk-xxx" # 阿里云DashScope平台申请的API Key TONGYI_MODEL = "qwen-plus" # 使用的通义千问模型
-
环境搭建
# 创建目录 uv init mcp-client cd mcp-client # 创建虚拟环境 uv venv # 激活虚拟环境 # On Windows: .venv\Scripts\activate # On Unix or MacOS: source .venv/bin/activate # 安装必备依赖包 uv add mcp # 创建文件 touch client.py
-
编码实现
client.py
- 代码流程参考MCP官方案例:https://modelcontextprotocol.io/quickstart/client
import asyncio import json import sys from typing import Optional from contextlib import AsyncExitStack import httpx # ClientSession:MCP客户端会话;StdioServerParameters:标准输入输出服务端配置参数 from mcp import ClientSession, StdioServerParameters # 标准输入输出客户端,用于通过控制台与服务端通信(适用于本地进程间通信) from mcp.client.stdio import stdio_client # 配置项(需替换为实际值) DASHSCOPE_API_KEY = "sk-0903038424424850a88ed161845d7d4c" # 阿里云DashScope平台API Key TONGYI_MODEL = "qwen-plus" # 使用qwen-plus模型 class MCPClient: """MCP客户端,用于连接服务端并通过LLM调用工具""" def __init__(self): """初始化MCP客户端和通义千问API连接""" # MCP客户端会话(用于与服务端通信,初始为None,连接后赋值) self.session: Optional[ClientSession] = None # 异步资源栈(用于自动管理后续创建的异步上下文,如连接、会话等,确保资源正确释放) self.exit_stack = AsyncExitStack() self.tools = {} # 存储可用工具的字典 self.tool_descriptions = [] # LLM可用的工具描述 async def connect_to_server(self, server_script_path: str): """ 连接到MCP服务端 Args: server_script_path: 服务端脚本路径(.py或.js) """ # 验证脚本类型 is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("服务端脚本必须是.py或.js文件") # 确定执行命令 command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) # 建立与服务端的连接 print(f"正在连接到服务端: {server_script_path}...") stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # 获取并展示可用工具 response = await self.session.list_tools() self.tools = {tool.name: tool for tool in response.tools} # 美化工具列表输出 print("\n✓ 已成功连接到服务端") print("🔧 可用工具:") for tool_name in self.tools: print(f" - {tool_name}") # 为LLM准备工具描述 self.tool_descriptions = [ { "name": tool.name, "parameters": json.loads(tool.inputSchema) if isinstance(tool.inputSchema, str) else tool.inputSchema, "description": tool.description } for tool in response.tools ] async def call_tool(self, tool_name: str, parameters: dict) -> str: """ 调用MCP服务端的工具 Args: tool_name: 工具名称 parameters: 工具参数 Returns: 工具执行结果 """ if tool_name not in self.tools: raise ValueError(f"未知工具: {tool_name}") # 调用前显示进度 print(f"🚀 正在调用工具: {tool_name}") print(f"🔧 参数: {json.dumps(parameters, ensure_ascii=False, indent=2)}") result = await self.session.call_tool(tool_name, parameters) return result.content
-
大模型开发MCPClient+Server综合实战《下》
-
继续完善代码
async def query_llm(self, user_query: str) -> dict: """ 调用通义千问API解析用户查询为工具调用指令 Args: user_query: 用户查询内容 Returns: 解析后的工具调用指令字典 """ print(f"🤖 正在分析查询: {user_query}") api_url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" # 系统提示词:指导模型使用工具 system_prompt = f""" 你是一个天气信息查询助手。用户的问题将由以下工具回答: {json.dumps(self.tool_descriptions, indent=2, ensure_ascii=False)} 请直接生成JSON格式的工具调用指令,不要包含其他解释。 示例: 用户输入:"上海明天的天气如何?" → {{ "name": "get_forecast", "parameters": {{ "city_adcode": "310000" }} }} """.strip() headers = { "Authorization": f"Bearer {DASHSCOPE_API_KEY}", "Content-Type": "application/json" } payload = { "model": TONGYI_MODEL, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query} ], "parameters": { "temperature": 0.1, # 极低随机性 "max_tokens": 128, # 限制生成长度 } } # 发送请求并处理响应 async with httpx.AsyncClient() as client: print("🌐 正在请求LLM服务...") response = await client.post(api_url, headers=headers, json=payload) response.raise_for_status() result = response.json() tool_call = result.get("choices", [{}])[0].get("message", {}).get("content", "{}") try: print("✓ 成功获取工具调用指令") return json.loads(tool_call) except json.JSONDecodeError: print(f"⚠️ 无效的工具调用格式: {tool_call}") return None async def process_query(self, user_query: str) -> str: """ 处理用户查询,调用LLM并执行工具 Args: user_query: 用户查询内容 Returns: 最终响应结果 """ # 获取工具调用指令 tool_call = await self.query_llm(user_query) if not tool_call or "name" not in tool_call or "parameters" not in tool_call: return "抱歉,无法理解您的查询。" tool_name = tool_call["name"] parameters = tool_call["parameters"] try: return await self.call_tool(tool_name, parameters) except Exception as e: return f"调用工具失败: {str(e)}" async def chat_loop(self): """交互式聊天循环""" # 显示欢迎信息 print("\n" + "="*30) print("🌦️ 天气查询助手") print("="*30) print("输入示例:北京明天天气如何?/ 北京今天天气怎么样?") print("输入'quit'、'退出'或'bye'结束对话") while True: try: query = input("\n查询: ").strip() if query.lower() in ["quit", "退出", "bye"]: print("👋 感谢使用,再见!") break if not query: continue response = await self.process_query(query) # 美化结果输出 print("\n" + "-"*30) print("📋 查询结果:") print("-"*30) print(response) print("-"*30) except Exception as e: print(f"\n⚠️ 发生错误: {str(e)}") async def cleanup(self): """清理资源""" print("🧹 正在清理资源...") await self.exit_stack.aclose() print("✓ 资源清理完成") async def main(): """程序入口点""" if len(sys.argv) < 2: print("使用方法: python client.py <服务端脚本路径>") sys.exit(1) print("🚀 天气查询助手启动中...") client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": asyncio.run(main())
-
项目整合测试
- 拷贝
weather.py
到当前目录 - 运行
uv run client.py ./weather.py
- 提问:北京明天天气如何
- 拷贝
-
工具调用流程
- 用户输入 → process_query → 调用query_llm获取工具调用指令(如get_forecast)
- 通过 MCP 协议向服务端发送指令,服务端执行具体工具逻辑(如调用天气 API)
- 服务端返回结果,客户端解析并展示
简历优化-大模型高阶技术MCP和微调板块
-
大模型高阶内容总结
- 大模型微调与环境准备
- 介绍大模型微调(FineTune)的常见方式,探“大模型微调与 RAG 技术适用场景” 的面试题
- 解决无 GPU 时算力平台的使用问题,并详细讲解大模型微调必备环境 Conda 的操作与应用
- 垂直领域大模型微调实战
- 围绕垂直领域展开,包括微调数据集格式转换、服务器安装千问 QWEEN 项目下载预训练模型与配置训练数据集
- 微调训练操作、Lora 微调模型文件合并,以及微调前后推理效果对比,形成完整的垂直领域微调实践链路
- MCP 技术应用实战
- 剖析 MCP 诞生的需求背景与项目效果,明确大模型在 MCP 中的角色,对比 “Function Call 工具与 MCP 技术” 的面试题
- 讲解 MCP 部署模式、通信原理及常见命令行工具,并通过 “AI 编辑器 + MCP 在高德地图” 的实战,展现 MCP 的应用场景
- MCP Server 服务开发实战
- 掌握如何开发 MCP Server,通过 MCP Inspector 调试实操,解析 LLM 大模型整合 MCP 的交互流程全链路
- 并开展 MCP Client+Server 综合实战,最后对 MCP 和微调板块进行总结
- 大模型微调与环境准备
-
为啥要学,包装简历啊,兄弟们
- 技术技能
- 工具与环境:熟练使用 Conda 进行大模型微调环境配置,掌握 GPU 场景下算力平台的应用。
- 大模型微调:熟练大模型 FineTune 技术,熟悉 Lora 微调方法,具备垂直领域数据集处理、模型训练及效果优化经验。
- MCP 技术:掌握 MCP 技术原理,具备 MCP Server 开发、调试(MCP Inspector)及部署能力,理解 MCP 与大模型整合的全链路逻辑。
- 熟悉大模型开发全流程,能结合业务需求进行技术选型(如微调与 MCP 技术的适用场景分析)
- 项目板块
- 垂直领域大模型微调实战项目
- 参与垂直领域大模型微调项目,负责数据集格式转换与配置,利用千问 QWEEN 模型完成预训练模型下载及训练数据集准备。
- 执行大模型微调训练操作,掌握 Lora 微调技术并完成模型文件合并
- 对比微调前后推理效果,优化模型在特定领域的性能表现,提升业务场景适配性。
- MCP 技术应用与开发项目
- 参与 MCP 技术实战项目,熟悉 MCP解决的问题及部署模式,掌握其通信原理,对比分析 Function Call 工具与 MCP 技术的差异。
- 主导或参与 MCP Server 开发,利用 MCP Inspector 进行服务调试,实现 LLM 大模型与 MCP 交互流程的全链路解析。
- 完成 MCP Client + Server 综合实战,业务系统实现高德地图 MCP Server 的应用,具备 MCP 技术在实际场景中整合与开发的能力
- 垂直领域大模型微调实战项目
- 技术技能
AI智能化云盘项目课程总结和未来规划
AI智能化云盘大课-知识点总结串讲《上》
-
线上地址:面试官如果提问是否有线上地址
- 小滴课堂提供地址: http://pan-front.open1024.com/
- 大家可以自己部署即可
- 课程提供了前端、后端代码, 自己只需要弄个前端页面和域名即可
- 推荐回答:智能化云盘更多是公司内部使用,所以没对外开放访问域名
-
课程知识点回顾
AI智能化云盘大课-知识点总结串讲《下》
- 课程知识点回顾
跳槽面试-避免同质化问题-选择性包装
- 跳槽简历关键点:避免同质化严重,每个人履历不一样,选择性定制化包装
- 100份简历,90份都是雷同的,某些大型机构或者B站上免费项目,播放量几百万,已经被写烂了
- 项目很多都只是开发,然后部署本地虚拟机,数据量也没有,简历也没有讲如何编写和面试官提问。
- 一个机构培训项目视频就那么几个,投资机构有钱,广告也是疯狂投放,每年全国各地大量招生
- 一年10万+线下习人员,线上还更多,很多同期毕业疯狂海投简历,班里都在卷你,这个怎么有竞争力?
- 下面是我和某个大厂面试官的聊天,也是我本身当了面试官招聘遇到的问题点
-
我们的区别:
- 小滴课堂这边一个大课学习人数不多,基本500到1千人,加上会专属定制化简历,而且都是不同城市和时间段
- 每年我们都会出2~3个项目大课,而且会拓展很多知识,简历也会写很多负责的模块,选择性包装,再结合我的辅导
- 不会让好的项目大课免费观看,也不会烂大街,另外关键项目类型是很通用,而且容易包装到各个领域的研发公司
- 常规工作0~3年,简历需要有两个亮点的互联网高并发项目,3~7年则常规需要3个以上的亮点项目
- 也需要掌握一些方法论,职业发展肯定要往技术组长、架构师方向发展
- 包括对上汇报、技术团队管理、生产环境问题处理、全链路的性能优化,比如架构图、技术选型、JVM性能优化
- 数据库性能优化、应用程序线程池、OOM问题处理、监控告警体系、分库分表、生产环境多问题处理、新领域学习能力
- 都是工作多年需要具备的方法论,不能四处拼凑,需要系统去提升,而且越早学习越好,公司可以不用,但是不能不会
- 前端、后端、测试、大数据、运维等方向,年限上来都需要具备独立带项目,包括项目各个岗位技术都需要会,有所侧重
-
技术培训的你应该知道的事情
- 技术是每年一小变,三年一大变,持续学习是关键
- 大家也是过来人,小滴课堂-每个技术栈课程不贵,从专题技术到大课,不同阶段你收获也不一样
- 市面上技术视频很多,但是与其不如四处搜索,还不错选个靠谱的平台持续学习
- 提升自己技术能力 + 不浪费时间 + 涨薪,是超过N倍的收益
- 我也在学习每年参加各种技术沙⻰和内部分享会投 入超过6位数,但是我认为是值的,且带来的收益更大
- 定时复习专题课程笔记+大课里面的笔记和解决方案
- 技术是不断演进的,后端+大数据,小滴课堂也会不断上架新的技术,做好技术储备,就不会被淘汰!!!
- 为啥说免费的才是最贵的,比如【盗版视频】或者【割韭菜的机构】
- 视频录制时间旧 或者 讲师水平Low,但导致学不到技术甚至还【学错了技术】我面试过不少人员
- 有些虽然是新的,但是讲干货少,如果免费能学到大量知识,线下培训机构如何生存?
- 课程资料、代码缺少,导致自己花了大量时间去调试最后还不一定解决
- 项目源码不规范、思考维度缺少,什么样的老师带出什么样的学生
- 正版学习群的关键性
- 盗版群里你能结实到什么人员,各种混杂人员,遇到问题卡死,花了几百块还盗版人员跑路不更新
- 正版学习群里,不少大厂技术负责人,本身内推和招聘,风气也好、价值观也好
- 遇到问题有群里的学霸和讲师帮你解决,也有更多面经分享等
- 大家计算下自己的时薪、日薪
- 月薪20k, 一天8小时,一个月工作22天,你一小时值多少钱、一天值多少钱 ,1千块!!
- 还有很多互联网公司,一年14到16个月工资+股票,那一天就是好几千了,阿里P6级别都是30k月薪
- 举个跳槽例子
- 老王月薪10k,花了4k学了大课,一线城市找到了20k Offer
- 入职第一个月月薪的四分之一就回本了,后续都是自己更高的收入
- 记住!!!!
- 跳槽涨薪不是你说跳就跳的,说涨就涨的
- 你需要有让面试官给你这个薪资的理由
- 比如你有好项目经历、各种高并发、分布式解决方案
- 能给公司和技术团队带来新血液,提升一个档次
- 你学了大课,里面N多技术难点解决方案,就可以让你靠这些去跳槽涨薪!!!
新大课预告-与时俱进的项目学习和能力提升
简介: 新大课预告-与时俱进的项目学习和能力提升
-
大课综合训练营
- 适合用于专项拔高,适合有一定工作经验的同学,往架构师方向发展,包括多个方向
- 高并发项目大课训练营 √
- 微服务架构-海量数据商用短链平台项目大课 √
- 独孤求败-小滴云架构大课十八式-最强面试大课 √
- 全栈-商业级大型前端项目大课-小滴云在线教育平台 √
- 全栈-多端低代码平台项目大课-系统化掌握React生态体系 √
- 中间件项目大课-自动化云测平台项目大课 √
- 手撕大厂算法-算法刷题大课训练营 √
- 分布式云存储-云网盘+AI大模型√
- 垂直领域AI大模型智能项目大课训练营【新大课】
- AI实时风控系统+规则引擎项目大课训练营【新大课】
- 热门技术解决方案多场景设计与实战训练营【新大课】
- AI大模型算法和底层优化进阶大课训练营【新大课】
- AI大模型+分布式爬虫平台项目大课训练营
- 大模型下的用户画像+大数据大课训练营
- AI云办公-即时通讯项目大课训练营
- AI自动化渗透测试-安全攻防大课训练营
- ...
- 适合用于专项拔高,适合有一定工作经验的同学,往架构师方向发展,包括多个方向
-
关于大家的好奇点:不少同学已经掌握了好几个大课,为啥还要持续学习呢? 几个关键原因
- 你的年纪和工龄是不是也在持续增长,这个是你不能控制的,一直递增
- 由此带来你的工作经验、项目经验、解决方案、薪资收入等,都需要持续递增
- 岗位级别也是:初级工程师-》中级工程师-》高级工程师-》技术组长-》架构师-》技术总监
- 总不能你工作10年,做的内容和工作两三年的人职责一样,那竞争力在哪里?
- 大家要记住【2-8原则】这个适合任何一个行业和方向,大家要努力成为这个【2】
- 高级岗位不是一朝一夕就达到的,不经过复杂项目积累,是达不到这样的架构师能力
- 不是说学了某个架构师课程、P8/P9课程就是架构师,也不是说有软考的《架构师认证》就是架构师
- 没有复杂互联网项目经历,只是懂某些理论,那PPT架构师是行不通的
- 我个人的一个座右铭:”什么阶段,做什么事情和规划什么事情“
- 在其位谋其政,目标是其位也要谋其政,才能逐步达到这个水平
- 工作1~5年,目标肯定是往架构师,学了两个大课,往下就是架构能力提升
- 在校大学生,学霸,两个项目大课+架构大课,你已经超越同龄人很多,大厂都喜欢潜力好的小年轻
-
期待下一个大课(预计11月首发)
- 多行业AI智能对话系统大课训练营【新大课】讲师:二当家小D
- 智能客服系统从架构设计到落地优化的全流程技术栈
- 通过真实案例学习,快速掌握金融、医疗、电商、政务等 多行业适配要点
- 定制行业大模型微调训练+VLLM服务化+私有化部署+MCP高阶技术
- 支持多行业接入的 AI 智能客服系统平台,满足不同行业的差异化需求
- 实现智能问答、多轮对话、情绪识别、智能路由、根据用户意图动态分配等核心功能
- 动态更新行业知识库,支持结构化与非结构化数据整合,覆盖行业术语、提供知识库构建查询功能
- 多轮对话引擎,持上下文记忆、话题切换与恢复,实现复杂业务场景的连贯处理
- 加入众多新技术+业务领域应用+超多全新解决方案+AI大模型应用落地
- 项目大课更多精彩内容,课程大纲会详细说明
- 更多大课【大钊、湧哥】持续进行中
- 多行业AI智能对话系统大课训练营【新大课】讲师:二当家小D
-
为啥是11月才出课程
- 下半年往往比上半年忙,一线互联网公司下半年是事情最多的,大家都是搬砖的都懂
- 每一个大课,都需要讲师贯穿全部内容+原创+备课+录制,这样才有竞争力,慢工出细活!!!
- 小滴课堂-让技术不再难学,也是持续贯穿下去,做大家需要的【技术持续充电平台】