mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
fix(seemedia): hear_audio 不再承诺「你会听到」,如实说明可能不支持 (#7)
## 问题(e2e 抓出,非猜测)
端到端跑 hear_audio 时模型答「老实说,这次我没法真的听到」,转而去数 WAV
字节。查日志:
tool hear_audio result: {"note":"音频已投给你——请在下一步直接聆听..."}
modal fallback: 0 block(s) transcribed, 0 skipped
(provider=llmsproxy vision=true audio=false)
真因是**配置缺口 + 描述说谎**叠加:
- 当前部署所有 LLM 源都是 audio=false(visionllm/llmsproxy 均如此)
- core.input_processing.audio.fallback_provider 为空 ⇒ 无回退源
- 于是内核既不能直视、也无法转写,只发一条「拿不到音频」的 notice
而工具描述写死「你会直接听到它」、note 写「请在下一步直接聆听」——
模型同时看到「你会听到」与「拿不到」,两句自相矛盾(这就是它转去数
WAV 字节的原因)。
## 为何不改代码改行为
插件在调用时**无法**得知当前模型是否支持音频,SDK 未暴露能力查询接口
(只有 LLMAPI 的 Chat/ReloadFromConfig)。所以插件没有能力「先判断再决定
要不要注块」。正确做法是:内核继续做它该做的诚实降级(已在 modalfallback
里实现),插件这边**不许替内核承诺**。
## 改了什么
- hear_audio 描述:从无条件承诺改为条件式(「若当前模型支持音频或配了回退源,
你会在下一步听到」),并明说:若都不支持,内核会明确告知拿不到(不会假装
听到),那时改用 transcribe_audio 或配置回退源
- handler 的 note:从「请直接聆听」改为「请确认是否真的收到;没收到就改用
transcribe_audio」
## 判据(可杀变异,3/3 实测)
- TestHearAudioDoesNotPromiseAudibility:描述不得含无条件承诺;必须指明
transcribe_audio 出路;必须如实说「不支持」
- TestHearAudioNoteIsHonest:note 不得说「直接聆听」;必须给替代方案
★ 判据自身修了一个 bug(变异测试抓出):原先用「下一个 RegisterTool」做段
终点,但 hear_audio 是**最后一个**注册 ⇒ 一路截到文件尾,把 handler 的 note
也吞进来,于是 note 里残留的 transcribe_audio 让「删掉描述出路」的变异逃逸。
改用 "p.handleHearAudio)" 作终点后该变异被杀。
变异实测:
F 描述改回无条件承诺 → FAIL ✓
G 描述删掉 transcribe_audio 出路 → FAIL ✓(修判据前曾逃逸)
H note 改回「直接聆听」 → FAIL ✓
I 描述删掉「不支持」说明 → FAIL ✓
18/18 通过;全量 homed 构建通过;二进制内已无旧假承诺、含新的诚实措辞。
Co-authored-by: JianFeeeee <JianFeeeee@users.noreply.gitcode.com>
This commit is contained in:
@ -166,9 +166,18 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
|
||||
if err := s.RegisterTool("hear_audio", sdk.ToolDef{
|
||||
Name: "hear_audio",
|
||||
Description: "【亲自听音频】把一段音频**原样投给你自己**去听,你会直接听到它," +
|
||||
"而不是读一段转写文字。需要判断语气/音乐/非语音声音时用它。" +
|
||||
"(只想要文字转写、或你本身不支持音频时,用 transcribe_audio 更省。)" +
|
||||
// ★ 不得无条件承诺「你会听到」(2026-10-11 e2e 抓出)。
|
||||
//
|
||||
// 实测:当前部署里**所有** LLM 源都是 audio=false,且
|
||||
// core.input_processing.audio.fallback_provider 为空 ⇒ 内核既不能直视
|
||||
// 也无法回退转写,只会发一条 notice 说「拿不到音频」。此前描述写死
|
||||
// 「你会直接听到它」,与内核随后发的 notice 自相矛盾,模型据此困惑
|
||||
// (实测它转而去数 WAV 字节)。描述必须如实说明可能拿不到。
|
||||
Description: "【亲自听音频】把一段音频**原样投给你自己**去听。若当前模型支持音频" +
|
||||
"(或配了可用的音频回退源),你会在下一步直接听到它。" +
|
||||
"★若当前模型与回退源都不支持音频,内核会明确告诉你拿不到音频(不会假装听到)——" +
|
||||
"那时请改用 transcribe_audio 拿文字转写,或请用户配置音频回退源" +
|
||||
"(core.input_processing.audio.fallback_provider + 该源 audio=true)。" +
|
||||
"来源三选一:path / digest / url。",
|
||||
Parameters: srcObject(srcParam),
|
||||
ParallelSafe: false,
|
||||
@ -239,9 +248,14 @@ func (p *Plugin) handleHearAudio(args map[string]interface{}) (interface{}, erro
|
||||
Type: "audio_url",
|
||||
AudioURL: &sdk.AudioURL{URL: url},
|
||||
}})
|
||||
// ★ note 同样不得承诺「你会听到」:插件在调用时无法得知当前模型/回退源
|
||||
// 是否支持音频(SDK 未暴露能力查询),支持与否由内核在下一步判定。
|
||||
// 这里只如实说「已投递」,并指明没收到时怎么办。
|
||||
return map[string]interface{}{
|
||||
"status": "ok",
|
||||
"note": "音频已投给你——请在下一步直接聆听(这不是转写文字)。",
|
||||
"note": "音频已投递——请在下一步确认是否真的收到音频内容。" +
|
||||
"若收到「不支持音频」之类的说明,说明当前模型与回退源都听不了," +
|
||||
"请改用 transcribe_audio 拿文字转写。",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -169,3 +169,87 @@ func TestSeeToolsAreNotParallelSafe(t *testing.T) {
|
||||
"SetToolBlocks 写的是当前 call 的块,并发会串台")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ⑤ 不得承诺「你会听到」 ----
|
||||
//
|
||||
// 2026-10-11 e2e 抓出:hear_audio 的注册描述写死「你会直接听到它」,而当前
|
||||
// 部署里所有 LLM 源都是 audio=false、且音频回退源为空 ⇒ 内核既不能直视也无法
|
||||
// 回退,只发一条「拿不到音频」的 notice。模型看到「你会听到」+「拿不到」两句
|
||||
// 自相矛盾的话,转而去数 WAV 字节(实测)。
|
||||
//
|
||||
// 插件在调用时**无法**得知当前模型是否支持音频(SDK 未暴露能力查询),
|
||||
// 所以描述必须如实说「可能拿不到」,并把出路(transcribe_audio / 配置回退源)
|
||||
// 写清楚。这条判据跨实现有效:谁把承诺改回去,谁就重建了那个自相矛盾。
|
||||
func TestHearAudioDoesNotPromiseAudibility(t *testing.T) {
|
||||
src, err := os.ReadFile("plugin.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := string(src)
|
||||
|
||||
// 钉住 RegisterTool("hear_audio" 这一段(到该注册调用结束为止)。
|
||||
// ★ 不能用「下一个 RegisterTool」做终点:hear_audio 是**最后一个**注册,
|
||||
// 那样会一直截到文件末尾,把 handler 的 note 也吞进来 —— 于是
|
||||
// note 里残留的 transcribe_audio 会让本判据误判为「描述合格」。
|
||||
// (这个 bug 是变异测试抓出来的:改掉描述里的出路却仍 PASS。)
|
||||
i := strings.Index(s, `RegisterTool("hear_audio"`)
|
||||
if i < 0 {
|
||||
t.Fatal("找不到 hear_audio 注册")
|
||||
}
|
||||
seg := s[i:]
|
||||
// 终点:该 RegisterTool 调用对应的 "}, p.handleHearAudio)" 之后。
|
||||
en := strings.Index(seg, "p.handleHearAudio)")
|
||||
if en < 0 {
|
||||
t.Fatal("找不到 hear_audio 注册调用的结尾")
|
||||
}
|
||||
seg = seg[:en]
|
||||
// ★ 剔除注释行:本文件的注释里会**引用旧文案**说明为何不得这么写,
|
||||
// 那不是承诺。判据只应检查真正会送到模型面前的字符串(Description/note)。
|
||||
var code []string
|
||||
for _, line := range strings.Split(seg, "\n") {
|
||||
t2 := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(t2, "//") {
|
||||
continue
|
||||
}
|
||||
code = append(code, line)
|
||||
}
|
||||
seg = strings.Join(code, "\n")
|
||||
|
||||
// ① 不得出现无条件承诺
|
||||
for _, bad := range []string{"你会直接听到它", "你会直接听到"} {
|
||||
if strings.Contains(seg, bad) {
|
||||
t.Errorf("hear_audio 描述不得无条件承诺可听性(当前配置下会与内核 notice 矛盾): 含 %q", bad)
|
||||
}
|
||||
}
|
||||
// ② 必须说明「拿不到时怎么办」——即指向 transcribe_audio
|
||||
if !strings.Contains(seg, "transcribe_audio") {
|
||||
t.Error("hear_audio 描述必须指明拿不到音频时的出路(transcribe_audio)")
|
||||
}
|
||||
// ③ 必须点明可能不支持(诚实降级),不能只说好话
|
||||
if !strings.Contains(seg, "不支持") {
|
||||
t.Error("hear_audio 描述必须如实说明「当前模型/回退源可能不支持音频」")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHearAudioNoteIsHonest 钉住 handler 返回的 note 同样不做虚假承诺。
|
||||
func TestHearAudioNoteIsHonest(t *testing.T) {
|
||||
src, err := os.ReadFile("plugin.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := string(src)
|
||||
i := strings.Index(s, "func (p *Plugin) handleHearAudio(")
|
||||
if i < 0 {
|
||||
t.Fatal("找不到 handleHearAudio")
|
||||
}
|
||||
seg := s[i:]
|
||||
if j := strings.Index(seg, "\nfunc "); j > 0 {
|
||||
seg = seg[:j]
|
||||
}
|
||||
if strings.Contains(seg, "请在下一步直接聆听") {
|
||||
t.Error("handleHearAudio 的 note 不得说「直接聆听」——插件无法保证当前模型支持音频")
|
||||
}
|
||||
if !strings.Contains(seg, "transcribe_audio") {
|
||||
t.Error("handleHearAudio 的 note 必须指明听不到时的替代(transcribe_audio)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user