⭐️⭐️⭐️ MLX Language Model Interactive Terminal ⭐️⭐️⭐️ さらにバージョンアップ stablelm-2-zephyr-1_6b用
MLX 対話 Terminalのバージョンアップをまたおこないました。
h で最初の画面 helpです。
r で regenerationします。
/tokens で生成するtoken数を途中で変えれます。100~1000の間にしてます。
import time
import datetime
import mlx.core as mx
from mlx_lm import load
from mlx_lm.utils import generate_step
# 翻訳
from easynmt import EasyNMT
################ ここから必要に応じて設定 ######################
model_name = "stabilityai/stablelm-2-zephyr-1_6b"
# 翻訳モデルの指定 引数でモデルを指定。予め用意されたモデルが色々あるので、easynmtのgithub参照
translation_model = EasyNMT('mbart50_m2m')
# LLMのTemperatureと生成されるテキストのmax_tokensの設定 dolphin 0.7 nous-helmes 0.8 が推奨値
temperature=0.7
max_tokens=750
ai_name = "StableLM" # 好きな表示の名前を設定
# 最初のシステムメッセージを設定
system_message = "This is a system prompt, please behave and help the user." # Stable Beluga 2 での例
# 対話の発言回数を設定 とりあえず10回(5回の対話やりとり)の発言のみを保持
number_memory = 10
#################### ここまでの上を設定 ################
# 初期プロンプトを設定(システムメッセージと最初のユーザー入力のプレースホルダー){{ }}でプレースホルダーの予約
initial_prompt = f"<|endoftext|>system\n{system_message}<|endoftext|>\n<|endoftext|>user\n{{first_user_input}}<|endoftext|>\n"
# 最初のユーザー入力を保持するためのフラグと変数
is_first_input = True
first_user_input = ""
last_input = ""
# 会話の履歴を保持するリスト (ユーザーとアシスタントの会話)
conversation_history = []
conversation_all = []
# モデルとトークナイザーをロード trust_remote_codeをTrueのパターン
model, tokenizer = load(model_name,tokenizer_config={"trust_remote_code": True})
############# ここから関数いろいろ ######################
# max_tokensを設定する関数
def set_max_tokens():
global max_tokens
while True:
try:
max_tokens_input = input("Enter max tokens (100-1000): ")
max_tokens = int(max_tokens_input)
if 100 <= max_tokens <= 1000:
print(f"Max tokens set to {max_tokens}.")
return
else:
print("Invalid input. Please enter a number between 100 and 1000.")
except ValueError:
print("Invalid input. Please enter a valid integer.") # ここでループの先頭に戻り、再入力を促す
# 対話記録のための関数
def record_conversation_history(user_prompt, assistant_response):
global conversation_history, conversation_all # 変更を加えるため宣言が必要
# conversation_historyを更新するための関数 最新のユーザーの入力とaiの応答を追加する
conversation_history.append(f"<|endoftext|>user\n{user_prompt}<|endoftext|>\n")
conversation_history.append(f"<|endoftext|>assistant\n{assistant_response}<|endoftext|>\n")
# conversation_historyが最新の10個の要素のみを保持するようにする
conversation_history = conversation_history[-number_memory:]
# conversation_allに対話を追加するための関数
conversation_all.append(f"<|endoftext|>user\n{user_prompt}<|endoftext|>\n")
conversation_all.append(f"<|endoftext|>assistant\n{assistant_response}<|endoftext|>\n")
def save_conversation(conversation_all):
"""会話履歴をファイルに保存する関数"""
# 現在の日付と時刻を取得し、ファイル名にするための文字列を作成
current_time = datetime.datetime.now()
timestamp_str = current_time.strftime("%Y%m%d_%H%M")
filename = f"conversation_all_{timestamp_str}.txt"
try:
with open(filename, 'w', encoding='utf-8') as file:
file.write('\n'.join(str(item) for item in conversation_all))
print(f"=== Conversation history saved as {filename}! ===")
except IOError as e:
print(f"Error while saving conversation history: {e}")
# テキスト生成のための関数 modelとtokenizerは読み込むだけだからglobalはいらない。
def produce_text(the_prompt):
tokens = []
skip = 0
REPLACEMENT_CHAR = "\ufffd"
# generate_step関数の結果をイテレートするためのforループ versionがあがったようなので、token, _ から (token, prob), n
for (token, prob),n in zip(generate_step(mx.array(tokenizer.encode(the_prompt)), model, temperature),
range(max_tokens)):
# EOS tokenが出現したときにループを抜ける
if token == tokenizer.eos_token_id:
break
tokens.append(token.item())
# 生成されたトークンからテキストをデコード
generated_text = tokenizer.decode(tokens)
# 置換文字である壊れた文字があればを取り除く
generated_text = generated_text.replace(REPLACEMENT_CHAR,'')
# 以前に出力されたテキストをスキップして新しいテキストをyield
yield generated_text[skip:]
skip = len(generated_text) # スキップする文字数を更新
# ユーザーの入力と翻訳フラグを受け取る。 produce_textに渡すfull_promptを作成、生成したテキストを表示する
def show_chat(user_input, translation_enabled):
global conversation_history, conversation_all, initial_prompt, is_first_input, first_user_input, last_input
full_prompt = ""
# 上は、これによってグローバル変数として扱う
# 最初のユーザー入力を確認し、保持するとともにsystem promptを含むinitial_promptを設定
if is_first_input:
if user_input in ('h', 'c', 'r', '/show', '/clear', '/history', '/save', '/reset', '/tokens'):
print('No initial prompt, yet.')
return
else:
first_user_input = user_input # showのため グローバル変数として保存
initial_prompt = initial_prompt.replace("{first_user_input}", user_input) # グローバル変数として維持する
is_first_input = False # 最初の入力が処理されたのでフラグを更新
# プロントに用いる会話履歴をクリアーにするコマンドの実行部分
if user_input == "/clear":
conversation_history = []
print("===! Conversation history cleared! ===")
return
# ユーザーが会話履歴を表示するコマンドを入力した場合
if user_input == '/history':
print("\n===== Recent Conversation History =====\n")
print("".join(conversation_history).strip())
return # 会話履歴を表示し、次の入力を待つ
# initial promptからすべての会話履歴をクリアーにするコマンドの実行部分
if user_input == "/reset":
conversation_all = []
conversation_history = []
first_user_input =""
is_first_input = True
print("===! ALL Conversation history and Initial Prompt cleared! ===")
return
if user_input == "/tokens":
set_max_tokens()
return
# 会話履歴を保存する
if user_input == "/save":
# save関数を呼び出し
save_conversation(conversation_all)
return
# システムプロンプトとイニシャルプロンプトを表示する実行
if user_input == "/show":
print("=== System Prompt and Initial Prompt ===")
print("System: ", system_message)
print("Initial: ", first_user_input)
print("")
return
if user_input == "h":
print_commands()
return
# 続きを促すショートカットがあったら、続きを促す文章にする
if user_input == 'c':
user_input = 'Continue from your last line.'
# regemerateを表示する実行
if user_input == "r":
print("=== Regeneration ===")
user_input = last_input
conversation_history = conversation_history[:-2] # スライス操作でリストの最後の2要素(対話)を取り除く
conversation_all = conversation_all[:-2]
# `conversation_history` は文字列のリストです。それを単一の文字列に変換するために、リストの要素を改行文字で連結します。
conversation_history_str = "\n".join(conversation_history)
# 連結して full_promptを設定。ただしhistoryが埋まるまで、user_inputが2回続いてしまう状態
full_prompt = initial_prompt + conversation_history_str + f"<|endoftext|>user\n{user_input}<|endoftext|>\n<|endoftext|>assistant\n"
print(f"\n{ai_name}: ", end="", flush=True)
full_response = ""
for chunk in produce_text(full_prompt): #produce_text関数を呼び出す
full_response += chunk # 生成されたテキスト全文を収納しておくため、翻訳時と会話記録更新に使う
if translation_enabled == False:
print(chunk, end="", flush=True) # chunkテキストを出力
time.sleep(0.1) # 生成中のタイピング効果をシミュレートするための遅延。適当な値にするか、必要ならコメントアウト。
if translation_enabled == True:
translated = translation_model.translate(full_response, target_lang="ja", max_length=1000)
print(translated) # 翻訳されたchunkテキストを出力
print("\n")
# conversation_allとconversation_historyを更新する関数の実行
record_conversation_history(user_input, full_response)
last_input = user_input
# ユーザーからの入力を処理し、整形されたテキストを返す関数
def get_user_input():
user_input = ""
multi_line = False
while True:
line = input("User: ")
if line.startswith('"""'):
if multi_line:
multi_line = False
if line.endswith('"""'):
user_input += line[:-3] # 末尾の引用符を除去する
else:
user_input += line + "\n"
break
else:
multi_line = True
if line.endswith('"""') and len(line) > 3:
user_input += line[3:-3] + "\n" # 先頭と末尾の引用符を除去する
break
else:
user_input += line[3:] + "\n" # 先頭の引用符を除去する
else:
if multi_line:
if line.endswith('"""'):
user_input += line[:-3] # 末尾の引用符を除去する
break
else:
user_input += line + "\n"
else:
user_input += line
break
return user_input.strip()
def print_commands():
print("\n⭐️⭐️⭐️ MLX Language Model Interactive Terminal ⭐️⭐️⭐️\n")
print("Model: ", model_name)
print("-" * 70)
print("Available Commands:")
print(
" type `h`: Help for display available commands.\n"
" type `c`: Continue from the last response.\n"
" type `r`: Regenerate another response.\n"
" type `q`: Quit the application.\n"
" type `/history`: View recent conversation history.\n"
" type `/save`: Save all conversation history to a file.\n"
" type `/reset`: Restart with a new initial prompt.\n"
" type `/show`: Display system and initial prompt.\n"
" type `/tokens`: Set the output's max token limit.\n"
" type `/translate`: Toggle JA-EN/EN-JA translation.")
print("-" * 70)
print('For multi-line input, enclose your text with triple quotes (""") ')
print('at the beginning and the end.')
print("=" * 70 + "\n")
# メイン部分
def main():
# 初期状態の翻訳フラグ (Falseは翻訳がオフ、Trueは翻訳がオン:初期値はFalse)
translation_enabled = False
print_commands()
while True:
formatted_input = get_user_input()
# ユーザーが終了コマンドを入力した場合
if formatted_input.lower() == 'q':
print("\n" + "=" * 70 + "\n")
print("Exiting the interactive terminal.")
print("\n")
break # スクリプトが終了する
# ユーザー入力が何もなかった場合に警告を出して次の入力を待つ
if not formatted_input:
print("Please enter some text to proceed.")
continue # Whileのループの頭に戻る
# /translate コマンドが呼び出されたとき、フラグをトグルする
if formatted_input == "/translate":
translation_enabled = not translation_enabled
# 状態を英語で表示する
print(f"Translation is now {'on' if translation_enabled else 'off'}.")
continue # 次の入力を待つ
# 実質、作業をする関数に入力された文章を引き渡し
if translation_enabled:
translated = translation_model.translate(formatted_input, target_lang="en", max_length=1000)
else:
translated = formatted_input
show_chat(translated.strip(), translation_enabled)
# このスクリプトを走らせるためのコマンド
if __name__ == "__main__":
main()
あとつけるとしたら、load機能ぐらいでしょうか。
Python入門者ですので、お気づきなことがあれがご教示ください。
#AI #AIとやってみる #やってみる #mlx #huggingface #LLM #ローカルLLM #大規模自然言語モデル #Python入門
この記事を最後までご覧いただき、ありがとうございます!もしも私の活動を応援していただけるなら、大変嬉しく思います。