Home About Contact
Python , Streamlit

Streamlit で簡単にウェブアプリをつくる(その2) Open AI API の Chat Completion を使う

その1 からの続きです。

NovelGen

その1では、 Streamlit で UI だけつくったので、 あとは、実際にプロンプトから OpenAI API 経由で文章を生成させる機能を追加します。

該当APIの使い方:

まずコマンドラインで作動するものをつくる

その1で Streamlit でつくった UI にいきなり実装しないで、 まずは、プロンプトを渡すと、文章を生成するところだけコード cmd.py つくります。

# cmd.py 

from openai import OpenAI

def generateNovelContent(prompt):
	client = OpenAI()
	completion = client.chat.completions.create(
	  #model="gpt-4o",
	  model="gpt-4o-mini",
	  messages=[
	    {"role": "system", "content": "You are a great Japanese novel writer."},
	    {"role": "user", "content": f"次の文章に続けて「徒然草」風の現代日本語文で300文字程度のエッセイを作成してください。次の文章: {prompt}"}
	  ]
	)
	
	chatCompletionMessage = completion.choices[0].message
	if chatCompletionMessage.content is None:
		return ""
	else:
		return chatCompletionMessage.content

	#return completion.choices[0].message


p = "遊びに興じているときあっという間に時間がすぎてしまうものだ。しかし"
print(generateNovelContent(p))

p に定義したのが与えるプロンプトです。 書き出しだけ与えてあとは頼む!方式にしました。

model は gpt-4o-mini を使いました。 実験なので、 mini の方でいいか、くらいの理由で選択。

completion.choices[0].messageChatCompletionMessage クラスのインスタンスです。

ソースはこれ:

class ChatCompletionMessage(BaseModel):
    content: Optional[str] = None
    """The contents of the message."""

    # 以下省略

したがって、 completion.choices[0].message.contentOptional[str] 型なので、 str か None のいずれかになる。

そこで、 generateNovelContent 関数の最後で 次のようにして、None だったら空文字を返すようにして、とにかく文字列を返すように対策しておきます。

chatCompletionMessage = completion.choices[0].message
if chatCompletionMessage.content is None:
	return ""
else:
	return chatCompletionMessage.content

実行する

実行する前に環境変数 OPENAI_API_KEY を設定する必要があります。 これを export OPENAI_API_KEY した上で、実行します。

( venv-novelgen ) $ python cmd.py
遊びに興じているときあっという間に時間がすぎてしまうものだ。しかし、日々の仕事や勉強に追われていると、終わりの見えな
いトンネルを進むが如く、時間がただ過ぎ去るのを感じることがある。ふとした瞬間に、童心に還り、無邪気に遊ぶことの意義に
思いを巡らせる。

昔、友人たちと河原で過ごした日々を思い出す。草の上に寝転び、夏の空を見上げては、白い雲の形を指でなぞったものだ。あの
無責任な幸福感。今はどうだろうか。忙殺された日常に埋もれ、心のどこかが渇いているように感じる。遊びこそ、心を満たす源
泉なのだ。

遊びは単なる娯楽ではなく、自己を取り戻し、人生を豊かにする力を秘めている。仕事の合間に小さな遊びを挟むこと、心に余裕
を持つことが、結果として生産性を高めることにもつながる。だからこそ、日常の中に少しの遊びを見つけることが、現代を生き
る知恵なのだろうと思う。遊びの中にこそ、私たちの本来の姿が宿っている。

作動しました。しかも、ちゃんと兼好法師の口調になっている気がする。 予想外の楽しさ。

もう一つ試してみます。 プロンプトを次のものに書き換えて再度実行してみます。

p = "つい言い過ぎてしまったと感じた時は"

実行。

( venv-novelgen ) $ python cmd.py
つい言い過ぎてしまったと感じた時は、まずは深呼吸をしてみるがよい。息を吸い込み、吐き出すその間に、自らの心を静めるのだ。
言葉は、時として刃となり、相手を傷つけることもある。しかし一度放たれた言葉は、流れ去ることなく、その根を深く心に残すものなり。
よって、言い過ぎた言葉の後には、必ずや反省と謝罪が必要であろう。

人は誰しも感情に流されてしまうことがある。それは天然の温泉の如く、心の奥底から湧き出るもの。
だが、湯に浸かりすぎると頑なになり、他者との交流が困難となる。故に、適度な距離感を持つことが大切なり。
言葉を慎み、思慮深く、相手に思いやりをもって接すれば、その関係はより一層深まるであろう。

また、言い過ぎの経験より学ぶことも多い。言葉の重みを知り、次第にその使い方が巧みに変わる。
分かち合う喜びや、思いやる心を胸に抱いて、日々の生活を穏やかに過ごすべきなり。言い過ぎたことを悔いるのも、人の常ということを忘れてはならぬ。

改行位置は読みやすいように調整しています。

Streamlit でつくった UI に組み込む

