AgentX 架构设计全解析:一个请求是如何从 HTTP 走到 LLM 再回来的|AgentX 专栏③
本文是 AgentX 技术专栏第三篇。基于真实项目源码(AgentController / AgentService / AgentWorkflow / ToolRegistry / RedisMemoryStore),逐层拆解 AgentX 的六层架构设计,以及每个关键决策背后的取舍与踩坑实录。
本文速览:
- AgentX 六层架构全景,每层解决什么问题?
- 一次用户请求的完整生命周期:HTTP → LLM → SSE 流式返回,每步耗时
general模式 vsworkflow模式:两条执行路径的差异- SSE 流式推送 + 心跳线程:前端 30 秒不超时的实现
- ToolRegistry 自动扫描原理,以及 AopProxy 解包这个大坑
- TraceId 跨线程丢失的根本原因与正确解法
- 技术栈版本全表、安全设计与扩展点
一、架构设计的核心挑战
做 Agent 系统的架构设计,和普通 Web 应用有一个本质差异:
普通 Web 请求:用户 → 查数据库 → 返回,整个过程 < 500ms,全程可预期。
Agent 请求 :用户 → LLM 思考(5--15s)→ 决定调用工具 → 执行工具 → LLM 再思考(5--15s)→ 返回,整个过程 10--60s,不可预期,中途可能循环多次。
这个差异直接决定了 AgentX 的架构选择:
| 问题 | 对应方案 |
|---|---|
| 线程被长时间占用 | 虚拟线程 + 异步提交 |
| 用户等 30s 不知道发生什么 | SSE 流式推送 + 心跳线程 |
| 某次推理失控 | CompletableFuture + orTimeout 超时熔断 |
| 多轮对话 AI 每次"金鱼脑" | Redis 短期记忆 + Milvus 长期语义记忆 |
| 分布式追踪失真 | 跨线程 Span 手动捕获传递 |
带着这五个约束,AgentX 的六层架构自然生长出来了。
二、六层架构总览
┌──────────────────────────────────────────────────────────┐
│ ① 客户端层 (Client) │
│ Web / Mobile / CLI / 第三方 API 调用 │
├──────────────────────────────────────────────────────────┤
│ ② 接入层 (API Gateway) │
│ AgentController · KnowledgeController · ToolController │
│ WorkflowController · JWT 认证 · 请求限流 │
├──────────────────────────────────────────────────────────┤
│ ③ 调度层 (Orchestration) │
│ AgentService │
│ 超时熔断 · 路由分发 · TraceId 捕获 · 虚拟线程提交 │
├──────────────────────────────────────────────────────────┤
│ ④ 执行层 (Execution Engine) │
│ AgentWorkflow · WorkflowExecutor · LangGraphOrchestrator │
│ 模式路由 · DCL 实例缓存 · SSE 推流 · 心跳线程 │
├──────────────────────────────────────────────────────────┤
│ ⑤ 能力层 (Capability) │
│ ToolRegistry · Built-in Tools · MCP Tools │
│ @Tool 自动扫描 · AopProxy 解包 · Observation 埋点 │
│ RedisMemoryStore · MilvusEmbeddingStore │
├──────────────────────────────────────────────────────────┤
│ ⑥ 基础设施层 (Infrastructure) │
│ Ollama (CPU) · DeepSeek API · Redis · Milvus │
│ PostgreSQL (元数据) · Jaeger · Prometheus · Docker │
└──────────────────────────────────────────────────────────┘
六层各司其职,每一层只和相邻层交互,不跨层调用。这让每个模块可以独立测试和替换------比如把 Ollama 换成其他本地模型,只需改第六层配置,上面五层不受影响。
下面是系统完整架构图:

