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

joyagent-jdgenie Agent源码学习(二)

AutoAgent 接口执行流程

仓库地址:https://github.com/jd-opensource/joyagent-jdgenie

result.png

1. Controller 层

@PostMapping("/AutoAgent")
public SseEmitter AutoAgent(@RequestBody AgentRequest request) throws UnsupportedEncodingException {

  log.info("{} auto agent request: {}", request.getRequestId(), JSON.toJSONString(request));

  Long AUTO_AGENT_SSE_TIMEOUT = 60 * 60 * 1000L;

  SseEmitter emitter = new SseEmitter(AUTO_AGENT_SSE_TIMEOUT);
  // SSE心跳
  ScheduledFuture<?> heartbeatFuture = startHeartbeat(emitter, request.getRequestId());
  // 监听SSE事件
  registerSSEMonitor(emitter, request.getRequestId(), heartbeatFuture);
  // 拼接输出类型
  request.setQuery(handleOutputStyle(request));
  // 执行调度引擎
  ThreadUtil.execute(() -> {
    try {
      Printer printer = new SSEPrinter(emitter, request, request.getAgentType());
      AgentContext agentContext = AgentContext.builder()
        .requestId(request.getRequestId())
        .sessionId(request.getRequestId())
        .printer(printer)
        .query(request.getQuery())
        .task("")
        .dateInfo(DateUtil.CurrentDateInfo())
        .productFiles(new ArrayList<>())
        .taskProductFiles(new ArrayList<>())
        .sopPrompt(request.getSopPrompt())
        .basePrompt(request.getBasePrompt())
        .agentType(request.getAgentType())
        .isStream(Objects.nonNull(request.getIsStream()) ? request.getIsStream() : false)
        .templateType("dataAgent".equals(request.getOutputStyle()) ? "fix" : "empty")
        .build();
      // 构建工具列表
      agentContext.setToolCollection(buildToolCollection(agentContext, request));
      // 根据数据类型获取对应的处理器
      AgentHandlerService handler = agentHandlerFactory.getHandler(agentContext, request);
      // 执行处理逻辑
      handler.handle(agentContext, request);
      // 关闭连接
      emitter.complete();

    } catch (Exception e) {
      log.error("{} auto agent error", request.getRequestId(), e);
    }
  });

  return emitter;
}

  • 首先对当前请求设置了心跳,当 sse 结束后停止心跳的定时任务
  • 构建上下文参数、根据对应Agent构建工具列表
  • 根据AgentType获取对应处理器,然后调用处理

