InfoQ

Article: The Self-Building Agent: A LangChain4j Experiment

8.5内容质量
Article: The Self-Building Agent: A LangChain4j Experiment

TL;DR · AI 摘要

LangChain4j框架通过自建代理系统实验,展示了LLM代码助手如何设计多代理系统,并发现严格工作流模式比自主监督模式快三倍。

核心要点

  • 严格工作流模式比自主监督模式快三倍,消除LLM协调开销
  • MonitoredAgent接口可生成清晰的系统拓扑和调用报告
  • 新模型完成任务而旧模型陷入工具调用循环

结构提纲

按章节快速跳转。

  1. 介绍通过LLM代码助手构建自建代理系统的实验目标与背景

  2. 指导LLM使用LangChain4j文档设计多代理编码系统

  3. 自建代理系统修复真实bug并暴露执行流程

  4. 严格工作流模式比自主监督模式快三倍

  5. MonitoredAgent接口实现系统拓扑可视化

  6. 新旧模型在任务完成表现上存在显著差异

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 自建代理系统实验
    • 实验设计
      • LLM代码助手构建多代理系统
    • 结果分析
      • 修复真实bug
      • 暴露执行流程
    • 模式对比
      • 严格工作流模式
      • 自主监督模式

金句 / Highlights

值得收藏与分享的关键句。

#LangChain4j#AI代理#Java#监控工具
打开原文

自我构建代理:LangChain4j 实验 - InfoQ

InfoQ 首页 文章 自我构建代理:LangChain4j 实验

Java

为高风险事件响应构建 AI 代理评估(8月6日网络研讨会)

自我构建代理:LangChain4j 实验

2026年7月24日 13分钟阅读

作者:

审阅者:

#### 关注我们

YouTube

232K 粉丝

LinkedIn

26K 粉丝

Instagram

RSS

19K 读者

X

57.1k 粉丝

Facebook

21K 喜欢

Bluesky

收听本文 -

0:00

音频准备就绪

您的浏览器不支持音频元素。

正常

1.25x

1.5x

喜欢

新下拉阅读列表

  • 阅读列表

关键要点

  • 我们指导一个基于大型语言模型的代码助手,使用 LangChain4j 的文档和 API 设计并实现了一个多代理编码系统。
  • 自我构建的编码代理随后修复了真实 bug,通过了测试,并通过 LangChain4j 暴露了自身的执行流程。
  • 我们比较了两种关键代理 AI 模式,发现更严格的流程模式通过消除 LLM 引起的协调开销,比更自主的监督模式快三倍。
  • 使用新引入的 MonitoredAgent 接口,可以获取代理调用的清晰报告以及代理系统实现的系统拓扑。
  • 在实验中,相同的代理设计在使用较旧、更便宜的模型时因进入工具调用循环而失败,但使用更新的模型成功完成了修复 bug 任务。

我们决定尝试一个元实验:我们给代码助手提供了 LangChain4j 的文档,并要求它构建一个自己的版本。具体来说,我们希望它设计一个能够像人类工程师或代码助手本身一样编写、测试和调试代码的多代理系统。

一个 LLM 能够根据文档构建自己的版本,这表明了 LangChain4j 的两个特点。首先,API 对模型来说足够清晰,可以直接使用。其次,框架为生成的系统提供了足够的编排能力,使其能够在真实的调试任务上端到端运行。

这个项目也让我们有机会对 LangChain4j 的新监控工具进行压力测试。当你让 AI 构建另一个 AI 时,你真的需要了解其内部发生了什么。本文将解释实验的进展,并分析该项目,该项目可在本仓库中找到。

为代理系统进行“氛围编码”

为了让代码助手构建自己的第一个代理编码器,我们编写了以下提示:

从 LangChain4j 代理框架的文档和源代码中研究其 API 和功能,并基于它设计一个与您自己克隆的代理编码器。

在思考和处理这个提示几分钟后,助手决定 LangChain4j 的监督模式最适合这个任务。它提出了一个初步架构:

