第四章 06:4.6 统一入口 —— analyze() 能力收束

第四章 06:4.6 统一入口 ------ analyze() 能力收束

课程专题 :《AI Agents 开发实践》

原文链接 :https://s.juejin.cn/ds/TAqu_hbSmd8/

章节 :第四章 4.6 统一入口

作者:Java 开发者转型 AI 全栈


4.6.1 问题:能力都在,但散着

到 4.5 为止,我们有了四个能力服务:Memory(记忆)、Filesystem(文件工具)、VectorStore(语义检索)、Orchestrator(多 Agent 编排)。但它们各自为战:

  • 调 orchestrate 拿到报告,不会落盘
  • 报告不写回记忆,下次对话模型不知道上次分析过什么
  • 前端要调 3 个接口才能拼出完整体验

需要一个应用层门面,把一次「需求分析」的完整闭环收进一个方法。

4.6.2 analyze() 的四步职责

typescript 复制代码
import { Injectable } from '@nestjs/common';
import { OrchestratorService } from './agents/orchestrator.service';
import { FilesystemService } from './filesystem/filesystem.service';
import { RunnableMemoryService } from './memory/runnable-memory.service';

export type AnalyzeResult =
    | { needsClarification: true; questions: string[] }
    | { needsClarification: false; report: string; reportPath: string; usedAgents: string[] };

@Injectable()
export class AdvancedAnalysisService {
    constructor(
        private readonly orchestrator: OrchestratorService,
        private readonly filesystem: FilesystemService,
        private readonly memory: RunnableMemoryService
    ) {}

    async analyze(sessionId: string, input: string): Promise<AnalyzeResult> {
        // ① 编排:跑多 Agent 流水线(内含澄清短路)
        const result = await this.orchestrator.orchestrate(input);

        // ② 澄清返回:不落盘、不写记忆,把问题抛回给用户
        if (result.status === 'need_clarification') {
            return { needsClarification: true, questions: result.clarificationQuestions };
        }

        // ③ 报告落盘:reports/<sessionId>-<时间戳>.md
        const report = result.report ?? '分析未生成报告';
        const reportPath = this.filesystem.writeReport(
            `reports/${sessionId}-${Date.now()}.md`,
            report
        );
        // ④ 写回记忆:appendMessage 直接写历史,不重新调用模型
        await this.memory.appendMessage(sessionId, input, report);

        return { needsClarification: false, report, reportPath, usedAgents: result.usedAgents };
    }
}

最值得品味的是第 ④ 步 :报告已经生成,写回记忆时用 appendMessage 直接写历史,而不是再调一次模型复述。省一次调用、省一份 token,且保证「记忆里的 AI 消息」和「API 返回的报告」一字不差。

闭环意义:下一次用户在这个 sessionId 上继续对话时,模型通过 4.2 的记忆回填,天然「知道」上次分析过什么------第四章开头的问题(失真)在这里被正式闭环。

Controller 与 Module 注册

typescript 复制代码
// advanced.controller.ts 末尾
@Controller('advanced')
export class AdvancedController {
    constructor(private readonly advanced: AdvancedAnalysisService) {}

    @Post('analyze')
    analyze(@Body() body: { sessionId: string; input: string }) {
        return this.advanced.analyze(body.sessionId, body.input);
    }
}

// advanced.module.ts:注册服务与控制器
@Module({
    controllers: [MemoryController, FilesystemController, EmbeddingController, AgentsController, AdvancedController],
    providers: [RunnableMemoryService, FilesystemService, EmbeddingService, VectorStoreService, OrchestratorService, AdvancedAnalysisService],
})
export class AdvancedModule {}

4.6.3 验收(三条命令看闭环)

cmd 复制代码
:: a. 完整链路 ------ 编排 + 落盘 + 写回记忆
curl.exe -s -X POST http://localhost:4001/api/advanced/analyze -H "Content-Type: application/json" -d "{\"sessionId\":\"demo\",\"input\":\"帮我判断这个需求是否完整,并产出一份需求分析报告:开发一个面向需求分析师的会话记忆系统,支持多轮澄清并自动裁剪长对话上下文\"}"

:: b. 报告真的落盘了
dir e:\code\gitee\cookieboty\autix-learning\ch04\services\chat\workspace\reports

:: c. 报告真的写回记忆了
curl.exe -s "http://localhost:4001/api/memory/history/demo"

实际结果:

  • a:返回完整 report + reportPath: "reports/demo-1792313491229.md" + usedAgents 五个全齐;
  • b:目录下有 4.3 的 test.md(53 字节)和新的 demo-1792313491229.md(3,713 字节);
  • c:恰好 2 条消息(Human + AI),且 AI 消息内容 = a 返回的 report,一字不差 ------证明是 appendMessage 直写,没有重新调模型。

一个已知现象:report 结尾停在半截(finish_reason: length 截断,见 4.5 坑二),不影响闭环验证。

Java 对照

analyze() 就是教科书级的 Application Service / Facade:

java 复制代码
@Service
public class RequirementAnalysisFacade {
    public AnalyzeResult analyze(String sessionId, String input) {
        OrchestratorResult result = orchestrator.orchestrate(input);        // ① 编排
        if (result.isNeedClarification()) {
            return AnalyzeResult.clarify(result.getQuestions());            // ② 短路
        }
        String path = filesystem.writeReport(reportName(sessionId), result.getReport()); // ③ 落盘
        memory.append(sessionId, input, result.getReport());                // ④ 写回
        return AnalyzeResult.done(result.getReport(), path);
    }
}
// 编排、存储、记忆各层只被它调用,前端只认这一个入口 ------ 典型的门面模式收口

DDD 的说法:领域服务(各 Agent/能力)保持纯粹,应用服务负责事务脚本式的流程编排 。四层各司其职,analyze() 一百行都不到。

判断标准

什么时候建统一入口? 当多个能力服务开始被「同一个业务场景」以固定顺序组合消费时。

收口的好处立刻可见 :换编排策略、加权限、加审计,只改一个地方;前端永远只认 POST /api/advanced/analyze。


回到索引 :第四章博客索引 | 下一节:07 4.7 本章小结(./07-4.7 本章小结.md)

相关推荐
硅谷秋水1 小时前
DynaHarness 方法总结
人工智能·机器学习·语言模型·机器人
TK泰妞1 小时前
跨境短视频真正难在哪?一套从找参考到批量测试的 AI 工作流
大数据·人工智能
m0_622023531 小时前
产业资本运作之金融结构重塑产业生态
人工智能·深度学习·金融
aichitang20241 小时前
Meta Muse Spark Open:816B 巨型开放权重多模态 Agent 模型架构、部署实战与社区首测全解
大数据·人工智能·python·算法·ai·架构·spark
她的男孩1 小时前
JAR 全对仍 checksum mismatch:Forge Admin 把 Flyway 从 filesystem 改成 classpath
java·数据库·spring boot
鬼手点金1 小时前
如何选择模型
java·前端·人工智能·git·深度学习
xrgs_shz1 小时前
基于OGD-Net的果园环境下蟠桃生长期检测
人工智能·yolo·目标检测·计算机视觉
我叫孙一鸣-专注电子元器件1 小时前
快反镜的镜面偏转角,为什么不等于光束偏转角?
人工智能·智能硬件·半导体·压电驱动器
古法老登瞎折腾1 小时前
大数据智能运维实战(三):实现第一个运维 MCP 工具——查询节点与组件状态
java·大数据·运维·人工智能·spring ai·mcp