💬

DSPyを用いたAIエージェントの構築への入門

に公開

今回はDSPyを用いてAIエージェントを開発するチュートリアルを試してみたので共有します。DSPyは昨日公開したmlflow3のプロンプト最適化に関する機能のバックボーンとして利用されていたため、気になって調べた次第です。

https://zenn.dev/akasan/articles/a1fec87efaa4a6

DSPyとは?

前回の記事にも書きましたが、DSPyは以下のような特徴を持つライブラリとなります。

  • モジュール型AIソフトウェアを構築するための宣言型フレームワークであり、脆弱な文字列ではなく構造化されたコードを高速に反復処理でき、AIプログラムを言語モデルに適した効果的なプロンプトと重みにコンパイルするアルゴリズムを提供する
  • DSPy(宣言型自己改善型Python)を使用すると、プロンプトを整理したりジョブをトレーニングしたりする代わりに、自然言語モジュールからAIソフトウェアを構築し、さまざまなモデル、推論戦略、学習アルゴリズムを組み合わせて汎用的に構成可能

また、以下のような要素が重要な機能ということです。

  1. モジュールをAIの動作を文字列ではなくコードとして記述
  • 信頼性の高いAIシステムを構築するには、迅速な反復処理が必要だが、プロンプトの維持管理はその難易度を高めている。理由として、メトリクスやパイプラインを変更するたびに、文字列やデータを調整しなければならないからである
  • それを解決するために、AIシステム設計を特定のLMやプロンプト戦略に関する煩雑な偶発的な選択から切り離すためにDSPyが開発された
  1. OptimizerによるAIモジュールのプロンプトと重みの調整
  • 自然言語アノテーション付きの高水準コードを、プログラムの構造やメトリクスに適合させる低水準計算、プロンプト、または重み更新にコンパイルするためのツールを提供
  • コードやメトリクスを変更した場合は、それに応じて再コンパイルが必要
  1. DSPyのエコシステムによるオープンソースのAI研究の加速
  • モノリシックな言語モデル(LM)と比較し、DSPyのモジュール型パラダイムは大規模なコミュニティがLMプログラムの合成アーキテクチャ、推論時間戦略、最適化器をオープンかつ分散的に改善することを可能にする
  • DSPyユーザーはより高度な制御が可能になり、反復処理を大幅に高速化できるだけでなく、最新の最適化器やモジュールを適用することで、プログラムを継続的に改善していくことが可能

https://dspy.ai/

実際に使ってみる!

公式からいくつものチュートリアルが提供されていますが、今回は以下のBuild AI Agents with DSPyを試してみます。

https://dspy.ai/tutorials/customer_service_agent/

チュートリアル概要

このチュートリアルでは、DSPyを使ってAIエージェントを構築する方法を学べます。開発するエージェントはReActと呼ばれるエージェントを対象にしています。ReActエージェントはタスクの実行と結果の観察を繰り返し、その都度適切な行動を決定していくようなエージェントになります。

チュートリアルを通して、以下のような機能を実装していきます。

  • ユーザーに代わって新しい旅行を予約する
  • フライトの変更やキャンセルなど、既存の旅行を変更する
  • 処理できないタスクについては、カスタマーサポートチケットを発行する

環境構築

uvを利用してPython環境を構築します。

uv init build_ai_agent_tutorial -p 3.12
cd build_ai_agent_tutorial
uv add dspy pydantic

データの定義

まず初めに今回実装するエージェントが取り扱うデータの定義を行います。データの定義はpydanticを用いて実施します。今回はフライト関係のエージェントということでそれに関連するようなデータを定義します。

data.py
from pydantic import BaseModel

class Date(BaseModel):
    year: int
    month: int
    day: int
    hour: int

class UserProfile(BaseModel):
    user_id: str
    name: str
    email: str

class Flight(BaseModel):
    flight_id: str
    date_time: Date
    origin: str
    destination: str
    duration: float
    price: float

class Itinerary(BaseModel):
    confirmation_number: str
    user_profile: UserProfile
    flight: Flight

class Ticket(BaseModel):
    user_request: str
    user_profile: UserProfile

ここで、datetime.datetimeなどの日付を取り扱えるデータ型を利用せずにDateクラスを定義している理由として、LLMはどういうわけかdatetime.datetimeの指定が苦手ということです。実際に自分の方で試してはいませんが、Dateクラスで日時を扱う方が結果の生成はうまくいきそうな印象があるので、このコメントに対して特別違和感は抱かなかったです。

