目录
joyagent-jdgenie Agent源码学习(一)
/        

joyagent-jdgenie Agent源码学习(一)

Genie Backend Agent 架构深度解析

源码地址:https://github.com/jd-opensource/joyagent-jdgenie

一、核心设计模式:ReAct (Reasoning + Acting)

整个 Agent 系统基于经典的 ReAct 模式,这是一种将"推理"和"行动"解耦的设计范式。

ReAct 循环的本质

思考 (Think) → 行动 (Act) → 观察 (Observe) → 思考 (Think) → ...

**核心抽象类:[ReActAgent.java](file:///Volumes/Data+1/JavaProject/joyagent-jdgenie/genie-backend/src/main/java/com/jd/genie/agent/agent/ReActAgent.java)**

```java
public abstract class ReActAgent extends BaseAgent {
    public abstract boolean think();  // 推理阶段
    public abstract String act();     // 行动阶段
  
    @Override
    public String step() {
        boolean shouldAct = think();  // 先思考
        if (!shouldAct) {
            return "Thinking complete - no action needed";
        }
        return act();  // 再行动
    }
}

二、三大 Agent 实现解析

1. PlanningAgent (规划者)

职责:将复杂任务拆解为有序的子任务列表

继承关系PlanningAgent extends ReActAgent

核心流程

(1) 初始化构造

public PlanningAgent(AgentContext context) {
    // 1. 设置角色名称和描述
    setName("planning");
  
    // 2. 从配置文件加载 System Prompt
    setSystemPrompt(genieConfig.getPlannerSystemPromptMap()
        .replace("{{tools}}", toolPrompt)  // 注入可用工具列表
        .replace("{{query}}", context.getQuery())  // 注入用户问题
        .replace("{{date}}", context.getDateInfo()));  // 注入当前日期
  
    // 3. 初始化 LLM 实例
    setLlm(new LLM(genieConfig.getPlannerModelName(), ""));
  
    // 4. 注册 PlanningTool (唯一可用工具)
    availableTools.addTool(planningTool);
}

(2) Think 阶段 - 调用 LLM 生成计划

@Override
public boolean think() {
    // 构建消息上下文
    Message userMsg = Message.userMessage(getNextStepPrompt(), null);
    getMemory().addMessage(userMsg);
  
    // 调用 LLM 的 Function Calling 能力
    CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(
        context,
        getMemory().getMessages(),
        Message.systemMessage(getSystemPrompt(), null),
        availableTools,  // 传入 PlanningTool
        ToolChoice.AUTO,
        null,
        context.getIsStream(),  // 支持流式输出
        300
    );
  
    // 获取 LLM 返回的工具调用列表
    LLM.ToolCallResponse response = future.get();
    setToolCalls(response.getToolCalls());
  
    return true;
}

(3) Act 阶段 - 执行 PlanningTool 生成计划

@Override
public String act() {
    List<String> results = new ArrayList<>();
  
    for (ToolCall toolCall : toolCalls) {
        // 执行工具 (PlanningTool.execute)
        String result = executeTool(toolCall);
        results.add(result);
    
        // 添加工具响应到 Memory (用于下一轮对话)
        Message toolMsg = Message.toolMessage(result, toolCall.getId(), null);
        getMemory().addMessage(toolMsg);
    }
  
    // 检查计划是否生成完毕
    if (Objects.nonNull(planningTool.getPlan())) {
        return getNextTask();  // 返回下一个待执行的子任务
    }
  
    return String.join("\n\n", results);
}

(4) PlanningTool 的内部逻辑

// PlanningTool.execute() 的核心功能
{
    "command": "create",  // 创建新计划
    "steps": [
        "执行顺序1. 搜索资料:使用搜索工具查找京东财报",
        "执行顺序2. 数据分析:使用代码工具分析财务数据",
        "执行顺序3. 生成报告:使用报告工具输出 HTML"
    ]
}

2. ExecutorAgent (执行者)

职责:领取 单个子任务,通过多轮 Function Calling 完成任务

继承关系ExecutorAgent extends ReActAgent

核心流程

(1) 初始化构造

public ExecutorAgent(AgentContext context) {
    setName("executor");
  
    // 注入所有可用工具 (不仅仅是 PlanningTool)
    availableTools = context.getToolCollection();  
    // 包含: FileTool, CodeInterpreterTool, ReportTool, DeepSearchTool 等
  
    // 使用独立的 Executor LLM (可能和 Planner 不同)
    setLlm(new LLM(genieConfig.getExecutorModelName(), ""));
}

(2) Think 阶段 - 根据子任务选择工具

@Override
public boolean think() {
    // 构建带有"当前子任务"上下文的 Prompt
    Message userMsg = Message.userMessage(getNextStepPrompt(), null);
    getMemory().addMessage(userMsg);
  
    // 调用 LLM,让它自主选择需要调用哪些工具
    CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(
        context,
        getMemory().getMessages(),
        Message.systemMessage(getSystemPrompt(), null),
        availableTools,  // 传入所有工具
        ToolChoice.AUTO,
        null,
        false,  // Executor 不支持流式输出
        300
    );
  
    LLM.ToolCallResponse response = future.get();
    setToolCalls(response.getToolCalls());
  
    // 如果 LLM 认为任务已完成 (不调用工具)
    if (toolCalls.isEmpty()) {
        setState(AgentState.FINISHED);
    }
  
    return true;
}

(3) Act 阶段 - 并发执行工具

@Override
public String act() {
    if (toolCalls.isEmpty()) {
        // 任务完成,返回最后的总结
        return getMemory().getLastMessage().getContent();
    }
  
    // 并发执行多个工具调用
    Map<String, String> toolResults = executeTools(toolCalls);
  
    for (ToolCall command : toolCalls) {
        String result = toolResults.get(command.getId());
    
        // 截断过长的结果 (防止 Memory 爆炸)
        if (maxObserve != null) {
            result = result.substring(0, Math.min(result.length(), maxObserve));
        }
    
        // 添加工具结果到 Memory
        Message toolMsg = Message.toolMessage(result, command.getId(), null);
        getMemory().addMessage(toolMsg);
    }
  
    return String.join("\n\n", results);
}

3. ReactImplAgent (单步 ReAct 模式)

职责:直接处理用户问题,不经过 Planner,适用于简单问题

继承关系ReactImplAgent extends ReActAgent

与 ExecutorAgent 的区别

维度ExecutorAgentReactImplAgent
输入由 Planner 拆解的子任务用户原始问题
流式输出❌ 不支持✅ 支持
使用场景Plan-Solve 模式的一部分独立使用
System PromptExecutor PromptReact Prompt

核心代码

@Override
public boolean think() {
    // 直接使用用户原始问题
    context.setStreamMessageType("tool_thought");
  
    CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(
        context,
        getMemory().getMessages(),
        Message.systemMessage(getSystemPrompt(), null),
        availableTools,
        ToolChoice.AUTO,
        null,
        context.getIsStream(),  // 支持流式输出
        300
    );
  
    // ... 其余逻辑与 ExecutorAgent 类似
}

三、Agent 协作编排

架构图

用户请求
   ↓
GenieController.AutoAgent()
   ↓
AgentHandlerFactory
   ├─→ PlanSolveHandlerImpl  (Plan-Solve 模式)
   │      ├─→ PlanningAgent.run()
   │      └─→ ExecutorAgent.run() × N
   │
   └─→ ReactHandlerImpl  (ReAct 模式)
          └─→ ReactImplAgent.run()

1. Plan-Solve 模式 (复杂任务)

orchestrator:PlanSolveHandlerImpl.java

@Override
public String handle(AgentContext agentContext, AgentRequest request) {
    // ① 初始化 Agent
    PlanningAgent planning = new PlanningAgent(agentContext);
    ExecutorAgent executor = new ExecutorAgent(agentContext);
    SummaryAgent summary = new SummaryAgent(agentContext);
  
    // ② Planner 生成初始计划
    String planningResult = planning.run(agentContext.getQuery());
  
    // ③ 反复执行 Plan → Execute → Replan 循环
    int stepIdx = 0;
    while (stepIdx <= maxStepNum) {
        // 拆分出多个子任务
        List<String> planningResults = Arrays.stream(planningResult.split("<sep>"))
            .map(task -> "你的任务是:" + task)
            .collect(Collectors.toList());
    
        // 执行子任务 (支持并发)
        String executorResult;
        if (planningResults.size() == 1) {
            // 单任务直接执行
            executorResult = executor.run(planningResults.get(0));
        } else {
            // 多任务并发执行
            Map<String, String> tmpTaskResult = new ConcurrentHashMap<>();
            CountDownLatch taskCount = ThreadUtil.getCountDownLatch(planningResults.size());
        
            for (String task : planningResults) {
                // 为每个子任务创建独立的 ExecutorAgent
                ExecutorAgent slaveExecutor = new ExecutorAgent(agentContext);
                slaveExecutor.getMemory().addMessages(executor.getMemory().getMessages());
            
                ThreadUtil.execute(() -> {
                    String taskResult = slaveExecutor.run(task);
                    tmpTaskResult.put(task, taskResult);
                    taskCount.countDown();
                });
            }
        
            ThreadUtil.await(taskCount);
            executorResult = String.join("\n", tmpTaskResult.values());
        }
    
        // ④ Planner 根据执行结果调整计划
        planningResult = planning.run(executorResult);
    
        // ⑤ 检查是否完成
        if ("finish".equals(planningResult)) {
            // 使用 SummaryAgent 总结任务
            TaskSummaryResult result = summary.summaryTaskResult(
                executor.getMemory().getMessages(), 
                request.getQuery()
            );
            agentContext.getPrinter().send("result", result);
            break;
        }
    
        stepIdx++;
    }
  
    return "";
}

2. ReAct 模式 (简单任务)

orchestrator:ReactHandlerImpl.java

@Override
public String handle(AgentContext agentContext, AgentRequest request) {
    // ① 直接使用 ReactImplAgent (无 Planner)
    ReActAgent executor = new ReactImplAgent(agentContext);
    SummaryAgent summary = new SummaryAgent(agentContext);
  
    // ② 执行用户问题
    executor.run(request.getQuery());
  
    // ③ 总结并返回结果
    TaskSummaryResult result = summary.summaryTaskResult(
        executor.getMemory().getMessages(), 
        request.getQuery()
    );
    agentContext.getPrinter().send("result", result);
  
    return "";
}

四、关键技术细节

1. Memory (记忆管理)

每个 Agent 都有独立的 Memory 对象,用于存储对话历史:

public class Memory {
    private List<Message> messages = new ArrayList<>();
  
    public void addMessage(Message message) {
        messages.add(message);
    }
  
    public List<Message> getMessages() {
        return messages;
    }
}

Message 类型

  • USER: 用户输入 / Prompt
  • ASSISTANT: LLM 的推理输出
  • TOOL: 工具执行结果
  • SYSTEM: System Prompt

2. Function Calling 机制

调用流程

// 1. 构建工具列表
ToolCollection availableTools = new ToolCollection();
availableTools.addTool(new FileTool());
availableTools.addTool(new CodeInterpreterTool());

// 2. 调用 LLM
LLM.ToolCallResponse response = llm.askTool(
    context,
    memory.getMessages(),
    systemMessage,
    availableTools,  // 工具列表
    ToolChoice.AUTO,  // 让 LLM 自主决定是否调用工具
    null,
    isStream,
    300
);

// 3. 执行工具
for (ToolCall toolCall : response.getToolCalls()) {
    String result = availableTools.execute(
        toolCall.getFunction().getName(),
        toolCall.getFunction().getArguments()
    );
}

3. SSE 流式输出

通过 SSEPrinter 实现实时推送 Agent 思考过程:

public class SSEPrinter implements Printer {
    private SseEmitter emitter;
  
    @Override
    public void send(String type, Object data) {
        try {
            AgentResponse response = AgentResponse.builder()
                .type(type)  // "plan_thought", "tool_thought", "tool_result" 等
                .data(data)
                .build();
        
            emitter.send(SseEmitter.event()
                .data(JSON.toJSONString(response))
                .name("message"));
        } catch (IOException e) {
            log.error("SSE send error", e);
        }
    }
}

前端接收示例

const eventSource = new EventSource('/AutoAgent');

eventSource.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
  
    switch(data.type) {
        case 'plan_thought':
            console.log('[Planner 思考]', data.data);
            break;
        case 'tool_thought':
            console.log('[Executor 思考]', data.data);
            break;
        case 'tool_result':
            console.log('[工具执行结果]', data.data);
            break;
    }
});

五、总结:为什么这样设计?

1. 职责分离

  • PlanningAgent:只负责"想"(战略)
  • ExecutorAgent:只负责"做"(战术)
  • ReActAgent:兼顾"想"和"做"(快速响应)

2. 灵活扩展

通过 AgentHandlerFactory + 策略模式,可以轻松添加新的编排模式:

@Component
public class AgentHandlerFactory {
    public AgentHandlerService getHandler(AgentContext context, AgentRequest request) {
        for (AgentHandlerService handler : handlers) {
            if (handler.support(context, request)) {
                return handler;
            }
        }
        throw new IllegalArgumentException("No handler found");
    }
}

3. 可观测性

通过 SSE 实时推送:

  • plan_thought: Planner 的推理过程
  • tool_thought: Executor 的推理过程
  • tool_result: 工具执行结果
  • task_summary: 任务总结

4. Prompt as Code

所有 Agent 行为通过 application.yml 中的 Prompt 配置,无需修改代码即可调整 Agent 策略。


六、关键代码位置索引

组件文件路径
Planner 实现PlanningAgent.java
Executor 实现ExecutorAgent.java
ReAct 实现ReactImplAgent.java
ReAct 抽象类ReActAgent.java
Base AgentBaseAgent.java
Plan-Solve 编排PlanSolveHandlerImpl.java
ReAct 编排ReactHandlerImpl.java
ControllerGenieController.java

标题:joyagent-jdgenie Agent源码学习(一)
作者:gitsilence
地址:https://blog.lacknb.cn/articles/2025/11/20/1763625580524.html