面向互联网2C业务的分布式类Manus Java框架

发布于:2025-07-25 ⋅ 阅读:(34) ⋅ 点赞:(0)

图片

本文介绍了阿里巴巴推出的分布式类ManusAgent框架——ali-langengine-dflow,旨在解决现有Agent架构在互联网2C业务场景中的局限性。文章从背景出发,分析了当前主流Agent架构(如Manus、字节TARS、AutoGLM)存在的问题,如云虚拟机架构的数据孤岛问题、本地化Agent架构的响应速度与安全性不足等,进而提出了一种结合分布式服务端与异构C端的混合架构。

图片

背景

在Manus成功实现营销破圈引发行业关注的背景下,Agent类产品正逐渐成为市场竞逐的技术焦点。当前主流技术方案呈现两种典型架构:以Manus为代表的云虚拟机架构体系,以及字节TARS、AutoGLM等采用的客户端架构体系,二者本质上均属于单机Agent技术框架(这也是Python技术生态主导的AI领域常见的技术范式)。

然而深入分析发现,现有架构存在显著局限性:云虚拟机架构受限于虚拟化隔离机制,难以通过用户登录态实现跨平台数据贯通,无法有效打破信息孤岛;而本地化Agent架构本质上属于客户端时代的技术思维,在应对互联网业务场景时,其迭代响应速度、安全防护能力等关键指标已难以满足现代业务需求。

我们需要的理想架构,是核心智能在云端,但是执行工具能触达用户,这需要分布式服务端和异构C端混合架构。

解决方案

  2.1 指导思想

  • 1.1 首先是AI框架本体

我们认为要实施足够灵活的Agent:

  1. 以完备能力的代码为载体而不是再次定义DSL,外加开源可轻松地复制修改非常精细的行为。

  2. 业务研发能力最强的平台仍旧是Java生态,减少不必要的架构复杂度也倾向于Java库而不是一个平台。

  3. Agent需要很多的能力和底层大模型抽象的依赖,丰富的生态会让其更加好用。

考虑到这些,我们整体基于Ali-LangEngine的框架来构建这个技术体系,因为底层比较干净,维护常用的LLM,有大量的外部框架实现和附属工具,易于提交代码贡献。

  • 1.2 分布式问题的解法

类Manus的Agent执行是一个耗时很久的长流程。为了避免服务端变更发布导致的大批量中断,其最好具有分布式的可靠性,而且发生在端上的异步流程也要能被串联起来,因此我们需要有流程引擎来支持agent的推进。

流程引擎的选取有一种是用传统BPMN,但是这是新的DSL会带来灵活性问题,更重要的是,行业的开源agent多数基于单机的代码结构,充满基于基于代码函数文本的继承、抽象。要把他们以BPMN DSL方式的复刻无法100%遵循参考方的代码结构,基本需要重写从而可能漏掉不少关键细节。


我们采用了基于Java Monad的DFlow分布式执行库,在ali-langengine-dflow包中实现Manus Agent。这样只需要用Java研发还算熟悉的map, flatMap函数式编程的写法,用一样的面向对象的代码结构重构开源方案。而且dflow本身是支持本地异步运行的,所以即使没有分布式场景不想学习DFlow带来的额外负担,它也可以当一个传统本地Agent框架去使用。

  • 1.3 工程&AI的分离

Agent研发既有大量的工程部分,也有具备AI特点的部分。我们认为工程的部分就应该沿用传统的Java体系。尤其是最近提到的Agent研发中,新提到的上下文工程(Context Engineering)的部分(有确定性)。

而具有AI特点(不确定性)的部分,是最小化的AI四元组-(LLM config,PromptTemplate,InputFormatter, OutputParser)的LLM原子化能力。我们内部基于这部分抽象,研发了Ali-langsmith平台实现构建了Prompt级别的独立开发,发布,在线调试回放,测评能力的完整体系。

以此为边界构建的Java的Agent框架决定了确定性的代码流程,prompt及其连带元素的研发迭代则可以独立调整,Manus体系中不同环节的prompt采用不同水准的大模型非常自然。