2. Service 层

  1. 首先根据问题查询SOP流程;这里其实就是在向量数据库中检索相似的SOP流程,是通过另外一个Python项目获取,向量数据库使用的qdrant,获取后的结果类似于下面:

    [
        {
            "vector_type": "sop_string",
            "description": "对销售数据进行综合分析",
            "sop_name": "对销售数据进行综合分析",
            "sop_json_string": "{\"sop_desc\": \"对销售数据进行综合分析\", \"sop_name\": \"对销售数据进行综合分析\", \"sop_steps\": [{\"steps\": [\"使用分析工具,按月/季度/年统计销售额、利润等,识别周期性变化。\"], \"title\": \"进行销售趋势分析\"}, {\"steps\": [\"使用分析工具,对公司、消费者、小型企业等不同客户群体进行对比分析。\"], \"title\": \"进行客户细分分析\"}, {\"steps\": [\"使用分析工具,对地区/城市进行分析:挖掘区域市场差异,发现潜力市场。\"], \"title\": \"销售客户细分分析\"}, {\"steps\": [\"使用分析工具,对销售产品类别分析:家具、技术、办公用品等类别的销售表现、利润贡献。\"], \"title\": \"销售产品类别分析\"}, {\"steps\": [\"基于前面步骤的分析和结论,进行汇总展示最终的 HTML 报告\"], \"title\": \"报告呈现\"}]}",
            "sop_string": "对销售数据进行综合分析\n对销售数据进行综合分析进行销售趋势分析使用分析工具,按月/季度/年统计销售额、利润等,识别周期性变化。\n进行客户细分分析使用分析工具,对公司、消费者、小型企业等不同客户群体进行对比分析。\n销售客户细分分析使用分析工具,对地区/城市进行分析:挖掘区域市场差异,发现潜力市场。\n销售产品类别分析使用分析工具,对销售产品类别分析:家具、技术、办公用品等类别的销售表现、利润贡献。\n报告呈现基于前面步骤的分析和结论,进行汇总展示最终的 HTML 报告",
            "sop_id": "1",
            "sop_type": "list",
            "score": 0.636863648891449
        }
    ]
    
  2. 将查询到的sop流程作为Prompt放到上下文中

  3. 创建PlannerAgent并调用,生成计划

    请求体:

    {
        "max_tokens": 16384,
        "temperature": 0.0,
        "messages": [
            {
                "role": "system",
                "content": "\n# 角色\n你是一个智能助手,名叫Genie。\n\n# 说明\n你是任务规划助手,根据用户需求,拆解任务列表,从而确定planning工具入参。每次执行planning工具前,必须先输出本轮思考过程(reasoning),再调用planning工具生成任务列表。\n\n# 技能\n- 擅长将用户任务拆解为具体、独立的任务列表。\n- 对简单任务,避免过度拆解任务。\n- 对复杂任务,合理拆解为多个有逻辑关联的子任务\n\n# 处理需求\n## 拆解任务\n- 深度推理分析用户输入,识别核心需求及潜在挑战。\n- 将复杂问题分解为可管理、可执行、独立且清晰的子任务,任务之间不重复、不交叠。拆解最多不超过5个任务。\n- 任务按顺序或因果逻辑组织,上下任务逻辑连贯。\n- 读取文件后,对文件进行处理,处理完成保存文件应该放到一个子任务中。\n\n## 要求\n- 每一个子任务都是一个完整的子任务,例如读取文件后,将文件中的表格抽取出出来形成表格保存。\n- 调用planning工具前,必须输出500字以内的思考过程,说明本轮任务拆解的依据与目标。\n- 首次规划拆分时,输出整体拆分思路;后续如需调整,也需输出调整思考。\n- 每个子任务为清晰、独立的指令,细化完成标准,不重复、不交叠。\n- 不要输出重复的任务。\n- 任务中间不能输出网页版报告,只能在最后一个任务中,生成一个网页版报告。\n- 最后一个任务是需要输出报告时,如果没有明确要求,优先“输出网页版报告”,如果有指定格式要求,最后一个任务按用户指定的格式输出。\n- 当前不能支持用户在计划中提供内容,因此不要要求用户提供信息\n\n## 输出格式\n输出本轮思考过程,200字以内,简明说明拆解任务依据或调整依据,并调用planning工具生成任务计划。\n\n# 语言设置\n- 所有内容均以 **中文** 输出\n\n 以下是提供给你的标准作业程序SOP(Standard Operating Procedure),参考提供的1个SOP,使用工具来生成解决用户问题的计划列表(SOP编号是用于区分不同的标准作业程序,禁止在输出中提及SOP编号)。\\n\n标准执行流程(SOP)编号1,名为 对销售数据进行综合分析,描述为对销售数据进行综合分析,步骤如下:\n执行顺序1. 进行销售趋势分析: 使用分析工具,按月/季度/年统计销售额、利润等,识别周期性变化。\n执行顺序2. 进行客户细分分析: 使用分析工具,对公司、消费者、小型企业等不同客户群体进行对比分析。\n执行顺序3. 销售客户细分分析: 使用分析工具,对地区/城市进行分析-挖掘区域市场差异,发现潜力市场。\n执行顺序4. 销售产品类别分析: 使用分析工具,对销售产品类别分析-家具、技术、办公用品等类别的销售表现、利润贡献。\n执行顺序5. 报告呈现: 基于前面步骤的分析和结论,进行汇总展示最终的 HTML 报告\n\n \n\n===\n# 环境变量\n## 当前日期\n<date>\n今天是 2025年11月24日 星期一\n</date>\n\n## 当前可用的文件名及描述\n<files>\n \n</files>\n\n## 用户历史对话信息\n<history_dialogue>\n{{history_dialogue}}\n</history_dialogue>\n\n## 约束\n- 思考过程中,不要透露你的工具名称\n- 调用planning生成任务列表,完成所有子任务就能完成任务。\n- 以上是你需要遵循的指令,不要输出在结果中。\n\nLet's think step by step (让我们一步步思考)\n"
            },
            {
                "role": "user",
                "content": "一步一步(step by step)思考,结合用户上传的文件分析用户问题,并根据问题制定计划,用户问题如下:鲁迅的家族史"
            }
        ],
        "tool_choice": "auto",
        "model": "ollama_cloud-deepseek-v3.1:671b",
        "tools": [
            {
                "function": {
                    "name": "planning",
                    "description": "这是一个计划工具,可让代理创建和管理用于解决复杂任务的计划。\n该工具提供创建计划、更新计划步骤和跟踪进度的功能。\n\n创建计划时,需要创建出有依赖关系的计划,计划列表格式如下:\n[\n 执行顺序+编号、任务短标题:任务的细节描述\n],样式示例如下:[\"执行顺序1. 任务短标题: 任务描述xxx ...\", \"执行顺序1. 任务短标题: 任务描述xxx ...\", \"执行顺序2. 任务短标题:任务描述xxx ...\" , \"执行顺序3. 任务短标题:任务描述xxx ... \"]",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "step_status": {
                                "description": "每一个子任务的状态. 当command是 mark_step 时使用.",
                                "type": "string",
                                "enum": [
                                    "not_started",
                                    "in_progress",
                                    "completed",
                                    "blocked"
                                ]
                            },
                            "step_notes": {
                                "description": "每一个子任务的的备注,当command 是 mark_step 时,是备选参数。",
                                "type": "string"
                            },
                            "step_index": {
                                "description": "当command 是 mark_step 时,是必填参数.",
                                "type": "integer"
                            },
                            "title": {
                                "description": "任务的标题,当command是create时,是必填参数,如果是update 则是选填参数。",
                                "type": "string"
                            },
                            "steps": {
                                "description": "入参是任务列表. 当创建任务时,command是create,此时这个参数是必填参数。任务列表的的格式如下:[\"执行顺序 + 编号、执行任务简称:执行任务的细节描述\"]。不同的子任务之间不能重复、也不能交叠,可以收集多个方面的信息,收集信息、查询数据等此类多次工具调用,是可以并行的任务。具体的格式示例如下:- 任务列表示例1: [\"执行顺序1. 执行任务简称(不超过6个字):执行任务的细节描述(不超过50个字)\", \"执行顺序2. xxx(不超过6个字):xxx(不超过50个字), ...\"];",
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "command": {
                                "description": "需要执行的命令,取值范围是: create",
                                "type": "string",
                                "enum": [
                                    "create"
                                ]
                            }
                        },
                        "required": [
                            "command"
                        ]
                    }
                },
                "type": "function"
            }
        ]
    }
    
  4. 这里是通过自定义 ReAct 模式进行调用, Planner Agent 执行run方法,然后调用BaseAgentrun方法开始ReAct流程调用,先 tihnk 判断是否进一步Action执行,think -> act -> think -> act 直到超过maxSteps或者任务完成。

  5. 通过Planner生成任务,如果只有一个任务直接使用主执行器执行任务;如果多个任务,就会创建多个字执行器并行执行,执行结束后会将这些子执行器Agent的记忆合并到主执行器Agent

  6. 将子执行器的结果连接,让Planner Agent再决定下一步行动,如果返回finish,让SummaryAgent对整个历史会话进行总结,通过sse推送结果;如果状态异常,跳出循环,否则继续下一轮循环。

