最近不少朋友在问“想做一款能自动回答用户问题的微信小程序后端用 Python 接 AI到底怎么落地”正好我这边刚把一个基于 AI 的在线答疑系统从零到一跑通包含小程序前端、Python 后端、知识库检索和内容安全过滤今天把完整的拆解思路、核心代码和踩坑记录整理出来。这套方案适合三类人一是想给自己的课程或产品做智能问答助手的开发者二是正在准备毕设或实训项目的学生三是想了解“小程序 大模型 API”怎么结合的产品经理。我会尽量把每一步的“为什么这么做”也讲清楚而不是只贴代码。1. 项目整体设计与架构拆解1.1 这个答疑系统到底解决了什么问题传统的在线答疑通常靠人工客服或者论坛帖子的形式回答时效性差、重复问题多、人力成本高。答疑系统的核心目标是把用户经常问的高频问题交给 AI 自动回答人工只处理 AI 解决不了的部分。举个例子某高校的课程答疑平台每天收到几百条类似“作业截止日期”“考试范围”“实验报告格式”的问题这些问题答案固定、重复度高非常适合做成知识库让 AI 检索回答。基于这一需求我把系统拆成三个端用户通过微信小程序提问并查看答案Python 后端负责处理请求、调用 AI 接口、管理会话历史管理端可以是简单的 Web 页面负责维护知识库文档。这种三层结构的好处是每层职责单一后续替换 AI 服务商或者更换前端框架都不会互相影响。实际项目里我把小程序端仅作为展示层所有业务逻辑都收敛在后端这样也方便未来扩展成 App 或 Web 端。1.2 技术选型背后的取舍技术栈我最终选的是微信小程序原生框架 PythonFastAPI 大模型 API 轻量级向量检索。这套组合不是随便定的每个选择都有具体考量。先说小程序为什么不选 uni-app 或 Taro。我的目标是做一个功能中等、需要快速上线的答疑工具原生框架的调试工具最成熟、API 文档最全遇到问题搜到的解决方案最多。如果你要一套代码同时跑微信和支付宝小程序那 uni-app 更合适但我建议第一版别给自己加这种负担先把一个平台跑通。后端选 FastAPI 而不是 Flask主要看中三点异步支持好调用 AI 接口属于 IO 密集型操作异步能显著提高并发处理能力自带 Pydantic 参数校验避免手写大量的请求体判断自动生成 OpenAPI 文档调试接口非常方便。如果你的部署环境对版本要求苛刻或者团队只熟悉 Flask那 Flask 也能做但异步这块需要额外引入 asyncio 库稍微麻烦一些。AI 能力直接调用大模型开放平台的 API而不是自己训练模型。自己训练一个大模型需要的算力和数据量个人开发者基本扛不住而且答疑场景下模型的准确性远不如“知识库 大模型”的组合。大模型只负责理解问题和组织语言具体的领域知识从知识库检索获得这样既保证了回答的时效性又能控制成本。1.3 系统架构图与数据流向整个系统的请求链路是这样的用户在微信小程序输入问题小程序通过 wx.request 将问题 POST 到后端 /api/ask 接口后端拿到问题后先做内容安全检测再判断是走知识库检索还是直接交给大模型最后把回答结果返回给小程序渲染。用户的历史记录会存入 MySQL 数据库方便随时查看。知识库的构建路径则是管理员把 PDF、Word、TXT 等文档上传到管理端后端对文本进行清洗和切片通过 Embedding 模型转成向量存入向量数据库。每次用户提问时后端把问题转成向量在库里检索最相关的几个文本片段拼接到系统提示词中交给大模型模型再基于这些材料生成最终回答。数据库选型上MySQL 存储用户信息、提问记录和反馈向量数据库负责知识库检索。前期数据量不大时用轻量级的向量方案完全够没必要一上来就上分布式搜索引擎这是很多新手容易犯的过度设计毛病。2. 小程序端核心模块从提问入口到答案渲染2.1 页面结构设计与跳转流程小程序的页面我控制在五个以内首页提问框 推荐问题、回答页流式展示答案、历史记录页、我的页面、管理后台入口仅管理员可见。页面少的好处是审核容易通过而且用户学习成本低。首页的提问框是整个产品的心脏需要注意几个细节输入框要有防抖处理用户在 300 毫秒内快速多次点击提交按钮时只发送一次请求提交后按钮进入 loading 状态禁止重复提交输入内容为空时给出提示前后端都要校验。推荐问题列表我用的是硬编码数据这些数据来自后台统计的高频问题每周更新一次即可没必要做成动态接口。回答页我采用“先显示问题再逐步渲染答案”的方式避免用户等待时面对空白屏。小程序端没有原生的流式渲染能力我实现了一个相对简单但效果不错的方案后端一次性返回完整回答前端通过 JS 定时器配合字符串截取模拟打字机效果。这个方案虽然不如真正的 Server-Sent Events 流式返回流畅但胜在实现简单、稳定用户体验也不差。2.2 请求封装与本地缓存策略小程序的网络请求不能裸用 wx.request我封装了一个 request.js 模块统一处理 BASE_URL 配置、请求超时、状态码判断、错误提示。核心代码长这样// utils/request.js const BASE_URL https://api.example.com // 换成你自己的域名 function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method: method, data: data, header: { Content-Type: application/json }, timeout: 30000, success: (res) { if (res.statusCode 200) { resolve(res.data) } else { wx.showToast({ title: 服务异常请稍后重试, icon: none }) reject(res) } }, fail: (err) { wx.showToast({ title: 网络连接失败, icon: none }) reject(err) } }) }) } module.exports { request }这里用 Promise 包装是因为 async/await 在小程序基础库里的支持情况比较微妙Promise 方案最保险。超时时间我设了 30 秒因为 AI 接口响应通常在 2 到 10 秒之间太短会频繁超时太长用户会等得不耐烦。历史记录用 wx.setStorageSync 存在本地数据结构是 JSON 数组每条包含 question、answer、timestamp 三个字段。这里有个坑本地存储容量限制是 10MB如果用户提问特别多数组会撑爆。我的解决方案是只保留最近 50 条记录超出部分自动丢弃并在用户进入历史页时通过分页接口拉取更多。2.3 录音提问和图片识别的扩展预留答疑场景里用户可能在移动端不方便打字所以我在第一版就预留了语音入口。小程序端通过 wx.getRecorderManager() 录音然后把音频文件上传到后端后端调用语音转文字服务变成文本再走正常的提问链路。图片识别稍微复杂一些涉及拍照上传、OCR 文字识别、再进入知识库检索。不少学生问的是“这道题怎么做”如果能把题目拍照上传识别文字后匹配相似题体验会提升一个档次。不过这两块功能工作量不小我建议你第一版先不做等文本问答稳定运营后再迭代但前端页面可以把入口先埋好避免后期改版。3. Python 后端核心实现API 设计、数据库与安全3.1 FastAPI 项目结构搭建后端项目我采用分模块结构不做大杂烩单文件project/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置管理 │ ├── models/ # 数据库模型 │ ├── routers/ # 路由文件 │ ├── services/ # 业务逻辑层 │ └── utils/ # 通用工具 ├── requirements.txt └── .env # 环境变量为什么要分模块因为答疑系统后续一定会扩展功能比如新增用户反馈接口、管理端接口如果所有路由都写在 main.py 里几百行代码挤在一起排查问题会非常痛苦。分模块之后每个业务域一个路由文件新人接手也能快速定位。main.py 的主逻辑很简单创建 FastAPI 实例、加载 CORS 配置、注册路由、启动时初始化数据库# app/main.py from fastapi import FastAPI from app.routers import ask, history, feedback, knowledge from app.utils.database import init_db app FastAPI(titleAI 答疑系统 API, version1.0.0) app.include_router(ask.router, prefix/api) app.include_router(history.router, prefix/api) app.include_router(feedback.router, prefix/api) app.include_router(knowledge.router, prefix/api) app.on_event(startup) async def startup_event(): init_db()需要注意 CORS 配置这次其实用不上因为小程序请求后端不受浏览器同源策略限制但我还是加上了方便以后要用 Web 管理端调试。运行时用 uvicorn 启动指定 host 为 0.0.0.0 才能让局域网访问到。3.2 /api/ask 接口的完整实现逻辑这是全系统最核心的接口我把它做成 POST 请求接收 JSON 参数question 用户问题、session_id 会话标识、user_id 可选。处理流程分成四步参数校验、安全检测、生成回答、保存历史。完整代码片段如下# app/routers/ask.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel from app.services.ai_service import generate_answer from app.services.security import check_content_safety from app.models.history import save_history from app.utils.cache import get_cache, set_cache router APIRouter() class AskRequest(BaseModel): question: str session_id: str user_id: str router.post(/ask) async def ask(req: AskRequest): # 1. 基础校验 if not req.question or len(req.question.strip()) 2: raise HTTPException(status_code400, detail问题内容太短) if len(req.question) 500: raise HTTPException(status_code400, detail问题长度超出限制) # 2. 内容安全检测 if not check_content_safety(req.question): raise HTTPException(status_code400, detail内容包含不合适的信息) # 3. 缓存查找完全相同的题目直接返回 cache_key fqa:{req.question} cached get_cache(cache_key) if cached: return {answer: cached, source: cache} # 4. 调用 AI 服务 answer await generate_answer(req.question) # 5. 写缓存和历史记录 set_cache(cache_key, answer, ttl3600) save_history(req.user_id, req.session_id, req.question, answer) return {answer: answer, source: ai}缓存这一层很多人会忽略但它对降低成本和提升响应速度立竿见影。答疑场景中重复问题比例相当高同一个问题在一天内被问几十次很常见加了 Redis 缓存后这些问题不再消耗大模型 API 的调用次数每个缓存命中基本是毫秒级返回。3.3 用户身份识别与 openid 获取小程序没有传统的用户名密码体系用户身份靠微信的 openid 来区分。小程序端通过 wx.login 获取临时 code后端拿到 code 后向微信接口换取 openid# app/services/wx_auth.py import httpx async def get_openid(code: str): appid config.WX_APPID secret config.WX_SECRET url fhttps://api.weixin.qq.com/sns/jscode2session?appid{appid}secret{secret}js_code{code}grant_typeauthorization_code async with httpx.AsyncClient() as client: resp await client.get(url) data resp.json() if openid in data: return data[openid] return None换取 openid 的接口有频率限制所以我会在后端做一个简单的内存缓存同一个 code 只允许换取一次避免恶意请求刷接口。拿到 openid 后可以查询用户表确认是否为新用户新用户自动注册一条记录。这里要特别提醒appid 和 secret 绝不能写在小程序前端代码里必须存在后端环境变量中。一旦泄露别人可以冒充你的小程序获取用户信息这是一个严重的安全隐患。3.4 数据库表结构与模型设计答疑系统的核心表我设计了三张用户表、问答历史表、反馈表。用户表字段包括 id、openid、nickname、avatar_url、created_at问答历史表包括 id、user_id、session_id、question、answer、sourceai/cache/manual、created_at反馈表包括 id、history_id、rating1 或 2、comment、created_at。用 SQLAlchemy 定义模型的代码不复杂关键点在于索引的设计。问答历史表的 user_id 和 created_at 字段要加联合索引因为用户查看历史的查询条件是“某个用户的所有记录按时间倒序”没有索引的话数据量一大查询就会变慢。反馈表的意义容易被低估。AI 回答并不总是正确的没有反馈机制你很难知道哪些问题答得不好。我上线后每天会分析 rating 为 1不认可的记录把这些问题的回答结果挑出来人工复核改进知识库或调整提示词这是系统能持续变好的关键一环。4. AI 回答与知识库检索的整体策略4.1 各种 AI 接入方式的取舍实现 AI 答疑有三种路线纯大模型直接回答、知识库增强生成RAG、微调专用模型。我最终选了第二种现在详细说说为什么。纯大模型直接回答最省事把问题丢给模型就能得到答案。但有两个致命问题一是通用大模型对你业务领域的具体知识几乎一无所知比如它不知道“某门课程期中考试的具体安排”回答只能是模糊的常识性建议二是回答内容不可控可能包含无关信息。答疑场景要求准确、简洁、可溯源纯模型路线达不到。微调模型成本太高需要准备大量高质量的“问题-答案”对还要算力资源做训练适合有强大数据积累的大团队。对绝大多数中小型答疑项目这是杀鸡用牛刀。最合理的路线就是 RAG用 Embedding 模型把你的知识库文档向量化提问时检索最相关的片段把这些片段作为参考资料塞进提示词让大模型基于这些资料作答。这样做的好处是答案有据可依还能通过控制检索范围限制模型发挥空间。4.2 知识库构建清洗、切片与向量化知识库的质量直接决定回答质量。原始文档通常是 PDF 或 Word里面可能夹杂页眉页脚、表格、水印直接切片会产生大量噪音。我的清洗流程分四步抽取出纯文本去掉页眉页脚和水印文字统一换行和空格检测并修正乱码内容。切片策略是 RAG 里的关键细节。切片太小比如一句话一个 chunk检索到的内容缺乏上下文模型看不懂切片太大比如整篇文档一个 chunk检索精度下降、token 消耗暴增。经过多轮测试我最终把切片的默认值设为 500 个字符重叠区域为 50 个字符。重叠的意义在于保证一句话不会被两个切片生硬截断降低信息丢失的概率。对于不同文档类型可以调整参数比如代码类的知识库可以按函数块切分。切片完成后通过 Embedding 接口把每个切片转成向量存入向量数据库。查询阶段的流程是把用户问题转成向量用余弦相似度算法找出最相似的 Top 3 到 Top 5 个切片把它们与系统提示词拼接。专门的向量数据库能轻松支撑百万级向量检索小规模项目用轻量级方案就行。4.3 提示词设计的三个层次我在实际调试中发现提示词写得差再好的知识库也发挥不出来。答疑场景的提示词我分了三层来设计系统层、示例层、资料层。系统层提示词定义 AI 的角色和边界我的模板大致是你是一个专业的在线答疑助手负责回答用户在学习过程中遇到的问题。 请基于以下资料回答问题回答要简洁、准确、友好。 如果资料中没有明确答案请如实说明“资料中暂时没有找到相关答案”不要编造。 回答控制在 200 字以内。示例层是可选的如果你有知识库外的少量高频问答对可以作为 few-shot 例子加进去帮助模型理解“什么样的回答风格是好的”。资料层就是把检索到的切片内容以“参考资料”的形式放在提示词末尾。这三个层次加载同一个请求里既限制模型的发挥范围又给它足够的上下文。调用大模型时还有几个参数值得注意temperature 设置为 0.2 左右太接近 1 回答会飘太接近 0 会显得机械max_tokens 设为 500防止冗长答案消耗 tokentop_p 配合 temperature 使用一般保持默认即可。不同的开放平台参数细节有差异但思路是通用的。4.4 内容安全与兜底方案做答疑系统必须面对一个问题用户可能问超出知识范围甚至不适合回答的问题。我在安全模块做了两层防护。第一层是输入检测通过敏感词库规则加分类模型的方式判断用户问题是否包含不合适的内容命中则直接拒绝并返回友好提示。第二层是输出检测大模型生成的回答同样要过一遍敏感词检测。兜底方案指的是知识库没有相关答案时的处理逻辑。我的策略是先看问题是否属于问候语或闲聊比如“你好”“谢谢”这种情况不检索知识库直接让大模型基于通用常识友好回应如果是正经问题但知识库没命中就返回“这个问题我暂时无法解答请稍后咨询人工客服”并把该问题自动记录到“未命中问题表”方便管理员每周复盘补充知识库。5. 上线前必须做的优化与自查清单5.1 接口并发控制与限流AI 接口的调用成本不低如果小程序上线后被恶意刷接口一天下来账单可能让人崩溃。限流方案我在后端加了一个简单的令牌桶按用户维度限制每分钟最多 10 次提问每个 IP 每天最多 200 次提问。实现不复杂用内存字典就能完成单机限流多实例部署时把计数放到 Redis 里。# app/utils/rate_limiter.py import time from collections import defaultdict class RateLimiter: def __init__(self, max_requests, period): self.max_requests max_requests self.period period self.records defaultdict(list) def allow(self, key): now time.time() self.records[key] [t for t in self.records[key] if now - t self.period] if len(self.records[key]) self.max_requests: self.records[key].append(now) return True return False当然这只是最基础的保护如果要更严谨可以引入滑动窗口或分布式限流框架。但实话实说对于中小型答疑项目令牌桶加人机验证已经足够挡掉绝大多数恶意流量。5.2 小程序审核需要注意的坑小程序审核比普通网页严格得多我在审核阶段被打回过两次总结出这几个必须提前处理的点一是类目选择答疑问答类一般选“教育-在线教育”或“工具-信息查询”选错类目可能导致审核拒绝二是用户协议和隐私政策小程序内必须能打开这两个文档的页面讲清楚收集了哪些信息、如何保护三是内容安全审核人员会重点测试敏感词拦截功能如果发现用户能发送并得到不适合的信息直接拒绝。还有一条容易被忽略的规则小程序的 request 合法域名必须 HTTPS且在微信公众平台后台配置。如果忘了配置开发工具里调试时能请求成功但手机上真机预览时会报“不在以下 request 合法域名列表中”。解决方法是先关闭开发工具的域名校验但发布前一定要把正式域名配好同时确保证书有效。5.3 成本控制策略Token 用量的监控与优化大模型 API 按 token 计费日调用量上来后费用不可忽视。我做了三件事来控制成本缓存热点问题、限制单次生成长度、设置每日预算告警。热点缓存前面说过了效果最明显基本能省掉 30% 的 token 消耗。max_tokens 的设置也很有讲究。太多会让用户等得久、花得多太少又可能回答不完整。综合各类问题的回答长度分布我最终把 max_tokens 设在 500。这个值和温度、top_p 等参数配合既保证了大部分问题能回答完整又防止回答过于啰嗦。每日预算告警我采用简单的方式在存储层记录每日的 token 消耗每完成一次请求就累加消耗超过预设阈值时通过邮件或者企业微信机器人通知管理员。管理员收到告警后可以临时调低单用户限流频率或者检查是否存在异常调用避免月底账单失控。6. 常见问题与排查技巧实录6.1 小程序真机请求失败这是新手上线时遇到最多的问题。报错信息五花八门但本质原因就几类域名没有配置到微信公众平台的服务器域名列表域名没有备案或没有 HTTPS开发工具里勾选了“不校验合法域名”换到真机就失效。排查思路是先看报错信息里是否包含“url not in domain list”如果有就是域名配置问题如果提示 SSL 证书错误那就要检查证书链是否完整。这里需要留意微信的域名校验是支持端口号的但只支持 80 和 443其他端口一律拒绝。如果后端部署在 8000 端口要么通过 Nginx 反向代理到 443要么换成 443。我前期为图省事直接用 8000 端口联调真机测试时吃了不少苦头。6.2 AI 回答内容截断和乱码内容截断最常见的原因是 max_tokens 设置太小模型生成到一半被迫停止回答停在半句话上。排查方法是在后端日志里打印完整的 model response看返回的 finish_reason 字段如果是 length就是 token 上限问题如果是 stop则是正常结束。解决方案有两种调大 max_tokens或者在提示词里明确要求“回答控制在 200 字以内”。建议两个都做前端展示更可控后端成本也更低。乱码问题多出在编码格式上。之前一次部署到 Windows 服务器时Python 读取知识库文件默认使用系统编码中文全部变成问号。解决方法是打开文件时显式指定 encodingutf-8数据库连接也统一设置 charsetutf8mb4。这些坑在本地开发时未必暴露一旦换环境就冒出来所以项目从一开始就要把编码写死别依赖系统默认值。6.3 历史记录丢失历史记录丢失去查两个地方小程序本地存储和后端数据库。本地存储的问题通常是你在 storage 里存了一个超大 JSON 对象超过 10MB 后写入失败。另一个常见原因是用户更换设备本地记录不会自动同步。我的做法是本地存储只做展示缓存每次进入历史页时通过接口拉取后端数据以服务端数据为准。这样即使本地缓存被清用户仍然能通过接口看到历史记录。6.4 知识库检索不到相关内容检索不到内容通常是下面几个原因叠加造成的切片粒度太大导致语义混淆、Embedding 模型处理特定领域词汇的效果不佳、知识库文档本身没有覆盖用户问题。排查时可以先把检索到的切片内容打印出来看看相似度分数是不是明显偏低。如果分数低试着把切片的 overlap 调大一些比如从 50 调到 100如果检索到了但模型没用好这些内容那要重点检查提示词里资料层的格式确保模型能清楚地看到“以下是参考资料”的边界标记。6.5 热门问题速查表我把高频排查项整理成一张表方便你直接对照症状可能原因解决动作真机请求报域名错误后台未配置合法域名在小程序管理后台添加 HTTPS 域名接口偶发超时AI 接口响应慢前端增加重试 后端打印耗时日志分析瓶颈相同问题回答不一致未设置缓存 / 温度过高加缓存 温度调低到 0.2知识库答案和问题无关切片粒度不合理调整 chunk_size 和 overlap用户重复提交前端未做防抖按钮添加 loading 状态并禁用7. 后续扩展方向第一版稳定运行后值得做的扩展有这么几个方向一是提问形式的多模态化支持拍照提问、语音提问前者对学习类场景帮助巨大二是知识库的自动化更新当问答历史里出现新的高频未命中问题时自动提醒管理员补充文档而不是等人发现问题三是管理面板的完整化目前知识库的上传和编辑还需要手动操作数据库可以开发一个简单的 Web 界面让运营人员自己维护。四是接入人工无缝切换当 AI 判断无法回答或用户主动发起转人工时自动创建工单分发给对应客服人员这就从自动答疑升级成了完整客服系统的核心模块。从架构角度来说现在这套代码在扩展这些能力时不需要推翻重来新的能力只要能对接上现有的服务层和数据库就能平滑融入所以前期分层的设计省了大量后期重构的时间。我个人在搭建这套答疑系统时踩过不少坑最大的体会是AI 答疑系统的难点从来不只是“调用一个聪明的大模型”而是知识库怎么组织、答案怎么可控、内容怎么安全合规。你要是自己动手做建议先把知识库这块做扎实再花心思调提示词最后才去考虑花哨的交互。先让 AI 能回答得准再让它回答得快这个顺序千万不要颠倒。
阅读完成 · 觉得有帮助?