图中从上到下展示了七个功能区域:客户端层、接入层、调度层、执行层、能力层(工具+记忆)、基础设施层,以及右侧三节点部署拓扑(A 节点 4C8G / B 节点 2C4G / C 节点 2C4G)。
三、一个请求的完整生命周期
以一个典型流式对话请求为例,追踪它从 HTTP 进来到 SSE 流出去的完整路径:
http
POST /api/v1/agents/process-stream
Content-Type: application/json
{
"message": "帮我查一下上海今天的天气",
"agentType": "general",
"conversationId": "ax-user-123"
}
完整执行链路(耗时参考):
[AgentController] < 1ms
├─ 分配 convId / 建立 SseEmitter
├─ 提交到虚拟线程池
└─ 立即返回 SseEmitter 给前端 ←─ HTTP 线程到此结束
[AgentService.processStream] (虚拟线程中) < 2ms
├─ 捕获当前 HTTP Span(跨线程追踪关键)
└─ 路由到 AgentWorkflow.executeSimpleWorkflowStream
[AgentWorkflow]
├─ 推送 [STATUS] "正在思考,请稍候..." → 前端 < 1ms
├─ 启动心跳虚拟线程(每 5s 推进度)
├─ 调用 LangChain4j StreamingChatModel
│
├─ LLM 推理中 ──────────────────── 5--10s ⬅ 最长等待
│ ├─ 模型决定调用 WeatherTool
│ └─ 推送 [STATUS] "正在调用工具: getWeather"
│
├─ WeatherTool.getWeather("上海") 执行 ──── ~0.5s
│
├─ LLM 整合结果再推理 ───────────── 3--5s
│ └─ onPartialResponse → 每个 token → [CONTENT] → 前端
│
└─ onCompleteResponse → 推送 [DONE] → 关闭 SseEmitter
总耗时:8--15s(CPU 推理),前端全程看到实时进度
四、第一层:接入层详解
AgentController 只做三件事:协议转换、会话 ID 分配、SSE 通道建立,业务逻辑一概不碰。
java
@PostMapping(value = "/process-stream", produces = "text/event-stream;charset=UTF-8")
public SseEmitter processStream(@RequestBody @Valid AgentRequest request) {
// 1. 分配会话ID(有就用旧的,没有就生成新的)
String convId = StringUtils.hasText(request.conversationId())
? request.conversationId()
: "ax-s-" + UUID.randomUUID().toString().substring(0, 8);
// 2. 建立 SSE 通道,超时时间 = AI超时 + 60s 缓冲
int aiTimeout = ollamaProps.chat().timeoutSeconds() != null
? ollamaProps.chat().timeoutSeconds() : 600;
SseEmitter emitter = new SseEmitter((aiTimeout + 60) * 1000L);
// 3. 提交到虚拟线程池,立即返回通道对象
taskExecutor.submit(() -> {
AgentThreadContext.set(AgentContext.current(convId));
try {
agentService.processStream(request, convId, emitter);
} finally {
AgentThreadContext.clear(); // 必须清理 ThreadLocal,防止内存泄漏
}
});
return emitter; // ← Controller 立即返回,不等 AI 推理完成
}
关键设计 :Controller 在提交任务后立即返回 SseEmitter,不阻塞 HTTP 线程。SSE 通道由后台虚拟线程持续写入数据,直到 emitter.complete() 被调用。
接入层还包含 KnowledgeController(文档上传 / RAG 查询)、ToolController(工具列表 / 手动执行)、WorkflowController(工作流 CRUD)------它们共享同一套 JWT 认证中间件。
五、第二层:调度层详解
AgentService 是整个系统的"大堂经理",核心职责是 超时熔断 + 链路追踪 + 路由分发。
同步模式的超时熔断
CPU 推理延迟无法预测,必须有兜底机制:
java
public AgentResponse process(AgentRequest request) {
return Observation.createNotStarted("agent_process_sync", observationRegistry)
.contextualName("agent-chat:" + agentType)
.observe(() -> {
// 在虚拟线程中执行推理,带超时熔断
CompletableFuture<String> task = CompletableFuture.supplyAsync(
() -> executeInternal(agentType, convId, request.message()),
virtualExecutor // ⚠️ 必须是虚拟线程池,不能用默认 ForkJoinPool
);
try {
int timeout = ollamaProps.chat().timeoutSeconds() != null
? ollamaProps.chat().timeoutSeconds() : 600;
responseText = task.orTimeout(timeout, TimeUnit.SECONDS).join();
} catch (Exception e) {
return handleProcessError(e, convId, agentType, startTime, currentCtx);
}
});
}
踩坑 1 :
CompletableFuture.supplyAsync()默认使用ForkJoinPool(平台线程)。CPU 推理期间,ForkJoinPool线程被长时间占用,导致其他任务排队。必须显式传入虚拟线程池 :Executors.newVirtualThreadPerTaskExecutor()。
跨线程的 TraceId 捕获
流式模式下,SSE 回调在不同线程执行,Span 绑定在 ThreadLocal,跨线程后 tracer.currentSpan() 返回 null:
java
public void processStream(AgentRequest request, String convId, SseEmitter emitter) {
// ⚠️ 必须在当前虚拟线程上捕获 Span 引用,而不是在 SSE 回调里调用
// 原因:onPartialResponse / onToolExecuted 等回调在 ForkJoinPool 线程触发
// 彼处 tracer.currentSpan() 返回 null(ThreadLocal 不跨线程)
// 直接持有 Span 对象引用,跨线程写 tag 是线程安全的
Span capturedHttpSpan = tracer.currentSpan();
// 后续在回调里直接使用 capturedHttpSpan,而不是 tracer.currentSpan()
agentWorkflow.executeSimpleWorkflowStream(convId, message, emitter, capturedHttpSpan);
}
这个细节如果处理不好,Jaeger 里的流式请求追踪会严重失真:只显示 800ms(Controller 方法返回时),实际 20s 的推理时间全部丢失。
六、第三层:执行层详解
AgentWorkflow 是"执行总监",管理两种执行模式和 Agent 实例缓存。
两种执行模式
java
private String executeInternal(String agentType, String convId, String message) {
return "workflow".equalsIgnoreCase(agentType)
? agentWorkflow.executeComplexWorkflow(convId, message) // LangGraph 编排
: agentWorkflow.executeSimpleWorkflow(convId, message); // AiService 简单对话
}
| 模式 | 适用场景 | 实现方式 | 特点 |
|---|---|---|---|
general |
日常对话 + 工具调用 | AiServices.builder() |
轻量,LangChain4j 自动管理 ReAct 循环 |
workflow |
复杂多步骤任务 | LangGraphOrchestrator |
重量,可自定义节点流转逻辑、条件分支 |
Agent 实例的 DCL 缓存策略
AiServices.build() 本身有一定开销,且 Agent 实例是无状态的(状态在 ChatMemoryProvider 里),可以安全复用:
java
// 双重检查锁定(DCL)确保单例,volatile 防止指令重排序
private volatile GeneralService cachedAgent;
private GeneralService getOrCreateAgent() {
GeneralService agent = cachedAgent;
if (agent == null) {
synchronized (this) {
agent = cachedAgent;
if (agent == null) {
agent = AiServices.builder(GeneralService.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.tools(getLimitedToolBeans()) // 最多 8 个工具
.build();
cachedAgent = agent;
}
}
}
return agent;
}
volatile 关键字保证 cachedAgent 的写入对所有线程可见,同时防止 JIT 对"对象分配 → 字段赋值 → 引用发布"这三步进行指令重排序------这是经典的 DCL 正确写法。
SSE 心跳线程
CPU 推理最慢可达 15s,前端如果没有任何响应,连接可能超时断开:
java
// 心跳虚拟线程:AI 思考期间定期发送进度,防止 SSE 连接超时
Thread heartbeatThread = Thread.ofVirtual().start(() -> {
long interval = 5_000L;
while (!firstTokenReceived.get() && !completed.get()) {
Thread.sleep(interval);
if (!firstTokenReceived.get() && !completed.get()) {
long elapsed = System.currentTimeMillis() - thinkingStart.get();
pushSseEvent(emitter, "[STATUS]",
"正在思考中... (已思考 " + (elapsed / 1000) + "秒)");
// 指数退避:思考越久,心跳间隔越长
interval = Math.min(interval + 5_000, 30_000L);
}
}
});
心跳采用指数退避:开始 5s 一次,之后每次加 5s,上限 30s------开始时用户最关注进度,后期反而无需频繁刷新。
七、第四层:能力层详解
ToolRegistry 自动扫描原理
java
@EventListener(ContextRefreshedEvent.class)
public void autoScanTools() {
String[] beanNames = applicationContext.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Object bean = applicationContext.getBean(beanName);
// 关键:脱掉 AOP 代理外壳,找到真正的目标类
// 否则 @Transactional / @Aspect 等让 Spring 用 CGLIB 包一层代理,
// 代理类上找不到 @Tool 注解
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
for (Method method : ReflectionUtils.getAllDeclaredMethods(targetClass)) {
if (method.isAnnotationPresent(Tool.class)) {
ToolSpecification spec = ToolSpecifications.toolSpecificationFrom(method);
toolSpecs.put(spec.name(), spec);
toolInvokers.put(spec.name(), new ToolInvoker(bean, method));
toolBeans.add(bean);
}
}
}
}
开发者只需写:
java
@Component
public class WeatherTool {
@Tool("查询指定城市的实时天气信息")
public String getWeather(@P("城市名称,如:上海、北京") String city) {
return weatherApiClient.query(city);
}
}
框架启动时自动发现,LLM 需要时自动调用,零配置。
工具数量上限与 AopProxy 解包
java
private List<Object> getLimitedToolBeans() {
List<Object> all = new ArrayList<>(toolRegistry.getToolBeans());
if (all.size() > 8) {
all = new ArrayList<>(all.subList(0, 8));
}
// 脱掉 CGLIB 代理外壳,否则 AiServices 无法识别 @Tool 方法
return all.stream().map(bean -> {
Object target = AopProxyUtils.getSingletonTarget(bean);
return target != null ? target : bean;
}).collect(Collectors.toList());
}
为什么限制 8 个? 每个工具的
ToolSpecification占用 Token,qwen2.5:3b这类小模型上下文有限,工具太多导致模型"选择困难",回复质量明显下降。
MCP 协议支持
AgentX 通过 langchain4j-mcp 1.13.0-beta23 同时支持 Stdio 和 SSE 两种 MCP 传输模式:
java
// Stdio 模式:适用于本地进程工具(如 Python 脚本)
McpClient stdioClient = new DefaultMcpClient.Builder()
.transport(new StdioMcpTransport.Builder()
.command(List.of("python", "tools/my_tool.py"))
.logEvents(true)
.build())
.build();
// SSE 模式:适用于远程 MCP 服务(如 GitHub MCP Server)
McpClient sseClient = new DefaultMcpClient.Builder()
.transport(new HttpMcpTransport.Builder()
.sseUrl("http://mcp-server:8080/sse")
.build())
.build();
// 统一注册到 ToolRegistry
List<ToolSpecification> mcpTools = mcpClient.listTools();
mcpTools.forEach(spec -> toolRegistry.registerMcpTool(spec, mcpClient));
这让 AgentX 可以直接复用 MCP 生态中任意工具服务器------无需自己实现,开箱即用。
带可观测性的工具执行
每次工具调用都有独立的 Jaeger Span:
java
public Object executeTool(String toolName, String argumentsJson) {
return Observation.createNotStarted("agent.tool.execute", observationRegistry)
.contextualName("tool:" + toolName)
.lowCardinalityKeyValue("tool.name", toolName)
.observe(() -> {
Map<String, Object> args = objectMapper.readValue(argumentsJson, ...);
return invoker.invoke(args);
});
}
Jaeger 里的效果:
[AgentX HTTP 请求] 18,432ms
└─ [ai.inference] 18,100ms
├─ [tool:getWeather] 523ms ← 天气查询
└─ [tool:queryDocument] 1,240ms ← 知识库检索
八、第五层:记忆层详解
AgentX 的记忆系统分两层,分别用 Redis 和 Milvus 承担不同职责。
短期记忆:Redis(会话上下文)
实现 LangChain4j 的 ChatMemoryStore 接口,对框架完全透明:
java
@Component
public class RedisMemoryStore implements ChatMemoryStore {
private static final String PREFIX = "agentx:memory:";
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = redisTemplate.opsForValue().get(PREFIX + memoryId);
return deserialize(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
redisTemplate.opsForValue().set(
PREFIX + memoryId,
serialize(messages),
Duration.ofHours(24) // TTL 24小时,自动清理
);
}
}
Redis Key 格式:agentx:memory:{convId},按会话隔离,24 小时无活动自动过期。
长期记忆:Milvus(RAG 知识库)
对话文本经过 bge-m3 Embedding(1024 维)后存入 Milvus,支持语义检索:
java
@Bean
public EmbeddingStore<TextSegment> embeddingStore(MilvusProperties props) {
return MilvusEmbeddingStore.builder()
.host(props.host())
.port(props.port())
.collectionName("agentx_knowledge")
.dimension(1024) // bge-m3 输出维度
.indexType(IndexType.HNSW) // HNSW 索引,查询延迟 < 50ms
.metricType(MetricType.COSINE)
.build();
}
两层记忆的分工对比
| Redis 短期记忆 | Milvus 长期记忆 | |
|---|---|---|
| 存储内容 | 完整对话消息列表 | 文档/知识的向量表示 |
| 检索方式 | Key-Value 精确查找 | 语义相似度搜索(Top-K) |
| 生命周期 | 24 小时 TTL | 持久化存储 |
| 访问延迟 | < 1ms | 10--50ms |
| 典型用途 | 多轮对话上下文 | RAG 知识库检索 |
| 容量 | 数百条消息/会话 | 百万级文档片段 |
元数据持久化:PostgreSQL
除了对话记忆,AgentX 还维护了一层 PostgreSQL 元数据存储,用于业务管理:
agentx_conversation → 会话元信息(创建时间、用户ID、标题)
agentx_tool_audit → 工具调用审计日志(who / when / what / result)
agentx_workflow_def → 工作流定义(JSON 配置持久化)
agentx_kb_document → 知识库文档索引(文件路径 / 分块数 / 状态)
PostgreSQL 是数据的"冷存储"------Redis 处理实时访问,Milvus 处理向量搜索,PostgreSQL 负责结构化查询和审计合规。
九、三个关键设计决策
决策 1:@Observed 还是手动 Observation?
同步方法用 Observation.createNotStarted().observe(),流式方法不用 @Observed:
java
// ❌ 流式方法不能用 @Observed
// 原因:@Observed 的 Span 在方法返回时关闭(约 800ms)
// 但真正的 AI 推理在异步回调中进行(20s+)
// Jaeger 里只显示 800ms,完全失真
@Observed // 千万不要这样用!
public void executeSimpleWorkflowStream(...) { ... }
// ✅ 正确做法:直接给 HTTP Span 追加业务标签
// HTTP Span 生命周期 = SSE 连接保持时间,天然匹配流式场景
Span httpSpan = tracer.currentSpan();
if (httpSpan != null) {
httpSpan.tag("agentx.conv_id", convId);
httpSpan.tag("agentx.agent_type", agentType);
}
这个问题花了将近一周才定位,在 Jaeger 里的表现是"所有流式请求只显示几百毫秒"。
决策 2:AgentContext + ThreadLocal
工具调用时需要记录"这轮对话调用了哪些工具",供响应元数据使用。用 ThreadLocal 传递,避免在方法参数里一路透传:
java
// 请求开始时设置
AgentThreadContext.set(AgentContext.current(convId, traceId));
// 工具执行监听器中使用
AgentContext ctx = AgentThreadContext.get();
if (ctx != null) ctx.recordToolInvocation(toolName);
// finally 块中清理(必须)
AgentThreadContext.clear(); // ⚠️ 虚拟线程也有 ThreadLocal 泄漏风险
踩坑 :流式模式下,SSE 回调在 ForkJoinPool 线程触发,
AgentThreadContext.get()返回 null。解法:在虚拟线程(HTTP 线程)上提前捕获AgentContext引用,传入闭包。
决策 3:工具注入 AiServices 时必须解包代理
java
// ❌ 直接传 Spring Bean:如果 Bean 被 CGLIB 代理(@Transactional 等),
// AiServices 反射扫描代理类,找不到 @Tool 方法,工具注册为 0
AiServices.builder(...).tools(toolBean).build(); // 工具无效!
// ✅ 先解包,再注入:getSingletonTarget 返回 CGLIB 代理的原始目标对象
Object target = AopProxyUtils.getSingletonTarget(toolBean);
AiServices.builder(...).tools(target != null ? target : toolBean).build();
十、技术栈版本全表
以下是 AgentX 核心依赖及其版本(与项目 pom.xml 保持一致):
| 组件 | 版本 | 用途 |
|---|---|---|
| Java | 21 | 虚拟线程、Record、Pattern Matching |
| Spring Boot | 3.4.4 | 企业级框架,Actuator、自动配置 |
| LangChain4j | 1.13.0 | Java 原生 AI 编排,AiServices、工具调用 |
| LangChain4j MCP | 1.13.0-beta23 | MCP 协议,Stdio/SSE 两种传输模式 |
| LangGraph4j | 1.8.4 | 图编排工作流,复杂多步骤任务 |
| Milvus Java SDK | 2.6.17 | 向量数据库客户端 |
| Redis | 7.4+ | 会话记忆、热数据缓存 |
| PostgreSQL | 15+ | 元数据、审计日志、工作流定义 |
| OpenTelemetry | 1.60.1 | 分布式追踪、指标收集 |
| OTel 语义约定 | 1.40.0 | 遥测数据模型标准命名 |
| Netty | 4.1.126.Final | 高性能异步网络框架(Milvus gRPC 底层) |
| gRPC | 1.65.1 | Milvus 通信协议 |
| Jaeger | 1.x | 链路追踪 UI,全链路可视化 |
| Prometheus + Grafana | 最新稳定版 | 指标监控与告警 |
十一、安全设计与扩展点
安全设计
边界安全:
- JWT 统一认证,接入层集中验证,核心层不重复校验
- 服务间网络隔离(Docker 内部网络),外部只暴露 Nginx 反代端口
- 工具调用权限检查:
@RateLimited注解限制高频调用工具
数据安全:
- TLS 1.3 全链路加密(Nginx 终止 SSL)
- 会话数据 Redis Key 按用户 ID 隔离,不同用户数据物理分区
- 敏感工具入参脱敏(API Key、密码等不进审计日志)
审计合规:
- 所有工具调用写入
agentx_tool_audit表(用户、时间、入参摘要、结果状态) - 关键操作 OpenTelemetry Span 保留 7 天(Jaeger TTL 配置)
扩展点设计
AgentX 在三个维度预留了扩展接口:
工具扩展 → 实现 @Tool 注解 + @Component,自动扫描注册
模型扩展 → 实现 LangChain4j ChatModel 接口,替换 ModelRouter 中的提供商
存储扩展 → 实现 ChatMemoryStore / EmbeddingStore 接口,替换 Redis / Milvus
三个接口都是标准 LangChain4j 抽象,切换成本极低------从 Redis 换到 PostgreSQL 存会话,只需重写一个类。
十二、三节点部署拓扑
A 节点(4C8G · 上海)
├── Ollama(qwen2.5:3b · CPU推理)
├── AgentX Backend(Spring Boot · 8080)
└── Jaeger(链路追踪 · 16686)
B 节点(2C4G · 上海)
├── Milvus(向量存储 · 19530)
├── Redis(会话缓存 · 6379)
└── MinIO(对象存储 · 9000)
C 节点(2C4G · 北京)
├── Nginx(反代 + SSL · 80/443)
├── Prometheus(指标采集 · 9090)
└── Grafana(监控看板 · 3000)
A 节点分配最高配置(4C8G)是因为 Ollama CPU 推理吃内存。B 节点是数据节点,Milvus 启动就要 1--2GB 内存,2C4G 刚好够。C 节点最轻松,只跑代理和监控。
全年运营成本 :A 节点 ¥80/月 + B 节点 ¥60/月 + C 节点 ¥50/月 + 域名 CDN ¥20/月 + DeepSeek API ¥30--80/月 = ¥2,880--3,480/年,无 GPU。
十三、架构总结
用户请求
↓
① AgentController 协议层:分配ID、建SSE通道、立即返回
↓
② AgentService 调度层:熔断、追踪、路由
↓
③ AgentWorkflow 执行层:模式选择、DCL实例缓存、流式推送、心跳
↓
④ ToolRegistry 能力层:@Tool自动扫描、MCP工具、带Span的工具执行
⑤ RedisMemoryStore 记忆层:短期会话/Milvus长期语义/PostgreSQL元数据
↓
⑥ Ollama / DeepSeek 基础层:LLM推理(CPU混合云API)
Redis / Milvus 数据层:缓存、向量存储
Jaeger / Grafana 观测层:链路追踪、指标监控
AgentX 的架构不是一开始设计出来的,而是在一次次 OOM、一次次 TraceId 丢失、一次次工具调用失败中被踩出来的。
每一个看起来"过度设计"的细节------心跳线程、Span 预捕获、AopProxy 解包、工具数量上限、DCL 缓存------背后都有一个真实踩过的坑。
如果你正在搭建类似系统,希望这篇文章能帮你少走一些弯路。
💬 关注公众号「SuniaCoder-AI全栈架构实战」,回复「架构」获取 AgentX 完整架构图高清 PDF(含六层架构 + 三节点部署拓扑 + 请求时序图)。
关于作者 & 联系方式
汪旭 / Sunia --- Java 全栈开发者,AI 应用工程化实践者
专注企业级 AI 落地,擅长极限资源优化,有 RAG、Agent、知识图谱方向的完整实战经验。
| 平台 | 地址 / 说明 |
|---|---|
| CSDN | SuniaCoder-AI|13.5 万+ 阅读,RAG/Agent 系列持续更新 |
| 微信公众号 | 搜索【SuniaCoder-AI全栈架构实战】|关注「架构」获取 AgentX 完整架构图 |
| 掘金 | SuniaCoder-AI |
| 知乎 | SuniaCoder-AI |
| 合作咨询 | 企业私有化大模型部署与定制开发,欢迎私信洽谈 |
如果内容对你有帮助,点赞 + 收藏 + 关注是最大的支持,也能让更多需要的人看到这篇文章。
上一篇 → 没有 GPU、只有 3 台低配云服务器,我如何选出 AgentX 的技术栈
Tags: Java Agent架构 / LangChain4j / AgentX / SSE流式推送 / ToolRegistry / 虚拟线程 / OpenTelemetry / Spring Boot AI / MCP协议 / Milvus / Redis