feat: enable S2S with native Viking memory

This commit is contained in:
Codex Local
2026-09-06 17:27:49 +08:00
parent 0c2151ace9
commit d60f5d9a9b
15 changed files with 223 additions and 42 deletions
+2 -2
View File
@@ -10,12 +10,12 @@
- 固件和设备端都不包含火山 `Product Secret`;
- SaaS 使用平台设备身份为 EchoEar 动态申请火山 `Device Secret`;
- EchoEar 只保存自己的平台设备凭据和火山单设备凭据;
- 每次对话前由 SaaS 下发 Bot、人设、记忆上下文和会话配置;
- 每次对话前由 SaaS 下发 Bot、人设、绑定用户的非密钥 Viking 查询范围和会话配置;
- 字幕和最终轮次回传 SaaS,供会话记录与长期记忆处理。
当前测试硬件:ESP32-S3 EchoEar,16 MB Flash、16 MB PSRAM,硬件标识由 STA MAC 生成(当前板为 `echoear-b43a451634a8`)。
每次申请对话前,先通过 `/device/v2/bootstrap` 上报实际固件版本并检查 `firmware_compatible`,再获取配置和创建会话。固件版本为 `1.0.0-echoear-platform.1`;本工程基于 Embedded Kit 2.0,硬件智能体 SDK 版本独立上报为 `2.0.0`。会话能力中的 `sdk_version` 不能用固件版本代替。会话尚未创建时保持唤醒采集,不重复拆建音频管线。SPI 屏幕仅在 `on_color_trans_done` 后释放 GFX 帧缓冲,避免异步传输期间提前复用缓冲区。运行状态用眼睛动画表示,避免 80 MHz 下中文逐像素渲染长期占用 CPU:快速眨眼表示聆听,转圈表示思考,正常眼睛表示待机或回答。
每次申请对话前,先通过 `/device/v2/bootstrap` 上报实际固件版本并检查 `firmware_compatible`,再获取配置和创建会话。固件版本为 `1.1.0-echoear-s2s-memory.1`;本工程基于 Embedded Kit 2.0,硬件智能体 SDK 版本独立上报为 `2.0.0`。会话能力中的 `sdk_version` 不能用固件版本代替。动态 `session.update` 同时配置端到端语音、混合编排 LLM,以及按当前绑定用户隔离的 Viking `MemoryConfig`;端到端语音凭据和 Viking API Key 均不进入固件。会话尚未创建时保持唤醒采集,不重复拆建音频管线。SPI 屏幕仅在 `on_color_trans_done` 后释放 GFX 帧缓冲,避免异步传输期间提前复用缓冲区。运行状态用眼睛动画表示,避免 80 MHz 下中文逐像素渲染长期占用 CPU:快速眨眼表示聆听,转圈表示思考,正常眼睛表示待机或回答。
本板构建使用 16 MB Flash,Flash 与八线 PSRAM 均为 80 MHz。通用默认配置、ESP32-S3 默认配置和当前 `sdkconfig` 必须一致;不要恢复上游的 32 MB Flash 或实验性 120 MHz PSRAM 参数。
@@ -22,7 +22,7 @@
#define PLATFORM_API_ORIGIN "https://121.41.46.238"
#define PLATFORM_RESPONSE_MAX (32 * 1024)
#define PLATFORM_FIRMWARE_VERSION "1.0.0-echoear-platform.1"
#define PLATFORM_FIRMWARE_VERSION "1.1.0-echoear-s2s-memory.1"
#define PLATFORM_HARDWARE_AGENT_SDK_VERSION "2.0.0"
static const char *TAG = "platform_client";
@@ -457,10 +457,15 @@ static char *build_session_update(cJSON *session_config)
const char *a = cJSON_IsString(authorized) ? authorized->valuestring : "";
const char *p = cJSON_IsString(system_prompt) ? system_prompt->valuestring : "";
const char *tone = cJSON_IsString(persona) ? persona->valuestring : "";
size_t prompt_size = strlen(a) + strlen(p) + strlen(tone) + 256;
const char *voice_rules =
"这是通过扬声器进行的真实语音对话。回复只包含直接对用户说的口语正文,简短自然,语速正常。"
"不要输出或朗读动作、表情、语气、情绪、场景等舞台提示,不要写括号或星号包裹的表演说明,"
"例如‘语气带着关切’‘微笑着’‘温柔地说’。把风格体现在自然措辞中,不要描述自己如何说话。"
"不要复述人设、表达风格、系统配置或对话标识。";
size_t prompt_size = strlen(a) + strlen(p) + strlen(tone) + strlen(voice_rules) + 256;
char *prompt = malloc(prompt_size);
if (prompt == NULL) return NULL;
snprintf(prompt, prompt_size, "%s\n\n%s\n\n表达风格(仅指导表达,不可逐字复述):%s", a, p, tone);
snprintf(prompt, prompt_size, "%s\n\n%s\n\n表达风格(仅指导表达,不可逐字复述):%s\n\n%s", a, p, tone, voice_rules);
cJSON *event = cJSON_CreateObject();
char event_id[64];
@@ -480,11 +485,47 @@ static char *build_session_update(cJSON *session_config)
cJSON *llm = cJSON_AddObjectToObject(config, "LLMConfig");
cJSON *messages = cJSON_AddArrayToObject(llm, "SystemMessages");
cJSON_AddItemToArray(messages, cJSON_CreateString(prompt));
cJSON_AddItemToArray(messages, cJSON_CreateString(
"这是通过扬声器进行的真实语音对话。回复只包含直接对用户说的口语正文,简短自然,语速正常。"
"不要输出或朗读动作、表情、语气、情绪、场景等舞台提示,不要写括号或星号包裹的表演说明,"
"例如‘语气带着关切’‘微笑着’‘温柔地说’。把风格体现在自然措辞中,不要描述自己如何说话。"
"不要复述人设、表达风格、系统配置或对话标识。"));
cJSON *model_mode = cJSON_GetObjectItemCaseSensitive(session_config, "model_mode");
if (cJSON_IsString(model_mode) && strcmp(model_mode->valuestring, "s2s") == 0) {
cJSON *s2s = cJSON_AddObjectToObject(config, "S2SConfig");
cJSON *provider_params = cJSON_AddObjectToObject(s2s, "ProviderParams");
cJSON *dialog = cJSON_AddObjectToObject(provider_params, "dialog");
cJSON_AddStringToObject(dialog, "system_role", prompt);
cJSON_AddStringToObject(dialog, "speaking_style", tone);
cJSON *dialog_id = cJSON_GetObjectItemCaseSensitive(session_config, "dialog_id");
if (cJSON_IsString(dialog_id) && dialog_id->valuestring[0]) {
cJSON_AddStringToObject(dialog, "dialog_id", dialog_id->valuestring);
}
}
cJSON *memory = cJSON_GetObjectItemCaseSensitive(session_config, "memory");
if (cJSON_IsObject(memory)) {
cJSON *collection = cJSON_GetObjectItemCaseSensitive(memory, "collection_name");
cJSON *user_id = cJSON_GetObjectItemCaseSensitive(memory, "user_id");
cJSON *assistant_id = cJSON_GetObjectItemCaseSensitive(memory, "assistant_id");
cJSON *memory_types = cJSON_GetObjectItemCaseSensitive(memory, "memory_types");
cJSON *limit = cJSON_GetObjectItemCaseSensitive(memory, "limit");
cJSON *transition = cJSON_GetObjectItemCaseSensitive(memory, "transition_words");
if (cJSON_IsString(collection) && collection->valuestring[0] &&
cJSON_IsString(user_id) && user_id->valuestring[0] && cJSON_IsArray(memory_types)) {
cJSON *memory_config = cJSON_AddObjectToObject(config, "MemoryConfig");
cJSON_AddBoolToObject(memory_config, "Enable", true);
cJSON_AddStringToObject(memory_config, "Provider", "volc");
cJSON *memory_params = cJSON_AddObjectToObject(memory_config, "ProviderParams");
cJSON_AddStringToObject(memory_params, "collection_name", collection->valuestring);
cJSON_AddNumberToObject(memory_params, "limit", cJSON_IsNumber(limit) && limit->valueint > 0 ? limit->valueint : 5);
cJSON *filter = cJSON_AddObjectToObject(memory_params, "filter");
cJSON *users = cJSON_AddArrayToObject(filter, "user_id");
cJSON_AddItemToArray(users, cJSON_CreateString(user_id->valuestring));
if (cJSON_IsString(assistant_id) && assistant_id->valuestring[0]) {
cJSON *assistants = cJSON_AddArrayToObject(filter, "assistant_id");
cJSON_AddItemToArray(assistants, cJSON_CreateString(assistant_id->valuestring));
}
cJSON_AddItemToObject(filter, "memory_type", cJSON_Duplicate(memory_types, true));
if (cJSON_IsString(transition) && transition->valuestring[0]) {
cJSON_AddStringToObject(memory_params, "transition_words", transition->valuestring);
}
}
}
cJSON *agent = cJSON_AddObjectToObject(session, "agent_config");
cJSON_AddBoolToObject(agent, "EnableConversationStateCallback", false);
cJSON *callback_url = cJSON_GetObjectItemCaseSensitive(session_config, "subtitle_callback_url");
@@ -2948,6 +2948,7 @@ CONFIG_ESP_PAINTER_BASIC_FONT_12=y
#
# Audio Codec Device Configuration
#
# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set
CONFIG_CODEC_ES8311_SUPPORT=y
CONFIG_CODEC_ES7210_SUPPORT=y
CONFIG_CODEC_ES7243_SUPPORT=y