ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Java后端转AI工程(一):AI入门篇 —— 从零搭建 Spring AI 对话 API + LoRA 微调实战】

【Java后端转AI工程(一):AI入门篇 —— 从零搭建 Spring AI 对话 API + LoRA 微调实战】 Java后端转AI工程一AI入门篇 —— 从零搭建 Spring AI 对话 API LoRA 微调实战本系列共 4 篇完整覆盖 Java 后端向 AI 工程转型的 6 个月学习路径。这是第一篇聚焦第一个月的学习内容AI 基础认知、Spring AI 框架上手、LoRA 微调实战。系列导航Java后端转AI工程一份6个月全脱产学习计划总体规划目标6个月从熟练 Java Tech Lead → Java AI 复合型工程师设计原则AI前置主线、架构按需补、全脱产高效冲刺每天有效学习6-8h约每周 35h时间线月份: 1 2 3 4 5 6 |--AI基础SpringAI--| |--AI中台项目--| |--输出--| |--LoRA微调--| |--DDD系统设计--| |--K8s--| |---RAG------Agent---| |--模型部署可观测--| |---架构按需补(穿插)---|阶段概览月份主线AI副线架构/输出关键产出第1月AI基础 Spring AI LoRA微调—命令行AI工具 Spring AI API 微调报告第2月RAG Agent架构按需补RAG系统 Agent Demo第3月模型部署 可观测 AI中台启动DDD 系统设计私有模型服务 可观测面板 AI中台 v0.5第4月AI中台项目完善上线架构补齐JVM/JUC/微服务AI中台 v1.0第5月K8s 容器化部署架构复习体系化K8s部署 架构知识图谱第6月技术输出 面试准备—GitHub作品集 技术文章 分享并行原则AI 主线不中断。架构补强只在做 AI 项目遇到具体问题时回溯查阅工具书不单独占时段。微调和 RAG 可以并行——微调是模型侧、RAG 是应用侧互不冲突。全脱产的优势一整天可以深度沉浸一个主题上午学理论、下午写代码、晚上复盘学习效率远高于碎片化在职模式。执行说明如何直接开始本计划按输入 → 动手 → 验收 → 复盘循环执行。不要先把所有资料看完再写代码每个学习单元都必须留下可运行代码、实验记录和一条可复述的结论。开始前检查第 0 天约 2 小时安装并确认JDK 17、Maven 3.9、Git、Docker Desktop、IntelliJ IDEA、curl、jq执行java -version、mvn -version、docker version、git --version把输出保存到docs/environment.md创建一个独立 Git 仓库约定main稳定版本、dev开发版本、docs/实验记录、scripts/启动和压测脚本启动 Docker Compose执行docker compose ps所有必需服务可访问后再开始 Week 1macOS: Docker Desktop 内存至少分配 8GBSettings → Resources → Memory记录本机内存、磁盘和是否有 GPU内存不足 16 GB 时Kafka、Ollama、K8s 只按对应阶段按需启动每日固定节奏全脱产约 7h/天5天/周时段做什么必须留下的结果上午 (3h)阅读官方文档/书籍整理关键结论docs/notes/week-N.md下午 (3h)完成最小可运行实验 / 项目编码可启动代码 README傍晚 (1h)制造故障或边界场景并排查 / 补测试故障记录 Git commit晚上 (0.5h)复述当天知识更新进度口述录音/文字总结周末 (弹性)不做新内容用于补进度、压测、源码阅读、写周报。如果当周进度正常休息。每个单元的完成标准同时满足以下条件才进入下一单元能从零启动项目并在 README 写清依赖、命令、端口和预期结果。至少有一个自动化测试单测、集成测试或可重复的压测脚本。至少记录一次失败实验现象、日志/指标、定位过程和修复方案。能不看资料回答本单元里程碑问题并画出核心架构图。代码已提交 Git提交信息包含主题和结论例如feat(rag): add hybrid retrieval experiment。卡住时的处理规则同一问题排查 45 分钟仍无进展保留现场日志、版本、命令查官方文档和 issue再继续。环境问题与知识问题分开记录先用最小配置跑通再逐项加组件。不为了追新版本频繁升级依赖以本计划中的主版本为基线每季度统一升级并重新跑验收。任何涉及密码、API Key、云账号的配置只放.env提交.env.example禁止提交真实密钥。建议的项目目录learning-lab/ ├── docker-compose.yml ├── services/ # 各阶段可运行项目 ├── experiments/ # 独立实验JVM、SQL、RAG 等 ├── scripts/ # 启停、初始化、压测、清理脚本 ├── docs/ │ ├── environment.md │ ├── notes/week-N.md │ ├── decisions/ # ADR技术选型与权衡 │ └── reports/ # 压测、故障和复盘报告 └── README.md环境准备Docker Compose 一键搭建学习任何模块前先把环境跑起来。文件docker-compose.ymlversion:3.8services:# 基础中间件 mysql:image:mysql:8.0container_name:dev-mysqlports:[3306:3306]environment:MYSQL_ROOT_PASSWORD:root123MYSQL_DATABASE:studyvolumes:-mysql-data:/var/lib/mysql-./init-sql:/docker-entrypoint-initdb.dcommand:--default-authentication-pluginmysql_native_password--max_connections500healthcheck:test:[CMD,mysqladmin,ping,-h,localhost]interval:10stimeout:5sretries:5redis:image:redis:7.2container_name:dev-redisports:[6379:6379]command:redis-server--appendonly yes--requirepass redis123healthcheck:test:[CMD,redis-cli,-a,redis123,ping]interval:5stimeout:3sretries:5# 微服务组件 nacos:image:nacos/nacos-server:v2.3.2container_name:dev-nacosports:[8848:8848,9848:9848,9849:9849]environment:MODE:standalonePREFER_HOST_MODE:hostnamedepends_on:mysql:condition:service_healthysentinel-dashboard:image:bladex/sentinel-dashboard:1.8.6container_name:dev-sentinelports:[8858:8858]environment:SERVER_PORT:8858# MQ rabbitmq:image:rabbitmq:3.13-managementcontainer_name:dev-rabbitmqports:[5672:5672,15672:15672]environment:RABBITMQ_DEFAULT_USER:adminRABBITMQ_DEFAULT_PASS:admin123# Kafka按需启动不用就注释掉zookeeper:image:confluentinc/cp-zookeeper:7.6.0container_name:dev-zookeeperenvironment:ZOOKEEPER_CLIENT_PORT:2181kafka:image:confluentinc/cp-kafka:7.6.0container_name:dev-kafkaports:[9092:9092]environment:KAFKA_BROKER_ID:1KAFKA_ZOOKEEPER_CONNECT:zookeeper:2181KAFKA_ADVERTISED_LISTENERS:PLAINTEXT://localhost:9092KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR:1depends_on:[zookeeper]# AI 相关 postgres:image:pgvector/pgvector:pg16container_name:dev-postgresports:[5432:5432]environment:POSTGRES_USER:studyPOSTGRES_PASSWORD:study123POSTGRES_DB:ragvolumes:-pg-data:/var/lib/postgresql/datacommand:postgres-c max_connections200healthcheck:test:[CMD-SHELL,pg_isready -U study -d rag]interval:5stimeout:3sretries:5volumes:mysql-data:pg-data:启动与验证# 1. 启动所有服务dockercompose up-d# 2. 查看启动状态所有服务都 healthy 才算就绪dockercomposeps# 3. 验证各服务# Nacos 控制台: http://localhost:8848/nacos (账号: nacos/nacos)# Sentinel 控制台: http://localhost:8858 (账号: sentinel/sentinel)# RabbitMQ 管理: http://localhost:15672 (账号: admin/admin123)# 4. 安装 OllamamacOS 建议原生安装Docker 版在 macOS 上 CPU 推理极慢brewinstallollama ollama pull qwen2.5:7b# 5. 停止环境dockercompose stop# 保留数据dockercompose down-v# 销毁并清除数据端口速查表服务端口用途管理界面MySQL3306数据库-Redis6379缓存-Nacos8848注册配置中心http://localhost:8848/nacosSentinel8858熔断限流面板http://localhost:8858RabbitMQ5672消息队列http://localhost:15672Kafka9092消息流平台-PostgreSQL5432向量数据库(PgVector)-Ollama11434本地大模型http://localhost:11434macOS 注意Docker Desktop 内存至少分配 8GBOllama 必须原生安装Docker 版性能极差如果内存不够 16GB注释掉 KafkaZookeeper按需启动Windows 注意使用 Docker Desktop WSL2 后端.wslconfig中内存至少 8GB[wsl2] memory8GB阶段一AI快速入门第1月核心原则AI 从第 1 天就启动不设前置阻塞。Java 主场 Python 客场Python 只学微调这一件事。全脱产节奏下这个阶段密度很高——前两周打基础后两周上实战。Week 1AI 基础速通学习目标不需要会数学推导只需要建立正确的直觉认知Token 是什么为什么 LLM 是预测下一个 Token的机器Embedding 的直觉相似的词向量在空间中距离近Attention 的直觉翻译我爱北京天安门翻译天安门时模型会关注“北京”LLM 的能力与局限它不会推理它在续写核心重点序号重点一句话解释1Token 是 LLM 的基本单位中文约 1.5 个字符 1 Token英文约 0.75 个单词 1 TokenLLM 本质是一个给定上文预测下一个 Token的概率模型2Embedding 的几何直觉高维空间中将语义映射为向量猫和狗的向量夹角小相似猫和汽车的夹角大用余弦相似度衡量3Attention 动态加权自注意力机制让每个词能看到上下文所有词计算相关性权重后加权求和 → 这是 Transformer 超越 RNN 的关键4LLM 的能力边界擅长续写/总结/翻译/代码生成不擅长精确数学计算、实时信息、需要推理的新问题需 Function Calling / RAG 补充5API 调用即核心技能先不用框架直接用 curl/OkHttp 调 OpenAI 兼容 API理解 messages 结构system/user/assistant 角色 temperature 参数5天安排天任务耗时Day 13Blue1Brown《神经网络》系列 (4集B站) 读《图解 Transformer》(jalammar.github.io)上午3h 下午2hDay 2李宏毅 GPT 课程B站 理解 Tokentiktoken 工具数各种文本的 Token 数上午3h 下午2hDay 3用 OpenAI/通义千问 API 写第一个对话程序Java, OkHttp 理解 Embedding用 Python 算两个句子的余弦相似度下午3h 傍晚2hDay 4-5用 curl/PostMan 手动调用 API熟悉请求格式 完成 Java CLI 工具支持流式输出、多轮对话每天 5-6h第一个 AI 程序Java// 用 OkHttp 直接调 DeepSeek API不依赖任何 AI 框架最直观publicclassFirstAiApp{privatestaticfinalStringAPI_KEYyour-api-key;privatestaticfinalStringAPI_URLhttps://api.deepseek.com/chat/completions;publicstaticvoidmain(String[]args)throwsException{OkHttpClientclientnewOkHttpClient();StringrequestBody { model: deepseek-chat, messages: [ {role: system, content: 你是一个有帮助的助手}, {role: user, content: 用一句话解释什么是机器学习} ], stream: false } ;RequestrequestnewRequest.Builder().url(API_URL).header(Authorization,Bearer API_KEY).header(Content-Type,application/json).post(RequestBody.create(requestBody,MediaType.get(application/json))).build();try(Responseresponseclient.newCall(request).execute()){System.out.println(response.body().string());}}}动手项目Java 命令行 AI 对话工具需求命令行输入问题 → 调用 API → 打印流式输出验收标准能选择模型gpt-4o / deepseek-chat / qwen-turbo支持多轮对话保留 messages 历史支持 --stream 参数启用流式输出逐字打印支持 --temperature 0.7 参数调整创造性常见报错与排查报错信息根因解决步骤401 UnauthorizedAPI Key 错误或过期确认环境变量设置正确、API Key 未过期/余额充足429 Too Many Requests请求频率超限加指数退避重试、升级 API 套餐context_length_exceeded对话历史超出模型上下文窗口截断历史消息只保留最近 N 轮、先总结再截断流式输出乱码SSE 数据格式解析错误确认Accept: text/event-stream、按data:前缀逐行解析 JSON中文 Token 消耗远高于英文中文字符 Token 化效率低中文 Prompt 尽量精炼、大段中文文档先压缩再送 LLM面试实战Q1: Token 是什么为什么中文比英文消耗更多LLM 处理文本的最小单位。英文单词通常就是 1 Token中文需要分词器切成子词1 个汉字约 1.5 Token。这是固有的分词器设计问题。Q2: Temperature 参数的作用控制输出的随机性。0 确定输出每次一样适合代码0.7-0.9 平衡通用对话1.0 创意写诗、故事。底层是通过调整 softmax 概率分布实现的。Q3: LLM 为什么会产生幻觉LLM 是续写机器而非推理机器。对不确定的内容它选择概率最高的 Token 续写这个 Token 可能是错误的。根本原因没有真实世界知识的验证机制。Q4: System Prompt 和 User Prompt 的区别System 设定角色和规则优先级最高User 是每次的具体问题。System Prompt 在整个对话中持续生效。Q5: 为什么 Attention 机制比 RNN 好RNN 顺序处理长序列遗忘开头信息梯度消失Attention 一次性计算所有位置的关系全局视野且可以并行计算训练更快。Q6: Embedding 有什么用文字 → 向量语义相似 → 向量距离近。是 RAG、语义搜索、文本分类的基础。用 cosine similarity 衡量相似度。Q7: Prompt Engineering 有哪些常用技巧Few-shot给示例、Chain-of-Thought让 LLM 一步步推理、角色设定系统提示词、格式化输出指定 JSON/Markdown 格式、反向提示告诉它不要做什么。Week 2-3Spring AI 核心核心重点序号重点一句话解释1ChatClient 是统一入口不管底层是 OpenAI / DeepSeek / Ollama / 通义千问上层都用同一套 ChatClient API只需改 yml 配置切换模型2Function Calling 机制用Tool注解标注 Java 方法 → LLM 自动判断何时调用 → Spring AI 执行方法、拿到结果后注入下文、继续回答3ChatMemory 记忆管理用MessageChatMemoryAdvisor自动将历史消息注入上下文支持 JDBC 持久化跨会话记忆不同 conversationId 互相隔离4SSE 流式输出返回FluxStringMediaType.TEXT_EVENT_STREAM_VALUE前端用 EventSource 逐字接收关键体验优化5Prompt Template用占位符{name}动态拼装提示词Tool(descriptionxxx)描述要精确LLM 靠描述判断何时调用架构师视角知识点Function Calling → Agent 的雏形架构师决策场景你接到需求做一个能查订单状态的 AI 客服。传统做法用户问 → NLP 意图识别 → 提取 orderId → 调接口 → 拼接回复 每个业务需要独立开发 NLP 模型和意图路由维护成本高。 Spring AI Function Calling 做法 1. 写一个 Tool 注解的 Java 方法queryOrder(orderId) 2. LLM 自动判断用户这句话是不是在查订单自动提取 orderId 3. Spring AI 执行方法 → 拿到结果 → 注入下文 → LLM 用自然语言回复 架构师决策 - 工具越多LLM 选错的概率越大 → 职责单一的工具 精确的 Tool(description) - Function Calling 本质是给 LLM 一个菜单菜单描述不清晰 → LLM 点错菜 - 敏感操作退款/删数据必须加人工确认环节不能全自动2周安排周任务产出Week 2Spring AI Quickstart Function Calling至少2个工具对话 API 工具调用 APIWeek 3ChatMemory SSE 流式 Prompt Template 结构化输出完整 Spring AI Demo动手项目Spring AI 对话 API验收标准能通过 yml 切换模型OpenAI / DeepSeek / Ollama至少实现 2 个 Function Calling 工具支持多轮对话记忆重启后保留支持 SSE 流式输出有单测覆盖核心逻辑常见报错报错根因解决401 UnauthorizedAPI Key 未配或过期检查application.yml中的spring.ai.openai.api-keyConnection refused连不上 OllamaOllama 未启动或端口不对ollama serve确认端口 11434Function Calling 不触发Tooldescription 太模糊description 写清楚什么情况下该调这个工具ChatMemory 对话历史丢失未配置 JDBC 持久化加JdbcChatMemory配置面试实战Q1: Spring AI 的 ChatClient 和直接调 OpenAI API 有什么区别ChatClient 屏蔽了不同模型提供商的差异切换模型只改配置。内置了 Prompt 模板、结构化输出、Function Calling 等能力。直接调 API 更灵活但需要自己管理这些。Q2: Function Calling 的原理是什么注册工具方法 → 调用时将工具定义名称描述参数 schema发给 LLM → LLM 决定是否调工具、调哪个、参数是什么 → Spring AI 执行方法 → 结果注入消息上下文 → LLM 基于结果给出最终回答。Q3: 流式输出SSE怎么实现服务端返回FluxStringContent-Type: text/event-stream。LLM 每生成一个 Token 就推送一次前端用 EventSource 逐字渲染。相比非流式用户感知延迟大幅降低。Spring AI 的chatClient.prompt().stream()一行搞定。Week 3-4LoRA 微调实战这是整个计划最关键的技能模块——面试硬通货早攒早变现。核心重点序号重点一句话解释1微调决策树90% 不需要微调Prompt 工程 → RAG → 才到微调只有风格/格式/专业术语类需求且前两者解决不了时才微调2LoRA 是性价比之王不修改原模型参数只训练低秩矩阵秩 r8~64显存需求降低 3-5 倍训练速度提升 2-3 倍3数据质量 数据量500 条高质量标注数据效果远好于 5000 条低质量数据每条必须包含 instruction input output4全量微调 vs LoRA vs QLoRA全量效果最好、最贵→ LoRA效果好、性价比高→ QLoRA4bit 量化 LoRA单卡 24GB 可跑 7B 模型5评估防过拟合训练前预留 10-20% 数据作验证集训练 Loss 降但验证 Loss 升 → 立即停止过拟合微调前的决策树你的场景需要微调吗 1. Prompt 工程能解决 → 不需要微调90%的场景 2. RAG 能解决领域知识缺失→ 不需要微调 3. 需要特定风格/格式/专业术语 → 考虑微调 4. 通用模型拒绝回答的合规场景 → 考虑微调 5. 需要极低延迟小模型 → 可能需要微调 微调前必须确认 □ 有 500-5000 条高质量标注数据 □ 明确评估指标怎么算微调成功 □ 有 GPU 资源至少 16GB 显存或云 GPU 预算数据准备Alpaca 格式[{instruction:将以下 Java 代码转换为更简洁的 Stream 写法,input:ListInteger result new ArrayList();\nfor (Integer item : list) {\n if (item 10) result.add(item * 2);\n},output:ListInteger result list.stream()\n .filter(item - item 10)\n .map(item - item * 2)\n .collect(Collectors.toList());},{instruction:解释以下 SQL 的性能问题并给出优化建议,input:SELECT * FROM orders WHERE YEAR(create_time) 2024,output:问题在 create_time 上使用了函数 YEAR()导致索引失效会全表扫描。\n优化将 YEAR(create_time) 2024 改为 create_time 2024-01-01 AND create_time 2025-01-01这样可以走 create_time 的索引。}]使用 LLaMA-Factory 训练# 安装gitclone https://github.com/hiyouga/LLaMA-Factory.git pipinstall-e.[torch,metrics]# LoRA 微调 Qwen2.5-7B单卡 24GB 显存可跑llamafactory-cli train\--model_name_or_pathQwen/Qwen2.5-7B-Instruct\--stagesft\--do_train\--finetuning_typelora\--lora_rank16\--lora_targetq_proj,v_proj\--datasetyour_dataset\--templateqwen\--max_length2048\--per_device_train_batch_size2\--gradient_accumulation_steps4\--lr_scheduler_typecosine\--learning_rate5e-5\--num_train_epochs3\--output_dir./output/qwen2.5-7b-lora\--bf16True# 合并 LoRA 权重llamafactory-cliexport\--model_name_or_pathQwen/Qwen2.5-7B-Instruct\--adapter_name_or_path./output/qwen2.5-7b-lora\--export_dir./output/qwen2.5-7b-merged\--export_size4动手项目微调一个 Java 代码审查模型准备 50 条示例数据[{instruction:请审查以下 Java 代码指出问题并给出改进建议,input:public class User { \n private String name; \n public void setName(String n) { name n; } \n},output:问题\n1. 参数名 n 不够语义化建议改为 name\n2. 缺少 getter 方法\n3. 建议添加构造方法\n改进后的代码\npublic class User {\n private String name;\n public User(String name) { this.name name; }\n public String getName() { return name; }\n public void setName(String name) { this.name name; }\n}}]对比验证# 微调前通用模型回答curlhttp://localhost:11434/v1/chat/completions-d{model:qwen2.5:7b,messages:[{role:user,content:审查这段代码: public void setName(String n){namen;}}]}# 微调后合并 LoRA 权重后用同样问题测试# 预期回答风格专业、指出具体问题、带改进建议常见报错与排查报错信息根因解决步骤CUDA Out of Memory显存不足减小 batch_size1、增加 gradient_accumulation_steps、用 QLoRA4bit 量化训练 Loss 不下降学习率过大 / 数据质量差降低 lr 到 1e-5、检查数据格式、先跑通小数据集验证 Loss 上升过拟合数据太少 / 训练轮次太多增加数据、降低 num_train_epochs、加正则化合并后模型输出乱码LoRA 合并不完整 / 模板不匹配确认--template qwen与基座模型匹配面试实战Q1: 什么是 LoRA为什么能降低显存LoRA 冻结原模型参数只训练两个低秩矩阵 A×Br8 时新增参数量仅几十万。相比全量微调 7B 参数参数量降低 1000 倍显存需求降低 3-5 倍。Q2: 微调和 RAG 怎么选择领域知识缺失 → RAG风格/格式调整 RAG 解决不了 → 微调。微调改变模型行为模式RAG 补充知识。两者可以结合使用。Q3: 微调需要多少数据LoRA 微调 500-5000 条高质量数据。关键不是数量而是质量——每条必须准确、格式规范、覆盖目标场景。数据清洗占微调总时间的 60-70%。Q4: 全量微调和 LoRA 的效果差多少大多数场景下 LoRAr8~16效果接近全量微调差距 2%。全量微调的优势主要体现在领域差距极大、需要大幅改变模型行为。硬件建议练手阶段Google Colab 免费 GPU或 AutoDL 租卡几块钱/小时7B 模型 LoRA 微调RTX 3090/4090 24GB 单卡足够不做全量微调的话不用上 A100第一篇检查清单完成以下所有项再进入第二篇能用 OkHttp 直接调 DeepSeek API理解 messages 结构和 temperature 参数能解释 Token 是什么、Embedding 的几何直觉、Attention 为什么比 RNN 好能用 Spring AI 在一小时内搭起一个带 Function Calling 的对话 API完成过至少一次 LoRA 微调实验Colab/AutoDL 云 GPU有训练曲线和前后对比能解释 LoRA 为什么能降低显存、微调和 RAG 的选择判断下一篇Java后端转AI工程二AI深化篇 —— RAG Agent 模型部署 可观测性第二篇覆盖 RAG 知识库、Agent 多工具协作、Ollama/vLLM 模型部署、AI 应用可观测性是整个计划最硬核的两个月。返回系列导航
返回列表