没有 GPU、只有 3 台低配云服务器,我如何选出 AgentX 的技术栈|AgentX 专栏②
本文是 AgentX 技术专栏第二篇。记录从 2024 年底到 2026 年初,在构建企业级 AI 智能体平台过程中所做的每一项技术决策------以及踩过的每一个坑。
本文速览:
- LangChain4j vs Spring AI,为什么最终选了社区方案?
- 没有 GPU 怎么跑大模型?混合推理架构的实际效果
- Milvus 部署复杂但值得,什么时候可以用 PGVector 凑合?
- Java 21 虚拟线程对 Agent 场景有多重要?
- 3 台 2C4G,一年不到 3000 元,成本明细公开
背景:三台低配服务器,没有 GPU
2024 年底我开始认真做 AgentX 时,资源约束非常明确:
- 预算:个人项目,每月不超过 300 元
- 服务器:3 台 2C4G 云服务器,月租 ¥50-80/台
- GPU:没有,用不起
在这个前提下,每一项技术选择都不是"哪个更先进"的问题,而是"哪个在这个约束下能活下去"。
下面是六个关键决策的完整推导过程。
选型总览
| 决策维度 | 最终选择 | 被淘汰的选项 |
|---|---|---|
| AI 编排框架 | LangChain4j | Spring AI |
| LLM 推理方案 | Ollama + qwen2.5 | DeepSeek API(纯云端)、云 GPU |
| 向量数据库 | Milvus | PGVector、Chroma、Redis Stack |
| 记忆/会话存储 | Redis | MySQL、内存 Map |
| Java 版本 | Java 21 | Java 17、Java 8 |
| 部署方案 | Docker Compose 三节点 | K8s(超预算)、裸机部署 |
| 可观测性 | OpenTelemetry + Jaeger | ELK Stack、Prometheus 全家桶 |
每个决策都是一次取舍,没有最好的,只有当时条件下最合适的。
一、AI 编排框架:LangChain4j vs Spring AI
这是整个项目中最核心的选择,影响后续所有的 Agent 开发方式。
两个框架的定位差异
Spring AI(VMware 官方维护)的优势:
- Spring 官方出品,与 Boot 全家桶集成自然
- 13+ 种 VectorStore 实现,数据库支持全面
- ChatClient API 简洁,5 分钟能跑通第一个示例
- 内置 Advisors 机制,Prompt 增强很方便
LangChain4j(社区驱动)的优势:
- 更完整的 Agent/Tool 支持:
@Tool注解自动扫描、ToolSpecification自动生成 - ReAct 循环引擎原生 Java 实现,不需要 Python 中转
- MCP 协议原生集成(
langchain4j-mcp),内置 Stdio/SSE 客户端 - 文档解析(Apache Tika)、文档分割(Recursive/ByParagraph)开箱即用
- 迭代速度更快(2025 年以来月均 2-3 个版本)
一个具体的对比场景
Spring AI 的工具调用方式(需要手动管理 ToolExecution):
java
// Spring AI:手动注册工具,管理工具执行结果
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultFunctions("weatherFunction", "documentFunction")
.build();
}
// 工具执行结果需要自己处理,缺乏统一的生命周期管理
LangChain4j 的工具调用方式(声明式,自动管理):
java
// LangChain4j:@Tool 注解直接声明,框架自动处理调用链
public class WeatherTool {
@Tool("查询指定城市的实时天气")
public String getWeather(@P("城市名称") String city) {
// 工具逻辑
return weatherService.query(city);
}
}
// Agent 自动发现并调用工具,完整的 ReAct 循环
AiServices.builder(AgentService.class)
.chatLanguageModel(model)
.tools(new WeatherTool(), new DocumentTool())
.chatMemory(memory)
.build();
LangChain4j 的工具系统更接近 Python LangChain 的体验,框架负责 ToolCall → 工具执行 → 结果回注 → 继续推理的完整循环,开发者只需要写工具逻辑本身。
我的决策:LangChain4j ✅
AgentX 的核心场景是智能体编排 + 多工具调用,这正是 LangChain4j 的强项。
另一个现实因素:Spring AI 在 1.x → 2.x 升级时 API 变更巨大(ChatClient 从 Fluent API 变为 Builder 模式),如果 2024 年选了 Spring AI,2025 年的迁移成本会很高。LangChain4j 1.x 的 API 稳定性明显更好。
踩坑:LangChain4j 的文档质量参差不齐,核心 API 有文档,边缘功能靠看源码。如果你习惯了 Spring 文档的完善程度,前期需要适应。
二、LLM 推理方案:混合架构,而不是二选一
三种方案的成本对比
| 方案 | 月成本 | 延迟 | 数据安全 | 离线可用 |
|---|---|---|---|---|
| 云 API(DeepSeek/GPT) | ¥100-500/月 | 1-3s | 数据出域 ❌ | ❌ |
| 云端 GPU 自部署 | ¥500-2000/月 | 0.5-2s | ✅ | ❌ |
| CPU 本地推理(Ollama) | ¥0 | 3-15s | ✅ | ✅ |
单看这张表,结论很显然:CPU 本地推理成本最低,但延迟是硬伤。
我的实际方案:混合推理架构
我没有选一种,而是把两种方案组合起来:
┌─────────────────────────────────────────┐
│ 请求路由层 │
├──────────────┬──────────────────────────┤
│ 非实时任务 │ 高优先级任务 │
│ (文档分析、 │ (用户直接对话、 │
│ 批处理) │ 实时问答) │
├──────────────┼──────────────────────────┤
│ Ollama │ DeepSeek API │
│ qwen2.5:3b │ 按量付费 │
│ CPU推理 │ 1-3s延迟 │
│ 5-15s延迟 │ 数据出域(非敏感场景) │
└──────────────┴──────────────────────────┘
Embedding层:Ollama + bge-m3(本地,不出域)
对应的 LangChain4j 配置:
yaml
# application.yml
langchain4j:
ollama:
base-url: http://ollama-node:11434
model-name: qwen2.5:3b
timeout: 60s
num-ctx: 4096 # 关键:从8192降到4096,避免OOM
num-predict: 1024
open-ai: # DeepSeek兼容OpenAI接口
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY}
model-name: deepseek-chat
timeout: 30s
java
// 路由逻辑:根据场景选择模型
@Service
public class ModelRouter {
@Autowired
private ChatLanguageModel ollamaModel; // 本地模型
@Autowired
private ChatLanguageModel deepseekModel; // 云端API
public ChatLanguageModel route(AgentRequest request) {
// 实时对话场景 → 云端API,低延迟优先
if (request.isRealtime()) {
return deepseekModel;
}
// 文档分析、批处理 → 本地模型,成本优先
return ollamaModel;
}
}
为什么不用 GPU
一台带 GPU 的云服务器月租 ¥500+,年成本 ¥6000+。
3 台 2C4G 的 CPU 服务器合计月租 ¥150-240,年成本不到 ¥3000。
这个差价,对个人项目来说,直接决定项目的生死。
qwen2.5:1.5b 在 4 核 CPU 上运行良好,响应 3-8 秒,覆盖大多数问答场景。3b 模型稍慢,但知识储备更强,实测效果达到预期。
踩坑 1 :qwen2.5:3b 在 2C4G 上 OOM 过几次。解决方案:限制 Ollama 并发数为 1,将
num_ctx从 8192 降到 4096。踩坑 2:不要把 Ollama 和 Java 进程放在同一台 2C 服务器上。Ollama 推理时 CPU 占满,Spring Boot 的请求处理会严重抖动。A 节点至少需要 4 核。
三、向量数据库:为什么选了最难部署的那个
四个方案的横向对比
| 方案 | 部署复杂度 | 向量性能 | 资源占用 | 与 LangChain4j 集成 |
|---|---|---|---|---|
| Milvus | 🔴 较高(独立服务) | 🚀 极高 | 独占约 1.5G | ⭐⭐⭐⭐⭐ 原生支持 |
| PGVector | 🟢 低(PG 插件) | 👍 良好 | 与 PG 共享 | ⭐⭐⭐⭐ |
| Chroma | 🟢 低(嵌入式) | 👍 良好 | 轻量 | ⭐⭐⭐ |
| Redis Stack | 🟢 低 | 👍 快速 | 轻量 | ⭐⭐⭐ |
Milvus 是最难部署的,但我还是选了它。
选 Milvus 的三个关键理由
1. 性能天花板最高
当向量数据量达到 10 万条以上,PGVector 的性能开始明显衰减(全表扫描的问题),而 Milvus 的 HNSW 索引 + 分片设计在百万级数据下仍然线性可扩展。
2. LangChain4j 原生集成,接入成本低
java
// 三行代码接入,EmbeddingStore 接口完全透明
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return MilvusEmbeddingStore.builder()
.host("milvus-node")
.port(19530)
.collectionName("agentx_knowledge")
.dimension(1024) // bge-m3 的向量维度
.build();
}
3. Attu 管理界面,运维可视化
Milvus 自带 Attu 界面,可以直接看 Collection 状态、查询耗时、索引构建进度,对排查 RAG 召回问题非常有帮助。
什么时候应该选 PGVector
如果你的项目:
- 数据量 < 10 万条向量
- 已有 PostgreSQL 在运行
- 不想引入额外的服务依赖
那选 PGVector 就够了,性价比最高。
踩坑 :Milvus 2.4 → 2.5 升级时,索引格式不兼容,需要重建 Collection。我在测试环境没有备份,数据全部重建了一遍。生产环境升级前,必须先备份 Collection 数据。
四、Java 21:虚拟线程对 Agent 场景的实际价值
Java 21 的虚拟线程(Virtual Threads)是我选它的最核心原因。
Agent 场景的并发特点
一个典型的 Agent 请求链路:
用户请求 → LLM 推理(5-15s)→ 工具调用(0.5-2s)→ LLM 再次推理(5-15s)→ 返回
整个过程大量时间在等待 I/O(LLM 推理、数据库查询、HTTP 工具调用)。传统线程模型下,每个请求占用一个平台线程,并发 100 个请求就需要 100 个线程,内存开销巨大。
虚拟线程让 I/O 等待期间线程可以被复用,并发能力从"线程数量"解耦出来。
Spring Boot 3 中开启虚拟线程
java
@Configuration
public class VirtualThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(
Executors.newVirtualThreadPerTaskExecutor()
);
};
}
}
yaml
# application.yml
spring:
threads:
virtual:
enabled: true # Spring Boot 3.2+ 一键开启
开启后,@Async 任务、@Scheduled、Tomcat 请求处理线程全部切换为虚拟线程,无需其他改动。
实测:同等硬件下,AgentX 的并发吞吐量提升约 40%,内存占用下降约 30%。
注意 :虚拟线程与
synchronized锁配合有 pinning 问题(线程被 pin 住无法释放)。如果你的代码里有 synchronized 块,建议换成 ReentrantLock。
五、部署方案:Docker Compose 就够了
为什么不用 K8s
K8s 的最低生产可用规格:3 台 4C8G,月成本 ¥600-900。
Docker Compose 三节点:3 台 2C4G,月成本 ¥150-240。
差距 4 倍,对个人项目没有悬念。
三节点分工
A 节点(上海,4C8G) B 节点(上海,2C4G)
┌─────────────────────┐ ┌─────────────────────┐
│ Ollama (qwen2.5) │ │ Milvus │
│ Spring Boot 后端 │ ←→ │ Redis │
│ Jaeger │ │ MinIO │
└─────────────────────┘ └─────────────────────┘
↑
C 节点(北京,2C4G)
┌─────────────────────┐
│ Nginx 反向代理 │
│ Prometheus + Grafana│
└─────────────────────┘
核心 docker-compose.yml 结构:
yaml
# B 节点:数据层
version: '3.8'
services:
milvus:
image: milvusdb/milvus:v2.5.0
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
deploy:
resources:
limits:
memory: 1536M # 关键:限制内存,避免OOM
redis:
image: redis:7-alpine
command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru
deploy:
resources:
limits:
memory: 600M
minio:
image: minio/minio:latest
deploy:
resources:
limits:
memory: 256M
踩坑 :第一次部署把所有服务塞进一台 2C4G,Milvus + Ollama + Spring Boot 同时跑,频繁 OOM。拆分为三节点后彻底解决。Milvus 和 Ollama 绝对不能放在同一台 2C4G 机器上。
六、可观测性:没有它,你完全不知道慢在哪里
为什么 AI 系统更需要可观测性
一个没有追踪的 Agent 请求,当它花了 30 秒才返回,你不知道:
- LLM 推理用了多久?
- 调用了几次工具?每次工具多久?
- 向量检索耗时多少?
- 是哪一步的并发等待导致的?
OpenTelemetry + Jaeger 的实际效果
Spring Boot 3 集成 OpenTelemetry 只需两步:
xml
<!-- pom.xml -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
yaml
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 开发环境全采样
otel:
exporter:
otlp:
endpoint: http://jaeger-node:4318
service:
name: agentx-backend
接入后,每个 Agent 请求在 Jaeger 上显示为完整的 Span 链路:
[请求入口 AgentController] 18,432ms
├─ [记忆加载 RedisMemoryStore] 12ms
├─ [向量检索 MilvusEmbeddingStore] 342ms
├─ [LLM 推理 qwen2.5:3b] 14,821ms ← 主要瓶颈
├─ [工具调用 WeatherTool] 523ms
└─ [LLM 推理 qwen2.5:3b] 2,734ms
一眼就能看出瓶颈在 LLM 推理,而不是工具调用。
实际案例:通过 Jaeger 发现 bge-m3 的 Embedding 耗时是 qwen 推理的 3 倍,于是优化为预计算 + 缓存,响应时间下降 40%。
踩坑 :Spring Boot 3.4 中,手动指定 OTel 版本号会导致 TraceId 在部分日志中丢失,复现极难。解决方法是完全交给 Spring BOM 管理版本,不要手动指定 opentelemetry-* 的版本号。
七、成本明细:一年不到 3000 元
| 项目 | 月成本(¥) | 年成本(¥) |
|---|---|---|
| A 节点(4C8G,Ollama + 后端) | 80 | 960 |
| B 节点(2C4G,Milvus + Redis) | 60 | 720 |
| C 节点(2C4G,Nginx + 监控) | 50 | 600 |
| 域名 + CDN | 20 | 240 |
| DeepSeek API(按量,高优任务) | 30-80 | 360-960 |
| 合计 | 240-290 | 2,880-3,480 |
如果用云端 GPU 方案(单卡 A10 约 ¥800/月),同等吞吐量的成本至少是现在的 3-4 倍。
写在最后
技术选型没有银弹。
这篇文章里的每一个选择,都不是"哪个更先进",而是"在 2024-2026 年、没有 GPU、只有 3 台低配服务器的条件下,哪个能活下去"。
AgentX 的技术栈不是最完美的,但它是这个约束下的最优解------用不到 3000 元/年的成本,跑起了一个能处理多工具调用、支持企业级知识库、具备全链路可观测性的 AI 智能体平台。
下一篇,我们进入架构设计:从这些技术决策出发,构建 AgentX 的六层架构,看看选型决定是如何一步步塑造系统骨架的。
关于作者 & 联系方式
汪旭 / Sunia --- Java 全栈开发者,AI 应用工程化实践者
专注企业级 AI 落地,擅长极限资源优化,有 RAG、Agent、知识图谱方向的完整实战经验。
| 平台 | 地址 / 说明 |
|---|---|
| CSDN | SuniaCoder-AI|13.5 万+ 阅读,RAG/Agent 系列持续更新 |
| 微信公众号 | 搜索【SuniaCoder-AI全栈架构实战】|关注回复「调优」获取完整 Spring AI RAG 项目和学习手册 |
| 掘金 | SuniaCoder-AI |
| 知乎 | SuniaCoder-AI |
| 合作咨询 | 提供企业私有化大模型部署与定制开发(基础部署 / 企业定制 / 年度维保)欢迎私信洽谈 |
如果内容对你有帮助,点赞 + 收藏 + 关注是最大的支持,也能让更多需要的人看到这篇文章。
上一篇 → 前言:一个 Java 开发者的 Agent 实践之路
Tags: Java Agent / LangChain4j / Spring AI / 技术选型 / Milvus / Ollama / Docker Compose / OpenTelemetry / Java 21 虚拟线程 / 企业级AI