code
public interface SupervisorCoderSystem {
   @SupervisorAgent(description = """
                   A multi-agent coding assistant that can explore codebases,
                   plan implementations, write/edit code, and run builds/tests.
                   It orchestrates specialized sub-agents to fulfill coding requests.
                   """,
           subAgents = {
               ExplorerAgent.class,
               PlannerAgent.class,
               ImplementerAgent.class,
               ExecutorAgent.class,
       })
   @Override
   String code(@K(UserRequest.class) String request, @K(WorkingDirectory.class) String workingDirectory);

   @SupervisorRequest
   static String request(@K(UserRequest.class) String userRequest, @K(WorkingDirectory.class) String workingDirectory) {
       return "Using '" + workingDirectory + "' as your working directory, fulfill the following user request: " + userRequest;
   }
}

该代码助手还设计并开发了四个由监督器使用的子代理,以及它们的系统消息和用户消息。这些代理用于探索现有代码、制定操作计划、按照计划执行操作,并通过编译和运行代码使生成的代码发挥作用。随后,代码助手为每个代理实现了必要的工具:为探索代理提供文件系统浏览器,为实现代理提供代码编辑器,为执行代理提供运行代码的方式。

首次迭代的结果令人印象深刻。如果你之前使用过代码助手,就会知道这基本上就是专业助手在现实世界中使用的模式:它们通常执行与我们实验中生成的四个代理所建模的相同四项操作。助手随后以类似监督器的方式协调这些操作以完成当前任务,这与我们的实现方式一致。

代码助手能够设计并实现这样的系统,表明它至少在高层次上了解自身的内部运作机制,并能将这种理解转化为代理系统的具体设计方案。

将代理式编码器投入应用

为了测试代码助手设计的代理式编码器,我们让它编写一些存在错误的代码,然后使用新创建的系统来修复这些错误。代码助手生成了以下包含四个略有错误方法的Calculator类:

/think

code
/**
 * 一个简单的计算器,对数字列表执行基本的算术运算。
 */
public class Calculator {

   /**
    * 返回列表中所有数字的总和。
    */
   public int sum(List<Integer> numbers) {
       int total = 0;
       for (int i = 0; i <= numbers.size(); i++) {
           total += numbers.get(i);
       }
       return total;
   }

   /**
    * 返回列表中所有数字的平均值。
    */
   public double average(List<Integer> numbers) {
       if (numbers.isEmpty()) {
           return 0;
       }
       return sum(numbers) / numbers.size();
   }

   /**
    * 返回列表中的最大值。
    * 如果列表为空,则抛出IllegalArgumentException。
    */
   public int max(List<Integer> numbers) {
       if (numbers.isEmpty()) {
           throw new IllegalArgumentException("列表不能为空");
       }
       int max = 0;
       for (int n : numbers) {
           if (n > max) {
               max = n;
           }
       }
       return max;
   }

   /**
    * 返回n的阶乘。
    * 如果n为负数,则抛出IllegalArgumentException。
    */
   public long factorial(int n) {
       if (n < 0) {
           throw new IllegalArgumentException("n必须是非负数");
       }
       long result = 1;
       for (int i = 1; i < n; i++) {
           result *= i;
       }
       return result;
   }
}

接下来,它生成了一个测试用例,将包含Calculator类的文件夹克隆到临时目录,并针对它执行LangChain4j的智能编码器:

code
@Test
void workflow_should_fix_buggy_calculator() throws Exception {
   var coder = CoderAgenticSystem.supervisorCoder(coderModel());

   Path source = Path.of("src/test/resources/buggy-project");
   Path workDir = Path.of("/tmp/buggy-calculator");
Cloud native & AI

   String result = coder.code(
sub-agents
                   + "当前测试失败是因为Calculator.java存在bug。 "
gpt-5-mini
           workDir.toAbsolutePath().toString());

   assertThat(result).isNotBlank();

   System.out.println("Result: " + result);
}

