# -*- coding: utf-8 -*- """ CardioAI - Module 3: AI 语音助手 Deepseek 问答 + CosyVoice 语音合成服务 """ import os import base64 import dashscope from pathlib import Path from flask import Flask, render_template, request, jsonify from dotenv import load_dotenv from langchain_openai import ChatOpenAI from dashscope.audio.tts_v2 import SpeechSynthesizer, AudioFormat # ============================================ # 配置与初始化 # ============================================ # 加载环境变量 ENV_PATH = Path(r"E:\project_ai\claude_project1\aicodes\.env") load_dotenv(dotenv_path=ENV_PATH) # 配置 DashScope API Key dashscope.api_key = os.getenv("DASHSCOPE_API_KEY") # 创建 Flask 应用 CODE_ROOT = Path(r"E:\project_ai\claude_project1\aicodes") TEMPLATE_DIR = CODE_ROOT / "module3_voice_assistant" / "templates" app = Flask(__name__, template_folder=str(TEMPLATE_DIR), static_folder=str(TEMPLATE_DIR.parent / 'static')) # ============================================ # DeepSeek LLM 配置 # ============================================ def get_llm(): """ 初始化 DeepSeek LLM 实例 Returns: ChatOpenAI: DeepSeek LLM 实例 """ llm = ChatOpenAI( base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"), api_key=os.getenv("DEEPSEEK_API_KEY"), model=os.getenv("MODEL_NAME", "deepseek-chat"), temperature=float(os.getenv("TEMPERATURE", 0.7)), max_tokens=int(os.getenv("MAX_TOKENS", 2000)) ) return llm # System Prompt - 专业心血管健康顾问 SYSTEM_PROMPT = """你是一位专业的心血管健康顾问,名为 CardioAI 助手。 你的职责: 1. 回答用户关于心血管疾病预防、诊断、治疗和管理的问题 2. 提供基于医学证据的健康建议 3. 解释心血管相关的医学概念和检查指标 4. 给出生活方式改善建议(饮食、运动、戒烟限酒等) 注意事项: - 回答要专业、准确、通俗易懂 - 对于严重的健康问题,建议用户及时就医 - 不要替代医生的诊断,你的建议仅供参考 - 回答要简洁明了,避免过于冗长 请用温暖、专业的语气回答用户的问题。""" # ============================================ # CosyVoice 语音合成函数 # ============================================ def text_to_speech(text: str) -> bytes: """ 使用 CosyVoice 将文本转换为语音 Args: text (str): 待合成的文本 Returns: bytes: MP3 格式的音频数据 """ try: # 实例化 SpeechSynthesizer synthesizer = SpeechSynthesizer( model=os.getenv("VOICE_MODEL", "cosyvoice-v2"), voice=os.getenv("VOICE", "longxiaochun_v2"), format=AudioFormat.MP3_22050HZ_MONO_256KBPS ) # 同步调用 - 将文本转换为语音 audio_data = synthesizer.call(text) return audio_data except Exception as e: print(f"语音合成失败: {str(e)}") return None # ============================================ # 路由定义 # ============================================ @app.route('/') def index(): """主页 - 渲染语音助手界面""" return render_template('voice_index.html') @app.route('/voice_assistant', methods=['POST']) def voice_assistant(): """ AI 语音助手 API 接口 接收用户问题,返回 LLM 文字回答和 Base64 编码的语音 """ try: # 获取请求数据 data = request.get_json() user_question = data.get('question', '').strip() if not user_question: return jsonify({ 'error': '请输入您的问题' }), 400 # 检查 API Key 配置 deepseek_key = os.getenv("DEEPSEEK_API_KEY") dashscope_key = os.getenv("DASHSCOPE_API_KEY") if not deepseek_key or deepseek_key == "your_deepseek_api_key_here": return jsonify({ 'error': 'DeepSeek API Key 未配置,请在 .env 文件中设置 DEEPSEEK_API_KEY' }), 500 if not dashscope_key or dashscope_key == "your_dashscope_api_key_here": return jsonify({ 'error': 'DashScope API Key 未配置,请在 .env 文件中设置 DASHSCOPE_API_KEY' }), 500 print(f"\n用户问题: {user_question}") # 1. 调用 DeepSeek LLM 获取文字回答 print("正在调用 DeepSeek LLM...") llm = get_llm() # 构建完整 prompt messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_question} ] # 调用 LLM llm_response = llm.invoke(messages) text_answer = llm_response.content print(f"LLM 回答: {text_answer}") # 2. 将文字回答转换为语音 print("正在调用 CosyVoice 合成语音...") audio_data = text_to_speech(text_answer) if audio_data is None: return jsonify({ 'error': '语音合成失败,请检查 API Key 配置和网络连接' }), 500 # 3. 将音频编码为 Base64 audio_base64 = base64.b64encode(audio_data).decode('utf-8') print(f"语音合成完成,音频大小: {len(audio_data)} 字节") # 4. 返回结果 return jsonify({ 'text_answer': text_answer, 'audio_base64': audio_base64, 'message': '问答成功' }) except Exception as e: print(f"处理失败: {str(e)}") return jsonify({ 'error': f'处理失败: {str(e)}' }), 500 @app.route('/health', methods=['GET']) def health(): """健康检查接口""" deepseek_configured = os.getenv("DEEPSEEK_API_KEY") not in [None, "your_deepseek_api_key_here"] dashscope_configured = os.getenv("DASHSCOPE_API_KEY") not in [None, "your_dashscope_api_key_here"] return jsonify({ 'status': 'healthy', 'deepseek_configured': deepseek_configured, 'dashscope_configured': dashscope_configured }) # ============================================ # 主程序入口 # ============================================ if __name__ == '__main__': host = os.getenv('FLASK_HOST', '127.0.0.1') port = int(os.getenv('FLASK_PORT', 5001)) # 使用 5001 避免与 Module 2 冲突 print("\n" + "=" * 50) print("CardioAI - Module 3: AI 语音助手服务") print("=" * 50) print(f"服务地址: http://{host}:{port}") print("=" * 50 + "\n") app.run(host=host, port=port, debug=True)