組み込むといっても 手間はなく、 from openai import OpenAI を追加した上で、 元からあった generateNovelContent を今回つくった関数に差し替えるだけです。

# app.py

import streamlit as st

from openai import OpenAI

def generateNovelContent(prompt):
	client = OpenAI()
	completion = client.chat.completions.create(
	  #model="gpt-4o",
	  model="gpt-4o-mini",
	  messages=[
	    {"role": "system", "content": "You are a great Japanese novel writer."},
	    {"role": "user", "content": f"次の文章に続けて「徒然草」風の現代日本語文で300文字程度のエッセイを作成してください。次の文章: {prompt}"}
	  ]
	)
	
	chatCompletionMessage = completion.choices[0].message
	if chatCompletionMessage.content is None:
		return "ERROR"
	else:
		return chatCompletionMessage.content

def view():
	st.title("NovelGen")
	col0, col1, col2 = st.columns([7,1,1], vertical_alignment="bottom")
	with col0:
		q_input = st.text_input("Prompt:", placeholder="Novel Prompt here...")
	with col1:
		c_button_send = st.empty()
	with col2:
		c_button_reset = st.empty()

	c_result = st.container()

	if c_button_send.button("Send", type="primary"):
		a = generateNovelContent(q_input)
		c_result.write( a )

	if c_button_reset.button("Reset"):
		c_result.write("") 

view()

あらたなプロンプト「豪華な食事をたらふく食べたあとは」に続くエッセイを書いてもらいました。

NovelGen

豪華な食事をたらふく食べたあとは、心の奥に小さな思索が芽を出す。
腹は満たされ、舌は喜びに浸るが、なぜか胸にぽっかりとした空洞が残る。
満腹感は一時の幸福であり、食物の美味しさは舌の記憶に留まる。
しかし、常々思うことだが、贅沢は幸福を約束するものではない。
その瞬間の歓びの後に、何かを求める気持ちが心に宿るのだ。

贅を尽くした料理が、人生の真理を教えてくれるわけではない。
むしろ、日々の食事の中にこそ、人の味わい深さが潜んでいることに気づくことがある。
雑草のように根付く、素朴な味が心を満たす瞬間があるからだ。
たまには、薄味の味噌汁や冷たいご飯を口にし、日常のありがたみを噛みしめたいと感じる。

結局、豪華さを追い求めて疲れた心を癒すのは、意外にも平凡に流れる日々の中にこそ見出せる仕合せなのかもしれない。
美味しさよりも、大切なのはその瞬間の心の満足を得ること。小さな幸せを知ることこそ、真の贅沢というものであろう。

改行位置は読みやすいように調整しています。

兼好法師っぽい。

まとめ

ただ、GPTの生成する文章(日本語)はやはり個人的には気に入らない。 もっとプロンプトを調整すれば、気に入る文章を生成してくれるのかもしれないけれど。 あと英語だったらもっと良いとか? 英語のニュアンスはわからないから評価しようがないけど。

ここから自分の意見と合わない部分を差しかえたり、 そうやって自分の意見が反映された文章をまた GPT にリライトしてもらう、 といったことを繰り返して対話的に文章を生成すればよいものになりそう。

実際、プログラミングでもコンパイラとの対話でコードをつくっていく。 おそらく、近い将来は プログラミングに特化したAIとの対話でつくっていくことになるだろうし。 だから、これからのライターはこの手のLLMモデルとの対話で文章をつくっていくのが常識になるのかも。 「ワープロ使わないで、原稿用紙に万年筆で手書き?!」と驚かれる時代も過去にあったのだろうから、 「AI(LLM)なしで自分だけで文章つくってるの?!」と言われる時代がくる日も近い。

おまけ requirements.txt

pip freeze した結果はこちら:

altair==5.4.1
annotated-types==0.7.0
anyio==4.6.2.post1
attrs==24.2.0
blinker==1.9.0
cachetools==5.5.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
distro==1.9.0
exceptiongroup==1.2.2
gitdb==4.0.11
GitPython==3.1.43
h11==0.14.0
httpcore==1.0.7
httpx==0.27.2
idna==3.10
Jinja2==3.1.4
jiter==0.7.1
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mdurl==0.1.2
narwhals==1.14.1
numpy==2.0.2
openai==1.55.0
packaging==24.2
pandas==2.2.3
pillow==11.0.0
protobuf==5.28.3
pyarrow==18.0.0
pydantic==2.10.1
pydantic_core==2.27.1
pydeck==0.9.1
Pygments==2.18.0
python-dateutil==2.9.0.post0
pytz==2024.2
referencing==0.35.1
requests==2.32.3
rich==13.9.4
rpds-py==0.21.0
six==1.16.0
smmap==5.0.1
sniffio==1.3.1
streamlit==1.40.1
tenacity==9.0.0
toml==0.10.2
tornado==6.4.2
tqdm==4.67.0
typing_extensions==4.12.2
tzdata==2024.2
urllib3==2.2.3
watchdog==6.0.0

以上です。