Preserve approved prompt and conversation limits

This commit is contained in:
2026-09-29 20:45:24 +08:00
parent 5064b7765a
commit 71b434accc
2 changed files with 101 additions and 10 deletions
+57 -10
View File
@@ -17,13 +17,16 @@ import (
// provider settings. It must be built before dispatching any side effects.
// Credential values are never included in errors or logs.
type CurrentBound struct {
Mode string
ASR CurrentASR
LLM *CurrentLLM
TTS *CurrentTTS
Prompt string
Opening string
HangupKeywords []string
Mode string
ASR CurrentASR
LLM *CurrentLLM
TTS *CurrentTTS
Prompt string
PromptMaxBytes int
AllowedVariables []string
Opening string
HangupKeywords []string
Conversation CurrentConversation
}
type CurrentASR struct {
@@ -46,6 +49,17 @@ type CurrentTTS struct {
Timeout time.Duration
}
// CurrentConversation preserves the task's explicit dialogue limits. An
// absent positive limit remains zero; an explicit false remains non-nil.
type CurrentConversation struct {
AllowInterrupt *bool
SilenceTimeout time.Duration
MaxDuration time.Duration
MaxTurns int
SentenceMaxChars int
MaxPendingAudioChunks int
}
type currentAgentSettings struct {
Mode string `json:"mode"`
ASR struct {
@@ -81,11 +95,19 @@ type currentAgentSettings struct {
} `json:"format"`
} `json:"tts"`
Prompt *struct {
Text string `json:"text"`
Text string `json:"text"`
AllowedVariables []string `json:"allowed_variables"`
MaxBytes *int `json:"max_bytes"`
} `json:"prompt"`
Conversation *struct {
Opening string `json:"opening"`
HangupKeywords []string `json:"hangup_keywords"`
Opening string `json:"opening"`
HangupKeywords []string `json:"hangup_keywords"`
AllowInterrupt *bool `json:"allow_interrupt"`
SilenceTimeoutMS *int64 `json:"silence_timeout_ms"`
MaxDurationMS *int64 `json:"max_duration_ms"`
MaxTurns int `json:"max_turns"`
SentenceMaxChars int `json:"sentence_max_chars"`
MaxPendingAudioChunks int `json:"max_pending_audio_chunks"`
} `json:"conversation"`
}
@@ -196,6 +218,31 @@ func BindCurrent(task configread.CurrentTask, providers map[string]configread.Cu
Format: doubaospeech.FormatPCMS16LE, SampleRate: ttsSampleRate, SpeechRate: ttsRate,
}, Timeout: ttsTimeout}
bound.Prompt = settings.Prompt.Text
bound.AllowedVariables = append([]string(nil), settings.Prompt.AllowedVariables...)
if settings.Prompt.MaxBytes != nil {
bound.PromptMaxBytes = *settings.Prompt.MaxBytes
if len(bound.Prompt) > bound.PromptMaxBytes {
return CurrentBound{}, errors.New("prompt exceeds its approved byte limit")
}
}
if settings.Conversation.AllowInterrupt != nil && *settings.Conversation.AllowInterrupt {
return CurrentBound{}, errors.New("conversation interrupt is unsupported by current media controller")
}
silence, err := currentTimeout(settings.Conversation.SilenceTimeoutMS)
if err != nil {
return CurrentBound{}, fmt.Errorf("conversation silence timeout: %w", err)
}
maxDuration, err := currentTimeout(settings.Conversation.MaxDurationMS)
if err != nil {
return CurrentBound{}, fmt.Errorf("conversation duration: %w", err)
}
bound.Conversation = CurrentConversation{
AllowInterrupt: settings.Conversation.AllowInterrupt,
SilenceTimeout: silence, MaxDuration: maxDuration,
MaxTurns: settings.Conversation.MaxTurns,
SentenceMaxChars: settings.Conversation.SentenceMaxChars,
MaxPendingAudioChunks: settings.Conversation.MaxPendingAudioChunks,
}
bound.Opening = settings.Conversation.Opening
bound.HangupKeywords = append([]string(nil), settings.Conversation.HangupKeywords...)
return bound, nil
+44
View File
@@ -0,0 +1,44 @@
package ai
import (
"strings"
"testing"
"time"
)
func TestBindCurrentPreservesPromptAndConversationControls(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
bound, err := BindCurrent(task, providers)
if err != nil {
t.Fatal(err)
}
if bound.PromptMaxBytes != 32768 || len(bound.AllowedVariables) != 0 {
t.Fatal("approved prompt controls not preserved")
}
c := bound.Conversation
if c.AllowInterrupt == nil || *c.AllowInterrupt || c.SilenceTimeout != 3*time.Second || c.MaxDuration != 2*time.Minute || c.MaxTurns != 20 || c.SentenceMaxChars != 80 || c.MaxPendingAudioChunks != 32 {
t.Fatalf("approved conversation controls lost: %+v", c)
}
}
func TestBindCurrentRejectsPromptOverConfiguredByteLimit(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
task = changeCurrentAgent(t, task, func(agent map[string]any) {
agent["prompt"].(map[string]any)["max_bytes"] = 3
})
_, err := BindCurrent(task, providers)
if err == nil || !strings.Contains(err.Error(), "prompt") {
t.Fatalf("prompt limit was ignored: %v", err)
}
}
func TestBindCurrentRejectsUnsupportedBargeInBeforeCall(t *testing.T) {
task, providers := currentFixture(t, "full_ai")
task = changeCurrentAgent(t, task, func(agent map[string]any) {
agent["conversation"].(map[string]any)["allow_interrupt"] = true
})
_, err := BindCurrent(task, providers)
if err == nil || !strings.Contains(err.Error(), "interrupt") {
t.Fatalf("unsupported interruption cannot silently become false: %v", err)
}
}