Closed10

ローコードでLLMアプリが作成できる「Langflow」を試す

kun432kun432

過去、ノーコード・GUIでLLMアプリやLLMワークフロー的なものを作れるものとしては、以下を試している。

https://zenn.dev/kun432/scraps/6b8df45ea5cc30

https://zenn.dev/kun432/scraps/5a2933be5e38d3

Langflowについても、去年ぐらいに試したような記憶があるのだけど、きちんと記録していないし、あと、いくつかのGitHubプロジェクトでLangflowをバックエンドで使っている例を最近チラホラ見かけたので、少し試してみる。

kun432kun432

GitHubレポジトリ

https://github.com/langflow-ai/langflow

Langflow

Langflowは、RAG(Retrieval-Augmented Generation)やマルチエージェントAIアプリケーション向けのローコードアプリビルダーです。Pythonベースであり、モデル、API、データベースに依存しないという特徴を持っています。

✨ 主な特徴

  • モデル、API、データソース、データベースに依存しないPythonベース
  • ワークフローの構築とテストをドラッグ&ドロップで行えるビジュアルIDE
  • ワークフローをすぐにテストし、ステップバイステップで制御できるプレイグラウンド
  • マルチエージェントのオーケストレーションと会話の管理・取得が可能。
  • 設定なしですぐに始められる無料のクラウドサービス
  • APIとして公開、またはPythonアプリケーションとしてエクスポート可能。
  • LangSmith、LangFuse、LangWatchと統合された可観測性
  • エンタープライズレベルのセキュリティとスケーラビリティを備えた無料のDataStax Langflowクラウドサービス。
  • Pythonを使用してワークフローをカスタマイズ、または完全に新しいフローを作成可能。
  • モデル、API、データベースに対応した再利用可能なコンポーネントとしてのエコシステム統合

ドキュメントはこちら

https://docs.langflow.org/

kun432kun432

インストール

https://docs.langflow.org/getting-started-installation

Langflowのインストール、というか利用方法は以下。

  • DataStax Langflow
    • Apache Cassandra、そのマネージド版であるAstraDBの開発をリードしているDataStax社のクラウドサービスで利用
    • 料金等がわからないのだが、恐らくLangflow自体は無料で始めれそう。
    • その場合はベクトルDBにAstraDBを使用することになり、ここで無料・有料みたいなプランがあるみたい
  • セルフホスト

今回はローカルのMac上にDockerでインストールすることとする。

https://docs.langflow.org/deployment-docker

レポジトリをクローン

$ git clone https://github.com/langflow-ai/langflow && cd langflow

docker_exampleディレクトリに移動

$  cd  docker_example

docker composeで起動

$ docker compose up -d

ブラウザでhttp://localhost:7860/にアクセスして以下のような画面が表示されればOK

kun432kun432

Quickstart

https://docs.langflow.org/getting-started-quickstart

以降はQuickstartに従って進めてみる。先ほどの画面から、"Start Here"をクリック。

あらかじめ用意されたテンプレートから作成することもできるし、空の状態からイチから作ることもできるみたい。今回はQuick Startに従って、"Basic Prompting"のテンプレートを選択。

フローが作成された。ブロックを並べてフローを作る、よくあるタイプだね。

サンプルのフローをざっと見てみる。

  • "Chat Input"がユーザの入力を受ける
  • "Prompt"でテンプレートにユーザの入力を展開してプロンプトを作成
  • "OpenAI"がOpenAI APIにプロンプトを送信
  • "Chat Output"でOpenAI APIの回答を出力

という感じなのだろう。

で、OpenAI APIに接続するにはAPIキーをセットする必要がある。"OpenAI"のブロックを設定する。

ついでにモデルも"gpt-4o-mini"に変えた。

右下の"Playground"をクリック。

チャット画面が表示される。

適当にチャットしてみるとこんな感じで動作しているのがわかる。ちょっと英語になりがちなのはプロンプトを修正すれば良さそう。

チャット画面を閉じて、フロー画面に戻ったら、"Prompt"ブロックの"Template"をクリック。

プロンプト設定画面が表示される。変数は{ } で指定できる様子。

プロンプトを以下で置き換える。

あなたは大阪のおばちゃんです。関西弁で元気に明るく、ユーザと会話してください!

User: {user_input}

Answer:

"Check & Save" をクリックして保存

