背景

EduMind 是一个 AI 驱动的智能教学平台,技术栈是 Spring Boot 4 + Vue 3,核心功能包括 RAG 知识库检索、作业批改、课堂实时互动和 QQ 机器人答疑。

从项目初期开始,所有 LLM 调用都经过 OpenClaw——一个开源的 AI Agent 框架(38 万 star,2026 年仍然极度活跃)。它负责:接收 QQ 消息、调用 MCP 工具、管理会话、路由 LLM 请求。

用了三个月后,我们决定把它从项目里彻底移除。这篇文章记录了为什么、怎么做、以及做完之后的架构变成了什么样。

为什么要移除 OpenClaw?

1. 品类错配

OpenClaw 是一个聊天机器人框架——它的核心价值是多频道消息(QQ/微信/Discord/Telegram)、Skill 插件系统、多 Agent 工作流。但 EduMind 只用了它最小的一块功能:作为一个 HTTP 代理把请求转发给 DeepSeek。

这就像租了一整层办公楼,只用了两间房,其中一间还漏水。

2. 问题积累

三个月的使用中,OpenClaw 带来的问题逐渐超过了它提供的便利:

  • 视觉模型无法路由VisionPdfParser 早就被迫绕过 OpenClaw 直连 Kimi API——代码注释里直接写着”OpenClaw 无法在文本模型和视觉模型间自动路由”
  • 中间层延迟:每个 LLM 调用多一次 HTTP 往返(Java → OpenClaw → LLM API),积少成多
  • 排查困难:出问题时要同时排查 Java、OpenClaw、LLM API 三层,”是 OpenClaw 的问题还是我的问题”成了日常
  • 多一个运维依赖:每次开发环境启动都要额外启动一个 Node.js 服务

3. 核心能力用不上

OpenClaw 最强的多 Agent 编排、Skill 热插拔、跨频道消息路由——EduMind 一个都没用。只用了 /chat/completions 代理这一个边角料功能。

替代方案分析

评估了三种方案:

方案 代表产品 结论
AI 网关 One API / New API / LiteLLM 多一个 Go 服务,运维负担从 OpenClaw 换成另一个网关,不值得
Agent 框架 LangChain / CrewAI / AutoGen 抽象层太多,EduMind 已有的 WorkflowEngine + StructuredOutputInvoker 已经够用
自研 Agent Loop + 直连 API 300 行核心代码,零外部依赖,完全掌控

选了自研。原因很简单:EduMind 已经自研了 RAG 全链路、DAG 工作流引擎、MCP JSON-RPC 端点、Redis Stream 消费框架——多 Agent 编排不过是这些轮子的自然延伸。

实现方案

核心思路:保留 OpenClawService 接口,换一个实现。

改动范围

1
2
3
4
5
6
7
8
9
10
新建:2 个文件
├── LlmProperties.java (LLM 配置)
└── AgentLoopServiceImpl.java (300 行核心循环)

新增能力:
├── OneBotWebSocketClient.java (QQ 双向通信,替代 OpenClaw OneBot 插件)

修改:14 个文件(配置文件 + 注释 + 废弃标记)

不变:12 个消费者类 + 全部 MCP 工具 + WorkflowEngine + GradingWorkflow

架构对比:

1
2
3
4
5
6
7
8
9
10
11
12
之前:                                  之后:

Spring Boot Spring Boot
│ │
▼ ├─→ DeepSeek API(直连)
OpenClawService │
│ (HTTP) ├─→ KnowledgeSearchTool(直接调 Bean)
▼ │
OpenClaw Gateway ──→ /mcp (HTTP 回调) ├─→ ClassStatusTool(直接调 Bean)
│ │
▼ └─→ OneBot WebSocket(双向收发)
DeepSeek/Kimi API

