commit 14e93107309d066d960e8c6e41f0e65239f7aae9 Author: leslie Date: Fri Jan 30 20:40:57 2026 +0800 first commit diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..1b1e4f1 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(dir \"E:\\\\project_ai\\\\claude_project1\\\\aicodes\" /b)", + "Bash(D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe:*)" + ] + } +} diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/claude_project1.iml b/.idea/claude_project1.iml new file mode 100644 index 0000000..a0fcdec --- /dev/null +++ b/.idea/claude_project1.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c5011b5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e960cd1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/aicodes/.env b/aicodes/.env new file mode 100644 index 0000000..18ae53a --- /dev/null +++ b/aicodes/.env @@ -0,0 +1,28 @@ +# ============================================ +# CardioAI - 心血管疾病智能辅助系统 +# 环境配置文件 +# ============================================ + +# 数据文件路径 +DATA_PATH=E:\project_ai\claude_project1\data\心血管疾病.xlsx + +# Module 2: XGBoost 预测服务配置 +FLASK_PORT=5000 +FLASK_HOST=127.0.0.1 + +# Module 3: AI 语音助手配置 +# DeepSeek API 配置 +DEEPSEEK_API_KEY=your_deepseek_api_key_here +DEEPSEEK_BASE_URL=https://api.deepseek.com + +# 阿里云 DashScope 配置 (CosyVoice 语音合成) +DASHSCOPE_API_KEY=your_dashscope_api_key_here + +# 语言模型配置 +MODEL_NAME=deepseek-chat +TEMPERATURE=0.7 +MAX_TOKENS=2000 + +# 语音合成配置 +VOICE_MODEL=cosyvoice-v2 +VOICE=longxiaochun_v2 diff --git a/aicodes/llm_streaming.py b/aicodes/llm_streaming.py new file mode 100644 index 0000000..51a5d37 --- /dev/null +++ b/aicodes/llm_streaming.py @@ -0,0 +1,47 @@ +import os +from langchain_openai import ChatOpenAI +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + + +def get_llm(): + """ + 初始化并返回 DeepSeek LLM 实例 + """ + # 注意:这里的 ChatOpenAI 实际上是兼容 OpenAI 接口的类,用于连接 DeepSeek API + llm = ChatOpenAI( + base_url=os.getenv("base_url1"), + api_key=os.getenv("DEEPSEEK_API_KEY1"), + model="deepseek-chat", + temperature=0, # 设置为0,确保结果稳定 + ) + return llm + + +# 1. 初始化 LLM +llm = get_llm() + +# 2. 用户级 prompt +user_prompt = "你是谁?请详细介绍一下你自己。" + +print(f"--- 用户提问:{user_prompt} ---") +print("--- LLM 流式回复开始 ---") + +# 3. **核心修改:使用 .stream() 方法进行流式调用** +# .stream() 返回一个迭代器,我们可以遍历它来获取分块的输出。 +response_stream = llm.stream(user_prompt) + +# 4. 遍历并打印流式输出 +full_response = "" +for chunk in response_stream: + # chunk.content 包含当前流片段的内容 + # end="" 确保打印时不换行,模拟流式输出效果 + print(chunk.content, end="", flush=True) + full_response += chunk.content + +print("\n--- LLM 流式回复结束 ---") + +# 5. 可选:打印完整的回复内容 +# print(f"\n完整的回复内容:{full_response}") \ No newline at end of file diff --git a/aicodes/module1_dashboard/cardio_dashboard.py b/aicodes/module1_dashboard/cardio_dashboard.py new file mode 100644 index 0000000..3188783 --- /dev/null +++ b/aicodes/module1_dashboard/cardio_dashboard.py @@ -0,0 +1,494 @@ +# -*- coding: utf-8 -*- +""" +CardioAI - Module 1: 数据可视化仪表板 +心血管疾病数据清洗、特征工程与交互式可视化 +""" + +import streamlit as st +import pandas as pd +import numpy as np +import plotly.express as px +from pathlib import Path +import os + +# ============================================ +# 配置与常量 +# ============================================ +CODE_ROOT = Path(r"E:\project_ai\claude_project1\aicodes") +DATA_PATH = Path(r"E:\project_ai\claude_project1\data\心血管疾病.xlsx") + +# 类别映射字典 +CHOLESTEROL_MAP = { + 1: "正常", + 2: "高于正常", + 3: "远高于正常" +} + +GLUC_MAP = { + 1: "正常", + 2: "高于正常", + 3: "远高于正常" +} + +GENDER_MAP = { + 1: "女性", + 2: "男性" +} + +CARDIO_MAP = { + 0: "无疾病", + 1: "有疾病" +} + + +# ============================================ +# 数据加载与清洗函数 +# ============================================ +@st.cache_data +def load_data(): + """ + 加载心血管疾病数据 + + Returns: + pd.DataFrame: 原始数据 + """ + try: + df = pd.read_excel(DATA_PATH, engine='openpyxl') + return df + except Exception as e: + st.error(f"数据加载失败: {str(e)}") + return pd.DataFrame() + + +@st.cache_data +def clean_and_process_data(df): + """ + 数据清洗与特征工程 + + 处理步骤: + 1. 将age(天)转换为年 + 2. 计算BMI + 3. 删除血压异常值 + 4. 类别转换 + 5. 创建BMI分类 + + Args: + df (pd.DataFrame): 原始数据 + + Returns: + pd.DataFrame: 处理后的数据 + """ + if df.empty: + return df + + df_clean = df.copy() + + # 1. 年龄转换: 天 -> 年 (四舍五入) + df_clean['age_years'] = (df_clean['age'] / 365.25).round().astype(int) + + # 2. 计算BMI: weight / (height/100)^2 + df_clean['bmi'] = df_clean['weight'] / ((df_clean['height'] / 100) ** 2) + + # 3. 异常值处理 + + # 删除舒张压 >= 收缩压的记录 (生理上不可能) + df_clean = df_clean[df_clean['ap_lo'] < df_clean['ap_hi']] + + # 删除血压极端异常值 (收缩压范围: [90, 250], 舒张压范围: [60, 150]) + df_clean = df_clean[ + (df_clean['ap_hi'] >= 90) & (df_clean['ap_hi'] <= 250) & + (df_clean['ap_lo'] >= 60) & (df_clean['ap_lo'] <= 150) + ] + + # 4. 类别转换 + df_clean['cholesterol_desc'] = df_clean['cholesterol'].map(CHOLESTEROL_MAP) + df_clean['gluc_desc'] = df_clean['gluc'].map(GLUC_MAP) + df_clean['gender_desc'] = df_clean['gender'].map(GENDER_MAP) + df_clean['cardio_desc'] = df_clean['cardio'].map(CARDIO_MAP) + + # 5. 创建BMI分类 + def categorize_bmi(bmi): + if bmi < 18.5: + return "体重过轻" + elif bmi < 24: + return "正常体重" + elif bmi < 28: + return "超重" + else: + return "肥胖" + + df_clean['bmi_category'] = df_clean['bmi'].apply(categorize_bmi) + + return df_clean + + +# ============================================ +# 筛选函数 +# ============================================ +def apply_filters(df, age_range, gender_filter, cardio_filter): + """ + 根据用户选择的筛选条件过滤数据 + + Args: + df (pd.DataFrame): 处理后的数据 + age_range (tuple): 年龄范围 (min, max) + gender_filter (list): 性别筛选列表 + cardio_filter (list): 心血管疾病状态筛选列表 + + Returns: + pd.DataFrame: 筛选后的数据 + """ + if df.empty: + return df + + df_filtered = df.copy() + + # 年龄范围筛选 + df_filtered = df_filtered[ + df_filtered['age_years'].between(age_range[0], age_range[1]) + ] + + # 性别筛选 + if gender_filter: + df_filtered = df_filtered[df_filtered['gender_desc'].isin(gender_filter)] + + # 心血管疾病状态筛选 + if cardio_filter: + df_filtered = df_filtered[df_filtered['cardio_desc'].isin(cardio_filter)] + + return df_filtered + + +# ============================================ +# 统计指标函数 +# ============================================ +def calculate_statistics(df): + """ + 计算统计指标 + + Args: + df (pd.DataFrame): 筛选后的数据 + + Returns: + dict: 包含总记录数和风险率的字典 + """ + if df.empty: + return {"total_records": 0, "risk_rate": 0.0} + + total_records = len(df) + disease_count = df['cardio'].sum() + risk_rate = (disease_count / total_records * 100) if total_records > 0 else 0 + + return { + "total_records": total_records, + "risk_rate": risk_rate + } + + +# ============================================ +# 可视化图表函数 +# ============================================ +def plot_age_distribution(df): + """ + 绘制年龄分布直方图 (按cardio区分) + + Args: + df (pd.DataFrame): 数据 + + Returns: + plotly.graph_objects.Figure: 年龄分布图 + """ + if df.empty: + return None + + fig = px.histogram( + df, + x='age_years', + color='cardio_desc', + nbins=30, + title='年龄分布 (按心血管疾病状态)', + labels={'age_years': '年龄 (岁)', 'count': '人数'}, + color_discrete_map={'无疾病': '#2ecc71', '有疾病': '#e74c3c'}, + barmode='overlay' + ) + + fig.update_layout( + xaxis_title="年龄 (岁)", + yaxis_title="人数", + legend_title="疾病状态", + hovermode='x unified' + ) + + return fig + + +def plot_bmi_vs_cardio(df): + """ + 绘制BMI分类对心血管疾病影响的堆叠柱状图 + + Args: + df (pd.DataFrame): 数据 + + Returns: + plotly.graph_objects.Figure: BMI与疾病关系图 + """ + if df.empty: + return None + + # 计算每个BMI分类的疾病比例 + bmi_cardio = df.groupby(['bmi_category', 'cardio_desc']).size().reset_index(name='count') + + # 确保BMI分类顺序正确 + bmi_order = ["体重过轻", "正常体重", "超重", "肥胖"] + bmi_cardio['bmi_category'] = pd.Categorical( + bmi_cardio['bmi_category'], + categories=bmi_order, + ordered=True + ) + bmi_cardio = bmi_cardio.sort_values('bmi_category') + + fig = px.bar( + bmi_cardio, + x='bmi_category', + y='count', + color='cardio_desc', + title='BMI分类与心血管疾病关系', + labels={'bmi_category': 'BMI分类', 'count': '人数'}, + color_discrete_map={'无疾病': '#2ecc71', '有疾病': '#e74c3c'}, + category_orders={'bmi_category': bmi_order} + ) + + fig.update_layout( + xaxis_title="BMI分类", + yaxis_title="人数", + legend_title="疾病状态", + barmode='stack' + ) + + return fig + + +# ============================================ +# 主应用程序 +# ============================================ +def main(): + """Streamlit 主应用程序""" + + # 页面配置 + st.set_page_config( + page_title="CardioAI - 心血管疾病数据分析仪表板", + page_icon="❤️", + layout="wide", + initial_sidebar_state="expanded" + ) + + # 标题与描述 + st.title("❤️ CardioAI - 心血管疾病智能辅助系统") + st.markdown("### Module 1: 数据可视化仪表板") + st.markdown("---") + + # 数据加载 + with st.spinner("正在加载数据..."): + raw_data = load_data() + + if raw_data.empty: + st.error("无法加载数据,请检查数据路径是否正确。") + st.stop() + + # 数据清洗与特征工程 + processed_data = clean_and_process_data(raw_data) + + if processed_data.empty: + st.warning("数据清洗后无有效记录,请检查数据质量。") + st.stop() + + # ============================================ + # 侧边栏 - 筛选器 + # ============================================ + st.sidebar.header("🔍 数据筛选") + + # 年龄范围滑块 + age_min = int(processed_data['age_years'].min()) + age_max = int(processed_data['age_years'].max()) + age_range = st.sidebar.slider( + "年龄范围 (岁)", + min_value=age_min, + max_value=age_max, + value=(age_min, age_max), + step=1 + ) + + # 性别多选框 + gender_options = processed_data['gender_desc'].unique().tolist() + gender_filter = st.sidebar.multiselect( + "性别", + options=gender_options, + default=gender_options + ) + + # 心血管疾病状态多选框 + cardio_options = processed_data['cardio_desc'].unique().tolist() + cardio_filter = st.sidebar.multiselect( + "心血管疾病状态", + options=cardio_options, + default=cardio_options + ) + + st.sidebar.markdown("---") + st.sidebar.markdown("### 📊 数据概览") + st.sidebar.markdown(f"- 原始记录数: **{len(raw_data):,}**") + st.sidebar.markdown(f"- 清洗后记录数: **{len(processed_data):,}**") + st.sidebar.markdown(f"- 数据清洗率: **{(1 - len(processed_data)/len(raw_data))*100:.2f}%**") + + # ============================================ + # 应用筛选条件 + # ============================================ + filtered_data = apply_filters( + processed_data, + age_range, + gender_filter, + cardio_filter + ) + + # ============================================ + # 主页 - 统计指标 + # ============================================ + st.subheader("📈 筛选结果统计") + + stats = calculate_statistics(filtered_data) + + col1, col2 = st.columns(2) + + with col1: + st.metric( + label="筛选后总记录数", + value=f"{stats['total_records']:,}", + delta=None + ) + + with col2: + # 根据风险率设置颜色 + risk_rate = stats['risk_rate'] + risk_color = "normal" if risk_rate < 50 else "inverse" + + st.metric( + label="心血管疾病风险率", + value=f"{risk_rate:.2f}%", + delta=None, + delta_color=risk_color + ) + + st.markdown("---") + + # ============================================ + # 图表展示 + # ============================================ + st.subheader("📊 数据可视化") + + # 第一行: 年龄分布图 + col1, col2 = st.columns(2) + + with col1: + age_fig = plot_age_distribution(filtered_data) + if age_fig: + st.plotly_chart(age_fig, use_container_width=True) + + with col2: + # 添加性别分布饼图 + gender_dist = filtered_data['gender_desc'].value_counts().reset_index() + gender_dist.columns = ['性别', '人数'] + gender_pie = px.pie( + gender_dist, + values='人数', + names='性别', + title='性别分布', + color_discrete_sequence=['#3498db', '#e91e63'] + ) + st.plotly_chart(gender_pie, use_container_width=True) + + # 第二行: BMI与疾病关系图 + st.markdown("### BMI分类与心血管疾病风险") + bmi_fig = plot_bmi_vs_cardio(filtered_data) + if bmi_fig: + st.plotly_chart(bmi_fig, use_container_width=True) + + # 第三行: 其他因素分析 + col1, col2 = st.columns(2) + + with col1: + # 胆固醇分布 + chol_cardio = filtered_data.groupby(['cholesterol_desc', 'cardio_desc']).size().reset_index(name='count') + chol_fig = px.bar( + chol_cardio, + x='cholesterol_desc', + y='count', + color='cardio_desc', + title='胆固醇水平与心血管疾病', + labels={'cholesterol_desc': '胆固醇水平', 'count': '人数'}, + color_discrete_map={'无疾病': '#2ecc71', '有疾病': '#e74c3c'} + ) + st.plotly_chart(chol_fig, use_container_width=True) + + with col2: + # 血糖分布 + gluc_cardio = filtered_data.groupby(['gluc_desc', 'cardio_desc']).size().reset_index(name='count') + gluc_order = ["正常", "高于正常", "远高于正常"] + gluc_cardio['gluc_desc'] = pd.Categorical( + gluc_cardio['gluc_desc'], + categories=gluc_order, + ordered=True + ) + gluc_cardio = gluc_cardio.sort_values('gluc_desc') + + gluc_fig = px.bar( + gluc_cardio, + x='gluc_desc', + y='count', + color='cardio_desc', + title='血糖水平与心血管疾病', + labels={'gluc_desc': '血糖水平', 'count': '人数'}, + category_orders={'gluc_desc': gluc_order}, + color_discrete_map={'无疾病': '#2ecc71', '有疾病': '#e74c3c'} + ) + st.plotly_chart(gluc_fig, use_container_width=True) + + # ============================================ + # 数据表格预览 + # ============================================ + st.markdown("---") + st.subheader("📋 数据预览") + + display_columns = [ + 'age_years', 'gender_desc', 'height', 'weight', 'bmi', 'bmi_category', + 'ap_hi', 'ap_lo', 'cholesterol_desc', 'gluc_desc', + 'smoke', 'alco', 'active', 'cardio_desc' + ] + + column_rename = { + 'age_years': '年龄(岁)', + 'gender_desc': '性别', + 'height': '身高(cm)', + 'weight': '体重(kg)', + 'bmi': 'BMI', + 'bmi_category': 'BMI分类', + 'ap_hi': '收缩压', + 'ap_lo': '舒张压', + 'cholesterol_desc': '胆固醇', + 'gluc_desc': '血糖', + 'smoke': '吸烟', + 'alco': '饮酒', + 'active': '运动', + 'cardio_desc': '心血管疾病' + } + + if not filtered_data.empty: + display_df = filtered_data[display_columns].copy() + display_df = display_df.rename(columns=column_rename) + st.dataframe(display_df.head(100), use_container_width=True, height=400) + + +# ============================================ +# 程序入口 +# ============================================ +if __name__ == "__main__": + main() diff --git a/aicodes/module2_predictor/app.py b/aicodes/module2_predictor/app.py new file mode 100644 index 0000000..55896f9 --- /dev/null +++ b/aicodes/module2_predictor/app.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +""" +CardioAI - Module 2: Flask API 部署 +心血管疾病预测服务后端 +""" + +import os +import json +import joblib +import numpy as np +from pathlib import Path +from flask import Flask, render_template, request, jsonify +from dotenv import load_dotenv + +# ============================================ +# 配置与初始化 +# ============================================ +# 加载环境变量 +load_dotenv() + +CODE_ROOT = Path(r"E:\project_ai\claude_project1\aicodes") +MODEL_PATH = CODE_ROOT / "module2_predictor" / "cardio_predictor_model.pkl" +TEMPLATE_DIR = CODE_ROOT / "module2_predictor" / "templates" + +# 创建 Flask 应用 +app = Flask(__name__, + template_folder=str(TEMPLATE_DIR), + static_folder=str(TEMPLATE_DIR.parent / 'static')) + +# 加载模型 +print("正在加载模型...") +try: + model_pipeline = joblib.load(MODEL_PATH) + print(f"模型加载成功: {MODEL_PATH}") +except Exception as e: + print(f"模型加载失败: {e}") + model_pipeline = None + + +# ============================================ +# 辅助函数 +# ============================================ +def preprocess_input(data): + """ + 预处理输入数据 + + Args: + data (dict): 包含11个特征的字典 + + Returns: + pd.DataFrame: 格式化后的输入数据 + """ + import pandas as pd + + # 构建输入DataFrame + input_data = { + 'age_years': int(data['age_years']), + 'gender': int(data['gender']), + 'height': float(data['height']), + 'weight': float(data['weight']), + 'ap_hi': int(data['ap_hi']), + 'ap_lo': int(data['ap_lo']), + 'cholesterol': int(data['cholesterol']), + 'gluc': int(data['gluc']), + 'smoke': int(data['smoke']), + 'alco': int(data['alco']), + 'active': int(data['active']) + } + + # 计算BMI + input_data['bmi'] = input_data['weight'] / ((input_data['height'] / 100) ** 2) + + df = pd.DataFrame([input_data]) + + # 按照模型训练时的特征顺序排列 + feature_order = ['age_years', 'height', 'weight', 'bmi', + 'ap_hi', 'ap_lo', 'gender', 'cholesterol', + 'gluc', 'smoke', 'alco', 'active'] + + return df[feature_order] + + +# ============================================ +# 路由定义 +# ============================================ +@app.route('/') +def index(): + """主页 - 渲染预测表单""" + return render_template('index.html') + + +@app.route('/predict_cardio', methods=['POST']) +def predict_cardio(): + """ + 心血管疾病预测API接口 + + 接收11个原始特征值的JSON POST请求 + 返回预测结果和概率 + """ + if model_pipeline is None: + return jsonify({ + 'error': '模型未加载,请先运行训练脚本' + }), 500 + + try: + # 获取JSON数据 + data = request.get_json() + + # 验证必填字段 + required_fields = [ + 'age_years', 'gender', 'height', 'weight', + 'ap_hi', 'ap_lo', 'cholesterol', 'gluc', + 'smoke', 'alco', 'active' + ] + + missing_fields = [field for field in required_fields if field not in data] + if missing_fields: + return jsonify({ + 'error': f'缺少必填字段: {", ".join(missing_fields)}' + }), 400 + + # 预处理输入数据 + input_df = preprocess_input(data) + + # 模型预测 + prediction = model_pipeline.predict(input_df)[0] + probability = model_pipeline.predict_proba(input_df)[0, 1] + + # 返回结果 + return jsonify({ + 'prediction': int(prediction), + 'probability': float(probability), + 'risk_level': get_risk_level(probability), + 'message': '预测成功' + }) + + except Exception as e: + return jsonify({ + 'error': f'预测失败: {str(e)}' + }), 500 + + +def get_risk_level(probability): + """ + 根据概率返回风险等级描述 + + Args: + probability (float): 疾病概率 + + Returns: + str: 风险等级描述 + """ + if probability < 0.3: + return "低风险" + elif probability < 0.5: + return "中等风险" + elif probability < 0.7: + return "高风险" + else: + return "极高风险" + + +@app.route('/health', methods=['GET']) +def health(): + """健康检查接口""" + return jsonify({ + 'status': 'healthy', + 'model_loaded': model_pipeline is not None + }) + + +# ============================================ +# 主程序入口 +# ============================================ +if __name__ == '__main__': + # 从环境变量读取配置,使用默认值 + host = os.getenv('FLASK_HOST', '127.0.0.1') + port = int(os.getenv('FLASK_PORT', 5000)) + + print("\n" + "=" * 50) + print("CardioAI - Module 2: 心血管疾病预测服务") + print("=" * 50) + print(f"服务地址: http://{host}:{port}") + print("=" * 50 + "\n") + + app.run(host=host, port=port, debug=True) diff --git a/aicodes/module2_predictor/cardio_predictor_model.pkl b/aicodes/module2_predictor/cardio_predictor_model.pkl new file mode 100644 index 0000000..28e9361 Binary files /dev/null and b/aicodes/module2_predictor/cardio_predictor_model.pkl differ diff --git a/aicodes/module2_predictor/templates/index.html b/aicodes/module2_predictor/templates/index.html new file mode 100644 index 0000000..e3dc118 --- /dev/null +++ b/aicodes/module2_predictor/templates/index.html @@ -0,0 +1,532 @@ + + + + + + CardioAI - 心血管疾病智能预测 + + + +
+
+