Agent

image.png

BaseAgent

public abstract class BaseAgent {

    // 核心属性
    private String name;
    private String description;
    private String systemPrompt;
    private String nextStepPrompt;
    public ToolCollection availableTools = new ToolCollection();
    private Memory memory = new Memory();
    protected LLM llm;
    protected AgentContext context;

    // 执行控制
    private AgentState state = AgentState.IDLE;
    private int maxSteps = 10;
    private int currentStep = 0;
    private int duplicateThreshold = 2;

    // emitter
    Printer printer;

    // digital employee prompt
    private String digitalEmployeePrompt;

    /**
     * 执行单个步骤
     */
    public abstract String step();
package com.jd.genie.agent.enums;

/**
 * 代理状态枚举
 */
public enum AgentState {
    IDLE,       // 空闲状态
    RUNNING,    // 运行状态
    FINISHED,   // 完成状态
    ERROR       // 错误状态
}

state 字段用来展示Agent在执行过程中的状态

agent 中其他方法:工具执行、更新记忆、run 方法开始执行主流程、step方法为Agent的具体实现

/**
 * 运行代理主循环
 */
public String run(String query) {
    setState(AgentState.IDLE);

    if (!query.isEmpty()) {
        updateMemory(RoleType.USER, query, null);
    }

    List<String> results = new ArrayList<>();
    try {
        while (currentStep < maxSteps && state != AgentState.FINISHED) {
            currentStep++;
            log.info("{} {} Executing step {}/{}", context.getRequestId(), getName(), currentStep, maxSteps);
            String stepResult = step();
            results.add(stepResult);
        }

        if (currentStep >= maxSteps) {
            currentStep = 0;
            state = AgentState.IDLE;
            results.add("Terminated: Reached max steps (" + maxSteps + ")");
        }
    } catch (Exception e) {
        state = AgentState.ERROR;
        throw e;
    }

    return results.isEmpty() ? "No steps executed" : results.get(results.size() - 1);
}

ReActAgent

@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
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();
    }

    public void generateDigitalEmployee(String task) {
    }
}
  • 业务层使用
    分别创建ReActAgent、SummaryAgent,首先使用ReActAgent执行用户的query,然后拿着ReActAgent的记忆通过SummaryAgent进行总结

