docs(asr): track boundary resampling and update shared contract

This commit is contained in:
2026-10-09 14:48:25 +08:00
parent d5fe1fa88d
commit aaa6b38920
6 changed files with 97 additions and 2 deletions
+1 -1
View File
@@ -108,7 +108,7 @@
- 非生产真实 Agent 的 Asterisk `res_hep`/`res_hep_pjsip` 仅镜像至本机 UDP:在 ARI Dial 前读取本次 SIP Call-ID 并绑定执行,按 Call-ID 和原始 INVITE transaction 只采集真实最终响应的状态码、状态行与完整 `raw`;丢失/无法关联以 `sip_capture_error` 显式报告,不从 ARI、号码或挂断原因推测。镜像缺失不阻止已证实终结的额度释放;未经确认的占用仍保留。配置及回滚见 [`deploys/cell/README.md`](deploys/cell/README.md),不得将完整 SIP 报文写入普通日志、提交或聊天;本地测试通过不等于测试机已部署 HEP 或真实接通。
- 2026-10-08 用户批准本地配置规则调整及新模型调用支持:SaaS SIP `revision` 可省略,完整读取覆盖旧配置;`transport/auth_mode` 判定忽略大小写,但不补其它缺失线路参数。Dispatcher 持久分配内部 SIP 加载代次并核对 Agent/Asterisk,旧通话排空与未知占用保留规则不变。任务列表仅忽略 `schema_version/control_seq`。任务用唯一的 `provider_ref` 对应连接的 `provider_code`,`provider_id` 仅为连接记录标识;一份连接可供多用途复用;任务决定厂商/模型/ASR–LLM–TTS 用途,provider 只提供连接信息,不要求 `role/adapter/enabled`,未知供应商/协议及不可表达的连接参数显式拒绝。2026-10-08 追加用户确认:移除精确型号/音色业务硬编码;同协议模型及可表达参数从任务取得。当前 TTS 固定使用 task-based WebSocket,不按 model 名或地址猜协议、不失败回退 HTTP。模型实际可用性由供应商调用返回事实决定,不以本地模拟代签。新增本地调用代码及模拟测试不授权部署、拨号或真实 AI 请求。SQLite 当前布局因新增持久 SIP 快照变为 3;旧布局只读拒绝,不自动迁移或改写旧文件。
- 2026-10-08 用户确认按共享合同 `b502ad2` 本地适配任务线路并发:`allowed_trunk_ids` 只接受包含 `trunk_id/concurrency` 的对象数组,重复线路拒绝;非负整数 `concurrency` 的 0 表示该任务不使用该线路。选线及 SQLite 原子占用同时遵守租户、任务总量、线路总量、任务在线路上的上限,未知执行不释放;任务修订在选线后变化时拒绝原选择,不用旧快照发出新呼叫。占用仍由现有 inbox 与冻结任务快照确定,不新增业务表、不转换旧快照或迁移旧库。ASR/TTS 使用连接的 `ws_endpoint`,LLM 使用 `api_endpoint`,缺失不互相回退。本轮仅本地代码、测试和说明,不部署、不拨号、不请求真实 AI。
- AI 使用任务内不可变授权快照:当前使用火山 ASR、OpenAI 兼容 LLM、百炼 task-based ASR/TTS,TTS 固定 WebSocket。任务的 `params` 为必填、可空的平坦对象,值为字符串、数字、布尔值或 null;原样透传,不校验厂商参数值、不补 SDK 默认值,不限制精确型号/音色。任务/session 的 model、voice、消息及事务身份不得被 params 覆盖,冲突明确拒绝。不设或检查 `prompt.max_bytes`,提示词不截断。原生媒体仍为单声道 16 kHz PCM16,不根据 opaque params 改写或重标,SaaS 须提供匹配的服务参数;ASR-only 不启动 LLM/TTS,完整 AI 不借旧语音测试的授权或参数。只有最终用户 ASR 文本的明确字面关键词可触发拒联/挂断;`agent.conversation.hangup_keywords` 仅接受必填非空 `name`/`triggers`/`closingRemark` 的对象数组,多组命中按配置顺序取第一组,经获批 TTS 完整播放该组结束语后主动挂断,不调用 LLM、不开始新一轮对话;合成/播放失败须明确报错并尝试结束,不重播,旧字符串数组直接拒绝;不由 SDK 默认值、环境、CLI、metadata 或宽松 Schema 改写业务参数,不因 SDK 重试产生第二次发起/收费或重播。日志只存脱敏版本/摘要/计数,不存密钥、prompt、完整对话或音频。
- AI 使用任务内不可变授权快照:当前使用火山 ASR、OpenAI 兼容 LLM、百炼 task-based ASR/TTS,TTS 固定 WebSocket。任务的 `params` 为必填、可空的平坦对象,值为字符串、数字、布尔值或 null;原样透传,不校验厂商参数值、不补 SDK 默认值,不限制精确型号/音色。任务/session 的 model、voice、消息及事务身份不得被 params 覆盖,冲突明确拒绝。不设或检查 `prompt.max_bytes`,提示词不截断。ASR 输入格式与内部媒体格式分离:任务 params.sample_rate 声明所选服务支持的目标采样率,Agent 须在对接边界转换并保证实际音频与声明一致,不得忽略配置、仅重标参数或失败换率。当前内部媒体仍为单声道 16 kHz PCM16,ASR 边界转换尚未实现,已列入 docs/TODO.md;合同或 Schema 接受其它采样率不代表当前运行已经支持,不能以本次更新声称媒体兼容。ASR-only 不启动 LLM/TTS,完整 AI 不借旧语音测试的授权或参数。只有最终用户 ASR 文本的明确字面关键词可触发拒联/挂断;`agent.conversation.hangup_keywords` 仅接受必填非空 `name`/`triggers`/`closingRemark` 的对象数组,多组命中按配置顺序取第一组,经获批 TTS 完整播放该组结束语后主动挂断,不调用 LLM、不开始新一轮对话;合成/播放失败须明确报错并尝试结束,不重播,旧字符串数组直接拒绝;不由 SDK 默认值、环境、CLI、metadata 或宽松 Schema 改写业务参数,不因 SDK 重试产生第二次发起/收费或重播。日志只存脱敏版本/摘要/计数,不存密钥、prompt、完整对话或音频。
- **私有配置位置(本机路径相对本仓库根目录,只读,绝不提交)**:`.local/provider-ai.env` 是 `0600` 的 `KEY=VALUE` 文件;字段名为 `BAILIAN_API_KEY`、`BAILIAN_BASE_URL`、`BAILIAN_WSS_BASE_URL`、`BAILIAN_TTS_VOICE`、`VOLC_ASR_APP_NAME`、`VOLC_ASR_APP_KEY`、`VOLCENGINE_ACCESS_KEY`、`VOLCENGINE_SECRET_KEY`、`VOLCENGINE_REGION`、`VOLCENGINE_DISABLE_SSL`。根目录 `aliyun-oss.env` 也是 `0600`,**不是 shell env 文件**;它以冒号分隔,字段名准确为 `bucket`、`Endpoint`、`Region`,以及 `RAM` 下的 `username`、`accessKeyId`、`accessKeySecret`(大小写须保持原样)。测试机 `rogee` 用户的现行 ARI 文件位于 `~/.config/go-sip-asterisk/{ari.conf,http.conf,ari-secret}`,不是旧 `.local/asterisk-*/ari.conf`;访问测试机前先核对已登记的 SSH 主机指纹,不展示 `ari-secret`。
- **下次安全读取步骤**:先确认工作目录是本仓库,用 `stat` 仅检查本机两份文件是否存在、所有者与权限 `0600`;不满足即停止。按各自格式在受限本机进程中解析所需字段到内存,不执行 `source`、不打印全文/字段值、不写临时明文副本,不把密钥、签名 URL、音频或完整对话带入聊天、日志、提交及长期证据。AI 的历史文件只可作为**获准凭据来源**,模型/voice/速度等仍由当前获批的 task/providers 快照固定,不能用环境变量覆盖。OSS 历史文件也不能直接传给 `DISPATCHER_OSS_CONFIG_FILE`:该运行配置要求私有 JSON、`dispatcher_id` 和 `oss` 字段,并以环境变量**名称引用**密钥;需按现行合同构造并核验授权后才能使用。普通构建和测试不读取这些私有文件;真实服务测试必须显式启用对应 opt-in 并受现行门禁约束。
- **授权边界**:本轮验收目标是三条已登记线路的真实接通及 LLM 正常应答;旧两个号码已分别在三条线路试拨,六通均为 SIP 480,零接通。新增号码的历史逐次授权不等于本次代码变更获准部署或拨号;本次全局审查本地业务硬编码并以 SaaS 配置快照决定任务、线路和额度,**不部署、不拨号**。上述 AI 与 OSS 私有配置仍仅用于另经明确授权的非生产测试,下次任务须重新确认范围和真实服务调用授权,不能沿用本轮或历史一次性授权。不得把历史配置直接当 SaaS 快照、任务授权或真实呼叫准入,不覆盖/清理旧 OSS 对象。
+75
View File
@@ -0,0 +1,75 @@
package contracts
import (
"encoding/json"
"testing"
)
func TestASRInputSampleRateExamples(t *testing.T) {
schema, err := CompileCurrent("http-task-detail.schema.json")
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
rate float64
}{
{"8khz", 8000},
{"16khz", 16000},
} {
t.Run(tc.name, func(t *testing.T) {
fixture, err := Files.ReadFile("schema/examples/config-read-task-asr-" + tc.name + ".json")
if err != nil {
t.Fatal(err)
}
var doc map[string]any
if err := json.Unmarshal(fixture, &doc); err != nil {
t.Fatal(err)
}
params := doc["agent"].(map[string]any)["asr"].(map[string]any)["params"].(map[string]any)
if got := params["sample_rate"]; got != tc.rate {
t.Fatalf("sample_rate=%v, want %v", got, tc.rate)
}
if err := schema.Validate(doc); err != nil {
t.Fatal(err)
}
})
}
}
// Schema acceptance describes the task input, not a working resampler or a
// provider's actual capabilities. Do not bind it to Agent's internal rate.
func TestASRSampleRateSchemaDoesNotBindInternalFormat(t *testing.T) {
schema, err := CompileCurrent("http-task-detail.schema.json")
if err != nil {
t.Fatal(err)
}
for _, mode := range []string{"asr", "full"} {
fixture, err := Files.ReadFile("schema/examples/config-read-task-" + mode + ".json")
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
params map[string]any
valid bool
}{
{"empty parameters remain valid", map[string]any{}, true},
{"8khz input", map[string]any{"sample_rate": 8000}, true},
{"16khz input", map[string]any{"sample_rate": 16000}, true},
{"no fixed supported-rate enumeration", map[string]any{"sample_rate": 48000}, true},
{"nested parameters remain invalid", map[string]any{"sample_rate": map[string]any{"hz": 8000}}, false},
} {
t.Run(mode+"/"+tc.name, func(t *testing.T) {
var doc map[string]any
if err := json.Unmarshal(fixture, &doc); err != nil {
t.Fatal(err)
}
doc["agent"].(map[string]any)["asr"].(map[string]any)["params"] = tc.params
if err := schema.Validate(doc); (err == nil) != tc.valid {
t.Fatalf("valid=%t, want %t: %v", err == nil, tc.valid, err)
}
})
}
}
}
+1
View File
@@ -4,6 +4,7 @@
- **唯一最终契约:** [`sip-contracts`](https://gitee.com/zzmbac/sip-contracts),由 SaaS 与本项目共同维护;本项目用 [`../contracts/schema/`](../contracts/schema/README.md) Git submodule 固定提交,不另存独立定义。维护与修改前更新步骤见 [`contracts.md`](contracts.md)。内部 Agent RPC 以 [`../proto/agent/agent.proto`](../proto/agent/agent.proto) 为准。Markdown 不替代机器校验。
- **可核查的验收事实:** [`evidence/saas-dispatcher-p08-acceptance.md`](evidence/saas-dispatcher-p08-acceptance.md)。P01–P08 只在单节点/单 Agent/单 Cell/单租户的隔离 Mock 中验证;真实 SaaS/management、MQ 应用收讫、OSS/AI/SIP/Asterisk/ECS 和生产均未签收。
- **归档来源与历史:** [`archive/sources/README.md`](archive/sources/README.md) 保存合同来源及使用者修改的旧文档原件;[`archive/upstream/README.md`](archive/upstream/README.md) 保存可离线核验的上游 v1;[`archive/plan-saas-dispatcher-completed.md`](archive/plan-saas-dispatcher-completed.md) 是已完成的阶段计划,不是第二份现行规范。其它旧证据和工作包均只供追溯,不作为兼容回退;其指向已清理旧提案的原路径需通过合并前提交 `d85480c` 的 Git 历史查阅,不能当作现行链接。
- **当前待办:** [`TODO.md`](TODO.md)。ASR 对接边界的音频转换尚未实现;合同支持的配置不等于当前程序已经支持。
- **部署与开发约束:** [`../deploys/README.md`](../deploys/README.md)、[`../AGENTS.md`](../AGENTS.md)。实际新主机/版本/Cell 验证及任何真实试拨必须另获授权并执行部署、状态、拨号前受限抓包与诊断门禁。
新增字段应先更新机器合同与正反例,再同步唯一现行规范、来源哈希和本地验收;不另写平行 Schema 或版本文档。不得在源码、日志、文档和证据中保存真实密钥、完整音频或完整对话。
+13
View File
@@ -0,0 +1,13 @@
# 当前待办
## ASR 对接边界音频转换(未实现)
- [ ] 将内部媒体格式与 ASR 输入要求分离;内部可继续使用统一格式,不要求服务商支持内部固定的 16000 采样率。
- [ ] 使用获批任务现有参数确定 ASR 目标输入格式,在发送边界转换音频;实际格式已经匹配时不转换。请求中的采样率必须与实际发送音频一致。
- [ ] 不忽略或覆盖任务参数,不仅修改采样率声明,不补 SDK 默认值,不因失败换采样率或换服务商。输入要求不能明确、目标不支持或转换失败时明确报错,不发送格式不匹配的音频。
- [ ] 实现前核查现有音频依赖的转换能力;按 TDD 补充至少 16000→8000、8000→16000、同采样率直通、流式分块与失败测试,核验实际音频而非只检查参数。
- [ ] 真实供应商兼容性须在另获授权后验证;本地结构校验、模拟测试不代替真实签收。
唯一规则来源是共享合同的 [业务规范](../contracts/schema/saas-dispatcher.md) 与 [任务 Schema](../contracts/schema/http-task-detail.schema.json),这里仅记录未完成工作,不维护第二份字段定义。
**当前边界:** Agent 仍使用单声道 16 kHz PCM16,尚未实现上述 ASR 转换。Schema 允许 8000、16000 等参数,只表示配置结构有效,不表示当前程序或任意供应商已经支持。此次仅更新待办、合同、示例及测试,不改变运行行为、不部署、不拨号、不调用 AI。
+6
View File
@@ -84,6 +84,12 @@ AI 连接的 HTTP 与 WebSocket 地址按调用用途分别使用,缺失时明
本轮本地验证与外部边界见 [`evidence/schema-task-trunk-concurrency-20261008.md`](evidence/schema-task-trunk-concurrency-20261008.md)。没有部署、真实拨号或真实 AI 请求。
## ASR 采样率规则与待实现转换
共享合同和任务 Schema 已明确:ASR 输入要求不绑定 Agent 内部固定采样率;实际音频与请求声明一致由 Agent 对接边界保证。Schema 不新增采样率必填字段或供应商范围枚举。
当前 Agent 尚未实现 ASR 边界转换,仍使用单声道 16 kHz PCM16。转换工作及验收要求见 [`TODO.md`](TODO.md);新增的 8000/16000 示例与回归测试仅验证配置结构,不证明运行支持或真实供应商兼容。本次不改变运行实现。
## 2026-10-09 本地适配
共享版本 `adc49ace34a0cdfd21c4de5a20dc6dfc1ab9b9df` 的任务计划可选字段变更已用于本项目合同引用;现有调度行为与新合同一致,无需改写业务逻辑。合同正反例、配置读取、选线边界及省略字段的隔离端到端验证已补齐。