File size: 10,145 Bytes
aef3deb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
```