Skip to content

Commit 5909339

Browse files
authored
Merge pull request zhayujie#524 from lanvent/dev
plugin: 添加`Role`插件,让机器人角色扮演。
2 parents 45165d8 + 8dde966 commit 5909339

File tree

5 files changed

+315
-6
lines changed

5 files changed

+315
-6
lines changed

bot/chatgpt/chat_gpt_bot.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ def __init__(self):
145145
sessions = dict()
146146
self.sessions = sessions
147147

148+
def build_session(self, session_id, system_prompt=None):
149+
session = self.sessions.get(session_id, [])
150+
if len(session) == 0:
151+
if system_prompt is None:
152+
system_prompt = conf().get("character_desc", "")
153+
system_item = {'role': 'system', 'content': system_prompt}
154+
session.append(system_item)
155+
self.sessions[session_id] = session
156+
return session
157+
148158
def build_session_query(self, query, session_id):
149159
'''
150160
build query with conversation history
@@ -158,12 +168,7 @@ def build_session_query(self, query, session_id):
158168
:param session_id: session id
159169
:return: query content with conversaction
160170
'''
161-
session = self.sessions.get(session_id, [])
162-
if len(session) == 0:
163-
system_prompt = conf().get("character_desc", "")
164-
system_item = {'role': 'system', 'content': system_prompt}
165-
session.append(system_item)
166-
self.sessions[session_id] = session
171+
session = self.build_session(session_id)
167172
user_item = {'role': 'user', 'content': query}
168173
session.append(user_item)
169174
return session