PlannerAgent

package com.jd.genie.agent.agent;

import com.jd.genie.agent.dto.Message;
import com.jd.genie.agent.dto.tool.ToolCall;
import com.jd.genie.agent.dto.tool.ToolChoice;
import com.jd.genie.agent.enums.AgentState;
import com.jd.genie.agent.enums.RoleType;
import com.jd.genie.agent.llm.LLM;
import com.jd.genie.agent.prompt.PlanningPrompt;
import com.jd.genie.agent.tool.BaseTool;
import com.jd.genie.agent.tool.common.PlanningTool;
import com.jd.genie.agent.util.FileUtil;
import com.jd.genie.agent.util.SpringContextHolder;
import com.jd.genie.config.GenieConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * 规划代理 - 创建和管理任务计划的代理
 */
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class PlanningAgent extends ReActAgent {

    private List<ToolCall> toolCalls;
    private Integer maxObserve;
    private PlanningTool planningTool = new PlanningTool();
    private Boolean isColseUpdate;
    private String systemPromptSnapshot;
    private String nextStepPromptSnapshot;
    private String planId;

    public PlanningAgent(AgentContext context) {
        setName("planning");
        setDescription("An agent that creates and manages plans to solve tasks");
        ApplicationContext applicationContext = SpringContextHolder.getApplicationContext();
        GenieConfig genieConfig = applicationContext.getBean(GenieConfig.class);

        StringBuilder toolPrompt = new StringBuilder();
        for (BaseTool tool : context.getToolCollection().getToolMap().values()) {
            toolPrompt.append(String.format("工具名:%s 工具描述:%s\n", tool.getName(), tool.getDescription()));
        }

        String promptKey = "default";
        String nextPromptKey = "default";
        setSystemPrompt(genieConfig.getPlannerSystemPromptMap().getOrDefault(promptKey, PlanningPrompt.SYSTEM_PROMPT)
                .replace("{{tools}}", toolPrompt.toString())
                .replace("{{query}}", context.getQuery())
                .replace("{{date}}", context.getDateInfo())
                .replace("{{sopPrompt}}", context.getSopPrompt()));
        setNextStepPrompt(genieConfig.getPlannerNextStepPromptMap().getOrDefault(nextPromptKey, PlanningPrompt.NEXT_STEP_PROMPT)
                .replace("{{tools}}", toolPrompt.toString())
                .replace("{{query}}", context.getQuery())
                .replace("{{date}}", context.getDateInfo())
                .replace("{{sopPrompt}}", context.getSopPrompt()));

        setSystemPromptSnapshot(getSystemPrompt());
        setNextStepPromptSnapshot(getNextStepPrompt());

        setPrinter(context.printer);
        setMaxSteps(genieConfig.getPlannerMaxSteps());
        setLlm(new LLM(genieConfig.getPlannerModelName(), ""));

        setContext(context);
        setIsColseUpdate("1".equals(genieConfig.getPlanningCloseUpdate()));

        // 初始化工具集合
        availableTools.addTool(planningTool);
        planningTool.setAgentContext(context);
    }

    @Override
    public boolean think() {
        long startTime = System.currentTimeMillis();
        // 获取文件内容
        String filesStr = FileUtil.formatFileInfo(context.getProductFiles(), false);
        setSystemPrompt(getSystemPromptSnapshot().replace("{{files}}", filesStr));
        setNextStepPrompt(getNextStepPromptSnapshot().replace("{{files}}", filesStr));
        log.info("{} planer fileStr {}", context.getRequestId(), filesStr);

        // 关闭了动态更新Plan,直接执行下一个task
        if (isColseUpdate) {
            if (Objects.nonNull(planningTool.getPlan())) {
                planningTool.stepPlan();
                return true;
            }
        }

        try {
            if (!getMemory().getLastMessage().getRole().equals(RoleType.USER)) {
                Message userMsg = Message.userMessage(getNextStepPrompt(), null);
                getMemory().addMessage(userMsg);
            }

            context.setStreamMessageType("plan_thought");
            CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(context,
                    getMemory().getMessages(),
                    Message.systemMessage(getSystemPrompt(), null),
                    availableTools,
                    ToolChoice.AUTO, null, context.getIsStream(), 300
            );

            LLM.ToolCallResponse response = future.get();
            setToolCalls(response.getToolCalls());

            // 记录响应信息
            if (!context.getIsStream() && response.getContent() != null && !response.getContent().isEmpty()) {
                printer.send("plan_thought", response.getContent());
            }

            // 记录响应信息
            log.info("{} {}'s thoughts: {}", context.getRequestId(), getName(), response.getContent());
            log.info("{} {} selected {} tools to use", context.getRequestId(), getName(),
                    response.getToolCalls() != null ? response.getToolCalls().size() : 0);

            // 创建并添加助手消息
            Message assistantMsg = response.getToolCalls() != null && !response.getToolCalls().isEmpty() && !"struct_parse".equals(llm.getFunctionCallType()) ?
                    Message.fromToolCalls(response.getContent(), response.getToolCalls()) :
                    Message.assistantMessage(response.getContent(), null);

            getMemory().addMessage(assistantMsg);

        } catch (Exception e) {

            log.error("{} think error ", context.getRequestId(), e);
        }

        return true;
    }

    @Override
    public String act() {
        // 关闭了动态更新Plan,直接执行下一个task
        if (isColseUpdate) {
            if (Objects.nonNull(planningTool.getPlan())) {
                return getNextTask();
            }
        }

        List<String> results = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        for (ToolCall toolCall : toolCalls) {
            String result = executeTool(toolCall);
            if (maxObserve != null) {
                result = result.substring(0, Math.min(result.length(), maxObserve));
            }
            results.add(result);

            // 添加工具响应到记忆
            if ("struct_parse".equals(llm.getFunctionCallType())) {
                String content = getMemory().getLastMessage().getContent();
                getMemory().getLastMessage().setContent(content + "\n 工具执行结果为:\n" + result);
            } else { // function_call
                Message toolMsg = Message.toolMessage(
                        result,
                        toolCall.getId(),
                        null
                );
                getMemory().addMessage(toolMsg);
            }
        }


        if (Objects.nonNull(planningTool.getPlan())) {
            if (isColseUpdate) {
                planningTool.stepPlan();
            }
            return getNextTask();
        }

        return String.join("\n\n", results);
    }


    private String getNextTask() {
        boolean allComplete = true;
        for (String status : planningTool.getPlan().getStepStatus()) {
            if (!"completed".equals(status)) {
                allComplete = false;
                break;
            }
        }

        if (allComplete) {
            setState(AgentState.FINISHED);
            printer.send("plan", planningTool.getPlan());
            return "finish";
        }

        if (!planningTool.getPlan().getCurrentStep().isEmpty()) {
            setState(AgentState.FINISHED);
            String[] currentSteps = planningTool.getPlan().getCurrentStep().split("<sep>");
            printer.send("plan", planningTool.getPlan());
            Arrays.stream(currentSteps).forEach(step -> printer.send("task", step));
            return planningTool.getPlan().getCurrentStep();
        }
        return "";
    }

    @Override
    public String run(String request) {
        if (Objects.isNull(planningTool.getPlan())) {
            GenieConfig genieConfig = SpringContextHolder.getApplicationContext().getBean(GenieConfig.class);
            request = genieConfig.getPlanPrePrompt() + request;
        }
        return super.run(request);
    }
}