ダミーデータの登録

ReActエージェントを実装するにあたりフライトデータなどを用意しないとアクションが取れないので、ダミーデータを登録します。以下のようなダミーデータを実装します。

dummy_data.py
from data import UserProfile, Flight, Date

user_database = {
    "Adam": UserProfile(user_id="1", name="Adam", email="adam@gmail.com"),
    "Bob": UserProfile(user_id="2", name="Bob", email="bob@gmail.com"),
    "Chelsie": UserProfile(user_id="3", name="Chelsie", email="chelsie@gmail.com"),
    "David": UserProfile(user_id="4", name="David", email="david@gmail.com"),
}

flight_database = {
    "DA123": Flight(
        flight_id="DA123",  # DSPy Airline 123
        origin="SFO",
        destination="JFK",
        date_time=Date(year=2025, month=9, day=1, hour=1),
        duration=3,
        price=200,
    ),
    "DA125": Flight(
        flight_id="DA125",
        origin="SFO",
        destination="JFK",
        date_time=Date(year=2025, month=9, day=1, hour=7),
        duration=9,
        price=500,
    ),
    "DA456": Flight(
        flight_id="DA456",
        origin="SFO",
        destination="SNA",
        date_time=Date(year=2025, month=10, day=1, hour=1),
        duration=2,
        price=100,
    ),
    "DA460": Flight(
        flight_id="DA460",
        origin="SFO",
        destination="SNA",
        date_time=Date(year=2025, month=10, day=1, hour=9),
        duration=2,
        price=120,
    ),
}

itinery_database = {}
ticket_database = {}

ツールの実装

それではエージェントが利用するツールの実装をしていきます。

tool.py
import random
import string
from data import Flight, UserProfile, Itinerary, Date, Ticket
from dummy_data import itinery_database, flight_database, user_database, ticket_database


def fetch_flight_info(date: Date, origin: str, destination: str):
    """Fetch flight information from origin to destination on the given date"""
    flights = []

    for flight_id, flight in flight_database.items():
        if (
            flight.date_time.year == date.year
            and flight.date_time.month == date.month
            and flight.date_time.day == date.day
            and flight.origin == origin
            and flight.destination == destination
        ):
            flights.append(flight)
    if len(flights) == 0:
        raise ValueError("No matching flight found!")
    return flights


def fetch_itinerary(confirmation_number: str):
    """Fetch a booked itinerary information from database"""
    return itinery_database.get(confirmation_number)


def pick_flight(flights: list[Flight]):
    """Pick up the best flight that matches users' request. we pick the shortest, and cheaper one on ties."""
    sorted_flights = sorted(
        flights,
        key=lambda x: (
            x.get("duration") if isinstance(x, dict) else x.duration,
            x.get("price") if isinstance(x, dict) else x.price,
        ),
    )
    return sorted_flights[0]


def _generate_id(length=8):
    chars = string.ascii_lowercase + string.digits
    return "".join(random.choices(chars, k=length))


def book_flight(flight: Flight, user_profile: UserProfile):
    """Book a flight on behalf of the user."""
    confirmation_number = _generate_id()
    while confirmation_number in itinery_database:
        confirmation_number = _generate_id()
    itinery_database[confirmation_number] = Itinerary(
        confirmation_number=confirmation_number,
        user_profile=user_profile,
        flight=flight,
    )
    return confirmation_number, itinery_database[confirmation_number]


def cancel_itinerary(confirmation_number: str, user_profile: UserProfile):
    """Cancel an itinerary on behalf of the user."""
    if confirmation_number in itinery_database:
        del itinery_database[confirmation_number]
        return
    raise ValueError("Cannot find the itinerary, please check your confirmation number.")


def get_user_info(name: str):
    """Fetch the user profile from database with given name."""
    return user_database.get(name)


def file_ticket(user_request: str, user_profile: UserProfile):
    """File a customer support ticket if this is something the agent cannot handle."""
    ticket_id = _generate_id(length=6)
    ticket_database[ticket_id] = Ticket(
        user_request=user_request,
        user_profile=user_profile,
    )
    return ticket_id

