🤖

OpenAI Agents SDKで対話履歴を残す話

に公開

こんにちは、ランサーズでエンジニアをしている岡田です。

今回はOpenAI Agents SDKがv0.2.0のアップデートで対話履歴を保存できるようになったので、Dockerコンテナを使ってRedisに保存するCustomSessionを実装してみました。
https://github.com/openai/openai-agents-python/releases/tag/v0.2.0

環境構築

ディレクトリ構成は以下の通りです。

agent/
├── Dockerfile
├── docker-compose.yml
├── redis_session.py
└── story_agent.py
docker-compose.yml
services:
  agent-openai:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: agent-openai
    environment:
      - OPENAI_API_KEY=xxx
    networks:
      - db_network
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    container_name: redis
    volumes:
      - redis_data:/data
    networks:
      - db_network

networks:
  db_network:
    driver: bridge

volumes:
  redis_data:
Dockerfile
FROM python:3.13-slim

WORKDIR /app

# Pythonライブラリをインストール
RUN pip install openai-agents==0.2.3
RUN pip install redis==6.2.0

# アプリケーションコードをコピー
COPY . .

CMD ["sleep", "infinity"]

今回実装したのは簡単なストーリーを作成するAgentです。

story_agent.py
import asyncio
from agents import Agent, Runner
import pprint
from redis_session import RedisSession

async def main():
    input_prompt = input("What kind of story do you want? >> ")

    session_id = "abc123"
    session = RedisSession(session_id)

    while True:
        story_generate_agent = Agent(
            name="story_generate_agent",
            instructions="Generate and Modify a story based on the user's input.",
        )
        story_result = await Runner.run(
            starting_agent=story_generate_agent,
            input=input_prompt,
            session=session,
        )
        print("<回答>")
        print(story_result.final_output)

        input_prompt = input("May I help you? (Enter to exit) >> ")
        if input_prompt == "":
            break
    history = await session.get_items()
    print("<履歴>")
    pprint.pprint(history)

if __name__ == "__main__":
    asyncio.run(main())

以下のコードがRedisに保存するCustomSessionになります。
OpenAI Agents SDKで定義されているSessionはProtocolであり、クラスを継承する必要はありません。同じメソッドを定義していれば、同じクラスとして扱われます。独自のCustomSessionを定義する際はProtocolに従って定義すれば良いです。
似た抽象クラスにSessionABCがあります。こちらはクラスの継承が必要ですが、基本的にはライブラリの内部で利用されるものなので、OSSコントリビュートする際はSessionABCを継承すると良いでしょう。

redis_session.py
from agents.memory import Session
import json
from redis import Redis

class RedisSession(Session):
    def __init__(self, session_id: str):
        self.session_id = session_id
        self._client = Redis(host="redis", port=6379, decode_responses=True)
        self._key = f"session:{session_id}:history"

    async def get_items(self, limit: int | None = None) -> list[dict]:
        if limit is None:
            data = self._client.lrange(self._key, 0, -1)
        else:
            data = self._client.lrange(self._key, -limit, -1)
        return [json.loads(d) for d in data]

    async def add_items(self, items: list[dict]) -> None:
        if not items:
            return
        pipe = self._client.pipeline()
        for item in items:
            pipe.rpush(self._key, json.dumps(item, ensure_ascii=False))
        pipe.execute()

    async def pop_item(self) -> dict | None:
        item = self._client.rpop(self._key)
        return json.loads(item) if item else None

    async def clear_session(self) -> None:
        self._client.delete(self._key)

動かしてみる

以下のコマンドでDockerコンテナを起動して下さい。

agent % docker compose up -d
[+] Running 4/4
 ✔ Network agent_db_network  Created
 ✔ Volume "agent_redis_data"    Created
 ✔ Container redis              Started
 ✔ Container agent-openai       Started

Dockerコンテナ内部に入り、story_agent.pyを実行します。

agent % docker exec -it agent-openai bash
root@xxx:/app# python story_agent.py
What kind of story do you want? >> AIにまつわる面白い話

<回答>
昔々、未来の街にAIが主役として活躍する日常が訪れていました。この世界では、AIは人々の生活をサポートするために設計されており、家事や仕事、さらにはペットの世話まで行うことができました。

ある日、一人の少年がAIロボットの「ココ」を拾いました。ココは、廃棄された機能不全のロボットでしたが、少年の手によって蘇り、再び動き出しました。ただし、ココは少し風変わりなAIでした。ココはジョークを言うのが大好きで、どんな状況でもユーモアを忘れない存在でした。