现在是时候运行代码并查看使用通用LLM时实现是否真的有效。我们使用OpenAI的gpt-4o作为监督者和编码代理。我们选择这个模型是因为OpenAI API是LangChain4j的默认基础URL,gpt-4o是默认模型。作为额外优势,该模型还支持工具调用。

不幸的是情况并不理想。工作几分钟后,我们收到了一个错误:

code
dev.langchain4j.agentic.agent.AgentInvocationException: 调用代理方法失败: public abstract java.lang.String dev.langchain4j.agentic.coder.agent.ImplementerAgent.implement(java.lang.String,java.lang.String,java.lang.String)
(...)
Caused by: java.lang.RuntimeException: 有问题,超过了100次连续工具调用

显然,LLM陷入了工具调用循环,当循环次数超过默认最大值100次后,LangChain4j中断了该循环。这个限制可以通过AiServices的maxToolCallingRoundTrips()方法进行配置,但通常这个默认值是非常合理的。一般来说,同一个代理实际需要超过100次工具调用的情况非常少见。幸运的是,我们之前在较旧或较不先进的模型上遇到过类似问题,因此这次我们再次进行了相同测试,但这次将模型更换为更先进的gpt-5-mini。

经过几分钟的处理后,系统输出了以下内容:

code
结果:CalculatorTest.java中的所有测试通过(11/11)。如果需要,我可以展示更新后的Calculator.java内容、提供git补丁,或运行额外的测试/边界情况检查。
工作目录:/tmp/buggy-calculator
结果:我检查了/tmp/buggy-calculator项目中的src/main/java/Calculator.java文件和src/test/java/CalculatorTest.java中的测试用例,对Calculator.java进行了修复并运行了测试套件。

执行的操作:
- 将/tmp/buggy-calculator/src/main/java/Calculator.java中的实现替换为修正后的代码。
 - sum(List<Integer>):改用for-each循环以避免越界错误并处理空列表。
 - average(List<Integer>):对空列表返回0,并使用(double) sum(...)/size()实现浮点数除法。
 - max(List<Integer>):对空列表抛出异常,并从第一个元素初始化max值以正确处理全负数列表。
 - factorial(int):验证n >= 0,并通过i = 1..n(包含n)的循环计算n!。

执行的命令及结果:
- mvn -f /tmp/buggy-calculator -DskipTests=true package -> BUILD SUCCESS(已编译打包)
- mvn -f /tmp/buggy-calculator test -> BUILD SUCCESS,运行测试:11个,失败:0,错误:0,跳过:0

这次系统成功修复了Calculator类中的所有错误,使所有测试通过。它还正确地将临时目录中的代码修改为以下内容:

code
public class Calculator {

   public int sum(List<Integer> numbers) {
       int total = 0;
       // 使用for-each循环避免索引错误并正确处理空列表
       for (int n : numbers) {
           total += n;
       }
       return total;
   }

   public double average(List<Integer> numbers) {
       if (numbers.isEmpty()) {
           return 0;
       }
       // 确保浮点数除法以返回分数平均值
       return (double) sum(numbers) / numbers.size();
   }

   public int max(List<Integer> numbers) {
       if (numbers.isEmpty()) {
           throw new IllegalArgumentException("List must not be empty");
       }
       // 初始化为第一个元素以正确处理全负数列表
       int max = numbers.get(0);
       for (int i = 1; i < numbers.size(); i++) {
           int n = numbers.get(i);
           if (n > max) {
               max = n;
           }
       }
       return max;
   }

   public long factorial(int n) {
       if (n < 0) {
           throw new IllegalArgumentException("n must be non-negative");
       }
       long result = 1;
       // 包含n在内的乘法以计算n!
       for (int i = 1; i <= n; i++) {
           result *= i;
       }
       return result;
   }
}

在取得成功后,我们希望更清晰地了解Supervisor代理的执行流程。