同时这套框架也不能耦合Ali-langsmith这样的研发平台。因此ali-langengine-dflow既可以独自运行,也可以基于ali-langsmith完成prompt研发,去迭代业务特定的agent。

  • 1.4 调AI 的主体应该是谁?

我们的答案很简单,最终的目标一定是要距离业务最近的人去调教AI。要么研发变得更懂业务,要么让业务能自己上。最开始和系统和AI Trick更多一些的部分,可以由研发打下基础的初始版本。业务调整了几次之后针对性的改几次基本上可以习得特点。

经过我们一年的实践,Ali-langsmith可以让PD和运营实现prompt迭代预览到直接看的最终产品的效果。虽然它是散的,但仍然即使是agent场景,也可用。令人惊奇的是,PD输入的prompt,对业务的理解明显高过技术同学的,其产生的效果也是有巨大差异,而且他自己能快速的体验迭代,这比中间加一个研发去翻译,效率是无与伦比的。当然,对于复杂Agent场景,由于ContextEngineering的存在,研发仍需告知这方面的细节,调整和教导几次。后续的精细化准确度优化,也更多需要技术进来。会是多方合作的场景。

至于训练数据,模型优化这些深度算法向的东西,可以在业务证明大方向上价值之后,基于平台系统的辅助下积累的线上数据和评估能力,半自动化地蒸馏优化,甚至引入专业算法团队去提升。

  • 1.5工程框架本身如何抽象?

目前为止,认真优化的先进系统很少会使用复杂多层附加概念的框架。因为新的抽象会破坏精细的操作和优化,langchain最初的抽象在这个问题上被人诟病颇深,我们不选用Dify为代表的托管式研发平台也出于此。

Ali-langengine-dflow框架除了AI四元组的原始抽象外,框架的组织也倾向于在平铺直叙地在单Agent文件完成流程编排。除了可以通过继承等方式重写。Copy相关Agent的文件去自定义也是非常简单而值得鼓励的事情。“Exampleas框架”,复杂性放在工具类。是本框架的抽象设计哲学。

实现

以下是我们完整的技术体系架构图,包含了本文讨论外的内容(红框部分是本文Release的重点)但是有助于理解上下文环境。


首先核心框架是在 ali-langengine 开源生态中的一部分。它们本身的实现不会依赖任何在线系统。哪怕 dflow 和 ali-langsmith 无可避免地有众多分布式中间件依赖,也是基于扩展 interface 让部署方实现。


ali-langsmith-starter是它的二方SpringStarter,实现了ali-langengine-langsmith-sdk中的必要服务依赖指向内部应用(Prompt四元组的定义服务和上报服务-相对弱依赖))。

dflow-starter实现了dflow的中间件依赖(基于MetaQ的消息,基于Redis的存储,基于HSF的转发)依赖。外部可以自行替换为其它消息中间件,Redis以及http转发的组合。

Java业务应用。后者中的PluginAgent是本文主要针对的电商域插件Manus产品业务场景。

