当前位置: 首页 > news >正文

SpeakCoach

项目地址:
https://github.com/elaysia-feng/SpeakCoach.git

SpeakCoach

AI 英语口语陪练 Agent。
浏览器 (React) → Java Spring Boot 网关 → Python FastAPI + LangGraph 工作流 →GPT-SoVITS 本地 TTS(可以下载代码下来将GTPSOVICE变成自己训练的语音模型, 实现和自己喜欢的角色训练)→ MySQL(长期记忆)+ Redis(LangGraph checkpoint)。


项目亮点

1. 4×4 网格化的「自定义角色」系统

SpeakCoach 把"AI 陪练老师"拆成两个独立维度,可以自由叠加:

  • 场景(Scene)×4interview(求职面试)、travel(出行旅游)、daily_chat(日常闲聊)、business(商务英语)。
  • 人设(Persona)×4warm_strictfriendly_tutorielts_examinerpatient_grandma

理论上一台部署就能组合出16 种不同人设的 AI 陪练——你既可以选"商务英语 × 雅思考官"做严肃模拟面试,也可以选"日常闲聊 × 奶奶"做零压力口语热身。

人设通过PERSONA_SYSTEM_ROLES字典注入到 LLM 的 system 消息头部,定义语气、词汇、句长、纠错风格,详见backend-python/app/workflow/prompts.py

2. 版本化的 Prompt 仓库

每个场景都有独立的 prompt 目录,按"角色"分文件并打版本号:

backend-python/app/prompts/ ├── interview/ # ability / challenge / grammar / hint / normal / shadow / strategy / summary (_v1, _v2) ├── travel/ # ability / grammar / shadow / strategy (_v1, _v2) ├── daily_chat/ # ability / grammar / shadow / strategy (_v1, _v2) ├── business/ # ability / grammar / shadow / strategy (_v1, _v2) └── ...
  • 场景优先prompts/{scene}/{role}_v{N}.txt找不到时回退到prompts/{role}_v{N}.txt,保证新场景不会因为缺文件而崩。
  • 版本优先render_prompt先找*_v2.txt,找不到回退到*_v1.txt。改 prompt 时只要新增一个*_v3.txt并把PROMPT_VERSION = "v2"改成"v3"即可灰度切换。
  • 占位符渲染string.Template.safe_substitute,缺失的${KEY}不会抛异常而是原样保留,方便排错。

3. GPT-SoVITS 本地 TTS + 静音兜底

synthesize(text, user_id, session_id, turn_id)是 TTS 客户端的唯一入口:

  • 文本 POST 到GPT_SOVITS_BASE_URL/tts,返回的 WAV 直接落盘storage/audio/{user_id}/{session_id}/turn_{n}.wav
  • 网络/服务故障时自动回退到一段 400 ms 的合法 PCM 静音 WAV,调用方永远能拿到可播放的文件,方便开发与冒烟测试。
  • 支持自定义参考音色:通过.env里的tts_ref_audio_path指向任何你训练好的 GPT-SoVITS 参考音频文件,就能让"奶奶"用你的声线说话。
  • 落盘布局是 Python 写入方与 JavaAudioController读取方之间唯一的事实来源(见audio_path_for/audio_url_for)。

4. 多 LLM Provider 热插拔

backend-python/app/llm_factory.py统一抽象:

  • openai(真正 OpenAI 或任意 OpenAI 兼容端点)
  • anthropic(Anthropic Claude)
  • minimax(MiniMax 网关,封装为 ChatAnthropic)
  • 任意 provider 缺 API key 时,自动降级为StubChatModel,让 workflow 在无 key 环境下也能跑通端到端测试。

切换 provider 只需在.envLLM_PROVIDER=,无需改业务代码。

5. LangGraph 长程记忆 + 可观测

  • thread_id = thread:{user_id}:{session_id}强保证每个用户的 checkpoint 隔离。
  • CHECKPOINT_BACKEND=memory|redis一键切换(默认内存,Redis 7+ 持久化)。
  • 每个 LangGraph 节点都写一条audit_log(输入 JSON / 输出 JSON / latency_ms / model_name / prompt_version),方便后续接 Java 的/internal/audit端点。

技术栈

选型端口
前端React 19 + Vite + TypeScript5173
网关 / 业务Spring Boot 3.3.4 + JDK 21 + Lombok 1.18.34 + MyBatis-Plus8080
AI 工作流FastAPI + LangGraph + langchain-openai / langchain-anthropic9000(内网)
TTSGPT-SoVITS(本地推理)9880
关系库MySQL 8.0(speakcoach库)3306
缓存 / CheckpointRedis 7+6379
鉴权JWT(HS256)

仓库布局