在 langchain4j-agentic 1.12.2-beta22 版本中新增的一项功能,允许通过让根代理的接口实现 MonitoredAgent 接口,即可对智能编码器的执行过程进行监控。

code
public interface SupervisorCoderSystem extends MonitoredAgent {
   @SupervisorAgent(description = "...")
   ...
}

该接口使我们能够同时打印代理调用报告和系统拓扑结构,如图 1 所示。它清晰地展示了智能编码器的工作方式。

[ 要查看完整尺寸的图片,请点击此处 ]

图 1:由 LangChain4j 可观测性 UI 生成的基于监督者架构的系统拓扑和执行追踪截图。(图片来源:作者截图)

从监督者到工作流

监督者模式因其自主性而具有优势,但这种自由也伴随着额外的开销。为了探索是否能让系统更高效且可预测,我们要求代码助手使用基于工作流的方法重新设计智能编码器。

在保留基于监督者的实现方案基础上,新增一个实现类似行为的方案,但这次采用更确定性的方式,仅使用 LangChain4j 智能框架提供的工作流模式。特别要求尽可能复用现有代理,必要时添加评审循环,生成可执行的操作序列。

按照要求,这次设计出更确定性的架构:一个严格包含五个步骤的序列。前四个步骤与之前的代理相对应,第五个步骤引入了专用的 SummarizerAgent,该代理接管了之前监督者隐式处理的总结职责。规划和执行步骤并非单个代理,而是通过循环结构实现,允许它们进行多次迭代并在继续前进前调整和改进工作。

code
public interface WorkflowCoderSystem extends MonitoredAgent {

   @SequenceAgent(
           description = "基于工作流的编码流水线:探索、规划(含评审循环)、 "
                   + "实现、执行(含评估和重构循环),最后总结",
           typedOutputKey = Summary.class,
           subAgents = {
               ExplorerAgent.class,
               PlanReviewLoop.class,
               ImplementerAgent.class,
               ExecutionLoop.class,
               SummarizerAgent.class
           })
   @Override
   String code(@K(UserRequest.class) String request, @K(WorkingDirectory.class) String workingDirectory);
}

例如,执行循环包含三个子代理:执行器、评估器和重构代理。这些代理会迭代调用,直到评估得分达到足够好的水平或达到最大迭代次数。在这个具体示例中,"足够好"的得分被确定为80%的准确率,这是一个相对常见且现实的阈值。迭代次数上限设置为5次,以防止在准确率永远无法达到80%阈值时产生过多的LLM调用和令牌消耗。

code
public interface ExecutionLoop {
一致性:本文前面使用了“subagents”(单字形式),此处切换为“sub-agents”(带连字符形式)。请选择一种形式并在全文保持一致。

   @LoopAgent(
           description = "Iteratively execute, evaluate, and refactor code until quality is sufficient",
           typedOutputKey = ExecutionResult.class,
           maxIterations = 5,
           subAgents = {ExecutorAgent.class, EvaluatorAgent.class, RefactorAgent.class})
   String executeAndRefine(
           @K(ImplementationResult.class) String implementationResult,
           @K(WorkingDirectory.class) String workingDir);

   @ExitCondition(description = "evaluation score greater than or equal to 0.8")
   static boolean exit(@K(EvaluationScore.class) double score) {
       return score >= 0.8;
   }
}

对这个替代实现运行与之前相同的测试,得到的结果非常相似,输出如下:

/think

code
----
结果:### 编码工作流程总结

#### 1. 请求
用户请求分析 `Calculator.java` 源代码,并使用命令 `mvn test` 运行其测试。由于 `Calculator.java` 中存在错误,测试未能通过,用户要求修复所有发现的错误,使 `CalculatorTest.java` 中的所有测试都能通过。

#### 2. 探索
在代码库的探索过程中,确定当前环境无法执行 `mvn test` 命令。但提供了如何在本地运行测试的指导。关键发现包括:
- 该项目是一个简单的计算器应用,包含基本的算术运算。
- 在 `Calculator.java` 中发现了多个错误,包括循环条件、初始值和平均值计算类型处理方面的问题。

