capsule AI-native Unix-like composition layer

src/server/internal/orchestrator/prompt_test.go

9,866 bytes · 286 lines · capsule://quake0day/[email protected] raw on github

package orchestrator

import (
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/cyberverse/server/internal/agenttask"
	"github.com/cyberverse/server/internal/character"
)

func TestComposeSystemPromptSeparatesGlobalAndRole(t *testing.T) {
	got := composeSystemPrompt("全局规则", "角色设定")

	if !strings.Contains(got, "【全局输出规范】\n全局规则") {
		t.Fatalf("expected global section, got %q", got)
	}
	if !strings.Contains(got, "【角色设定】\n角色设定") {
		t.Fatalf("expected role section, got %q", got)
	}
}

func TestStandardSystemPromptUsesGlobalWithoutCharacter(t *testing.T) {
	orch := New(nil, nil, nil, nil, nil)
	session := NewSession("s1", ModeStandard, "")

	got := orch.standardSystemPrompt(session)
	if got != composeSystemPrompt(standardGlobalSystemPrompt, "") {
		t.Fatalf("unexpected system prompt: %q", got)
	}
}

func TestBuildVoiceLLMSessionConfigUsesPersonaAndOnlyOmniRolePrompt(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:          "晴天",
		Description:   "日常聊天伙伴",
		VoiceProvider: "qwen_omni",
		VoiceType:     "Tina",
		SpeakingStyle: "自然、简洁",
		Personality:   "温和真诚",
		SystemPrompt:  "你和用户像熟悉的朋友。",
	})
	if err != nil {
		t.Fatal(err)
	}

	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeOmni, char.ID)

	got := orch.buildVoiceLLMSessionConfig(session, "s1")
	if got.Provider != "persona" {
		t.Fatalf("expected persona provider, got %q", got.Provider)
	}
	if got.BotName != "晴天" || got.SpeakingStyle != "自然、简洁" {
		t.Fatalf("expected voice-specific fields to stay separate, got %+v", got)
	}
	if got.CharacterID != char.ID || got.CharacterDir == "" {
		t.Fatalf("expected character RAG identity fields, got %+v", got)
	}
	if got.SystemPrompt != "你和用户像熟悉的朋友。" {
		t.Fatalf("expected omni mode to keep only the character prompt, got %q", got.SystemPrompt)
	}
	for _, unexpected := range []string{"【全局输出规范】", "默认简短", "角色描述:", "角色性格:", "说话风格:"} {
		if strings.Contains(got.SystemPrompt, unexpected) {
			t.Fatalf("omni prompt should not contain %q: %q", unexpected, got.SystemPrompt)
		}
	}
}

func TestBuildVoiceLLMSessionConfigIncludesLiveHistoryOnReconnect(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:          "晴天",
		VoiceProvider: "qwen_omni",
		VoiceType:     "Tina",
		SystemPrompt:  "你和用户像熟悉的朋友。",
	})
	if err != nil {
		t.Fatal(err)
	}

	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeOmni, char.ID)
	session.SetDialogContext([]DialogContextItem{
		{Role: "user", Text: "上次我们聊到她喜欢睡前吃零食。", Timestamp: 1},
		{Role: "assistant", Text: "我记得这个细节。", Timestamp: 2},
	})
	now := time.Date(2026, 5, 26, 0, 0, 0, 0, time.UTC)
	session.AddMessage(ChatMessage{Role: "user", Content: "她睡前不洗脚,吃饭不擦嘴", Timestamp: now, TurnSeq: 1})
	session.AddMessage(ChatMessage{Role: "assistant", Content: "你说的是她的生活习惯。", Timestamp: now.Add(time.Second), TurnSeq: 1})
	session.AddMessage(ChatMessage{Role: "user", Content: "就是,快点说说她", Timestamp: now.Add(2 * time.Second), TurnSeq: 2})
	session.AddMessage(ChatMessage{Role: "user", Content: "她,当前前面提到的她,你还有上下文吗", Timestamp: now.Add(3 * time.Second), TurnSeq: 3})

	got := orch.buildVoiceLLMSessionConfigExcludingTurn(session, "s1", 3)
	texts := make([]string, 0, len(got.DialogContext))
	for _, item := range got.DialogContext {
		texts = append(texts, item.Text)
	}
	joined := strings.Join(texts, "\n")
	for _, want := range []string{
		"上次我们聊到她喜欢睡前吃零食。",
		"我记得这个细节。",
		"她睡前不洗脚,吃饭不擦嘴",
		"你说的是她的生活习惯。",
		"就是,快点说说她",
	} {
		if !strings.Contains(joined, want) {
			t.Fatalf("expected live reconnect context to contain %q, got %+v", want, got.DialogContext)
		}
	}
	if strings.Contains(joined, "她,当前前面提到的她,你还有上下文吗") {
		t.Fatalf("current text input should not be duplicated in dialog context: %+v", got.DialogContext)
	}
}

