34051a4631
- Updated labels in the AI Agents dashboard for clarity, changing "流程状态" to "Playbook 状态" and "未发布流程" to "未发布 Playbook". - Introduced AI Agent rollout percentage management in channel editing, allowing users to set and rollback rollout percentages. - Added new API endpoints for rolling back AI Agent rollout and fetching agent run metrics. - Implemented new UI components for displaying agent run details, including status, duration, and input/output tokens. - Enhanced type definitions for AdminChannel and AIAgent to include rollout percentages and runtime modes. - Updated navigation to include a section for agent runs. - Added new translations for agent run features in both English and Chinese.
126 lines
3.8 KiB
Go
126 lines
3.8 KiB
Go
package runtime
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
applicationruntime "agent-desk/internal/ai/application/runtime"
|
|
"agent-desk/internal/models"
|
|
"agent-desk/internal/pkg/enums"
|
|
)
|
|
|
|
func TestReplyEligibilityCanReply(t *testing.T) {
|
|
eligibility := newReplyEligibility()
|
|
conversation := newConversationFixture()
|
|
message := newCustomerMessageFixture("hello")
|
|
aiAgent := newAIAgentFixture()
|
|
|
|
if !eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected customer message to be replyable")
|
|
}
|
|
|
|
message.SenderType = enums.IMSenderTypeAgent
|
|
if eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected non-customer message to be rejected")
|
|
}
|
|
|
|
message = newCustomerMessageFixture("hello")
|
|
conversation.HandoffAt = ptrTime(time.Now())
|
|
if eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected handed-off conversation to be rejected")
|
|
}
|
|
|
|
conversation = newConversationFixture()
|
|
conversation.CurrentAssigneeID = 1
|
|
if eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected assigned conversation to be rejected")
|
|
}
|
|
|
|
conversation = newConversationFixture()
|
|
aiAgent.ServiceMode = enums.IMConversationServiceModeHumanOnly
|
|
if eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected human-only agent to be rejected")
|
|
}
|
|
|
|
aiAgent = newAIAgentFixture()
|
|
message.Content = " "
|
|
if eligibility.CanReply(conversation, message, aiAgent) {
|
|
t.Fatalf("expected blank message to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestAIAgentRolloutUsesStableConversationBucket(t *testing.T) {
|
|
conversation := models.Conversation{ID: 101, ChannelID: 7}
|
|
agent := models.AIAgent{ID: 9, RolloutPercent: 50}
|
|
first := IsAIAgentRolloutEligible(conversation, agent, &models.Channel{AIAgentRolloutPercent: 100})
|
|
for range 20 {
|
|
if got := IsAIAgentRolloutEligible(conversation, agent, &models.Channel{AIAgentRolloutPercent: 100}); got != first {
|
|
t.Fatalf("rollout bucket changed within one conversation: first=%t got=%t", first, got)
|
|
}
|
|
}
|
|
if normalizedRolloutPercent(0) != 100 || normalizedRolloutPercent(101) != 100 || normalizedRolloutPercent(25) != 25 {
|
|
t.Fatal("unexpected rollout percent normalization")
|
|
}
|
|
if !IsAIAgentRolloutEligible(conversation, models.AIAgent{ID: 9, RolloutPercent: 0}, &models.Channel{}) {
|
|
t.Fatal("legacy zero rollout values must preserve full rollout")
|
|
}
|
|
}
|
|
|
|
func TestResolveReplyTimeout(t *testing.T) {
|
|
service := newAIReplyService()
|
|
aiAgent := newAIAgentFixture()
|
|
|
|
if got := service.resolveReplyTimeout(aiAgent); got != 180*time.Second {
|
|
t.Fatalf("expected default timeout, got %v", got)
|
|
}
|
|
|
|
aiAgent.ReplyTimeoutSeconds = 30
|
|
if got := service.resolveReplyTimeout(aiAgent); got != 30*time.Second {
|
|
t.Fatalf("expected exact timeout, got %v", got)
|
|
}
|
|
|
|
aiAgent.ReplyTimeoutSeconds = 999
|
|
if got := service.resolveReplyTimeout(aiAgent); got != 600*time.Second {
|
|
t.Fatalf("expected clamped timeout, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveInterruptPrompt(t *testing.T) {
|
|
summary := &applicationruntime.Summary{
|
|
Interrupts: []applicationruntime.InterruptContextSummary{
|
|
{
|
|
ID: "interrupt-1",
|
|
Type: "question",
|
|
InfoPreview: `{"message":"请补充订单号"}`,
|
|
},
|
|
},
|
|
}
|
|
if got := resolveInterruptPrompt(summary); got != "请补充订单号" {
|
|
t.Fatalf("unexpected interrupt prompt: %q", got)
|
|
}
|
|
|
|
summary.Interrupts[0].InfoPreview = "直接补充手机号"
|
|
if got := resolveInterruptPrompt(summary); got != "直接补充手机号" {
|
|
t.Fatalf("unexpected raw interrupt prompt: %q", got)
|
|
}
|
|
}
|
|
|
|
func newConversationFixture() models.Conversation {
|
|
return models.Conversation{}
|
|
}
|
|
|
|
func newCustomerMessageFixture(content string) models.Message {
|
|
return models.Message{
|
|
SenderType: enums.IMSenderTypeCustomer,
|
|
Content: content,
|
|
}
|
|
}
|
|
|
|
func newAIAgentFixture() models.AIAgent {
|
|
return models.AIAgent{}
|
|
}
|
|
|
|
func ptrTime(v time.Time) *time.Time {
|
|
return &v
|
|
}
|