Closed7

LLMアプリにhuman-in-the-loopを追加する「HumanLayer」を試す

kun432kun432

GitHubレポジトリ

https://github.com/humanlayer/humanlayer

HumanLayer

HumanLayer: AIエージェントがツールベースおよび非同期ワークフローで人間とコミュニケーションできるようにするPythonツールキット。人間をループに組み込むことで、エージェントツールはより強力で意味のあるツールコールやタスクへのアクセスが可能になります。

あなたのLLM(OpenAI、Llama、Claudeなど)やフレームワーク(LangChain、CrewAIなど)を持ち込み、AIエージェントに安全に世界へのアクセスを提供しましょう。

なぜHumanLayerなのか?

機能やツールは、エージェントワークフローの重要な要素です。これらは、LLM(大規模言語モデル)が外部の世界と意味のある形でやり取りし、広範囲にわたる重要な作業を自動化することを可能にします。正確で正しい機能呼び出しは、AIエージェントがアポイントメントを予約したり、顧客とやり取りしたり、請求情報を管理したり、コードを書いて実行するなど、意味のある作業を行う上で欠かせません。


refered from From https://louis-dupont.medium.com/transforming-software-interactions-with-tool-calling-and-llms-dc39185247e9

しかし、LLM(大規模言語モデル)に提供できる最も有用な機能は、同時に最もリスクが高いものでもあります。SQLデータベースを常にチューニングし、リファクタリングするAIデータベース管理者の価値を想像することはできますが、大半のチームはLLMに本番データベースに対して任意のSQLステートメントを実行するアクセス権を与えることはないでしょう(実際、人間にもそのような権限をほとんど与えないことが多いです)。つまり:

最先端のエージェント的な推論やプロンプトルーティングがあっても、人間の監視なしにLLMに高リスクの機能を与えるには信頼性が十分ではありません。

「高リスク」が意味する範囲を明確にするために、いくつかの例を挙げます:

  • 低リスク: 公開データへの読み取りアクセス(例: Wikipediaの検索、公開APIやデータセットへのアクセス)
  • 低リスク: エージェント作成者とのコミュニケーション(例: エンジニアがエージェントに進捗状況を伝えるためにプライベートなSlackメッセージを送信する権限を与える)
  • 中リスク: 非公開データへの読み取りアクセス(例: メールの読み取り、カレンダーへのアクセス、CRMへのクエリ)
  • 中リスク: 厳格なルールに基づいたコミュニケーション(例: 特定のハードコーディングされたメールテンプレートに基づいて送信する)
  • 高リスク: 自分や会社を代表してコミュニケーションを行う(例: メールの送信、Slackへの投稿、ソーシャルメディアやブログのコンテンツを公開する)
  • 高リスク: 非公開データへの書き込みアクセス(例: CRMレコードの更新、機能トグルの変更、請求情報の更新)


refered from https://github.com/humanlayer/humanlayer and translated by kun432

高リスクの機能は、最も価値があり、人間のワークフローを自動化することで最大の効果を約束するものです。しかし、これらの機能において「90%の正確性」は許容されません。今日のLLMが持つ「幻覚」や、明らかにAI生成である低品質なテキストを作り出す傾向は、信頼性にさらなる影響を与えます。チームがこれらのツールを高品質な入力で確実かつ安全に呼び出せるようになれば、その分早く大きな利益を得ることができるでしょう。

HumanLayerは、高リスクの機能呼び出しにおいて、人間の監視を確実に保証するための一連のツールを提供します。たとえLLMがミスをしたり幻覚を生じさせたりしても、HumanLayerがツールや機能自体に組み込まれているため、人間の関与が必ず保証されます。


refered from https://github.com/humanlayer/humanlayer and translated by kun432

HumanLayerは、高リスクの機能呼び出しにおいて、人間の監視を「確実に」保証するための一連のツールを提供します。

未来: 自律エージェントと「Outer Loop」

もっと詳しく読む: OpenAI's RealTime API is a step towards outer-loop agents

