# 语音助手系统提示词可配置与简短回答实现方案

## 一、需求来源

1. **简短回答**：语音助手当前回答偏长，不适合语音播报场景，需要通过提示词要求模型回答尽量简短，但不再使用固定字数上限。
2. **本地可配置**：系统提示词硬编码在 C++ 源码中，修改需重新编译。改为从本地 INI 配置文件加载，方便运维和用户自定义。

## 二、改动前现状分析

### 已实现

- `mainwindow.cpp` 中定义了硬编码常量 `kPlainTextReplyPrompt`：
  ```cpp
  const QString kPlainTextReplyPrompt = QStringLiteral(
      "请仅使用纯文本回答，不要使用Markdown或HTML格式，不要使用代码块、列表标记、加粗或斜体。用户问题：");
  ```
- `submitFinalText()` 中拼接提示词 + 用户文本发送给 Gateway：
  ```cpp
  QString fullContent = kPlainTextReplyPrompt + text;
  ```
- `gatewaychatclient.cpp` 中已有 `QSettings` 读取 `~/.config/kylin-aiassistant/settings.ini` 的先例（用于 gateway token、host、port 配置）。

### 问题

1. 提示词硬编码，无法运行时修改
2. 提示词未约束回答长度，语音播报体验差

## 三、设计方案

### 3.1 提示词增加简短约束

在现有提示词末尾、`"用户问题："` 之前，追加：

> 回答尽量简短。

### 3.2 提示词本地可配置

**配置文件路径**：`~/.config/kylin-aiassistant/voice-assistant.ini`

**配置格式**（INI）：
```ini
[prompt]
system=请仅使用纯文本回答，不要使用Markdown或HTML格式，不要使用代码块、列表标记、加粗或斜体。回答尽量简短。用户问题：
```

**加载策略**：

```
启动时 loadSystemPrompt()
  → 配置文件存在且 prompt/system 非空？
    → 返回配置文件值
  → 否则
    → 返回内置默认值 kDefaultSystemPrompt
```

**设计决策**：

| 决策点 | 选择 | 理由 |
|--------|------|------|
| 配置文件格式 | INI（QSettings::IniFormat） | 与项目已有 `settings.ini` 风格一致，Qt 原生支持 |
| 配置文件路径 | `~/.config/kylin-aiassistant/voice-assistant.ini` | 遵循 XDG 规范，与已有 `settings.ini` 同级目录，独立文件避免耦合 |
| 配置键名 | `[prompt]` / `system` | 语义清晰，预留未来扩展（如可增加 `prompt/screen` 等场景化提示词） |
| 未配置时行为 | 使用内置默认值 | 保证删配置文件即可恢复出厂行为 |
| 加载时机 | 构造函数中一次性加载 | 提示词在运行期间无需热更新，避免过度设计 |

## 四、文件改动详情

### 4.1 mainwindow.cpp

**新增头文件**：

```cpp
#include <QDir>
#include <QSettings>
```

**替换硬编码常量为默认值 + 加载函数**：

原代码：
```cpp
const QString kPlainTextReplyPrompt = QStringLiteral(
    "请仅使用纯文本回答，不要使用Markdown或HTML格式，不要使用代码块、列表标记、加粗或斜体。用户问题：");
```

新代码：
```cpp
const QString kDefaultSystemPrompt = QStringLiteral(
    "请仅使用纯文本回答，不要使用Markdown或HTML格式，不要使用代码块、列表标记、加粗或斜体。"
    "回答尽量简短。用户问题：");

QString voiceAssistantConfigPath()
{
    return QDir::homePath() + QStringLiteral("/.config/kylin-aiassistant/voice-assistant.ini");
}

QString loadSystemPrompt()
{
    const QString configPath = voiceAssistantConfigPath();
    if (QFile::exists(configPath)) {
        QSettings ini(configPath, QSettings::IniFormat);
        const QString prompt = ini.value(QStringLiteral("prompt/system")).toString();
        if (!prompt.trimmed().isEmpty()) {
            return prompt;
        }
    }
    return kDefaultSystemPrompt;
}
```

**构造函数中加载提示词**：

```cpp
// 在 ui->setupUi(this) 之后
m_systemPrompt = loadSystemPrompt();
```

**submitFinalText 使用成员变量**：

原代码：
```cpp
QString fullContent = kPlainTextReplyPrompt + text;
```

新代码：
```cpp
QString fullContent = m_systemPrompt + text;
```

### 4.2 mainwindow.h

新增成员变量：

```cpp
/** 从本地配置文件加载的系统提示词，若配置文件不存在则使用内置默认值 */
QString m_systemPrompt;
```

### 4.3 新建配置文件

路径：`~/.config/kylin-aiassistant/voice-assistant.ini`

```ini
[prompt]
system=请仅使用纯文本回答，不要使用Markdown或HTML格式，不要使用代码块、列表标记、加粗或斜体。回答尽量简短。用户问题：
```

## 五、关键流程图

### 5.1 提示词加载流程

```
MainWindow 构造
  → ui->setupUi(this)
  → m_systemPrompt = loadSystemPrompt()
    → voiceAssistantConfigPath() → ~/.config/kylin-aiassistant/voice-assistant.ini
    → QFile::exists(configPath)?
      → 是：QSettings 读取 prompt/system
        → 非空？返回配置值
        → 空？返回 kDefaultSystemPrompt
      → 否：返回 kDefaultSystemPrompt
```

### 5.2 消息发送流程（改动点）

```
submitFinalText(text)
  → fullContent = m_systemPrompt + text
  → 屏幕截图? → 拼接截图提示
  → m_gatewayClient->sendMessage(payload)
```

## 六、测试用例覆盖

| 测试场景 | 预期结果 |
|----------|----------|
| 配置文件存在且值非空 | 使用配置文件中的提示词 |
| 配置文件存在但值为空 | 退回使用内置默认值 |
| 配置文件不存在 | 退回使用内置默认值 |
| 删除配置文件后重启 | 自动使用内置默认值，功能正常 |
| 修改配置文件后重启 | 使用新的提示词值 |
| 默认提示词下模型回答 | 回答尽量简短，纯文本格式 |

## 七、风险与缓解

| 风险 | 缓解措施 |
|------|----------|
| 配置文件编码问题（如含特殊字符） | QSettings 默认 UTF-8，与 Qt 生态一致 |
| 配置目录不存在 | `loadSystemPrompt()` 仅读取，目录不存在时退回默认值，不主动创建目录 |
| 配置值过长导致 Token 浪费 | 由用户自行控制，内置默认值已为合理长度 |
| 删除配置文件无法恢复默认 | 内置 `kDefaultSystemPrompt` 兜底，无需配置文件即可运行 |