❤️ CardioAI

+

心血管疾病智能预测系统

+
+ +
+
+
+ +
+ + + 请输入您的年龄 +
+ + +
+ + +
+ + +
+ + + 范围: 100-250 cm +
+ + +
+ + + 范围: 30-200 kg +
+ + +
+ + + 范围: 90-250 mmHg +
+ + +
+ + + 范围: 60-150 mmHg +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ + +
+
+ + +
+
+

正在分析您的健康数据...

+
+ + +
+ + +
+
+

预测结果

+

+
+ +
+
+
预测结果
+
-
+
+
+
疾病概率
+
-
+
+
+
风险等级
+
-
+
+
+ +
+
+
+
+
疾病风险概率
+
+
+
+ +
+

⚠️ 本预测结果仅供参考,不能替代专业医疗诊断

+
+
+ + + + diff --git a/aicodes/module2_predictor/train_and_save.py b/aicodes/module2_predictor/train_and_save.py new file mode 100644 index 0000000..03fedc0 --- /dev/null +++ b/aicodes/module2_predictor/train_and_save.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +""" +CardioAI - Module 2: 模型训练与保存 +一次性脚本 - 训练XGBoost模型并保存Pipeline +""" + +import pandas as pd +import numpy as np +import joblib +from pathlib import Path +from sklearn.model_selection import train_test_split +from sklearn.compose import ColumnTransformer +from sklearn.preprocessing import StandardScaler, OneHotEncoder +from sklearn.pipeline import Pipeline +from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score +import xgboost as xgb + +# ============================================ +# 配置与常量 +# ============================================ +CODE_ROOT = Path(r"E:\project_ai\claude_project1\aicodes") +DATA_PATH = Path(r"E:\project_ai\claude_project1\data\心血管疾病.xlsx") +MODEL_SAVE_PATH = CODE_ROOT / "module2_predictor" / "cardio_predictor_model.pkl" + +# 类别映射字典 +CHOLESTEROL_MAP = {1: "正常", 2: "高于正常", 3: "远高于正常"} +GLUC_MAP = {1: "正常", 2: "高于正常", 3: "远高于正常"} +GENDER_MAP = {1: "女性", 2: "男性"} + + +# ============================================ +# 数据加载与清洗函数 +# ============================================ +def load_data(): + """加载心血管疾病数据""" + print("正在加载数据...") + df = pd.read_excel(DATA_PATH, engine='openpyxl') + print(f"数据加载完成: {df.shape[0]} 条记录, {df.shape[1]} 个特征") + return df + + +def clean_and_process_data(df): + """ + 数据清洗与特征工程 + + 处理步骤: + 1. 将age(天)转换为年 + 2. 计算BMI + 3. 删除血压异常值 + 4. 类别转换 + 5. 删除id和原始age字段 + """ + print("\n开始数据清洗与特征工程...") + + df_clean = df.copy() + + # 1. 年龄转换: 天 -> 年 (四舍五入) + df_clean['age_years'] = (df_clean['age'] / 365.25).round().astype(int) + + # 2. 计算BMI: weight / (height/100)^2 + df_clean['bmi'] = df_clean['weight'] / ((df_clean['height'] / 100) ** 2) + + # 3. 异常值处理 + original_count = len(df_clean) + + # 删除舒张压 >= 收缩压的记录 + df_clean = df_clean[df_clean['ap_lo'] < df_clean['ap_hi']] + + # 删除血压极端异常值 (收缩压范围: [90, 250], 舒张压范围: [60, 150]) + df_clean = df_clean[ + (df_clean['ap_hi'] >= 90) & (df_clean['ap_hi'] <= 250) & + (df_clean['ap_lo'] >= 60) & (df_clean['ap_lo'] <= 150) + ] + + removed_count = original_count - len(df_clean) + print(f" - 删除异常值: {removed_count} 条记录") + + # 4. 删除id和原始age字段 + df_clean = df_clean.drop(columns=['id', 'age'], errors='ignore') + + print(f"数据清洗完成: {len(df_clean)} 条有效记录") + + return df_clean + + +# ============================================ +# 模型训练函数 +# ============================================ +def train_model(df): + """ + 训练XGBoost分类模型 + + Args: + df (pd.DataFrame): 清洗后的数据 + + Returns: + Pipeline: 包含预处理器和模型的完整Pipeline + """ + print("\n开始模型训练...") + + # 定义特征列 + # 连续特征 + numeric_features = ['age_years', 'height', 'weight', 'bmi', 'ap_hi', 'ap_lo'] + + # 分类特征 + categorical_features = ['gender', 'cholesterol', 'gluc', 'smoke', 'alco', 'active'] + + # 特征与目标变量 + X = df[numeric_features + categorical_features] + y = df['cardio'] + + # 划分训练集和测试集 + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42, stratify=y + ) + + print(f" - 训练集: {X_train.shape[0]} 条记录") + print(f" - 测试集: {X_test.shape[0]} 条记录") + + # 构建预处理器 + preprocessor = ColumnTransformer( + transformers=[ + ('num', StandardScaler(), numeric_features), + ('cat', OneHotEncoder(drop='first', handle_unknown='ignore'), categorical_features) + ], + remainder='passthrough' + ) + + # 构建完整Pipeline + pipeline = Pipeline(steps=[ + ('preprocessor', preprocessor), + ('classifier', xgb.XGBClassifier( + n_estimators=100, + max_depth=6, + learning_rate=0.1, + subsample=0.8, + colsample_bytree=0.8, + random_state=42, + eval_metric='logloss', + use_label_encoder=False + )) + ]) + + # 训练模型 + print(" - 正在训练 XGBoost 模型...") + pipeline.fit(X_train, y_train) + + # 评估模型 + print("\n模型评估:") + y_pred = pipeline.predict(X_test) + y_pred_proba = pipeline.predict_proba(X_test)[:, 1] + + print("\n分类报告:") + print(classification_report(y_test, y_pred, target_names=['无疾病', '有疾病'])) + + print("\n混淆矩阵:") + cm = confusion_matrix(y_test, y_pred) + print(f" 真阴性(TN): {cm[0,0]} 假阳性(FP): {cm[0,1]}") + print(f" 假阴性(FN): {cm[1,0]} 真阳性(TP): {cm[1,1]}") + + auc_score = roc_auc_score(y_test, y_pred_proba) + print(f"\nAUC-ROC: {auc_score:.4f}") + + return pipeline + + +# ============================================ +# 模型保存函数 +# ============================================ +def save_model(pipeline, save_path): + """ + 保存训练好的模型 + + Args: + pipeline (Pipeline): 训练好的Pipeline + save_path (Path): 模型保存路径 + """ + print(f"\n正在保存模型到: {save_path}") + joblib.dump(pipeline, save_path) + print("模型保存完成!") + + +# ============================================ +# 主程序 +# ============================================ +def main(): + """主程序入口""" + print("=" * 60) + print("CardioAI - Module 2: XGBoost模型训练与保存") + print("=" * 60) + + # 1. 加载数据 + df = load_data() + + # 2. 数据清洗与特征工程 + df_clean = clean_and_process_data(df) + + # 3. 训练模型 + pipeline = train_model(df_clean) + + # 4. 保存模型 + save_model(pipeline, MODEL_SAVE_PATH) + + print("\n" + "=" * 60) + print("训练完成! 模型已保存至:") + print(f" {MODEL_SAVE_PATH}") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/aicodes/module3_voice_assistant/templates/voice_index.html b/aicodes/module3_voice_assistant/templates/voice_index.html new file mode 100644 index 0000000..681cfb3 --- /dev/null +++ b/aicodes/module3_voice_assistant/templates/voice_index.html @@ -0,0 +1,468 @@ + + + + + + CardioAI - AI 语音助手 + + + +
+
+