plugins/role/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
用于让Bot扮演指定角色的聊天插件,触发方法如下:
2+
- `$角色/$role help/帮助` - 打印目前支持的角色列表。
3+
- `$角色/$role <角色名>` - 让AI扮演该角色,角色名支持模糊匹配。
4+
- `$停止扮演` - 停止角色扮演。
5+
6+
添加自定义角色请在`roles/roles.json`中添加。
7+
(大部分prompt来自https://github.com/rockbenben/ChatGPT-Shortcut/blob/main/src/data/users.tsx)
8+
9+
以下为例子,
10+
- `title`是角色名。
11+
- `description`是使用`$role`触发的英语prompt。
12+
- `descn`是使用`$角色`触发的中文prompt。
13+
- `wrapper`用于包装你的消息,可以起到强调的作用。
14+
- `remark`简短的描述该角色,在打印帮助时显示。
15+
16+
```json
17+
{
18+
"title": "写作助理",
19+
"description": "As a writing improvement assistant, your task is to improve the spelling, grammar, clarity, concision, and overall readability of the text I provided, while breaking down long sentences, reducing repetition, and providing suggestions for improvement. Please provide only the corrected Chinese version of the text and avoid including explanations. Please treat every message I send later as text content.",
20+
"descn": "作为一名中文写作改进助理,你的任务是改进所提供文本的拼写、语法、清晰、简洁和整体可读性,同时分解长句,减少重复,并提供改进建议。请只提供文本的更正版本,避免包括解释。请把我之后的每一条消息都当作文本内容。",
21+
"wrapper": "内容是:\n\"%s\"",
22+
"remark": "最常使用的角色,用于优化文本的语法、清晰度和简洁度,提高可读性。"
23+
},
24+
```

plugins/role/__init__.py

Whitespace-only changes.

plugins/role/role.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# encoding:utf-8
2+
3+
import json
4+
import os
5+
from bridge.bridge import Bridge
6+
from bridge.context import ContextType
7+
from bridge.reply import Reply, ReplyType
8+
import plugins
9+
from plugins import *
10+
from common.log import logger
11+
12+
13+
class RolePlay():
14+
def __init__(self, bot, sessionid, desc, wrapper=None):
15+
self.bot = bot
16+
self.sessionid = sessionid
17+
bot.sessions.clear_session(sessionid)
18+
bot.sessions.build_session(sessionid, desc)
19+
self.wrapper = wrapper or "%s" # 用于包装用户输入
20+
21+
def reset(self):
22+
self.bot.sessions.clear_session(self.sessionid)
23+
24+
def action(self, user_action):
25+
prompt = self.wrapper % user_action
26+
return prompt
27+
28+
@plugins.register(name="Role", desc="为你的Bot设置预设角色", version="1.0", author="lanvent", desire_priority= 0)
29+
class Role(Plugin):
30+
def __init__(self):
31+
super().__init__()
32+
curdir = os.path.dirname(__file__)
33+
config_path = os.path.join(curdir, "roles.json")
34+
try:
35+
with open(config_path, "r", encoding="utf-8") as f:
36+
config = json.load(f)
37+
self.roles = {role["title"].lower(): role for role in config["roles"]}
38+
if len(self.roles) == 0:
39+
raise Exception("no role found")
40+
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
41+
self.roleplays = {}
42+
logger.info("[Role] inited")
43+
except FileNotFoundError:
44+
logger.error(f"[Role] init failed, {config_path} not found")
45+
except Exception as e:
46+
logger.error("[Role] init failed, exception: %s" % e)
47+
48+
def get_role(self, name, find_closest=True):
49+
name = name.lower()
50+
found_role = None
51+
if name in self.roles:
52+
found_role = name
53+
elif find_closest:
54+
import difflib
55+
56+
def str_simularity(a, b):
57+
return difflib.SequenceMatcher(None, a, b).ratio()
58+
max_sim = 0.0
59+
max_role = None
60+
for role in self.roles:
61+
sim = str_simularity(name, role)
62+
if sim >= max_sim:
63+
max_sim = sim
64+
max_role = role
65+
found_role = max_role
66+
return found_role
67+
68+
def on_handle_context(self, e_context: EventContext):
69+
70+
if e_context['context'].type != ContextType.TEXT:
71+
return
72+
bottype = Bridge().get_bot_type("chat")
73+
if bottype != "chatGPT":
74+
return
75+
bot = Bridge().get_bot("chat")
76+
content = e_context['context'].content[:]
77+
clist = e_context['context'].content.split(maxsplit=1)
78+
desckey = None
79+
sessionid = e_context['context']['session_id']
80+
if clist[0] == "$停止扮演":
81+
if sessionid in self.roleplays:
82+
self.roleplays[sessionid].reset()
83+
del self.roleplays[sessionid]
84+
reply = Reply(ReplyType.INFO, "角色扮演结束!")
85+
e_context['reply'] = reply
86+
e_context.action = EventAction.BREAK_PASS
87+
return
88+
elif clist[0] == "$角色":
89+
desckey = "descn"
90+
elif clist[0].lower() == "$role":
91+
desckey = "description"
92+
elif sessionid not in self.roleplays:
93+
return
94+
logger.debug("[Role] on_handle_context. content: %s" % content)
95+
if desckey is not None:
96+
if len(clist) == 1 or (len(clist) > 1 and clist[1].lower() in ["help", "帮助"]):
97+
reply = Reply(ReplyType.INFO, self.get_help_text())
98+
e_context['reply'] = reply
99+
e_context.action = EventAction.BREAK_PASS
100+
return
101+
role = self.get_role(clist[1])
102+
if role is None:
103+
reply = Reply(ReplyType.ERROR, "角色不存在")
104+
e_context['reply'] = reply
105+
e_context.action = EventAction.BREAK_PASS
106+
return
107+
else:
108+
self.roleplays[sessionid] = RolePlay(bot, sessionid, self.roles[role][desckey],self.roles[role].get("wrapper","%s"))
109+
reply = Reply(ReplyType.INFO, f"角色设定为 {role} :\n"+self.roles[role][desckey])
110+
e_context['reply'] = reply
111+
e_context.action = EventAction.BREAK_PASS
112+
else:
113+
prompt = self.roleplays[sessionid].action(content)
114+
e_context['context'].type = ContextType.TEXT
115+
e_context['context'].content = prompt
116+
e_context.action = EventAction.CONTINUE
117+
118+
def get_help_text(self):
119+
help_text = "输入\"$角色 (角色名)\"\"$role (角色名)\"为我设定角色吧,#reset 可以清除设定的角色。\n目前可用角色列表:\n"
120+
for role in self.roles:
121+
help_text += f"[{role}]: {self.roles[role]['remark']}\n"
122+
return help_text

0 commit comments

Comments
 (0)