手搓一个AI心理测评工具:FastAPI + DeepSeek + Streamlit 实战

从想法到上线,一个人一周做完的 AI 小产品


背景

两周前一个做心理咨询的朋友问我:"能不能做个工具,让用户先跟AI聊聊天,再填标准量表,自动出评估报告?"

需求很清晰:

  1. AI对话评估 --- 用户像跟咨询师聊天一样描述状态,AI 引导式提问
  2. 标准量表 --- PHQ-9、GAD-7、SDS、SAS 四大临床量表,自动评分出报告
  3. 危机干预 --- 检测到自伤/自杀关键词或量表高分,立即弹热线
  4. 隐私 --- 全部本地部署,数据不留存

于是花了一周,做了个叫「心晴助手」的东西。今天把完整的做法和踩坑记录下来。


一、技术选型为什么这么搭

需求 选型 理由
API 层 FastAPI 异步原生,自动生成 OpenAPI 文档,pydantic 校验
AI 对话 DeepSeek API 便宜(百万token不到1块),兼容 OpenAI 格式,随时能换
前端 Streamlit 纯 Python 搞定 UI,不用写 HTML/JS/Vue
量表数据 JSON 文件 新增量表只需加一个 JSON 文件,零代码改动
部署 Docker Compose 用户拉下来 docker compose up -d 就能跑

这套组合最大的好处是:一个人能全栈搞定


二、核心架构

javascript 复制代码
┌─────────────┐     HTTP API      ┌──────────────┐
│  Streamlit  │ ────────────────► │   FastAPI    │
│  前端:8501   │                   │  后端:8001   │
└─────────────┘                   └──────┬───────┘
                                         │
                               ┌─────────┴─────────┐
                               │   chat_agent.py    │──► DeepSeek
                               │   (对话+危机检测)    │
                               ├───────────────────┤
                               │    engine.py        │
                               │   (量表评分引擎)     │
                               └─────────┬─────────┘
                                         │
                                    ┌────▼────┐
                                    │ scales/ │
                                    │ JSON量表 │
                                    └─────────┘

API 设计

一共 5 个端点,很克制:

python 复制代码
GET  /api/scales              # 量表列表
GET  /api/scales/{id}         # 量表详情(含题目)
POST /api/scales/{id}/score   # 提交答案,返回评分
POST /api/chat                # AI 对话
POST /api/summary             # 对话结束后生成评估摘要

为什么不做流式?做了,/api/chat/stream 用 SSE 返回,但 Streamlit 的 st.chat_message + 流式渲染在 1.28 版本后体验才够好。非流式在 DeepSeek 下响应时间 1-2 秒,能接受。


三、量表引擎:最花心思的部分

评分逻辑

四种量表评分方式不一样,engine.py 统一处理:

python 复制代码
# PHQ-9 / GAD-7:直接看原始分
# 0-4 无,5-9 轻,10-14 中,15-21/27 重

# SDS / SAS:先算粗分,转指数
# 指数 = 粗分 / 80 × 100
# 按指数判定:0-49 无,50-59 轻,60-69 中,70-100 重

反向计分,最容易写错的地方

SDS 有 10 道反向题(如"我感到早晨心情最好"------选"没有"反而是抑郁),SAS 有 5 道。

python 复制代码
# engine.py
if qid in reverse_items:
    raw_score = 5 - raw_score

公式是 5 - raw_score,因为选项是 1-4 分。如果选"没有或很少时间"(分=1),反向后变成 4。验证时我算了一遍:

arduino 复制代码
Q2 "我感到早晨心情最好" → 选"小部分时间"(分=2)
→ 反向计分 5-2=3(分数越高越抑郁 ✓)

危机预警双层防御

arduino 复制代码
对话层 → check_crisis("想死", "自杀"...) → 匹配则返回热线
量表层 → PHQ-9 第9题 > 0 → alert_rules 弹出危机提示

不依赖 AI 判断,纯规则匹配,宁可误报不可漏报


四、AI 对话:提示词设计

System Prompt

核心就一个原则:不要让他觉得自己在被审问

diff 复制代码
你是一位专业的心理评估师,名叫"心晴助手"。
- 通过自然对话了解用户的心理状态
- 使用情绪、睡眠、社交、压力、自我认知 5 个维度评估
- 语气温和、共情、不评判
- 不要下诊断,只用"可能"、"初步判断"等措辞
- 每次回答控制在 100-200 字
- 自然地引导对话,不要像审问一样连续提问

