You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

role.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # encoding:utf-8
  2. import json
  3. import os
  4. import plugins
  5. from bridge.bridge import Bridge
  6. from bridge.context import ContextType
  7. from bridge.reply import Reply, ReplyType
  8. from common import const
  9. from common.log import logger
  10. from config import conf
  11. from plugins import *
  12. class RolePlay:
  13. def __init__(self, bot, sessionid, desc, wrapper=None):
  14. self.bot = bot
  15. self.sessionid = sessionid
  16. self.wrapper = wrapper or "%s" # 用于包装用户输入
  17. self.desc = desc
  18. self.bot.sessions.build_session(self.sessionid, system_prompt=self.desc)
  19. def reset(self):
  20. self.bot.sessions.clear_session(self.sessionid)
  21. def action(self, user_action):
  22. session = self.bot.sessions.build_session(self.sessionid)
  23. if session.system_prompt != self.desc: # 目前没有触发session过期事件,这里先简单判断,然后重置
  24. session.set_system_prompt(self.desc)
  25. prompt = self.wrapper % user_action
  26. return prompt
  27. @plugins.register(
  28. name="Role",
  29. desire_priority=0,
  30. namecn="角色扮演",
  31. desc="为你的Bot设置预设角色",
  32. version="1.0",
  33. author="lanvent",
  34. )
  35. class Role(Plugin):
  36. def __init__(self):
  37. super().__init__()
  38. curdir = os.path.dirname(__file__)
  39. config_path = os.path.join(curdir, "roles.json")
  40. try:
  41. with open(config_path, "r", encoding="utf-8") as f:
  42. config = json.load(f)
  43. self.tags = {tag: (desc, []) for tag, desc in config["tags"].items()}
  44. self.roles = {}
  45. for role in config["roles"]:
  46. self.roles[role["title"].lower()] = role
  47. for tag in role["tags"]:
  48. if tag not in self.tags:
  49. logger.warning(f"[Role] unknown tag {tag} ")
  50. self.tags[tag] = (tag, [])
  51. self.tags[tag][1].append(role)
  52. for tag in list(self.tags.keys()):
  53. if len(self.tags[tag][1]) == 0:
  54. logger.debug(f"[Role] no role found for tag {tag} ")
  55. del self.tags[tag]
  56. if len(self.roles) == 0:
  57. raise Exception("no role found")
  58. self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
  59. self.roleplays = {}
  60. logger.info("[Role] inited")
  61. except Exception as e:
  62. if isinstance(e, FileNotFoundError):
  63. logger.warn(
  64. f"[Role] init failed, {config_path} not found, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role ."
  65. )
  66. else:
  67. logger.warn(
  68. "[Role] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role ."
  69. )
  70. raise e
  71. def get_role(self, name, find_closest=True, min_sim=0.35):
  72. name = name.lower()
  73. found_role = None
  74. if name in self.roles:
  75. found_role = name
  76. elif find_closest:
  77. import difflib
  78. def str_simularity(a, b):
  79. return difflib.SequenceMatcher(None, a, b).ratio()
  80. max_sim = min_sim
  81. max_role = None
  82. for role in self.roles:
  83. sim = str_simularity(name, role)
  84. if sim >= max_sim:
  85. max_sim = sim
  86. max_role = role
  87. found_role = max_role
  88. return found_role
  89. def on_handle_context(self, e_context: EventContext):
  90. if e_context["context"].type != ContextType.TEXT:
  91. return
  92. bottype = Bridge().get_bot_type("chat")
  93. if bottype not in (const.CHATGPT, const.OPEN_AI):
  94. return
  95. bot = Bridge().get_bot("chat")
  96. content = e_context["context"].content[:]
  97. clist = e_context["context"].content.split(maxsplit=1)
  98. desckey = None
  99. customize = False
  100. sessionid = e_context["context"]["session_id"]
  101. trigger_prefix = conf().get("plugin_trigger_prefix", "$")
  102. if clist[0] == f"{trigger_prefix}停止扮演":
  103. if sessionid in self.roleplays:
  104. self.roleplays[sessionid].reset()
  105. del self.roleplays[sessionid]
  106. reply = Reply(ReplyType.INFO, "角色扮演结束!")
  107. e_context["reply"] = reply
  108. e_context.action = EventAction.BREAK_PASS
  109. return
  110. elif clist[0] == f"{trigger_prefix}角色":
  111. desckey = "descn"
  112. elif clist[0].lower() == f"{trigger_prefix}role":
  113. desckey = "description"
  114. elif clist[0] == f"{trigger_prefix}设定扮演":
  115. customize = True
  116. elif clist[0] == f"{trigger_prefix}角色类型":
  117. if len(clist) > 1:
  118. tag = clist[1].strip()
  119. help_text = "角色列表:\n"
  120. for key, value in self.tags.items():
  121. if value[0] == tag:
  122. tag = key
  123. break
  124. if tag == "所有":
  125. for role in self.roles.values():
  126. help_text += f"{role['title']}: {role['remark']}\n"
  127. elif tag in self.tags:
  128. for role in self.tags[tag][1]:
  129. help_text += f"{role['title']}: {role['remark']}\n"
  130. else:
  131. help_text = f"未知角色类型。\n"
  132. help_text += "目前的角色类型有: \n"
  133. help_text += (
  134. ",".join([self.tags[tag][0] for tag in self.tags]) + "\n"
  135. )
  136. else:
  137. help_text = f"请输入角色类型。\n"
  138. help_text += "目前的角色类型有: \n"
  139. help_text += ",".join([self.tags[tag][0] for tag in self.tags]) + "\n"
  140. reply = Reply(ReplyType.INFO, help_text)
  141. e_context["reply"] = reply
  142. e_context.action = EventAction.BREAK_PASS
  143. return
  144. elif sessionid not in self.roleplays:
  145. return
  146. logger.debug("[Role] on_handle_context. content: %s" % content)
  147. if desckey is not None:
  148. if len(clist) == 1 or (
  149. len(clist) > 1 and clist[1].lower() in ["help", "帮助"]
  150. ):
  151. reply = Reply(ReplyType.INFO, self.get_help_text(verbose=True))
  152. e_context["reply"] = reply
  153. e_context.action = EventAction.BREAK_PASS
  154. return
  155. role = self.get_role(clist[1])
  156. if role is None:
  157. reply = Reply(ReplyType.ERROR, "角色不存在")
  158. e_context["reply"] = reply
  159. e_context.action = EventAction.BREAK_PASS
  160. return
  161. else:
  162. self.roleplays[sessionid] = RolePlay(
  163. bot,
  164. sessionid,
  165. self.roles[role][desckey],
  166. self.roles[role].get("wrapper", "%s"),
  167. )
  168. reply = Reply(
  169. ReplyType.INFO, f"预设角色为 {role}:\n" + self.roles[role][desckey]
  170. )
  171. e_context["reply"] = reply
  172. e_context.action = EventAction.BREAK_PASS
  173. elif customize == True:
  174. self.roleplays[sessionid] = RolePlay(bot, sessionid, clist[1], "%s")
  175. reply = Reply(ReplyType.INFO, f"角色设定为:\n{clist[1]}")
  176. e_context["reply"] = reply
  177. e_context.action = EventAction.BREAK_PASS
  178. else:
  179. prompt = self.roleplays[sessionid].action(content)
  180. e_context["context"].type = ContextType.TEXT
  181. e_context["context"].content = prompt
  182. e_context.action = EventAction.BREAK
  183. def get_help_text(self, verbose=False, **kwargs):
  184. help_text = "让机器人扮演不同的角色。\n"
  185. if not verbose:
  186. return help_text
  187. trigger_prefix = conf().get("plugin_trigger_prefix", "$")
  188. help_text = (
  189. f"使用方法:\n{trigger_prefix}角色"
  190. + " 预设角色名: 设定角色为{预设角色名}。\n"
  191. + f"{trigger_prefix}role"
  192. + " 预设角色名: 同上,但使用英文设定。\n"
  193. )
  194. help_text += f"{trigger_prefix}设定扮演" + " 角色设定: 设定自定义角色人设为{角色设定}。\n"
  195. help_text += f"{trigger_prefix}停止扮演: 清除设定的角色。\n"
  196. help_text += (
  197. f"{trigger_prefix}角色类型" + " 角色类型: 查看某类{角色类型}的所有预设角色,为所有时输出所有预设角色。\n"
  198. )
  199. help_text += "\n目前的角色类型有: \n"
  200. help_text += ",".join([self.tags[tag][0] for tag in self.tags]) + "。\n"
  201. help_text += f"\n命令例子: \n{trigger_prefix}角色 写作助理\n"
  202. help_text += f"{trigger_prefix}角色类型 所有\n"
  203. help_text += f"{trigger_prefix}停止扮演\n"
  204. return help_text