0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangGraphベースのAIエージェントをランタイム中に組み上げられるようにした

Last updated at Posted at 2024-10-10

経緯

  • もともと、プライベート運用しているslackにLangChainを用いてOpenAIへの問い合わせを行うBotを作成していた。
  • Slackでチャンネル別に「日本語の質問を英語へ翻訳→AIに英語で質問に答えてもらう→日本語に翻訳する」というような線形のコールチェーンをモーダルから組めるようにしていたが、ToolCallingをさせてみたくなる。
  • LangGraphの存在を知り、How To Guideを一通り写経する。
  • Slack上でLangGraphのAIエージェントを組めるようにするには、ノードとエッジを切り離して外部データからStateGraphをコンパイル出来るようにしなければならない。
  • 私が検索した範囲では形になっている類似のアイデアが無かった。
  • → じゃあ、作ってみるか。

公開しているリポジトリ

kenkenpa 本体
https://github.com/onihei0910/kenkenpa

kenkenpaの使用例
https://github.com/onihei0910/kenkenpa_example

使用例

kenkenpaに書いてあるとおりですが、LangGraph公式のReact-Agentをこのライブラリで実装すると次のようになります。

アプリケーションへの実装手順は以下の通りです。

  1. ノードはLangGraphをアプリケーションと同じように実装する。
  2. ノードを生成するファクトリー関数を実装する。
  3. ConditionalEdgeに相当する関数は、直接次のノードを指定するのではなく、TrueかFalseかを返す評価関数として実装する。
  4. StateGraphを組み上げるための辞書データを定義しておく。
  5. kenkenpaを使用してコンパイル可能なStateGraphを生成する。

実装例は以下の通りです。

Toolノードは普段通り実装します。

from langchain_core.tools import tool

@tool
def search(query: str):
    """Call to surf the web."""

    if "sf" in query.lower() or "san francisco" in query.lower():
        return "It's 60 degrees and foggy."
    return "It's 90 degrees and sunny."

Toolノードのファクトリー関数を定義します。

from langgraph.prebuilt import ToolNode

tools = {
    "search_function":search,
    }

def gen_tool_node(factory_parameter,flow_parameter):
    functions = factory_parameter['functions']

    tool_functions = []
    for function in functions:
        tool_functions.append(tools[function])

    tool_node = ToolNode(tool_functions)
    return tool_node

agentノードのファクトリー関数を定義します。

from langchain_openai import ChatOpenAI

def gen_agent(factory_parameter,flow_parameter):
    functions = factory_parameter['functions']

    tool_functions = []
    for function in functions:
        tool_functions.append(tools[function])

    # LLMの設定
    model = ChatOpenAI(
        model="gpt-4o-mini"
    )

    model = model.bind_tools(tool_functions)

    # Define the function that calls the model
    def call_model(state):
        messages = state['messages']
        response = model.invoke(messages)
        # We return a list, because this will get added to the existing list
        return {"messages": [response]}

    return call_model

should_continueの代わりに最終メッセージがtool_callsかを評価する関数を定義します。

def is_tool_message(state, config, **kwargs):
    """最後のメッセージがtool_callsかを評価します。"""
    messages = state['messages']
    last_message = messages[-1]
    if last_message.tool_calls:
        return True
    return False

StateGraphを表す構造化データを作成します。