🤖 CardioAI 语音助手

+

专业心血管健康顾问 - DeepSeek + CosyVoice

+
+ +
+ +
+ + +
+
+
❤️
+

您好!我是 CardioAI 健康助手

+

我可以回答您关于心血管健康的任何问题

+
+ 什么是高血压? + 如何预防心血管疾病? + 正常的心率是多少? + 胆固醇高怎么办? +
+
+
+ + +
+
+

正在思考并生成语音回答...

+
+ + +
+
+ +
+ +
+
+ +
+

⚠️ 本助手提供的健康建议仅供参考,不能替代专业医疗诊断

+
+
+ + + + diff --git a/aicodes/module3_voice_assistant/voice_assistant_app.py b/aicodes/module3_voice_assistant/voice_assistant_app.py new file mode 100644 index 0000000..b363689 --- /dev/null +++ b/aicodes/module3_voice_assistant/voice_assistant_app.py @@ -0,0 +1,216 @@ +# -*- 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) diff --git a/aicodes/requirements.txt b/aicodes/requirements.txt new file mode 100644 index 0000000..071e5a4 --- /dev/null +++ b/aicodes/requirements.txt @@ -0,0 +1,40 @@ +# ============================================ +# CardioAI - 心血管疾病智能辅助系统 +# 项目依赖配置文件 +# ============================================ + +# Conda 环境设置说明: +# 1. 创建名为 cardioenv 的 conda 虚拟环境 (Python 3.10): +# conda create -n cardioenv python=3.10 +# +# 2. 激活虚拟环境: +# conda activate cardioenv +# +# 3. 安装项目依赖: +# pip install -r requirements.txt +# ============================================ + +# 数据处理与科学计算 +pandas>=1.5.0 +openpyxl>=3.0.0 +numpy>=1.23.0 + +# 机器学习与模型 +scikit-learn>=1.2.0 +xgboost>=2.0.0 +joblib>=1.3.0 + +# 数据可视化 +streamlit>=1.28.0 +plotly>=5.18.0 + +# Web 服务框架 +Flask>=3.0.0 + +# 环境配置 +python-dotenv>=1.0.0 + +# AI 与 LLM 集成 +langchain-openai>=0.0.5 +dashscope>=1.14.0 +requests>=2.31.0 diff --git a/aicodes/语音合成CosyVoice.md b/aicodes/语音合成CosyVoice.md new file mode 100644 index 0000000..9a52b33 --- /dev/null +++ b/aicodes/语音合成CosyVoice.md @@ -0,0 +1,274 @@ +# 语音合成CosyVoice Python SDK + +https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#915a935d871ak + +```python +dashscope.api_key = "sk-6fecb52bb96d4492bb7e58213eeeb00f" +``` + +### **流式调用** + +在同一个语音合成任务中分多次提交文本,并通过回调的方式实时获取合成结果。 + +**说明** + +- 流式输入时可多次调用`streaming_call`按顺序提交文本片段。服务端接收文本片段后自动进行分句: + + - 完整语句立即合成 + - 不完整语句缓存至完整后合成 + + 调用 `streaming_complete` 时,服务端会强制合成所有已接收但未处理的文本片段(包括未完成的句子)。 + +- 发送文本片段的间隔不得超过23秒,否则触发“request timeout after 23 seconds”异常。 + + 若无待发送文本,需及时调用 `streaming_complete`结束任务。 + + > 服务端强制设定23秒超时机制,客户端无法修改该配置。 + + ![image](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/0165103671/CAEQVRiBgMDb7PahrBkiIDVkNjEwOTMxYjEwOTRmOWFhMmI1OTRiY2Q3ZDgzZmE54709861_20241015153444.149.svg) + +1. 实例化SpeechSynthesizer类 + + 实例化[SpeechSynthesizer类](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#d6bc1f133f871)绑定[请求参数](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#2fe363ace1l4k)和[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)。 + +2. 流式传输 + + 多次调用[SpeechSynthesizer类](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#d6bc1f133f871)的`streaming_call`方法分片提交待合成文本,将待合成文本分段发送至服务端。 + + 在发送文本的过程中,服务端会通过[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)的`on_data`方法,将合成结果实时返回给客户端。 + + 每次调用`streaming_call`方法发送的文本片段(即`text`)长度不得超过2000字符,累计发送的文本总长度不得超过20万字符。 + +3. 结束处理 + + 调用[SpeechSynthesizer类](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#d6bc1f133f871)的`streaming_complete`方法结束语音合成。 + + 该方法会阻塞当前线程,直到[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)的`on_complete`或者`on_error`回调触发后才会释放线程阻塞。 + + 请务必确保调用该方法,否则可能会导致结尾部分的文本无法成功转换为语音。 + +~~~python +# coding=utf-8 +# +# pyaudio安装说明: +# 如果是macOS操作系统,执行如下命令: +# brew install portaudio +# pip install pyaudio +# 如果是Debian/Ubuntu操作系统,执行如下命令: +# sudo apt-get install python-pyaudio python3-pyaudio +# 或者 +# pip install pyaudio +# 如果是CentOS操作系统,执行如下命令: +# sudo yum install -y portaudio portaudio-devel && pip install pyaudio +# 如果是Microsoft Windows,执行如下命令: +# python -m pip install pyaudio + +import time +import pyaudio +import dashscope +from dashscope.api_entities.dashscope_response import SpeechSynthesisResponse +from dashscope.audio.tts_v2 import * + +from datetime import datetime + +def get_timestamp(): + now = datetime.now() + formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]") + return formatted_timestamp + +# 若没有将API Key配置到环境变量中,需将your-api-key替换为自己的API Key +# dashscope.api_key = "your-api-key" + +# 模型 +model = "cosyvoice-v2" +# 音色 +voice = "longxiaochun_v2" + + +# 定义回调接口 +class Callback(ResultCallback): + _player = None + _stream = None + + def on_open(self): + print("连接建立:" + get_timestamp()) + self._player = pyaudio.PyAudio() + self._stream = self._player.open( + format=pyaudio.paInt16, channels=1, rate=22050, output=True + ) + + def on_complete(self): + print("语音合成完成,所有合成结果已被接收:" + get_timestamp()) + + def on_error(self, message: str): + print(f"语音合成出现异常:{message}") + + def on_close(self): + print("连接关闭:" + get_timestamp()) + # 停止播放器 + self._stream.stop_stream() + self._stream.close() + self._player.terminate() + + def on_event(self, message): + pass + + def on_data(self, data: bytes) -> None: + print(get_timestamp() + " 二进制音频长度为:" + str(len(data))) + self._stream.write(data) + + +callback = Callback() + +test_text = [ + "流式文本语音合成SDK,", + "可以将输入的文本", + "合成为语音二进制数据,", + "相比于非流式语音合成,", + "流式合成的优势在于实时性", + "更强。用户在输入文本的同时", + "可以听到接近同步的语音输出,", + "极大地提升了交互体验,", + "减少了用户等待时间。", + "适用于调用大规模", + "语言模型(LLM),以", + "流式输入文本的方式", + "进行语音合成的场景。", +] + +# 实例化SpeechSynthesizer,并在构造方法中传入模型(model)、音色(voice)等请求参数 +synthesizer = SpeechSynthesizer( + model=model, + voice=voice, + format=AudioFormat.PCM_22050HZ_MONO_16BIT, + callback=callback, +) + + +# 流式发送待合成文本。在回调接口的on_data方法中实时获取二进制音频 +for text in test_text: + synthesizer.streaming_call(text) + time.sleep(0.1) +# 结束流式语音合成 +synthesizer.streaming_complete() + +# 首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时 +print('[Metric] requestId为:{},首包延迟为:{}毫秒'.format( + synthesizer.get_last_request_id(), + synthesizer.get_first_package_delay())) +~~~ + +## **请求参数** + +请求参数通过[SpeechSynthesizer类](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#d6bc1f133f871)的构造方法进行设置。 + +| | | | | | +| ---- | ---- | ---- | ---- | ---- | +| | | | | | + +| **参数** | **类型** | **默认值** | **是否必须** | **说明** | +| ---------------------- | -------------- | ---------- | ------------ | ------------------------------------------------------------ | +| model | str | - | 是 | 指定模型。不同版本的模型编码方式一致,但使用时须确保模型(`model`)与音色(`voice`)匹配:每个版本的模型只能使用本版本的默认音色或专属音色。 | +| voice | str | - | 是 | 指定语音合成所使用的音色。支持默认音色和专属音色:**默认音色**:参见**音色列表**章节。**专属音色**:通过[声音复刻](https://help.aliyun.com/zh/model-studio/voice-replica-1/)功能定制。使用复刻音色时,请确保声音复刻与语音合成使用同一账号。详细操作步骤请参见[CosyVoice声音复刻API](https://help.aliyun.com/zh/model-studio/cosyvoice-clone-api#da30eeebc4uwk)。**⚠️ 使用声音复刻系列模型合成语音时,仅能使用该模型复刻生成的专属音色,不能使用默认音色。****⚠️ 使用专属音色合成语音时,语音合成模型(**`**model**`**)必须与声音复刻模型(**`**target_model**`**)相同。** | +| format | enum | 因音色而异 | 否 | 指定音频编码格式及采样率。若未指定`format`,则合成音频采样率为22.05kHz,格式为mp3。**说明**默认采样率代表当前音色的最佳采样率,缺省条件下默认按照该采样率输出,同时支持降采样或升采样。可指定的音频编码格式及采样率如下:所有模型均支持的音频编码格式及采样率:AudioFormat.WAV_8000HZ_MONO_16BIT,代表音频格式为wav,采样率为8kHzAudioFormat.WAV_16000HZ_MONO_16BIT,代表音频格式为wav,采样率为16kHzAudioFormat.WAV_22050HZ_MONO_16BIT,代表音频格式为wav,采样率为22.05kHzAudioFormat.WAV_24000HZ_MONO_16BIT,代表音频格式为wav,采样率为24kHzAudioFormat.WAV_44100HZ_MONO_16BIT,代表音频格式为wav,采样率为44.1kHzAudioFormat.WAV_48000HZ_MONO_16BIT,代表音频格式为wav,采样率为48kHzAudioFormat.MP3_8000HZ_MONO_128KBPS,代表音频格式为mp3,采样率为8kHzAudioFormat.MP3_16000HZ_MONO_128KBPS,代表音频格式为mp3,采样率为16kHzAudioFormat.MP3_22050HZ_MONO_256KBPS,代表音频格式为mp3,采样率为22.05kHzAudioFormat.MP3_24000HZ_MONO_256KBPS,代表音频格式为mp3,采样率为24kHzAudioFormat.MP3_44100HZ_MONO_256KBPS,代表音频格式为mp3,采样率为44.1kHzAudioFormat.MP3_48000HZ_MONO_256KBPS,代表音频格式为mp3,采样率为48kHzAudioFormat.PCM_8000HZ_MONO_16BIT,代表音频格式为pcm,采样率为8kHzAudioFormat.PCM_16000HZ_MONO_16BIT,代表音频格式为pcm,采样率为16kHzAudioFormat.PCM_22050HZ_MONO_16BIT,代表音频格式为pcm,采样率为22.05kHzAudioFormat.PCM_24000HZ_MONO_16BIT,代表音频格式为pcm,采样率为24kHzAudioFormat.PCM_44100HZ_MONO_16BIT,代表音频格式为pcm,采样率为44.1kHzAudioFormat.PCM_48000HZ_MONO_16BIT,代表音频格式为pcm,采样率为48kHz除`cosyvoice-v1`外,其他模型支持的音频编码格式及采样率:音频格式为opus时,支持通过`bit_rate`参数调整码率。仅对1.24.0及之后版本的DashScope适用。AudioFormat.OGG_OPUS_8KHZ_MONO_32KBPS,代表音频格式为opus,采样率为8kHz,码率为32kbpsAudioFormat.OGG_OPUS_16KHZ_MONO_16KBPS,代表音频格式为opus,采样率为16kHz,码率为16kbpsAudioFormat.OGG_OPUS_16KHZ_MONO_32KBPS,代表音频格式为opus,采样率为16kHz,码率为32kbpsAudioFormat.OGG_OPUS_16KHZ_MONO_64KBPS,代表音频格式为opus,采样率为16kHz,码率为64kbpsAudioFormat.OGG_OPUS_24KHZ_MONO_16KBPS,代表音频格式为opus,采样率为24kHz,码率为16kbpsAudioFormat.OGG_OPUS_24KHZ_MONO_32KBPS,代表音频格式为opus,采样率为24kHz,码率为32kbpsAudioFormat.OGG_OPUS_24KHZ_MONO_64KBPS,代表音频格式为opus,采样率为24kHz,码率为64kbpsAudioFormat.OGG_OPUS_48KHZ_MONO_16KBPS,代表音频格式为opus,采样率为48kHz,码率为16kbpsAudioFormat.OGG_OPUS_48KHZ_MONO_32KBPS,代表音频格式为opus,采样率为48kHz,码率为32kbpsAudioFormat.OGG_OPUS_48KHZ_MONO_64KBPS,代表音频格式为opus,采样率为48kHz,码率为64kbps | +| volume | int | 50 | 否 | 合成音频的音量,取值范围:0~100。**重要**该字段在不同版本的DashScope SDK中有所不同:1.20.10及以后版本的SDK:volume1.20.10以前版本的SDK:volumn | +| speech_rate | float | 1.0 | 否 | 合成音频的语速,取值范围:0.5~2。0.5:表示默认语速的0.5倍速。1:表示默认语速。默认语速是指模型默认输出的合成语速,语速会因音色不同而略有不同。约每秒钟4个字。2:表示默认语速的2倍速。 | +| pitch_rate | float | 1.0 | 否 | 合成音频的语调,取值范围:0.5~2。 | +| bit_rate | int | 32 | 否 | 指定音频的[码率](https://opus-codec.org/),取值范围:6~510kbps。码率越大,音质越好,音频文件体积越大。仅在音频格式(`format`)为opus时可用。`cosyvoice-v1`模型不支持该参数。**说明**`bit_rate`需要通过`additional_params`参数进行设置: `synthesizer = SpeechSynthesizer(model="cosyvoice-v2", voice="longxiaochun_v2", format=AudioFormat.OGG_OPUS_16KHZ_MONO_16KBPS, additional_params={"bit_rate": 32})` | +| word_timestamp_enabled | bool | False | 否 | 是否开启字级别时间戳,默认关闭。仅cosyvoice-v2支持该功能。时间戳结果仅能通过回调接口获取**说明**`word_timestamp_enabled`需要通过`additional_params`参数进行设置: `synthesizer = SpeechSynthesizer(model="cosyvoice-v2", voice="longxiaochun_v2", callback=callback, # 时间戳结果仅能通过回调接口获取 additional_params={'word_timestamp_enabled': True})`**点击查看完整示例代码** | +| seed | int | 0 | 否 | 生成时使用的随机数种子,使合成的效果产生变化。默认值0。取值范围:0~65535。cosyvoice-v1不支持该功能。 | +| language_hints | list[str] | - | 否 | 合成文本语言提示,可选值为 `zh`(中文)或 `en`(英文),列表中仅第一个语言生效。仅cosyvoice-v3、cosyvoice-v3-plus支持该功能。此设置会影响阿拉伯数字等内容的读法。例如,当合成“123”时,若设置为`zh`,则读作“一百二十三”;而`en`则会读作“one hundred and twenty-three”。如果不设置,系统会根据文本内容自动判断并应用相应的合成规则。 | +| instruction | String | - | 否 | 设置提示词。仅cosyvoice-v3、cosyvoice-v3-plus支持该功能。目前仅支持设置情感。格式:“`你说话的情感是<情感值>。`”(注意,结尾一定不要遗漏句号,使用时将“`<情感值>`”替换为具体的情感值,例如替换为`neutral`)。示例:“`你说话的情感是neutral。`”支持的情感值:`neutral`、`fearful`、`angry`、`sad`、`surprised`、`happy`、`disgusted`。 | +| callback | ResultCallback | - | 否 | [回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s). | + +## **关键接口** + +### `SpeechSynthesizer`类 + +`SpeechSynthesizer`通过“`from dashscope.audio.tts_v2 import *`”方式引入,提供语音合成的关键接口。 + +| | | | | +| ---- | ---- | ---- | ---- | +| | | | | + +| **方法** | **参数** | **返回值** | **描述** | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------ | +| `def call(self, text: str, timeout_millis=None)` | `text`:待合成文本`timeout_millis`:阻塞线程的超时时间,单位为毫秒,不设置或值为0时不生效 | 没有指定`ResultCallback`时返回二进制音频数据,否则返回None | 将整段文本(无论是纯文本还是包含[SSML](https://help.aliyun.com/zh/model-studio/introduction-to-cosyvoice-ssml-markup-language)的文本)转换为语音。在创建`SpeechSynthesizer`实例时,存在以下两种情况:没有指定`ResultCallback`:`call`方法会阻塞当前线程直到语音合成完成并返回二进制音频数据。使用方法请参见[同步调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#8341058094tc3)。指定了`ResultCallback`:`call`方法会立刻返回None,并通过[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)的`on_data`方法返回语音合成的结果。使用方法请参见[异步调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#cc2a504f344s2)。**重要**每次调用`call`方法前,需要重新初始化`SpeechSynthesizer`实例。 | +| `def streaming_call(self, text: str)` | `text`:待合成文本片段 | 无 | 流式发送待合成文本(不支持包含SSML的文本)。您可以多次调用该接口,将待合成文本分多次发送给服务端。合成结果通过[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)的`on_data`方法获取。使用方法请参见[流式调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#ba023aacfbr84)。 | +| `def streaming_complete(self, complete_timeout_millis=600000)` | `complete_timeout_millis`:等待时间,单位为毫秒 | 无 | 结束流式语音合成。该方法阻塞当前线程N毫秒(具体时长由`complete_timeout_millis`决定),直到任务结束。如果`completeTimeoutMillis`设置为0,则无限期等待。默认情况下,如果等待时间超过10分钟,则停止等待。使用方法请参见[流式调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#ba023aacfbr84)。**重要**在[流式调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#ba023aacfbr84)时,请务必确保调用该方法,否则可能会出现合成语音缺失的问题。 | +| `def get_last_request_id(self)` | 无 | 上一个任务的request_id | 获取上一个任务的request_id。 | +| `def get_first_package_delay(self)` | 无 | 首包延迟 | 获取首包延迟(一般在500ms左右)。首包延迟是开始发送文本和接收第一个音频包之间的时间,单位为毫秒。在任务完成后使用。首次发送文本时需建立 WebSocket 连接,因此首包延迟会包含连接建立的耗时。 | +| `def get_response(self)` | 无 | 最后一次报文 | 获取最后一次报文(为JSON格式的数据),可以用于获取task-failed报错。 | + +### **回调接口(**`ResultCallback`) + +[异步调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#cc2a504f344s2)或[流式调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#ba023aacfbr84)时,服务端会通过回调的方式,将关键流程信息和数据返回给客户端。您需要实现回调方法,处理服务端返回的信息或者数据。 + +通过“`from dashscope.audio.tts_v2 import *`”方式引入。 + +点击查看示例 + +| | | | | +| ---- | ---- | ---- | ---- | +| | | | | + +| **方法** | **参数** | **返回值** | **描述** | +| ------------------------------------------- | ---------------------------------- | ---------- | ------------------------------------------------------------ | +| `def on_open(self) -> None` | 无 | 无 | 当和服务端建立连接完成后,该方法立刻被回调。 | +| `def on_event( self, message: str) -> None` | `message`:服务端返回的信息 | 无 | 当服务有回复时会被回调。`message`为JSON字符串,解析可获取Task ID(`task_id`参数)、本次请求中计费的有效字符数(`characters`参数)等信息。 | +| `def on_complete(self) -> None` | 无 | 无 | 当所有合成数据全部返回(语音合成完成)后被回调。 | +| `def on_error(self, message) -> None` | `message`:异常信息 | 无 | 发生异常时该方法被回调。 | +| `def on_data(self, data: bytes) -> None` | `data`:服务器返回的二进制音频数据 | 无 | 当服务器有合成音频返回时被回调。您可以将二进制音频数据合成为一个完整的音频文件后使用播放器播放,也可以通过支持流式播放的播放器实时播放。**重要**流式语音合成中,对于mp3/opus等压缩格式,音频分段传输需使用流式播放器,不可逐帧播放,避免解码失败。支持流式播放的播放器:ffmpeg、pyaudio (Python)、AudioFormat (Java)、MediaSource (Javascript)等。将音频数据合成完整的音频文件时,应以追加模式写入同一文件。流式语音合成的wav/mp3 格式音频仅首帧包含头信息,后续帧为纯音频数据。 | +| `def on_close(self) -> None` | 无 | 无 | 当服务已经关闭连接后被回调。 | + +## **响应结果** + +服务器返回二进制音频数据: + +- [同步调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#8341058094tc3):对[SpeechSynthesizer类](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#d6bc1f133f871)的`call`方法返回的二进制音频数据进行处理。 +- [异步调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#cc2a504f344s2)或[流式调用](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#ba023aacfbr84):对[回调接口(ResultCallback)](https://help.aliyun.com/zh/model-studio/cosyvoice-python-sdk#85d698b9f9g8s)的`on_data`方法的参数(bytes类型数据)进行处理。 + + + +## **错误码** + +如遇报错问题,请参见[错误信息](https://help.aliyun.com/zh/model-studio/error-code)进行排查。 + +若问题仍未解决,请加入[开发者群](https://github.com/aliyun/alibabacloud-bailian-speech-demo)反馈遇到的问题,并提供Request ID,以便进一步排查问题。 + +## **音色列表** + +当前默认支持的音色如下表所示。若您需要更加个性化的音色,可通过声音复刻功能免费定制专属音色,详情请参见[使用复刻的音色进行语音合成](https://help.aliyun.com/zh/model-studio/cosyvoice-clone-api#b6d3449fb336v)。 + +进行语音合成时,`model`参数必须与所选音色对应,否则调用将失败。 + +待合成文本(text)应使用与所选音色语种一致的语言,否则可能出现发音错误或不自然 + +cosyvoice-v3 + +cosyvoice-v2 + +cosyvoice-v1 + +| **音色** | **音频试听(右键保存音频)** | **voice参数** | **适用场景** | **语言** | +| -------- | ---------------------------- | ------------- | ------------------------------------------------------------ | --------------- | +| 龙婉 | | longwan | 语音助手、导航播报、聊天数字人 | 中文普通话 | +| 龙橙 | | longcheng | 语音助手、导航播报、聊天数字人 | 中文普通话 | +| 龙华 | | longhua | 语音助手、导航播报、聊天数字人 | 中文普通话 | +| 龙小淳 | | longxiaochun | 语音助手、导航播报、聊天数字人 | 中文普通话+英文 | +| 龙小夏 | | longxiaoxia | 语音助手、聊天数字人 | 中文普通话 | +| 龙小诚 | | longxiaocheng | 语音助手、导航播报、聊天数字人 | 中文普通话+英文 | +| 龙小白 | | longxiaobai | 聊天数字人、有声书、语音助手 | 中文普通话 | +| 龙老铁 | | longlaotie | 新闻播报、有声书、语音助手、直播带货、导航播报 | 中文东北口音 | +| 龙书 | | longshu | 有声书、语音助手、导航播报、新闻播报、智能客服 | 中文普通话 | +| 龙硕 | | longshuo | 语音助手、导航播报、新闻播报、客服催收 | 中文普通话 | +| 龙婧 | | longjing | 语音助手、导航播报、新闻播报、客服催收 | 中文普通话 | +| 龙妙 | | longmiao | 客服催收、导航播报、有声书、语音助手 | 中文普通话 | +| 龙悦 | | longyue | 语音助手、诗词朗诵、有声书朗读、导航播报、新闻播报、客服催收 | 中文普通话 | +| 龙媛 | | longyuan | 有声书、语音助手、聊天数字人 | 中文普通话 | +| 龙飞 | | longfei | 会议播报、新闻播报、有声书 | 中文普通话 | +| 龙杰力豆 | | longjielidou | 新闻播报、有声书、聊天助手 | 中文普通话+英文 | +| 龙彤 | | longtong | 有声书、导航播报、聊天数字人 | 中文普通话 | +| 龙祥 | | longxiang | 新闻播报、有声书、导航播报 | 中文普通话 | +| Stella | | loongstella | 语音助手、直播带货、导航播报、客服催收、有声书 | 中文普通话+英文 | +| Bella | | loongbella | 语音助手、客服催收、新闻播报、导航播报 | 中文普通话 | \ No newline at end of file diff --git a/data/心血管疾病.xlsx b/data/心血管疾病.xlsx new file mode 100644 index 0000000..6a4700e Binary files /dev/null and b/data/心血管疾病.xlsx differ