feat: add language support and tests for English and Chinese seed data

- Introduced a new package `seedlang` to handle language parsing and constants for English and Chinese.
- Updated `Init` functions across various modules (kb, quickreply, skill, tag) to accept a language parameter.
- Implemented English seed data for FAQs, quick replies, skills, and tags, ensuring no Chinese text is present in English data.
- Added tests to verify that English seed data does not contain Chinese characters.
- Removed outdated README file from the kb test data directory.
- Created new test files for channel and kb to validate the absence of Chinese text in English seed data.
This commit is contained in:
mlogclub
2026-06-01 12:03:57 +08:00
parent 6f86bdcd75
commit b743639731
21 changed files with 665 additions and 129 deletions
-3
View File
@@ -1,3 +0,0 @@
# 初始化 FAQ 知识库测试数据
`kb.Init()` 会初始化“贝壳客服平台 FAQ”知识库,用于本地 AI Agent 和知识检索调试。
+68 -11
View File
@@ -1,6 +1,7 @@
package kb
import (
"agent-desk/cmd/testdata/seedlang"
"agent-desk/internal/models"
"agent-desk/internal/pkg/constants"
"agent-desk/internal/pkg/enums"
@@ -26,13 +27,13 @@ type faqSeed struct {
Remark string
}
func Init() (*InitResult, error) {
faqSeeds := knowledgeFAQSeeds()
func Init(lang seedlang.Language) (*InitResult, error) {
faqSeeds := knowledgeFAQSeeds(lang)
result := &InitResult{
TotalFAQs: len(faqSeeds),
}
err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
faqKnowledgeBase, ensureFAQErr := ensureFAQKnowledgeBase(ctx.Tx)
faqKnowledgeBase, ensureFAQErr := ensureFAQKnowledgeBase(ctx.Tx, lang)
if ensureFAQErr != nil {
return ensureFAQErr
}
@@ -58,13 +59,14 @@ func Init() (*InitResult, error) {
return result, nil
}
func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) {
func ensureFAQKnowledgeBase(db *gorm.DB, lang seedlang.Language) (*models.KnowledgeBase, error) {
now := time.Now()
item := repositories.KnowledgeBaseRepository.FindOne(db, sqls.NewCnd().Eq("name", "贝壳客服平台 FAQ"))
name, description, remark := faqKnowledgeBaseText(lang)
item := repositories.KnowledgeBaseRepository.FindOne(db, sqls.NewCnd().Eq("name", name))
if item == nil {
item = &models.KnowledgeBase{
Name: "贝壳客服平台 FAQ",
Description: "模拟真实客服场景的 FAQ 测试数据,覆盖账号、坐席、机器人、知识库、工单、计费与发票等常见问题。",
Name: name,
Description: description,
KnowledgeType: string(enums.KnowledgeBaseTypeFAQ),
Status: enums.StatusOk,
DefaultTopK: 8,
@@ -75,7 +77,7 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) {
ChunkMaxTokens: 0,
ChunkOverlapTokens: 0,
AnswerMode: int(enums.KnowledgeAnswerModeStrict),
Remark: "测试数据初始化自动生成",
Remark: remark,
AuditFields: models.AuditFields{
CreatedAt: now,
CreateUserID: constants.SystemAuditUserID,
@@ -92,7 +94,7 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) {
}
err := repositories.KnowledgeBaseRepository.Updates(db, item.ID, map[string]any{
"description": "模拟真实客服场景的 FAQ 测试数据,覆盖账号、坐席、机器人、知识库、工单、计费与发票等常见问题。",
"description": description,
"knowledge_type": string(enums.KnowledgeBaseTypeFAQ),
"status": enums.StatusOk,
"default_top_k": 8,
@@ -103,7 +105,7 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) {
"chunk_max_tokens": 0,
"chunk_overlap_tokens": 0,
"answer_mode": int(enums.KnowledgeAnswerModeStrict),
"remark": "测试数据初始化自动生成",
"remark": remark,
"update_user_id": constants.SystemAuditUserID,
"update_user_name": constants.SystemAuditUserName,
"updated_at": now,
@@ -114,6 +116,17 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) {
return repositories.KnowledgeBaseRepository.Get(db, item.ID), nil
}
func faqKnowledgeBaseText(lang seedlang.Language) (name string, description string, remark string) {
if lang == seedlang.English {
return "AgentDesk Support Platform FAQ",
"FAQ test data that simulates real support scenarios, covering accounts, agents, AI bots, knowledge bases, tickets, billing, invoices, and troubleshooting.",
"Generated by testdata initialization"
}
return "贝壳客服平台 FAQ",
"模拟真实客服场景的 FAQ 测试数据,覆盖账号、坐席、机器人、知识库、工单、计费与发票等常见问题。",
"测试数据初始化自动生成"
}
func upsertKnowledgeFAQ(db *gorm.DB, knowledgeBaseID int64, seed faqSeed) (bool, error) {
now := time.Now()
similarQuestions, err := json.Marshal(seed.SimilarQuestions)
@@ -165,7 +178,51 @@ func upsertKnowledgeFAQ(db *gorm.DB, knowledgeBaseID int64, seed faqSeed) (bool,
return false, nil
}
func knowledgeFAQSeeds() []faqSeed {
func knowledgeFAQSeeds(lang seedlang.Language) []faqSeed {
if lang == seedlang.English {
return englishKnowledgeFAQSeeds()
}
return chineseKnowledgeFAQSeeds()
}
func englishKnowledgeFAQSeeds() []faqSeed {
return []faqSeed{
{Question: "What should I do if the admin console says my account or password is incorrect?", Answer: "First confirm that the account is complete and that the email domain is correct. Passwords are case-sensitive. After 5 failed attempts, the account may be locked for 15 minutes. If you still cannot sign in, ask an organization administrator to reset the password in Organization Settings > Members, or use Forgot Password on the sign-in page.", SimilarQuestions: []string{"admin password is wrong", "cannot sign in to the console", "username or password error"}, Remark: "Account login"},
{Question: "How do I reset a forgotten password?", Answer: "Click Forgot Password on the sign-in page, enter the bound email address, and use the verification code to reset the password. If the account has no usable email address, contact an enterprise administrator for a reset. After a successful reset, the account must sign in again on all devices.", SimilarQuestions: []string{"forgot password", "reset console password", "recover login password"}, Remark: "Account login"},
{Question: "Why am I not receiving the login verification email?", Answer: "Check spam, promotions, and your enterprise mail quarantine first. If no email arrives within 5 minutes, confirm the email address and ask your IT team to allowlist the platform sender domain. Repeated missing emails may indicate provider throttling, so try a backup email if available.", SimilarQuestions: []string{"verification email not received", "no login code email", "email code missing"}, Remark: "Account login"},
{Question: "Can multiple people use the same agent account at the same time?", Answer: "Sharing one agent account is not recommended. The platform may allow the same account on multiple devices, but login logs and abnormal activity reminders are recorded. For traceability, permission isolation, and accurate conversation assignment, each agent should use a separate account.", SimilarQuestions: []string{"share one account", "multiple people one login", "same account on many computers"}, Remark: "Account login"},
{Question: "How do I create a console account for a new team member?", Answer: "An administrator can open Organization Settings > Members, click Add Member, fill in name, email, team, and role, then save. The system sends an activation email and the member sets a password on first sign-in. If SSO is enabled, members can also be synced from the enterprise identity provider.", SimilarQuestions: []string{"create new agent account", "add employee account", "invite member to console"}, Remark: "Member management"},
{Question: "What is the difference between online, busy, and offline agent statuses?", Answer: "Online agents can receive new conversations. Busy agents stop receiving new conversations but can continue existing ones. Offline agents do not join assignment or receive real-time reminders. If automatic status switching is enabled, long inactivity or sign-out can move the agent offline.", SimilarQuestions: []string{"agent status meaning", "online busy offline difference", "support agent status"}, Remark: "Agent service"},
{Question: "How are conversations assigned to agents?", Answer: "By default, conversations are assigned by skill group and round-robin rules. The platform can also consider current workload, recent response time, and priority. If a customer matches channel, language, or tag conditions, routing can prefer the matching team or agent.", SimilarQuestions: []string{"conversation routing rule", "how chats are assigned", "new conversation distribution"}, Remark: "Agent service"},
{Question: "Can a conversation be automatically closed when the customer does not reply?", Answer: "Yes. In Reception Settings > Conversation Rules, configure the no-response timeout, such as closing the conversation after 30 minutes without a customer reply. If the customer sends another message later, a new conversation is created and history remains visible in the customer timeline.", SimilarQuestions: []string{"auto close conversation", "customer no reply timeout", "inactive chat closing"}, Remark: "Agent service"},
{Question: "When does the AI bot take over a conversation?", Answer: "When the channel uses AI-first service and the message matches the bot's service hours, language, and business scope, the bot replies first. If the customer asks for a human, matches a handoff rule, or the bot misses knowledge repeatedly, the system can switch to a human agent.", SimilarQuestions: []string{"AI bot service rules", "when does AI answer first", "bot to human conditions"}, Remark: "AI bot"},
{Question: "What should I check first when the bot answer is inaccurate?", Answer: "First check whether the knowledge base covers the question, whether FAQ wording is too far from user wording, whether the recall threshold is too high, and whether the wrong knowledge base is enabled. If retrieval hits are good but answers still drift, review the prompt, answer mode, and reranking strategy.", SimilarQuestions: []string{"AI answer inaccurate", "bot answers wrong", "what config to check first"}, Remark: "AI bot"},
{Question: "How do I restrict a bot to after-sales questions only?", Answer: "Bind the bot to an after-sales knowledge base and restrict routing to after-sales channels, entry points, or tagged conversations. Set the fallback policy to guide users to a human agent so the bot avoids answering out-of-scope questions.", SimilarQuestions: []string{"bot only after sales", "limit bot answer scope", "bind bot to business scope"}, Remark: "AI bot"},
{Question: "Does the bot support multilingual replies?", Answer: "Yes, it can support English, Chinese, and other languages supported by the connected model, but the knowledge base should contain the corresponding language content. If only Chinese knowledge is uploaded, English business answers may not be stable. Use separate language-specific knowledge bases for better quality.", SimilarQuestions: []string{"AI supports English", "multilingual bot", "serve English customers"}, Remark: "AI bot"},
{Question: "What is the difference between a document knowledge base and an FAQ knowledge base?", Answer: "A document knowledge base works well for long help articles, policies, and manuals because the system chunks and retrieves passages. An FAQ knowledge base works better for standard question-answer pairs and high-frequency support questions. If the content has fixed questions and fixed answers, use FAQ first.", SimilarQuestions: []string{"FAQ vs document knowledge base", "when to use FAQ", "knowledge base type selection"}, Remark: "Knowledge base"},
{Question: "How long after uploading a document can the bot retrieve it?", Answer: "After upload, the document enters the indexing queue. Small text documents usually finish in 1 to 3 minutes. Longer files, image-heavy documents, or busy indexing queues can take longer. You can check indexing status in the knowledge base list.", SimilarQuestions: []string{"document indexing time", "knowledge upload effective time", "when can AI use the document"}, Remark: "Knowledge base"},
{Question: "Can FAQ entries be imported in bulk?", Answer: "Yes. Download the template and fill in question, answer, similar questions, and remark columns, then import it from the FAQ page. Similar questions should be separated by line breaks or semicolons, and duplicate questions should be removed before import.", SimilarQuestions: []string{"bulk FAQ upload", "FAQ import template", "batch import Q&A"}, Remark: "Knowledge base"},
{Question: "Why does an uploaded document show indexing failed?", Answer: "Common causes include empty documents, text copied as images, excessive content length, unavailable vector service, or model configuration errors. Check the indexing error first, then convert the file to plain text or Markdown and upload it again if needed.", SimilarQuestions: []string{"document indexing failed", "knowledge processing failed", "upload error after indexing"}, Remark: "Knowledge base"},
{Question: "How do I embed the website support button on our site?", Answer: "Open Channel Access > Web Widget, copy the generated script, and paste it before the closing body tag on your website. If the site uses a Content Security Policy, add the platform domain to the allowed list. After publishing, test both desktop and mobile pages.", SimilarQuestions: []string{"embed web widget", "website chat button", "install support script"}, Remark: "Channel access"},
{Question: "Can the web widget use our brand color and logo?", Answer: "Yes. In the web channel configuration, set the title, subtitle, theme color, position, width, and brand assets. After saving, refresh the website page. If CDN caching is enabled, it may take a few minutes for the change to appear.", SimilarQuestions: []string{"custom web widget brand", "change chat color", "support widget logo"}, Remark: "Channel access"},
{Question: "How do I create a support ticket from a conversation?", Answer: "In the conversation detail page, click Create Ticket, select the issue category and priority, fill in the title and description, then submit. Conversation context can be linked to the ticket so the follow-up team can see the original messages.", SimilarQuestions: []string{"create ticket from chat", "turn conversation into ticket", "submit support ticket"}, Remark: "Tickets"},
{Question: "What information should be included when reporting an urgent issue?", Answer: "Include the issue time, organization ID, affected channel or customer scope, screenshots, error messages, and reproduction steps. Clear impact and steps help the support team triage faster and avoid repeated clarification.", SimilarQuestions: []string{"urgent incident information", "report system failure", "what to include in a ticket"}, Remark: "Tickets"},
{Question: "How do I configure automatic welcome messages?", Answer: "Enable the welcome message in the channel or bot configuration. You can use different text by working hours, channel, customer tag, or page URL. Keep the message concise and clarify whether the current service is AI or human.", SimilarQuestions: []string{"automatic greeting", "welcome message setup", "first message to customer"}, Remark: "Automation"},
{Question: "Can keywords automatically trigger human handoff?", Answer: "Yes. Add handoff rules in bot strategy and enter keywords such as complaint, refund, human agent, or invoice reissue. Include synonyms and common user phrasing to reduce missed matches.", SimilarQuestions: []string{"keyword handoff", "words trigger human agent", "automatic human transfer"}, Remark: "Automation"},
{Question: "How do I view conversation volume trends and peak hours?", Answer: "Open Data Reports > Traffic Analysis to view conversations, visitors, queue peaks, and human service rate by hour, date, and channel. Peak-hour analysis should be compared with staffing schedules.", SimilarQuestions: []string{"conversation trend report", "peak traffic hours", "traffic analytics"}, Remark: "Reports"},
{Question: "What do bot hit rate and resolution rate mean?", Answer: "Hit rate is the percentage of in-scope bot messages where the bot retrieves and returns an answer. Resolution rate is the percentage of conversations completed without human handoff after bot handling. A high hit rate does not always mean a high resolution rate, so review both metrics together.", SimilarQuestions: []string{"AI hit rate meaning", "bot resolution rate", "bot metrics explanation"}, Remark: "Reports"},
{Question: "How do I upgrade a plan or add more seats?", Answer: "Contact your customer success manager or submit an upgrade request in Billing Center > Plan Management. Added seats are usually prorated for the remaining billing cycle, and permissions and quotas are expanded after the upgrade takes effect.", SimilarQuestions: []string{"add seats", "upgrade subscription", "buy more agents"}, Remark: "Billing"},
{Question: "Can we issue a VAT invoice?", Answer: "Yes. Maintain complete invoice information in Billing Center > Invoice Information, including company name, tax number, registered address, phone, and bank information. After approval, invoices are issued according to the billing cycle.", SimilarQuestions: []string{"request invoice", "VAT invoice", "invoice information setup"}, Remark: "Billing"},
{Question: "What happens after AI usage exceeds the included quota?", Answer: "If your plan includes a fixed AI quota, excess usage is usually billed by actual calls or token usage. Enable usage alerts in the billing center to avoid unexpected overage near month end.", SimilarQuestions: []string{"AI overage billing", "token usage charge", "quota exceeded"}, Remark: "Billing"},
{Question: "What should I do if messages are sent but customers do not receive them?", Answer: "Confirm that the message status is successful, the customer channel is online, and the third-party channel has no delivery failure receipt. If only one channel is affected, the cause is often channel throttling, template review, or network issues.", SimilarQuestions: []string{"customer cannot receive message", "message delivery failed", "sent but not received"}, Remark: "Troubleshooting"},
{Question: "Why is the page slow or the message list loading slowly?", Answer: "Common causes include too much browser cache, too many conversation tabs open, high network latency, or very large attachments in the page. Refresh the page, close unnecessary tabs, and check the local network and browser version first.", SimilarQuestions: []string{"console is slow", "message list slow", "chat page lag"}, Remark: "Troubleshooting"},
{Question: "Why does a newly configured bot not take effect?", Answer: "Check whether the bot is bound to the correct channel, whether service hours cover the current time, and whether the channel is still using human-first service. If configuration looks correct, inspect debug logs to confirm whether routing conditions were matched.", SimilarQuestions: []string{"bot not working", "AI not taking over", "configuration not effective"}, Remark: "Troubleshooting"},
{Question: "How do I decide whether an FAQ should be optimized?", Answer: "Prioritize three signals: high hit rate but high human handoff rate, high hit rate but low satisfaction, and repeated customer follow-up questions. These usually mean the answer is incomplete, inconsistent, or missing similar question coverage.", SimilarQuestions: []string{"FAQ optimization signals", "which FAQ to improve", "knowledge base quality review"}, Remark: "Knowledge operations"},
{Question: "How long should an FAQ answer be?", Answer: "Start with the conclusion, then add steps and notes. Most support FAQ answers work best at roughly 80 to 220 Chinese characters or the English equivalent. Very short answers miss context; very long answers are harder for bots to cite and customers to read quickly.", SimilarQuestions: []string{"FAQ answer length", "how detailed should answers be", "write Q&A content"}, Remark: "Knowledge operations"},
}
}
func chineseKnowledgeFAQSeeds() []faqSeed {
return []faqSeed{
{Question: "登录后台时提示账号或密码错误怎么办?", Answer: "请先确认账号是否输入完整,邮箱登录要区分公司域名,密码需注意大小写。如果连续输错 5 次,系统会临时锁定 15 分钟。仍无法登录时,可让企业管理员在“组织设置-成员管理”中重置密码,或通过登录页的“忘记密码”重新设置。", SimilarQuestions: []string{"后台密码错误怎么处理", "账号登录不上去怎么办", "提示用户名或密码错误"}, Remark: "账号登录"},
{Question: "忘记登录密码后怎么找回?", Answer: "在登录页点击“忘记密码”,输入绑定邮箱后获取验证码即可重置密码。如果账号未绑定邮箱或邮箱无法使用,请联系企业管理员协助重置。为了安全,重置成功后系统会让该账号在所有设备重新登录。", SimilarQuestions: []string{"忘记密码了怎么办", "怎么重置后台密码", "找回登录密码"}, Remark: "账号登录"},
+34
View File
@@ -0,0 +1,34 @@
package kb
import (
"agent-desk/cmd/testdata/seedlang"
"regexp"
"testing"
)
var hanTextPattern = regexp.MustCompile(`\p{Han}`)
func TestEnglishKnowledgeBaseTextDoesNotContainChineseText(t *testing.T) {
name, description, remark := faqKnowledgeBaseText(seedlang.English)
for _, value := range []string{name, description, remark} {
if hanTextPattern.MatchString(value) {
t.Fatalf("english knowledge base text contains Chinese text: %q", value)
}
}
}
func TestEnglishKnowledgeFAQSeedsDoNotContainChineseText(t *testing.T) {
seeds := knowledgeFAQSeeds(seedlang.English)
if len(seeds) == 0 {
t.Fatal("english FAQ seeds are empty")
}
for _, seed := range seeds {
values := []string{seed.Question, seed.Answer, seed.Remark}
values = append(values, seed.SimilarQuestions...)
for _, value := range values {
if hanTextPattern.MatchString(value) {
t.Fatalf("english FAQ seed contains Chinese text: %q", value)
}
}
}
}