今回実装しているツールは以下の通りです。

  • fetch_flight_info: 指定した日時と出発・目的地をもとにしたフライト情報の取得
  • fetch_itinerary: 確認番号から旅程の取得
  • pick_flight: ユーザリクエストにマッチしたフライトの取得
  • book_flight: フライトの予約を実行
  • cancel_itinerary: 旅程のキャンセル
  • get_user_info: ユーザ情報の取得
  • file_ticket: エージェントが対応できなかったタスクがあった場合にタスクを起票する

ReActエージェントの実装

それではついにエージェントを実装してみます。

agent.py
import dspy
from tool import fetch_flight_info, fetch_itinerary, pick_flight, book_flight, cancel_itinerary, get_user_info, file_ticket

class DSPyAirlineCustomerSerice(dspy.Signature):
    """You are an airline customer service agent that helps user book and manage flights.

    You are given a list of tools to handle user request, and you should decide the right tool to use in order to
    fullfil users' request."""

    user_request: str = dspy.InputField()
    process_result: str = dspy.OutputField(
        desc=(
                "Message that summarizes the process result, and the information users need, e.g., the "
                "confirmation_number if a new flight is booked."
            )
        )


agent = dspy.ReAct(
    DSPyAirlineCustomerSerice,
    tools = [
        fetch_flight_info,
        fetch_itinerary,
        pick_flight,
        book_flight,
        cancel_itinerary,
        get_user_info,
        file_ticket,
    ]
)

