ReActAgent 源码分析

概述 ReActAgent 是 AgentScope Java 的核心推理引擎,位于 agentscope-core 模块。它实现了 ReAct (Reasoning and Acting) 设计模式——在推理(想下一步做什么)和执行(调工具)之间交替循环。 源码位置:agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java(1925 行) 核心依赖 public class ReActAgent extends StructuredOutputCapableAgent { private final Memory memory; // 对话历史(InMemoryMemory) private final String sysPrompt; // 系统提示词 private final Model model; // LLM 模型 private final int maxIters; // 最大迭代次数(默认 10) private final Toolkit toolkit; // 可用工具集 private final PlanNotebook planNotebook; // 计划本 private final StatePersistence statePersistence; // 状态持久化策略 } 核心循环:reason → act → reason → act → … 整个 ReAct 循环的入口: private Mono<Msg> executeIteration(int iter) { return reasoning(iter, false); // 先推理 } reasoning 阶段(第 560-650 行) ① 检查 maxIters → 超限则走 summarizing() ② notifyPreReasoningEvent() ← Hook 注入点:CompactionHook、SubagentsHook、WorkspaceContextHook ③ model.stream(messages + tool schemas) ← 调 LLM ④ 逐 chunk 处理 + notifyReasoningChunk() ← 流式输出 ⑤ notifyPostReasoning() ← Hook 注入点 ⑥ 判断: - stopRequested → 返回(HITL 打断) - gotoReasoning → 继续推理(结构化输出重试) - isFinished(msg) → 没有 tool call → 返回结果 - 有 tool call → going to acting(iter) 关键判断 isFinished(第 969 行): ...

May 22, 2026 · 3 min · 531 words · WY

把你的Java后端经验重新编译一次:当Netty/Epoll/JVM映射到AI Infra

翻译:你的Java后端经验,就是AI Infra最好的预习材料。 一、你先要理解一件事 AI Infra 不是什么全新的知识体系。它是一个同构系统,只是运行在 GPU 上。 你的舒适区 AI Infra 的真相 ─────────────────────────────────────────────────────── Netty EventLoop 接收请求 SM(Streaming Multiprocessor)接收 Warp ByteBuf 池化管理内存 PagedAttention 分块管理显存 epoll 事件驱动 IO CUDA Stream 异步执行 JVM 的 GC 分代回收 KV Cache 的 Block Eviction Kafka 的分区与批量 NCCL 的 Ring AllReduce Tomcat 的连接池 Triton Server 的 Request Queue Spring 的 AOP 代理 CUDA Graph 的 Kernel 捕获 CompletableFuture 异步编排 CUDA Stream 的依赖管理 JMX / Metrics 监控 Nsight Systems 性能分析 一模一样的问题,一模一样的抽象层次,区别只在 Scale 和物理介质。 ...

May 22, 2026 · 11 min · 2328 words · WY

系列01:内存管理——当你在管理 ByteBuf 时,你已经在管理 KV Cache 了

这篇讲什么 这是个系列的第一篇。我选"内存管理"作为开篇,因为: 你最熟——Netty ByteBuf、JVM 堆、堆外内存,你每天都在打交道 映射最直接——ByteBuf 池化 = KV Cache Block 管理 = GPU 显存池 代码最少——核心逻辑 100 行就能说清原理 读完这篇,你会理解: ByteBuf 的引用计数为什么等于 vLLM Block 的 refcount JVM 的 G1 Region 为什么等于 PagedAttention 的 Block GPU 显存池为什么是你的 PooledByteBufAllocator 在另一个地址空间的翻版 并且,你可以亲手写一个"Java 版显存管理器" 一、问题本质 内存管理要解决的只有三个问题: 1. 分配——如何快速找到一块可用的内存 2. 回收——用完后如何归还,确保不漏 3. 碎片——如何避免小块空隙越积越多 你在 Java 后端看到过这些方案的演进: JVM 堆管理: Serial GC → 标记-整理(压缩碎片) CMS → 标记-清除(不压缩,碎片多) G1 → Region 分区(固定大小,消除碎片) ZGC → 染色指针 + 读屏障(几乎无停顿) Netty 堆外内存管理: UnpooledByteBufAllocator → 每次分配新 DirectBuffer PooledByteBufAllocator → 预分配 Arena + Chunk + Page 分三级管理 AI Infra 的显存管理(正在发生和你一模一样的事情): PyTorch 默认(CUDACachingAllocator)→ 有缓存但碎片严重 vLLM PagedAttention → Region 分块(和 G1 一样的思路) 这不是类比,这是同一个问题在不同约束下的工程演化。 ...

May 22, 2026 · 8 min · 1693 words · WY

AgentScope Java Harness 架构与实战指南

前言 AgentScope Java 的 Harness 模块是一个面向生产环境的多智能体运行时框架。本文基于官方文档,从其设计理念到具体代码实践,完整梳理 Harness 的核心架构与使用方式。 一、Harness 是什么 agentscope-harness 在 agentscope-core 的 ReActAgent 之上,通过 Hook 和 Toolkit 两个扩展点,装配出一套面向长期稳定运行的工程化基础设施。它的入口只有一个类:HarnessAgent。 裸的 ReActAgent 只有"请求 — 推理 — 工具 — 回复"一轮循环。而 Harness 要回答的是一组更贴近生产的问题: 下一轮怎么办? 下一天怎么办? 上下文爆了怎么办? 状态丢了怎么办? 任务太重怎么办? 它不替换推理循环,而是在循环的关键时机插入 hook、为模型补上一组基础工具,把这些问题的默认工程答案打包好。 快速开始 <dependency> <groupId>io.agentscope</groupId> <artifactId>agentscope-harness</artifactId> <version>${agentscope.version}</version> </dependency> public class QuickstartExample { public static void main(String[] args) throws Exception { // 1. 准备工作区 Path workspace = Paths.get(".agentscope/workspace"); initWorkspaceIfAbsent(workspace); // 2. 构建模型 Model model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-max") .stream(true) .build(); // 3. 构建 HarnessAgent HarnessAgent agent = HarnessAgent.builder() .name("quickstart-agent") .sysPrompt("你是一个帮助用户做笔记的助手。") .model(model) .workspace(workspace) .compaction(CompactionConfig.builder() .triggerMessages(30) .keepMessages(10) .flushBeforeCompact(true) .build()) .build(); // 4. 两轮对话,同一 sessionId RuntimeContext ctx = RuntimeContext.builder() .sessionId("demo-session") .userId("alice") .build(); Msg turn1 = agent.call( Msg.builder().role(MsgRole.USER) .textContent("我叫天宇,今天准备做一个关于 ReAct 的技术分享。") .build(), ctx).block(); Msg turn2 = agent.call( Msg.builder().role(MsgRole.USER) .textContent("我叫什么?我今天要干什么?") .build(), ctx).block(); } private static void initWorkspaceIfAbsent(Path workspace) throws Exception { Files.createDirectories(workspace); Path agentsMd = workspace.resolve("AGENTS.md"); if (Files.exists(agentsMd)) return; Files.writeString(agentsMd, """ # 笔记助手 你是一个帮助用户整理笔记和知识的助手。 ## 行为约定 - 主动记录用户提到的关键事实(姓名、计划、偏好等) - 回答用简洁中文,必要时给出要点列表 - 对不确定的内容要主动说明,不要臆造 """); } } 上述代码展示了 Harness 的三个核心价值:工作区驱动的人格、会话持久化(同一 sessionId 的第二轮对话记得第一轮的内容)、显式启用对话压缩。 ...

May 20, 2026 · 13 min · 2670 words · WY

CompletableFuture 到底该怎么用:从方法、技巧、踩坑到源码

CompletableFuture 到底该怎么用:从方法、技巧、踩坑到源码 很多人第一次接触 CompletableFuture,感觉它很优雅:链式调用、异步编排、异常处理、多个任务聚合,看起来比手写线程池高级得多。 但真到项目里,问题往往不是“不会用”,而是“以为自己会用”。 比如: 为什么有的回调在主线程执行,有的又跑到了线程池里? 为什么 thenApply() 和 thenApplyAsync() 只差一个 Async,行为却可能完全不同? 为什么我明明用了异步,结果接口还是卡住了? 为什么 allOf() 执行完了,结果还得自己一个个 join() 去拿? 为什么异常被包成了 CompletionException,日志看起来特别恶心? 为什么线上机器 CPU 很高,最后发现是把阻塞 IO 全扔进了 ForkJoinPool.commonPool()? 这篇文章不打算只列 API,而是把 CompletableFuture 真正在工程里怎么用、怎么避坑、JDK 为什么这么设计,以及核心源码怎么运作,一次讲透。 一、先建立一个正确心智模型 很多人把 CompletableFuture 理解成“异步线程工具”,这个说法不算错,但太浅了。 更准确一点,它其实同时做了两件事: 它是一个 Future,代表“未来某个时刻会完成的结果”。 它是一个 CompletionStage,代表“这个结果完成之后,还能继续挂后续动作的可编排节点”。 也就是说,CompletableFuture 不只是“拿结果”,更重要的是“描述依赖关系”。 你可以把它想成一个节点: 节点里最终会有一个结果,或者一个异常。 节点完成后,会触发依赖它的后续节点。 节点和节点之间可以串行、并行、汇聚、竞争。 这才是它和传统 Future 最大的区别。 传统 Future 的典型写法是: Future<Order> future = executor.submit(this::queryOrder); Order order = future.get(); 问题在于: get() 是阻塞的。 不能优雅地声明“拿到订单后再查用户”。 多个 Future 的组合很难写。 而 CompletableFuture 的核心价值,就是把“等待结果”和“结果完成后的动作”拆开。 二、最常用的几类 API,到底该怎么分 1. 创建任务 最常见的是这两个: ...

April 26, 2026 · 5 min · 990 words · WY

彻底搞懂 Monad:从概念到 Java 实战

彻底搞懂 Monad:从概念到 Java 实战 你可能每天都在用 Optional.flatMap、Stream.flatMap、CompletableFuture.thenCompose,却不知道它们背后的统一模型——Monad。本文带你一次性搞懂 Monad 是什么、为什么需要它、在 Java 中如何应用,以及如何自己设计 Monad。 1. 从问题出发:为什么需要 Monad? 先看一段典型的代码: User user = findUserById(id); if (user != null) { Address addr = user.getAddress(); if (addr != null) { String city = addr.getCity(); return city; } } return "Unknown"; 这段代码里有 两个判空,如果还有更多步骤,if 会层层嵌套,难以阅读和维护。 这种 “带有空值可能性的计算” 非常常见,每次我们都要手动检查,写一堆样板代码。 类似的场景还有: 异步计算:拿到 Future 后必须等它完成才能继续,还要处理异常。 多值计算:处理集合时,每个元素返回一个集合,然后要手动 addAll 合并。 可失败计算:方法可能抛出异常,每一步都要 try-catch。 Monad 的使命就是 把这些重复的控制逻辑抽离出来,让你只关心“下一步做什么”,而不是“上一步是否成功/有值/已完成”。 2. Monad 是什么? Monad 是一种设计模式,它定义了一个容器类型(比如 Optional<T>),并提供两个操作: unit(也叫 return、of、pure):把一个普通值包装进 Monad。 flatMap(也叫 bind、>>=、thenCompose):把容器里的值取出来,应用一个函数(这个函数返回同类型容器),然后自动 展平 结果,避免嵌套。 用公式表达(Haskell 风格): ...

April 26, 2026 · 4 min · 662 words · WY

构建生产级 Agent Memory 系统架构

构建生产级 Agent Memory 系统架构 很多人在做 Agent 时,第一版 Memory 的实现大概长这样: memory = [] memory.append({"role": "user", "content": "你好"}) memory.append({"role": "assistant", "content": "你好!有什么可以帮你的?"}) import java.util.*; List<Map<String, String>> memory = new ArrayList<>(); memory.add(Map.of("role", "user", "content", "你好")); memory.add(Map.of("role", "assistant", "content", "你好!有什么可以帮你的?")); 跑几天后发现问题了: 对话一长,Token 消耗爆炸 用户上次说过的偏好,下次完全不记得 多个 Session 之间的信息没法共享 想查某个历史事实,得翻完整条对话 模型在长上下文里注意力稀释,反而答得更差 于是开始往里面塞向量数据库、塞总结、塞 Graph,结果又发现:Memory 系统不是越复杂越好,而是越适合你 Agent 的行为模式越好。 这篇文章不讲泛泛的概念,而是从工程角度拆解:一个生产级的 Agent Memory 系统到底该考虑哪些维度,每种方案解决什么问题、带来什么新问题,以及如何组合它们。 目录 一、Agent Memory 到底是什么 二、整体架构:三层分离 三、Episodic Memory(情节记忆):最基础也最容易搞砸的部分 问题本质 常见实现方式对比 推荐方案:分层管理 工程实现要点 四、Semantic Memory(语义记忆):从经历中提炼事实 核心思路 提取什么 存储选型 提取策略:被动 vs 主动 关键难点:事实的更新与冲突 五、Procedural Memory(程序记忆):Agent 怎么学会"做事情的方式" 实现方式 何时写入 六、Context Builder:决定往 Prompt 里塞什么 组装策略 Token 预算分配 检索触发策略 七、数据生命周期与维护 TTL 与过期 Consolidation(合并) Importance Scoring(重要性评分) 八、从 Claude Code 中看到的优秀设计 文件即数据库 类型化记忆 检索时的验证机制 不混淆"记忆"和"任务状态" 九、主流框架的 Memory 实现案例 LangChain:模块化 Memory 体系 CrewAI:基于角色的记忆分工 Mem0:专注"记忆层"的独立框架 Spring AI:Java 生态的 Memory 实现 AgentScope4J:observe/call 分离与 MsgHub 记忆共享 Hermes Agent:四层记忆与可插拔 Provider OpenClaw:文件即记忆的"梦境"系统 AutoGen:对话驱动的记忆共享 框架对比小结:Hermes vs OpenClaw 十、生产级 Checklist 十一、总结 一、Agent Memory 到底是什么 很多人把 Memory 等同于"聊天记录",这是第一个坑。 ...

April 26, 2026 · 16 min · 3319 words · WY

AgentScope4J 实战:MCP、Function Calling、Hermes、Skills 全链路落地

AgentScope4J 实战:MCP、Function Calling、Hermes、Skills 全链路落地 很多团队在做 AI Agent 时,会把 MCP、Function Calling、Workflow、Skills 混在一起,最后导致系统边界不清晰: 模型在“决策层”做了太多“执行层”的事 工具能力散落在不同模块,难治理、难审计 Demo 阶段能跑,线上阶段一旦扩展就失控 这篇文章给出一个工程化拆解:基于 agentscope4j 搭建一套清晰的分层,把这四个关键能力拼成一条可扩展、可观测、可治理的链路。 一、先把概念边界讲清楚 很多讨论无效,根源是“词对了,语义不对”。下面先做边界划分。 概念 核心职责 解决的问题 不负责的事情 Function Calling 让模型按 JSON Schema 选择并调用本地函数 结构化参数、减少自由文本歧义 跨进程发现工具、权限治理 MCP 标准化“模型如何发现并调用远端工具” 工具注册、工具描述、跨系统接入 业务编排策略 Hermes(编排层) 统一调度策略与执行控制 路由、重试、超时、回退、审批 工具协议本身 Skills 可复用能力单元(提示词+工具+策略) 把“能力”沉淀为可组合模块 具体传输协议 一句话总结: Function Calling 是“调用形态”,MCP 是“工具接入协议”,Hermes 是“执行与治理中枢”,Skills 是“业务能力封装”。 二、目标架构:四层分离 flowchart TD U["User Request"] --> A["AgentScope4J Agent (Planner)"] A --> H["Hermes Orchestrator"] H --> S1["Skill: Research"] H --> S2["Skill: Report"] S1 --> FC["Function Calling Adapter"] S1 --> MCP["MCP Client Adapter"] FC --> T1["Local Tool: SqlTool/FileTool"] MCP --> T2["Remote MCP Tools"] S2 --> T3["Formatter/Exporter"] T1 --> O["Observability: Trace/Log/Metrics"] T2 --> O T3 --> O 设计原则: ...

April 22, 2026 · 6 min · 1102 words · WY

JDK线程、虚拟线程、Go协程与Netty Event Loop:调度模型和阻塞队列全景解析

目录 目录 引子:这几个东西为什么总被拿来一起比较 先统一一个视角:它们本质上都在解决调度问题 JDK 平台线程:1:1 映射内核线程 JDK 虚拟线程:M:N 调度,但语义仍然是 Thread Go 协程:语言运行时主导的 GMP 调度模型 Netty Event Loop:少量线程驱动大量连接 把四者放在一张图里看 它们之间到底是什么关系 一个关键问题:阻塞到底意味着什么 JDK 阻塞队列全景梳理 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue PriorityBlockingQueue DelayQueue LinkedTransferQueue LinkedBlockingDeque 阻塞队列之间的核心区别 这些队列和线程模型怎么配合 怎么选:按场景给结论 最后总结 引子:这几个东西为什么总被拿来一起比较 很多人第一次接触这几个概念时,会感觉它们像是同一层面的东西: JDK 线程 JDK 虚拟线程 Go 协程 Netty Event Loop JDK 阻塞队列 但它们其实分布在不同层次: 线程 / 协程 / 虚拟线程 解决的是执行单元怎么表示 调度器 解决的是谁来跑、什么时候跑 Event Loop 解决的是I/O 事件如何复用少量线程 阻塞队列 解决的是任务或数据如何在并发单元之间传递 把这些层次混在一起,就很容易得出错误结论,比如: “虚拟线程就是 Java 版协程” “Netty loop 就是协程调度器” “用了虚拟线程,就不需要线程池和队列了” “所有生产者消费者问题都用 LinkedBlockingQueue 就行” 这篇文章的目标,就是把这些东西放到一张统一地图里。 ...

April 22, 2026 · 5 min · 905 words · WY

AgentScope MsgHub 多智能体通信机制详解

多智能体系统里,Agent 之间的通信是个容易被低估的难题。本文以 AgentScope-Java 的 MsgHub 为例,聊聊我们在实践中踩过的坑,以及订阅-广播机制的设计思路。 一、为什么要专门做个通信层 1.1 常见但别扭的做法 三个狼人 Agent 讨论今晚刀谁,你会怎么写? 手动透传消息 // 没有 MsgHub 的情况 Msg aResponse = wolfA.call().block(); wolfB.observe(aResponse).block(); wolfC.observe(aResponse).block(); Msg bResponse = wolfB.call().block(); wolfA.observe(bResponse).block(); wolfC.observe(bResponse).block(); // 代码重复,维护噩梦 并发调用 // 同时调用所有 Agent var responses = Flux.just(wolfA, wolfB, wolfC) .flatMap(agent -> agent.call()) .collectList() .block(); // 问题:每个 Agent 基于相同的初始上下文,无法互相影响 同步阻塞(死锁风险) // 如果每个 Agent 都在等其他人先发言 Msg aResponse = wolfA.call().block(); // A 等 B Msg bResponse = wolfB.call().block(); // B 等 C Msg cResponse = wolfC.call().block(); // C 等 A // 死锁 这些方案要么啰嗦,要么有隐患。我们需要一种让 Agent 既能"听到"彼此,又不会互相卡住的机制。 ...

April 15, 2026 · 4 min · 709 words · WY