.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # cache files
2
+ __pycache__
3
+ .DS_store
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Play Semantle with GPT (Japanese)
3
- emoji: 🤓💬
4
  colorFrom: pink
5
  colorTo: purple
6
  sdk: gradio
 
1
  ---
2
  title: Play Semantle with GPT (Japanese)
3
+ emoji: 🍡💬
4
  colorFrom: pink
5
  colorTo: purple
6
  sdk: gradio
__pycache__/app.cpython-310.pyc DELETED
Binary file (5.95 kB)
 
__pycache__/app_test.cpython-310.pyc DELETED
Binary file (3.66 kB)
 
app.py CHANGED
@@ -5,75 +5,87 @@ import pandas as pd
5
  import gradio as gr
6
  import openai
7
 
8
- from src.semantle import get_guess, get_secret
9
  from src.functions import get_functions
10
- from src.utils import add_guess
11
 
12
  GPT_MODEL = "gpt-3.5-turbo"
13
- TITLE = "やりとりSemantle"
14
 
15
- system_content = task_background+task_description
16
- system_message = [{"role": "system", "content": system_content}]
17
 
18
- def create_chat(user_input, chat_history, api_key):
19
- openai.api_key = api_key
20
- chat_messages = [{"role": "user", "content": user_input}]
21
- response = openai.ChatCompletion.create(
22
- model=GPT_MODEL,
23
- messages=system_message+chat_messages,
24
- functions=get_functions()
25
- )
26
- response_message = response.choices[0].message
27
 
28
- # Step 2: check if CPT wanted to call a function
29
- if response_message.get("function_call"):
30
  # Step 3: call the function
31
  # Note: the JSON response may not always be valid; be sure to handle errors
32
  available_functions = {
33
- "evaluate_score": get_guess,
34
- "get_data_for_hint": get_play_data,
 
 
35
  }
36
- function_name = response_message["function_call"]["name"]
37
  function_to_call = available_functions[function_name]
38
- function_args = json.loads(response_message["function_call"]["arguments"])
39
  function_response = function_to_call(
40
- word=function_args.get("word"),
41
- puzzle_num=puzzle_num
42
  )
43
- guess_result = update_guess(function_response, guessed, guesses)
44
- print(guess_result)
 
45
  # Step 4: send the info on the function call and function response to GPT
46
- chat_messages.append(response_message.to_dict()) # extend conversation with assistant's reply
47
  chat_messages.append(
48
  {"role": "function",
49
  "name": function_name,
50
- "content": guess_result}
51
  ) # extend conversation with function response
52
- second_response = openai.ChatCompletion.create(
53
  model=GPT_MODEL,
54
- messages=system_message+chat_history+chat_messages,
 
 
55
  ) # get a new response from GPT where it can se the function response
56
- chat_messages.append(second_response["choices"][0]["message"].to_dict())
57
- chat_history = chat_history[-8:] + chat_messages
58
- return chat_messages[-1]
59
-
60
- chat_messages.append(response_message.to_dict())
61
- chat_history = chat_history[-8:] + chat_messages
62
- return chat_messages[-1]
63
 
64
- with gr.Blocks() as demo:
 
 
 
 
 
 
 
 
 
 
 
 
65
 
 
 
 
 
 
 
 
 
 
66
  with gr.Row():
67
  gr.Markdown(
68
  """
69
- # やりとりSemantle
70
- [semantle日本語版](https://semantoru.com/)をchatbotと楽しめるspaceです。
71
  ## ゲームのやり方
72
  - 正解は一つの単語で、これを答えるとゲームの勝利になります。
73
  - 推測した単語が正解じゃない場合、類似度スコアと順位が表示されます。それは正解を推測する大事なヒントになります。
74
- ## chatbotの仕事
75
  - 単語のスコアとランク以外に他のヒントがもらえます。
76
  - ゲームに関して困っている時、何か質問してみてください。
 
 
77
  """
78
  )
79
 
@@ -83,10 +95,11 @@ with gr.Blocks() as demo:
83
  idx = gr.State(value=0)
84
  guessed = gr.State(value=set())
85
  guesses = gr.State(value=list())
86
- cur_guess = gr.State()
 