require_approvalhuman_as_toolの間に位置するHumanLayerは、次世代のAIエージェントである自律エージェントを強化するために設計されていますが、これは全体のパズルの一部に過ぎません。「次世代」を明確にするために、LLMアプリケーションの歴史を簡単にまとめることができます。

  • 第1世代: チャット - 人間が発端となる質問/応答インターフェース
  • 第2世代: エージェント型アシスタント - フレームワークがプロンプトルーティング、ツール呼び出し、思考の連鎖、コンテキストウィンドウ管理を駆動し、信頼性と機能性が大幅に向上しました。ほとんどのワークフローは、人間が「これがタスクです、実行してください」と一度だけ指示するか、または継続的なチャットインターフェースで開始されます。
  • 第3世代: 自律エージェント - もはや人間が開始するのではなく、エージェントは「Outer Loop」に存在し、さまざまなツールや機能を駆使して自分の目標に向かって進みます。人間とエージェントのコミュニケーションは、エージェントが開始するものになります(人間が開始するのではなく)。


referred from https://github.com/humanlayer/humanlayer

第3世代の自律エージェントは、さまざまなタスクに対して人間からのインプットを求める手段が必要になります。これらのエージェントが実際に有用な作業を行うためには、デリケートな操作に対して人間の監視が必要です。

これらのエージェントは、チャット、メール、SMSなど、さまざまなチャネルを通じて一人以上の人間に連絡を取る手段が必要となります。

初期のバージョンでは、技術的には「人間が開始する」形式(例えば、cronなどで定期的に開始される)かもしれませんが、最も優れたエージェントは、自分自身でスケジューリングやコスト管理を行うようになるでしょう。このためには、コストの検査を行うためのツールキットや、sleep_untilのような機能が必要です。また、これらのエージェントは、ツールの呼び出しが数時間や数日かかる場合にも、エージェントワークフローを耐久的にシリアライズして再開できるオーケストレーションフレームワークで実行する必要があります。これらのフレームワークは、「マネージャーLLM」によるコンテキストウィンドウの管理をサポートし、エージェントが特定のタスクや役割を処理するためにサブチェーンをフォークできるようにする必要があります。

これらのouter-loopエージェントの使用例として、LinkedInの受信ボックスアシスタント顧客のオンボーディングアシスタントが挙げられますが、これはほんの始まりに過ぎません。

主な機能

  • 関数呼び出しに対する人間の承認が必要: @hl.require_approval()デコレーターは、特定の関数呼び出しを人間に確認するまでブロックします。拒否された場合、そのフィードバックがLLMに返されます。
  • ツールとしての人間: 汎用のhl.human_as_tool()は、人間に連絡し、回答、アドバイス、またはフィードバックを受け取るために使用できます。
  • マルチチャネル連絡: Slack、Email、Discordなど、複数のチャネルを通じて人間に連絡し、応答を収集します。
  • 詳細なルーティング: 承認を特定のチームや個人にルーティングすることが可能です。
  • 任意のLLMとフレームワークに対応: HumanLayerはツール層で実装されているため、どのLLMでも、ツール呼び出しをサポートするすべての主要なオーケストレーションフレームワークと互換性があります。
kun432kun432

LangChainのサンプル

パッケージインストール

!pip install humanlayer langchain langchain-openai langgraph
!pip freeze | egrep -i "humanlayer|langchain|langgraph"
humanlayer==0.5.8
langchain==0.3.4
langchain-core==0.3.12
langchain-openai==0.2.3
langchain-text-splitters==0.3.0
langgraph==0.2.39
langgraph-checkpoint==2.0.1
langgraph-sdk==0.1.33

OpenAI APIキーをセット

import os
from google.colab import userdata

os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')

まず、ツールを持ったエージェントのサンプルコード。まだHumanLayerは使っていない。なお、元のサンプルコードはLangChainエージェントの呼び出し方が恐らく古いように思えたので、LangChainのチュートリアルのコードに置き換えてみた。(とはいえ、LangChainでエージェントをやる場合、今はLangGraphで書くべきではあるのだけども)

from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver


@tool
def add(x: int, y: int) -> int:
    """2つの整数値を足し算する"""
    return x + y


@tool
def multiply(x: int, y: int) -> int:
    """2つの整数値を掛け算する"""
    return x * y