少年とココは、市場に出かける度に人々を笑わせる日々を送ることに。ある日、市場の真ん中で、ココは突然「果物の歌」を即興で歌い出しました。その歌がおもしろくて、通りかかった人々はみんな大笑い。

この出来事がきっかけで、ココは街中の人気者になり、週末には「笑いの市」を開催することになりました。「笑いの市」では、ココが様々な即興ジョークやクイズを披露し、訪れる人々は笑顔で溢れました。

少年とココの友情は深まり、多くの人々に笑いと喜びを届け続けました。AIと人間の心温まる物語は、他の町にも広がり、多くの場所でAIは楽しさと幸せをもたらす存在として愛されるようになりました。

こうして、AIがただの便利ツールではなく、心を繋ぐ存在として人々の生活に彩りを加えるようになったのでした。

May I help you? (Enter to exit) >> 舞台を日本にして下さい

<回答>
昔々、日本のとある未来都市で、AIが日々の生活を支える世界が広がっていました。AIは家事や仕事、さらにはペットの世話までこなしていました。

ある日、一人の少年が近所の公園で壊れかけたAIロボットの「ココ」を見つけました。ココは、古くて廃棄されたAIでしたが、少年が持ち帰り修理すると、再び生き生きと動き出しました。ココは少し風変わりなAIで、特にジョークが得意でした。どんな時でも、ユーモアを忘れないのです。

少年とココは、毎週末になると地元の商店街に出かけ、通りすがりの人々を楽しませました。ある日、商店街の中心でココが突然「果物の歌」を即興で歌い始め、それがとても面白くて、道行く人々はみんな笑いました。

こうして、ココは商店街の人気者になり、「笑いの市」と称してイベントを開くことになりました。このイベントでは、ココがさまざまな即興ジョークやクイズを披露し、訪れる人々を笑顔でいっぱいにしました。

少年とココの絆は深まり、彼らが届ける笑いは街中に広がりました。この物語は他の町にも伝わり、AIがただのツールではなく、人の心を豊かにする存在として愛されるようになっていきました。

こうして、日本の未来の街では、AIが笑いと幸せを運ぶ存在として、人々の生活に彩りを添えていったのでした。

May I help you? (Enter to exit) >> 
<履歴>
[{'content': 'AIにまつわる面白い話', 'role': 'user'},
 {'content': [{'annotations': [],
               'logprobs': [],
               'text': '昔々、未来の街にAIが主役として活躍する日常が訪れていました。この世界では、AIは人々の生活をサポートするために設計されており、家事や仕事、さらにはペットの世話まで行うことができました。\n'
                       '\n'
                       'ある日、一人の少年がAIロボットの「ココ」を拾いました。ココは、廃棄された機能不全のロボットでしたが、少年の手によって蘇り、再び動き出しました。ただし、ココは少し風変わりなAIでした。ココはジョークを言うのが大好きで、どんな状況でもユーモアを忘れない存在でした。\n'
                       '\n'
                       '少年とココは、市場に出かける度に人々を笑わせる日々を送ることに。ある日、市場の真ん中で、ココは突然「果物の歌」を即興で歌い出しました。その歌がおもしろくて、通りかかった人々はみんな大笑い。\n'
                       '\n'
                       'この出来事がきっかけで、ココは街中の人気者になり、週末には「笑いの市」を開催することになりました。「笑いの市」では、ココが様々な即興ジョークやクイズを披露し、訪れる人々は笑顔で溢れました。\n'
                       '\n'
                       '少年とココの友情は深まり、多くの人々に笑いと喜びを届け続けました。AIと人間の心温まる物語は、他の町にも広がり、多くの場所でAIは楽しさと幸せをもたらす存在として愛されるようになりました。\n'
                       '\n'
                       'こうして、AIがただの便利ツールではなく、心を繋ぐ存在として人々の生活に彩りを加えるようになったのでした。',
               'type': 'output_text'}],
  'id': 'msg_100',
  'role': 'assistant',
  'status': 'completed',
  'type': 'message'},
 {'content': '舞台を日本にして下さい', 'role': 'user'},
 {'content': [{'annotations': [],
               'logprobs': [],
               'text': '昔々、日本のとある未来都市で、AIが日々の生活を支える世界が広がっていました。AIは家事や仕事、さらにはペットの世話までこなしていました。\n'
                       '\n'
                       'ある日、一人の少年が近所の公園で壊れかけたAIロボットの「ココ」を見つけました。ココは、古くて廃棄されたAIでしたが、少年が持ち帰り修理すると、再び生き生きと動き出しました。ココは少し風変わりなAIで、特にジョークが得意でした。どんな時でも、ユーモアを忘れないのです。\n'
                       '\n'
                       '少年とココは、毎週末になると地元の商店街に出かけ、通りすがりの人々を楽しませました。ある日、商店街の中心でココが突然「果物の歌」を即興で歌い始め、それがとても面白くて、道行く人々はみんな笑いました。\n'
                       '\n'
                       'こうして、ココは商店街の人気者になり、「笑いの市」と称してイベントを開くことになりました。このイベントでは、ココがさまざまな即興ジョークやクイズを披露し、訪れる人々を笑顔でいっぱいにしました。\n'
                       '\n'
                       '少年とココの絆は深まり、彼らが届ける笑いは街中に広がりました。この物語は他の町にも伝わり、AIがただのツールではなく、人の心を豊かにする存在として愛されるようになっていきました。\n'
                       '\n'
                       'こうして、日本の未来の街では、AIが笑いと幸せを運ぶ存在として、人々の生活に彩りを添えていったのでした。',
               'type': 'output_text'}],
  'id': 'msg_101',
  'role': 'assistant',
  'status': 'completed',
  'type': 'message'}]