87
  guesses_table = gr.DataFrame(
88
- value=pd.DataFrame(columns=["#", "答え", "スコア", "ランク"]),
89
- headers=["#", "答え", "score", "score"],
90
  datatype=["number", "str", "number", "str"],
91
  elem_id="guesses-table",
92
  interactive=False
@@ -102,27 +115,42 @@ with gr.Blocks() as demo:
102
 
103
  def unfreeze():
104
  return msg.update(interactive=True, placeholder="正解と思う言葉を答えてください。")
105
- def greet():
106
- return "", [("[START]", "ゲームを始まります!好きな言葉をひとつだけいってみてください。")]
 
 
 
 
 
 
 
 
 
107
 
108
- def respond(key, user_input):
109
- reply = create_chat(key, user_input)
110
- chatbot.append((user_input, reply["content"]))
 
 
 
 
 
111
  time.sleep(2)
112
- return "", chatbot
113
 
114
  def update_guesses(cur, i, guessed_words, guesses_df):
115
- if cur[0] not in guessed_words:
116
- guessed_words.add(cur[0])
117
- guesses_df.loc[i] = [i+1] + cur
 
 
118
  i += 1
119
- guesses_df = guesses_df.sort_values(by=["score"], ascending=False)
120
  return i, guessed_words, guesses_df
121
 
122
- api_key.change(unfreeze, [], [msg]).then(greet, [], [msg, chatbot])
123
- msg.submit(respond, [api_key, msg, chatbot, cur_guess], [msg, chatbot, cur_guess]).then(
124
- update_guesses, [cur_guess, idx, guessed, guesses_table], [idx, guessed, guesses_table]
125
- )
126
 
127
  gr.Examples(
128
  [
@@ -137,4 +165,5 @@ with gr.Blocks() as demo:
137
  )
138
 
139
  if __name__ == "__main__":
140
- demo.queue(concurrency_count=20).launch()
 
 
5
  import gradio as gr
6
  import openai
7
 
8
+ from src.semantle import get_guess, get_secret, get_puzzle_num
9
  from src.functions import get_functions
 
10
 
11
  GPT_MODEL = "gpt-3.5-turbo"
12
+ TITLE = "やりとりxイミトル"
13
 
14
+ with open("data/rule.md", "r", encoding="utf-8") as f:
15
+ RULEBOOK = "\n".join(f.readlines())
16
 
17
+ def get_rulebook():
18
+ return RULEBOOK
 
 
 
 
 
 
 
19
 
20
+ def _execute_function(function_call, chat_messages, guess_result):
 
21
  # Step 3: call the function
22
  # Note: the JSON response may not always be valid; be sure to handle errors
23
  available_functions = {
24
+ "guess_word": get_guess,
25
+ "lookup_answer": get_secret,
26
+ "retrieve_puzzle_num": get_puzzle_num,
27
+ "read_rule": get_rulebook,
28
  }
29
+ function_name = function_call["name"]
30
  function_to_call = available_functions[function_name]
31
+ function_args = json.loads(function_call["arguments"])
32
  function_response = function_to_call(
33
+ **function_args
 
34
  )
35
+ if function_call["name"] == "guess_word":
36
+ guess_result = function_response
37
+ print(function_response)
38
  # Step 4: send the info on the function call and function response to GPT
 
39
  chat_messages.append(
40
  {"role": "function",
41
  "name": function_name,
42
+ "content": f"{function_response}"}
43
  ) # extend conversation with function response
44
+ next_response = openai.ChatCompletion.create(
45
  model=GPT_MODEL,
46
+ messages=chat_messages,
47
+ functions=get_functions(),
48
+ temperature=0
49
  ) # get a new response from GPT where it can se the function response
50
+ chat_messages.append(next_response.choices[0].message.to_dict())
51
+ return next_response.choices[0].message.to_dict(), chat_messages, guess_result
 
 
 
 
 
52
 
53
+ def create_chat(system_input, user_input, guess_result=dict()):
54
+ chat_messages = []
55
+ for s in system_input:
56
+ chat_messages.append({"role": "system", "content": s})
57
+ chat_messages.append({"role": "user", "content": user_input})
58
+ response = openai.ChatCompletion.create(
59
+ model=GPT_MODEL,
60
+ messages=chat_messages,
61
+ functions=get_functions(),
62
+ temperature=0
63
+ )
64
+ response_message = response.choices[0].message.to_dict()
65
+ chat_messages.append(response_message) # extend conversation with assistant's reply
66
 
67
+ # Step 2: check if CPT wanted to call a function
68
+ while response_message.get("function_call"):
69
+ # Step 3: call the function
70
+ # Note: the JSON response may not always be valid; be sure to handle errors
71
+ response_message, chat_messages, guess_result = _execute_function(response_message["function_call"], chat_messages, guess_result)
72
+ print(chat_messages)
73
+ return response_message, chat_messages, guess_result
74
+
75
+ with gr.Blocks() as demo:
76
  with gr.Row():
77
  gr.Markdown(
78
  """
79
+ # やりとりxイミトル
80
+ 「イミトル」は[semantle日本語版](https://semantoru.com/)の名前で、こちらはイミトルをassistantと楽しめるspaceです。
81
  ## ゲームのやり方
82
  - 正解は一つの単語で、これを答えるとゲームの勝利になります。
83
  - 推測した単語が正解じゃない場合、類似度スコアと順位が表示されます。それは正解を推測する大事なヒントになります。
84
+ ## assistantの仕事
85
  - 単語のスコアとランク以外に他のヒントがもらえます。
86
  - ゲームに関して困っている時、何か質問してみてください。
87
+ ## ご了承のお願い
88
+ - ゲームをするため、openaiのapiが必要です。答えによって少々tokenを使うようになります。
89
  """
90
  )
91
 
 
95
  idx = gr.State(value=0)
96
  guessed = gr.State(value=set())
97
  guesses = gr.State(value=list())
98
+ cur_guess = gr.JSON(visible=False)
99
+ history = gr.State(list())
100
  guesses_table = gr.DataFrame(
101
+ value=pd.DataFrame(columns=["i", "guess", "sim", "rank"]),
102
+ headers=["i", "guess", "score", "rank"],
103
  datatype=["number", "str", "number", "str"],
104
  elem_id="guesses-table",
105
  interactive=False
 
115
 
116
  def unfreeze():
117
  return msg.update(interactive=True, placeholder="正解と思う言葉を答えてください。")
118
+ def reset_history():
119
+ return list()
120
+ def greet(key, gradio_messages):
121
+ openai.api_key = key
122
+ system_input = [get_rulebook(), "あなたは「イミトル」というゲームの進行役です。ユーザーが答えをするところです。よろしくお願いします。"]
123
+ user_input = ""
124
+ reply, _, _ = create_chat(system_input, user_input, guess_result=dict())
125
+ gradio_messages.append(("", reply["content"]))
126
+ # gradio_messages.append(("", f"今日のゲームのパズル番号は{get_puzzle_num()}です。それでは、始めましょう!言葉を当ててみてください。"))
127
+ time.sleep(2)
128
+ return gradio_messages
129
 
130
+ def respond(user_input, gradio_messages, guess_result=dict()):
131
+ system_input = [get_rulebook(), """あなたは「イミトル」というゲームの進行役です。
132
+ 普段、ユーザーは答えをしますが、たまにゲームに関するヒントをリクエストをします。短くても丁寧に答えてください。
133
+ ヒントを教える場合、正解を必ず隠してください。絶対、正解を言わないでください。"""]
134
+
135
+ _user_input = "答え:" + user_input
136
+ reply, messages, guess_result = create_chat(system_input, _user_input, guess_result=dict())
137
+ gradio_messages.append((user_input, reply["content"]))
138
  time.sleep(2)
139
+ return gradio_messages, guess_result
140
 
141
  def update_guesses(cur, i, guessed_words, guesses_df):
142
+ if cur.get('guess') and cur['guess'] not in guessed_words:
143
+ guessed_words.add(cur['guess'])
144
+ cur['sim'] = round(cur['sim'], 2)
145
+ cur['i'] = i
146
+ guesses_df.loc[i] = cur
147
  i += 1
148
+ guesses_df = guesses_df.sort_values(by=["sim"], ascending=False)
149
  return i, guessed_words, guesses_df
150
 
151
+ api_key.change(unfreeze, [], [msg]).then(reset_history, [], [history]).then(greet, [api_key, chatbot], [chatbot])
152
+ msg.submit(respond, [msg, chatbot, cur_guess], [chatbot, cur_guess])
153
+ cur_guess.change(update_guesses, [cur_guess, idx, guessed, guesses_table], [idx, guessed, guesses_table])
 
154
 
155
  gr.Examples(
156
  [
 
165
  )
166
 
167
  if __name__ == "__main__":
168
+ demo.queue(concurrency_count=20).launch()
169
+ # demo.queue(concurrency_count=20).launch()
data/rule.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # イミトル
2
+
3
+ ## ゲームの遊び方
4
+ 今日の言葉を当てるゲームです。 正解と推測される単語を答えると、正解かどうかがわかります。正解でない場合、正解に近い度合いと順位がわかります。正解に近い程度とは単語の意味や文脈、単語の言語的特性などによって決まります。 推測した単語の近い度合いと順位を参考にして、答えを当て続けてみてください。
5
+
6
+ ## ヒントのについて
7
+ もし正解が難しかったり、ゲームのルールがわかりにくい場合は、ヒントを求めることができます。正解単語を使う例文、正解単語とある単語の意味や文法的な共通点など正解に関する様々な情報を聞くことができます。正し、ヒントに正解単語は`<mask>`などで隠すようになっています。
8
+
9
+ ## 「近い度合いと順位」について
10
+ 「近い度合い」は正解単語との類似度で、-100から+100までの数字で表します。「順位」はデータベースの中、近い度合いの相対的な位置で、1位から1000位まで表します。「近い度合い('sim')」が100の単語が正解単語です。
src/__pycache__/functions.cpython-310.pyc DELETED
Binary file (764 Bytes)
 
src/__pycache__/semantle.cpython-310.pyc DELETED
Binary file (801 Bytes)
 
src/__pycache__/utils.cpython-310.pyc DELETED
Binary file (986 Bytes)
 
src/functions.py CHANGED
@@ -7,42 +7,29 @@ guess_word = {"name": "guess_word",
7
  "type": "string",
8
  "description": "A single Japanese word to guess, which is can be a noun, verb, adverb or adjective. e.g. 空, 近い, 行く, etc."
9
  },
10
- "puzzle_num": {
11
- "type": "integer",
12
- "description": "An index indicating today's puzzle."
13
- }
14
  },
15
- "required": ["word", "puzzle_num"]
16
  }}
17
 
18
- prepare_hint = {"name": "prepare_hint",
19
- "description": "Use this function to retrieve information for hint.",
20
- "parameters": {
21
- "type": "object",
22
- "properties": {
23
- "puzzle_num": {
24
- "type": "interger",
25
- "description": "An index for today's puzzle set."
26
- }
27
- },
28
- "required": ["puzzle_num"]
29
- }}
30
-
31
- get_secret = {"name": "get_secret",
32
- "description": "You can check what the correct answer of today's puzzle is by this function.",
33
  "parameters": {
34
  "type": "object",
35
  "properties": {
36
- "puzzle_num": {
37
- "type": "integer",
38
- "description": "An index indicating today's puzzle."
39
  }
40
- },
41
- "required": ["puzzle_num"]
42
  }}
43
 
44
- get_puzzle_num = {"name": "get_puzzle_num",
45
- "description": "Use this function to check today's puzzle number.",
46
  "parameters": {
47
  "type": "object",
48
  "properties": {}
@@ -60,13 +47,23 @@ update_history = {"name": "update_history",
60
  },
61
  "guess_history": {
62
  "type": "object",
63
- "description": "A dataframe containing the guessed words and its xore and rank in a row."
64
  }
65
  },
66
  "required": ["current_guess", "guess_history"]
67
  }}
 
 
 
 
 
 
