Day30:Java程序员的多Agent协作实战:我手敲了一个代码审查流水线,跑通了
你有没有想过,让AI像真正的技术团队一样协作——一个人盯代码质量,另一个抓安全漏洞,最后还有人帮你汇总审查报告?
听起来挺玄乎,但LangChain4j的agentic模块确实把这事儿变成了几行Java代码的事。
今天这篇文章,我会带你从零开始,手敲一个三Agent串行协作的代码审查流水线。不是看文档照抄,是真的一个字一个字敲出来,跑起来,看到输出,然后分析它到底发生了什么。
说实话,整个过程踩的坑不少——504超时、环境变量读不到、版本冲突、上下文膨胀……每个坑都是真实跑出来的,不是文档里写的"最佳实践"。
先搞清楚一个核心概念:AgenticScope
多Agent协作的关键问题是什么?——数据怎么传递。
你写Java微服务时,服务之间靠REST API传JSON。但Agent之间传数据如果也搞API,那就太蠢了——每个Agent都要定义输入输出接口,写序列化反序列化逻辑,跟写微服务没区别。
LangChain4j的方案是AgenticScope——一个全局共享的状态容器。就像HttpServletRequest的attribute map,所有Agent都在同一个Scope里读写。
具体机制很简单:
- Agent A 执行完,把结果写入Scope:
scope.writeState("codeReview", 审核结果) - Agent B 的Prompt里用变量引用:
{{codeReview}} - LangChain4j自动从Scope里取出
codeReview的值,填进Agent B的Prompt
零胶水代码——你不用写任何传参逻辑。
官方文档原文说:
The AgenticScope is a collection of data shared among the agents participating in an agentic system. The AgenticScope is used to store shared variables, which can be written by an agent to communicate the results it produced and read by another agent to put together the information that it needs to perform its task.
翻译一下:AgenticScope是参与系统的所有Agent共享的数据集合,Agent写结果、读输入,全靠它。
这个概念理解了,后面所有的Workflow模式就都通了。
Agent的本质:一个加了outputKey的AiService
很多人第一次看LangChain4j的Agent,会觉得"这不就是个AiService吗?"
对,单Agent确实跟AiService没区别。但Agent天生为组合设计——它有一个AiService没有的关键属性:outputKey。
看官方示例:
public interface CreativeWriter {
@UserMessage("Generate a story about {{topic}}")
@Agent("Generates a story based on the given topic")
String generateStory(@V("topic") String topic);
}
跟AiService唯一的区别就是@Agent注解。但真正的威力在组装环节:
CreativeWriter writer = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(myChatModel)
.outputKey("story") // ← 这就是关键!
.build();
outputKey("story")的意思是:Agent执行完后,把结果存进AgenticScope的story变量。下一个Agent如果Prompt里有{{story}},就能自动读到。
这就是"胶水"——不用你手动传参,Scope自动把数据从一个Agent流向下一个。
官方文档也强调了这一点:
The other main difference with an AI service is the presence of the outputKey parameter that is used to specify the name of the shared variable where the result of the agent invocation will be stored in order to make it available for other agents in the same agentic system.
实战开始:三Agent代码审查流水线
理论够了,直接上手。我要做一个代码审查流水线:
SQL注入代码 → CodeReviewer(质量审查) → SecurityScanner(安全检测) → ReportGenerator(综合报告)
第一步:定义Agent接口
CodeReviewer.java——审查代码质量:
public interface CodeReviewer {
@UserMessage("""
你是一个资深的代码审核者,负责审核以下代码片段:
{{codeSnippet}}
请按以下格式输出:
1. 代码质量评分(0-10)
2. 具体问题列表
3. 改进建议
""")
@Agent("审查代码质量、命名规范、设计模式使用,返回结构化反馈")
String checkCode(@V("codeSnippet") String codeSnippet);
}
几个设计细节值得说一下:
@Agent的描述字符串不是随便写的——它是Agent的"简历"。在Supervisor模式下,LLM靠这个描述来决定是否调用这个Agent。写得越精确,路由越准确。- Prompt里加了"按以下格式输出"的约束——没有格式约束,LLM会写一大段散文,下游Agent很难解析。
@V("codeSnippet")定义了变量名——这个名字必须跟AgenticScope里的key一致。
SecurityScanner.java——安全检测,注意它读两个Scope变量:
public interface SecurityScanner {
@UserMessage("""
你是一个资深的安全专家,负责审核以下代码片段:
{{codeSnippet}}
以及代码审核者的反馈:
{{codeReview}}
请重点关注以下安全问题:
1. SQL注入:检查是否存在未正确处理的用户输入
2. 权限漏洞:检查是否存在权限检查不严密的地方
3. 敏感数据泄露:检查是否有敏感数据被硬编码在代码中
请按以下格式输出:
1. 安全评分(0-10)
2. 具体安全问题列表
3. 改进建议
""")
@Agent("安全检测,关注 SQL注入、权限漏洞、敏感数据泄露")
String checkSecurity(@V("codeSnippet") String codeSnippet,
@V("codeReview") String codeReview);
}
这里有个关键点:SecurityScanner的方法签名声明了两个参数codeSnippet和codeReview,但你不用手动传值。当Agent被invoke时,LangChain4j自动从AgenticScope里取出同名变量填进去。
这跟传统Java编程的思维完全不同——你声明的是"我需要什么",不是"我传入什么"。Scope负责把数据送到需要的地方。
ReportGenerator.java——综合报告,读三个Scope变量:
public interface ReportGenerator {
@UserMessage("""
你是一个资深的软件工程师,负责综合分析以下信息并生成代码审核报告:
1. 代码片段:{{codeSnippet}}
2. 代码审核者的反馈:{{codeReview}}
3. 安全专家的反馈:{{securityReview}}
请综合以上信息,生成一份结构化的代码审核报告:
1. 综合评分(0-10)
2. 主要问题总结
3. 改进建议
""")
@Agent("综合代码审核和安全检测结果,生成最终审查报告")
String createReport(@V("codeSnippet") String codeSnippet,
@V("codeReview") String codeReview,
@V("securityReview") String securityReview);
}
第二步:组装Workflow
三个Agent接口写好了,怎么把它们串起来?用sequenceBuilder:
// 1. 创建ChatModel
ChatModel chatModel = OpenAiChatModel.builder()
.baseUrl("https://model.indata.cc/v1")
.apiKey(byaiKey)
.modelName("glm-5")
.timeout(Duration.ofSeconds(120))
.build();
// 2. 创建3个子Agent,每个配好outputKey
CodeReviewer codeReviewer = AgenticServices
.agentBuilder(CodeReviewer.class)
.chatModel(chatModel)
.outputKey("codeReview") // ← 结果写入Scope的"codeReview"
.build();
SecurityScanner securityScanner = AgenticServices
.agentBuilder(SecurityScanner.class)
.chatModel(chatModel)
.outputKey("securityReview") // ← 结果写入Scope的"securityReview"
.build();
ReportGenerator reportGenerator = AgenticServices
.agentBuilder(ReportGenerator.class)
.chatModel(chatModel)
.outputKey("finalReport") // ← 最终输出
.build();
// 3. 用sequenceBuilder串联
UntypedAgent workflow = AgenticServices
.sequenceBuilder()
.subAgents(codeReviewer, securityScanner, reportGenerator)
.outputKey("finalReport")
.build();
注意outputKey的设计——它就是AgenticScope里的变量名:
- CodeReviewer写
codeReview→ SecurityScanner读{{codeReview}} - SecurityScanner写
securityReview→ ReportGenerator读{{securityReview}}
变量名对上了,数据就自动流转。变量名没对上,Agent执行时就会报MissingArgumentException。
第三步:准备输入数据并调用
// 4. 一段有SQL注入漏洞的代码
String codeSnippet = """
public class UserService {
public void createUser(String username, String password) {
String sql = "INSERT INTO users VALUES ('" + username + "', '" + password + "')";
// 执行 SQL 语句
}
}
""";
// 5. UntypedAgent需要传Map(不是String!)
Map<String, Object> arguments = Map.of("codeSnippet", codeSnippet);
String result = (String) workflow.invoke(arguments);
System.out.println("最终代码审核报告:\n" + result);
这里有个踩坑点:UntypedAgent.invoke()接受的是Map<String, Object>,不是String。我第一次写的时候直接传了String,编译直接报错。官方文档定义UntypedAgent的invoke签名是:
public interface UntypedAgent {
@Agent
Object invoke(Map<String, Object> input);
}
Map里的所有key-value会被拷贝进AgenticScope,这样子Agent就能通过@V注解自动读取。
跑出来的结果:AgenticScope变化实录
代码跑通了,但光看到结果不够,你得理解AgenticScope在每一步到底发生了什么。
第1步:CodeReviewer执行后
AgenticScope状态:
┌──────────────┬──────────────────────────────────┐
│ codeSnippet │ UserService的SQL注入代码 │ ← 外部输入
│ codeReview │ "评分1分,SQL注入漏洞+明文密码..." │ ← outputKey("codeReview")写入
└──────────────────────────────────────────────────────┘
CodeReviewer给了1分(满分10分),精准识别了SQL注入和明文密码存储。输出约1800字,包含详细问题列表和改进后的代码示例。
实际输出片段:
1. 代码质量评分(0-10)
1 分
(注:该代码存在致命的安全漏洞,在生产环境中属于"高危"代码,几乎不可用。)2. 具体问题列表
- 严重的 SQL 注入漏洞:代码直接将用户输入拼接到 SQL 语句中。攻击者可以通过构造恶意输入来绕过验证或执行任意数据库命令。
- 明文存储密码:密码直接以字符串形式存入数据库。必须使用加盐哈希算法(如 BCrypt, Argon2)。
- 缺乏输入验证:没有对 username 和 password 进行非空检查。
- 缺乏异常处理:数据库操作必须捕获并处理 SQLException。
第2步:SecurityScanner执行后
AgenticScope状态:
┌──────────────┬──────────────────────────────────┐
│ codeSnippet │ UserService的SQL注入代码 │ ← 不变
│ codeReview │ CodeReviewer的1800字反馈 │ ← 不变
│ securityReview│ "安全评分1分,SQL注入致命+权限漏洞..." │ ← outputKey("securityReview")写入
└──────────────────────────────────────────────────────┘
SecurityScanner也给了1分,但它从安全专家视角补充了CodeReviewer没提到的点——权限漏洞:
- 权限漏洞(中危/高危):代码片段中 createUser 方法是一个公开接口,没有任何权限检查或访问控制机制。任何匿名用户都可以调用此接口创建账户。
这就是多Agent串行的核心价值:不同视角发现不同问题。CodeReviewer关注代码质量和可读性,SecurityScanner关注安全攻击面。两种视角的交叉覆盖,比单个Agent的审查更全面。
一个关键发现:上下文膨胀问题
SecurityScanner的HTTP请求里,我注意到了一个严重问题——它的Prompt非常长。
为什么?因为{{codeReview}}变量里塞了CodeReviewer的完整1800字输出(包含改进后的代码示例)。加上SecurityScanner自己的Prompt和原始代码片段,总token数接近4000。
这导致了第一个504 Gateway Timeout——中转站处理不了这么大的输入,超时了。重试后才成功。
上下文膨胀是多Agent串行的真实问题——每个Agent的输出越来越长,传给下一个Agent时token爆炸。三个Agent还好,七个Agent串联时,最后一个Agent的输入可能比原始输入大10倍。
第3步:ReportGenerator执行
AgenticScope状态:
┌──────────────┬──────────────────────────────────┐
│ codeSnippet │ UserService的SQL注入代码 │
│ codeReview │ CodeReviewer的1800字反馈 │
│ securityReview│ SecurityScanner的反馈 │
│ finalReport │ 综合审查报告 │ ← outputKey("finalReport")写入
└──────────────────────────────────────────────────────┘
ReportGenerator读取了Scope里的三个变量,合成一份最终报告。到此,流水线完整运行完毕。
五种Workflow模式全解析
代码审查流水线用的是Sequential模式,但LangChain4j agentic模块支持5种Workflow模式。我把每种都跑了一遍,结合官方文档和实际输出,给你一个对比总结。
1. Sequential(顺序串联)——我们刚写的
机制:Agent按顺序执行,上一个的outputKey变成下一个的输入变量。
适用场景:有明确前后依赖的任务链,比如简历生成→调整→审核。
Java直觉:就像Spring Batch的Step链,或者责任链模式的严格执行。
UntypedAgent workflow = AgenticServices
.sequenceBuilder()
.subAgents(agentA, agentB, agentC) // 按顺序执行
.outputKey("finalResult")
.build();
官方文档描述:
A sequential workflow is the simplest possible pattern where multiple agents are invoked one after the other, with each agent’s output being passed as input to the next agent.
2. Loop(循环迭代)
机制:Agent反复执行,直到退出条件满足或达到最大迭代次数。
适用场景:需要迭代改进的任务,比如简历打分→调整→再评分,直到分数≥0.8。
Java直觉:就像while循环,但循环体是Agent调用而不是代码逻辑。
UntypedAgent loop = AgenticServices
.loopBuilder()
.subAgents(tailor, reviewer) // 每轮执行这两个
.exitCondition(scope -> {
CvReview review = scope.readState("cvReview");
return review.score >= 0.8; // 代码判断退出条件
})
.maxIterations(3) // 安全上限
.build();
我跑3a时看到的实际输出:
检查退出条件,当前评分=0.55
评分没达标,但因为maxIterations或其他原因只跑了1轮就结束。这揭示了Loop模式的一个特点:退出条件是代码判断,不是LLM决定。这比ReAct的"自主循环"更可控——你明确知道最多跑几轮,什么时候停下。
官方文档补充了一个高级用法——testExitAtLoopEnd(true),让退出条件只在每轮结束时检查(而不是每次Agent调用后都检查),以及动态调整阈值:
.exitCondition((scope, loopCounter) -> {
double score = scope.readState("score", 0.0);
return loopCounter <= 3 ? score >= 0.8 : score >= 0.6; // 前几轮要求高,后面降低标准
})
3. Parallel(并行执行)
机制:多个Agent同时执行,结果聚合后输出。
适用场景:多个独立视角的任务,比如HR+经理+团队成员同时审核简历。
Java直觉:就像CompletableFuture.allOf(),并行执行后合并结果。
UntypedAgent parallel = AgenticServices
.parallelBuilder()
.subAgents(hrReviewer, managerReviewer, teamReviewer)
.executor(Executors.newFixedThreadPool(3)) // 线程池
.outputKey("fullReview")
.output(scope -> {
// 手动聚合——LLM不会帮你算平均分
CvReview hr = scope.readState("hrReview");
CvReview mgr = scope.readState("managerReview");
CvReview team = scope.readState("teamMemberReview");
double avg = (hr.score + mgr.score + team.score) / 3.0;
return new CvReview(avg, combinedFeedback);
})
.build();
关键洞察:并行Agent的输出聚合需要你手动写output函数。LLM不会帮你算平均分——这也是非AI Agent(ScoreAggregator)存在的意义。
4. Conditional(条件分支)
机制:根据AgenticScope里的状态,选择走哪条路。
适用场景:审核通过走面试安排,不通过走拒绝邮件。
Java直觉:就像if-else路由,但条件是lambda表达式而不是boolean变量。
UntypedAgent conditional = AgenticServices
.conditionalBuilder()
.subAgents(
scope -> ((CvReview)scope.readState("cvReview")).score >= 0.8,
interviewOrganizer // 条件1满足走这里
)
.subAgents(
scope -> ((CvReview)scope.readState("cvReview")).score < 0.8,
emailAssistant // 条件2满足走这里
)
.build();
核心区别:条件分支是确定性路由(Java代码判断),Supervisor是LLM自主路由。前者可控但需要你写逻辑,后者灵活但不可控+慢+贵。
5. Supervisor(LLM自主决策路由)
机制:一个"管理者"Agent动态决定调用哪些子Agent、什么顺序、调几次。
适用场景:无法预先定义流程的复杂任务。
Java直觉:就像策略模式,但策略选择由LLM实时决定而不是硬编码。
SupervisorAgent supervisor = AgenticServices.supervisorBuilder()
.chatModel(CHAT_MODEL)
.subAgents(hrReviewer, managerReviewer, interviewOrganizer, emailAssistant)
.responseStrategy(SupervisorResponseStrategy.SUMMARY)
.supervisorContext("Always use full panel of reviewers.")
.build();
String result = supervisor.invoke("评估这位候选人...");
Supervisor每次调用一个子Agent后,重新规划下一步——每步都要调LLM,所以慢(官方示例说GPT-4o-mini需要50秒),而且不可控(LLM可能不调用某些Agent)。
官方文档还提到了Context策略的选择:
.contextGenerationStrategy(SupervisorContextStrategy.CHAT_MEMORY_AND_SUMMARIZATION)
三种策略:CHAT_MEMORY(保留完整对话)、SUMMARIZATION(压缩历史)、CHAT_MEMORY_AND_SUMMARIZATION(两者兼顾)。上下文越长,LLM越容易迷失方向,所以压缩策略很重要。
组合模式:Workflow嵌套子流程
这是LangChain4j agentic最强大的能力——Agent是完全可组合的。
看agentic-tutorial的_6_Composed_Workflow_Example:候选人工作流 = 生成简历 + 审核简历 + 循环改进简历,这三个步骤又分别包含子Agent。
// 循环改进简历(本身就是一个Loop Workflow)
UntypedAgent cvImprovementLoop = AgenticServices
.loopBuilder()
.subAgents(scoredCvTailor, cvReviewer)
.exitCondition(scope -> scope.readState("cvReview").score >= 0.8)
.maxIterations(3)
.build();
// 嵌套进顺序Workflow
CandidateWorkflow candidateWorkflow = AgenticServices
.sequenceBuilder(CandidateWorkflow.class)
.subAgents(cvGenerator, cvReviewer, cvImprovementLoop) // ← cvImprovementLoop本身就是个Workflow!
.outputKey("cv")
.build();
顺序里面嵌循环,循环里面嵌并行,并行后面接条件——任何Agent都可以嵌套进任何Workflow。AgenticScope是所有层级共享的,不需要层层传参。
官方文档原文说:
Every agent, whether a single-task agent, a sequential workflow, …, is still an Agent object. This makes agents fully composable.
翻译:每个Agent(不管单任务还是Workflow)都是Agent对象,完全可组合。
非AI Agent:确定性逻辑也能当一等公民
agentic-tutorial的第8章引入了一个新概念——非AI Agent。
@Agent(description = "聚合审核评分", outputKey = "combinedCvReview")
public CvReview aggregate(@V("hrReview") CvReview hr,
@V("managerReview") CvReview mgr,
@V("teamMemberReview") CvReview team) {
double avgScore = (hr.score + mgr.score + team.score) / 3.0;
return new CvReview(avgScore, combinedFeedback);
}
这不是调LLM的Agent——它是纯Java方法。但它跟AI Agent用同一个AgenticScope通信,用同一个编排模式。
**为什么要这么设计?**三个理由:
- 省钱——聚合计算不需要LLM,一次调用省几千token
- 省时间——纯计算毫秒级完成,LLM调用秒级甚至分钟级
- 零幻觉——平均分算出来就是算出来,不存在"LLM算错了0.85变成0.75"的问题
还有另一种写法——用AgenticServices.agentAction()定义一个匿名非AI Agent:
AgenticServices.agentAction(scope -> {
CvReview review = scope.readState("combinedCvReview");
scope.writeState("scoreAsPercentage", review.score * 100);
})
直接在Workflow中间插入一段纯Java逻辑,不调LLM。
官方文档没有专门讲非AI Agent(这是agentic-tutorial的补充),但这个概念值得重视。生产级Agent系统里,能用非AI Agent的步骤,绝不应该用AI Agent。
人在回路(Human-in-the-Loop)
第9章演示了两种人在回路模式:
简单验证型——AI做决策,人类确认:
HumanInTheLoop humanValidator = AgenticServices.humanInTheLoopBuilder()
.description("验证模型提出的招聘决策")
.outputKey("finalDecision")
.responseProvider(scope -> {
System.out.println("AI建议: " + scope.readState("request"));
System.out.println("选项:邀请(I) / 拒绝(R) / 暂缓(H)");
return new BufferedReader(new InputStreamReader(System.in)).readLine();
})
.build();
对话循环型——AI和人类来回协商直到达成一致:
UntypedAgent schedulingLoop = AgenticServices
.loopBuilder()
.subAgents(meetingProposer, humanInTheLoop) // Agent提议→人类回复→循环
.exitCondition(scope -> {
String response = scope.readState("candidateAnswer");
String proposal = scope.readState("proposal");
return decisionService.isDecisionReached(proposal, response); // LLM判断是否达成一致
})
.maxIterations(5)
.build();
这里有个巧妙设计——用另一个AiService来判断是否达成了决定(DecisionsReachedService)。这相当于让一个"小模型"做简单判断,避免用大模型浪费token。
官方文档说:
Human-in-the-loop and human validation can typically take long for the user to respond. In this case, async agents are recommended so they don’t block the rest of the workflow.
翻译:人类响应通常很慢,建议用async Agent避免阻塞整个Workflow。
Optional Agent:缺失参数时跳过而不是崩溃
官方文档介绍了optional属性——当Agent的输入参数在AgenticScope里不存在时,跳过这个Agent而不是报错。
AudienceEditor audienceEditor = AgenticServices
.agentBuilder(AudienceEditor.class)
.chatModel(BASE_MODEL)
.optional(true) // ← 如果scope里没有"audience",跳过这个Agent
.outputKey("story")
.build();
如果input Map里没有audience键,audienceEditor就会被静默跳过,Workflow继续执行下一个Agent。
也可以用注解方式声明:@Agent(optional = true)。
这跟Spring的@Autowired(required = false)一个意思——依赖可选,没有就跳过。
动态模型选择:根据质量阈值切换模型
官方文档还介绍了一个高级特性——动态ChatModel选择:
StoryEditor storyEditor = AgenticServices.agentBuilder(StoryEditor.class)
.chatModel(scope -> {
CritiqueResult critique = scope.readState("critique");
return critique != null && critique.score() > 7.8
? enhancedModel() : baseModel(); // 高质量时用贵模型,低质量时用便宜模型
})
.outputKey("story")
.build();
这个chatModel方法接受Function<AgenticScope, ChatModel>——每次Agent调用前重新评估用哪个模型。
Java直觉:就像策略模式+工厂模式的组合,但策略选择基于AgenticScope的实时状态。
错误处理:retry、ignore、自定义恢复
官方文档定义了三种错误恢复策略:
ErrorRecoveryResult.throwException()——默认行为,异常上抛ErrorRecoveryResult.retry()——重试Agent调用(可先修正Scope)ErrorRecoveryResult.result(Object)——忽略错误,返回指定结果
UntypedAgent workflow = AgenticServices.sequenceBuilder()
.subAgents(creativeWriter, audienceEditor, styleEditor)
.errorHandler(errorContext -> {
if (errorContext.exception() instanceof MissingArgumentException) {
errorContext.agenticScope().writeState("topic", "default topic");
return ErrorRecoveryResult.retry(); // 修正Scope后重试
}
return ErrorRecoveryResult.throwException();
})
.build();
声明式API:用注解代替Builder
最新的1.15.0版本引入了声明式API——不用Builder,用注解直接定义整个Agentic系统:
@SequentialAgent(outputKey = "plans",
subAgents = {FoodExpert.class, MovieExpert.class})
String plan(@V("mood") String mood);
// 然后一行创建:
EveningPlannerAgent agent = AgenticServices.createAgenticSystem(
EveningPlannerAgent.class, BASE_MODEL);
这个声明式API还支持用@ChatModelSupplier、@ToolsSupplier、@ActivationCondition等注解,把所有配置都写在接口上,不需要Builder链式调用。
这跟Spring Boot的自动配置一个思路——声明代替编程,约定优于配置。
五种模式对比总结
| 模式 | 控制方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Sequential | 硬编码顺序 | 有前后依赖的任务链 | 简单可控 | 无法跳步 |
| Loop | 代码判断退出条件 | 需要迭代改进 | 可控迭代 | 上下文膨胀 |
| Parallel | 线程池并发 | 多视角独立任务 | 高效 | 聚合需手写 |
| Conditional | Java lambda路由 | 分类处理 | 确定性路由 | 条件必须预定义 |
| Supervisor | LLM自主决策 | 无法预定义流程 | 灵活 | 慢+贵+不可控 |
一条核心规律:确定性越强,可控性越好,但灵活性越差。反之亦然。
生产环境的选择原则:能用Conditional就不要用Supervisor。Supervisor是最后的手段,不是默认选项。
上下文膨胀:多Agent系统的隐形杀手
这是我跑代码审查流水线时发现的最严重的实战问题。
三个Agent串行时,上下文膨胀是这样的:
| 步骤 | 输入token | 输出token | 总输入 |
|---|---|---|---|
| CodeReviewer | ~110 | ~1739 | 原始代码 |
| SecurityScanner | ~2000+ | ~1500 | 原始代码+CodeReviewer输出 |
| ReportGenerator | ~4000+ | ~1000 | 前两个Agent的完整输出 |
每个Agent的输出都被塞进下一个Agent的输入,token数指数增长。SecurityScanner的第一次调用直接504超时——因为输入太长了。
解决方案有三个:
- 缩短Agent的Prompt输出——让CodeReviewer不要输出改进后的完整代码示例(那个示例占了输出的一半)。只输出评分+问题列表+简短建议就够了。
- 在output函数里做压缩——不把Agent的完整输出传给下一个,而是在output函数里提取关键信息:
.output(scope -> {
String fullReview = scope.readState("codeReview");
String compressed = fullReview.substring(0, 500); // 只传前500字
scope.writeState("codeReviewSummary", compressed);
return compressed;
})
- 用非AI Agent做摘要——用纯Java逻辑提取关键字段,而不是传整段文本。
Anthropic的Claude Code架构用了四级压缩(Snip→MicroCompact→Collapse→AutoCompact),思路是一样的——上下文越长,越需要主动压缩。
实战踩坑清单
| 坑 | 原因 | 解决方案 |
|---|---|---|
| UntypedAgent.invoke()需要Map | 官方定义是Map<String,Object>不是String |
Map.of("codeSnippet", code) |
| macOS IDEA不读.zshrc环境变量 | Dock启动的应用不读shell配置 | 代码fallback或launchctl setenv |
| 504 Gateway Timeout | 中转站处理大token输入超时 | timeout设120秒+缩短Prompt输出 |
| langchain4j版本不一致 | pom里0.36.2和1.15.0混用 | 统一到1.15.0 |
| @Agent描述写太简略 | Supervisor无法准确路由 | 写完整职责描述 |
| SecurityScanner没引用{{codeReview}} | 声明了参数但Prompt没用 | Prompt里必须引用所有@V变量 |
核心收获一句话总结
LangChain4j agentic模块的设计哲学:Agent是一等公民,AgenticScope是胶水,outputKey是接口契约。
理解了这三个概念,5种Workflow模式、非AI Agent、组合嵌套、人在回路——全部都是这三个概念的排列组合。
就像学Spring时理解了IoC+DI+AOP,其他所有特性都是这三个概念的延伸。LangChain4j agentic的"三板斧"就是:Agent + Scope + outputKey。
下一篇文章,我会写如何给Agent加上工具链(搜索API、数据库查询、MCP Server),以及Agent Skill的概念——为什么Skill比工具函数更强大。
欢迎评论区交流,下期预告:LangChain4j Agent工具链实战——从Tool到Skill的进化之路。
更多推荐

所有评论(0)