ali-langengine-dflow则是本次开源发布的包。它干净地只依赖ali-langengine-core和dflow,后两者“比较”干净。(https://github.com/AIDC-AI/Agentic-ADK/)。

  3.1 dflow

DFlow的理想是进一步简化异步流程编排,去中心化的库设计,编排逻辑和节点业务代码在一个地方,不破坏业务逻辑流畅性;思路是in language的 monad 库,新构建了分布式流程范畴的类型,使得业务逻辑串联流畅,无需切换工程范式。类似Flowable会自行完成本地的异步化,DFlow是分布式环境的异步化。

  • 3.1.1 工作原理

首先是用户通过DFlow算子把各种DFlow节点创建和串联起来,表达其业务逻辑。然后通过init创建流程。init的时候,会在本地全局内存中记录流程节点step,这样,在触发实际任务之时,每个step都会通过消息adapter在任意一台机器上执行一个流程节点。流程节点是DFlow节点在具体一个流程中的实例,其key为当前流转链路剩下的DFlow节点名串联在一起。

flatMap会动态创建节点,只有执行到的时候才会初始化内部节点。因此执行到这中间的流程如果发生了重新部署,需要整个系统环境中再有人触发这个节点才能继续。

  3.2 ali-langengine-dflow

<dependency>    <groupId>com.alibaba</groupId>    <artifactId>ali-langengine-dflow</artifactId>    <version>0.1.12</version></dependency>

该包基于DFlow实现了一些Agent。
他们的核心Runnable函数都可以被动态替换。

代码详解:

  • 3.2.1 DFlowConversationAgentExecutor

这是DFlowAgent框架的第一个Case。直接基于ali-langengine-semantickernel-core里的agentSKConversationAgentExecutor重写的。

ali-langengine-semantickernel-core承载了两个使命,一个是当年langchain抽象再没有进行lcel改革之前并不是面向prompt研发的框架,因此以semantickernel为核心,当LangEngine复刻了LCEL理念的Runnable系列后,这个最重要的意义不再存在。二是基于可替换的四元组来构建agent。由于个人去年的主旋律仍然是直接给业务带来实际性结果的Workflow型Agent(此类工作无需Agent级别框架,裸用ali-langengine-langsmith-sdk手工拼流程毫无问题)。没有投入重新以此理念但是摆脱semantickernel的框架。


SKConversationAgentExecutor基于semantickernel的写法中,所有的流程都是基于Flowable异步化的Monad。因此改造成dflow Monad非常简单。改造完之后,由于DFlow可以通过设置成本地运行的模式用,把它当做本地异步框架用也不是不行。这样ali-langengine-dflow就彻底取代了ali-langengine-semantickernel-core在这个体系中的生态位。

然而,ConversationAgentExecutor是基于传统ReAct的Prompt范式,在各大模型都开始内置特殊token支持FunctionCall的今天,它有些落伍了。也有不支持多工具并发的问题。

  • 3.2.2 DFlowToolCallAgent & DFlowPlanningFlow

这俩是复刻OpenManus的关键组件。核心逻辑与OpenManus一致。
整个ali-langengine-dflow的基础架构,是基于OpenManus的复刻来的,DFlowBaseAgent,DFlowReActAgent,DFlowToolCallAgent是基于 python的复刻。

它又和OpenManus基于OpenAI模型有重大的不同。我们仍然是基于raw底层实现的toolcall。不同的是内置版本采用了Qwen2.5的原生格式已达到对齐OpenAI风格的效果。因此其准确度要超越ConversationAgentExecutor。没有使用Message风格的底层。

若要换成其他模型,模版须调整成<|im_start|>function 包裹的能被RawWrapChatModelLLM解析成function call的样子。实测qwen raw模型也能理解这个格式,考虑默认换成这个。当然也可以自己实现的Runnable中,用原生Message风格的API来处理Toolcall的变量。

DFlowToolCallAgent也支持了多Tool的并发调用。同时支持普通的BaseTool和异步的DFlowTool。

DFlowPlanningFlow开发的时候正好赶上了ali-langengine-openmanus复刻,因此是基于Java代码改的,对于PlanningTool这样的快速Tool并没有完全DFlow化。在业务实践中,最终对于子Agent的状态,原有的Finish状态被我们拆成了FINISH和TERMINATE两个状态,这样把内部agent的异常终止和正常终止分开,以决定外部Planning是继续下一个计划还是彻底终止。

  • 3.2.3 DFlowDeepCrawlerAgent

Manus中最重要的Tool是 Browser use Tool,要实现这个能力,RPA+Vision的技术路线会比较通用,对模型的要求也更高。在端上Plugin中用JS实现它是一条路线也有众多的参考,我们也在研究但它不是服务端框架的一部分。另一条路是基于简单概念的Dom爬虫和url分析,使得模型能深度遍历,这条路径操作更短,对模型要求更低。



爬虫工具可以是任何DFlow包装的工具调用(既可以是我们用的发送给端的任务,也可以是简单爬虫)。其中三个AI函数都是可以在ali-langsmith自定义定制。这里要注意的是爬虫拿到的html内容会很多。我们端上工具除了做了基于jina-reader的markdown化,还对所有url做了映射以便缩短content。

  • 3.2.4 AgentTool

它可以把一个DFlowAgent封装成一个DFlowTool。这样可以嵌套使用。特别地,它复制copy了DFlow上下文,避免了与相同代码的上级BaseAgent代码里的上下文污染。

  • 3.2.5 JedisBasedPullingJobDFlowTool

它基于异构client(如前端)拉任务,完成后上报的架构构建。把这种模式封装成一种异步的DFlowTool。

  3.3 ali-langsmith

这部分注重一个清晰的极小边界来做变更单元。在另外的平台迭代,由于本次不在开源范围。不做赘述。要注意的,就是这个LLM config,PromptTemplate,InputFormatter, OutputParser四元组定义了一个LangEngine中的LCEL单元—— Runnable对象。能接受变量参数,返回确定性结构化的一次LLMOutput即可。(可以很简单的自己实现,用其他方式变更)

效果

  4.1 使用示例

简单使用示例代码:

这些代码示例了最简单的不用DFlow分布式能力的用法

DFlow.globalInitForTest(); //DFlow本地化,无需引入DFlow-starter即可本地用。    DashScopeLLM llm = new DashScopeLLM();    llm.setModel("qwen-plus-latest");    llm.setToken("sk-x");     ConversationBufferMemory memory = new ConversationBufferMemory();     DFlowToolCallAgent agent = new DFlowToolCallAgent("manus",memory, getBaseTools(),llm);    //SystemPrompt和NextStepPrompt是OpenManus中的扩展方式,对于基于Ali-langsmith的应用,这个直接在prompt中,这个变量就无用了    agent.setSystemPrompt("你是 一个搭配助手,帮用户搭配穿衣");    agent.setNextStepPrompt("你可以用GoogleSearch 搜索信息. 用dapei工具给用户出搭配建议,用chuan工具真正的穿上\n"        + "\n"        + "GoogleSearch: Perform web information retrieval\n"        + "\n"        + "dapai: 出搭配方案\n chuan:真的穿上"        + "\n"        + "Terminate: End the current interaction when the task is complete or when you need additional information from the user. Use this tool to signal that you've finished addressing the user's request or need clarification before proceeding further.\n"        + "\n"        + "Based on user needs, proactively select the most appropriate tool or combination of tools. For complex tasks, you can break down the problem and use different tools step by step to solve it. After using each tool, clearly explain the execution results and suggest the next steps.\n"        + "\n"        + "Always maintain a helpful, informative tone throughout the interaction. If you encounter any limitations or need more details, clearly communicate this to the user before terminating.\n"    );    agent.setFunctionChoose(agent.genQwen25Function(llm));     DFlowPlanningFlow planningFlow = new DFlowPlanningFlow("manus", agent);    planningFlow.setFinalizePlanFunction(planningFlow.genQwen25FinalizeFunction(llm));    planningFlow.setInitPlanFunction(planningFlow.genQwen25InitFunction(llm));    // 运行时样例代码    DFlow.directRun(DFlow.InitParam.of("春天我应该怎么穿?"),(y)->DFlow.just(y)        .flatMap(x -> {            System.out.println("====" + x);            return planningFlow.execute(x.getParam());        }).id("a1")        .flatMap(x -> {            System.out.println("====" + x);            return planningFlow.execute("休闲一点,在室内");        }).id("a2")        .map(x -> {            System.out.println("====" + x);            return x;        }).id("a3"));    //等    System.in.read();


正经基于Ali-LangSmith替换函数后的线上用法

bean初始化函数,准备好memorytoolAI函数

@PostConstructprivate void init()    memory = new JedisMemory(jedisPool);    crawlerJobDFlowTool = new BrowserCrawlerTool(jedisPool, runnableService, memory, this::log);     humanaskJobDFlowTool = new JedisBasedPullingJobDFlowTool(jedisPool);    humanaskJobDFlowTool.setName("askhuman_func_b2ac");    StructuredSchema schema = new StructuredSchema();    ArrayList<StructuredParameter> params = new ArrayList<>();    StructuredParameter p = new StructuredParameter();    p.setName("choices");    p.setDescription("给用户的选项,以json列表形式['option1','option2']给出,不要超过3个");    params.add(p);    schema.setParameters(params);    humanaskJobDFlowTool.setStructuredSchema(schema);    humanaskJobDFlowTool.setHumanName("询问用户的工具,实在无法确定时,可以询问用户从选项中选择");    humanaskJobDFlowTool.setDescription("询问用户的工具,实在无法确定时,可以询问用户从选项中选择");     agent = new DFlowToolCallAgent("main", memory, Arrays.asList(crawlerJobDFlowTool, humanaskJobDFlowTool));    agent.setLogger(this::log);     agent.setFunctionChoose(() -> runnableService.getRunnable("ai_content", "ManusAgent"));     dFlowPlanningFlow = new DFlowPlanningFlow("main", agent, memory);    dFlowPlanningFlow.setLogger(this::log);     dFlowPlanningFlow.setFinalizePlanFunction(        () -> runnableService.getRunnable("ai_content", "ManusPlanningFinal"));    dFlowPlanningFlow.setInitPlanFunction(        () -> runnableService.getRunnable("ai_content", "ManusPlanningInit"));}

