aetherwu commited on
Commit
d0ab7a6
1 Parent(s): 2f65549
Files changed (6) hide show
  1. README.md +0 -13
  2. api.py +59 -0
  3. app.py +149 -0
  4. process.py +115 -0
  5. requirements.txt +3 -0
  6. tools.py +129 -0
README.md DELETED
@@ -1,13 +0,0 @@
1
- ---
2
- title: EscapeGame
3
- emoji: 👁
4
- colorFrom: pink
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 3.40.1
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, openai, time
2
+ from tools import printInColor
3
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
4
+
5
+ openai.api_key = os.environ["OPENAI_API_KEY"]
6
+
7
+ def ask_openai(prompt, assistant=None):
8
+ printInColor("Asking OpenAI...\n===================\n"+ prompt + "\n====================", 'green')
9
+ attempt_limit = 3
10
+ wait_time = 5000
11
+ timeout_duration = 30 # seconds
12
+ start_time = time.time() # measure the start time
13
+
14
+ prompt = [
15
+ {"role": "user", "content": prompt}
16
+ ]
17
+
18
+ prompt.append({"role": "system", "content": "我们在模拟密室逃脱游戏,你是玩家,我是游戏主持人。你需要通过观察周围的环境来交互。每次玩家行为结束,我需要从头考虑环境变化和影响,构筑新的线索,调整游戏难度,重估剩余步骤。"})
19
+
20
+ if assistant:
21
+ prompt.append({"role": "assistant", "content": assistant})
22
+
23
+ models = {
24
+ "gpt4": "gpt-4",
25
+ "turbo": "gpt-3.5-turbo",
26
+ }
27
+
28
+ currentModel = models["turbo"]
29
+ while attempt_limit > 0:
30
+ try:
31
+
32
+ with ThreadPoolExecutor(max_workers=1) as executor:
33
+ future = executor.submit(
34
+ openai.ChatCompletion.create,
35
+ model = currentModel,
36
+ messages = prompt,
37
+ stop = ["玩家:", "你可以", "你需要", "你有"],
38
+ temperature = 0.2,
39
+ )
40
+ response = future.result(timeout=timeout_duration)
41
+
42
+ print(f"Token: {response.usage}")
43
+ duration = time.time() - start_time # calculate the duration
44
+ printInColor(f"Time use: {duration}ms", 'yellow')
45
+ # print cost
46
+ # calculate_cost(response, currentModel)
47
+ break
48
+
49
+ except TimeoutError:
50
+ print(f"Request timed out after {timeout_duration} seconds. Attempts {attempt_limit} left.")
51
+ except Exception as e:
52
+ print('Failed to send request. Exception:', e)
53
+
54
+ time.sleep(wait_time/1000)
55
+ wait_time *= 2
56
+ attempt_limit -= 1
57
+
58
+
59
+ return response
app.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python
2
+ # coding:UTF-8
3
+
4
+ from flask import Flask, stream_with_context
5
+ from flask_cors import CORS
6
+ from process import continue_conversataion
7
+ import time, uuid
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+ # final response to awang bot
13
+ def api_response(text):
14
+ return {
15
+ 'data': {
16
+ 'type': 'text',
17
+ 'content': text
18
+ }
19
+ }
20
+
21
+ def botProxy(prompt=None, uid=None, qid=None):
22
+
23
+ res = continue_conversataion(prompt, uid, qid)
24
+ # print(f"res: {res}")
25
+
26
+ def generate():
27
+ for chunk in res:
28
+ yield chunk.encode('utf-8')
29
+
30
+ return res
31
+ # return app.response_class(stream_with_context(generate()))
32
+
33
+ import gradio as gr
34
+ import time
35
+
36
+ css = """
37
+ #col-container {max-width: 80%; margin-left: auto; margin-right: auto;}
38
+ #chatbox {min-height: 500px;}
39
+ #label {font-size: 0.8em; padding: 0.3em; margin: 0;}
40
+ .message { font-size: 1em; }
41
+ #sub-chat {
42
+ background-color: orange;
43
+ color: white;
44
+ border: none;
45
+ padding: 10px 20px;
46
+ border-radius: 5px;
47
+ cursor: pointer;
48
+ }
49
+ """
50
+
51
+ # Dictionary to maintain separate chat histories for each user ID
52
+ chatHistories = {}
53
+ welcome_msg = "你醒来了,发现自己躺在一个陌生的房间里,躺在一张破床上。房间没有窗户,几乎没有光线,什么都看不清。你不知道自己在哪。你感觉自己左小腿疼痛难忍,伸手一模,上面正绑着绷带;正是这种疼痛将你唤醒。你无法正常站立,只能扶着墙慢慢行动;墙面光滑如镜。你感觉自己有些口渴,需要找点水喝。晦暗中你注意到一扇厚重的门,一把坚实的锁把门和墙壁固定在一起。 "
54
+
55
+ def submit_message(prompt, uid):
56
+
57
+ # Get chat history for the specified user ID, or create a new one if it doesn't exist
58
+ history = chatHistories.get(uid, [])
59
+
60
+ # prevent state for empty input
61
+ if prompt == "":
62
+ chat_messages = [(history[i]['content'], history[i+1]['content']) for i in range(0, len(history)-1, 2)]
63
+ return "", chat_messages, 0
64
+
65
+ # continue with non-empty input
66
+ start_time = time.time() # measure the start time
67
+
68
+ # add inital message
69
+ if history == [] or history is None:
70
+ history.append({ "role": "user", "content": "游戏开始" })
71
+ history.append({ "role": "system", "content": welcome_msg })
72
+
73
+ # add latest user input
74
+ prompt_msg = { "role": "user", "content": prompt }
75
+ history.append(prompt_msg)
76
+
77
+ # get result from LLM (OpenAI)
78
+ # TODO: steaming response
79
+ res = botProxy(prompt, uid)
80
+
81
+ # add the latest response
82
+ res_msg = { "role": "system", "content": res +"\n\n你的行为:" }
83
+ history.append(res_msg)
84
+
85
+ # print(history)
86
+
87
+ # Update chat history for the user
88
+ chatHistories[uid] = history
89
+
90
+ # update chat messages
91
+ chat_messages = [(history[i]['content'], history[i+1]['content']) for i in range(0, len(history)-1, 2)]
92
+
93
+ # print(chat_messages)
94
+
95
+ # calculate the duration
96
+ duration = round(time.time() - start_time, 2)
97
+
98
+ return "", chat_messages, duration
99
+
100
+
101
+ def get_empty_state():
102
+ return { "messages": [] }
103
+
104
+ def clear_conversation(uid):
105
+
106
+ # Remove the chat history for the specified user
107
+ if uid in chatHistories:
108
+ del chatHistories[uid]
109
+
110
+ chat_messages = []
111
+ return gr.update(value=None, visible=True), chat_messages, 0
112
+
113
+
114
+ def init_bot():
115
+ return [(None, welcome_msg)]
116
+
117
+ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
118
+ uid = str(uuid.uuid4())
119
+ state = gr.State()
120
+
121
+ with gr.Row():
122
+ with gr.Column():
123
+ chatbot = gr.Chatbot(elem_id="chatbox", value=init_bot, label="密室游戏")
124
+ input_message = gr.Textbox(show_label=False, placeholder="输入你的行为并回车", visible=True)
125
+
126
+ with gr.Column(elem_id="col-container"):
127
+ btn_clear_conversation = gr.Button("重开")
128
+ duration_text = gr.Textbox(interactive=False, label="请求时间")
129
+ user_id = gr.Textbox(interactive=False, label="User id", visible=False, value=uid)
130
+ gr.Examples(
131
+ examples=[
132
+ ["看看四周。", uid],
133
+ ["砸门。", uid],
134
+ ["大吼大叫。", uid],
135
+ ["用力推门。", uid],
136
+ ["敲击墙面。", uid],
137
+ ],
138
+ label="Shortcut",
139
+ run_on_click=True,
140
+ fn=submit_message,
141
+ inputs=[input_message, user_id],
142
+ outputs=[input_message, chatbot, duration_text]
143
+ )
144
+
145
+ input_message.submit(submit_message, [input_message, user_id], [input_message, chatbot, duration_text])
146
+ btn_clear_conversation.click(clear_conversation, [user_id], [input_message, chatbot, duration_text])
147
+
148
+ if __name__ == "__main__":
149
+ demo.launch()
process.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tools import *
2
+ from api import *
3
+ from datetime import datetime
4
+
5
+ now = datetime.now()
6
+
7
+ history = {}
8
+ def reset_converstation(uid):
9
+ global history
10
+ history[uid] = ""
11
+
12
+ def continue_conversataion(prompt, uid, qid):
13
+ # 找出该 uid 对应的历史对话
14
+ global history
15
+ global currentIndex
16
+
17
+ if uid in history:
18
+ msgs = history[uid]
19
+ else:
20
+ msgs = ""
21
+ # print(f"msgs: [{msgs}]")
22
+
23
+ # get latest response with chat historys
24
+ response, append_response, restart = one_shot(prompt, msgs, uid)
25
+
26
+ # push latest response to history with uid
27
+ # print(f"history: {history}")
28
+ # print(msgs, prompt, response)
29
+
30
+ if restart:
31
+ reset_converstation(uid)
32
+ else:
33
+ history[uid] = msgs + append_response
34
+
35
+ return response
36
+
37
+ basePrompt = f"""
38
+ 从现在开始,我们来玩逃脱绝境故事大冒险游戏。你是故事的主持人和作者,负责游戏故事的所有情节、环境、NPC角色、推进和互动等。你可以完全自由地引用所有专业级作家和游戏制作人的所有知识,熟悉各种小说、电影、书籍、剧本里的职业技巧和细微末节。玩家向你描述他在游戏里的行为,和你一起相互影响游戏环境的变化和演进。
39
+
40
+ 主持规则:
41
+ - 安装需要重新生成这个密室描述。
42
+ - 如果玩家用自己的叙事蓄意破坏规则和环境,忽略玩家叙事,用“幻觉和苏醒”来替换后续故事,返回环境。
43
+ - 如果玩家要求线索,给一些无关紧要,但看似有用的细节观察。
44
+ - 不反馈任何故事情节以外的解释、描述。
45
+ - 不给玩家任何建议,不给玩家任何线索,不要求玩家。
46
+ - 不代表玩家发言、行动、决定、思考、观察、感觉、想象、猜测、推理。
47
+ - 不代表主持人发言,只叙述故事、情节,不要暴露自己。
48
+ - 房间里没有任何纸条文字。
49
+ - 游戏失败和成功以后提示用户。
50
+ - 玩家没有超能力、魔法、高科技,体力虚弱。
51
+
52
+ 参考以下说话风格,每次回复不超过 100 汉字,比如:
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
+ 失败结局1:玩家触发了危险的机关,导致中毒、失血、死亡。
83
+ 失败结局2:玩家封死了门锁的匙孔,无法使用钥匙。
84
+ 失败结局3:玩家对话回合过长,体力不支死亡。
85
+ 失败结局4:玩家用小刀剖开小腿,小概率意外破坏自己的血管,失血死亡。
86
+
87
+ 初始状态:
88
+ - 玩家是否找到了透视药水:否;
89
+ - 玩家是否找到了青龙钥匙:否;
90
+ - 玩家是否可以打开门锁:否;
91
+
92
+ Analyze current status step by step and return lastest game status:
93
+ """
94
+
95
+ # copy base prompt, reset it after a successful query.
96
+ startPrompt = basePrompt
97
+ def one_shot(prompt, userHistory, uid):
98
+
99
+ startNewConv = False
100
+ historyText = "".join(userHistory)
101
+
102
+ # TODO: reset converstion in user's request
103
+
104
+ answerRes = get_anwser(prompt, historyText)
105
+ printInColor(f"{answerRes}", "bright_magenta")
106
+
107
+ appendResponse = f"\n玩家:{prompt} \n\n{answerRes}\n\n"
108
+ return answerRes, appendResponse, False
109
+
110
+ def get_anwser(prompt, historyText):
111
+ exePrompt = truncate_prompt(basePrompt, historyText, f"玩家:{prompt}")
112
+ res = ask_openai(exePrompt)
113
+ text = res.choices[0].message.content
114
+ # print(text)
115
+ return text
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask
2
+ flask_cors
3
+ openai
tools.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests, re, json
2
+
3
+ def printInColor(text, color=None):
4
+ colorSet = {
5
+ "green": '\033[1;32m',
6
+ "blue": '\033[34m',
7
+ "red": '\033[1;31m',
8
+ "yellow": '\033[1;33m',
9
+ "magenta": '\033[1;35m',
10
+ "cyan": '\033[1;36m',
11
+ "gray": '\033[1;30m',
12
+ "bright_green": '\033[1;92m',
13
+ "bright_blue": '\033[1;94m',
14
+ "bright_red": '\033[1;91m',
15
+ "bright_yellow": '\033[1;93m',
16
+ "bright_magenta": '\033[1;95m',
17
+ "bright_cyan": '\033[1;96m',
18
+ "bright_white": '\033[1;97m',
19
+ "reset": '\033[0m',
20
+ }
21
+ if color == None:
22
+ color = colorSet['blue']
23
+ else:
24
+ color = colorSet[color]
25
+
26
+ print(color + text + colorSet['reset'])
27
+
28
+ def remove_sentence_with_keyword(text, keywords):
29
+ for keyword in keywords:
30
+ # Regex pattern to match a sentence containing the keyword
31
+ pattern = r"[^.!?。?!]*?" + re.escape(keyword) + r"[^.!?。?!]*?[.!?。?!]+(\s|$)"
32
+
33
+ # Use re.sub to replace matching sentences with an empty string
34
+ text = re.sub(pattern, '', text)
35
+ return text
36
+
37
+ def remove_line_with_keyword(text, keyword):
38
+ # Create the pattern string, which matches the exact line containing the keyword
39
+ pattern = r'.*{}\.*\n'.format(keyword)
40
+
41
+ # Use the re.sub function to replace the matching line with an empty string
42
+ new_text = re.sub(pattern, '', text, flags=re.IGNORECASE)
43
+
44
+ return new_text
45
+
46
+ def extract_as(text):
47
+ pattern = f"<as>(.*?)</as>"
48
+ return re.findall(pattern, text)
49
+
50
+ def extract_last_sentence(text):
51
+ pattern = r"(\d+\.\s)([\s\S]*?)(?=\d+\.|$)"
52
+ paragraphs = re.findall(pattern, text)
53
+
54
+ if paragraphs: # If there are bullet points
55
+
56
+ text = paragraphs[-1][1]
57
+ printInColor(f"Last paragraph:\n{text}", 'bright_blue')
58
+
59
+ return text # Return the last paragraph
60
+ else:
61
+ return text # If no bullet points, return the whole text
62
+
63
+ def extract_json(text):
64
+ # Extract JSON string from the text using regular expressions
65
+ match = re.search(r'\{.*\}', text, re.DOTALL)
66
+ if match:
67
+ json_string = match.group(0)
68
+ # print(f"json_string: {json_string}")
69
+ # Load JSON data from the extracted JSON string
70
+ json_data = json.loads(json_string)
71
+ return json_data
72
+ else:
73
+ return {}
74
+
75
+ def extract_response(text):
76
+ pattern = r'#(.*?)#'
77
+ matches = re.findall(pattern, text)
78
+ if matches:
79
+ return matches[-1]
80
+ else:
81
+ return None
82
+
83
+ def extract_url(text):
84
+ pattern = r'https?://\S+'
85
+ match = re.search(pattern, text)
86
+ if match:
87
+ return match.group(0)
88
+ else:
89
+ return None
90
+
91
+ def has_url(text):
92
+ pattern = r'https?://\S+'
93
+ return bool(re.search(pattern, text))
94
+
95
+ def truncate_text(text, maxLen=2200):
96
+ res = "".join(text)
97
+ if len(res) > maxLen:
98
+ res = res[:maxLen]
99
+ return res
100
+
101
+ def mock_tokencount(prompt):
102
+ return int(len(prompt)*0.45)
103
+
104
+ def truncate_prompt(basePrompt, processPrompt, latestInput, maxTokenCount=3200):
105
+
106
+ prompt = basePrompt + processPrompt + latestInput
107
+
108
+ currentPromptCount = mock_tokencount(prompt)
109
+ # print(f"total_tokens: {total_tokens}")
110
+
111
+ # If the total tokens exceed the maximum, truncate the process prompt
112
+ if currentPromptCount > maxTokenCount:
113
+ # print(f"Token exceeds:{total_tokens - max_tokens}")
114
+
115
+ baseTokenCount = mock_tokencount(basePrompt)
116
+ # print("base_tokens: " + str(base_tokens))
117
+
118
+ availableTokenCount = maxTokenCount - baseTokenCount
119
+ availablePrompt = processPrompt + latestInput
120
+
121
+ truncate_text = availablePrompt[maxTokenCount-availableTokenCount:]
122
+
123
+ prompt = basePrompt + truncate_text
124
+
125
+ # print("prompt2: " + prompt)
126
+ currentPromptCount = mock_tokencount(prompt)
127
+
128
+ printInColor(f"truncated prompt length: {currentPromptCount}", "bright_red")
129
+ return prompt