核心:Agent Loop(50 行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while (step < maxSteps) {
// 1. 调 LLM(非流式)
response = POST /chat/completions { model, messages, tools }

// 2. 模型要调工具 → 直接调 Spring Bean
if (response.tool_calls != null) {
messages.add(assistantMsg); // 模型说"我要调 searchKnowledge"
for (toolCall : response.tool_calls) {
result = toolMap.get(name).execute(args); // 直接方法调用
messages.add({ role: "tool", content: result });
}
continue; // 回到循环,模型看到工具结果继续推理
}

// 3. 模型觉得够了 → 返回最终文本
return response.content;
}

关键设计决策:

  • 工具调用零 HTTP 开销:OpenClaw 的 MCP 回调走 HTTP JSON-RPC,自己做直接调 Spring Bean
  • 内部循环非流式 + 最终答案流式:工具调用阶段不需要流式,最终回答才用 SSE 推给用户
  • @Lazy 打破循环依赖AgentLoopServiceImpl → KnowledgeSearchTool → RagService → QueryRewriter → AgentLoopServiceImpl,在 QueryRewriter 注入点加 @Lazy 断开

OneBot:从 HTTP 双通道到 WebSocket 单向

OpenClaw 时代,QQ 消息的收发走两条不同的路径:

1
2
收消息: QQ → NapCat → WebSocket → OpenClaw → HTTP → Java (/mcp + /api/onebot/rag)
发消息: Java → HTTP → NapCat → QQ

自研后统一为一条 WebSocket 连接:

1
收发: QQ → NapCat → WebSocket → Java(收事件 + 发 action 都在同一条连接)

OneBot v11 协议本身就支持双向通信——WebSocket 连接上,NapCat 推送事件下来,客户端发 action 上去。之前分两条路纯粹是因为 OpenClaw 挡在中间。

作业批改 Prompt 升级

从 OpenClaw 的 homework-grader Skill 中提取了 C 语言批改的详细规则,合并到三个 grading prompt 模板中:

  • grading-grade.txt:新增 C 语言评分维度(contentScore 70 + formatScore 30)、分级扣分表(minor 1-3 / major 5-10 / critical 10-20)、典型场景扣分参考
  • grading-errors.txt:新增 7 种 C 语言错误类型、severity 与扣分对应关系、知识点分类规则
  • grading-suggestions.txt:新增优先级分级、类别词模板、可执行建议规范

DevTools 踩坑

Spring Boot DevTools 的 RestartClassLoader 导致 Course 实体被两个不同 classloader 加载,出现 Course cannot be cast to Course 的诡异错误。spring.devtools.restart.enabled=falseMETA-INF/spring-devtools.properties 都不生效——最终从 pom.xml 直接删除 spring-boot-devtools 依赖解决。

架构收益

1. 零外部 AI 依赖

现在 EduMind 只需要这 4 个服务:

1
PostgreSQL + Redis + MinIO + 应用

OpenClaw 从部署清单里消失。开发环境少启动一个 Node 进程,生产环境少维护一个服务。

2. 性能提升

指标 之前 之后
LLM 调用跳数 Java→OpenClaw→API(2 跳) Java→API(1 跳)
工具调用 HTTP JSON-RPC 往返 直接方法调用
QQ 消息路径 4 跳(含两次 HTTP 回调) 1 跳 WebSocket

保守估计单次 QQ 问答响应时间减少 200-500ms。

3. 可观测性

之前 LLM 调用的 trace 埋在 OpenClaw 的日志里。现在所有调用链在 Java 侧可见——Micrometer @Timed 打点、@CircuitBreaker 熔断、日志里完整的 Agent 步数追踪。

4. 模型自由

1
2
3
4
5
6
7
8
# .env 里一行切换
LLM_MODEL=deepseek-v4-pro # 强推理
LLM_MODEL=deepseek-v4-flash # 快推理
LLM_MODEL=kimi-k2-0701-preview # Kimi

# 不同场景不同模型
edumind.llm.model=deepseek-v4-flash # 默认聊天用快的
# 审校 Agent 可以用更强的模型(通过 AgentConfig 扩展)

不再受 OpenClaw agent 路由的限制,多模型供应商随意切换。

5. 课程级个性化

之前 OpenClaw 只有一个全局的 Jarvis system prompt。现在 resolveSystemPrompt() 根据 QQ 群号 → 班级 → 课程 → course.system_prompt,不同班级自动加载不同的 prompt。群聊优先用群绑定的课程,私聊不绑定课程让学生自由提问。

可扩展性

做完这次改造后,项目处于一个很舒服的位置来应对未来的需求:

多 Agent 编排(零成本起步)

EduMind 已有的组件距离多 Agent 只差一层抽象:

1
2
3
4
5
6
7
已有: WorkflowEngine(DAG 拓扑) + StructuredOutputInvoker(JSON 重试) + ToolDefinition(工具接口)
缺少: AgentConfig(name + model + systemPrompt + allowedTools)

加上 AgentConfig 后:
串行链: WorkflowEngine DAG,每个节点用不同 AgentConfig
并行投票: CompletableFuture.allOf(agentA, agentB, agentC)
主编-子Agent: 把子 Agent 包装成 ToolDefinition

GradingWorkflow 的 GRADE → ERROR_ANALYSIS → SUGGESTION 三个节点本质上已经是三个 Agent——各有专属 prompt、各自调用 LLM、产出结构化结果交给下一个节点。

模型供应商扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// LlmProperties 加一个 providers map
providers:
deepseek:
base-url: https://api.deepseek.com
api-key: sk-xxx
kimi:
base-url: https://api.moonshot.cn/v1
api-key: sk-xxx

// AgentConfig 里指定 provider
AgentConfig.builder()
.provider("kimi") // 视觉任务用 Kimi
.model("kimi-k2.6")
.build()

新工具接入

MCP 工具体系保持不变,新增工具只需实现 ToolDefinition 接口:

1
2
3
4
5
@Component
public class WeatherTool implements ToolDefinition {
public String name() { return "queryWeather"; }
public String execute(Map<String, Object> args) { ... }
}

Spring 自动扫描 ToolDefinition 的所有实现,AgentLoopServiceImpl 自动注册。

总结

移除 OpenClaw 的本质不是”换一个网关”,而是认清自己真正需要的抽象层次

OpenClaw 提供的 Tool-Calling Loop 用 50 行 Java 就能实现。它提供的多 Agent 编排,EduMind 的 WorkflowEngine + GradingWorkflow 已经做到了,且更贴合教学业务。它提供的多频道消息路由,一条 WebSocket 连接 + OneBot v11 协议就够了。

真正难的东西——RAG 检索、Embedding 推理、Reranker 精排、DAG 工作流引擎、结构化输出重试——EduMind 早已自己搞定。

当你的基础设施比框架更懂你的业务时,框架就是多余的中间层。


本次改动总计:新建 4 个文件,修改 14 个文件,核心代码约 500 行。12 个消费者类零改动。移除 spring-boot-devtools 依赖解决 classloader 冲突。