定义AI函数的ali-langsmith后台

运行时函数

public String chat(Long userId, String query) throws Exception {    String sessionId = getSessionId(userId);    String traceId = sessionId + "-" + System.currentTimeMillis();    clear(sessionId);     String realinput = query;    InitParam param = new InitParam(realinput, sessionId);     DFlow.directRun(DFlow.InitParam.of(JSON.toJSONString(param), traceId), (y) -> {        InitParam p = JSON.parseObject(y.getParam(), InitParam.class);        return dFlowPlanningFlow.execute(p.getQuery(), p.getSessionId()).map((contextStack, x) -> {            String sid = contextStack.get(SESSIONID);            log.info("chat {} result:{}", sid, contextStack.getId());            log(contextStack.get(SESSIONID), "Terminate");            return "";        }).id("setbackmaindata2");    });     return traceId;}

具体BrowserTool的实现,用AgentToolDFlowDeepCrawlerAgent封装成一个Tool,内核的爬虫工具基于JedisBasedPullingJobDFlow,由前端拉取任务完成后,回报结果得到。

public class BrowserCrawlerTool extends JedisBasedPullingJobDFlowTool {    private RunnableService runnableService;     private AgentTool agentTool;     public BrowserCrawlerTool(JedisPool jedisPool, RunnableService runnableService, BaseChatMemory memory,        BiConsumer<String,String> logger) {        super(jedisPool);        DFlowDeepCrawlerAgent agent = new DFlowDeepCrawlerAgent("crawler_func_a32a", memory);         agent.setLogger(logger);         agent.setSummaryFunction(() -> {            return runnableService.getRunnable("ai_content", "ManusDeepCrawlerSummary");        });        agent.setValidationFunction(() -> {            return runnableService.getRunnable("ai_content", "ManusHtmlValidation");        });        agent.setResearchFunction(() -> {            return runnableService.getRunnable("ai_content", "ManusHtmlResearch");        });         agent.setDflowCrawler((ctx, url) -> {            return super.dflowRun(ctx, url)                .map(x -> x.getOutput()).id("crawler_func_a32a_output");        });        agentTool = new AgentTool(agent);         this.runnableService = runnableService;         this.setName("crawler_func_a32a");        StructuredSchema schema = new StructuredSchema();        List<StructuredParameter> params = new ArrayList<>();        StructuredParameter p = new StructuredParameter();        p.setName("url");        p.setDescription("待爬取网页的url");        params.add(p);        p = new StructuredParameter();        p.setName("prompt");        p.setDescription("爬取这个网页要做的任务和目标");        params.add(p);        schema.setParameters(params);        this.setStructuredSchema(schema);        this.setHumanName("爬虫工具,可以爬取网页内容");        this.setDescription("爬虫工具,可以爬取网页内容");    }     public DFlow<ToolExecuteResult> dflowRun(ContextStack ctx, String toolInput) throws     DFlowConstructionException {         DFlowCtx c = JSON.parseObject(ctx.get(DFlowBaseAgent.DFLOW_CTX), DFlowCtx.class);        String topic = Optional.ofNullable(c.getInputs()).flatMap(                i -> Optional.ofNullable(i.get("input")).map(Object::toString))            .orElse("");        return agentTool.dflowRun(ctx, toolInput);       } }

  4.2 以类ManusAgent为例串起来的完整架构

