はじめに
昨今、各所でAIエージェントの開発が活発に行われ、次々と発表されています。
これまでにDevinやOpenHandsを実際に使用した記事を2件ほど公開してきましたが、AIエージェントの導入は確かに業務がスムーズに進むようになった感覚はあります。
とはいえ、Devinのような高機能なAIエージェントは導入コストが高く、費用面を考慮すると自分でエージェントを開発できる方が望ましいのも事実です。
一方で、ゼロからモデルを作成するとなると、技術的にも費用的にも非常に高コストになります。そこで、効率的な方法としてAPIを活用するアプローチが有力です。
そこで本記事では、APIを利用して簡単なChat botを作成する方法について解説します。
自然言語処理の仕様
トークンとは?
みなさん、ChatGPTなどのサービスを使っていて、テキストの生成が途中で止まってしまった経験はありませんでしたか?
その原因の一つとして、「トークン数の制限」がサービス側で設定されている可能性があります。
トークンとは、自然言語処理の分野で使われる単位で、文章を単語や記号などの要素に分割し、それらを数値化したものを指します。
つまり機械は、数値データに変換して処理しています。この変換作業や処理には計算リソースが必要になるため、トークン数が増えると負荷も大きくなります。

例えば、「I have a pen .」でさえ、5つのトークンに分解されます。
これが会話レベルの文章となると、当然ながらトークン数は一気に増加し、それに比例して処理負荷も高くなるのも納得しますね。
APIの取得と設定
Anthropic Claudeモデル
Chat botの開発に利用できる大規模言語モデル(LLM)には、OpenAIのGPTモデル、AnthropicのClaudeモデル、GoogleのGeminiなど、いくつかの選択肢があります。
その中でも、コード生成に特化した性能が高いモデルはどれかを調査したところ、Claudeモデルが非常に優秀であることがわかりました。
特に注目すべきは、コード生成能力を評価するSWE-benchにおいて、Claude 4 Sonnetが70%以上のスコアを記録しているという点です。これは他のモデルと比較しても非常に高い数値です。