实测效果:用户说"最近睡不好",AI 回复不是直接甩量表,而是"听起来你最近压力比较大,能具体说说失眠的表现吗?比如入睡困难还是容易醒?"

评估摘要 Prompt

对话 4 轮以上后,用户可以点"生成评估报告"。这里有个坑------最开始我直接复用 chat() 函数,结果 AI 继续以咨询师角色回复,而不是生成摘要。

修复 :新增独立的 summarize() 函数,不注入对话系统提示词:

python 复制代码
# 新函数,不含 SYSTEM_PROMPT
async def summarize(messages, system_prompt):
    full_messages = [{"role": "system", "content": system_prompt}] + messages
    # ... 直接调 LLM API

五、踩坑记录

1. Streamlit 的 rerun 陷阱

量表答题是逐题进行的,用户选一题 → 存 session_state → st.rerun() → 显示下一题。但 st.rerun() 后所有未缓存的请求会重新执行,如果不小心在 rerun 前调了 API,后端会被刷爆。

解决 :用 if qid in st.session_state.scale_answers: continue 跳过已答题。

2. 路径问题

一开始 SCALES_DIR 写的是 os.path.join(os.path.dirname(__file__), "..", "scales")。这在开发环境没问题,但 Docker 打包后 __file__ 的路径变了,量表加载失败。

解决 :支持 SCALES_DIR 环境变量覆盖,fallback 到约定路径。

3. PHQ-9 第 8 题的笔误

原 JSON 数据里第 8 题是"动作或说话速度缓慢到别人已经察觉?或正好相反------ Loss 得比平常更多"。这个 Loss 明显是翻译残留。改了。

4. LLM 调用没有重试

最开始直接 httpx.post(),没有重试。DeepSeek 偶尔会有 5xx 或超时,用户对话到一半报错很劝退。

解决 :用 tenacity 加 3 次重试,指数退避,全部失败后返回友好提示。


六、运行效果

ruby 复制代码
$ curl -X POST http://localhost:8001/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"最近睡不好,心情也很低落"}]}'

{
  "reply": "听起来你最近状态不太好,能具体说说吗?比如睡不着是因为脑子里想事情,还是身体上不舒服?"
}

PHQ-9 答题结果示例(总分 8,轻度抑郁):

erlang 复制代码
📊 PHQ-9 评估结果
总分:8
等级:轻度抑郁
建议:可能有轻度抑郁情绪。建议关注自身情绪变化...

七、Docker 一键部署

bash 复制代码
git clone https://github.com/tangwenqing123/mood-assistant
cd mood-assistant
cp backend/.env.example backend/.env
# 编辑 .env 填入 DeepSeek API Key
docker compose up -d
# 前端 http://localhost:8501

整个镜像加起来 600MB(Python 3.11-slim + FastAPI + Streamlit),不算大。


八、复盘

做得好的:

  • 量表数据 JSON 配置化,加新量表零代码
  • 危机检测双层防御
  • 前后端分离,API 可复用

可以更好的:

  • 没有测试覆盖(纯手测)
  • 对话历史存在 Streamlit session_state 里,刷新就丢
  • 没有用户系统(当前就是匿名使用)

项目地址:github.com/tangwenqing... 欢迎 Star,欢迎 PR,欢迎拿去改造成自己的版本。

相关推荐
先锋部队1 小时前
移动端 H5 接 AI 对话,软键盘弹起把输入框顶飞了
人工智能
weixin_397574091 小时前
企业智能体平台部署上线全流程:从环境搭建到智能体配置实操
人工智能
QZ166560951591 小时前
动态感知·全覆盖管控·符合司法要求:通用行业知形数据库风险监测合规落地方案
大数据·人工智能
Kobebryant-Manba2 小时前
深度学习时候d2l报错和使用问题
人工智能·深度学习
HackTwoHub2 小时前
Sqli-Scanner SQL注入SKILL自动化挖掘SQL注入,零依赖自动化SQL注入挖掘,赏金猎人
数据库·人工智能·sql·web安全·网络安全·自动化·系统安全
GEO优化小助手2 小时前
2026临沂GEO优化公司实测解析:3家本土机构适配性参考
大数据·人工智能·python
NeilYuen2 小时前
gRPC结合FAISS构建AI助手语义缓存模块(一):设计
人工智能·缓存·faiss
unique2 小时前
AI Coding 工具使用监控 — 市场竞品调研报告
人工智能·ai编程
环球科讯2 小时前
爱征信 惠民生 促发展——建行江西省新余市分行开展征信知识进商户宣讲活动
人工智能