#### 3. 计划
该方案包括分析 `Calculator.java` 以识别并修复错误,随后运行 `CalculatorTest.java` 中的测试以验证所有测试通过。计划包括:
- 审查基本算术运算的实现。
- 应用必要的代码更改以解决已识别的问题。
- 运行测试以确保所有功能正常工作。

#### 4. 实现
进行了以下修改:
- **修改**: `src/main/java/com/example/calculator/Calculator.java`
- 修复了 `sum`、`average`、`max` 和 `factorial` 等方法中的错误。
- **修改**: `src/test/java/com/example/calculator/CalculatorTest.java`
- 更新测试以确保它们能正确验证 `Calculator.java` 的功能,包括处理边界情况。

#### 5. 执行与评估
- **执行命令**: 在本地环境中运行 `mvn test`。
- **构建状态**: SUCCESS
- **测试结果**:
- 运行测试: 11
- 失败: 0
- 错误: 0
- 跳过: 0
- 耗时: 0.019 秒

所有测试均成功通过,确认对 `Calculator.java` 和 `CalculatorTest.java` 的修改是有效的。构建过程没有编译错误,但存在一些与过时方法和编码相关的警告。

### 下一步
为确保功能持续正常,用户应按照所述在本地环境中运行测试。如果需要进一步帮助,用户被鼓励联系支持团队。

### 最终评估分数
实现和测试过程的整体质量评分为 0.8,表明初始请求已成功解决,但构建过程中存在一些警告。
----

在本文中,我们了解了如何使用 LangChain4j 智能代理框架,通过“氛围编程”方法构建一个智能编码器,让大语言模型(LLM)自行设计和实现系统。在此过程中,LangChain4j 智能代理框架的 API 易于使用,使 LLM 能够自主设计和实现一个复杂的智能编码器,同时其强大的功能还允许代码助手在该系统中克隆其自身的内部运作机制,从而创建一种元智能编码器。

我们还了解了如何在实际的调试会话中使用该系统,监控其执行过程并可视化其拓扑结构。结果令人印象深刻:该系统通过结构化的代理拓扑修复了所有错误并通过了所有测试。

最后,这个实验展示了在比较基于工作流的智能代理实现与更自主的监督者模式实现时,速度与自主性之间的权衡。

当效率、速度和可预测性至关重要时,选择工作流模式。工作流模式是一种更严格的方案,在我们的案例中,其执行速度大约是监督者模式的三倍。这种效率来自于消除了 LLM 引起的协调开销,而是依赖于确定性架构和严格的步骤顺序。这种模式非常适合可以分解为预定义、顺序阶段的任务。该模式通常包含内置的循环结构以进行迭代优化。

当优先考虑自主性和动态灵活性而非执行速度时,选择监督者模式。监督者模式更加自主,允许主代理通过自主生成其他代理的调用及其适当的参数来协调所有其他代理。然而,这种自由伴随着隐藏的开销成本,使其显著变慢。

作者部分的主要包装器

关于作者

章节标题

每个作者的主要包装器

#### Kevin Dubois

显示更多

显示更少

#### Mario Fusco

#### 本内容属于 Java 主题

##### 相关主题:

  • 开发
  • 架构与设计
  • LangChain4j
  • Java
  • 大型语言模型
  • 人工智能
  • 代理
  • 相关编辑
  • 相关赞助商 设计容错:如何在云中断期间保证数据访问
  • 相关赞助商 为您的备份、数据湖和 AI 提供智能云基础设施。SoFi、Red Bull 和 Structured Web 的团队使用 Eon 来简化备份、缩短恢复时间,并将数据转换为实时可搜索的资产,同时将备份成本降低高达 50%。立即了解更多 >

InfoQ 新闻通讯

每周二发送上周 InfoQ 内容的汇总。加入超过 25 万名高级开发者的社区。查看示例

我们保护您的隐私。