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

@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;
}
首先根据问题查询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
}
]
将查询到的sop流程作为Prompt放到上下文中
创建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"
}
]
}
这里是通过自定义 ReAct 模式进行调用, Planner Agent 执行run方法,然后调用BaseAgentrun方法开始ReAct流程调用,先 tihnk 判断是否进一步Action执行,think -> act -> think -> act 直到超过maxSteps或者任务完成。
通过Planner生成任务,如果只有一个任务直接使用主执行器执行任务;如果多个任务,就会创建多个字执行器并行执行,执行结束后会将这些子执行器Agent的记忆合并到主执行器Agent
将子执行器的结果连接,让Planner Agent再决定下一步行动,如果返回finish,让SummaryAgent对整个历史会话进行总结,通过sse推送结果;如果状态异常,跳出循环,否则继续下一轮循环。

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);
}
@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) {
}
}
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);
}
}
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();
}
}
}
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);
}
}