graph_settings = {
    "graph_type":"stategraph",
    "flow_parameter":{
        "name":"React-Agent",
        "state" : [
            {
                "field_name": "messages", # state name
                "type": "list", # type(*1)
                "reducer":"add_messages" # reducer(*2)
            },
        ],
    },
    "flows":[
        { # agent node(*3)
            "graph_type":"node",
            "flow_parameter":{
                "name":"agent",
                "factory":"agent_node_factory",
            },
            "factory_parameter" : {
                "functions":[
                    "search_function",
                ],
            },
        },
        { # tools node(*3)
            "graph_type":"node",
            "flow_parameter":{
                "name":"tools",
                "factory":"tool_node_factory",
            },
            "factory_parameter":{
                "functions":[
                    "search_function",
                ],
            },
        },
        {# edge START -> agent
            "graph_type":"edge",
            "flow_parameter":{
                "start_key":"START",
                "end_key":"agent"
            },
        },
        {# coditional edge 
            "graph_type":"configurable_conditional_edge",
            "flow_parameter":{
                "start_key":"agent",
                "conditions":[
                    {
                        # is_tool_messageの結果がTrueの時にtools nodeに遷移します。
                        "expression": {
                            "eq": [{"type": "function", "name": "is_tool_message_function"}, True], # *4
                        },
                        "result": "tools"
                    },
                    {"default": "END"} 
                ]
            },
        },
        {# edge tools -> agent
            "graph_type":"edge",
            "flow_parameter":{
                "start_key":"tools",
                "end_key":"agent"
            },
        },
    ]
}

graph_settingsからStateGraphBuilderを生成します。

from langchain_core.messages import HumanMessage
from langgraph.graph import  add_messages
from langgraph.checkpoint.memory import MemorySaver

from kenkenpa.builder import StateGraphBuilder

# StateGraphBuilderのインスタンス化
stategraph_builder = StateGraphBuilder(graph_settings)

# コンパイル可能なStateGraphを生成するにはいくつかの準備が必要です。
# *1. 使用する型を登録します。(listは予約されていますが、説明のために記述しています。)
#stategraph_builder.add_type("list",list)

# *2. 使用するreducerを登録します。
stategraph_builder.add_reducer("add_messages",add_messages)

# *3. Node Factoryを登録します。
stategraph_builder.add_node_factory("agent_node_factory",gen_agent)
stategraph_builder.add_node_factory("tool_node_factory",gen_tool_node)

# *4. 評価関数も登録します。
stategraph_builder.add_evaluete_function("is_tool_message_function", is_tool_message,)

# gen_stategraph()メソッドでコンパイル可能なStateGraphを取得できます。
stategraph = stategraph_builder.gen_stategraph()

# 以降はLangGraphの一般的な使用方法に従ってコードを記述します。
memory = MemorySaver()
app =  stategraph.compile(checkpointer=memory,debug=False)

print(f"\ngraph")
app.get_graph().print_ascii()
final_state = app.invoke(
    {"messages": [HumanMessage(content="what is the weather in sf")]},
    config={"configurable": {"thread_id": 42}}
)
print(final_state["messages"][-1].content)

実行結果

        +-----------+         
        | __start__ |         
        +-----------+         
              *               
              *               
              *               
          +-------+           
          | agent |           
          +-------+           
         .         .          
       ..           ..        
      .               .       
+-------+         +---------+ 
| tools |         | __end__ | 
+-------+         +---------+ 

The current weather in San Francisco is 60 degrees and foggy.

工夫している点

Stateの動的生成

使用例で示したこの部分。TypedDict形式の型を動的に組み上げられるようにしてあります。

graph_settings = {
    "graph_type":"stategraph",
    "flow_parameter":{
        "name":"React-Agent",
        "state" : [
            {
                "field_name": "messages", # state name
                "type": "list", # type(*1)
                "reducer":"add_messages" # reducer(*2)
            },
        ],
    },

条件付きエッジの動的生成

この部分。条件付きエッジをコードに焼き付けるとSlackからエージェントを組むメリットが大幅に薄れるので、辞書データで定義できるようにしています。

        {# coditional edge 
            "graph_type":"configurable_conditional_edge",
            "flow_parameter":{
                "start_key":"agent",
                "conditions":[
                    {
                        # is_tool_messageの結果がTrueの時にtools nodeに遷移します。
                        "expression": {
                            "eq": [{"type": "function", "name": "is_tool_message_function"}, True], # *4
                        },
                        "result": "tools"
                    },
                    {"default": "END"} 
                ]
            },
        },

この他のケース

このほかのケースはkenkenpa_exampleに投稿しています。

この実装に至るまでに読んだ書籍

ChatGPT/LangChainによるチャットシステム構築[実践]入門
つくりながら学ぶ!生成AIアプリ&エージェント開発入門

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?