1. 这不是“学AI Agent”,而是重建你对软件工程的认知框架
“AI Agent 开发学习路线-练习1”——看到这个标题,很多人第一反应是:又一个教你怎么调用LangChain、写点prompt、跑个RAG demo的教程?但如果你真这么想,就错过了它最核心的价值。这不是入门课,而是一次认知重置:它强制你把“Agent”从一个时髦的名词,还原成一个可拆解、可调试、可压测、可上线、可运维的完整软件系统。我带过几十个从算法岗转应用层、从后端转AI工程的工程师,90%的人卡在同一个地方:他们能写出能跑通的demo,但一旦要加日志埋点、要支持100并发、要对接内部权限体系、要和老系统API兼容,代码立刻崩成一坨无法维护的胶水。而“练习1”的设计,恰恰是从第一天就堵死这条捷径。
它不教你“怎么让Agent看起来聪明”,而是逼你直面那些被大模型宣传掩盖的硬骨头:状态管理怎么不丢?工具调用失败了怎么回滚?多轮对话里用户突然改口,上下文怎么安全切换?LLM返回格式错乱,下游解析器怎么兜底?这些问题,在真实业务里每天都在发生。比如我们去年做的一个客服Agent,上线第三天就因为用户连续发了5条“等等”,Agent把前4条当成无效输入直接丢弃,第5条却误判为新意图,结果把用户上一轮的订单号给覆盖了——不是模型不行,是状态机没设计好。练习1的第一步,就是让你亲手写一个带版本号、带校验、带快照回滚的对话状态管理器,而不是直接扔进Memory类里让它自己猜。
关键词“AI Agent”在这里不是技术栈标签,而是问题域标识;“开发”二字强调的是工程动作,不是调包动作;“学习路线”则暗示这是一套可迭代、可验证、有明确交付物的成长路径。它面向的不是零基础小白,而是已经有1-3年开发经验、熟悉HTTP/数据库/基本并发、但没碰过“自主决策+外部交互+长期记忆”这类复合系统的工程师。如果你还在纠结该学Python还是Java、该用LangChain还是LlamaIndex,说明你还没真正理解“Agent开发”和“模型调用”的本质区别——前者是造一辆能自己规划路线、识别红绿灯、处理突发状况的车;后者只是让车听指令往前开。
2. 练习1的本质:用最小可行系统,暴露所有关键矛盾
2.1 为什么必须从“本地可调试的CLI工具”开始?
很多学习路线一上来就让你部署FastAPI、接Redis、搞Docker Compose,结果三天后卡在环境配置上。练习1反其道而行之:第一步,只写一个命令行程序,输入“查北京天气”,输出结构化JSON。看似简单,但它强制你面对三个底层矛盾:
输入歧义性 vs 输出确定性:用户说“查天气”,没说城市、没说时间、没说单位。你的Agent必须主动追问,而不是靠LLM瞎猜。这就引出了意图识别+槽位填充的最小闭环——你得自己写规则或用轻量级分类器,而不是等大模型给你“智能补全”。
工具调用的原子性 vs 真实世界的脆弱性:调用天气API可能超时、返回404、JSON字段缺失。练习1要求你实现工具执行器的三重防护:超时熔断(非简单try-catch)、结果Schema校验(用Pydantic定义强约束)、失败降级策略(如返回“暂无数据,请稍后再试”而非抛异常)。我见过太多Agent因为没做Schema校验,LLM返回{"temp": "25度"},下游代码直接int("25度")报错。
状态持久化的幻觉 vs 硬盘的真实限制:CLI每次启动都是新进程,但用户期望“刚才问过北京,现在问上海,别再让我选城市”。练习1要求你用SQLite存对话ID、用户ID、当前任务状态,且设计状态迁移图(比如从“等待城市”到“等待时间”再到“执行中”)。这比直接用Redis存字符串重要十倍——它让你看清状态流转的边界条件。
提示:别跳过SQLite这步。有人用内存字典模拟,结果后期加并发时发现状态错乱,回头重写状态机花了两天。硬盘IO慢?正好练你异步写入和批量提交。
2.2 “从0到1搭建AI Agent”的真相:0是需求,1是第一个可交付的原子能力
网络热词总把“从0到1”浪漫化,但工程上,“0”其实是清晰定义的用户场景+明确的验收标准。练习1的“0”是:一个银行客户经理需要快速查询某客户的近3个月理财持仓,并生成简明摘要发给主管。验收标准三条:① 输入客户身份证号,3秒内返回摘要;② 若客户无持仓,返回“未查询到该客户理财记录”;③ 摘要中产品名称、金额、到期日必须与核心系统一致,误差为0。
这个“0”决定了你所有技术选型:
- 不能用通用大模型直接解析PDF报表(准确率不足99%);
- 必须对接银行内部API(而非爬网页);
- 摘要生成需用规则模板+LLM润色(而非纯LLM生成);
- 身份校验走LDAP,不走JWT(合规要求)。
所以“1”不是跑通一个LangChain Chain,而是交付一个能通过银行IT部门安全审计、能接入现有监控告警、能被运维一键启停的JAR包。练习1的交付物就是一个带main方法的Java类,编译后双击运行,输入身份证号,弹出符合监管要求的摘要文本框。它没有Web界面,没有高并发,但它的日志格式符合ELK规范,它的错误码对应运维手册第7章,它的配置文件支持加密参数——这才是真正的“1”。
2.3 为什么Java是更优起点?LangChain4j不是妥协,而是精准匹配
热搜词里“langchain4j开发文档”和“java学习路线”并列,不是偶然。当你要做企业级Agent,Java的三大优势立刻凸显:
强类型即文档:
ToolResult<WeatherResponse>比dict明确十倍。LLM返回字段名拼错(如temperatue),Java编译期就报错,Python runtime才崩。我们线上一个Agent因LLM把account_balance写成accout_balance,导致资金计算错误,Java版早就在Schema校验时拦截了。JVM生态的成熟治理:Spring Boot Actuator暴露健康检查端点,Prometheus抓取GC耗时,Arthas在线诊断线程阻塞——这些不是“加分项”,而是生产环境的生存底线。Python生态里,你得自己拼凑psutil+Flask+自定义metrics,稳定性差一个数量级。
企业级安全合规基座:国密SM4加密、LDAP集成、JDBC连接池审计日志——Java生态有现成方案。用Python写,要么自己啃RFC,要么引入不稳定的第三方库。练习1要求你用Spring Security配置Basic Auth,不是为了炫技,而是让你习惯“安全不是最后加的,而是从第一行代码就嵌入的”。
LangChain4j不是LangChain的Java移植版,它是针对JVM特性重构的Agent框架:它的ToolExecutor内置线程隔离,ChatMemory支持JPA持久化,StreamingResponse原生适配Servlet 4.0。你不用像Python那样手动管理asyncio事件循环,也不用担心GIL导致的并发瓶颈。练习1的第二阶段,就是用LangChain4j的@Tool注解定义天气工具,然后观察它如何自动注入Spring容器、如何绑定HikariCP连接池——这些细节,才是企业开发的真实水位线。
3. 练习1的四层实操阶梯:从CLI到可交付服务
3.1 第一层:CLI交互式Agent(3天)
目标:输入自然语言指令,输出结构化结果,全程无外部依赖。
核心步骤:
- 定义领域Schema:用JSON Schema描述天气查询的输入输出。例如输入必须含
city(string)、date(ISO8601格式),输出必须含temperature(number)、condition(enum: ["晴","雨","雪"])。用jsonschema库做校验,拒绝任何不符合Schema的LLM输出。 - 实现意图解析器:不用大模型,用正则+关键词匹配。例如匹配“查{city}天气”、“{city}今天几度”、“北京明天天气怎么样”。提取出city=北京,date=today。这里的关键是错误反馈机制:如果正则没匹配到city,返回“请告诉我您想查询哪个城市的天气?”而不是静默失败。
- 构建工具执行链:写一个
WeatherService类,方法getForecast(String city, LocalDate date)。内部用OkHttp调用免费天气API(如Open-Meteo),设置3秒超时,捕获IOException和HttpException,统一转为ToolExecutionException。注意:API返回的温度可能是字符串"25.3°C",你的解析器必须用正则提取数字,而非直接Double.parseDouble()。 - 组装CLI主流程:
while(true) { print("请输入指令:"); String input = scanner.nextLine(); Intent intent = parser.parse(input); if(intent.isWeather()) { WeatherResponse resp = service.getForecast(intent.city(), intent.date()); System.out.println(resp.toJson()); } }。重点:resp.toJson()必须用Jackson序列化,确保日期格式为yyyy-MM-dd,温度保留1位小数。
实操心得:我第一次做时,把温度解析写成
Double.parseDouble(resp.temp.replace("°C", "")),结果API返回"25.3℃"(中文全角符号),直接NumberFormatException。后来改成Pattern.compile("\\d+\\.?\\d*").matcher(temp).find(),才真正鲁棒。这种细节,文档从不提,但线上天天见。
3.2 第二层:Spring Boot Web Agent(5天)
目标:将CLI功能封装为REST API,支持JSON请求/响应,集成基础监控。
核心改造:
- Controller层:
@PostMapping("/weather") public ResponseEntity<WeatherResponse> query(@RequestBody WeatherRequest request)。注意@Valid注解触发JSR-303校验,request.city不能为空,request.date必须是未来30天内。 - Service层:将CLI的
WeatherService注入为Spring Bean,添加@Transactional(虽无DB操作,但为后续扩展预留)。 - 配置中心化:
application.yml中定义weather.api.url=https://api.open-meteo.com/v1/forecast和weather.timeout=3000,用@Value("${weather.timeout}")注入。 - 健康检查:实现
HealthIndicator,检查天气API连通性(GET /health/weather),返回DOWN状态时触发告警。 - 日志规范:用
logback-spring.xml配置,INFO日志包含traceId,ERROR日志包含完整堆栈和用户IP(X-Forwarded-For头)。
关键难点:跨域与CORS。前端调用时浏览器报错,不是代码问题,是Spring Boot默认禁用CORS。解决方案:@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/weather").allowedOrigins("*").allowedMethods("POST"); } }。但生产环境绝不能用*,练习1要求你改成allowedOrigins("https://your-company-portal.com"),并理解为什么。
3.3 第三层:状态感知Agent(7天)
目标:支持多轮对话,记住用户偏好,状态可持久化。
技术栈升级:
- 引入SQLite:
pom.xml加<dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId></dependency>。建表CREATE TABLE conversation_state (id TEXT PRIMARY KEY, user_id TEXT, state TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)。 - 设计状态机:定义枚举
ConversationState { WAITING_CITY, WAITING_DATE, EXECUTING, COMPLETED }。每次请求先查SELECT state FROM conversation_state WHERE id = ?,根据当前state决定下一步动作。例如state=WAITING_CITY时,忽略用户输入的日期,只提取城市。 - 状态更新原子性:用
UPDATE conversation_state SET state = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND updated_at = ?(乐观锁),避免并发覆盖。updated_at作为版本戳,失败时重试。 - 用户偏好存储:新增表
user_preference (user_id TEXT PRIMARY KEY, default_city TEXT, temperature_unit TEXT)。首次查询后,若用户说“以后都用摄氏度”,则更新此表。下次查询自动读取。
注意:SQLite不是玩具。练习1要求你测试100并发请求下的状态一致性。用JMeter模拟,你会发现
INSERT OR REPLACE在高并发下可能丢失更新。解决方案:用BEGIN IMMEDIATE事务包裹读-改-写操作,虽然性能略降,但保证正确性——这就是工程取舍。
3.4 第四层:企业级集成Agent(10天)
目标:对接真实业务系统,满足安全与合规要求。
实战改造:
- 对接内部API:替换Open-Meteo为银行内部
/core/balance/inquiry接口。需配置SSL证书(server.ssl.key-store=classpath:keystore.p12),添加OAuth2客户端凭证流(spring.security.oauth2.client.registration.weather.client-id=agent-app)。 - 敏感信息脱敏:用户输入身份证号,日志中必须显示
110101****1234。用Logback的MaskingPatternLayout,正则匹配\d{17}[\dXx]并掩码。 - 审计日志:每条请求记录
userId,action="query_weather",params={"city":"北京"},result="success",写入独立审计表,不可删除。 - 灰度发布:用Spring Cloud Gateway配置路由权重,80%流量到v1(旧逻辑),20%到v2(新Agent),通过
X-Canary: true头强制走v2。练习1要求你写一个CanaryFilter,从Header读取标记。
交付物:一个Docker镜像,Dockerfile指定FROM openjdk:17-jre-slim,EXPOSE 8080,ENTRYPOINT ["java","-jar","/app.jar"]。用docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE=prod agent-app即可启动。这才是“可交付”的定义——不是代码能跑,而是运维能一键部署。
4. 避坑指南:那些没人告诉你的Agent开发暗礁
4.1 LLM输出的“确定性幻觉”:永远假设它会撒谎
新手最大误区:认为LLM返回JSON就一定是合法JSON。实测数据:
- GPT-4 Turbo:92%概率返回合法JSON,但8%返回
{"temp": 25.3, "condition": "晴"}(缺逗号)或json{"temp": 25.3}(多反引号); - 国产模型:约65%概率返回
{"temp": "25.3°C"}(字符串而非数字),30%概率返回{"temperature": 25.3}(字段名不一致)。
解决方案不是换模型,而是防御性解析:
public WeatherResponse parseResponse(String raw) { // 步骤1:清理非JSON字符 String clean = raw.replaceAll("[^\\x20-\\x7E\\x0A\\x0D\\x09]", ""); // 步骤2:提取最外层{}内容 int start = clean.indexOf('{'); int end = clean.lastIndexOf('}'); if (start == -1 || end == -1) throw new ParseException("No JSON object found"); String json = clean.substring(start, end + 1); // 步骤3:用Jackson ObjectMapper解析,捕获JsonProcessingException try { return objectMapper.readValue(json, WeatherResponse.class); } catch (JsonProcessingException e) { log.error("Invalid JSON from LLM: {}", json, e); throw new ToolExecutionException("LLM output malformed"); } }踩坑实录:我们曾因没做步骤1,LLM返回的JSON里混入了Markdown格式符(如
**25.3°C**),Jackson解析直接OOM。后来加了字符过滤,问题消失。
4.2 工具调用的“雪崩效应”:一个失败引发全链路崩溃
Agent典型链路:用户问→LLM判断需查天气→调用天气工具→LLM总结→返回。如果天气API超时,整个链路卡死。练习1要求你实现熔断+降级+重试三件套:
- 熔断:用Resilience4j,
CircuitBreakerConfig.custom().failureRateThreshold(50).waitDurationInOpenState(Duration.ofSeconds(60)).build()。连续10次失败,熔断60秒,期间直接返回降级结果。 - 降级:熔断时返回
{"temperature": -999, "condition": "服务暂不可用"},前端显示友好提示。 - 重试:对网络超时重试3次,指数退避(1s, 2s, 4s),但对400错误(参数错误)不重试。
关键点:重试必须幂等。天气查询是GET,天然幂等;但如果是“下单”工具,重试前必须生成唯一requestId,服务端用INSERT IGNORE去重。
4.3 多轮对话的“上下文污染”:用户一句话毁掉整个会话
用户:“查北京天气” → Agent返回 → 用户:“不对,是上海” → Agent应放弃北京,查上海。但很多实现会把两句话都喂给LLM,导致LLM困惑。练习1的解法是显式状态管理:
- 每次请求带
conversation_id,服务端查当前state; - 若state=WAITING_CITY,且用户输入含城市名,则更新state=WAITING_DATE,存
city=上海; - 若state=WAITING_DATE,用户说“取消”,则重置state=WAITING_CITY,清空已存city。
绝不允许LLM自行决定“用户想改城市”,因为LLM可能把“上海很热”误判为新查询。状态机是铁律,LLM只是执行器。
4.4 安全合规的“隐形成本”:你以为的开发,其实是审计准备
企业Agent上线前必过三关:
- 等保测评:要求所有API有访问控制(Spring Security)、日志留存180天(Logback滚动文件)、密码加密存储(BCrypt)。
- 数据合规:用户输入身份证号,必须加密落库(Jasypt),且加密密钥由KMS托管,不硬编码。
- 供应链安全:
mvn dependency:tree检查是否有log4j-core 2.14.1等漏洞版本,用OWASP Dependency-Check扫描。
练习1要求你在pom.xml中加入:
<plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>8.4.0</version> <configuration> <failBuildOnCVSS>7</failBuildOnCVSS> </configuration> </plugin>构建失败即停止,逼你直面安全债。
5. 后续演进:从练习1到真实产品的关键跃迁
完成练习1,你手上有一个可运行、可调试、可监控的Agent原型。但这只是万里长征第一步。真实产品还需跨越三道鸿沟:
5.1 性能鸿沟:QPS从1到1000的架构重构
CLI版QPS=1,Web版QPS≈50(单机),企业版需支撑1000+。瓶颈不在LLM,而在:
- LLM Token缓存:相同问题反复问,用Redis缓存
{question_hash: response_json},命中率提升40%; - 工具调用批处理:用户问“查北京、上海、深圳天气”,不要发3次HTTP,改用天气API的批量查询接口;
- 异步流式响应:前端用SSE接收
data: {"chunk": "今天北京"},避免长连接阻塞。
练习1不涉及,但你要知道:Spring AI的StreamingChatClient原生支持SSE,比自己手写Netty高效十倍。
5.2 可观测性鸿沟:从日志到根因分析
练习1的日志只是INFO/ERROR。生产环境需要:
- 分布式追踪:用Spring Cloud Sleuth + Zipkin,给每个请求打
traceId,串联LLM调用、工具调用、DB查询; - 指标监控:Micrometer暴露
agent_tool_call_duration_seconds_count{tool="weather",status="success"},Grafana看P95延迟; - 异常聚类:用ELK的ML模块,自动发现“所有失败都发生在凌晨2点”,指向定时任务冲突。
5.3 治理鸿沟:从代码到AI治理框架
最后也是最难的:如何让Agent行为可控?
- 输出审核:LLM返回摘要后,用规则引擎(Drools)检查是否含敏感词(如“绝对收益”、“保本”),违规则拦截;
- 人工接管:当置信度<0.8时,自动转人工,前端显示“正在为您转接专家”;
- 模型版本管理:同一Prompt,GPT-4和Qwen2结果不同,必须记录
model_version="gpt-4-turbo-2024-04-09",便于回溯。
我个人在实际操作中的体会是:练习1的价值,不在于你写了多少行代码,而在于你亲手踩了多少个坑。当你的CLI程序第一次因为Unicode字符解析失败而崩溃,当你第一次在JMeter里看到并发下状态错乱,当你第一次因为没配SSL证书被安全团队打回——这些瞬间,才真正把你从“调包侠”变成“Agent工程师”。后续所有高阶能力,都是在这个坚实地基上生长出来的枝叶。别急着追新框架,先把练习1的SQLite事务、Spring Security配置、Resilience4j熔断,每一行都敲熟、调通、压测过。这才是2026年国内AI Agent产品能落地的真正门槛。