「归戎筑行」是学校 2026 年”大思政实践课”的专项实践项目,面向退役大学生:把他们散在国家、省市区、校级三层的政策,加上同路人的经验,做成一个能搜、能问的站点。
技术含量不算高,没有算法层面的东西。下面只记几个我认为值得写的决定。
数据是怎么来的:抓包公众号 + 模拟请求
政策文件和推文没有现成接口,我的做法是抓包微信:
- 在客户端抓取公众号文章列表与正文的请求,拿到接口地址、必要的 header 和签名参数;
- 模拟请求复现这些调用,控制频率、加延时,避免给对端造成压力;
- 批量拉下来再清洗:去重、剥掉排版噪声、抽取「政策名称 / 文号 / 条款 / 生效日期 / 适用对象」,落成结构化 JSON。
最后得到 57 篇政策推文、16 条结构化政策。这一步花的时间比写页面多。
政策存到什么粒度
政策在入库时就拆到了条款级:每条带条款号、内容、文号、生效日期、适用对象。
这个粒度直接决定了后面 AI 能不能”引用”。整篇文本进去,模型只能转述大意;拆到条款级,它才有能力说”依据《XX 办法》第 X 条”——引用粒度是数据层决定的,提示词救不了。
AI 问答:一个不上向量库的轻量 RAG
检索这块我没有用 embedding,也没有向量数据库。数据量就是 16 条政策 + 22 份案例 + 若干官方问答,关键词召回够用;引入向量库意味着多一个服务、多一份索引运维,我认为不划算。
链路大致是:
提问 ↓ 中文关键词抽取:2 字以上片段 → 相邻二字组扩展 → 去停用词 → 截断 12 个 ↓ 三路检索打分:政策条款 / 官方问答 / 相似案例,各取 Top3 ↓ 分别注入 system prompt 的三个上下文槽位 ↓ DeepSeek 流式生成几个我认为真正有用的点:
- 三个槽位必须分开。政策、官方问答、案例是三种不同的证据,混成一个上下文块,模型很容易把案例当政策讲;
- 命中政策,注入条款,而不是注入整篇;
- 检索为空时,槽位里写”(未匹配到相关政策资料)“,而不是留空。留空时模型会退回预训练记忆硬答,明确告诉它”我没找到”,它才会走”资料不足”这条分支——这是调的时候踩出来的;
- 提示词把规则写死:必须引用文号和条款号;资料不足必须明说、严禁编造条款金额;涉军涉密的问题一律不答。
实际问”退役大学生考研有什么政策”,它会回带文号和条款号的引用。
没做的:没有 rerank,没有查询改写,也没上向量检索。不过 retrieveContext() 是唯一入口,将来换成向量或混合检索,前端和提示词都不用动。
其他几个选择
- 不上数据库:政策 16 条、案例 22 份,直接以 JSON 随构建打包,前端用 Fuse.js 做模糊搜索,AI 接口读同一份数据。这个量级下,数据库带来的运维成本大于收益;
- Next.js 16 + React 19 + TypeScript,
output: 'standalone'构建产物 + PM2 + nginx 反代,跑在一台 ECS 上; - 首页那三个数字(22+ 案例 / 57 篇推文 / 3 级传导发现)和 Sankey 图的数值,来自团队对三级主体(事务局 / 街道服务站 / 学生)的实地访谈。
没做好的
案例数字馆的页面在线上,但数据是空的。
22 份案例里任何细节组合(兵种 + 学院 + 年份)都可能指向具体的人,逐条复核脱敏需要的人力我们凑不出来。原始数据加密内部存档,公开页面留白——打开会看到”共 0 位同学”。
复盘的一句话结论:脱敏应该前置到数据采集阶段,而不是等整理完再补一轮全量审核。顺序错了,成本会翻几倍。
分工
平台的技术实现(前端、可视化、检索与 AI 接口、部署运维)是我一个人做的;调研、访谈、案例清洗和政策整理是全队三个人一起做的。