本文记录了一次完整的 AI 辅助开发实践——从需求提出、技术调研、架构设计到代码实现与测试验证,全程由 AI 编程助手(Qoder)协同完成。
一、背景与需求
我正在构建一个企业级智能体平台,底层对接开源 AI Agent 框架,提供会话管理、流式对话、工具调用等能力。
平台已具备完整的对话功能,但缺少一个关键能力:让 AI 助手在回答问题时能够引用企业内部知识库。
我们已经部署了 RagFlow(一款开源的 RAG 引擎),里面存放了架构设计文档、技术规范等企业知识。需求很明确:
在会话对话框中,允许用户主动开启知识库开关,选择要引用的知识库,AI 在回答时自动检索相关资料作为参考依据。
二、技术调研:RagFlow API 探秘
2.1 认证方式的抉择
RagFlow 提供两种认证方式:
POST /api/v1/auth/login | ||
Authorization: Bearer ragflow-xxx |
调研过程中发现,RagFlow 的 Web 登录接口要求对密码做 RSA 公钥加密(返回 "Fail to crypt password" 错误),这在前后端分离架构中实现成本过高。最终选择 API Key 认证——在 RagFlow 管理界面「头像 → 设置 → API → Create new key」一键生成。
2.2 核心 API 验证
通过 AI 助手操控浏览器登录 RagFlow 实例,创建 API Key 后,逐一验证了关键接口:
bash
# 知识库列表GET /api/v1/datasets?page=1&page_size=100→ 返回 "架构书籍"(19 分块 / 2 文档)# 语义检索POST /api/v1/retrievalBody: { "question": "微服务架构", "dataset_ids": ["726bb44a..."], ... }→ 返回带相似度分数的相关文本块
两个接口均验证通过,为后续集成奠定了基础。
三、架构设计:研读 QwenPaw 源码的启示
3.1 QwenPaw 内部的 RAG 机制
在动手之前,我让 AI 助手深入研读了 QwenPaw 源码,发现它有一套成熟的 RAG 架构——双通道检索:
- 自动检索通道
(Auto Memory Search):中间件在每次调用大模型前拦截,无条件执行语义检索,将结果伪装成 tool_call/tool_result注入消息 - 主动检索通道
(memory_search 工具):注册为 Agent 工具,由大模型自主判断是否需要检索
QwenPaw 的 RAG 哲学可以总结为一句话:
开关即意图,每轮自动检索,相关性交给检索引擎,取舍交给大模型。
3.2 我们的集成策略
QwenPaw 内部的 RAG 对接的是自己的长期记忆系统,并非外挂知识库。但它的设计思路给了我们重要启示:
- 用户显式打开开关 = 明确的意图表达
,无需再做复杂的意图识别 - 每轮提问前前置检索
,将资料注入上下文 - 检索相关性由 RagFlow 保证
,最终取舍由大模型完成
考虑到我们的场景是通过 QwenPaw 的公开 HTTP API(/api/console/chat)通信,无法像内部中间件那样注入 tool_call 块,因此采用提示词注入方式——将检索结果拼接为用户消息的一部分。
3.3 整体架构
plaintext
┌─────────────────────────────────────────────────────┐│ 前端 (Vue 3) ││ ││ 设置页:配置 RagFlow 地址 + API Key ││ 首页:知识库开关 → 选择知识库 → 发送时前置检索 │└──────────────────────┬──────────────────────────────┘│ HTTP (localhost:8000)┌──────────────────────▼──────────────────────────────┐│ 后端代理 (FastAPI) ││ ││ /api/ragflow/config — 保存/获取配置 ││ /api/ragflow/test — 测试连接 ││ /api/ragflow/datasets — 获取知识库列表 ││ /api/ragflow/retrieval — 执行语义检索 │└──────────────────────┬──────────────────────────────┘│ httpx (内网)┌─────────────┼─────────────┐▼ ▼┌─────────────────┐ ┌─────────────────────┐│ RagFlow 服务 │ │ QwenPaw AI 服务 ││ 知识库 + 检索 │ │ 对话 + 流式响应 │└─────────────────┘ └─────────────────────┘
为什么用后端代理而非前端直连 RagFlow?
- 避免 CORS 问题
——RagFlow 服务未配置跨域头 - 保护 API Key
——密钥不暴露给浏览器 - 与现有架构一致
——QwenPaw 的 Token 管理也走后端代理
四、开发过程
4.1 后端:5 个代理端点
在 FastAPI 后端新增 RagFlowConfig 数据模型和 5 个 API 端点:
classRagFlowConfig(Base):__tablename__ ="ragflow_config"id= Column(Integer, primary_key=True)base_url = Column(String(500), nullable=False)api_key = Column(String(500), default="")updated_at = Column(DateTime, default=datetime.utcnow)
端点设计:
/api/ragflow/config | ||
/api/ragflow/config | ||
/api/ragflow/test | ||
/api/ragflow/datasets | ||
/api/ragflow/retrieval |
关键设计细节:
- 保存即验证
POST /config保存前会先调 RagFlow 列知识库,连通失败直接拒绝保存 - API Key 脱敏
GET 返回时只显示末 4 位( ****dpc) - 超时控制
检索接口 30s 超时,列表接口 15s 超时
4.2 前端 API 层:ragflow.js
新建 ragflow.js,封装 8 个函数,其中最核心的是提示词拼接和历史还原:
// 将检索结果拼接为注入给大模型的提示词exportfunctionbuildRagPrompt(question, chunks, datasetNames){const refs = chunks.map((c, i)=>{const src = c.document?`(来源:${c.document})`:''return`[${i +1}]${src}\n${c.content.trim()}`}).join('\n\n')return(`【知识库参考资料】\n`+`以下为从知识库「${datasetNames}」检索到的相关资料,`+`请优先结合这些资料回答用户问题。`+`若资料与问题无关请忽略,不要编造资料中不存在的内容:\n\n`+`${refs}\n\n`+`【用户问题】\n${question}`)}// 历史回显时还原干净的用户问题exportfunctionstripRagPrompt(text){const idx = text.indexOf('【用户问题】')if(idx ===-1)return textreturn text.slice(idx +'【用户问题】'.length).trim()}
为什么需要 stripRagPrompt? 因为注入的提示词会被 QwenPaw 存入会话历史。当用户回看历史消息时,不应看到那一大段注入的参考资料,而应只看到自己原始的提问。通过标记包裹(【知识库参考资料】...【用户问题】...),前端可以在展示时精准剥离。
4.3 设置页:RagFlow 配置卡片
在设置页新增第三张配置卡片(前两张是 QwenPaw 连接和企业 Branding):
- 服务地址
输入框 - API Key
密码输入框(附创建指引提示) - 测试连接
按钮 → 调后端验证 - 保存配置
按钮 → 保存并自动列出知识库 - 知识库预览区
→ 显示已配置的知识库名称、分块数、文档数

