端到端产品级通用智能体--JoyAgent总结之深度搜索智能问答

目录

  • 前置文章
  • 业务展示
  • 逐步分析
    • 下图是官网给的总体的架构图
    • 代码级别的时序图
      • PlanAgent拆解之总体拆解----核心代码1
      • While循环调用ExecutorAgent + 下一步PlanAgent
  • 对比没有PlanAgent直接大模型执行单Agent的效果
  • 对比Dify等
  • deep_search深度搜索工具详解
  • deep_search深度搜索工具详解
    • 高效并行搜索 (_search_queries_and_dedup)
    • 动态上下文管理
    • 拿腾讯2025年第三季度财报分析举例

前置文章


业务展示

  • joyagent选择深度搜索,网页模式,并提问:


    提问.png
  • 最终结果展示


    最终结果展示.png

逐步分析

  • joyagent, 是使用多智能体,PlanningAgent总体拆解如下:
    • 用户需求是分析腾讯2025年第三季度财报并以表格形式展示,但当前未提供文件。需先获取公开财报文件,再进行解析与分析。任务拆解为:
      1. 获取财报文件;
      2. 提取关键财务数据;
      3. 将数据整理为结构化表格;
      4. 分析财务表现;
      5. 生成网页版报告。该流程符合从数据获取到分析输出的逻辑顺序,覆盖完整分析链条。
  • 然后PlannigAgent每一步子流程都是ExecutorAgent去执行


    流程.png

下图是官网给的总体的架构图

总体架构图.png

代码级别的时序图

  • 核心代码在genie-backend


    代码级别的时序图.png
  • PlanAgent使用的tool就是PlanTool,专门规划拆解任务的,ExecutorAgent有各种各样的Tool ,比如深度搜索Tool, 报告生成tool等
  • PlanAgent对应的Tool, 大模型在执行step的think流程会生成符合对应tool的输入,在act执行工具调用会填充对应信息


    PlanTool.png
  • ExecutorAgent对应的Tool比较多样,是真正执行任务的Agent


    ExecutorAgent.png
  • 核心代码如下, 入口是/AutoAgent


    图示.png
@Override
public String handle(AgentContext agentContext, AgentRequest request) {

    // sop recall
    handleSopRecall(agentContext, request);

    PlanningAgent planning = new PlanningAgent(agentContext);
    ExecutorAgent executor = new ExecutorAgent(agentContext);
    SummaryAgent summary = new SummaryAgent(agentContext);
    summary.setSystemPrompt(summary.getSystemPrompt().replace("{{query}}", request.getQuery()));
    // 核心代码1
    String planningResult = planning.run(agentContext.getQuery());
    int stepIdx = 0;
    int maxStepNum = genieConfig.getPlannerMaxSteps();
    while (stepIdx <= maxStepNum) {
        List<String> planningResults = Arrays.stream(planningResult.split("<sep>"))
                .map(task -> "你的任务是:" + task)
                .collect(Collectors.toList());
        String executorResult;
        agentContext.getTaskProductFiles().clear();
        if (planningResults.size() == 1) {
            // 核心代码2
            executorResult = executor.run(planningResults.get(0));
        } else {
            Map<String, String> tmpTaskResult = new ConcurrentHashMap<>();
            CountDownLatch taskCount = ThreadUtil.getCountDownLatch(planningResults.size());
            int memoryIndex = executor.getMemory().size();
            List<ExecutorAgent> slaveExecutors = new ArrayList<>();
            for (String task : planningResults) {
                ExecutorAgent slaveExecutor = new ExecutorAgent(agentContext);
                slaveExecutor.setState(executor.getState());
                slaveExecutor.getMemory().addMessages(executor.getMemory().getMessages());
                slaveExecutors.add(slaveExecutor);
                ThreadUtil.execute(() -> {
                    String taskResult = slaveExecutor.run(task);
                    tmpTaskResult.put(task, taskResult);
                    taskCount.countDown();
                });
            }
            ThreadUtil.await(taskCount);
            for (ExecutorAgent slaveExecutor : slaveExecutors) {
                for (int i = memoryIndex; i < slaveExecutor.getMemory().size(); i++) {
                    executor.getMemory().addMessage(slaveExecutor.getMemory().get(i));
                }
                slaveExecutor.getMemory().clear();
                executor.setState(slaveExecutor.getState());
            }
            executorResult = String.join("\n", tmpTaskResult.values());
        }
        // 核心代码3
        planningResult = planning.run(executorResult);
        if ("finish".equals(planningResult)) {
            //任务成功结束,总结任务
            TaskSummaryResult result = summary.summaryTaskResult(executor.getMemory().getMessages(), request.getQuery());

            Map<String, Object> taskResult = new HashMap<>();
            taskResult.put("taskSummary", result.getTaskSummary());

            if (CollectionUtils.isEmpty(result.getFiles())) {
                if (!CollectionUtils.isEmpty(agentContext.getProductFiles())) {
                    List<File> fileResponses = agentContext.getProductFiles();
                    // 过滤中间搜索结果文件
                    fileResponses.removeIf(file -> Objects.nonNull(file) && file.getIsInternalFile());
                    Collections.reverse(fileResponses);
                    taskResult.put("fileList", fileResponses);
                }
            } else {
                taskResult.put("fileList", result.getFiles());
            }

            agentContext.getPrinter().send("result", taskResult);


            break;
        }
        if (planning.getState() == AgentState.IDLE || executor.getState() == AgentState.IDLE) {
            agentContext.getPrinter().send("result", "达到最大迭代次数,任务终止。");
            break;
        }
        if (planning.getState() == AgentState.ERROR || executor.getState() == AgentState.ERROR) {
            agentContext.getPrinter().send("result", "任务执行异常,请联系管理员,任务终止。");
            break;
        }
        stepIdx++;
    }

    return "";
}
PlanAgent拆解之总体拆解----核心代码1
  • 对应代码