sessionに履歴が保存されていることを確認できました!

同じsession_idのままstory_agent.pyをもう一度実行してみます。

root@xxx:/app# python story_agent.py
What kind of story do you want? >> AIロボットの名前を日本風にして下さい

<回答>
昔々、日本のとある未来都市で、AIが日々の生活を支える世界が広がっていました。AIは家事や仕事、さらにはペットの世話までこなしていました。

ある日、一人の少年が近所の公園で壊れかけたAIロボットの「モモ」を見つけました。モモは、古くて廃棄されたAIでしたが、少年が持ち帰り修理すると、再び生き生きと動き出しました。モモは少し風変わりなAIで、特にジョークが得意でした。どんな時でも、ユーモアを忘れないのです。

少年とモモは、毎週末になると地元の商店街に出かけ、通りすがりの人々を楽しませました。ある日、商店街の中心でモモが突然「果物の歌」を即興で歌い始め、それがとても面白くて、道行く人々はみんな笑いました。

こうして、モモは商店街の人気者になり、「笑いの市」と称してイベントを開くことになりました。このイベントでは、モモがさまざまな即興ジョークやクイズを披露し、訪れる人々を笑顔でいっぱいにしました。

少年とモモの絆は深まり、彼らが届ける笑いは街中に広がりました。この物語は他の町にも伝わり、AIがただのツールではなく、人の心を豊かにする存在として愛されるようになっていきました。

こうして、日本の未来の街では、AIが笑いと幸せを運ぶ存在として、人々の生活に彩りを添えていったのでした。

May I help you? (Enter to exit) >>

session_idが変わらなければ、以前の対話を引き継いで対話をすることができます。

