《Agentx专栏》02-技术选型:预算有限时如何做出正确的技术决策

没有 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

相关推荐
羡寒.8 小时前
接口突然变慢,你怎么排查?
java·后端·spring
zuowei28898 小时前
编程语言对比:C/C++/Java/C#/PHP
java·c语言·c++
百数平台8 小时前
功能更新——百数详情页“数据简报”与“关联标签页”配置指南
java·服务器·前端
接着奏乐接着舞8 小时前
java lambda表达式
java·开发语言·python
金銀銅鐵8 小时前
[Java] 自己写程序,来解析字段的 descriptor
java·后端
ch.ju8 小时前
Java程序设计(第3版)第四章——成员方法
java·开发语言
椰椰椰耶8 小时前
[SpringCloud][8]Spring Cloud LoadBanlancer快速上手以及LoadBalancer原理
后端·spring·spring cloud
唐璜Taro8 小时前
LangChain与LangGraph多Agent实战:从工具链到工作流编排(上)
langchain·agent·langgraph
Dicky-_-zhang9 小时前
微服务安全防护实战:OAuth2与JWT鉴权
java·jvm