tools = [add, multiply]

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = MemorySaver()

agent_executor = create_react_agent(llm, tools, checkpointer=memory)
config = {"configurable": {"thread_id": "abc123"}}

response = agent_executor.invoke({"messages": [HumanMessage(content="(10 + 2) * 5 を計算して")]}, config)

for m in response["messages"]:
    print(m.pretty_print())

結果

================================ Human Message =================================

(10 + 2) * 5 を計算して
None
================================== Ai Message ==================================
Tool Calls:
  add (call_OpvvcMJx5j9BufJWLryZbpnc)
 Call ID: call_OpvvcMJx5j9BufJWLryZbpnc
  Args:
    x: 10
    y: 2
  multiply (call_xynPVxF9B046WL0wwyoiiC1S)
 Call ID: call_xynPVxF9B046WL0wwyoiiC1S
  Args:
    x: 12
    y: 5
None
================================= Tool Message =================================
Name: add

12
None
================================= Tool Message =================================
Name: multiply

60
None
================================== Ai Message ==================================

(10 + 2) * 5 の計算結果は 60 です。
None

では、これにHumanLayerを追加して、掛け算のときはユーザの許可が必要ということにする。

from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver

# HumanLayerをインポートして初期化
from humanlayer import HumanLayer
hl = HumanLayer()


@tool
def add(x: int, y: int) -> int:
    """2つの整数値を足し算する"""
    return x + y


@tool
@hl.require_approval()  # 掛け算の場合に許可を求める
def multiply(x: int, y: int) -> int:
    """2つの整数値を掛け算する"""
    return x * y


tools = [add, multiply]

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = MemorySaver()

agent_executor = create_react_agent(llm, tools, checkpointer=memory)
config = {"configurable": {"thread_id": "abc123"}}

response = agent_executor.invoke({"messages": [HumanMessage(content="(10 + 2) * 5 を計算して")]}, config)

for m in response["messages"]:
    print(m.pretty_print())

実行すると、掛け算実行前に確認が求められる。ENTERで許可する。

以降の処理が行われて回答が得られる。

ツールで使用する関数を@hl.require_approval()デコレータでラップするだけ。確かに簡単。

kun432kun432

OpenAI Python SDKの場合

上と同じことをOpenAI Python SDKで。

import json

from openai import OpenAI

from humanlayer import HumanLayer
from humanlayer.core.models import FunctionCallSpec, FunctionCallStatus

hl = HumanLayer()

def add(x: int, y: int) -> int:
    """2つの整数値を足し算する"""
    return x + y


@hl.require_approval()
def multiply(x: int, y: int) -> int:
    """2つの整数値を掛け算する"""
    return x * y


tools = [
    {
        "type": "function",
        "function": {
            "name": "add",
            "description": "2つの整数値を足し算する",
            "parameters": {
                "type": "object",
                "properties": {
                    "x": {"type": "integer"},
                    "y": {"type": "integer"},
                },
                "required": ["x", "y"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "multiply",
            "description": "2つの整数値を掛け算する",
            "parameters": {
                "type": "object",
                "properties": {
                    "x": {"type": "integer"},
                    "y": {"type": "integer"},
                },
                "required": ["x", "y"],
            },
        },
    },
]

tools_mapping = {
    "add": add,
    "multiply": multiply,
}

client = OpenAI()

messages = [
    {
        "role": "system",
        "content": "あなたは親切な日本語のアシスタントです。"
    }
]

while True:

    user_input = input("USER: ")
    if user_input.lower() == 'quit':
        print("チャットを終了します。さようなら。")
        break
    
    messages.append({"role": "user", "content": user_input})
                
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    
    while response.choices[0].finish_reason != "stop":
        response_message = response.choices[0].message
        tool_calls = response_message.tool_calls

        if tool_calls:
            messages.append(response_message)

            print("=== ツール呼び出し発生 ===")
            print([(tool_call.function.name, tool_call.function.arguments) for tool_call in tool_calls])
            print("=======================")

            for tool_call in tool_calls:
                function_name = tool_call.function.name
                function_to_call = tools_mapping[function_name]
                function_args = json.loads(tool_call.function.arguments)

                function_response_json: str
                try:
                    function_response = function_to_call(**function_args)
                    function_response_json = json.dumps(function_response)
                except Exception as e:
                    function_response_json = json.dumps(
                        {
                            "error": str(e),
                        }
                    )
                
                print(f"=== ツール実行結果: {function_name} ===")
                print(function_response_json)
                print("=======================")
                
                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response_json,
                    }
                )

        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools,
            tool_choice="auto",
        )

    assistant_response = response.choices[0].message.content
    messages.append({"role": "assistant", "content": assistant_response})   
    print(f"ASSISTANT: {assistant_response}")

