diff --git a/cmd/enums/generator.go b/cmd/enums/generator.go index 7cf1cb7..2eb51e6 100644 --- a/cmd/enums/generator.go +++ b/cmd/enums/generator.go @@ -17,7 +17,7 @@ import ( const ( enumsPkgName = "enums" enumsDir = "internal/pkg/enums" - outputPath = "web/lib/generated/enums.ts" + outputPath = "dashboard/lib/generated/enums.ts" ) type enumValueType string diff --git a/cmd/testdata/aiagent/init.go b/cmd/testdata/aiagent/init.go index 9f0ee77..4eda9d2 100644 --- a/cmd/testdata/aiagent/init.go +++ b/cmd/testdata/aiagent/init.go @@ -87,6 +87,7 @@ func buildSeedItems(aiConfigID int64, knowledgeIDs []int64, defaultTeamIDs strin ReplyTimeoutSeconds: 180, TeamIDs: defaultTeamIDs, HandoffMode: enums.AIAgentHandoffModeWaitPool, + FallbackMode: enums.AIAgentFallbackModeSuggestRetry, FallbackMessage: "我暂时没有找到足够准确的信息。你可以补充具体的问题,我再继续帮你查。", KnowledgeIDs: utils.JoinInt64s(knowledgeIDs), SkillIDs: defaultSkillIDs, diff --git a/cmd/testdata/kb/kb.go b/cmd/testdata/kb/kb.go index a11428e..26c16c8 100644 --- a/cmd/testdata/kb/kb.go +++ b/cmd/testdata/kb/kb.go @@ -118,7 +118,6 @@ func ensureKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) { ChunkMaxTokens: 400, ChunkOverlapTokens: 40, AnswerMode: int(enums.KnowledgeAnswerModeStrict), - FallbackMode: int(enums.KnowledgeFallbackModeNoAnswer), AuditFields: models.AuditFields{ CreatedAt: now, CreateUserID: constants.SystemAuditUserID, @@ -168,7 +167,6 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) { ChunkMaxTokens: 0, ChunkOverlapTokens: 0, AnswerMode: int(enums.KnowledgeAnswerModeStrict), - FallbackMode: int(enums.KnowledgeFallbackModeNoAnswer), Remark: "测试数据初始化自动生成", AuditFields: models.AuditFields{ CreatedAt: now, @@ -197,7 +195,6 @@ func ensureFAQKnowledgeBase(db *gorm.DB) (*models.KnowledgeBase, error) { "chunk_max_tokens": 0, "chunk_overlap_tokens": 0, "answer_mode": int(enums.KnowledgeAnswerModeStrict), - "fallback_mode": int(enums.KnowledgeFallbackModeNoAnswer), "remark": "测试数据初始化自动生成", "update_user_id": constants.SystemAuditUserID, "update_user_name": constants.SystemAuditUserName, diff --git a/dashboard/app/(dashboard)/ai-agents/_components/edit.tsx b/dashboard/app/(dashboard)/ai-agents/_components/edit.tsx index f31ab4c..6f44729 100644 --- a/dashboard/app/(dashboard)/ai-agents/_components/edit.tsx +++ b/dashboard/app/(dashboard)/ai-agents/_components/edit.tsx @@ -50,6 +50,8 @@ import { } from "@/lib/api/admin"; import { getEnumOptions } from "@/lib/enums"; import { + AIAgentFallbackMode, + AIAgentFallbackModeLabels, AIAgentHandoffMode, AIAgentHandoffModeLabels, AIModelType, @@ -93,6 +95,7 @@ const schema = z.object({ .number() .min(0, "回复超时秒数必须是大于等于 0 的整数"), handoffMode: z.string().trim().min(1, "请选择转人工模式"), + fallbackMode: z.string().trim().min(1, "请选择兜底策略"), fallbackMessage: z.string().trim(), }); @@ -118,6 +121,13 @@ const handoffModeOptions = getEnumOptions(AIAgentHandoffModeLabels).map( }), ); +const fallbackModeOptions = getEnumOptions(AIAgentFallbackModeLabels).map( + (option) => ({ + value: String(option.value), + label: option.label, + }), +); + function buildForm(item: AIAgent | null): EditForm { if (!item) { return { @@ -129,6 +139,7 @@ function buildForm(item: AIAgent | null): EditForm { welcomeMessage: "", replyTimeoutSeconds: 180, handoffMode: String(AIAgentHandoffMode.WaitPool), + fallbackMode: String(AIAgentFallbackMode.NoAnswer), fallbackMessage: "", }; } @@ -141,6 +152,7 @@ function buildForm(item: AIAgent | null): EditForm { welcomeMessage: item.welcomeMessage || "", replyTimeoutSeconds: item.replyTimeoutSeconds ?? 180, handoffMode: String(item.handoffMode), + fallbackMode: String(item.fallbackMode), fallbackMessage: item.fallbackMessage || "", }; } @@ -163,6 +175,7 @@ function buildPayload( replyTimeoutSeconds: Number(form.replyTimeoutSeconds), teamIds, handoffMode: Number(form.handoffMode), + fallbackMode: Number(form.fallbackMode), fallbackMessage: form.fallbackMessage.trim(), knowledgeIds, skillIds, @@ -737,13 +750,34 @@ function EditDialogBody({ + + 知识库兜底策略 + + ( + + )} + /> + + + + 兜底文案 - 仅在兜底模式为“直接声明无答案”或“引导补充信息”时使用。转人工已统一改为 AI Graph Tool,不再通过兜底模式直接触发。 + 知识库未命中或命中证据不足时,会按这里的策略和文案进行兜底回复。转人工请通过 Graph Tool 配合转人工模式实现。 - - 回退模式 - - ( - - - - {field.value - ? getEnumLabel( - KnowledgeFallbackModeLabels, - Number(field.value), - ) - : undefined} - - - - {getEnumOptions(KnowledgeFallbackModeLabels).map((option) => ( - - {option.label} - - ))} - - - )} - /> - - - diff --git a/dashboard/lib/api/admin.ts b/dashboard/lib/api/admin.ts index f4d9601..83cbd51 100644 --- a/dashboard/lib/api/admin.ts +++ b/dashboard/lib/api/admin.ts @@ -208,6 +208,8 @@ export type AIAgent = { teams: { id: number; name: string }[] handoffMode: number handoffModeName: string + fallbackMode: number + fallbackModeName: string fallbackMessage: string knowledgeIds: number[] knowledgeBaseNames: string[] @@ -239,6 +241,7 @@ export type CreateAIAgentPayload = { replyTimeoutSeconds: number teamIds: number[] handoffMode: number + fallbackMode: number fallbackMessage: string knowledgeIds: number[] skillIds: number[] @@ -1189,8 +1192,6 @@ export type KnowledgeBase = { chunkOverlapTokens: number answerMode: number answerModeName: string - fallbackMode: number - fallbackModeName: string documentCount: number faqCount: number remark: string @@ -1212,7 +1213,6 @@ export type CreateKnowledgeBasePayload = { chunkMaxTokens: number chunkOverlapTokens: number answerMode: number - fallbackMode: number remark: string } @@ -1400,7 +1400,6 @@ export type KnowledgeSearchPayload = { export type KnowledgeAnswerPayload = KnowledgeSearchPayload & { answerMode?: number - fallbackMode?: number } export type CreateKnowledgeDocumentPayload = { diff --git a/dashboard/lib/generated/enums.ts b/dashboard/lib/generated/enums.ts index 4b62a36..d45ed3a 100644 --- a/dashboard/lib/generated/enums.ts +++ b/dashboard/lib/generated/enums.ts @@ -1,5 +1,14 @@ // Code generated by cmd/enums/generator.go. DO NOT EDIT. +export enum AIAgentFallbackMode { + NoAnswer = 1, + SuggestRetry = 2, +} +export const AIAgentFallbackModeLabels: Record = { + [AIAgentFallbackMode.NoAnswer]: "直接声明无答案", + [AIAgentFallbackMode.SuggestRetry]: "建议补充信息", +} + export enum AIAgentHandoffMode { WaitPool = 1, DefaultTeamPool = 2, @@ -274,17 +283,6 @@ export const KnowledgeDocumentIndexStatusLabels: Record = { - [KnowledgeFallbackMode.NoAnswer]: "直接声明无答案", - [KnowledgeFallbackMode.SuggestRetry]: "建议重试", - [KnowledgeFallbackMode.TransferHuman]: "转人工", -} - export enum KnowledgeFeedbackType { Like = 1, Dislike = 2, diff --git a/docs b/docs index 3b32ffb..c694af9 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 3b32ffb2f8e218f31d8dd939d2750a02a77ec189 +Subproject commit c694af9a912105490498965cdaa681659ecbcee8 diff --git a/internal/ai/rag/answer.go b/internal/ai/rag/answer.go index 24ce07e..cd2f178 100644 --- a/internal/ai/rag/answer.go +++ b/internal/ai/rag/answer.go @@ -112,15 +112,7 @@ func (s *answer) DebugAnswer(ctx context.Context, req request.KnowledgeAnswerReq } } - fallbackMode := enums.KnowledgeFallbackMode(req.FallbackMode) - if fallbackMode == 0 { - if knowledgeBase != nil { - fallbackMode = enums.KnowledgeFallbackMode(knowledgeBase.FallbackMode) - } - if fallbackMode == 0 { - fallbackMode = enums.KnowledgeFallbackModeNoAnswer - } - } + fallbackMode := enums.AIAgentFallbackModeNoAnswer answerStatus := enums.KnowledgeAnswerStatusNormal answer := "" @@ -391,12 +383,10 @@ func buildAnswerSystemPrompt(answerMode enums.KnowledgeAnswerMode) string { return "你是严格的客服知识库助手。只能依据提供的知识片段回答;如果资料不足,请明确说明知识库暂无明确信息。" } -func buildFallbackAnswer(fallbackMode enums.KnowledgeFallbackMode) string { +func buildFallbackAnswer(fallbackMode enums.AIAgentFallbackMode) string { switch fallbackMode { - case enums.KnowledgeFallbackModeSuggestRetry: + case enums.AIAgentFallbackModeSuggestRetry: return "当前知识库里没有找到足够明确的信息,你可以换个更具体的问法再试一次。" - case enums.KnowledgeFallbackModeTransferHuman: - return "当前知识库里没有找到足够明确的信息,建议转人工进一步处理。" default: return "当前知识库暂无明确信息。" } diff --git a/internal/ai/rag/answer_test.go b/internal/ai/rag/answer_test.go index 30f5b5c..92e48eb 100644 --- a/internal/ai/rag/answer_test.go +++ b/internal/ai/rag/answer_test.go @@ -11,24 +11,19 @@ import ( func TestBuildFallbackAnswer(t *testing.T) { tests := []struct { name string - mode enums.KnowledgeFallbackMode + mode enums.AIAgentFallbackMode expected string }{ { name: "no answer", - mode: enums.KnowledgeFallbackModeNoAnswer, + mode: enums.AIAgentFallbackModeNoAnswer, expected: "当前知识库暂无明确信息。", }, { name: "suggest retry", - mode: enums.KnowledgeFallbackModeSuggestRetry, + mode: enums.AIAgentFallbackModeSuggestRetry, expected: "当前知识库里没有找到足够明确的信息,你可以换个更具体的问法再试一次。", }, - { - name: "transfer human", - mode: enums.KnowledgeFallbackModeTransferHuman, - expected: "当前知识库里没有找到足够明确的信息,建议转人工进一步处理。", - }, } for _, tt := range tests { diff --git a/internal/ai/runtime/executor/knowledge_guard.go b/internal/ai/runtime/executor/knowledge_guard.go index e79b71c..aecf3a4 100644 --- a/internal/ai/runtime/executor/knowledge_guard.go +++ b/internal/ai/runtime/executor/knowledge_guard.go @@ -19,7 +19,7 @@ func buildKnowledgeGuardDecision(aiAgent models.AIAgent, retrieveResult *retriev if retrieveResult == nil || len(retrieveResult.KnowledgeBaseIDs) == 0 { return knowledgeGuardDecision{} } - fallbackReply := resolveKnowledgeFallbackReply(aiAgent, retrieveResult.FallbackMode) + fallbackReply := resolveKnowledgeFallbackReply(aiAgent) if len(retrieveResult.Hits) == 0 { return knowledgeGuardDecision{FallbackReply: fallbackReply} } @@ -32,15 +32,13 @@ func buildKnowledgeGuardDecision(aiAgent models.AIAgent, retrieveResult *retriev } } -func resolveKnowledgeFallbackReply(aiAgent models.AIAgent, fallbackMode enums.KnowledgeFallbackMode) string { +func resolveKnowledgeFallbackReply(aiAgent models.AIAgent) string { if reply := strings.TrimSpace(aiAgent.FallbackMessage); reply != "" { return reply } - switch fallbackMode { - case enums.KnowledgeFallbackModeSuggestRetry: + switch aiAgent.FallbackMode { + case enums.AIAgentFallbackModeSuggestRetry: return "当前知识库里没有找到足够明确的信息,你可以换个更具体的问法再试一次。" - case enums.KnowledgeFallbackModeTransferHuman: - return "当前知识库里没有找到足够明确的信息,建议转人工进一步处理。" default: return "当前知识库暂无明确信息。" } diff --git a/internal/ai/runtime/executor/knowledge_guard_test.go b/internal/ai/runtime/executor/knowledge_guard_test.go index 71ab01b..1059833 100644 --- a/internal/ai/runtime/executor/knowledge_guard_test.go +++ b/internal/ai/runtime/executor/knowledge_guard_test.go @@ -12,9 +12,9 @@ import ( func TestBuildKnowledgeGuardDecisionFallsBackWhenKnowledgeMisses(t *testing.T) { agent := newKnowledgeGuardAgentFixture() + agent.FallbackMode = enums.AIAgentFallbackModeSuggestRetry decision := buildKnowledgeGuardDecision(agent, &retrievers.KnowledgeRetrieveResult{ KnowledgeBaseIDs: []int64{1}, - FallbackMode: enums.KnowledgeFallbackModeSuggestRetry, }) if decision.FallbackReply != "当前知识库里没有找到足够明确的信息,你可以换个更具体的问法再试一次。" { @@ -30,7 +30,6 @@ func TestBuildKnowledgeGuardDecisionUsesAgentFallbackMessage(t *testing.T) { agent.FallbackMessage = "请联系人工客服" decision := buildKnowledgeGuardDecision(agent, &retrievers.KnowledgeRetrieveResult{ KnowledgeBaseIDs: []int64{1}, - FallbackMode: enums.KnowledgeFallbackModeNoAnswer, }) if decision.FallbackReply != "请联系人工客服" { @@ -45,8 +44,7 @@ func TestBuildKnowledgeGuardDecisionInjectsStrictInstructionOnHit(t *testing.T) Hits: []rag.RetrieveResult{ {KnowledgeBaseID: 1, Score: 0.88}, }, - AnswerMode: enums.KnowledgeAnswerModeStrict, - FallbackMode: enums.KnowledgeFallbackModeNoAnswer, + AnswerMode: enums.KnowledgeAnswerModeStrict, }) if decision.FallbackReply != "" { @@ -65,5 +63,7 @@ func TestBuildKnowledgeGuardDecisionInjectsStrictInstructionOnHit(t *testing.T) } func newKnowledgeGuardAgentFixture() models.AIAgent { - return models.AIAgent{} + return models.AIAgent{ + FallbackMode: enums.AIAgentFallbackModeNoAnswer, + } } diff --git a/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go b/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go index 42995c1..2a58a81 100644 --- a/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go +++ b/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go @@ -46,7 +46,6 @@ type KnowledgeRetrieveResult struct { ContextText string TopScore float64 AnswerMode enums.KnowledgeAnswerMode - FallbackMode enums.KnowledgeFallbackMode Trace *rag.RetrieveTrace TraceItems []callbacks.RetrieverTraceItem TraceSummary callbacks.RetrieverTraceSummary @@ -127,7 +126,7 @@ func (r *KnowledgeRetriever) RetrieveContextByOptions(ctx context.Context, opts ret.ContextResults = limitContextResults(ret.ContextResults, maxContextItems) ret.ContextText = strings.TrimSpace(buildContextText(ret.ContextResults)) ret.TopScore = resolveTopScore(results) - ret.AnswerMode, ret.FallbackMode = resolveRuntimeAnswerSettings(knowledgeBaseIDs, results) + ret.AnswerMode = resolveRuntimeAnswerMode(knowledgeBaseIDs, results) ret.TraceItems = buildRetrieverTraceItems(queryPreview, results, trace) ret.TraceSummary = buildRetrieverTraceSummary(ret.Options, ret.Policies, ret.ContextResults, results, trace) return ret, nil @@ -188,34 +187,30 @@ func (r *KnowledgeRetriever) resolvePolicies(knowledgeBaseIDs []int64, opts Know return ret } -func resolveRuntimeAnswerSettings(knowledgeBaseIDs []int64, results []rag.RetrieveResult) (enums.KnowledgeAnswerMode, enums.KnowledgeFallbackMode) { +func resolveRuntimeAnswerMode(knowledgeBaseIDs []int64, results []rag.RetrieveResult) enums.KnowledgeAnswerMode { knowledgeBases := loadRuntimeKnowledgeBases(knowledgeBaseIDs) if len(knowledgeBases) == 0 { - return enums.KnowledgeAnswerModeStrict, enums.KnowledgeFallbackModeNoAnswer + return enums.KnowledgeAnswerModeStrict } if len(results) > 0 { if knowledgeBase, ok := knowledgeBases[results[0].KnowledgeBaseID]; ok { - return normalizeRuntimeAnswerSettings(knowledgeBase) + return normalizeRuntimeAnswerMode(knowledgeBase) } } for _, knowledgeBaseID := range knowledgeBaseIDs { if knowledgeBase, ok := knowledgeBases[knowledgeBaseID]; ok { - return normalizeRuntimeAnswerSettings(knowledgeBase) + return normalizeRuntimeAnswerMode(knowledgeBase) } } - return enums.KnowledgeAnswerModeStrict, enums.KnowledgeFallbackModeNoAnswer + return enums.KnowledgeAnswerModeStrict } -func normalizeRuntimeAnswerSettings(knowledgeBase models.KnowledgeBase) (enums.KnowledgeAnswerMode, enums.KnowledgeFallbackMode) { +func normalizeRuntimeAnswerMode(knowledgeBase models.KnowledgeBase) enums.KnowledgeAnswerMode { answerMode := enums.KnowledgeAnswerMode(knowledgeBase.AnswerMode) if answerMode == 0 { answerMode = enums.KnowledgeAnswerModeStrict } - fallbackMode := enums.KnowledgeFallbackMode(knowledgeBase.FallbackMode) - if fallbackMode == 0 { - fallbackMode = enums.KnowledgeFallbackModeNoAnswer - } - return answerMode, fallbackMode + return answerMode } func loadRuntimeKnowledgeBases(ids []int64) map[int64]models.KnowledgeBase { diff --git a/internal/builders/knowledge_builder.go b/internal/builders/knowledge_builder.go index 00fcd89..cf9a094 100644 --- a/internal/builders/knowledge_builder.go +++ b/internal/builders/knowledge_builder.go @@ -25,8 +25,6 @@ func BuildKnowledgeBase(item *models.KnowledgeBase) response.KnowledgeBaseRespon ChunkOverlapTokens: item.ChunkOverlapTokens, AnswerMode: item.AnswerMode, AnswerModeName: enums.GetKnowledgeAnswerModeLabel(enums.KnowledgeAnswerMode(item.AnswerMode)), - FallbackMode: item.FallbackMode, - FallbackModeName: enums.GetKnowledgeFallbackModeLabel(enums.KnowledgeFallbackMode(item.FallbackMode)), Remark: item.Remark, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, diff --git a/internal/controllers/dashboard/ai_agent_controller.go b/internal/controllers/dashboard/ai_agent_controller.go index 52fd172..a6bf6b2 100644 --- a/internal/controllers/dashboard/ai_agent_controller.go +++ b/internal/controllers/dashboard/ai_agent_controller.go @@ -124,6 +124,8 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse { ReplyTimeoutSeconds: item.ReplyTimeoutSeconds, HandoffMode: item.HandoffMode, HandoffModeName: enums.GetAIAgentHandoffModeLabel(item.HandoffMode), + FallbackMode: item.FallbackMode, + FallbackModeName: enums.GetAIAgentFallbackModeLabel(item.FallbackMode), FallbackMessage: item.FallbackMessage, KnowledgeIDs: utils.SplitInt64s(item.KnowledgeIDs), SkillIDs: utils.SplitInt64s(item.SkillIDs), diff --git a/internal/models/models.go b/internal/models/models.go index 183dcb0..c7aef65 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -509,6 +509,7 @@ type AIAgent struct { ReplyTimeoutSeconds int `gorm:"type:int;not null;default:180"` // ReplyTimeoutSeconds 为异步自动回复超时秒数。 TeamIDs string `gorm:"type:varchar(500);not null;default:''"` // TeamIDs 为转人工时可路由的客服组ID列表,多个之间使用逗号分隔。 HandoffMode enums.AIAgentHandoffMode `gorm:"type:int;not null;default:1"` // HandoffMode 为转人工模式,如进入待接入池、进入默认客服组待接入池。 + FallbackMode enums.AIAgentFallbackMode `gorm:"type:int;not null;default:1"` // FallbackMode 为知识库未命中时的兜底策略。 FallbackMessage string `gorm:"type:text"` // FallbackMessage 为兜底回复文案。 KnowledgeIDs string `gorm:"type:varchar(500);not null;default:''"` // KnowledgeIDs 为绑定的知识库ID列表,按顺序表示优先级。 SkillIDs string `gorm:"type:varchar(500);not null;default:''"` // SkillIDs 为绑定的技能ID列表,按顺序表示允许路由的范围。 @@ -775,7 +776,6 @@ type KnowledgeBase struct { ChunkMaxTokens int `gorm:"type:int;not null;default:400"` // ChunkMaxTokens 为单 chunk 最大 token 数。 ChunkOverlapTokens int `gorm:"type:int;not null;default:40"` // ChunkOverlapTokens 为相邻 chunk 重叠 token 数。 AnswerMode int `gorm:"type:int;not null;default:1"` // AnswerMode 为回答模式:1严格知识库模式 2辅助解释模式。 - FallbackMode int `gorm:"type:int;not null;default:1"` // FallbackMode 为兜底模式:1声明无答案 2引导换问法 3转人工。 SortNo int `gorm:"type:int;not null;default:0;index"` // SortNo 为排序号,用于后台展示和知识库的人工排序管理。 Remark string `gorm:"type:text"` // Remark 为备注。 AuditFields diff --git a/internal/pkg/dto/request/ai_request.go b/internal/pkg/dto/request/ai_request.go index edefdaf..d8bce1b 100644 --- a/internal/pkg/dto/request/ai_request.go +++ b/internal/pkg/dto/request/ai_request.go @@ -52,6 +52,7 @@ type CreateAIAgentRequest struct { ReplyTimeoutSeconds int `json:"replyTimeoutSeconds"` TeamIDs []int64 `json:"teamIds"` HandoffMode enums.AIAgentHandoffMode `json:"handoffMode"` + FallbackMode enums.AIAgentFallbackMode `json:"fallbackMode"` FallbackMessage string `json:"fallbackMessage"` KnowledgeIDs []int64 `json:"knowledgeIds"` SkillIDs []int64 `json:"skillIds"` diff --git a/internal/pkg/dto/request/knowledge_request.go b/internal/pkg/dto/request/knowledge_request.go index 4cec191..5de5126 100644 --- a/internal/pkg/dto/request/knowledge_request.go +++ b/internal/pkg/dto/request/knowledge_request.go @@ -14,7 +14,6 @@ type CreateKnowledgeBaseRequest struct { ChunkMaxTokens int `json:"chunkMaxTokens"` ChunkOverlapTokens int `json:"chunkOverlapTokens"` AnswerMode int `json:"answerMode"` - FallbackMode int `json:"fallbackMode"` Remark string `json:"remark"` } @@ -71,7 +70,6 @@ type KnowledgeAnswerRequest struct { SessionID string `json:"sessionId"` ConversationID int64 `json:"conversationId"` AnswerMode int `json:"answerMode"` - FallbackMode int `json:"fallbackMode"` } type CreateKnowledgeFeedbackRequest struct { diff --git a/internal/pkg/dto/response/ai_response.go b/internal/pkg/dto/response/ai_response.go index e2cc3ea..cc2665b 100644 --- a/internal/pkg/dto/response/ai_response.go +++ b/internal/pkg/dto/response/ai_response.go @@ -83,6 +83,8 @@ type AIAgentResponse struct { Teams []AIAgentTeamResponse `json:"teams"` HandoffMode enums.AIAgentHandoffMode `json:"handoffMode"` HandoffModeName string `json:"handoffModeName"` + FallbackMode enums.AIAgentFallbackMode `json:"fallbackMode"` + FallbackModeName string `json:"fallbackModeName"` FallbackMessage string `json:"fallbackMessage"` KnowledgeIDs []int64 `json:"knowledgeIds"` KnowledgeBaseNames []string `json:"knowledgeBaseNames"` diff --git a/internal/pkg/dto/response/knowledge_response.go b/internal/pkg/dto/response/knowledge_response.go index 5a3062c..a22cd8c 100644 --- a/internal/pkg/dto/response/knowledge_response.go +++ b/internal/pkg/dto/response/knowledge_response.go @@ -22,8 +22,6 @@ type KnowledgeBaseResponse struct { ChunkOverlapTokens int `json:"chunkOverlapTokens"` AnswerMode int `json:"answerMode"` AnswerModeName string `json:"answerModeName"` - FallbackMode int `json:"fallbackMode"` - FallbackModeName string `json:"fallbackModeName"` DocumentCount int64 `json:"documentCount"` FAQCount int64 `json:"faqCount"` Remark string `json:"remark"` diff --git a/internal/pkg/enums/im.go b/internal/pkg/enums/im.go index 244a4c1..2cf7f86 100644 --- a/internal/pkg/enums/im.go +++ b/internal/pkg/enums/im.go @@ -215,6 +215,27 @@ func GetAIAgentHandoffModeLabel(mode AIAgentHandoffMode) string { return aiAgentHandoffModeLabelMap[mode] } +type AIAgentFallbackMode int + +const ( + AIAgentFallbackModeNoAnswer AIAgentFallbackMode = 1 + AIAgentFallbackModeSuggestRetry AIAgentFallbackMode = 2 +) + +var AIAgentFallbackModeValues = []AIAgentFallbackMode{ + AIAgentFallbackModeNoAnswer, + AIAgentFallbackModeSuggestRetry, +} + +var aiAgentFallbackModeLabelMap = map[AIAgentFallbackMode]string{ + AIAgentFallbackModeNoAnswer: "直接声明无答案", + AIAgentFallbackModeSuggestRetry: "建议补充信息", +} + +func GetAIAgentFallbackModeLabel(mode AIAgentFallbackMode) string { + return aiAgentFallbackModeLabelMap[mode] +} + const ( IMRealtimeEventConnected = "connected" IMRealtimeEventPong = "pong" diff --git a/internal/pkg/enums/knowledge.go b/internal/pkg/enums/knowledge.go index cc95f1d..0ab43c8 100644 --- a/internal/pkg/enums/knowledge.go +++ b/internal/pkg/enums/knowledge.go @@ -214,21 +214,3 @@ var knowledgeAnswerModeLabelMap = map[KnowledgeAnswerMode]string{ func GetKnowledgeAnswerModeLabel(mode KnowledgeAnswerMode) string { return knowledgeAnswerModeLabelMap[mode] } - -type KnowledgeFallbackMode int - -const ( - KnowledgeFallbackModeNoAnswer KnowledgeFallbackMode = 1 - KnowledgeFallbackModeSuggestRetry KnowledgeFallbackMode = 2 - KnowledgeFallbackModeTransferHuman KnowledgeFallbackMode = 3 -) - -var knowledgeFallbackModeLabelMap = map[KnowledgeFallbackMode]string{ - KnowledgeFallbackModeNoAnswer: "直接声明无答案", - KnowledgeFallbackModeSuggestRetry: "建议重试", - KnowledgeFallbackModeTransferHuman: "转人工", -} - -func GetKnowledgeFallbackModeLabel(mode KnowledgeFallbackMode) string { - return knowledgeFallbackModeLabelMap[mode] -} diff --git a/internal/services/ai_agent_service.go b/internal/services/ai_agent_service.go index 880aa52..62c08a3 100644 --- a/internal/services/ai_agent_service.go +++ b/internal/services/ai_agent_service.go @@ -101,6 +101,7 @@ func (s *aIAgentService) UpdateAIAgent(req request.UpdateAIAgentRequest, operato "reply_timeout_seconds": item.ReplyTimeoutSeconds, "team_ids": item.TeamIDs, "handoff_mode": item.HandoffMode, + "fallback_mode": item.FallbackMode, "fallback_message": item.FallbackMessage, "knowledge_ids": item.KnowledgeIDs, "skill_ids": item.SkillIDs, @@ -157,6 +158,12 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe if !slices.Contains(enums.AIAgentHandoffModeValues, enums.AIAgentHandoffMode(req.HandoffMode)) { return nil, errorsx.InvalidParam("转人工模式不合法") } + if req.FallbackMode == 0 { + req.FallbackMode = enums.AIAgentFallbackModeNoAnswer + } + if !slices.Contains(enums.AIAgentFallbackModeValues, enums.AIAgentFallbackMode(req.FallbackMode)) { + return nil, errorsx.InvalidParam("兜底策略不合法") + } if enums.AIAgentHandoffMode(req.HandoffMode) == enums.AIAgentHandoffModeDefaultTeamPool && len(teamIDs) == 0 { return nil, errorsx.InvalidParam("默认客服组待接入池模式必须至少选择一个客服组") } @@ -209,6 +216,7 @@ func (s *aIAgentService) buildAIAgentModel(id int64, req request.CreateAIAgentRe ReplyTimeoutSeconds: req.ReplyTimeoutSeconds, TeamIDs: utils.JoinInt64s(teamIDs), HandoffMode: req.HandoffMode, + FallbackMode: req.FallbackMode, FallbackMessage: strings.TrimSpace(req.FallbackMessage), KnowledgeIDs: utils.JoinInt64s(knowledgeIDs), SkillIDs: utils.JoinInt64s(skillIDs), diff --git a/internal/services/knowledge_base_service.go b/internal/services/knowledge_base_service.go index 44813fd..9ec0afc 100644 --- a/internal/services/knowledge_base_service.go +++ b/internal/services/knowledge_base_service.go @@ -112,7 +112,6 @@ func (s *knowledgeBaseService) UpdateKnowledgeBase(req request.UpdateKnowledgeBa "chunk_max_tokens": item.ChunkMaxTokens, "chunk_overlap_tokens": item.ChunkOverlapTokens, "answer_mode": item.AnswerMode, - "fallback_mode": item.FallbackMode, "remark": item.Remark, "update_user_id": operator.UserID, "update_user_name": operator.Username, @@ -161,7 +160,6 @@ func (s *knowledgeBaseService) buildKnowledgeBaseModel(req request.CreateKnowled ChunkMaxTokens: req.ChunkMaxTokens, ChunkOverlapTokens: req.ChunkOverlapTokens, AnswerMode: req.AnswerMode, - FallbackMode: req.FallbackMode, Remark: req.Remark, } if item.DefaultTopK == 0 { @@ -208,9 +206,6 @@ func (s *knowledgeBaseService) buildKnowledgeBaseModel(req request.CreateKnowled if item.AnswerMode == 0 { item.AnswerMode = 1 } - if item.FallbackMode == 0 { - item.FallbackMode = 1 - } return item, nil } diff --git a/web/lib/generated/enums.ts b/web/lib/generated/enums.ts new file mode 100644 index 0000000..d45ed3a --- /dev/null +++ b/web/lib/generated/enums.ts @@ -0,0 +1,389 @@ +// Code generated by cmd/enums/generator.go. DO NOT EDIT. + +export enum AIAgentFallbackMode { + NoAnswer = 1, + SuggestRetry = 2, +} +export const AIAgentFallbackModeLabels: Record = { + [AIAgentFallbackMode.NoAnswer]: "直接声明无答案", + [AIAgentFallbackMode.SuggestRetry]: "建议补充信息", +} + +export enum AIAgentHandoffMode { + WaitPool = 1, + DefaultTeamPool = 2, + AIHoldAndNotify = 3, +} +export const AIAgentHandoffModeLabels: Record = { + [AIAgentHandoffMode.WaitPool]: "进入待接入池", + [AIAgentHandoffMode.DefaultTeamPool]: "进入默认客服组待接入池", + [AIAgentHandoffMode.AIHoldAndNotify]: "AI托底并提醒人工", +} + +export enum AIModelType { + LLM = "llm", + Embedding = "embedding", + Rerank = "rerank", +} +export const AIModelTypeLabels: Record = { + [AIModelType.LLM]: "大语言模型", + [AIModelType.Embedding]: "向量模型", + [AIModelType.Rerank]: "重排序模型", +} + +export enum AIProvider { + OpenAI = "openai", +} +export const AIProviderLabels: Record = { + [AIProvider.OpenAI]: "OpenAI", +} + +export enum AssetProvider { + Local = "local", + OSS = "oss", +} +export const AssetProviderLabels: Record = { + [AssetProvider.Local]: "本地存储", + [AssetProvider.OSS]: "对象存储", +} + +export enum AssetStatus { + Pending = 1, + Success = 2, + Failed = 3, + Deleted = 4, +} +export const AssetStatusLabels: Record = { + [AssetStatus.Pending]: "处理中", + [AssetStatus.Success]: "成功", + [AssetStatus.Failed]: "失败", + [AssetStatus.Deleted]: "已删除", +} + +export enum ContactType { + Mobile = "mobile", + Email = "email", + Other = "other", +} +export const ContactTypeLabels: Record = { + [ContactType.Mobile]: "手机号", + [ContactType.Email]: "邮箱", + [ContactType.Other]: "其他", +} + +export enum ExternalSource { + WebChat = "web_chat", + WxWorkKF = "wxwork_kf", +} +export const ExternalSourceLabels: Record = { + [ExternalSource.WebChat]: "网页客服", + [ExternalSource.WxWorkKF]: "企业微信客服", +} + +export enum Gender { + Unknown = 0, + Male = 1, + Female = 2, +} +export const GenderLabels: Record = { + [Gender.Unknown]: "未知", + [Gender.Male]: "男", + [Gender.Female]: "女", +} + +export enum IMAssignmentStatus { + Active = 0, + Inactive = 1, +} +export const IMAssignmentStatusLabels: Record = { + [IMAssignmentStatus.Active]: "进行中", + [IMAssignmentStatus.Inactive]: "已结束", +} + +export enum IMAssignmentType { + Assign = "assign", + Transfer = "transfer", +} +export const IMAssignmentTypeLabels: Record = { + [IMAssignmentType.Assign]: "分配", + [IMAssignmentType.Transfer]: "转接", +} + +export enum IMConversationServiceMode { + AIOnly = 1, + HumanOnly = 2, + AIFirst = 3, +} +export const IMConversationServiceModeLabels: Record = { + [IMConversationServiceMode.AIOnly]: "仅AI", + [IMConversationServiceMode.HumanOnly]: "仅人工", + [IMConversationServiceMode.AIFirst]: "AI优先", +} + +export enum IMConversationStatus { + AIServing = 1, + Pending = 2, + Active = 3, + Closed = 4, +} +export const IMConversationStatusLabels: Record = { + [IMConversationStatus.AIServing]: "AI接待中", + [IMConversationStatus.Pending]: "待接入", + [IMConversationStatus.Active]: "处理中", + [IMConversationStatus.Closed]: "已关闭", +} + +export enum IMEventType { + Create = "create", + Assign = "assign", + Transfer = "transfer", + Close = "close", + MessageSend = "message_send", + MessageRecall = "message_recall", +} +export const IMEventTypeLabels: Record = { + [IMEventType.Create]: "创建会话", + [IMEventType.Assign]: "分配会话", + [IMEventType.Transfer]: "转接会话", + [IMEventType.Close]: "关闭会话", + [IMEventType.MessageSend]: "发送消息", + [IMEventType.MessageRecall]: "撤回消息", +} + +export enum IMMessageStatus { + Sending = 1, + Sent = 2, + Delivered = 3, + Read = 4, + Failed = 5, + Recalled = 6, +} +export const IMMessageStatusLabels: Record = { + [IMMessageStatus.Sending]: "发送中", + [IMMessageStatus.Sent]: "已发送", + [IMMessageStatus.Delivered]: "已送达", + [IMMessageStatus.Read]: "已读", + [IMMessageStatus.Failed]: "发送失败", + [IMMessageStatus.Recalled]: "已撤回", +} + +export enum IMMessageType { + Text = "text", + Image = "image", + Attachment = "attachment", + HTML = "html", +} +export const IMMessageTypeLabels: Record = { + [IMMessageType.Text]: "文本", + [IMMessageType.Image]: "图片", + [IMMessageType.Attachment]: "附件", + [IMMessageType.HTML]: "富文本", +} + +export enum IMParticipantType { + Customer = "customer", + Agent = "agent", + AI = "ai", + System = "system", +} +export const IMParticipantTypeLabels: Record = { + [IMParticipantType.Customer]: "客户", + [IMParticipantType.Agent]: "客服", + [IMParticipantType.AI]: "AI", + [IMParticipantType.System]: "系统", +} + +export enum IMSenderType { + Agent = "agent", + Customer = "customer", + AI = "ai", + System = "system", +} +export const IMSenderTypeLabels: Record = { + [IMSenderType.Agent]: "客服", + [IMSenderType.Customer]: "客户", + [IMSenderType.AI]: "AI", + [IMSenderType.System]: "系统", +} + +export enum KnowledgeAnswerMode { + Strict = 1, + Assist = 2, +} +export const KnowledgeAnswerModeLabels: Record = { + [KnowledgeAnswerMode.Strict]: "严格模式", + [KnowledgeAnswerMode.Assist]: "辅助模式", +} + +export enum KnowledgeAnswerStatus { + Normal = 1, + NoAnswer = 2, + Fallback = 3, + Blocked = 4, +} +export const KnowledgeAnswerStatusLabels: Record = { + [KnowledgeAnswerStatus.Normal]: "正常", + [KnowledgeAnswerStatus.NoAnswer]: "无答案", + [KnowledgeAnswerStatus.Fallback]: "兜底回复", + [KnowledgeAnswerStatus.Blocked]: "已屏蔽", +} + +export enum KnowledgeBaseType { + Document = "document", + FAQ = "faq", +} +export const KnowledgeBaseTypeLabels: Record = { + [KnowledgeBaseType.Document]: "文档知识库", + [KnowledgeBaseType.FAQ]: "FAQ知识库", +} + +export enum KnowledgeChunkProvider { + Fixed = "fixed", + Structured = "structured", + FAQ = "faq", + Semantic = "semantic", +} +export const KnowledgeChunkProviderLabels: Record = { + [KnowledgeChunkProvider.Fixed]: "固定长度", + [KnowledgeChunkProvider.Structured]: "结构化分块", + [KnowledgeChunkProvider.FAQ]: "问答式分块", + [KnowledgeChunkProvider.Semantic]: "语义分块", +} + +export enum KnowledgeChunkType { + Text = "text", + FAQ = "faq", + Table = "table", + Code = "code", +} +export const KnowledgeChunkTypeLabels: Record = { + [KnowledgeChunkType.Text]: "文本", + [KnowledgeChunkType.FAQ]: "问答", + [KnowledgeChunkType.Table]: "表格", + [KnowledgeChunkType.Code]: "代码", +} + +export enum KnowledgeDocumentContentType { + HTML = "html", + Markdown = "markdown", +} +export const KnowledgeDocumentContentTypeLabels: Record = { + [KnowledgeDocumentContentType.HTML]: "HTML", + [KnowledgeDocumentContentType.Markdown]: "Markdown", +} + +export enum KnowledgeDocumentIndexStatus { + Pending = "pending", + Indexed = "indexed", + Failed = "failed", +} +export const KnowledgeDocumentIndexStatusLabels: Record = { + [KnowledgeDocumentIndexStatus.Pending]: "待索引", + [KnowledgeDocumentIndexStatus.Indexed]: "已索引", + [KnowledgeDocumentIndexStatus.Failed]: "索引失败", +} + +export enum KnowledgeFeedbackType { + Like = 1, + Dislike = 2, + NotHelpful = 3, + WrongCitation = 4, + Other = 5, +} +export const KnowledgeFeedbackTypeLabels: Record = { + [KnowledgeFeedbackType.Like]: "点赞", + [KnowledgeFeedbackType.Dislike]: "点踩", + [KnowledgeFeedbackType.NotHelpful]: "无帮助", + [KnowledgeFeedbackType.WrongCitation]: "引用错误", + [KnowledgeFeedbackType.Other]: "其他", +} + +export enum KnowledgeRetrieveChannel { + IM = "im", + AgentAssist = "agent_assist", + API = "api", + Debug = "debug", +} +export const KnowledgeRetrieveChannelLabels: Record = { + [KnowledgeRetrieveChannel.IM]: "客服会话", + [KnowledgeRetrieveChannel.AgentAssist]: "客服助手", + [KnowledgeRetrieveChannel.API]: "API接口", + [KnowledgeRetrieveChannel.Debug]: "调试", +} + +export enum KnowledgeRetrieveScene { + FirstResponse = "first_response", + Assist = "assist", + QA = "qa", +} +export const KnowledgeRetrieveSceneLabels: Record = { + [KnowledgeRetrieveScene.FirstResponse]: "首次回复", + [KnowledgeRetrieveScene.Assist]: "辅助回复", + [KnowledgeRetrieveScene.QA]: "问答", +} + +export enum ServiceStatus { + Idle = 0, + Busy = 1, +} +export const ServiceStatusLabels: Record = { + [ServiceStatus.Idle]: "空闲", + [ServiceStatus.Busy]: "忙碌", +} + +export enum Status { + Ok = 0, + Disabled = 1, + Deleted = 2, +} +export const StatusLabels: Record = { + [Status.Ok]: "启用", + [Status.Disabled]: "禁用", + [Status.Deleted]: "已删除", +} + +export enum ThirdProvider { + WxWork = "wxwork", + Dingtalk = "dingtalk", +} +export const ThirdProviderLabels: Record = { + [ThirdProvider.WxWork]: "企业微信", + [ThirdProvider.Dingtalk]: "钉钉", +} + +export enum TicketSeverity { + Minor = 1, + Major = 2, + Critical = 3, +} +export const TicketSeverityLabels: Record = { + [TicketSeverity.Minor]: "轻微", + [TicketSeverity.Major]: "严重", + [TicketSeverity.Critical]: "致命", +} + +export enum TicketStatus { + New = "new", + Open = "open", + PendingCustomer = "pending_customer", + PendingInternal = "pending_internal", + Resolved = "resolved", + Closed = "closed", + Cancelled = "cancelled", +} +export const TicketStatusLabels: Record = { + [TicketStatus.New]: "新建", + [TicketStatus.Open]: "处理中", + [TicketStatus.PendingCustomer]: "待客户反馈", + [TicketStatus.PendingInternal]: "待内部处理", + [TicketStatus.Resolved]: "已解决", + [TicketStatus.Closed]: "已关闭", + [TicketStatus.Cancelled]: "已取消", +} + +export enum VectorDBType { + Qdrant = "qdrant", +} +export const VectorDBTypeLabels: Record = { + [VectorDBType.Qdrant]: "Qdrant", +}