背景
正在开发的作业批改系统需要支持 QQ 群聊答疑功能。学生通过 QQ 向机器人提问时,希望机器人能够基于上传的教学文档(知识库)进行回答,而不是仅依赖模型自身的知识。
架构设计
最终架构
1
| 学生QQ提问 → NapCat(WS) → OpenClaw OneBot插件 → RAG增强 → 贾维斯Agent → OpenClaw → 回复学生
|
关键点:
- 在 OneBot 插件层拦截消息,调用后端 RAG 接口增强 prompt
- 增强后的消息再发给 Agent 处理
- 复用现有的向量检索和文档存储系统
实现过程
1. 后端接口开发
创建 OnebotRagController.java,提供 RAG 增强接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @PostMapping("/rag") public ResponseEntity<RagResponse> rag(@RequestBody RagRequest request) { List<String> relevantContents = documentService.searchRelevantContent( request.getMessage(), RAG_TOP_K); String enhancedMessage; if (relevantContents.isEmpty()) { enhancedMessage = request.getMessage(); } else { String context = String.join("\n\n---\n\n", relevantContents); enhancedMessage = String.format(""" 基于以下参考文档内容回答问题。如果文档中没有相关信息,请基于你的知识回答。 参考文档内容: %s 用户问题:%s """, context, request.getMessage()); } return ResponseEntity.ok(new RagResponse(enhancedMessage, !relevantContents.isEmpty())); }
|
2. 插件层修改
修改 openclaw-onebot 插件,在消息转发前插入 RAG 调用:
config.js - 添加 RAG 配置读取:
1 2 3 4 5 6 7
| export function getRagEnabled(cfg) { return cfg?.channels?.onebot?.ragEnabled === true; }
export function getRagEndpoint(cfg) { return cfg?.channels?.onebot?.ragEndpoint ?? "http://localhost:8080/api/onebot/rag"; }
|
process-inbound.js - 添加 RAG 调用逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const shouldUseRag = () => { if (!getRagEnabled(cfg)) return false; if (isGroup) { const targetGroups = getRagTargetGroups(cfg); if (targetGroups.length === 0) return true; return targetGroups.includes(Number(msg.group_id)); } return getRagTargetPrivate(cfg); };
if (shouldUseRag()) { const enhanced = await callRagApi({ qq: String(msg.user_id), message: messageText, endpoint: getRagEndpoint(cfg) }); if (enhanced) messageText = enhanced; }
|
3. 配置
openclaw.json:
1 2 3 4 5 6 7 8 9 10
| { "channels": { "onebot": { "ragEnabled": true, "ragEndpoint": "http://localhost:8080/api/onebot/rag", "ragTargetGroups": [], "ragTargetPrivate": true } } }
|
遇到的问题与解决方案
问题 1:401 鉴权失败
现象: 插件调用后端接口返回 401
原因: 后端有 JWT 过滤器拦截,未放行 /api/onebot/rag
解决: 在 JwtAuthenticationFilter 和 SecurityConfig 中添加放行路径:
1 2 3 4 5 6
| if (path.startsWith("/api/auth/") || path.startsWith("/api/chat/health") || path.startsWith("/api/onebot/rag")) { filterChain.doFilter(request, response); return; }
|
问题 2:变量未定义错误
现象: Cannot access 'cfg' before initialization
原因: RAG 代码块放在 cfg 变量定义之前
解决: 调整代码顺序,确保 cfg 先定义再使用
问题 3:检索质量差,返回无关内容
现象: 问”今天天气如何”,返回的是 C语言字符串赋值相关内容
原因: 向量检索没有相似度阈值,返回了低相关度的结果
解决: 在 DocumentServiceImpl 中添加余弦相似度阈值过滤:
1 2 3 4 5 6 7 8 9
| private static final double SIMILARITY_THRESHOLD = 0.75;
return results.stream() .map(chunk -> new ScoredChunk(chunk, cosineSimilarity(queryEmbedding, chunk.getEmbedding()))) .filter(sc -> sc.score >= SIMILARITY_THRESHOLD) .sorted((a, b) -> Double.compare(b.score, a.score)) .limit(topK) .map(sc -> sc.chunk.getContent()) .toList();
|
问题 4:响应慢
现象: RAG 调用耗时较长,影响用户体验
原因: 向量检索和 embedding 计算需要时间
待优化:
- 添加 Redis 缓存热门查询
- 减少 topK 数量
- 异步处理 RAG 请求
最终效果
学生 QQ 提问时:
- 消息先经过 RAG 检索,查找知识库相关内容
- 如果找到相关内容,组装成增强 prompt 发给 AI
- AI 基于知识库内容回答问题
- 如果没有相关知识,AI 基于自身知识回答
总结
通过在后端和插件层分别开发 RAG 功能,实现了 QQ 机器人的知识库问答能力。关键在于:
- 解耦设计:插件负责消息拦截和转发,后端负责检索和存储
- 质量把控:添加相似度阈值,避免低质量检索结果干扰
- 灵活配置:支持按群、私聊分别配置是否启用 RAG
后续可以进一步优化检索性能和缓存策略。