Reject Agent media sample rate mismatches
This commit is contained in:
@@ -46,14 +46,15 @@
|
||||
- pause/stop/resume 控制先持久挡住该任务新呼叫,Agent 确认收到指令后在同一事务提交应用状态和回执;省略策略默认 hangup,重复控制仍执行、重复回执复用事件身份,stopped 同 ID 不可恢复,未接纳旧外呼静默 ACK。SIP 通知先持久关闭准入,旧/未知通话未确认终结、SaaS 新版尚未分发或 Agent/Asterisk 未加载时不重开;等待期间控制和 outbox 仍可处理。
|
||||
- TDD 与隔离验证:`go test ./internal/store ./internal/dispatcher ./internal/mq -count=1`、`bash scripts/check-current-mq-mock.sh`、`git diff --check` 通过;Mock 覆盖任务积压、恢复、控制、重复投递、发布失败、SIP revision 栅栏与格式错误。当前 Agent side effect 为注入的**假外呼**,主 CLI 仍旧;真实 SaaS/RabbitMQ、供应商、线路、录音与 `call.result` 均**未验收**,分别留 P05–P08。
|
||||
|
||||
## P05:Agent 快照与 SDK 隔离链路(分批,未整体签收)
|
||||
## P05:Agent 快照与 SDK 隔离链路(项目内组件通过,主入口待 P07)
|
||||
|
||||
- Dispatcher 在启动、增量发现、恢复任务、SIP 更新及发指令前对完整 AI/provider 快照执行能力校验;不支持的任务关闭准入而不占额度或呼叫。已按用户确认保留现行 TTS Schema:火山 TTS V2 SDK 无法表达的 PCMA、0.5–2 倍以外或整数 speech_rate 无法精确表达的速度均明确拒绝,不静默修改配置;不支持的插话配置同样拒绝。
|
||||
- `ExecuteApproved` 只在隔离 Mock 中签发:携带任务原始 JSON、引用的 provider 明文凭据、SIP revision、选定路由/主叫/原始被叫、独立的拨号期限及最大通话时限;任务+provider 原始字节+SIP revision 计算绑定摘要。Agent 对照已激活的 D 会话身份、绑定摘要、SDK 能力及**实际观察到的** SIP 加载结果;发出 Mock 指令前仅持久保存摘要和未知占用,失败/重复/重启不自动重拨,文件中不留明文凭据。当前 `LoadedSIP` 为可注入的隔离 Mock 观察器,**不等于真实 Asterisk 已加载核验**。
|
||||
- ASR-only 不启动 LLM/TTS;full-AI 使用冻结的 model、显式 temperature=0/max_tokens、TTS voice/format/speech_rate 与 provider 原值凭据。真实 ASR SDK WebSocket 发出的音频输入/识别配置、LLM 与 TTS SDK 向隔离 HTTP Mock 发出的实际参数均已捕获核对;已配置的开场白由 TTS 单次合成,失败不自动重播且未完成时不进入对话轮次;空开场白不调用 TTS。关键词仅匹配用户侧最终 ASR,失败/未知挂断不自动重试。`ApprovedOriginator` 经生成的 Unary gRPC Stub 交付完整快照,SIP 全量 revision 不吻合即拒绝。
|
||||
- 对话控制隔离验证:共享 `callflow` 不再根据转写或回复内的硬编码词推断拒联,仅处理明确的关键词/拒联事实;ASR-only 不播报开场或 TTS 回复。配置的首语音/静默期限、整通话期限与最大轮数交给媒体控制器;回复按 Unicode 字符数分片,火山 TTS 的缓存音频块数超限明确失败而不重试;不支持的插话配置在准入前拒绝。该控制器尚未接入新主 CLI,不能宣称真实通话媒体已验证。
|
||||
- 验证:`PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-proto.sh`、`go test -race ./internal/ai ./internal/callflow ./internal/configread ./internal/dispatcher ./internal/rpc -count=1`、`go vet ./...`、`go build ./...`、`git diff --check` 均通过。
|
||||
- **尚未完成:** 新主 CLI 接入、对话控制与真实媒体的完整联动、录音/OSS/最终结果及真实 Agent/Asterisk、SaaS、MQ、AI 供应商联调。这些不能被本地 Mock 结果代签,分别属于 P05 收尾、P06–P08 或第二阶段外部验收。
|
||||
- Agent 隔离媒体入口 `rpc.RunApprovedCall` 仅接收签发的执行快照、媒体会话和挂断动作:签发通话期限与 AI 总期限均约束整通电话;ASR-only 不合成开场,full-AI 开场完成才采集语音。父级期限不会被“未检测到语音”掩盖,空媒体 Mock 等到上下文真正结束。当前媒体只处理 16 kHz PCM16,SDK 虽接受、但媒体不能正确处理的 24 kHz ASR/TTS 配置在 Dispatcher 与 Agent 绑定时明确拒绝,不静默播放或识别错速音频。该入口尚未连接主 CLI,也未用于真实拨号。
|
||||
- 验证:`PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-current-contracts.sh`、`PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-proto.sh`、`go test ./... -count=1`、`go test -race ./internal/ai ./internal/callflow ./internal/configread ./internal/dispatcher ./internal/rpc -count=1`、`go vet ./...`、`go build ./...`、`git diff --check` 均通过。
|
||||
- **后续边界:** 录音直传/OSS 失败恢复及最终结果属 P06;新主 CLI 接线、真实媒体完整联动及旧路径清理属 P07;隔离端到端和 A01–A12 属 P08。真实 Agent/Asterisk、SaaS、MQ、AI 供应商联调未开展,不能由 Mock 结果代签。
|
||||
|
||||
## 验收台账
|
||||
|
||||
|
||||
@@ -144,6 +144,9 @@ func BindCurrent(task configread.CurrentTask, providers map[string]configread.Cu
|
||||
if err != nil {
|
||||
return CurrentBound{}, fmt.Errorf("ASR input: %w", err)
|
||||
}
|
||||
if sampleRate != doubaospeech.SampleRate(16000) {
|
||||
return CurrentBound{}, errors.New("ASR media requires 16000 Hz PCM16")
|
||||
}
|
||||
lang := doubaospeech.Language(settings.ASR.Language)
|
||||
switch lang {
|
||||
case doubaospeech.LanguageZhCN, doubaospeech.LanguageEnUS, doubaospeech.LanguageJaJP, doubaospeech.LanguageKoKR:
|
||||
@@ -201,6 +204,9 @@ func BindCurrent(task configread.CurrentTask, providers map[string]configread.Cu
|
||||
if err != nil {
|
||||
return CurrentBound{}, fmt.Errorf("TTS output: %w", err)
|
||||
}
|
||||
if ttsSampleRate != doubaospeech.SampleRate(16000) {
|
||||
return CurrentBound{}, errors.New("TTS media requires 16000 Hz PCM16")
|
||||
}
|
||||
ttsRate := 0
|
||||
if settings.TTS.Speed != nil {
|
||||
floatRate := (*settings.TTS.Speed - 1) * 100
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCurrentBindRejectsSampleRatesUnsupportedByAgentMedia(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, mode, endpoint string
|
||||
}{
|
||||
{name: "ASR-only capture", mode: "asr_only", endpoint: "asr"},
|
||||
{name: "full-AI capture", mode: "full_ai", endpoint: "asr"},
|
||||
{name: "full-AI playback", mode: "full_ai", endpoint: "tts"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
task, providers := currentFixture(t, tc.mode)
|
||||
task = changeCurrentAgent(t, task, func(agent map[string]any) {
|
||||
if tc.endpoint == "asr" {
|
||||
agent["asr"].(map[string]any)["input"].(map[string]any)["sample_rate_hz"] = 24000
|
||||
} else {
|
||||
agent["tts"].(map[string]any)["format"].(map[string]any)["sample_rate_hz"] = 24000
|
||||
}
|
||||
})
|
||||
if _, err := BindCurrent(task, providers); err == nil || !strings.Contains(err.Error(), "media") {
|
||||
t.Fatalf("SDK-supported sample rate cannot silently mismatch 16 kHz Agent media: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user