ChainlitでLLMアプリを試してみた
Chainlit lets you create ChatGPT-like UIs on top of any Python code in minutes! Some of the key features include intermediary steps visualisation, element management & display (images, text, carousel, etc.) as well as cloud deployment.
Chainlitは、あらゆるPythonコードの上にChatGPTのようなUIを数分で作成することができます!主な機能として、中間ステップの可視化、要素の管理・表示(画像、テキスト、カルーセルなど)、クラウドデプロイメントなどが挙げられます。
インストール
Get Startedやっていく。
LAN内のサーバ上にあるpyenv-virtualenv環境で試す。Python-3.10.11。
$ pip install chainlit
起動。8000番ポートで上がってくる。
$ chainlit hello
OSError: [Errno 98] Address already in use: ('0.0.0.0', 8000)
うちの場合はポートが埋まっていた。GitHubのissueを見てみると以下を使えば良さそう。
- 環境変数
-
CHAINLIT_HOST
(デフォルト:0.0.0.0
) -
CHAINLIT_PORT
(デフォルト:8000
)
-
- コマンドラインオプション
- ただしこちらは
chainlit run
の場合のみっぽい。 --host IPアドレス
--port ポート番号
- ただしこちらは
ということで、今回は環境変数で8888ポートを使う。
$ CHAINLIT_PORT=8888 chainlit hello
2023-06-06 19:17:15 - Your app is available at http://localhost:8888
ブラウザにアクセスしてみるも真っ白。設定変えたり、8000ポートを開けてデフォルトで起動してみるも同じ状況。issue見るとどうも同じ状況の人がいる様子。
環境依存なのかもしれないが、ちょっと原因がわからない。
気を取り直して、ローカルマシンのdevcontainerでやってみるとうまくいった。
サンプルは単に名前を入れて応答するだけのものなので、これ以上の会話は続かない。
ということでコードを書いていく。
ChainlitでPythonアプリを書く
app.pyを以下の内容で作成する
import chainlit as cl
@cl.on_message
def main(message: str):
# Your custom logic goes here...
# Send a response back to the user
cl.Message(
content=f"Received: {message}",
).send()
スクリプトをchainlitで実行する場合はrun
。このへんはstereamlitと同じ。名前や見た目もそれっぽいしね。
$ chainlit run app.py
ブラウザでアクセスしてみる。実行したスクリプトと同じパスにchainlit.md
があるとその内容が表示される様子(上にある"Readme"も同じ)。
フォームに入力するとこんな感じで先ほどのコードが動く
コードをざっと見た感じ
-
@cl.on_message
でユーザからの入力があれば実行される処理を書く。 -
cl.Message().send
でメッセージを返す。
run に -w をつけると、スクリプトに変更があれば自動リロードされる。
$ chainlit run app.py -w
らしいのだが、修正して実行するとこうなる。
ValueError: Module should at least expose one of @langchain_factory, @on_message or @on_chat_start function
なぜこのエラーに鳴るのか意味がわからないが、例えば以下のようなコードだと問題なく自動リロードが効くので、バグなのかな?
import chainlit as cl
@cl.on_chat_start
def main():
res = cl.AskUserMessage(content="What is your name?", timeout=10).send()
if res:
cl.Message(
content=f"Your name is: {res['content']}",
).send()
@cl.on_message
def main(message: str):
cl.Message(
content=f"R: {message}",
).send()
LangChainと組み合わせてみる
LangChainをインストール
$ pip install langchain
.envを作成。chainlitは.envを自動的に読んでくれる。
OPENAI_API_KEY=XXXXXXXXXX
コード
import os
from langchain import PromptTemplate, OpenAI, LLMChain
import chainlit as cl
template = """質問: {question}
回答: ステップバイステップで考えてみましょう。"""
@cl.langchain_factory
def factory():
prompt = PromptTemplate(template=template, input_variables=["question"])
llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0), verbose=True)
return llm_chain
起動
$ chainlit run app.py -w
こんな感じで。ChatGPTのプラグインっぽい雰囲気がある。
こういう感じで回答が来る。Took 1 step
というところをクリックすると、
処理内容がここに表示されるっぽい。今回はシンプルなLLMChainなので大した内容ではないけど。
chainlitを起動したターミナルを見ると、プロンプトが表示されている。LangChainでverbose=True
しているため。
ちなみにLangChainを使った場合、プロンプトやcompletionの結果は.chainlit
ディレクトリにキャッシュされるらしい。
$ ls -la .chainlit/
total 24
drwxr-xr-x 2 vscode vscode 4096 Jun 6 12:55 .
drwxr-xr-x 5 vscode vscode 4096 Jun 6 12:34 ..
-rw-r--r-- 1 vscode vscode 12288 Jun 6 12:55 .langchain.db
-rw-r--r-- 1 vscode vscode 954 Jun 6 10:34 config.toml
.langchain.dbはSQLite3で読める。
$ sqlite3 .chainlit/.langchain.db
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> .tables
full_llm_cache
sqlite> .schema full_llm_cache
CREATE TABLE full_llm_cache (
prompt VARCHAR NOT NULL,
llm VARCHAR NOT NULL,
idx INTEGER NOT NULL,
response VARCHAR,
PRIMARY KEY (prompt, llm, idx)
);
sqlite> .mode list
sqlite> select * from full_llm_cache;
prompt|llm|idx|response
質問: 明日の天気は?
回答: ステップバイステップで考えてみましょう。|[('_type', 'openai'), ('frequency_penalty', 0), ('logit_bias', {}), ('max_tokens', 256), ('model_name', 'text-davinci-003'), ('n', 1), ('presence_penalty', 0), ('request_timeout', None), ('stop', None), ('temperature', 0.0), ('top_p', 1)]|0|まず、地域によって天気は異なります。そのため、明日の天気を知るには、あなたが住んでいる地域の天気予報を確認する必要があります。
ということはキャッシュが効くはず。同じことを聞いてみた。
同じ回答が返ってくるのはもちろん、質問から回答を返すまでの時間が初回とそれ以降で異なることがわかる。
もう少し複雑な例として、wolframalphaを使ったAgentを作ってみる。
$ pip install wolframalpha
.envに追加
WOLFRAM_ALPHA_APPID=XXXXXXX
コード
import os
from langchain import OpenAI
from langchain.agents import initialize_agent, load_tools, AgentType
import chainlit as cl
@cl.on_chat_start
def main():
cl.Message(
content=f"何でも聞いてね。",
).send()
@cl.langchain_factory
def factory():
agent = initialize_agent(
tools=load_tools(['wolfram-alpha']),
llm=OpenAI(temperature=0),
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
)
return agent
Took...
をクリックすると処理が見える。Wolfram Alphaに問い合わせしつつReACTで回答を検討しているのがわかる。
factoryでまるっとChainやAgentを渡すだけでいいのはとても楽ちん。
コンソールの方も。
> Entering new AgentExecutor chain...
I need to compare the size of two lakes
Action: Wolfram Alpha
Action Input: "What is the size of Lake Biwa compared to twice the size of Lake Biwa"
Observation: Wolfram Alpha wasn't able to answer it
Thought: I need to compare the size of Lake Biwa and Lake Baikal
Action: Wolfram Alpha
Action Input: "What is the size of Lake Biwa compared to Lake Baikal"
Observation: Assumption: Lake Biwa
Lake Baikal | surface area
Answer: Lake Biwa | 674 km^2 (square kilometers) (national rank: 1st | world rank: 276th)
Lake Baikal | 31500 km^2 (square kilometers) (national rank: 2nd | world rank: 8th)
Thought: I now know the final answer
Final Answer: バイカル湖の方が広いです。バイカル湖は、日本の最大の湖である琵琶湖の2倍以上の広さがあります。
> Finished chain.
まとめ
まだそこまで深く触ってはないけど所感として。
- かなりお手軽に書ける。たぶんstreamlitよりもライトに書けるのでは。
- その名の通り、streamlitと雰囲気が似てる。streamlitを使ったことがあれば、ツールセットとしては同じ感覚になれそう。
- ドキュメントもまあそこそこある印象。ただ内容が薄かったり、探しにくいところも。
- ちょいちょいおかしな動きをするところがある。自動リロードでエラーになったり、なかなか起動しなかったり。
まだできたてホヤホヤなのでいろいろ不備もありそうだけど、LangChain触ってるなら、いい感じに使えそう。あとstreamlitと同じく、セッションも扱えるようだし、クラウドもあるみたい。
今後にも期待できそうなので、もうちょっと触ってみるつもり。
そういえば日本語入力で変換確定のENTERを拾っちゃって送信するみたい。ChatGPTに聞きながらちょっと直してみた。
手元で確認した限りは問題なさそうではある。
が、react全然ワカラナイマンなので、pull req出すのちょっと躊躇してる・・・ ビクビクしながらPRだした
マージされた!
0.3.0でいろいろ変更が入っていてlangchain_factoryとかも書き方が変わっている様子。
最新のドキュメントのコードを参考にされたし。