以上の理由から、本記事ではAnthropic社が提供するClaudeモデルを使って、Chat botを実装していきます。
環境変数の設定
取得した API Key は、アプリケーション内で安全に扱うために、環境変数として設定しておくのが一般的です。
ディレクトリ内に.envという名前の隠しファイルを作成し、以下のように記述します
この.envファイルは、アプリケーションの起動時などに自動的に読み込まれることで、コード内に直接キーを書かずに済むようになります。
ANTHROPIC_API_KEY=取得したAPI Key
続いて、ターミナル上で以下のように設定します。
export ANTHROPIC_API_KEY=取得したAPI Key
実装
Langchain
LangChainとは大規模言語モデルを活用したアプリケーション開発を支援するためのライブラリです。
今回使用するAnthropic社のclaudeモデルも、langchainを通じて実装することができます。そのほかにも、OpenAIやGoogleGeminiなども実装ができるので、用途に合わせて対応することができます。
今回の実装では以下のモジュールを使用します。
・from langchain_core.prompts import ChatPromptTemplate
・from langchain_core.output_parsers import StrOutputParser
・from langchain_anthropic import ChatAnthropic
import tiktoken #トークン数のカウントに使用
import streamlit as st
from langchain_core.prompts import ChatPromptTemplate #入力
from langchain_core.output_parsers import StrOutputParser #出力
from langchain_anthropic import ChatAnthropic #モデル
なお、今回もUI部分には Streamlit を使用して、シンプルなWebアプリとして開発します。
モデルの定義と各種パラメータ
APIを使った自作のChat botでは生成されるテキストの質に関わる各種パラメータを、ユーザー側で自由に設定することができます。
主に調整可能なパラメータは以下の通りです。
・temperature (高いほど創造性あり、低いほど機械的)
・top_p (高いほど創造性あり。top_kの影響を受ける)
・top_k (高いほど創造性あり)
・max_tokens (値が低いと長文出力が中断される)
今回の実装では、操作性を優先してtemperatureのみを可変設定とし、top_p、top_kはデフォルト値のままとしています。
また、冒頭でも述べた通り、トークン数が最も重要となります。日本語は英語に比べてもトークン数が多くなる傾向にあるため、max_tokensを16,000に設定し、長文生成時にも途中で途切れないように対策をしています。
なお、モデルによっては、max_tokensが16,000に設定できず、Claude 3.5 Sonnetモデルでは8192となります。
def select_model():
temperature = st.sidebar.slider("Temperature", min_value=0.0, max_value=1.0, value=0.0, step=0.01)
# top_p = st.sidebar.slider("Top_p", min_value=0.0, max_value=1.0, value=0.0, step=0.01)
# top_k = st.sidebar.slider("Top_k", min_value=0.0, max_value=1.0, value=0.0, step=0.01)
models = ("Claude 3.5 Sonnet", "Claude 3.7 Sonnet", "Claude Sonnet 4", "Claude Opus 4")
model = st.sidebar.radio(":", models)
if model == "Claude 3.5 Sonnet":
st.session_state.model_name = "claude-3-5-sonnet-20240620"
return ChatAnthropic(
# extended_thinking=True,
temperature=temperature,
# top_p = top_p,
# top_k = int(top_k),
model_name=st.session_state.model_name,
max_tokens = 8192
)
elif model == "〜〜〜〜〜":
上記は例として挙げていますが、モデルを増やしたい場合は、elifで追加していくだけで良いです。
実行処理
Chat botの基本的な流れはざっくりと入力→実行→出力(生成)の3つのフェーズがあります。
今回の実装では、実行処理に関わるinit_chain関数でパイプ演算子(|
) を使用しています。ただし、たとえばGPT系のモデルなどを使う場合、この構文がうまく動作しないケースがあります。その理由として、LangChainが内部的にRunnableという実行コンポーネントを使って処理を構築しているためです。モデルによっては、このRunnableに対応していない場合があり、その結果としてパイプ演算子が機能しなくなることがあります。
ユーザーからの入力処理は、main関数内で行います。Streamlitを用いて入力欄を表示し、そこに入力されたテキストをappendして会話履歴を保持する形にしています。
このように履歴を蓄積することで、過去の対話を踏まえた自然な返答が可能になります。
def init_chain():
llm = select_model()
st.session_state.llm = llm
prompt = ChatPromptTemplate.from_messages([
*st.session_state.message_history,
("user", "{user_input}")
])
output_parser = StrOutputParser()
# Create and return the chain
return prompt | st.session_state.llm | output_parser
def main():
init_page()
init_messages()
chain = init_chain()
# Display chat history
for role, message in st.session_state.get("message_history", []):
st.chat_message(role).markdown(message)
# Monitor user input
if user_input := st.chat_input("何を聞きたいか入力してください"):
st.chat_message("user").markdown(user_input)
# Add user message to history
st.session_state.message_history.append(("user", user_input))
# Process with the chain if it's available
if chain is not None:
with st.chat_message("ai"):
response = st.write_stream(chain.stream({"user_input": user_input}))
# Add AI response to history
st.session_state.message_history.append(("ai", response))
else:
with st.chat_message("ai"):
st.error("Unable to process your request. Please check the model configuration.")

完成
今回作成したソースコードを実行してみたところ、以下のような形でChat botが表示されました。
画像が少し粗くて見づらいかもしれませんが、試しにtemperatureを0の状態で「こんにちは」と入力してみたところ、以下のような出力が得られました。
「こんにちは!お元気ですか?何かお手伝いできることはありますか?」
非常に機械的で、定型的な返答となっているのが分かります。
次に、temperatureを1に設定して同じように「こんにちは」と入力したところ、以下のような、より人間らしい応答が生成されました。
「はい、こんにちは。お話できて嬉しいです。今日はどのようなことでお困りでしょうか?あるいは、何か特定の話題について話したいことはありますか?私にできる限りのサポートをさせていただきます。」
このように、temperatureの値を上げると、より自然で柔らかい返答になる一方で、出力が長くなり、トークン消費量も増える傾向にあることが分かります。
そのため、実運用においては、コストと品質のバランスを見ながら温度設定を調整することが大切です。

終わりに
いかがでしたでしょうか。
今回はstreamlitとLangChainを使って、claudeモデルベースのChat botを作成してみました。
LangChainがどのように使われるのかを今回試してみましたが、実際にコードとして書くことはあまりなく、内部的に動作するもののようでしたのでかなり概念的でわかりずらいところがありました。
今後はただのchat botではなく、出力されたものに対して、内部的にデバックするエージェントループという機能をどのように実装するのか検討していきます。
今回作成したChatbotアプリは僕のgithubから使用することができます。なお、実行に必要なモデルのAPIは各自で準備してください。
必要なライブラリや実行コマンドはREADMEを参考にしてください。
参考URL・ 書籍