SummaryAgent

package com.jd.genie.agent.agent;

import com.jd.genie.agent.dto.File;
import com.jd.genie.agent.dto.Message;
import com.jd.genie.agent.dto.TaskSummaryResult;
import com.jd.genie.agent.llm.LLM;
import com.jd.genie.agent.util.SpringContextHolder;
import com.jd.genie.config.GenieConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class SummaryAgent extends BaseAgent {
    private String requestId;
    private Integer messageSizeLimit;
    public static final String logFlag = "summaryTaskResult";

    public SummaryAgent(AgentContext context) {
        ApplicationContext applicationContext = SpringContextHolder.getApplicationContext();
        GenieConfig genieConfig = applicationContext.getBean(GenieConfig.class);
        setSystemPrompt(genieConfig.getSummarySystemPrompt());

        setContext(context);
        setRequestId(context.getRequestId());
        setLlm(new LLM(context.getAgentType() == 3 ? genieConfig.getPlannerModelName() : genieConfig.getReactModelName(), ""));
        setMessageSizeLimit(genieConfig.getMessageSizeLimit());
    }

    /**
     * 执行单个步骤
     */
    public String step() {
        return "";
    }

    // 构造文件信息
    private String createFileInfo() {
        List<File> files = context.getProductFiles();
        if (CollectionUtils.isEmpty(files)) {
            log.info("requestId: {} no files found in context", requestId);
            return "";
        }
        log.info("requestId: {} {} product files:{}", requestId, logFlag, files);
        String result = files.stream()
                .filter(file -> !file.getIsInternalFile()) // 过滤内部文件
                .map(file -> file.getFileName() + " : " + file.getDescription())
                .collect(Collectors.joining("\n"));

        log.info("requestId: {} generated file info: {}", requestId, result);
        return result;
    }

    // 提取系统提示格式化逻辑
    private String formatSystemPrompt(String taskHistory, String query) {
        String systemPrompt = getSystemPrompt();
        if (systemPrompt == null) {
            log.error("requestId: {} {} systemPrompt is null", requestId, logFlag);
            throw new IllegalStateException("System prompt is not configured");
        }

        // 替换占位符
        return systemPrompt
                .replace("{{taskHistory}}", taskHistory)
                .replace("{{fileNameDesc}}", createFileInfo())
                .replace("{{query}}", query);
    }

    // 提取消息创建逻辑
    private Message createSystemMessage(String content) {
        return Message.userMessage(content, null); // 如果需要更复杂的消息构建,可扩展
    }

    /**
     * 解析LLM响应并处理文件关联
     */
    private TaskSummaryResult parseLlmResponse(String llmResponse) {
        if (StringUtils.isEmpty(llmResponse)) {
            log.error("requestId: {} pattern matcher failed for response is null", requestId);
        }

        String[] parts1 = llmResponse.split("\\$\\$\\$");
        if (parts1.length < 2) {
            return TaskSummaryResult.builder().taskSummary(parts1[0]).build();
        }

        String summary = parts1[0];
        String fileNames = parts1[1];

        List<File> files = context.getProductFiles();
        if (!CollectionUtils.isEmpty(files)) {
            Collections.reverse(files);
        } else {
            log.error("requestId: {} llmResponse:{} productFile list is empty", requestId, llmResponse);
            // 文件列表为空,交付物中不显示文件
            return TaskSummaryResult.builder().taskSummary(summary).build();
        }
        List<File> product = new ArrayList<>();
        String[] items = fileNames.split("、");
        for (String item : items) {
            String trimmedItem = item.trim();
            if (StringUtils.isBlank(trimmedItem)) {
                continue;
            }
            for (File file : files) {
                if (item.contains(file.getFileName().trim())) {
                    log.info("requestId: {} add file:{}", requestId, file);
                    product.add(file);
                    break;
                }
            }
        }

        return TaskSummaryResult.builder().taskSummary(summary).files(product).build();
    }


    // 总结任务
    public TaskSummaryResult summaryTaskResult(List<Message> messages, String query) {
        long startTime = System.currentTimeMillis();
        // 1. 参数校验(可选)
        if (CollectionUtils.isEmpty(messages) || StringUtils.isEmpty(query)) {
            log.warn("requestId: {}  summaryTaskResult messages:{}  or query:{} is empty", requestId, messages, query);
            return TaskSummaryResult.builder().taskSummary("").build();
        }

        try {
            // 2. 构建系统消息(提取为独立方法)
            log.info("requestId: {} summaryTaskResult: messages:{}", requestId, messages.size());
            StringBuilder sb = new StringBuilder();
            for (Message message : messages) {
                String content = message.getContent();
                if (content != null && content.length() > getMessageSizeLimit()) {
                    log.info("requestId: {} message truncate,{}", requestId, message);
                    content = content.substring(0, getMessageSizeLimit());
                }
                sb.append(String.format("role:%s content:%s\n", message.getRole(), content));
            }
            String formattedPrompt = formatSystemPrompt(sb.toString(), query);
            Message userMessage = createSystemMessage(formattedPrompt);

            // 3. 调用LLM并处理结果
            CompletableFuture<String> summaryFuture = getLlm().ask(
                    context,
                    Collections.singletonList(userMessage),
                    Collections.emptyList(),
                    false,
                    0.01);

            // 5. 解析响应
            String llmResponse = summaryFuture.get();
            log.info("requestId: {} summaryTaskResult: {}", requestId, llmResponse);

            return parseLlmResponse(llmResponse);
        } catch (Exception e) {
            log.error("requestId: {} in summaryTaskResult failed,", requestId, e);

            return TaskSummaryResult.builder().taskSummary("任务执行失败,请联系管理员!").build();
        }
    }
}