SpeakCoach/ ├── backend-java/ Spring Boot 3 网关 + 鉴权 + 业务 + AudioController ├── backend-python/ FastAPI + LangGraph 工作流 + GPT-SoVITS 客户端 │ └── app/prompts/ 版本化 prompt 仓库(场景 × 角色 × 版本) ├── frontend/ React + Vite + TypeScript ├── db/ MySQL 初始化脚本(init.sql) ├── storage/audio/ TTS 生成的 WAV 文件(按 userId/sessionId 分目录) ├── e2e/ 端到端冒烟测试 ├── .omc/ OMC 编排系统的 plan / handoff / research(不入版本库已 ignore) ├── AGENTS.md 架构 / 包图 / 服务契约(详细文档,先读它) └── README.md ← 你正在看这个

架构硬规则

  1. 前端只连 Java。Python 是内部服务,浏览器永远不直接打到:9000
  2. Java 在每个/api/chat/api/audio/**上都校验session.userId == currentUserId
  3. LangGraphthread_id永远是thread:{user_id}:{session_id},确保多用户 checkpoint 不串。
  4. 每个 LangGraph 节点都写audit_log(input/output JSON + latency_ms + model_name + prompt_version)。
  5. 音频由 Python 生成、落盘到storage/audio/,由 Java 鉴权后再返回,URL 形如/api/audio/{userId}/{sessionId}/turn_{n}.wav
  6. 必须 JDK 21(Lombok 1.18.34 + Spring Boot 3.3.4)。JDK 17 会IncompatibleClassChangeError,且崩溃时会在backend-java/留下hs_err_pid*.log
  7. 持久层是MyBatis-Plus(Mapper 继承BaseMapper<T>),不是 JPA。userId是自增BIGINTsessionId是 UUID 字符串。

前置依赖(原生本地安装,不依赖 Docker)

JDK 21 是硬性要求。Lombok 1.18.34 + Spring Boot 3.3.4 在 JDK 17 上会编译失败 / 运行时崩溃。
验证当前 JDK:

java-version# → openjdk version "21.x.x" ...

如果装了多个 JDK,把JAVA_HOME指向 JDK 21。

工具版本备注
Java21+java -version必须 21
Maven3.9+或用自带的./mvnw
Python3.11+推荐 venv
Node.js20+LTS
MySQL8.0+监听localhost:3306,库名speakcoach
Redis7+可选(生产/共享 checkpoint 才需要)
GPT-SoVITSlatest本地推理服务,.env里配 base URL

Windows 一键安装

# MySQLwinget install Oracle.MySQL# Redis(Windows 下的原生 Redis,推荐 Memurai)winget install Memurai.Memurai# MySQL 启动后导入 schemamysql-uroot-p < db/init.sql

启动整个栈

需要开 3 个终端:

# 终端 1 — Java 网关cdbackend-java# 确保 JDK 21 生效$env:JAVA_HOME="E:\develop\java\jdk-21"mvn clean compile-DskipTestsmvn spring-boot:run# → http://localhost:8080# 终端 2 — Python AI 服务cdbackend-python python-mvenv .venv .venv\Scripts\activate pipinstall-rrequirements.txt copy .env.example .env# 然后编辑填 keyuvicorn app.main:app--host0.0.0.0--port9000--reload# → http://localhost:9000(仅内网)# 终端 3 — React 前端cdfrontendnpminstallnpmrun dev# → http://localhost:5173

环境变量

backend-java/src/main/resources/application.yml

spring:datasource:url:jdbc:mysql://localhost:3306/speakcoachusername:rootpassword:YOUR_PASSWORDredis:host:localhostport:6379python:service:base-url:http://localhost:9000jwt:secret:change-me-in-production# 生产环境务必改

backend-python/.env

# LLM LLM_PROVIDER=openai # openai | anthropic | minimax LLM_MODEL=gpt-4o-mini OPENAI_API_KEY=sk-... # anthropic 用 ANTHROPIC_API_KEY,minimax 用 MINIMAX_API_KEY # TTS(GPT-SoVITS 本地推理) GPT_SOVITS_BASE_URL=http://localhost:9880 TTS_REF_AUDIO_PATH=./ref_voices/default.wav # 自定义角色音色的参考音频 # 音频落盘 AUDIO_STORAGE_PATH=../storage/audio AUDIO_BASE_URL=/api/audio # LangGraph checkpoint CHECKPOINT_BACKEND=memory # memory | redis REDIS_URL=redis://localhost:6379/0

frontend/.env

VITE_API_BASE=http://localhost:8080

测试

完整 curl 串在AGENTS.md§7。精简版:

# 1. 注册(MyBatis-Plus:email 必填,userId 自增 Long)curl-i-XPOST http://localhost:8080/api/auth/register\-H"Content-Type: application/json"\-d'{"username":"smoketest","email":"smoketest@e2e.local","password":"test1234"}'# → 201 {userId, username, email, token}# 2. 登录curl-i-XPOST http://localhost:8080/api/auth/login\-H"Content-Type: application/json"\-d'{"username":"smoketest","email":"smoketest@e2e.local","password":"test1234"}'# → 200 {...}# 3. /me 不带 / 带 tokencurl-ihttp://localhost:8080/api/auth/me# → 401curl-ihttp://localhost:8080/api/auth/me-H"Authorization: Bearer <token>"# → 200# 4. 建会话curl-i-XPOST http://localhost:8080/api/sessions\-H"Authorization: Bearer <token>"-H"Content-Type: application/json"\-d'{"scene":"interview"}'# → 201# 5. 对话(Java → Python :9000)curl-i-XPOST http://localhost:8080/api/chat\-H"Authorization: Bearer <token>"-H"Content-Type: application/json"\-d"{\"sessionId\":\"<sessionId>\",\"userText\":\"Hello, my name is John.\"}"# → 200# 6. 结束会话curl-i-XPOST http://localhost:8080/api/sessions/<sessionId>/finish\-H"Authorization: Bearer <token>"-H"Content-Type: application/json"\-d'{"summary":"Smoke test complete."}'# → 200# 7. 音频未鉴权访问应被拒curl-ihttp://localhost:8080/api/audio/1/<sessionId>/turn_1.wav# → 401 / 403

已知限制

完整列表见AGENTS.md§8,要点:

  • GPT-SoVITS 不可达时 TTS 自动回退到 400 ms 静音 WAV,保证调用方不抛异常。
  • 没配 LLM API key 时自动降级到StubChatModel,workflow 端到端可跑,回复是占位文本。
  • LangGraph 默认内存 checkpoint(保底机制,如果没有开启redis的话就默认为本地内存存储);想跨进程共享就设CHECKPOINT_BACKEND=redis
  • Maven 构建硬性要求 JDK 21(Lombok 1.18.34 / Spring Boot 3.3.4)。JDK 17 不行。
  • application.yml自带的数据库密码和 JWT secret 是开发占位,生产部署前必须覆盖

进度

  • 实现:端到端可跑通,Java 干净编译、Python 启动正常、所有内部端点响应正常、TTS 在 GPT-SoVITS 离线时回退静音
http://www.cnnetsun.cn/news/2819710.html

相关文章:

  • 实测揭秘:WPS双进程备份机制,内存占用真的高吗?手把手教你手动清理驻留进程
  • VMware网络感叹号?别急着重装!手把手教你修复VMnet1/VMnet8驱动代码31错误
  • 扫描阅卷机支持哪些格式的试卷?
  • 2、K8S网络概述
  • x64汇编案例5
  • SysConfig Device Support 笔记
  • VC6环境下内存直载DLL的完整可运行工程包(含源码、编译成品与测试模块)
  • ToxiTwitch:基于混合模型的Twitch实时聊天毒性检测
  • 新闻语义处理流水线:面向金融NLP的结构化解码与时序锚定
  • AI动态简报之商业洞察篇(2026.06.07)
  • 电机控制工程师必看:手把手教你配置TMS320F280049的SDFM模块进行电流采样
  • 【个人博客—山东大学项目实训——古诗词与文章智能创作助学平台(六)】
  • 生产级机器学习服务的三大支柱:可观测性、弹性和契约
  • AI实战第5篇:Python+DeepSeek智能简历优化器,HR看了直呼专业
  • 跨境支付业务流程
  • Sqribble文档自动化系统:模板驱动的结构化出版流水线
  • 别再只用System.out.printf了!Java格式化数字的三种姿势,从基础到实战一次讲透
  • ROS 2进阶:深入理解rosdep与package.xml的依赖关系,打造可复用的机器人软件包
  • Vue3 + Baidu Map API 实战:手把手教你实现一个带搜索和自定义弹窗的店铺地图
  • 多维聚合中的数据变形:从GROUP BY到高维视图的工程实践
  • 手机存储速度翻倍的秘密:一文看懂UFS 2.2里的M-PHY物理层(附避坑指南)
  • 告别黑盒:用dotPeek和Symbol Server在VS里一步步调试Newtonsoft.Json源码
  • AT24C02不止是存储:聊聊I2C总线上的设备地址与多机通信那点事
  • 你的V-SLAM为啥飘?从重投影误差的角度聊聊后端优化的那些坑
  • Logisim新手避坑指南:复用器、译码器、优先编码器到底怎么用?
  • 从IEBus到AVC-LAN:拆解丰田老车机里的“古董”通信协议与数据帧
  • 给CANoe DLL加个“耳朵”:手把手教你用Visual Studio 2019编写并调试回调函数
  • 从监控面板到服务治理:手把手教你用Dubbo-Admin管理微服务(附Docker部署彩蛋)
  • AD9831输出信号不过零点?一个电容或变压器轻松搞定(附Multisim仿真)
  • 告别玄学调试:用Process Monitor精准定位Qt+QAxObject加载COM组件的失败原因