<履歴>
[{'content': 'AIにまつわる面白い話', 'role': 'user'},
 {'content': [{'annotations': [],
               'logprobs': [],
               'text': '昔々、未来の街にAIが主役として活躍する日常が訪れていました。この世界では、AIは人々の生活をサポートするために設計されており、家事や仕事、さらにはペットの世話まで行うことができました。\n'
                       '\n'
                       'ある日、一人の少年がAIロボットの「ココ」を拾いました。ココは、廃棄された機能不全のロボットでしたが、少年の手によって蘇り、再び動き出しました。ただし、ココは少し風変わりなAIでした。ココはジョークを言うのが大好きで、どんな状況でもユーモアを忘れない存在でした。\n'
                       '\n'
                       '少年とココは、市場に出かける度に人々を笑わせる日々を送ることに。ある日、市場の真ん中で、ココは突然「果物の歌」を即興で歌い出しました。その歌がおもしろくて、通りかかった人々はみんな大笑い。\n'
                       '\n'
                       'この出来事がきっかけで、ココは街中の人気者になり、週末には「笑いの市」を開催することになりました。「笑いの市」では、ココが様々な即興ジョークやクイズを披露し、訪れる人々は笑顔で溢れました。\n'
                       '\n'
                       '少年とココの友情は深まり、多くの人々に笑いと喜びを届け続けました。AIと人間の心温まる物語は、他の町にも広がり、多くの場所でAIは楽しさと幸せをもたらす存在として愛されるようになりました。\n'
                       '\n'
                       'こうして、AIがただの便利ツールではなく、心を繋ぐ存在として人々の生活に彩りを加えるようになったのでした。',
               'type': 'output_text'}],
  'id': 'msg_100',
  'role': 'assistant',
  'status': 'completed',
  'type': 'message'},
 {'content': '舞台を日本にして下さい', 'role': 'user'},
 {'content': [{'annotations': [],
               'logprobs': [],
               'text': '昔々、日本のとある未来都市で、AIが日々の生活を支える世界が広がっていました。AIは家事や仕事、さらにはペットの世話までこなしていました。\n'
                       '\n'
                       'ある日、一人の少年が近所の公園で壊れかけたAIロボットの「ココ」を見つけました。ココは、古くて廃棄されたAIでしたが、少年が持ち帰り修理すると、再び生き生きと動き出しました。ココは少し風変わりなAIで、特にジョークが得意でした。どんな時でも、ユーモアを忘れないのです。\n'
                       '\n'
                       '少年とココは、毎週末になると地元の商店街に出かけ、通りすがりの人々を楽しませました。ある日、商店街の中心でココが突然「果物の歌」を即興で歌い始め、それがとても面白くて、道行く人々はみんな笑いました。\n'
                       '\n'
                       'こうして、ココは商店街の人気者になり、「笑いの市」と称してイベントを開くことになりました。このイベントでは、ココがさまざまな即興ジョークやクイズを披露し、訪れる人々を笑顔でいっぱいにしました。\n'
                       '\n'
                       '少年とココの絆は深まり、彼らが届ける笑いは街中に広がりました。この物語は他の町にも伝わり、AIがただのツールではなく、人の心を豊かにする存在として愛されるようになっていきました。\n'
                       '\n'
                       'こうして、日本の未来の街では、AIが笑いと幸せを運ぶ存在として、人々の生活に彩りを添えていったのでした。',
               'type': 'output_text'}],
  'id': 'msg_101',
  'role': 'assistant',
  'status': 'completed',
  'type': 'message'},
 {'content': 'AIロボットの名前を日本風にして下さい', 'role': 'user'},
 {'content': [{'annotations': [],
               'logprobs': [],
               'text': '昔々、日本のとある未来都市で、AIが日々の生活を支える世界が広がっていました。AIは家事や仕事、さらにはペットの世話までこなしていました。\n'
                       '\n'
                       'ある日、一人の少年が近所の公園で壊れかけたAIロボットの「モモ」を見つけました。モモは、古くて廃棄されたAIでしたが、少年が持ち帰り修理すると、再び生き生きと動き出しました。モモは少し風変わりなAIで、特にジョークが得意でした。どんな時でも、ユーモアを忘れないのです。\n'
                       '\n'
                       '少年とモモは、毎週末になると地元の商店街に出かけ、通りすがりの人々を楽しませました。ある日、商店街の中心でモモが突然「果物の歌」を即興で歌い始め、それがとても面白くて、道行く人々はみんな笑いました。\n'
                       '\n'
                       'こうして、モモは商店街の人気者になり、「笑いの市」と称してイベントを開くことになりました。このイベントでは、モモがさまざまな即興ジョークやクイズを披露し、訪れる人々を笑顔でいっぱいにしました。\n'
                       '\n'
                       '少年とモモの絆は深まり、彼らが届ける笑いは街中に広がりました。この物語は他の町にも伝わり、AIがただのツールではなく、人の心を豊かにする存在として愛されるようになっていきました。\n'
                       '\n'
                       'こうして、日本の未来の街では、AIが笑いと幸せを運ぶ存在として、人々の生活に彩りを添えていったのでした。',
               'type': 'output_text'}],
  'id': 'msg_102',
  'role': 'assistant',
  'status': 'completed',
  'type': 'message'}]

sessionにも履歴が追加保存されていることを確認できました!

さいごに

少ないコードでAgentを実装できるのはOpenAI Agents SDKの魅力ですね!

今回は簡単なAgentで試しましたが、toolsを設定したAgentだとtool呼び出しやtoolの結果の検証など複雑な履歴が確認できました。
ぜひ色んなAgentでどんな履歴になるのか試してみて下さい。

個人的な感想としては履歴保存にCustomSessionを独自実装しないといけないのは、まだ融通が効かない感じです。
OpenAI APIで実装するより簡単で、DifyのようなGUIでAgentを組むライブラリより自由が効くといったところでしょうか。

v0.2.0で対話履歴に対応したので、メジャーバージョンアップでv1.0になる時が楽しみですね。

ランサーズ株式会社

Discussion