実行結果

足し算は特に確認なく実行されるけど、掛け算の場合だけ確認が求められて、確認後に実行されているのがわかる。

kun432kun432

なお、承認せずにこういうフィードバックを返すこともできる。

kun432kun432

あともう一つ

https://github.com/humanlayer/humanlayer/tree/main/docs#human-as-tool

承認自体をツール化するhuman_as_tool()というメソッドがある。ここは実際に試すまではしてないので紹介だけ。

サンプルで用意されているのは、slackへの確認をツールとして定義することで、複数人の承認をもらうという感じになってるぽい。

from humanlayer import
ContactChannel, HumanLayer, SlackContactChannel

hl = HumanLayer()

customer_success_direct_message = ContactChannel(
  slack=SlackContactChannel(
    channel_or_user_id="U01JQX9KZZ",
    context_about_channel_or_user="a DM with the head of customer success",
  )
)

ceo_direct_message = ContactChannel(
  slack=SlackContactChannel(
    channel_or_user_id="U09J9CV8ZX",
    context_about_channel_or_user="a DM with the CEO",
  )
)

# made up function, use whatever framework you want
run_llm_task(
  prompt="Determine the top objectives for the customer success org and send them to the CEO",
  tools=[
    hl.human_as_tool(customer_success_direct_message),
    hl.human_as_tool(ceo_direct_message)
  ],
  llm=OpenAI(model="gpt-4o")
)

Slackについてはなんかモジュールが用意されているっぽい。この辺にコードがある。

https://github.com/humanlayer/humanlayer/blob/main/humanlayer/core/models.py

なるほど、Slack、SMS、WhatsAppあたりには対応してそう。

kun432kun432

まとめ

最近のエージェントフレームワークの多くは、Human-in-the-loop機能を備えているものが多くなってきていると思うが、一方で、エージェントの自律的なループに人間の関与を含めるということは、ループ内のフローや状態の制御が必要になるし、人間を関与させるインタフェースについても考えないといけないと思うので、難易度は高いと思う。既存フレームワークを使うにしても習熟コストはそれなりにありそうだし、自前で実装する場合はさらにハードルが高くなると思う。

HumanLayerを使うと、この部分をシンプルに実装できるので、すでに作成済みのLLMアプリがある場合や使用しているフレームワークにHuman-in-the-loopがない場合でも追加しやすいのではないかと思う。

以下のLangChainのサンプルには

  • Slack上で承認が求められて、承認するとDMメッセージが送信される
  • LinkedInの受信ボックスに来たメッセージに自動で返信、顧客の不満が含まれているものがあればCEOにエスカレ、ということに対して、それぞれHumanLayerで承認プロセスを実装

などの例が用意されている。このあたりは具体的なユースケースとしてありそうだと思うので、また試してみたい。

https://github.com/humanlayer/humanlayer/tree/main/examples/langchain

あとはクラウドサービスもやっているらしく、こちらを使うと、より柔軟な承認フローの制御が可能になったりする模様。

https://www.humanlayer.dev/

ニッチなところではあるが、お手軽に実現できるというところで、興味深く感じた。デコレータの使い方としても、とても納得できる。

ただし、エージェントフレームワークでHuman-in-the-loopはもはや標準機能として必須になりつつある中で、このニッチな部分だけでどこまでニーズがあるのかなぁ?というところではある。LangChainなら最初からLangGraph使えばいいし、CrewAIとかもデフォルトで機能としてはあるわけだし。

インテグレーションを増やしていくとかはいいかもね。

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