第1991篇:整理行装再出发——写在第2000篇之前的技术复盘
第1991篇:整理行装再出发——写在第2000篇之前的技术复盘
距离第2000篇还有最后10篇。
说实话,到了这个节点,我反而没有想象中那种激动的感觉。更多是一种……类似于长跑跑到38公里时的状态——终点近了,但脚下的每一步反而更稳、更沉。
我在键盘前坐了很久,想着应该写点什么来开启这最后10篇。想了几个方向:总结排行榜、知识体系梳理、读者感谢信……最后全推翻了。
决定先做一件事:把这1990篇走过来的技术轨迹,认认真真复盘一遍。
不是给别人看的那种复盘,是给自己的。
一、起点:一个对AI将信将疑的Java工程师
时间拉回到这个公众号开始的时候。那时候我已经做了七八年Java,项目经历从电商到金融到医疗都沾过边,技术栈算是扎实——Spring全家桶、消息队列、分布式事务这些基本功是不差的。
但AI这块,说实话,当时我是排斥的。
不是因为不聪明,而是被之前几次"AI风口"给搞怕了。2016年那波深度学习热,我也跟着看过几篇论文,跑过几个demo,结果业务上完全用不上。2019年的"AI中台"浪潮,折腾了大半年,最后就是把几个机器学习模型包了一层REST接口,没什么本质区别。
所以当ChatGPT出来的时候,我第一反应是:又是一波炒作。
直到有一天,我用它重构了一段我自己都头疼了三年的遗留代码——一个2000行的订单处理类,充斥着各种if-else和魔法数字。Claude一次性帮我梳理出了8个职责边界,并给出了重构方案。整个过程15分钟。
那一刻我知道这次不一样了。
不是因为它写出了完美的代码,而是因为它能理解上下文、能做工程判断、能跟我对话。这已经不是工具了,这是另一种意义上的"同事"。
二、技术轨迹:三个明显的阶段
回望这近2000篇,我能清晰感受到自己经历了三个技术阶段,每个阶段的思维方式都是不同的。
阶段一:工具使用期(前300篇左右)
这个阶段,主要在探索"LLM能做什么"。
写得最多的是:如何用Java调LLM的API、如何写好Prompt、如何处理流式输出、如何构建基础的对话链路。
现在回头看,那时候的代码写得其实不够好。比如早期我写的LLM调用工具类,异常处理基本靠try-catch打个日志,超时重试是硬编码的固定次数,完全没有考虑背压和熔断:
// 早期的"能用就行"写法
public String callLLM(String prompt) {
try {
HttpResponse response = httpClient.post(LLM_URL, buildRequest(prompt));
return parseResponse(response);
} catch (Exception e) {
log.error("LLM调用失败", e);
return null; // 返回null,让调用方去处理
}
}这段代码在生产上跑了一段时间之后,出现了经典问题:LLM服务偶发超时时,上游请求直接堆积,最终把整个服务打挂了。
那次事故让我开始认真思考:AI调用不是普通的HTTP请求,它有自己特殊的失败模式。响应时间动辄几秒甚至几十秒,token限制会导致截断,模型偶尔会返回格式错乱的内容……这些都需要专门的工程设计来应对。
阶段二:工程化期(300篇到1000篇)
这个阶段开始认真做"AI工程化"——把AI能力可靠地嵌入生产系统。
这期间写了大量关于:
- LLM调用的可靠性设计(超时、重试、熔断、降级)
- Prompt版本管理
- RAG系统的完整链路
- 向量数据库的选型和调优
- LLM结果的结构化解析和校验
- 多轮对话的上下文管理
这一阶段我踩了很多坑,但也真正建立起了对"AI系统"和"普通后端系统"差异的深刻理解。
一个让我印象特别深的坑:向量检索的"幻觉放大"问题。
我们做了一个基于产品文档的问答系统,RAG链路看起来很完整:文档切块、向量化、存入pgvector、检索top-k、拼Prompt、调LLM。测试的时候效果不错,上线之后开始出现一类奇怪的问题:用户问A,系统给出的答案里混入了B相关的内容,而且听起来还挺像那么回事。
排查了很久,发现根源在于:我们的文档切块策略太粗,一个chunk里包含了多个子主题。向量检索时,某个chunk因为局部相关性很高被召回了,但这个chunk里不相关的部分被LLM"发挥"了。
解决方案不复杂,但需要对整个链路有完整认知:
// 改进后的分块策略
@Component
public class SmartDocumentSplitter {
private static final int MAX_CHUNK_SIZE = 512;
private static final int OVERLAP_SIZE = 64;
public List<DocumentChunk> split(String content, DocumentMeta meta) {
List<DocumentChunk> chunks = new ArrayList<>();
// 先按语义边界(段落、标题)粗切
List<String> sections = splitBySemanticBoundary(content);
for (String section : sections) {
if (section.length() <= MAX_CHUNK_SIZE) {
chunks.add(new DocumentChunk(section, meta, extractKeywords(section)));
} else {
// 超长段落再按句子细切,保留重叠
chunks.addAll(slidingWindowSplit(section, meta, MAX_CHUNK_SIZE, OVERLAP_SIZE));
}
}
return chunks;
}
private List<DocumentChunk> slidingWindowSplit(String text, DocumentMeta meta,
int windowSize, int overlap) {
List<DocumentChunk> result = new ArrayList<>();
List<String> sentences = splitSentences(text);
int start = 0;
while (start < sentences.size()) {
StringBuilder chunk = new StringBuilder();
int end = start;
while (end < sentences.size() &&
chunk.length() + sentences.get(end).length() <= windowSize) {
chunk.append(sentences.get(end));
end++;
}
result.add(new DocumentChunk(chunk.toString(), meta, extractKeywords(chunk.toString())));
// 重叠:回退几个句子
start = end - calculateOverlapSentences(sentences, start, end, overlap);
if (start >= end) start = end; // 防止死循环
}
return result;
}
}这只是其中一个点。工程化阶段教会我的核心认知是:AI系统的质量问题,70%不在模型,而在工程。
阶段三:架构设计期(1000篇至今)
到了这个阶段,开始更多地思考宏观架构,而不只是单个组件。
这期间涉及的话题包括:
- Multi-Agent系统设计
- AI网关的统一治理
- 大模型在企业架构中的定位
- LLMOps:从实验到生产的完整流程
- AI功能的可测试性设计
思维方式的变化是最显著的:以前想的是"这个功能怎么实现",现在想的是"这个系统在什么条件下会失效,怎么让它优雅降级"。
三、那些真正让我成长的时刻
技术成长不是靠写文章积累的,写文章是输出,输入来自真实的问题和踩坑。
让我列几个真正让我想法发生改变的时刻:
时刻一:第一次在生产环境被LLM的幻觉坑到
那是一个合同审查系统,LLM审查结果里把"乙方有权在合同期满后续约"说成了"乙方无权……"——一字之差,意思完全相反。
这件事之后我开始在所有法律、财务相关的LLM输出上加双重校验:先用规则引擎做关键字段提取校验,再用一个轻量模型做二次审查,最后才进人工复核队列。信任LLM,但要有托底机制。
时刻二:一个读者的问题让我重新思考RAG的本质
他问我:"老张,我们的RAG系统搜出来的相关文档都是对的,但最后LLM给的答案还是错的,为什么?"
我研究了一下他的案例,发现问题出在:检索到的文档是"相关"的,但不是"充分"的。LLM面对不完整的上下文,不是说"我不知道",而是用已有信息脑补出一个听起来合理但实际错误的答案。
这让我开始认真研究"充分性检验"——在把检索结果交给LLM之前,先评估这些上下文是否足以回答这个问题。
@Component
public class ContextSufficiencyChecker {
private final LLMClient llmClient;
/**
* 评估检索到的上下文是否足以回答问题
* 返回[0,1]的置信度分数
*/
public double checkSufficiency(String question, List<String> contexts) {
String prompt = buildSufficiencyCheckPrompt(question, contexts);
String response = llmClient.complete(prompt, Map.of(
"temperature", 0.0,
"max_tokens", 100
));
return parseSufficiencyScore(response);
}
private String buildSufficiencyCheckPrompt(String question, List<String> contexts) {
return String.format("""
你是一个严格的信息评估专家。
用户问题:%s
检索到的上下文:
%s
请评估:以上上下文是否包含足够信息来准确回答该问题?
只输出一个JSON:{"sufficient": true/false, "confidence": 0.0-1.0, "reason": "简短原因"}
不要输出其他内容。
""",
question,
String.join("\n---\n", contexts)
);
}
}时刻三:第一次做Multi-Agent失败的教训
我花了三周设计了一个"智能运营助手",有规划Agent、执行Agent、检验Agent,设计图画了好几页。上线之后,平均一个任务要调用LLM 40多次,耗时8分钟,成本是预算的6倍。
这件事让我理解了一个道理:Agent不是越复杂越好,每增加一个节点都要有充分的理由。能用单次LLM调用+工具完成的事,就不要上Multi-Agent;能用两个Agent的,不要用五个。
四、技术体系的沉淀
这些年走下来,我感觉自己在AI工程方向上积累了一套还算系统的认知框架。用一张图来表达:
每一层都是真实踩坑积累出来的,不是凭空构建的框架。
五、对技术本身的一些判断
在这个复盘里,我想说几个自己认为比较确定的判断,供你参考:
判断一:LLM的价值在"理解",不在"执行"
用LLM做精确计算、强逻辑推理,迟早翻车。但用它做语义理解、意图识别、内容生成、格式转换,天花板很高。认清这个边界,很多架构决策就清晰了。
判断二:RAG在未来3年仍是企业AI落地的主战场
不管长上下文窗口如何发展,企业私域知识的实时更新、精准检索、来源可追溯,这三个需求是RAG专属的优势,模型自身解决不了。
判断三:Agent的爆发期还没到,但正在接近
当前大多数"Agent"其实是披着Agent外衣的workflow。真正自主的Agent,需要更强的规划能力和工具使用能力,这个能力还在快速进化中。未来18个月内会有质变。
判断四:Java在AI工程领域的地位被严重低估
Python是AI的第一语言这没争议,但生产级AI系统——特别是需要高并发、强事务、复杂业务逻辑的——Java的优势太明显了。Spring AI、LangChain4j这些生态正在快速成熟。
六、整理行装,再出发
这篇文章写到这里,字数已经够了,但我想最后说几句真心话。
写了近2000篇,我没有觉得自己变成了"专家"。恰恰相反,越写越觉得这个领域变化之快、深度之深,让人保持谦卑是必要的。
我见过太多人在一个新技术出来时立刻"精通"了,然后在下一个新技术出来时又立刻"精通"了。这种精通是危险的,它遮蔽了真正深入的可能。
我希望自己的2000篇,是实实在在写给在一线做工程的人的。不是写给看热闹的,不是写给收藏就等于学会的,而是写给那些愿意打开IDE、愿意在生产系统里验证、愿意被坑一次再爬起来的人。
如果你就是这样的人,最后这10篇,一篇都别错过。