68
 
69
 
70
  def get_functions():
71
- functions = [guess_word]
 
 
 
 
72
  return functions
 
7
  "type": "string",
8
  "description": "A single Japanese word to guess, which is can be a noun, verb, adverb or adjective. e.g. 空, 近い, 行く, etc."
9
  },
10
+ # "puzzle_num": {
11
+ # "type": "integer",
12
+ # "description": "An index indicating today's puzzle."
13
+ # }
14
  },
15
+ "required": ["word"]
16
  }}
17
 
18
+ lookup_answer = {"name": "lookup_answer",
19
+ "description": "Use this function to check the correct answer of today's puzzle.",
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  "parameters": {
21
  "type": "object",
22
  "properties": {
23
+ # "puzzle_num": {
24
+ # "type": "integer",
25
+ # "description": "An index indicating today's puzzle."
26
  }
27
+ # },
28
+ # "required": ["puzzle_num"]
29
  }}
30
 
31
+ retrieve_puzzle_num = {"name": "retrieve_puzzle_num",
32
+ "description": "Use this function to retrieve today's puzzle number.",
33
  "parameters": {
34
  "type": "object",
35
  "properties": {}
 
47
  },
48
  "guess_history": {
49
  "type": "object",
50
+ "description": "A dataframe containing the guessed words and its score and rank in a row."
51
  }
52
  },