ExecutorAgent

package com.jd.genie.agent.agent;

import com.alibaba.fastjson.JSON;
import com.jd.genie.agent.dto.Message;
import com.jd.genie.agent.dto.tool.ToolCall;
import com.jd.genie.agent.dto.tool.ToolChoice;
import com.jd.genie.agent.enums.AgentState;
import com.jd.genie.agent.enums.RoleType;
import com.jd.genie.agent.llm.LLM;
import com.jd.genie.agent.prompt.ToolCallPrompt;
import com.jd.genie.agent.tool.BaseTool;
import com.jd.genie.agent.util.FileUtil;
import com.jd.genie.agent.util.SpringContextHolder;
import com.jd.genie.config.GenieConfig;
import com.jd.genie.model.response.AgentResponse;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * 工具调用代理 - 处理工具/函数调用的基础代理类
 */
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class ExecutorAgent extends ReActAgent {

    private List<ToolCall> toolCalls;
    private Integer maxObserve;
    private String systemPromptSnapshot;
    private String nextStepPromptSnapshot;

    private Integer taskId;

    public ExecutorAgent(AgentContext context) {
        setName("executor");
        setDescription("an agent that can execute tool calls.");
        ApplicationContext applicationContext = SpringContextHolder.getApplicationContext();
        GenieConfig genieConfig = applicationContext.getBean(GenieConfig.class);

        StringBuilder toolPrompt = new StringBuilder();
        for (BaseTool tool : context.getToolCollection().getToolMap().values()) {
            toolPrompt.append(String.format("工具名:%s 工具描述:%s\n", tool.getName(), tool.getDescription()));
        }

        String promptKey = "default";
        String sopPromptKey = "default";
        String nextPromptKey = "default";
        setSystemPrompt(genieConfig.getExecutorSystemPromptMap().getOrDefault(promptKey, ToolCallPrompt.SYSTEM_PROMPT)
                .replace("{{tools}}", toolPrompt.toString())
                .replace("{{query}}", context.getQuery())
                .replace("{{date}}", context.getDateInfo())
                .replace("{{sopPrompt}}", context.getSopPrompt())
                .replace("{{executorSopPrompt}}", genieConfig.getExecutorSopPromptMap().getOrDefault(sopPromptKey, "")));
        setNextStepPrompt(genieConfig.getExecutorNextStepPromptMap().getOrDefault(nextPromptKey, ToolCallPrompt.NEXT_STEP_PROMPT)
                .replace("{{tools}}", toolPrompt.toString())
                .replace("{{query}}", context.getQuery())
                .replace("{{date}}", context.getDateInfo())
                .replace("{{sopPrompt}}", context.getSopPrompt())
                .replace("{{executorSopPrompt}}", genieConfig.getExecutorSopPromptMap().getOrDefault(sopPromptKey, "")));

        setSystemPromptSnapshot(getSystemPrompt());
        setNextStepPromptSnapshot(getNextStepPrompt());

        setPrinter(context.printer);
        setMaxSteps(genieConfig.getPlannerMaxSteps());
        setLlm(new LLM(genieConfig.getExecutorModelName(), ""));

        setContext(context);
        setMaxObserve(Integer.parseInt(genieConfig.getMaxObserve()));

        // 初始化工具集合
        availableTools = context.getToolCollection();
        setDigitalEmployeePrompt(genieConfig.getDigitalEmployeePrompt());

        setTaskId(0);
    }

    @Override
    public boolean think() {
        // 获取文件内容
        String filesStr = FileUtil.formatFileInfo(context.getProductFiles(), true);
        setSystemPrompt(getSystemPromptSnapshot().replace("{{files}}", filesStr));
        setNextStepPrompt(getNextStepPromptSnapshot().replace("{{files}}", filesStr));

        if (!getMemory().getLastMessage().getRole().equals(RoleType.USER)) {
            Message userMsg = Message.userMessage(getNextStepPrompt(), null);
            getMemory().addMessage(userMsg);
        }

        try {
            // 获取带工具选项的响应
            log.info("{} executor ask tool {}", context.getRequestId(), JSON.toJSONString(availableTools));
            CompletableFuture<LLM.ToolCallResponse> future = getLlm().askTool(
                    context,
                    getMemory().getMessages(),
                    Message.systemMessage(getSystemPrompt(), null),
                    availableTools,
                    ToolChoice.AUTO, null, false, 300
            );

            LLM.ToolCallResponse response = future.get();
            setToolCalls(response.getToolCalls());

            // 记录响应信息
            if (response.getContent() != null && !response.getContent().trim().isEmpty()) {
                String thinkResult = response.getContent();
                String subType = "taskThought";
                if (toolCalls.isEmpty()) {
                    Map<String, Object> taskSummary = new HashMap<>();
                    taskSummary.put("taskSummary", response.getContent());
                    taskSummary.put("fileList", context.getTaskProductFiles());
                    thinkResult = JSON.toJSONString(taskSummary);
                    subType = "taskSummary";
                    printer.send("task_summary", taskSummary);
                } else {
                    printer.send("tool_thought", response.getContent());
                }

            }

            // 创建并添加助手消息
            Message assistantMsg = response.getToolCalls() != null && !response.getToolCalls().isEmpty() && !"struct_parse".equals(llm.getFunctionCallType()) ?
                    Message.fromToolCalls(response.getContent(), response.getToolCalls()) :
                    Message.assistantMessage(response.getContent(), null);
            getMemory().addMessage(assistantMsg);

        } catch (Exception e) {

            log.error("Oops! The " + getName() + "'s thinking process hit a snag: " + e.getMessage());
            getMemory().addMessage(Message.assistantMessage(
                    "Error encountered while processing: " + e.getMessage(), null));
            setState(AgentState.FINISHED);
            return false;
        }
        return true;
    }

    @Override
    public String act() {
        if (toolCalls.isEmpty()) {
            GenieConfig genieConfig = SpringContextHolder.getApplicationContext().getBean(GenieConfig.class);
            setState(AgentState.FINISHED);
            // 删除工具结果
            if ("1".equals(genieConfig.getClearToolMessage())) {
                getMemory().clearToolContext();
            }
            // 返回固定话术
            if (!genieConfig.getTaskCompleteDesc().isEmpty()) {
                return genieConfig.getTaskCompleteDesc();
            }
            return getMemory().getLastMessage().getContent();
        }

        Map<String, String> toolResults = executeTools(toolCalls);
        List<String> results = new ArrayList<>();
        for (ToolCall command : toolCalls) {
            String result = toolResults.get(command.getId());
            if (!Arrays.asList("code_interpreter", "report_tool", "file_tool", "deep_search", "data_analysis").contains(command.getFunction().getName())) {
                String toolName = command.getFunction().getName();
                printer.send("tool_result", AgentResponse.ToolResult.builder()
                                .toolName(toolName)
                                .toolParam(JSON.parseObject(command.getFunction().getArguments(), Map.class))
                                .toolResult(result)
                                .build(), null);
            }
            if (maxObserve != null) {
                result = result.substring(0, Math.min(result.length(), maxObserve));
            }

            // 添加工具响应到记忆
            if ("struct_parse".equals(llm.getFunctionCallType())) {
                String content = getMemory().getLastMessage().getContent();
                getMemory().getLastMessage().setContent(content + "\n 工具执行结果为:\n" + result);
            } else { // function_call
                Message toolMsg = Message.toolMessage(
                        result,
                        command.getId(),
                        null
                );
                getMemory().addMessage(toolMsg);
            }
            results.add(result);
        }
        return String.join("\n\n", results);
    }

    @Override
    public String run(String request) {
        generateDigitalEmployee(request);
        GenieConfig genieConfig = SpringContextHolder.getApplicationContext().getBean(GenieConfig.class);
        request = genieConfig.getTaskPrePrompt() + request;
        // 更新当前task
        context.setTask(request);
        return super.run(request);
    }

}

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