Preserve approved prompt and conversation limits
This commit is contained in:
+57
-10
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user