# AI Agent SDK 接入指南

> 百度多模态对话 AI Agent 嵌入式终端接入 SDK
> 基于杰理 AC79 AIoT SDK 的 WebSocket 实时语音交互应用

---

## 一、SDK 简介

本 SDK 通过 WebSocket 长连接，将嵌入式设备接入百度多模态对话 AI Agent 服务，实现实时语音对话、Function Call、图片生成与视觉理解等能力。

### 功能特性

- 实时语音交互：WebSocket 长连接，低延迟语音对话
- 三种交互模式：按键说话（PRESS）、唤醒词（WAKEUP）、连续对话（CONTINUOUS）
- 多场景支持：AI 玩具、AI 拍学机、AI 打印机三个参考场景，可自定义扩展
- 音频编码可选：`raw16k`（PCM 直通）或 `g722`（宽带压缩，节省上行带宽）
- License 认证：基于设备信息的授权机制
- AEC 回声消除：连续对话模式下支持全双工打断
- 双 WebSocket 实现：杰理官方实现（默认）与 Mongoose 实现，编译期二选一
- 云端能力：情绪识别、声纹识别、云端音乐播控、文生图、视觉理解

### 分层结构

```
aiagent_sdk/
├── include/          # 公共 API（对外只需关注这几个头文件）
│   ├── aiagent_engine.h    # 引擎 API
│   ├── aiagent_scene.h     # 场景注册 API
│   ├── aiagent_config.h    # 配置结构体
│   └── aiagent_types.h     # 类型 / 枚举 / 回调定义
├── config/           # 凭证与默认参数
├── core/             # 核心逻辑（平台无关，无需修改）
├── scenes/           # 场景插件（选用或自定义）
├── platform/         # 平台 HAL 实现
│   └── jieli/        #   杰理 AC79 参考实现
└── third_party/      # 第三方库（G.722、Mongoose 等）
```

---

## 二、接入方式

| 方式 | 适用场景 | 工作量 |
|------|---------|--------|
| A. 使用已有场景 | 产品形态与 AI 玩具 / 拍学机 / 打印机相近 | 最小，改配置即可 |
| B. 自定义场景 | 新产品形态，需要特殊业务逻辑 | 中等，实现 scene_ops |
| C. 直接使用引擎 API | 不使用场景管理，完全自主控制 | 较大，自行管理生命周期 |
| D. 适配新硬件平台 | 非杰理平台（ESP32 / BK / TXW81 等） | 较大，实现 HAL 接口 |

---

## 三、快速开始（方式 A：使用已有场景）

### 步骤 1：配置凭证

在百度智能云控制台申请凭证，然后填入 `aiagent_sdk/config/aiagent_credentials.h`：

```c
/* 连接模式：AKSK / USERSERVER / TEST，编译期选择一种 */
#define AIAGENT_CONN_MODE     AIAGENT_CONN_MODE_AKSK

/* 接入区域：MAINLAND / HONGKONG / AMERICA */
#define AIAGENT_REGION        AIAGENT_REGION_MAINLAND

/* 公共参数 */
#define AIAGENT_APPID         "your_app_id"
#define AIAGENT_LICENSE_KEY   "your_license_key"

/* AKSK 模式参数 */
#define AIAGENT_ACCESS_KEY    "your_access_key"
#define AIAGENT_SECRET_KEY    "your_secret_key"
```