我们的Agent的载体是PC淘宝浏览器插件,理念是模拟专业的买手帮用户真实的浏览网页,收集信息,真实的用户访问使得没有登陆屏蔽信息孤岛问题,过程的可见使得用户不必怀疑夹带私货有损可信性。

插件端从chat任务开始,然后轮询llmPull获取中间的返回结果。chat函数代码事实上还有一个前置流控,需求分类和改写,以确保只执行导购类任务。

与此同时有一个toolPull拉取当前的Tool任务,完成则使用ToolReport回报。然后流程逻辑在宿主Java应用内执行,碰到AI函数则基于ali-langengine-langsmith-sdk执行,其中prompt定义缓存弱依赖地去ali-langsmith部署应用去取。llm调用通过本地ali-langengine-llm的包去访问dashscope或者idealab。

未来发展

  5.1 基础能力的完善

DFlow本身原地发布变更问题的解法(老代码会在最后一批发布消失使得中间任务无法执行完毕,需自行控制;发布后需有一个请求预热),以及处理闭包的解法(不支持跨机器的闭包变量,自己确定没问题的类需实现ValidClosure标记),至今仍有瑕疵。另外外部可用的starter并没有好好提供。目前需自行实现interface。

  5.2 发挥灵活架构优势

在端云协作方面,插件端可以转发服务端很多DFlow的调用,一些标准的比如转发到用户侧可访问MCP服务,或是用户的本地大模型调用。还有局部DFlow工作流的端云切换也是简单而很有趣的方案(DFlow一部分节点里一个if else即可),并且能解决浏览隐私和成本问题。

  5.3 畅想未来