4.4 首页:知识库开关与检索注入
这是用户体验的核心。在输入框上方添加了一个知识库工具条:
<divclass="kb-toolbar"><!-- 开关按钮 --><buttonclass="kb-toggle":class="{ active: kbEnabled }"@click="toggleKb">📚 知识库<spanv-if="kbEnabled">:开</span></button><!-- 知识库多选下拉 --><divv-if="kbEnabled"class="kb-dropdown-wrap"><buttonclass="kb-select-btn"@click="kbDropdownOpen = !kbDropdownOpen">{{ kbSelectedIds.length ? `已选 ${kbSelectedIds.length} 个` : '选择知识库' }} ▾</button><divv-if="kbDropdownOpen"class="kb-dropdown"><labelv-for="ds in kbDatasets":key="ds.id"class="kb-option"><inputtype="checkbox":value="ds.id"v-model="kbSelectedIds"/><span>{{ ds.name }}</span><span>{{ ds.chunk_count }} 分块</span></label></div></div><!-- 检索状态提示 --><spanv-if="kbRetrieving"class="kb-retrieving">📚 检索知识库中...</span></div>
发送消息时的检索注入流程:
asyncsendMessage(){let sendText = displayText// 开启知识库时:先检索 RagFlow,将资料注入发给大模型的文本if(this.kbEnabled&&this.kbSelectedIds.length){this.kbRetrieving=truetry{const result =awaitretrieveRagFlow(displayText,this.kbSelectedIds,5)if(result.chunks&& result.chunks.length){sendText =buildRagPrompt(displayText, result.chunks,this.kbSelectedNames)}}catch(e){console.warn('RagFlow 检索失败,降级为普通提问:', e)}this.kbRetrieving=false}// 将(可能注入了知识库资料的)文本发给 QwenPawawaitsendChatMessage(sendText, sessionId, callbacks, signal, attachments)}
优雅降级:如果 RagFlow 检索失败(网络超时、服务宕机等),不会阻断对话,而是静默降级为普通提问,仅在控制台输出警告。
五、测试验证
5.1 后端接口测试
5 个端点逐一通过 curl 验证:
POST /api/ragflow/config → 200 "配置已保存,连通正常(1 个知识库)"GET /api/ragflow/config → 200 {"configured": true, "api_key_masked": "****dpc"}POST /api/ragflow/test → 200 {"success": true, "message": "连接成功,共 1 个知识库"}GET /api/ragflow/datasets → 200 {"datasets": [{"name": "架构书籍", "chunk_count": 19}]}POST /api/ragflow/retrieval → 200 {"total": 3, "chunks": [...]}
5.2 浏览器端到端测试
通过 AI 助手操控浏览器完成全流程实测:
✅ 设置页 RagFlow 卡片正确渲染,状态显示"已配置" ✅ 知识库预览显示"📚 架构书籍 19分块/2文档" ✅ 首页"📚 知识库"开关按钮可点击切换 ✅ 下拉选择器正确列出知识库并支持多选 ✅ 发送问题时显示"📚 检索知识库中..."状态 ✅ 用户气泡标注"📚 引用知识库:架构书籍" ✅ 大模型基于检索资料流式作答(内容包含微服务架构定义、核心特征等) ✅ 控制台零报错,所有网络请求 HTTP 200





六、技术亮点与思考
6.1 "简单版"的哲学
最初考虑过两种方案:
选择方案 A 的理由:
- 用户意图明确
——开关本身就是意图表达,无需复杂的意图识别 - 可控性强
——用户知道什么时候在用知识库,什么时候没有 - 实现轻量
——不需要改动 QwenPaw 侧配置 - 可渐进升级
——后续如需更智能的触发,可在此基础上叠加
6.2 提示词注入 vs 工具注入
QwenPaw 内部用 tool_call/tool_result 伪装注入检索结果(模型更重视工具返回),但我们通过公开 API 只能传用户文本。提示词注入的效果如何?
实测表明,只要提示词结构清晰(用标记分隔参考资料和用户问题,并明确指示"优先结合资料回答"),大模型能很好地利用注入的知识库内容,且不会在资料无关时强行引用。
6.3 后端代理模式的价值
整个项目中,QwenPaw 和 RagFlow 的所有敏感操作都通过 FastAPI 后端代理:
- Token 管理
:后端自动登录 QwenPaw、5 天轮换 Token - API Key 保护
:RagFlow 密钥只存后端数据库,前端永远看不到明文 - 统一错误处理
:后端将各种异常统一转换为友好的中文提示 - CORS 无忧
:前端只与同源后端通信
七、代码规模与交付
backend/main.py | ||
frontend/src/api/ragflow.js | ||
frontend/src/views/Settings.vue | ||
frontend/src/views/Home.vue | ||
| 合计 | 4 个文件 | ~500 行 |
从需求提出到功能上线(含调研、设计、编码、测试),AI 辅助开发总耗时约 2 小时。
八、总结
这次 RagFlow 知识库集成的实践,验证了一个完整的 AI 辅助开发工作流:
- 需求描述
→ 用自然语言向 AI 表达意图 - 技术调研
→ AI 操控浏览器登录 RagFlow、验证 API、研读 QwenPaw 源码 - 架构设计
→ AI 分析现有代码结构,提出集成方案并给出理由 - 方案审批
→ 人类决策"先做简单版",AI 立即调整执行 - 代码实现
→ AI 逐模块编码,人类审查关键逻辑 - 自动测试
→ AI 操控浏览器完成端到端验证
最终交付的功能虽然"简单",但架构合理、体验流畅、可维护可扩展。用户只需在设置页配一次 API Key,之后在对话中一键开启知识库,AI 就能结合企业内部资料给出有据可依的回答。
这就是 AI 时代软件开发的缩影——人类负责决策和审美,AI 负责调研和实现。
夜雨聆风