func TestVoiceStartupGreetingUsesUnderlyingProviderAndNoFixedWelcome(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:           "晴天",
		VoiceProvider:  "qwen_omni",
		VoiceType:      "Tina",
		SpeakingStyle:  "自然、简洁",
		SystemPrompt:   "你和用户像熟悉的朋友。",
		WelcomeMessage: "欢迎回来,我们继续聊。",
	})
	if err != nil {
		t.Fatal(err)
	}

	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeOmni, char.ID)

	normal := orch.buildVoiceLLMSessionConfig(session, "s1")
	if normal.Provider != "persona" {
		t.Fatalf("expected normal omni config to use persona, got %q", normal.Provider)
	}
	if normal.WelcomeMessage != "" {
		t.Fatalf("expected fixed welcome to be withheld from normal voice config, got %q", normal.WelcomeMessage)
	}

	greeting := orch.buildVoiceStartupGreetingSessionConfig(session, "s1")
	if greeting.Provider != "qwen_omni" {
		t.Fatalf("expected startup greeting to use underlying provider, got %q", greeting.Provider)
	}
	if greeting.WelcomeMessage != "" {
		t.Fatalf("expected startup greeting to disable provider SayHello, got %q", greeting.WelcomeMessage)
	}
}

func TestBuildVoiceStartupGreetingPromptUsesHistory(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:           "晴天",
		SpeakingStyle:  "自然、简洁",
		SystemPrompt:   "你和用户像熟悉的朋友。",
		WelcomeMessage: "见到你很高兴。",
	})
	if err != nil {
		t.Fatal(err)
	}

	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeOmni, char.ID)
	session.SetDialogContext([]DialogContextItem{
		{Role: "user", Text: "上次我们在聊出差计划。", Timestamp: 1},
		{Role: "assistant", Text: "我帮你整理了行程重点。", Timestamp: 2},
	})

	got := orch.buildVoiceStartupGreetingPrompt(session)
	for _, want := range []string{
		"你的名字:晴天",
		"可参考的开场偏好:见到你很高兴。",
		"用户:上次我们在聊出差计划。",
		"你:我帮你整理了行程重点。",
		"默认不要回顾、总结、复述或主动延续这些内容",
		"不要主动提及取消、失败、争执、情绪化表达、敏感内容或具体历史细节",
	} {
		if !strings.Contains(got, want) {
			t.Fatalf("expected prompt to contain %q, got %q", want, got)
		}
	}
	if strings.Contains(got, "可以自然提到上次关注的话题") {
		t.Fatalf("expected startup greeting not to encourage routine history recall: %q", got)
	}
	if strings.Contains(got, "当前没有可用的历史对话") {
		t.Fatalf("expected history prompt, got no-history branch: %q", got)
	}
}

func TestBuildVoiceStartupGreetingPromptIntroducesSelfWithoutHistory(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{Name: "晴天"})
	if err != nil {
		t.Fatal(err)
	}

	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeOmni, char.ID)

	got := orch.buildVoiceStartupGreetingPrompt(session)
	for _, want := range []string{"你的名字:晴天", "当前没有可用的历史对话", "实时语音视频聊天", "查询、调研、整理资料"} {
		if !strings.Contains(got, want) {
			t.Fatalf("expected prompt to contain %q, got %q", want, got)
		}
	}
}

func TestBuildVoiceLLMSessionConfigUsesPersonaWhenAgentEnabled(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:          "晴天",
		VoiceProvider: "qwen_omni",
		VoiceType:     "Tina",
		SystemPrompt:  "你和用户像熟悉的朋友。",
	})
	if err != nil {
		t.Fatal(err)
	}

	taskRoot := t.TempDir()
	taskStore, err := agenttask.OpenStore(filepath.Join(taskRoot, "tasks.db"), filepath.Join(taskRoot, "artifacts"))
	if err != nil {
		t.Fatal(err)
	}
	defer taskStore.Close()

	orch := New(nil, nil, nil, nil, store)
	orch.SetTaskService(agenttask.NewService(taskStore, nil))
	session := NewSession("s1", ModeOmni, char.ID)

	got := orch.buildVoiceLLMSessionConfig(session, "s1")
	if got.Provider != "persona" {
		t.Fatalf("expected persona provider, got %q", got.Provider)
	}
	if !orch.sessionSupportsVisualInput(session) {
		t.Fatal("expected qwen_omni visual support to use the underlying character provider")
	}
}

func TestStandardSystemPromptWithRAGAppendsMaterialContext(t *testing.T) {
	store, err := character.NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	char, err := store.Create(&character.Character{
		Name:         "晴天",
		SystemPrompt: "你是晴天。",
	})
	if err != nil {
		t.Fatal(err)
	}
	orch := New(nil, nil, nil, nil, store)
	session := NewSession("s1", ModeStandard, char.ID)

	got := orch.standardSystemPromptWithRAG(session, "【角色素材检索结果】\n[1] 早年经历\n出生在海边。")
	if !strings.Contains(got, "角色提示:你是晴天。") {
		t.Fatalf("expected role prompt, got %q", got)
	}
	if !strings.Contains(got, "【角色素材检索结果】") || !strings.Contains(got, "出生在海边") {
		t.Fatalf("expected RAG context, got %q", got)
	}
	if strings.Contains(got, "biography|") {
		t.Fatalf("expected material type label to be omitted, got %q", got)
	}
}