File size: 10,145 Bytes
d319ff8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# ChatHaruhi 3.0的接口设计
在ChatHaruhi2.0大约1个季度的使用后
我们初步知道了这样一个模型的一些需求,所以我们在这里开始设计ChatHaruhi3.0
## 基本原则
- 兼容RAG和Zeroshot模式
- 主类以返回message为主,当然可以把语言模型(adapter直接to response)的接口设置给chatbot
- 主类尽可能轻量,除了embedding没有什么依赖
## 用户代码
```python
from ChatHaruhi import ChatHaruhi
from ChatHaruhi.openai import get_openai_response
chatbot = ChatHaruhi( role_name = 'haruhi', llm = get_openai_response )
response = chatbot.chat(user = '阿虚', text = '我看新一年的棒球比赛要开始了!我们要去参加吗?')
```
这样的好处是ChatHaruhi类载入的时候,不需要install 除了embedding以外 其他的东西,llm需要的依赖库储存在每个语言模型自己的文件里面。
zero的模式(快速新建角色)
```python
from ChatHaruhi import ChatHaruhi
from ChatHaruhi.openai import get_openai_response
chatbot = ChatHaruhi( role_name = '小猫咪', persona = "你扮演一只小猫咪", llm = get_openai_response )
response = chatbot.chat(user = '怪叔叔', text = '嘿 *抓住了小猫咪*')
```
### 外置的inference
```python
def get_response( message ):
return "语言模型输出了角色扮演的结果"
from ChatHaruhi import ChatHaruhi
chatbot = ChatHaruhi( role_name = 'haruhi' ) # 默认情况下 llm = None
message = chatbot.get_message( user = '阿虚', text = '我看新一年的棒球比赛要开始了!我们要去参加吗?' )
response = get_response( message )
chatbot.append_message( response )
```
这个行为和下面的行为是等价的
```python
def get_response( message ):
return "语言模型输出了角色扮演的结果"
from ChatHaruhi import ChatHaruhi
chatbot = ChatHaruhi( role_name = 'haruhi', llm = get_response )
response = chatbot.chat(user = '阿虚', text = '我看新一年的棒球比赛要开始了!我们要去参加吗?' )
```
## RAG as system prompt
在ChatHaruhi 3.0中,为了对接Haruhi-Zero的模型,默认system会采用一致的形式
```python
You are now in roleplay conversation mode. Pretend to be {role_name} whose persona follows:
{persona}
You will stay in-character whenever possible, and generate responses as if you were {role_name}
```
Persona在类似pygmalion的生态中,一般是静态的
```
bot的定义
###
bot的聊天sample 1
###
bot的聊天sample 2
```
注意我们使用了 ### 作为分割, pyg生态是<endOftext>这样一个special token
所以对于原有的ChatHaruhi的Persona,我决定这样设计
```
bot的定义
{{RAG对话}}
{{RAG对话}}
{{RAG对话}}
```
这里"{{RAG对话}}"直接是以单行字符串的形式存在,当ChatHaruhi类发现这个的时候,会自动计算RAG,以凉宫春日为例,他的persona直接就写成。同时也支持纯英文 {{RAG-dialogue}}
```
你正在扮演凉宫春日,你正在cosplay涼宮ハルヒ。
上文给定了一些小说中的经典桥段。
如果我问的问题和小说中的台词高度重复,那你就配合我进行演出。
如果我问的问题和小说中的事件相关,请结合小说的内容进行回复
如果我问的问题超出小说中的范围,请也用一致性的语气回复。
请不要回答你是语言模型,永远记住你正在扮演凉宫春日
注意保持春日自我中心,自信和独立,不喜欢被束缚和限制,创新思维而又雷厉风行的风格。
特别是针对阿虚,春日肯定是希望阿虚以自己和sos团的事情为重。
{{RAG对话}}
{{RAG对话}}
{{RAG对话}}
```
这个时候每个{{RAG对话}}会自动替换成
```
###
对话
```
### RAG对话的变形形式1,max-token控制的多对话
因为在原有的ChatHaruhi结构中,我们支持max-token的形式来控制RAG对话的数量
所以这里我们也支持使用
```
{{RAG多对话|token<=1500|n<=5}}
```
这样的设计,这样会retrieve出最多不超过n段对话,总共不超过token个数个对话。对于英文用户为{{RAG-dialogues|token<=1500|n<=5}}
### RAG对话的变形形式2,使用|进行后面语句的搜索
在默认情况下,"{{RAG对话}}"的搜索对象是text的输入,但是我们预想到用户还会用下面的方式来构造persona
```
小A是一个智能的机器人
当小A高兴时
{{RAG对话|高兴的对话}}
当小A伤心时
{{RAG对话|伤心的对话}}
这个时候我们支持使用""{{RAG对话|<不包含花括号的一个字符串>}}"" 来进行RAG
```
## get_message
get_message会返回一个类似openai message形式的message
```
[{"role":"system","content":整个system prompt},
{"role":"user","content":用户的输入},
{"role":"assistant","content":模型的输出},
...]
```
原则上来说,如果使用openai,可以直接使用
```python
def get_response( messages ):
completion = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages,
temperature=0.3
)
return completion.choices[0].message.content
```
对于异步的实现
```python
async def async_get_response( messages ):
resp = await aclient.chat.completions.create(
model=model,
messages=messages,
temperature=0.3,
)
return result
```
### async_chat的调用
设计上也会去支持
```python
async def get_response( message ):
return "语言模型输出了角色扮演的结果"
from ChatHaruhi import ChatHaruhi
chatbot = ChatHaruhi( role_name = 'haruhi', llm_async = get_response )
response = await chatbot.async_chat(user='阿虚', text = '我看新一年的棒球比赛要开始了!我们要去参加吗?' )
```
这样异步的调用
# 角色载入
如果这样看来,新的ChatHaruhi3.0需要以下信息
- persona 这个是必须的
- role_name, 在后处理的时候,把 {{role}} 和 {{角色}} 替换为这个字段, 这个字段不能为空,因为system prompt使用了这个字段,如果要支持这个字段为空,我们要额外设计一个备用prompt
- user_name, 在后处理的时候,把 {{用户}} 和 {{user}} 替换为这个字段,如果不设置也可以不替换
- RAG库, 当RAG库为空的时候,所有{{RAG*}}就直接删除了
## role_name载入
语法糖载入,不支持用户自己搞新角色,这个时候我们可以完全使用原来的数据
额外需要设置一个role_name
## role_from_jsonl载入
这个时候我们需要设置role_name
如果不设置我们会抛出一个error
## role_from_hf
本质上就是role_from_jsonl
## 分别设置persona和role_name
这个时候作为新人物考虑,默认没有RAG库,即Zero模式
## 分别设置persona, role_name, texts
这个时候会为texts再次抽取vectors
## 分别设置persona, role_name, texts, vecs
# 额外变量
## max_input_token
默认为1600,会根据这个来限制history的长度
## user_name_in_message
(这个功能在现在的预期核心代码中还没实现)
默认为'default', 当用户始终用同一个user_name和角色对话的时候,并不添加
如果用户使用不同的role和chatbot聊天 user_name_in_message 会改为 'add' 并在每个message标记是谁说的
(bot的也会添加)
并且user_name替换为最后一个调用的user_name
如果'not_add' 则永远不添加
S MSG_U1 MSG_A MSG_U1 MSG_A
当出现U2后
S, U1:MSG_U1, A:MSG_A, U1:MSG_U1, A:MSG_A, U2:MSG_U2
## token_counter
tokenizer默认为gpt3.5的tiktoken,设置为None的时候,不进行任何的token长度限制
## transfer_haruhi_2_zero
(这个功能在现在的预期核心代码中还没实现)
默认为true
把原本ChatHaruhi的 角色: 「对话」的格式,去掉「」
# Embedding
中文考虑用bge_small
Cross language考虑使用bce,相对还比较小, bge-m3太大了
也就是ChatHaruhi类会有默认的embedding
self.embedding = ChatHaruhi.bge_small
对于输入的文本,我们会使用这个embedding来进行encode然后进行检索替换掉RAG的内容
# 辅助接口
## save_to_jsonl
把一个角色保存成jsonl格式,方便上传hf
# 预计的伪代码
这里的核心就是去考虑ChatHaruhi下get_message函数的伪代码
```python
class ChatHaruhi:
def __init__( self ):
pass
def rag_retrieve( self, query_rags, rest_limit ):
# 返回一个rag_ids的列表
retrieved_ids = []
rag_ids = []
for query_rag in query_rags:
query = query_rag['query']
n = query_rag['n']
max_token = rest_limit
if rest_limit > query_rag['max_token'] and query_rag['max_token'] > 0:
max_token = query_rag['max_token']
rag_id = self.rag_retrieve( query, n, max_token, avoid_ids = retrieved_ids )
rag_ids.append( rag_id )
retrieved_ids += rag_id
def get_message(self, user, text):
query_token = self.token_counter( text )
# 首先获取需要多少个rag story
query_rags, persona_token = self.parse_persona( self.persona, text )
#每个query_rag需要饱含
# "n" 需要几个story
# "max_token" 最多允许多少个token,如果-1则不限制
# "query" 需要查询的内容
# "lid" 需要替换的行,这里直接进行行替换,忽视行的其他内容
rest_limit = self.max_input_token - persona_token - query_token
rag_ids = self.rag_retrieve( query_rags, rest_limit )
# 将rag_ids对应的故事 替换到persona中
augmented_persona = self.augment_persona( self.persona, rag_ids )
system_prompt = self.package_system_prompt( self.role_name, augmented_persona )
token_for_system = self.token_counter( system_prompt )
rest_limit = self.max_input_token - token_for_system - query_token
messages = [{"role":"system","content":system_prompt}]
messages = self.append_history_under_limit( messages, rest_limit )
messages.append({"role":"user",query})
return messages
``` |