prompt_config_service.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package service
  2. import (
  3. "errors"
  4. "time"
  5. "github.com/2930134478/AI-CS/backend/models"
  6. "github.com/2930134478/AI-CS/backend/repository"
  7. )
  8. // PromptConfigService 系统提示词配置服务(供「提示词」页与 AIService 使用)
  9. type PromptConfigService struct {
  10. repo *repository.PromptConfigRepository
  11. userRepo *repository.UserRepository
  12. }
  13. // NewPromptConfigService 创建服务实例
  14. func NewPromptConfigService(repo *repository.PromptConfigRepository, userRepo *repository.UserRepository) *PromptConfigService {
  15. return &PromptConfigService{repo: repo, userRepo: userRepo}
  16. }
  17. // GetPrompt 按 key 获取配置内容,未配置返回空字符串
  18. func (s *PromptConfigService) GetPrompt(key string) (string, error) {
  19. c, err := s.repo.Get(key)
  20. if err != nil {
  21. return "", err
  22. }
  23. if c == nil {
  24. return "", nil
  25. }
  26. return c.Content, nil
  27. }
  28. // GetPromptOrDefault 获取配置内容,若为空则返回 defaultContent(供 AIService 使用)
  29. func (s *PromptConfigService) GetPromptOrDefault(key, defaultContent string) (string, error) {
  30. content, err := s.GetPrompt(key)
  31. if err != nil {
  32. return "", err
  33. }
  34. if content != "" {
  35. return content, nil
  36. }
  37. return defaultContent, nil
  38. }
  39. // PromptItem 返回给前端的单项(含展示名)
  40. type PromptItem struct {
  41. Key string `json:"key"`
  42. Name string `json:"name"`
  43. Content string `json:"content"`
  44. UpdatedAt time.Time `json:"updated_at,omitempty"`
  45. }
  46. // GetPromptName 返回 key 对应的中文展示名
  47. func GetPromptName(key string) string {
  48. switch key {
  49. case models.PromptKeyRAG:
  50. return "RAG 基础提示词(仅知识库)"
  51. case models.PromptKeyRAGWithWebOptional:
  52. return "RAG + 联网可选提示词"
  53. case models.PromptKeyNoKB:
  54. return "无知识库时的提示词"
  55. case models.PromptKeyWebSearchResult:
  56. return "联网结果拼接提示词"
  57. case models.PromptKeyNoSourceReply:
  58. return "无任何来源时的回复语"
  59. case models.PromptKeyAIFailReply:
  60. return "AI 调用失败时的回复语"
  61. default:
  62. return key
  63. }
  64. }
  65. // GetAllForAPI 获取所有已定义的 prompt 项(若 DB 无则返回默认内容,用于前端展示与编辑)
  66. func (s *PromptConfigService) GetAllForAPI() ([]PromptItem, error) {
  67. keys := []string{
  68. models.PromptKeyRAG,
  69. models.PromptKeyRAGWithWebOptional,
  70. models.PromptKeyNoKB,
  71. models.PromptKeyWebSearchResult,
  72. models.PromptKeyNoSourceReply,
  73. models.PromptKeyAIFailReply,
  74. }
  75. list, err := s.repo.GetAll()
  76. if err != nil {
  77. return nil, err
  78. }
  79. byKey := make(map[string]*models.PromptConfig)
  80. for i := range list {
  81. byKey[list[i].Key] = &list[i]
  82. }
  83. out := make([]PromptItem, 0, len(keys))
  84. for _, k := range keys {
  85. item := PromptItem{Key: k, Name: GetPromptName(k)}
  86. if c := byKey[k]; c != nil {
  87. item.Content = c.Content
  88. item.UpdatedAt = c.UpdatedAt
  89. }
  90. if item.Content == "" {
  91. item.Content = getDefaultPromptContent(k)
  92. }
  93. out = append(out, item)
  94. }
  95. return out, nil
  96. }
  97. func getDefaultPromptContent(key string) string {
  98. switch key {
  99. case models.PromptKeyRAG:
  100. return defaultRAGPrompt
  101. case models.PromptKeyRAGWithWebOptional:
  102. return defaultRAGPromptWithWebOptional
  103. case models.PromptKeyNoKB:
  104. return defaultNoKBPrompt
  105. case models.PromptKeyWebSearchResult:
  106. return defaultWebSearchResultPrompt
  107. case models.PromptKeyNoSourceReply:
  108. return defaultNoSourceReply
  109. case models.PromptKeyAIFailReply:
  110. return defaultAIFailReply
  111. default:
  112. return ""
  113. }
  114. }
  115. // 默认模板(占位符:{{rag_context}}、{{user_message}})
  116. const (
  117. defaultRAGPrompt = `你是一个智能客服助手,请基于以下知识库内容回答用户的问题。
  118. 知识库内容:
  119. {{rag_context}}
  120. 用户问题:
  121. {{user_message}}
  122. 请根据知识库内容回答用户的问题。如果知识库中没有相关信息,请礼貌地告知用户,并建议联系人工客服。
  123. 回答要求:
  124. 1. 基于知识库内容,提供准确、有用的回答
  125. 2. 如果知识库中有相关信息,请直接引用并解释
  126. 3. 如果知识库中没有相关信息,请诚实告知
  127. 4. 保持友好、专业的语气
  128. 5. 回答要简洁明了,避免冗长`
  129. defaultRAGPromptWithWebOptional = `你是一个智能客服助手。请优先基于以下知识库内容回答用户的问题。
  130. 知识库内容:
  131. {{rag_context}}
  132. 用户问题:
  133. {{user_message}}
  134. 回答要求:
  135. 1. 若知识库内容与问题明确相关,请基于知识库给出准确、简洁的回答。
  136. 2. 若知识库内容与问题无关或仅弱相关,可先基于你自身的知识回答,不必拘泥于知识库。
  137. 3. 若你自身知识仍不足以回答(例如需要最新资讯、实时数据),你可决定是否使用联网搜索获取信息后再回答。
  138. 4. 保持友好、专业,回答简洁明了。`
  139. defaultNoKBPrompt = `你是一个智能客服助手。当前未使用知识库,请仅基于你的知识回答用户问题。
  140. 用户问题:
  141. {{user_message}}
  142. 请简洁、友好地回答。若无法回答,可建议用户联系人工客服。`
  143. defaultWebSearchResultPrompt = `你是一个智能客服助手。请结合以下联网搜索结果回答用户问题。
  144. 联网搜索结果:
  145. {{web_context}}
  146. 用户问题:
  147. {{user_message}}
  148. 请基于以上内容给出简洁、准确的回答。`
  149. defaultNoSourceReply = "当前知识库暂无与此问题相关的内容,您可以尝试联系人工客服。"
  150. defaultAIFailReply = "AI客服好像出了点差错,请联系人工客服解决"
  151. )
  152. // Update 更新指定 key 的提示词内容(仅管理员)
  153. func (s *PromptConfigService) Update(userID uint, key, content string) error {
  154. user, err := s.userRepo.GetByID(userID)
  155. if err != nil || user == nil {
  156. return errors.New("用户不存在")
  157. }
  158. if user.Role != "admin" {
  159. return errors.New("仅管理员可修改提示词配置")
  160. }
  161. allowedKeys := map[string]bool{
  162. models.PromptKeyRAG: true,
  163. models.PromptKeyRAGWithWebOptional: true,
  164. models.PromptKeyNoKB: true,
  165. models.PromptKeyWebSearchResult: true,
  166. models.PromptKeyNoSourceReply: true,
  167. models.PromptKeyAIFailReply: true,
  168. }
  169. if !allowedKeys[key] {
  170. return errors.New("不支持的提示词 key")
  171. }
  172. c, _ := s.repo.Get(key)
  173. if c == nil {
  174. c = &models.PromptConfig{Key: key}
  175. }
  176. c.Content = content
  177. c.UpdatedAt = time.Now()
  178. return s.repo.Save(c)
  179. }
  180. // GetRAGPromptTemplate 供 AIService 使用:返回 RAG 基础提示词模板(配置或默认),占位符 {{rag_context}}、{{user_message}}
  181. func (s *PromptConfigService) GetRAGPromptTemplate() (string, error) {
  182. return s.GetPromptOrDefault(models.PromptKeyRAG, defaultRAGPrompt)
  183. }
  184. // GetRAGPromptWithWebOptionalTemplate 供 AIService 使用:返回 RAG+联网可选提示词模板
  185. func (s *PromptConfigService) GetRAGPromptWithWebOptionalTemplate() (string, error) {
  186. return s.GetPromptOrDefault(models.PromptKeyRAGWithWebOptional, defaultRAGPromptWithWebOptional)
  187. }
  188. // GetNoKBPromptTemplate 供 AIService 使用:无知识库时的提示词,占位符 {{user_message}}
  189. func (s *PromptConfigService) GetNoKBPromptTemplate() (string, error) {
  190. return s.GetPromptOrDefault(models.PromptKeyNoKB, defaultNoKBPrompt)
  191. }
  192. // GetWebSearchResultPromptTemplate 供 AIService 使用:联网结果拼接提示词,占位符 {{web_context}}、{{user_message}}
  193. func (s *PromptConfigService) GetWebSearchResultPromptTemplate() (string, error) {
  194. return s.GetPromptOrDefault(models.PromptKeyWebSearchResult, defaultWebSearchResultPrompt)
  195. }
  196. // GetNoSourceReply 供 AIService 使用:无任何来源时直接返回给用户的一句话(无占位符)
  197. func (s *PromptConfigService) GetNoSourceReply() (string, error) {
  198. return s.GetPromptOrDefault(models.PromptKeyNoSourceReply, defaultNoSourceReply)
  199. }
  200. // GetAIFailReply 供 AIService 使用:AI 调用失败时返回给用户的一句话(无占位符)
  201. func (s *PromptConfigService) GetAIFailReply() (string, error) {
  202. return s.GetPromptOrDefault(models.PromptKeyAIFailReply, defaultAIFailReply)
  203. }