> 凭证请在 [百度智能云 RTC 控制台](https://console.bce.baidu.com/rtc/#/rtc/interaction) 获取，切勿将真实凭证提交到公开仓库。
> WebSocket 接入地址由 `AIAGENT_REGION` 自动选择（大陆 / 香港 / 美国），无需手动填写。

### 步骤 2：选择场景

SDK 内置三个参考场景，可直接使用：

| 场景名 | 头文件 | 注册函数 | 说明 |
|--------|--------|----------|------|
| `ai_toy` | `scene_ai_toy.h` | `scene_ai_toy_get_ops()` | AI 玩具：角色 / 音色切换、情绪 / 声纹、音乐播控 |
| `ai_study` | `scene_ai_study.h` | `scene_ai_study_get_ops()` | AI 拍学机：拍万物 / 图生图 / 多模态子场景 |
| `ai_printer` | `scene_ai_printer.h` | `scene_ai_printer_get_ops()` | AI 打印机：按键说话、文生图、JPEG 输出 |

### 步骤 3：注册并启动场景

```c
#include "aiagent_scene.h"
#include "scene_ai_toy.h"

void app_main(void)
{
    /* 等待网络就绪 */
    wait_network_connected();

    /* 注册并启动场景 */
    aiagent_scene_register(scene_ai_toy_get_ops());
    aiagent_scene_start("ai_toy");
}
```

> 杰理平台的完整集成示例见 `project/jieli/aiagent_app.c`：在应用启动时注册场景，网络就绪（`NET_EVENT_CONNECTED`）后自动 `aiagent_engine_start()`，并已接好按键事件分发，可直接参考。

### 步骤 4：接入按键事件

按键说话模式需要把设备按键事件转发给引擎。杰理平台已在 `aiagent_app.c` 中接好，其它平台按下列方式接入：

```c
/* 按键按下 */
void on_key_press(void)
{
    aiagent_engine_t engine = aiagent_scene_get_engine();
    if (engine) {
        aiagent_engine_notify_key_press(engine);
    }
}

/* 按键松开 */
void on_key_release(void)
{
    aiagent_engine_t engine = aiagent_scene_get_engine();
    if (engine) {
        aiagent_engine_notify_key_release(engine);
    }
}
```

### 步骤 5：编译烧录

- 工具链：杰理 pi32 工具链（默认路径 `C:/JL/pi32`），随杰理 SDK 环境安装。
- Code::Blocks：打开 `project/jieli/board/wl82/SPEED_AGENT.cbp`，选择 Release 编译。
- 命令行：进入 `project/jieli/board/wl82/` 执行 `make`。
- 使用杰理下载工具烧录固件；设备上电联网后，听到提示音即可开始语音交互。

### 步骤 6：WiFi 配网（BLE）

设备默认不预置 WiFi，首次使用通过手机 BLE 配网写入 WiFi 名称和密码，配网成功后凭证保存到设备，下次开机自动连接。

> 若 `INIT_SSID/INIT_PWD` 留空且设备从未配网，开机不会自动联网，需先完成一次 BLE 配网。

设备端：

- 双击 `KEY_OK` 进入 / 退出配网模式（再次双击可退出）。
- BLE 设备名前缀 `JL` 不可修改，否则小程序搜索不到设备；设备名可在 `le_net_cfg.c` 中调整。

手机端：

1. 打开手机蓝牙。
2. 微信扫描杰理配网小程序二维码，进入小程序（二维码见下方杰理官方文档）。
3. 在小程序中填写 WiFi 名称和密码，点击“搜索蓝牙设备”，等待连接成功。
4. 点击“蓝牙设备配网”，设备获取到 WiFi 名称和密码后自动连接。

配网流程与小程序二维码参考杰理官方文档：
https://doc.zh-jieli.com/AC79/zh-cn/master/module_example/bluetooth/ble_net_cfg.html

> 换网只需重新配网即可；编译期 `INIT_SSID/INIT_PWD` 仅作为默认值，正式产品建议留空、由配网写入。

---

## 四、方式 B：自定义场景

### 步骤 1：创建场景文件

```
scenes/scene_my_product/
├── scene_my_product.h
├── scene_my_product_config.h
└── scene_my_product.c
```

### 步骤 2：实现场景接口

```c
#include "aiagent_scene.h"
#include "aiagent_engine.h"
#include "aiagent_credentials.h"

static aiagent_engine_t s_engine = NULL;

/* 场景配置：连接 URL 由引擎层根据配置构建 */
static const aiagent_config_t s_config = {
    .ws_host        = AIAGENT_WS_HOST,

    /* 凭证 */
    .appid          = AIAGENT_APPID,
    .license_key    = AIAGENT_LICENSE_KEY,
    .access_key     = AIAGENT_ACCESS_KEY,
    .secret_key     = AIAGENT_SECRET_KEY,

    /* 上行音频编码：raw16k 直通 / g722 压缩 */
    .audio_codec    = "raw16k",

    /* 云端会话参数 */
    .cfg = {
        .tts_sayhi  = "你好，我是你的 AI 助手",
        .audiocodec = "raw16k",
    },

    /* 本地运行参数 */
    .audio_sample_rate = 16000,
    .audio_channels    = 1,
    .default_mode      = AIAGENT_INTERACT_CONTINUOUS,
    .aec_enable        = true,
};

static const aiagent_config_t *get_config(void) { return &s_config; }

static int  on_init(aiagent_engine_t engine)  { s_engine = engine; return 0; }
static void on_start(aiagent_engine_t engine)  { }
static void on_stop(aiagent_engine_t engine)   { s_engine = NULL; }
static void on_destroy(void)                    { }

/* 自定义 Function Call：处理完成后回复结果 */
static int on_fc(const char *session_id, const char *name, const char *params)
{
    /* 在此处理业务 FC，成功返回 1，未处理返回 0 交给引擎兜底 */
    aiagent_engine_send_fc_result(s_engine, session_id, "ok", "done");
    return 1;
}

static const aiagent_scene_ops_t s_ops = {
    .name             = "my_product",
    .get_config       = get_config,
    .on_init          = on_init,
    .on_start         = on_start,
    .on_stop          = on_stop,
    .on_destroy       = on_destroy,
    .on_function_call = on_fc,
};

const aiagent_scene_ops_t *scene_my_product_get_ops(void)
{
    return &s_ops;
}
```

### 步骤 3：注册并启动

```c
aiagent_scene_register(scene_my_product_get_ops());
aiagent_scene_start("my_product");
```

> `.audio_codec` 控制 SDK 本地上行编码 / 下行解码，`.cfg.audiocodec` 是云端会话参数，两者需保持一致。

---

## 五、方式 C：直接使用引擎 API

不使用场景管理，直接控制引擎生命周期：

```c
#include "aiagent_engine.h"

static void on_state(aiagent_state_t old_s, aiagent_state_t new_s, void *ctx)
{
    printf("state: %d -> %d\n", old_s, new_s);
}
static void on_asr(const char *text, bool is_final, void *ctx)
{
    printf("ASR: %s (final=%d)\n", text, is_final);
}

void start_ai_agent(void)
{
    aiagent_callbacks_t cbs = {0};
    cbs.on_state_change = on_state;
    cbs.on_asr_result   = on_asr;

    aiagent_engine_t engine = aiagent_engine_create(&cbs);

    aiagent_config_t config = { /* 按需填写，参见 aiagent_config.h */ };

    aiagent_engine_init(engine, &config);
    aiagent_engine_start(engine);   /* 连接服务器并开始录放 */

    /* ... 运行中 ... */

    aiagent_engine_stop(engine);
    aiagent_engine_destroy(engine);
}
```

---

## 六、方式 D：适配新硬件平台

在 `platform/<your_platform>/` 下实现 HAL 接口，核心逻辑无需改动。

| 接口 | 优先级 | 说明 |
|------|--------|------|
| `hal_log.c` | P0 | 日志输出 |
| `hal_mutex.c` | P0 | 互斥锁 |
| `hal_semaphore.c` | P0 | 信号量 |
| `hal_thread.c` | P0 | 线程 + 消息队列 |
| `hal_system.c` | P0 | 系统时间 / 堆信息 |
| `hal_websocket.c` | P1 | WebSocket 客户端 |
| `hal_http.c` | P1 | HTTP 客户端（AKSK 鉴权） |
| `hal_device_info.c` | P1 | 设备 UUID / MAC / 版本号 |
| `hal_audio_recorder.c` | P2 | 麦克风 PCM 采集 |
| `hal_audio_player.c` | P2 | 流式 PCM 播放 |
| `hal_local_player.c` | P2 | 本地提示音播放 |
| `hal_json.c` / `hal_file.c` | P3 | 可直接复用杰理实现 |
| `hal_timer.c` | P3 | 软件定时器 |

接口规范：使用不透明句柄，`create()` 失败返回 `NULL`，操作函数返回 `0` 成功、负数失败，`destroy()` 允许传入 `NULL`。

---

## 七、交互模式

| 模式 | 交互方式 | AEC | 打断方式 | 适用场景 |
|------|---------|-----|---------|---------|
| 按键说话（PRESS） | 按住按键收音 | 否 | 按键 | 打印机、精确控制 |
| 唤醒词（WAKEUP） | 唤醒后对话 | 否 | 唤醒词 | 家居、车载 |
| 连续对话（CONTINUOUS） | 随时说话 | 是 | 任意语音 | 自然对话（推荐） |

- 默认模式由场景配置 `.default_mode` 决定，运行时可通过 `aiagent_engine_switch_mode()` 切换。
- 按键说话模式下，仅在按键按下区间上行音频；松开后停止上行。

---

## 八、Function Call

### 参考实现

`aiagent_sdk/core/src/function_call/function_call_menu.c` 提供了一个菜单类 FC 参考实现，云端统一下发 `menu_switch`，端侧按动作类别路由：

| 动作类别 | 说明 |
|----------|------|
| `adjust_volume` | 调节音量（up / down / set） |
| `switch_mode` | 切换交互模式（press / wakeup / continuous） |
| `switch_role` | 切换场景角色（按名称 / 循环下一个） |
| `switch_voice` | 切换 TTS 音色（按名称查表得到音色 ID 后下发） |

> 音色切换需要在场景中配置「音色显示名 → 音色 ID」映射表，端侧按用户说出的显示名查表得到云端 `vcn`。参见 `scene_ai_toy.c`。

### 自定义 FC

在场景的 `on_function_call` 回调中处理，处理完成后回复结果：

```c
aiagent_engine_send_fc_result(engine, session_id, "ok", "done");
```

---

## 九、常用运行时 API

```c
/* 按键 / 唤醒 */
aiagent_engine_notify_key_press(engine);
aiagent_engine_notify_key_release(engine);
aiagent_engine_notify_key_long_press(engine);
aiagent_engine_notify_wakeword(engine);

/* 打断当前 TTS */
aiagent_engine_interrupt(engine);

/* 文本输入 / 直接 TTS */
aiagent_engine_send_text(engine, "今天天气怎么样");
aiagent_engine_send_tts_direct(engine, "开始工作了");

/* 运行时配置 */
aiagent_engine_set_volume(engine, 50);                        /* 音量 0-100 */
aiagent_engine_switch_mode(engine, AIAGENT_INTERACT_CONTINUOUS);
aiagent_engine_switch_voice(engine, "1000382");               /* 音色 ID */
aiagent_engine_update_system_prompt(engine, "{\"prompt\":\"你是一个儿童故事机\"}");
aiagent_engine_set_enhance_query(engine,
    "{\"enhance_type\":\"3\",\"pre_query\":\"生成一张\",\"post_query\":\"的图片\"}");
```

---

## 十、回调事件

| 回调 | 触发时机 | 用途 |
|------|---------|------|
| `on_state_change` | 状态迁移 | LED 指示、UI 更新 |
| `on_asr_result` | ASR 识别结果 | 显示识别文本 |
| `on_asr_result_ext` | ASR 识别结果（含情绪 / 声纹） | 情绪 / 声纹扩展处理 |
| `on_ai_response` | AI 回复（流式） | 显示回复文本 |
| `on_audio_data` | 收到 PCM 音频 | 自定义音频处理 |
| `on_function_call` | 服务端 FC 调用 | 执行设备动作（返回 1 已处理，0 交引擎兜底） |
| `on_tts_event` | TTS 开始 / 结束 | 播放动画同步 |
| `on_event` | 通用事件 | VOICE_COMING、图片生成结果等 |
| `on_error` | 错误发生 | 错误处理 / 重连 |
| `on_license_result` | 鉴权结果 | 授权状态显示 |

---

## 十一、音频与编码

| 参数 | 数值 |
|------|------|
| 采样率 | 16000 Hz |
| 位深 | 16 bit |
| 声道 | 1（单声道） |
| 帧长 | 20 ms |
| 每帧 PCM | 640 字节 |

编码方式由 `.audio_codec` / `.cfg.audiocodec` 决定：

- `raw16k`：PCM 直通，上行零拷贝。
- `g722`：宽带 16 kHz / 64 kbps 压缩，20 ms 帧 640B PCM 压缩为约 160B，节省上行带宽；编解码在 SDK 软件层完成，本地录音 / 播放仍为 PCM。

---

## 十二、WebSocket 实现选择

SDK 支持两种 WebSocket 实现，编译期二选一（不可同时启用）：

| 实现 | 宏定义 | 适用场景 |
|------|--------|---------|
| 杰理官方（默认） | `WS_IMPL_JIELI` | 使用杰理平台内置 WS 能力 |
| Mongoose | `WS_IMPL_MONGOOSE` | 需要独立 WS/WSS 协议栈（mbedTLS + lwIP） |

切换到 Mongoose 实现：

```bash
make WS_IMPL=mongoose
```

或在 `aiagent_sdk/config/aiagent_defaults.h` 中定义 `WS_IMPL_MONGOOSE`。两个宏同时定义会触发编译错误。

---

## 十三、常见问题

**Q：没有声音？**
1. 确认 License 认证通过：日志出现 `[E]:[LIC]:[RES]:[PASS]`。
2. 确认 `AIAGENT_LICENSE_KEY` 正确、设备已在控制台注册。

**Q：按键说话模式下不按键也在收音？**
确认场景 `.default_mode` 为 `AIAGENT_INTERACT_PRESS`；该模式仅在按键按下区间上行音频。

**Q：连续对话模式无法打断？**
确认场景 `.aec_enable = true`，连续对话依赖 AEC 回声消除。

**Q：切换音色后无 TTS？**
音色需按「显示名 → 音色 ID」映射后下发，`vcn` 必须是有效音色 ID，而非中文显示名。

**Q：WebSocket 连接失败？**
1. 确认 WiFi 正常、能访问外网。
2. 确认 APPID / AK / SK / 区域配置正确。
3. 查看 `[CONN]` / `[WS]` 相关日志。

**Q：如何配置或更换 WiFi？**
双击 `KEY_OK` 进入 BLE 配网，用手机小程序写入 WiFi，详见“步骤 6：WiFi 配网（BLE）”。

**Q：如何调试？**
SDK 使用分级日志：

```
[BD][D][TAG] 调试信息
[BD][I][TAG] 一般信息
[BD][W][TAG] 警告
[BD][E][TAG] 错误
```

---

## 十四、硬件要求

| 项目 | 要求 |
|------|------|
| 芯片 | AC79xx 系列 |
| Flash | >= 8 MB |
| SDRAM | >= 8 MB |
| WiFi | 2.4 GHz |

---

## 十五、相关链接

- 百度大模型互动官网文档：https://cloud.baidu.com/doc/RTC/s/Xm8y487ix
- 百度 RTC WebSocket API：https://cloud.baidu.com/doc/RTC/s/Jmakuvimy
- 杰理 AC79 SDK 文档：https://doc.zh-jieli.com/AC79/zh-cn/master/index.html