53
  "required": ["current_guess", "guess_history"]
54
  }}
55
+ read_rule = {"name": "read_rule",
56
+ "description": "Use this function to read the game rule for clarification of your response.",
57
+ "parameters": {
58
+ "type": "object",
59
+ "properties": {},
60
+ }}
61
 
62
 
63
  def get_functions():
64
+ functions = [guess_word,
65
+ lookup_answer,
66
+ # retrieve_puzzle_num,
67
+ # update_history,
68
+ read_rule]
69
  return functions
src/semantle.py CHANGED
@@ -2,25 +2,34 @@ from datetime import date, datetime
2
  from pytz import utc, timezone
3
  import requests
4
 
5
- def get_secret(puzzle_num: int):
 
6
  request_url = f"https://semantoru.com/yesterday/{puzzle_num+1}"
7
- response = requests.get(request_url)
8
  if response.status_code == 200:
9
  return response.content
10
  else:
11
  return "Not found error."
12
 
13
- def get_guess(word: str, puzzle_num: int):
 
14
  request_url = f"https://semantoru.com/guess/{puzzle_num}/{word}"
15
- response = requests.get(request_url)
 
16
  print(response.status_code)
17
  if response.status_code == 200:
18
- return response.json()
 
 
 
 
 
 
19
  else:
20
  return {"guess": word,
21
  "sim": None,
22
  "rank": None}
23
 
24
  def get_puzzle_num():
25
- FIRST_DAY = date(2023, 4, 2)
26
- return (utc.localize(datetime.utcnow()).astimezone(timezone('Asia/Tokyo')).date() - FIRST_DAY).days
 
2
  from pytz import utc, timezone
3
  import requests
4
 
5
+ def get_secret():
6
+ puzzle_num = get_puzzle_num()
7
  request_url = f"https://semantoru.com/yesterday/{puzzle_num+1}"
8
+ response = requests.get(request_url, timeout=5)
9
  if response.status_code == 200:
10
  return response.content
11
  else:
12
  return "Not found error."
13
 
14
+ def get_guess(word: str):
15
+ puzzle_num = get_puzzle_num()
16
  request_url = f"https://semantoru.com/guess/{puzzle_num}/{word}"
17
+ print(request_url)
18
+ response = requests.get(request_url, timeout=5)
19
  print(response.status_code)
20
  if response.status_code == 200:
21
+ rtn = response.json()
22
+ print(rtn)
23
+ if rtn['rank'] == '正解!':
24
+ return rtn
25
+ elif rtn['rank'] > 1000:
26
+ rtn['rank'] = '?'
27
+ return rtn
28
  else:
29
  return {"guess": word,
30
  "sim": None,
31
  "rank": None}
32
 
33
  def get_puzzle_num():
34
+ fisrt_day = date(2023, 4, 2)
35
+ return (utc.localize(datetime.utcnow()).astimezone(timezone('Asia/Tokyo')).date() - fisrt_day).days
src/utils.py CHANGED
@@ -1,18 +1,3 @@
1
-
2
-
3
- def add_guess(guess_result, guessed, guesses):
4
- word, sim, rank = guess_result
5
- if sim:
6
- if word not in guessed:
7
- sim = round(sim, 2)
8
- rank = "情報なし" if rank == 1001 else rank
9
- guesses.loc[len(guessed)] = ([len(guessed), word, sim, rank])
10
- guessed.add(word)
11
- cur_result = format_result(word, sim, rank)
12
- else:
13
- cur_result = "不正解: 正しくない単語"
14
- return "\n".join([cur_result, "最高スコア:", format_table(guesses)]), guessed, guesses
15
-
16
  def format_result(word, sim, rank):
17
  return f"{word}: スコア {sim}, ランク {rank}"
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  def format_result(word, sim, rank):
2
  return f"{word}: スコア {sim}, ランク {rank}"
3