在AI工程架构设计中,一个核心命题始终存在:工程边界究竟应如何划定?是选择以ali-langsmith的工程自由主义路径——赋予系统最大限度的灵活性,还是采用OpenAI的模型产品覆盖模式——仅通过模型扩展手脚接入能力即可完成一切?这一问题的答案,正在随着AI工程范式的演进而不断被重新定义。

过去一年间,我们见证了AI工程范式的螺旋式迭代:从Cot(Chain-of-Thought)到Reasoning模型内化,从Agent架构到Deep Research的模型即产品,每一次突破均遵循相似的路径——工程创新先行:通过设计新颖的prompt或架构,率先实现功能验证,再利用合成数据反哺模型训练,最终将工程智慧沉淀为模型能力。这种“工程驱动-数据强化-模型内化”的闭环,构成了AI应用发展的核心逻辑。

现在,LLM的快思考特性决定了Agent应用最好控制好它需处理的信息空间:通过约束每次调用的上下文粒度,让模型聚焦特定抽象层级的推理任务,可显著提升输出质量与稳定性。这一思想恰如人类认知的分层分治机制。而随着模型能力的迭代,这些工程逻辑可能再次模型内化。

然而,工程框架的先验价值始终不可替代。即使模型逐步覆盖了原本由工程实现的逻辑,工程实现的可靠性仍是更加重要:在生产环境中,高度可控的代码流程比完全依赖真人AGI直接执行更具确定性和效率。最小化执行的发散性,始终是严肃应用产品追求的核心目标。

因此,在更多是确定性的需求世界中,更多的工程,必然会占据更为重要的Runtime世界。当然,AGI会再次学会这些工程。但是没有足够的在这一抽象层的代码和数据,它从何学起呢?

团队介绍

本文作者虬枝,来自淘天集团-网站技术团队。本团队专注于PC端电商平台的技术创新与产品升级。我们负责淘宝网PC主站核心功能研发以及PC上其它创新形态的发展。为消费者打造更流畅、更智能的大屏购物体验,助力有特色的企业用户高效采购。团队积极探索AI驱动的PC端创新产品形态,让技术服务于用户体验与商业价值的提升。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法


网站公告

今日签到

点亮在社区的每一天
去签到