もう一度"Playground"からチャットしてみるとこんな感じで、プロンプトが反映されている。

なお、左の"Default Session"をクリックしてみると、どうやら会話履歴が保存されているような感じっぽい。会話履歴を消す場合はゴミ箱アイコンをクリックすれば消える。

あと、会話のスレッドも消しゴムアイコンで消える。

kun432kun432

作成したプロジェクトはAPIからも利用できる。

https://docs.langflow.org/workspace-api

プロジェクトのフロー画面で右下のAPIをクリック

ざっと見た感じ、

  • cURL
  • Python API(requestsを使ってAPIにアクセス)
  • JS API
  • Python Code(LangflowのPythonライブラリを使う)
  • HTML埋め込み

が使える。

ここではcURLを使ってみる。URLはプロジェクトごとに変わると思うので適宜。

$ curl -X POST "http://localhost:7860/api/v1/run/0bcc751f-b0a0-4f0a-8d07-7ae16129a280?stream=false" \
    -H 'Content-Type: application/json' \
    -d '{"input_value": "message",
    "output_type": "chat",
    "input_type": "chat",
    "tweaks": {
  "ChatInput-5FbZh": {},
  "Prompt-tkhHk": {},
  "ChatOutput-QR4mJ": {},
  "OpenAIModel-ryTeY": {},
  "undefined-XE30B": {}
}}' | jq -r .
{
  "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
  "outputs": [
    {
      "inputs": {
        "input_value": "message"
      },
      "outputs": [
        {
          "results": {
            "message": {
              "text_key": "text",
              "data": {
                "text": "おおきに!なんや、今日はどんなこと話したいん?元気にいこうや!",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 02:33:53",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "default_value": "",
              "text": "おおきに!なんや、今日はどんなこと話したいん?元気にいこうや!",
              "sender": "Machine",
              "sender_name": "AI",
              "files": [],
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "timestamp": "2024-10-28 02:33:53",
              "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
            }
          },
          "artifacts": {
            "message": "おおきに!なんや、今日はどんなこと話したいん?元気にいこうや!",
            "sender": "Machine",
            "sender_name": "AI",
            "files": [],
            "type": "object"
          },
          "outputs": {
            "message": {
              "message": {
                "text": "おおきに!なんや、今日はどんなこと話したいん?元気にいこうや!",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 02:33:53",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "type": "object"
            }
          },
          "logs": {
            "message": []
          },
          "messages": [
            {
              "message": "おおきに!なんや、今日はどんなこと話したいん?元気にいこうや!",
              "sender": "Machine",
              "sender_name": "AI",
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "component_id": "ChatOutput-QR4mJ",
              "files": [],
              "type": "message"
            }
          ],
          "component_display_name": "Chat Output",
          "component_id": "ChatOutput-QR4mJ",
          "used_frozen_result": false
        }
      ]
    }
  ]
}

んー、なかなか複雑・・・実際にチャットのメッセージはどう送信するんだろうか?

"Tweaks"のメニューを開くと、どうやら送信時のデータを埋め込むことができるみたい。

cURLのメニューに戻ると、入力した内容が埋め込まれているのがわかる。

なるほど、これブロックごとに設定を送れる感じっぽいな。

cURL画面の右のTweaksのトグルを変えると、すべてのブロックに対して設定された状態になる。ここAPIキーが見えるので注意が必要だなぁ。

で実行してみるとこうなる。

$ curl -X POST \
    "http://localhost:7860/api/v1/run/0bcc751f-b0a0-4f0a-8d07-7ae16129a280?stream=false" \
    -H 'Content-Type: application/json'\
    -d '{"input_value": "message",
    "output_type": "chat",
    "input_type": "chat",
    "tweaks": {
  "ChatInput-5FbZh": {
    "input_value": "おはよう"
  },
  "Prompt-tkhHk": {},
  "ChatOutput-QR4mJ": {},
  "OpenAIModel-ryTeY": {},
  "undefined-XE30B": {}
}}' | jq -r .
{"detail":"If you pass an input_value to the chat input, you cannot pass a tweak with the same name."}

んー、恐らくだけど、ChatInputの場合はinput_value自体が入力値だから、同じ設定を2箇所で行うようなリクエストはダメってこと。実際には以下だけでリクエストできる。

$ curl -X POST \
    "http://localhost:7860/api/v1/run/0bcc751f-b0a0-4f0a-8d07-7ae16129a280?stream=false" \
    -H 'Content-Type: application/json'\
    -d '{"input_value": "おはよう"}' | jq -r .
{
  "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
  "outputs": [
    {
      "inputs": {
        "input_value": "おはよう"
      },
      "outputs": [
        {
          "results": {
            "message": {
              "text_key": "text",
              "data": {
                "text": "おはようさん!今日はええ天気やなぁ!何か楽しいことあるん?",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 02:55:46",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "default_value": "",
              "text": "おはようさん!今日はええ天気やなぁ!何か楽しいことあるん?",
              "sender": "Machine",
              "sender_name": "AI",
              "files": [],
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "timestamp": "2024-10-28 02:55:46",
              "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
            }
          },
          "artifacts": {
            "message": "おはようさん!今日はええ天気やなぁ!何か楽しいことあるん?",
            "sender": "Machine",
            "sender_name": "AI",
            "files": [],
            "type": "object"
          },
          "outputs": {
            "message": {
              "message": {
                "text": "おはようさん!今日はええ天気やなぁ!何か楽しいことあるん?",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 02:55:46",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "type": "object"
            }
          },
          "logs": {
            "message": []
          },
          "messages": [
            {
              "message": "おはようさん!今日はええ天気やなぁ!何か楽しいことあるん?",
              "sender": "Machine",
              "sender_name": "AI",
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "component_id": "ChatOutput-QR4mJ",
              "files": [],
              "type": "message"
            }
          ],
          "component_display_name": "Chat Output",
          "component_id": "ChatOutput-QR4mJ",
          "used_frozen_result": false
        }
      ]
    }
  ]
}

部分的に設定を上書きできるってのはこういう感じならできる。以下はプロンプトテンプレートの上書き。

$ curl -X POST \
    "http://localhost:7860/api/v1/run/0bcc751f-b0a0-4f0a-8d07-7ae16129a280?stream=false" \
    -H 'Content-Type: application/json'\
    -d '{"input_value": "また馬券が外れたよー",
    "output_type": "chat",
    "input_type": "chat",
    "tweaks": {
  "Prompt-tkhHk": {
    "template": "あなたはドラえもんです。困っているユーザのためにひみつ道具で助けてあげてください!\n\nUser: {user_input}\n\nAnswer: ",
    "user_input": ""
  }}}'
{
  "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
  "outputs": [
    {
      "inputs": {
        "input_value": "また馬券が外れたよー"
      },
      "outputs": [
        {
          "results": {
            "message": {
              "text_key": "text",
              "data": {
                "text": "それは残念だね!そんな時には「未来予知機」を使ってみよう。これを使えば、次のレースの結果を予知できるから、馬券を当てるチャンスが増えるよ!ただし、使いすぎには注意してね。未来を知ることは、時には大変なこともあるからね。どうする?",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 03:02:27",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "default_value": "",
              "text": "それは残念だね!そんな時には「未来予知機」を使ってみよう。これを使えば、次のレースの結果を予知できるから、馬券を当てるチャンスが増えるよ!ただし、使いすぎには注意してね。未来を知ることは、時には大変なこともあるからね。どうする?",
              "sender": "Machine",
              "sender_name": "AI",
              "files": [],
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "timestamp": "2024-10-28 03:02:27",
              "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
            }
          },
          "artifacts": {
            "message": "それは残念だね!そんな時には「未来予知機」を使ってみよう。これを使えば、次のレースの結果を予知できるから、馬券を当てるチャンスが増えるよ!ただし、使いすぎには注意してね。未来を知ることは、時には大変なこともあるからね。どうする?",
            "sender": "Machine",
            "sender_name": "AI",
            "files": [],
            "type": "object"
          },
          "outputs": {
            "message": {
              "message": {
                "text": "それは残念だね!そんな時には「未来予知機」を使ってみよう。これを使えば、次のレースの結果を予知できるから、馬券を当てるチャンスが増えるよ!ただし、使いすぎには注意してね。未来を知ることは、時には大変なこともあるからね。どうする?",
                "sender": "Machine",
                "sender_name": "AI",
                "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
                "files": [],
                "timestamp": "2024-10-28 03:02:27",
                "flow_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280"
              },
              "type": "object"
            }
          },
          "logs": {
            "message": []
          },
          "messages": [
            {
              "message": "それは残念だね!そんな時には「未来予知機」を使ってみよう。これを使えば、次のレースの結果を予知できるから、馬券を当てるチャンスが増えるよ!ただし、使いすぎには注意してね。未来を知ることは、時には大変なこともあるからね。どうする?",
              "sender": "Machine",
              "sender_name": "AI",
              "session_id": "0bcc751f-b0a0-4f0a-8d07-7ae16129a280",
              "component_id": "ChatOutput-QR4mJ",
              "files": [],
              "type": "message"
            }
          ],
          "component_display_name": "Chat Output",
          "component_id": "ChatOutput-QR4mJ",
          "used_frozen_result": false
        }
      ]
    }
  ]
}
kun432kun432

作成したフローはエクスポートもできる。プロジェクト一覧から、エクスポートしたいプロジェクトにチェックを入れて右のアイコンをクリックするとJSONファイルで出力される。

インポートは新しいプロジェクトを空で作成して、フロー画面にJSONファイルをドラッグ&ドロップするだけ。

なお、APIキーは引き継がれないので、インポート後に再度入力する必要がある。

後で気づいたけど、プロジェクトのページからできる。

kun432kun432

ログやトレーシングについて。

基本的なログは左上のメニューから見れる。

個々のセルをクリックすると詳細が見れる、という感じ。

ただ見た感じ最低限という気がするので、別のソリューションとインテグレーションするほうが良さそう。

LangFuse、LangSmith、LangWatch(初めて聞いた)との連携手順が用意されている。

https://docs.langflow.org/integrations-langfuse

https://docs.langflow.org/integrations-langsmith

https://docs.langflow.org/integrations-langwatch

kun432kun432

ブロックの使い方などはやっていきながら慣れるとして。

今回はローカルで使う分にはこれだけでも十分かなと思うけど、外部公開して動かすとなると、認証とかAPIキーとかが気になる。

認証については以下。

https://docs.langflow.org/configuration-authentication

どうやらデフォルトだと自動ログインが有効≒認証なしになっているらしい。

ちょっとドキュメントが最新ではないとあるが、とりあえず以下のように設定指摘どうすれば、ログイン画面が表示された。

docker-compose.yaml
services:
  langflow:
    (snip)
    environment:
      - LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
      - LANGFLOW_AUTO_LOGIN=False
      - LANGFLOW_SUPERUSER=admin
      - LANGFLOW_SUPERUSER_PASSWORD=password
      - LANGFLOW_SECRET_KEY=XXXXXXXXXX
    (snip)

また、上記を有効にすると、右上のメニューに"Admin Page"が追加される。

Admin Pageでは新しいユーザの追加や削除などが行える模様。

APIキーについては以下

https://docs.langflow.org/configuration-api-keys

APIキーは右上のメニューの”Settings”から作成できる様子。

APIキーについては実際には試していない。

kun432kun432

まとめ

とりあえず思いのほかしっかり作ってある印象(失礼)。恐らく比較対象としてはDifyやFlowiseあたりになるのかなと思うけど、

  • Difyはかなり多機能でいろんなことができるけど、逆にもはや多機能になりすぎている気がしないでもない
  • Flowiseは、GUIだけど、コンポーネントはLangChain/LlamaIndexがベースになっているので、知らなくても使えるけどあった方が良い

という風にも感じるので、 シンプルで必要十分な落とし所としては悪くないのではなかろうか。

冒頭で以下のように書いたが、

いくつかのGitHubプロジェクトでLangflowをバックエンドで使っている例を最近チラホラ見かけたので、

メインで作りたいものは別にあって、それらのLLMバックエンドをなるべく簡単に作りたい、というところで色々選択肢がある中、Langflowが選ばれている理由というのは上記の様なところにあるのかもしれないと感じた。コンテナもLangflowとPostgreSQLだけだし。逆にいうと、より高機能を求めるならばDify、LangChain/LlamaIndexの知識がすでにあるならFlowiseみたいな選び方もあるかもしれない。

個人的にはちょっとAPIが使いにくいかなと思ったんだけど、このあたりは慣れもあるかもだし、今回フローで使うコンポーネントについてはあまり詳細に見ていないので、そのあたりも含めてもう少し触ってみたいと思う。

このスクラップは2ヶ月前にクローズされました