String planningResult = planning.run(agentContext.getQuery());
  • 进入核心代码1之后planning.run会调用常见的step方法,step氛围think-act不断循序直至大模型任务达到了可以终止的条件或者达到最大迭代次数
  • 比如拿获取腾讯财报举例,总体就是step步骤, think就是调用大模型,act就是具体执行调用工具,大模型会通过循环step决策流程是否可以结束


    举例.png
  • 核心代码1的think思考llm响应部分为:
首先确认腾讯2025年第三季度财报是否已公开发布。由于当前日期为2025年12月12日,理论上该财报应已在11月初披露。接下来将通过权威财经渠道搜索并获取该财报的公开信息。
  • 那planning.run(agentContext.getQuery())就是对应的
执行顺序1. 获取财报文件:从腾讯官网或公开财经平台下载2025年第三季度财务报告
  • 调用核心代码1对应的act的tool如下:
ToolCall.Function(name=planning, arguments={"command": "{"command": "create", "title": "腾讯2025Q3财报分析", "steps": ["执行顺序1. 读取文件:加载腾讯2025年Q3财报文件,确认文件完整性与格式兼容性", "执行顺序2. 提取数据:从财报中抽取关键财务指标,包括总收入、净利润、运营成本等", "执行顺序3. 整理表格:将提取的数据按财务类别组织成结构化表格,确保清晰可读", "执行顺序4. 生成报告:将表格嵌入HTML页面,生成可视化网页版财报分析报告"]})
while循环调用ExecutorAgent + 下一步PlanAgent
  • 根据主Agent-PlanAgent分配的任务
执行顺序1. 获取财报文件:从腾讯官网或公开财经平台下载2025年第三季度财务报告
  • ExecutorAgent接收到任务之后调用Step(Think-Act), Think接受PlanAgent来的子任务决策Act要取调用deep_search tool, Act继续调用深度搜索获取数据


    调用.png
  • 获取到足够信息之后核心代码3根据ExecutorAgent的结果和当前的执行步骤,决策出:

执行顺序2. 提取财报数据:解析PDF或网页内容,提取营收、利润、成本等关键指标
  • 后续流程
执行顺序3. 整理数据成表:将提取的数据转化为结构化表格,包含项目、金额及同比变化
执行顺序4. 分析财务表现:分析收入构成、利润趋势及主要业务贡献 处理几次之后也变成处理完了
执行顺序5. 输出网页版报告:生成包含表格与分析结果的HTML格式报告
各个执行步骤1.png

各个执行步骤2.png

对比没有PlanAgent直接大模型执行单Agent的效果

  • 单Agent, 可能会漏分析广告业务,用普通搜索替代deep_search等


    单Agent.png
  • 多Agent, 有主Agent校验任务把控全局,能使得任务被更好的完成


    多Agent.png

对比Dify等

  • Dify 的 Agent 主要是单个 Agent 进行任务规划和工具调用,属于相对简单的 ReAct 模式,更适合中小型应用和快速原型开发。
  • 而对于复杂场景的任务,就少不了通过 Multi-Agent 方式来实现

deep_search深度搜索工具详解

  • 核心代码在genie-tool, python项目,入口/deepsearch
  • 流程图


    流程图.png
  • 主流程展开


    主流程展开.png

高效并行搜索 (_search_queries_and_dedup)

  • 去重策略:基于文档内容哈希(seen_content集合)
  • 线程安全:每个线程创建独立事件循环(asyncio.new_event_loop())

动态上下文管理

  • 文档截断:根据模型上下文长度自动压缩
truncate_docs = truncate_files(self.current_docs, max_tokens=int(max_tokens*0.8))
  • 环境变量:
  1. SINGLE_PAGE_MAX_SIZE:搜索结果摘要长度(默认200字符)
  2. SEARCH_REASONING_MODEL/SEARCH_ANSWER_MODEL:指定推理/答案生成模型

拿腾讯2025年第三季度财报分析举例

  • 开始时


    开始.png
  • 查询分解(query_decompose), 输入腾讯2025年第三季度财报的核心亮点、风险点及未来战略方向是什么?, llm分解
sub_queries = [
    "腾讯2025Q3总营收及同比增长率",
    "腾讯2025Q3游戏业务收入细分(国内/海外)",
    "腾讯2025Q3广告与金融科技收入表现",
    "腾讯2025Q3研发投入与AI布局进展",
    "腾讯2025Q3财报提及的主要风险因素",
    "腾讯2025Q3股东分红与股票回购计划",
    "腾讯2025Q3对2026年战略规划"
]
  • 并行搜索(_search_queries_and_dedup),线程分配(3线程处理7个子查询)


    并行搜索.png
  • 推理验证(search_reasoning),如果搜索不充分则继续下一波循环


    整体流程.png

参考文章

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容