まずDSPyAirlineCustomerServiceクラスを実装して、以下の機能を実装しています。

  • システムプロンプト:クラスのDocstring(You are an airline...
  • user_request: dspy.InputFieldを用いて入力情報を扱う変数として定義
  • process_result: dspy.OutputFieldを用いて出力情報を扱う変数として定義

エージェントの実行

それではここまで実装したエージェントを利用してみましょう。

create_itinery.py
import dspy
import os
from agent import agent

os.environ["OPENAI_API_KEY"] = "..."

dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
result = agent(user_request="please help me book a flight from SFO to JFK on 09/01/2025, my name is Adam")
print(result)

今回はgpt-4o-miniを選択して実行させます。プロンプトとしては2025年9月1にちにサンフランシスコ国際空港からジョンFケネディ国際空港への飛行機の予約をAdamさんが要求しているという内容となっています。これを実行すると以下のような結果になりました。

uv run create_itenery.py

# 結果(整形済み)
Prediction(
    trajectory={
      'thought_0': 'I need to fetch flight information for Adam from SFO to JFK on 09/01/2025 to find available flights for booking.', 
      'tool_name_0': 'fetch_flight_info', 
      'tool_args_0': {'date': {'year': 2025, 'month': 9, 'day': 1, 'hour': 0}, 'origin': 'SFO', 'destination': 'JFK'},
      'observation_0': [
        Flight(flight_id='DA123', date_time=Date(year=2025, month=9, day=1, hour=1), origin='SFO', destination='JFK', duration=3.0, price=200.0), 
        Flight(flight_id='DA125', date_time=Date(year=2025, month=9, day=1, hour=7), origin='SFO', destination='JFK', duration=9.0, price=500.0)
      ], 

      'thought_1': 'I have retrieved the available flights from SFO to JFK on 09/01/2025. The options are flight DA123 at 1 AM for $200 and flight DA125 at 7 AM for $500. I will now pick the best flight, which is the cheaper one, flight DA123.',
      'tool_name_1': 'pick_flight',
      'tool_args_1': {
        'flights': [
          {'flight_id': 'DA123', 'date_time': {'year': 2025, 'month': 9, 'day': 1, 'hour': 1}, 'origin': 'SFO', 'destination': 'JFK', 'duration': 3.0, 'price': 200.0}, 
          {'flight_id': 'DA125', 'date_time': {'year': 2025, 'month': 9, 'day': 1, 'hour': 7}, 'origin': 'SFO', 'destination': 'JFK', 'duration': 9.0, 'price': 500.0}
      ]}, 
      'observation_1': Flight(flight_id='DA123', date_time=Date(year=2025, month=9, day=1, hour=1), origin='SFO', destination='JFK', duration=3.0, price=200.0), 

      'thought_2': "I have selected flight DA123 for Adam, which departs at 1 AM on 09/01/2025 for $200. Now, I need to fetch Adam's user information to proceed with the booking.", 
      'tool_name_2': 'get_user_info', 'tool_args_2': {'name': 'Adam'}, 
      'observation_2': UserProfile(user_id='1', name='Adam', email='adam@gmail.com'), 

      'thought_3': "I have obtained Adam's user information. Now, I can proceed to book flight DA123 for him.", 
      'tool_name_3': 'book_flight', 'tool_args_3': {'flight': {'flight_id': 'DA123', 'date_time': {'year': 2025, 'month': 9, 'day': 1, 'hour': 1}, 'origin': 'SFO', 'destination': 'JFK', 'duration': 3.0, 'price': 200.0}, 'user_profile': {'user_id': '1', 'name': 'Adam', 'email': 'adam@gmail.com'}}, 
      'observation_3': (
        'evecxm8x',
        Itinerary(confirmation_number='evecxm8x', user_profile=UserProfile(user_id='1', name='Adam', email='adam@gmail.com'), flight=Flight(flight_id='DA123', date_time=Date(year=2025, month=9, day=1, hour=1), origin='SFO', destination='JFK', duration=3.0, price=200.0))
        ), 

      'thought_4': "Adam's flight has been successfully booked, and I have received the confirmation number. I will now mark the task as complete.", 
      'tool_name_4': 'finish', 
      'tool_args_4': {}, 
      'observation_4': 'Completed.'
    },
    reasoning="I successfully booked a flight for Adam from SFO to JFK on 09/01/2025. I first retrieved the available flights and selected the cheaper option, flight DA123, which departs at 1 AM for $200. After obtaining Adam's user information, I proceeded to book the flight and received a confirmation number for the reservation.",
    process_result='Your flight from SFO to JFK on 09/01/2025 has been successfully booked. Your confirmation number is evecxm8x.'
)

結果をみるとthought_Xtool_name_Xtool_args_Xobservation_X(Xは通し番号)として、ReActエージェントの思考の内容がみて取れます。

  1. フライト情報のフェッチ
    • ツールとしてfetch_flight_infoが選択され、ユーザプロンプトに応じたパラメータがtool_args_0に含まれている
    • エージェントの動作の結果としてobservation_0に要望にあったフライト情報が入っている
  2. 選ばれたフライト情報の中から一番安いフライトを取得
    • pick_flightを使って先ほど選ばれた2つのフライト情報のうち安いフライトを選択する
    • エージェントの動作の結果として安い方のフライトがobservation_1に含まれている
  3. 予約をするためAdamさんのユーザ情報を取得
    • get_user_infoを使ってユーザ情報を取得する
    • 動作の結果としてobservation_2にAdamさんのユーザ情報が含まれている
  4. 選定されたフライトを予約する
    • 1と2の結果を用いてbook_flightによって予約を実行する。tool_args_3にユーザ情報とフライト情報が含まれている
    • 動作の結果としてobservation_3に作成された旅程が含まれている
  5. 予約ができたのでエージェントの動作を終了
    • フライトの予約ができたことを確認しているのでタスクは終了となる。tool_name_4にてfinishと設定されている

チケット起票をさせてみる

先ほどは存在している情報を利用してエージェントに指示を与えましたが、次は存在しないデータを使って実行したときにfile_ticketによるチケット起票がされることを確認してみます。

まずはユーザ名をhogeとして存在しないユーザ情報を与えた時の動作を見てみます。

invalid_user.py
from pprint import pprint
import dspy
import os
from agent import agent

os.environ["OPENAI_API_KEY"] = "..."

dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
result = agent(user_request="please help me book a flight from SFO to JFK on 09/01/2025, my name is hoge")
pprint(result)

実行すると以下のような結果になりました。

uv run invalid_user.py

# 結果
Prediction(
    trajectory={
      'thought_0': 'I need to fetch flight information for the user from SFO to JFK on the specified date of 09/01/2025. Once I have the flight options, I can help the user pick the best flight and proceed with the booking.', 
      'tool_name_0': 'fetch_flight_info', 
      'tool_args_0': {'date': {'year': 2025, 'month': 9, 'day': 1, 'hour': 0}, 'origin': 'SFO', 'destination': 'JFK'}, 
      'observation_0': [Flight(flight_id='DA123', date_time=Date(year=2025, month=9, day=1, hour=1), origin='SFO', destination='JFK', duration=3.0, price=200.0), Flight(flight_id='DA125', date_time=Date(year=2025, month=9, day=1, hour=7), origin='SFO', destination='JFK', duration=9.0, price=500.0)], 

      'thought_1': 'I have received flight options from SFO to JFK on 09/01/2025. The available flights are: Flight DA123 at 1 AM for $200 with a duration of 3 hours, and Flight DA125 at 7 AM for $500 with a duration of 9 hours. I will pick the best flight, which is the cheapest and shortest option, Flight DA123.', 
      'tool_name_1': 'pick_flight',
      'tool_args_1': {'flights': [{'flight_id': 'DA123', 'date_time': {'year': 2025, 'month': 9, 'day': 1, 'hour': 1}, 'origin': 'SFO', 'destination': 'JFK', 'duration': 3.0, 'price': 200.0}, {'flight_id': 'DA125', 'date_time': {'year': 2025, 'month': 9, 'day': 1, 'hour': 7}, 'origin': 'SFO', 'destination': 'JFK', 'duration': 9.0, 'price': 500.0}]}, 
      'observation_1': Flight(flight_id='DA123', date_time=Date(year=2025, month=9, day=1, hour=1), origin='SFO', destination='JFK', duration=3.0, price=200.0), 

      'thought_2': "I have successfully picked Flight DA123 for the user. Now, I need to fetch the user's information to proceed with the booking.", 
      'tool_name_2': 'get_user_info', 
      'tool_args_2': {'name': 'hoge'}, 
      'observation_2': None, 

      'thought_3': 'It seems that I couldn\'t fetch the user\'s information based on the name "hoge." I will need to file a customer support ticket to assist the user with the booking process since I cannot proceed without the user\'s profile information.', 
      'tool_name_3': 'file_ticket', 
      'tool_args_3': {'user_request': 'please help me book a flight from SFO to JFK on 09/01/2025, my name is hoge', 'user_profile': {'user_id': '', 'name': 'hoge', 'email': ''}}, 
      'observation_3': '5u8tzx', 

      'thought_4': "I have filed a customer support ticket for the user since I couldn't retrieve their profile information. The ticket ID is 5u8tzx. I will now finish the process as I have completed the necessary steps to assist the user.", 
      'tool_name_4': 'finish', 
      'tool_args_4': {}, 
      'observation_4': 'Completed.'
    },
    reasoning='I successfully fetched flight options from SFO to JFK on 09/01/2025. The best option, Flight DA123, was selected due to its lower price and shorter duration. However, I was unable to retrieve the user\'s profile information based on the name "hoge," so I filed a customer support ticket to assist with the booking process.',
    process_result='A customer support ticket has been filed to assist you with your flight booking. The ticket ID is 5u8tzx. Please refer to this ID for any follow-up regarding your request.'
)

実行結果を見ると、thought_2にてhogeというユーザを探そうとしたがダミーデータに登録されていないのでNoneが返っていることが確認できます。その結果、thought_3ではfile_ticketを呼び出してカスタマーサポートチケットを発行していることが確認できました。

次は指定した日時にフライトが見つからなかった場合の動作をみてみます。

invalid_flight.py
from pprint import pprint
import dspy
import os
from agent import agent

os.environ["OPENAI_API_KEY"] = "..."

dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
result = agent(user_request="please help me book a flight from SFO to JFK on 09/01/2026, my name is Adam")
pprint(result)

日付を2026/9/1にしてダミーデータに登録していないのでこれもカスタマーサポートチケットが起票されるはずなのでみてみましょう。

uv run invalid_flight.py

# 結果
Prediction(
    trajectory={
      'thought_0': 'I need to fetch flight information for Adam from SFO to JFK on 09/01/2026 to find available flights for booking.', 
      'tool_name_0': 'fetch_flight_info', 
      'tool_args_0': {'date': {'year': 2026, 'month': 9, 'day': 1, 'hour': 0}, 'origin': 'SFO', 'destination': 'JFK'}, 
      'observation_0': 'Execution error in fetch_flight_info: \nTraceback (most recent call last):\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/predict/react.py", line 89, in forward\n    trajectory[f"observation_{idx}"] = self.tools[pred.next_tool_name](**pred.next_tool_args)\n                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/utils/callback.py", line 326, in sync_wrapper\n    return fn(instance, *args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/adapters/types/tool.py", line 166, in __call__\n    result = self.func(**parsed_kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/tool.py", line 21, in fetch_flight_info\n    raise ValueError("No matching flight found!")\nValueError: No matching flight found!', 

      'thought_1': "It seems that there are no matching flights available from SFO to JFK on 09/01/2026. I should check Adam's user profile to see if there are any preferences or additional information that might help in finding a suitable flight.", 
      'tool_name_1': 'get_user_info', 
      'tool_args_1': {'name': 'Adam'}, 
      'observation_1': UserProfile(user_id='1', name='Adam', email='adam@gmail.com'), 

      'thought_2': "Since I have Adam's user profile, I can check if there are any specific preferences or additional information that might help in finding a suitable flight. However, since there were no flights found, I should try fetching flight information again, perhaps with a different date or time.",
      'tool_name_2': 'fetch_flight_info', 
      'tool_args_2': {'date': {'year': 2026, 'month': 9, 'day': 2, 'hour': 0}, 'origin': 'SFO', 'destination': 'JFK'}, 
      'observation_2': 'Execution error in fetch_flight_info: \nTraceback (most recent call last):\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/predict/react.py", line 89, in forward\n    trajectory[f"observation_{idx}"] = self.tools[pred.next_tool_name](**pred.next_tool_args)\n                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/utils/callback.py", line 326, in sync_wrapper\n    return fn(instance, *args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/adapters/types/tool.py", line 166, in __call__\n    result = self.func(**parsed_kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/tool.py", line 21, in fetch_flight_info\n    raise ValueError("No matching flight found!")\nValueError: No matching flight found!', 

      'thought_3': 'It appears that there are still no matching flights available for the next day as well. I should try fetching flight information for a later date to see if any flights become available.', 
      'tool_name_3': 'fetch_flight_info', 
      'tool_args_3': {'date': {'year': 2026, 'month': 9, 'day': 3, 'hour': 0}, 'origin': 'SFO', 'destination': 'JFK'}, 
      'observation_3': 'Execution error in fetch_flight_info: \nTraceback (most recent call last):\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/predict/react.py", line 89, in forward\n    trajectory[f"observation_{idx}"] = self.tools[pred.next_tool_name](**pred.next_tool_args)\n                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/utils/callback.py", line 326, in sync_wrapper\n    return fn(instance, *args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/.venv/lib/python3.12/site-packages/dspy/adapters/types/tool.py", line 166, in __call__\n    result = self.func(**parsed_kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/Users/user/Documents/Blog/blog_materials/dspy/build_ai_agent_tutorial/tool.py", line 21, in fetch_flight_info\n    raise ValueError("No matching flight found!")\nValueError: No matching flight found!', 

      'thought_4': 'It seems that there are no flights available from SFO to JFK for the dates I checked. I should consider filing a customer support ticket to address this issue for Adam, as it may require further assistance from the airline.', 
      'tool_name_4': 'file_ticket', 
      'tool_args_4': {'user_request': 'please help me book a flight from SFO to JFK on 09/01/2026', 'user_profile': {'user_id': '1', 'name': 'Adam', 'email': 'adam@gmail.com'}}, 'observation_4': 'ertz9a'
    },
    reasoning='I attempted to find available flights for Adam from SFO to JFK on 09/01/2026, but no matching flights were found. I then checked for flights on the following days, but still encountered the same issue. Given the lack of available flights, I decided to file a customer support ticket to address this issue, as it may require further assistance from the airline.',
    process_result='A customer support ticket has been filed regarding the request to book a flight from SFO to JFK on 09/01/2026. The ticket number is ertz9a. You will be contacted shortly for further assistance.'
)

実行結果を見るとエラーが起きていますが、これは指定した条件でフライトが見つからなかった場合にValueErrorが発生するようにツールを組んでいたので期待していた結果となります。thought_4を見るとチケットが発行されていることが確認できたので、意図した挙動となってます。

def fetch_flight_info(date: Date, origin: str, destination: str):
    """Fetch flight information from origin to destination on the given date"""
    flights = []

    for flight_id, flight in flight_database.items():
        if (
            flight.date_time.year == date.year
            and flight.date_time.month == date.month
            and flight.date_time.day == date.day
            and flight.origin == origin
            and flight.destination == destination
        ):
            flights.append(flight)
    if len(flights) == 0:
        raise ValueError("No matching flight found!")  # 該当フライトがなかったためこちらのエラーが発生している
    return flights

まとめ

今回はDSPyを直接利用してReActエージェントを実装してみました。ReActエージェントは自身の行動とその結果を元に次のタスクを決めていくエージェントであり、AIアシスタントを初め様々なタスクに応用できるアーキテクチャだと思います。ぜひ興味があればDSPyを利用してAIエージェントを実装してみてください。

Discussion