SpeakCoach

SpeakCoach 项目地址https://github.com/elaysia-feng/SpeakCoach.gitSpeakCoachAI 英语口语陪练 Agent。浏览器 (React) → Java Spring Boot 网关 → Python FastAPI LangGraph 工作流 →GPT-SoVITS 本地 TTS可以下载代码下来将GTPSOVICE变成自己训练的语音模型 实现和自己喜欢的角色训练→ MySQL长期记忆 RedisLangGraph checkpoint。项目亮点1. 4×4 网格化的「自定义角色」系统SpeakCoach 把AI 陪练老师拆成两个独立维度可以自由叠加场景Scene×4interview求职面试、travel出行旅游、daily_chat日常闲聊、business商务英语。人设Persona×4warm_strict、friendly_tutor、ielts_examiner、patient_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 兼容端点anthropicAnthropic ClaudeminimaxMiniMax 网关封装为 ChatAnthropic任意 provider 缺 API key 时自动降级为StubChatModel让 workflow 在无 key 环境下也能跑通端到端测试。切换 provider 只需在.env改LLM_PROVIDER无需改业务代码。5. LangGraph 长程记忆 可观测thread_id thread:{user_id}:{session_id}强保证每个用户的 checkpoint 隔离。CHECKPOINT_BACKENDmemory|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-Plus8080AI 工作流FastAPI LangGraph langchain-openai / langchain-anthropic9000内网TTSGPT-SoVITS本地推理9880关系库MySQL 8.0speakcoach库3306缓存 / CheckpointRedis 76379鉴权JWTHS256—仓库布局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 ← 你正在看这个架构硬规则前端只连 Java。Python 是内部服务浏览器永远不直接打到:9000。Java 在每个/api/chat和/api/audio/**上都校验session.userId currentUserId。LangGraphthread_id永远是thread:{user_id}:{session_id}确保多用户 checkpoint 不串。每个 LangGraph 节点都写audit_loginput/output JSON latency_ms model_name prompt_version。音频由 Python 生成、落盘到storage/audio/由 Java 鉴权后再返回URL 形如/api/audio/{userId}/{sessionId}/turn_{n}.wav。必须 JDK 21Lombok 1.18.34 Spring Boot 3.3.4。JDK 17 会IncompatibleClassChangeError且崩溃时会在backend-java/留下hs_err_pid*.log。持久层是MyBatis-PlusMapper 继承BaseMapperT不是 JPA。userId是自增BIGINTsessionId是 UUID 字符串。前置依赖原生本地安装不依赖 DockerJDK 21 是硬性要求。Lombok 1.18.34 Spring Boot 3.3.4 在 JDK 17 上会编译失败 / 运行时崩溃。验证当前 JDKjava-version# → openjdk version 21.x.x ...如果装了多个 JDK把JAVA_HOME指向 JDK 21。工具版本备注Java21java -version必须 21Maven3.9或用自带的./mvnwPython3.11推荐 venvNode.js20LTSMySQL8.0监听localhost:3306库名speakcoachRedis7可选生产/共享 checkpoint 才需要GPT-SoVITSlatest本地推理服务.env里配 base URLWindows 一键安装# MySQLwinget install Oracle.MySQL# RedisWindows 下的原生 Redis推荐 Memuraiwinget install Memurai.Memurai# MySQL 启动后导入 schemamysql-uroot-p db/init.sql启动整个栈需要开 3 个终端# 终端 1 — Java 网关cdbackend-java# 确保 JDK 21 生效$env:JAVA_HOMEE:\develop\java\jdk-21mvn 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.ymlspring: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_PROVIDERopenai # openai | anthropic | minimax LLM_MODELgpt-4o-mini OPENAI_API_KEYsk-... # anthropic 用 ANTHROPIC_API_KEYminimax 用 MINIMAX_API_KEY # TTSGPT-SoVITS 本地推理 GPT_SOVITS_BASE_URLhttp://localhost:9880 TTS_REF_AUDIO_PATH./ref_voices/default.wav # 自定义角色音色的参考音频 # 音频落盘 AUDIO_STORAGE_PATH../storage/audio AUDIO_BASE_URL/api/audio # LangGraph checkpoint CHECKPOINT_BACKENDmemory # memory | redis REDIS_URLredis://localhost:6379/0frontend/.envVITE_API_BASEhttp://localhost:8080测试完整 curl 串在AGENTS.md§7。精简版# 1. 注册MyBatis-Plusemail 必填userId 自增 Longcurl-i-XPOST http://localhost:8080/api/auth/register\-HContent-Type: application/json\-d{username:smoketest,email:smoketeste2e.local,password:test1234}# → 201 {userId, username, email, token}# 2. 登录curl-i-XPOST http://localhost:8080/api/auth/login\-HContent-Type: application/json\-d{username:smoketest,email:smoketeste2e.local,password:test1234}# → 200 {...}# 3. /me 不带 / 带 tokencurl-ihttp://localhost:8080/api/auth/me# → 401curl-ihttp://localhost:8080/api/auth/me-HAuthorization: Bearer token# → 200# 4. 建会话curl-i-XPOST http://localhost:8080/api/sessions\-HAuthorization: Bearer token-HContent-Type: application/json\-d{scene:interview}# → 201# 5. 对话Java → Python :9000curl-i-XPOST http://localhost:8080/api/chat\-HAuthorization: Bearer token-HContent-Type: application/json\-d{\sessionId\:\sessionId\,\userText\:\Hello, my name is John.\}# → 200# 6. 结束会话curl-i-XPOST http://localhost:8080/api/sessions/sessionId/finish\-HAuthorization: Bearer token-HContent-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 时自动降级到StubChatModelworkflow 端到端可跑回复是占位文本。LangGraph 默认内存 checkpoint保底机制如果没有开启redis的话就默认为本地内存存储想跨进程共享就设CHECKPOINT_BACKENDredis。Maven 构建硬性要求 JDK 21Lombok 1.18.34 / Spring Boot 3.3.4。JDK 17 不行。application.yml自带的数据库密码和 JWT secret 是开发占位生产部署前必须覆盖。进度实现端到端可跑通Java 干净编译、Python 启动正常、所有内部端点响应正常、TTS 在 GPT-SoVITS 离线时回退静音