メインコンテンツへスキップ
見出し画像

OpenAI Agents SDK で シンプルなDeep Researchクローンを実装する

    以下のサンプルコードの使い方をまとめました。

    ・openai/openai-agents-python/examples/research_bot

    前回

    1. Research Bot

    「OpenAI Agents SDK」でシンプルな「Deep Research」クローンを実装します。処理の流れは、次のとおりです。

    (1) ユーザーが 研究テーマ を入力
    (2) planner_agent でWeb検索のプランを作成。
    プランは検索クエリのリストです。
    (3) search_agent で検索クエリを実行。
    Web検索ツールを使用して検索して結果を要約します。これらは並行して実行されます。
    (4) writer_agent でレポートを作成。

    ファイル構成は、次のとおりです。

    ・research_bot
     ・main.py
     ・manager.py
     ・printer.py
     ・agents
      ・planner_agent.py
      ・search_agent.py
      ・writer_agent.py

    2. コードの解説

    2-1. main.py

    「何を研究したいですか?」の質問を入力してリサーチを実行します。

    import asyncio
    from .manager import ResearchManager
    
    # mainの定義
    async def main() -> None:
        query = input("何を研究したいですか?")
        await ResearchManager().run(query)
    
    # mainの実行
    if __name__ == "__main__":
        asyncio.run(main())

    2-2. manager.py

    リサーチを実行を管理します。具体的には、Web検索のプランの作成、検索クエリの実行、レポートの作成を順番に実行します。

    from __future__ import annotations
    import asyncio
    import time
    from rich.console import Console
    from agents import Runner, custom_span, gen_trace_id, trace
    from .agents.planner_agent import WebSearchItem, WebSearchPlan, planner_agent
    from .agents.search_agent import search_agent
    from .agents.writer_agent import ReportData, writer_agent
    from .printer import Printer
    
    # リサーチマネージャ
    class ResearchManager:
        # 初期化
        def __init__(self):
            self.console = Console()
            self.printer = Printer(self.console)
    
        # リサーチの実行
        async def run(self, query: str) -> None:
            trace_id = gen_trace_id()
            with trace("Research trace", trace_id=trace_id):
                self.printer.update_item(
                    "trace_id",
                    f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}",
                    is_done=True,
                    hide_checkmark=True,
                )
    
                self.printer.update_item(
                    "starting",
                    "Starting research...",
                    is_done=True,
                    hide_checkmark=True,
                )
    
                # Web検索のプランの作成
                search_plan = await self._plan_searches(query)
    
                # 検索クエリの実行
                search_results = await self._perform_searches(search_plan)
    
                # レポートの作成
                report = await self._write_report(query, search_results)
    
                final_report = f"Report summary\n\n{report.short_summary}"
                self.printer.update_item("final_report", final_report, is_done=True)
    
                self.printer.end()
    
            print("\n\n=====REPORT=====\n\n")
            print(f"Report: {report.markdown_report}")
            print("\n\n=====FOLLOW UP QUESTIONS=====\n\n")
            follow_up_questions = "\n".join(report.follow_up_questions)
            print(f"Follow up questions: {follow_up_questions}")
    
        # Web検索のプランの作成
        async def _plan_searches(self, query: str) -> WebSearchPlan:
            self.printer.update_item("planning", "Planning searches...")
            result = await Runner.run(
                planner_agent,
                f"クエリ: {query}",
            )
            self.printer.update_item(
                "planning",
                f"Will perform {len(result.final_output.searches)} searches",
                is_done=True,
            )
            return result.final_output_as(WebSearchPlan)
    
        # 検索クエリの実行
        async def _perform_searches(self, search_plan: WebSearchPlan) -> list[str]:
            with custom_span("Search the web"):
                self.printer.update_item("searching", "Searching...")
                num_completed = 0
                tasks = [asyncio.create_task(self._search(item)) for item in search_plan.searches]
                results = []
                for task in asyncio.as_completed(tasks):
                    result = await task
                    if result is not None:
                        results.append(result)
                    num_completed += 1
                    self.printer.update_item(
                        "searching", f"Searching... {num_completed}/{len(tasks)} completed"
                    )
                self.printer.mark_item_done("searching")
                return results
    
        # 個別の検索クエリの実行
        async def _search(self, item: WebSearchItem) -> str | None:
            input = f"検索ワード: {item.query}\n検索理由: {item.reason}"
            try:
                result = await Runner.run(
                    search_agent,
                    input,
                )
                return str(result.final_output)
            except Exception:
                return None
    
        # レポートの作成
        async def _write_report(self, query: str, search_results: list[str]) -> ReportData:
            self.printer.update_item("writing", "Thinking about report...")
            input = f"オリジナルクエリ: {query}\n要約された検索結果: {search_results}"
            result = Runner.run_streamed(
                writer_agent,
                input,
            )
            update_messages = [
                "Thinking about report...",
                "Planning report structure...",
                "Writing outline...",
                "Creating sections...",
                "Cleaning up formatting...",
                "Finalizing report...",
                "Finishing report...",
            ]
    
            last_update = time.time()
            next_message = 0
            async for _ in result.stream_events():
                if time.time() - last_update > 5 and next_message < len(update_messages):
                    self.printer.update_item("writing", update_messages[next_message])
                    next_message += 1
                    last_update = time.time()
    
            self.printer.mark_item_done("writing")
            return result.final_output_as(ReportData)

    2-3. printer.py

    コンソール上で処理の進捗を表示するユーティリティです。

    from typing import Any
    from rich.console import Console, Group
    from rich.live import Live
    from rich.spinner import Spinner
    
    # プリンター
    class Printer:
        # 初期化
        def __init__(self, console: Console):
            self.live = Live(console=console)
            self.items: dict[str, tuple[str, bool]] = {}
            self.hide_done_ids: set[str] = set()
            self.live.start()
    
        # 指定した項目の内容の更新
        def update_item(
            self, item_id: str, content: str, is_done: bool = False, hide_checkmark: bool = False
        ) -> None:
            self.items[item_id] = (content, is_done)
            if hide_checkmark:
                self.hide_done_ids.add(item_id)
            self.flush()
    
        # 指定した項目を完了に変更
        def mark_item_done(self, item_id: str) -> None:
            self.items[item_id] = (self.items[item_id][0], True)
            self.flush()
    
        # ライブ表示の更新
        def flush(self) -> None:
            renderables: list[Any] = []
            for item_id, (content, is_done) in self.items.items():
                if is_done:
                    prefix = "✅ " if item_id not in self.hide_done_ids else ""
                    renderables.append(prefix + content)
                else:
                    renderables.append(Spinner("dots", text=content))
            self.live.update(Group(*renderables))
    
        # ライブ表示の停止
        def end(self) -> None:
            self.live.stop()

    2-4. planner_agent.py

    Web検索のプランを作成するAgentです。入力はクエリ、出力はWeb検索プラン (検索アイテムのリスト) です。

    from pydantic import BaseModel
    from agents import Agent
    
    # 指示
    INSTRUCTIONS = (
        "あなたは役に立つ研究アシスタントです。"
        "クエリが与えられたら、クエリに最適な回答を得るために実行するWeb検索のセットを考え出してください。"
        "クエリする5から20個の用語を出力してください。"
    )
    
    # Web検索アイテム
    class WebSearchItem(BaseModel):
        reason: str
        "この検索がクエリにとって重要である理由。"
    
        query: str
        "Web検索に使用する検索用語。"
    
    # Web検索プラン
    class WebSearchPlan(BaseModel):
        searches: list[WebSearchItem]
        """クエリに最適な回答を得るために実行するWeb検索のリスト。"""
    
    # PlannerAgentの準備
    planner_agent = Agent(
        name="PlannerAgent",
        instructions=INSTRUCTIONS,
        model="gpt-4o",
        output_type=WebSearchPlan,
    )

    2-5. search_agent.py

    個別の検索クエリを実行するAgentです。Web検索ツールを使用して検索して結果を要約します。これらは並行して実行されます。入力は検索アイテム、出力は要約された検索結果です。

    from agents import Agent, WebSearchTool
    from agents.model_settings import ModelSettings
    
    # 指示
    INSTRUCTIONS = (
        "あなたは研究助手です。検索語句が与えられたら、その語句で Webを検索し、結果の簡潔な要約を作成します。"
        "要約は2~3段落、300語未満でなければなりません。"
        "要点を押さえてください。簡潔に書きます。完全な文や正しい文法である必要はありません。"
        "これはレポートをまとめる人が読むものなので、要点を押さえ、余分な部分は無視することが重要です。"
        "要約自体以外のコメントは含めないでください。"
    )
    
    # SearchAgentの準備
    search_agent = Agent(
        name="SearchAgent",
        instructions=INSTRUCTIONS,
        tools=[WebSearchTool()],
        model_settings=ModelSettings(tool_choice="required"),
    )

    2-6. writer_agent.py

    レポートを作成するAgentです。入力はオリジナルクエリと検索結果、出力はレポートです。

    from pydantic import BaseModel
    from agents import Agent
    
    # 指示
    INSTRUCTIONS = (
        "あなたは、リサーチ クエリに対するまとまりのあるレポートを書くことを任された上級研究者です。"
        "元のクエリと、リサーチアシスタントが行った初期リサーチが提供されます。"
        "まず、レポートの構造と流れを説明するレポートのアウトラインを作成します。"
        "次に、レポートを生成して、最終出力として返します。"
        "最終出力はマークダウン形式で、長くて詳細なものにする必要があります。"
        "5~10 ページ、少なくとも 1,000語のコンテンツを目指します。"
    )
    
    # レポート
    class ReportData(BaseModel):
        short_summary: str
        """調査結果を2~3文で短く要約"""
    
        markdown_report: str
        """最終報告書"""
    
        follow_up_questions: list[str]
        """さらに研究するための提案トピック"""
    
    # WriterAgentの準備
    writer_agent = Agent(
        name="WriterAgent",
        instructions=INSTRUCTIONS,
        model="o3-mini",
        output_type=ReportData,
    )

    3. コードの実行

    (1) ローカルPCでPython仮想環境を準備。

    (2) ローカルPCの環境変数「OPENAI_API_KEY」にAPIキーを指定。

    ・Mac

    export OPENAI_API_KEY=sk-...

    ・Windows

    set OPENAI_API_KEY=sk-...

    (3) パッケージのインストール。

    pip install openai-agents
    pip install rich

    (4) コードの実行。

    python -m research_bot.main

    何を研究したいですか?
    ぼっち・ざ・ろっくの成功の秘密

    本レポートは、『ぼっち・ざ・ろっく!』の成功の秘密を、多角的な視点から分析しています。アニメの受賞歴や国内外の高い評価、キャラクターの魅力、作画や音楽の質、そして独自のマーケティング戦略とコラボレーションなど、成功要因を詳細に解説します。これらの要素がどのように融合し、社会現象となるほどの人気を獲得したのか、その全貌を紐解いていきます。


    =====REPORT=====


    Report: # 『ぼっち・ざ・ろっく!』成功の秘密を探る

    本レポートでは、アニメ『ぼっち・ざ・ろっく!』が国内外で成功を収めた背景について、さまざまな角度から検証を行います。受賞歴や制作面の革新、キャラクターの親しみやすさ、音楽やマーケティング戦略、さらにはファンとの相互作用に至るまで、これらの要素がどのように絡み合い、作品全体の魅力を形成しているのかを考察します。

    ---

    ## アウトライン

    1. **はじめに**
    - レポートの目的と概要
    - 『ぼっち・ざ・ろっく!』の市場における位置づけ

    2. **受賞歴と国際的評価**
    - 国内受賞歴(ニュータイプアニメアワード、その他の国内賞)
    - 国外での評価と受賞事例(Anime Trending 大賞など)

    3. **キャラクターと物語の魅力**
    - 主人公・後藤ひとり(ぼっちちゃん)の設定と成長過程
    - キャラクターの個性とその影響
    - ギャグとシリアスのバランスによるドラマ展開

    4. **制作と演出面の優位性**
    - 監督・演出陣の役割と革新的なアプローチ
    - 手描きの演出、ライブシーンの再現、アニメーションのクオリティ
    - 声優陣の実力とキャラクター表現

    5. **音楽とサウンドトラックの影響**
    - 劇中バンド「結束バンド」の楽曲の分析
    - 主題歌「青春コンプレックス」などのヒット要因
    - 邦楽ロックへのオマージュと音楽ファンへのアピール

    6. **マーケティングとコラボレーション戦略**
    - 企業との積極的なコラボレーション(カラオケチェーン、ヤマハ、パック酒等)
    - 商品化やイベントを通じたブランディング戦略
    - SNSとデジタルマーケティングの役割

    7. **ファンとの双方向コミュニケーション**
    - ソーシャルメディア上での急速な人気拡大の事例
    - Twitterなどのプラットフォームでの反響
    - ファンアートや演奏動画の拡散とその影響

    8. **結論と今後の展望**
    - 成功要因の総括
    - 今後の展開とアニメ業界への影響

    ---

    ## 1. はじめに

    『ぼっち・ざ・ろっく!』は、現代のアニメ界において一大ブームを巻き起こしている作品です。視覚的、音響的な革新とともに、内向的な少女がバンド活動を通じて成長していくというテーマが、多くの視聴者に強い共感を呼んでいます。本レポートの目的は、その成功の秘密に迫ることであり、受賞歴、キャラクター設定、制作のクオリティ、音楽、そしてマーケティング戦略という多角的な観点から考察を行います。これにより、『ぼっち・ざ・ろっく!』がどのようにして国内外で大きな影響力を持つまでになったのか、そのプロセスを詳細に分析します。

    ## 2. 受賞歴と国際的評価

    ### 2.1 国内での評価

    本作は、2023年10月に行われた「ニュータイプアニメアワード2022-2023」で、TV部門の作品賞を受賞しています。主題歌賞でオープニングテーマ「青春コンプレックス」が1位に輝いたほか、監督賞も斎藤圭一郎監督が受賞するなど、各部門で高い評価を受けました。これらの受賞は、作品の芸術的および技術的な完成度が認められた結果と言えます。

    ### 2.2 国際的な評価

    さらに、アメリカのアニメメディア「Anime Trending」による「第9回アニメトレンド大賞」では、年間最優秀作品賞を含む史上最多の8冠を受賞。国内外での受賞実績は、『ぼっち・ざ・ろっく!』の普遍的な魅力と国際的な影響力の証左です。視聴者層が多様であること、そして国境を越えて共感されるテーマが、こうした評価につながっています。

    ## 3. キャラクターと物語の魅力

    ### 3.1 主人公・後藤ひとり(ぼっちちゃん)の魅力

    物語の中心となるのは、極度の人見知りで内向的な少女・後藤ひとり。彼女は、ひとりでギターを練習していた孤独なキャラクターですが、バンドに参加することで自己表現の道を歩み始めます。この成長の過程は、多くの内向的な視聴者が自分に重ね合わせやすい要素となっています。

    ### 3.2 キャラクター間の相互作用

    各登場人物には個性豊かなバックボーンが与えられており、彼らのやり取りや関係性が物語に深みを加えています。内気な主人公と、明るい仲間たちとの対比が物語に対する感情移入を助け、視聴者に温かい感動を提供しています。

    ### 3.3 ギャグとシリアスのバランス

    『ぼっち・ざ・ろっく!』は、笑いと涙のバランスを絶妙に組み合わせることで、日常のささいな瞬間さえもドラマティックに描き出しています。ギャグシーンでの意図的なデフォルメやシリアスなシーンでの感情表現は、視聴者の幅広い層に受け入れられる要因となっています。

    ## 4. 制作と演出面の優位性

    ### 4.1 監督・演出陣の革新

    第1期においては斎藤圭一郎監督が作品の方向性を決定付け、彼の手腕は多くの批評家から絶賛されました。シリーズ構成や脚本、キャラクターデザインにおいても、スタッフの連携が光り、アニメーションの質を格段に高める結果となりました。

    ### 4.2 アニメーションとライブシーン

    ライブディレクターの川上雄介氏やライブアニメーターの伊藤優希氏の貢献により、バンドの演奏シーンがリアルにかつダイナミックに描写されています。モーションキャプチャーに頼らず、手描きで精密に再現された演奏シーンは、視聴者に臨場感を提供し、作品の大きな魅力の一つとなっています。

    ### 4.3 声優陣の演技

    青山吉能さんをはじめとする声優陣は、キャラクターの心理描写や成長過程を見事に表現。彼らの演技により、視聴者はキャラクターに深く共感し、感情移入が促される結果となりました。各声優の名演は、アニメの完成度を引き上げる決定的な要因です。

    ## 5. 音楽とサウンドトラックの影響

    ### 5.1 楽曲の魅力

    『ぼっち・ざ・ろっく!』の音楽は、作品の魅力を大きく左右する要素です。劇中バンド「結束バンド」によって披露される楽曲は、ポップでありながら心に深く響くメロディーを持っており、多くのファンを魅了しています。特に主題歌「青春コンプレックス」は、その完成度の高さが評価され、作品のシンボルともなっています。

    ### 5.2 邦楽ロックへのオマージュ

    作品内には、実在のロックバンドやアーティストへのオマージュが随所に見られます。ASIAN KUNG-FU GENERATIONなど、邦楽ロックの影響を色濃く受けた演出や音楽アレンジは、音楽ファンにとって大きな魅力となり、作品の世界観に厚みを加えています。

    ### 5.3 演出との融合

    音楽は単なる背景音として留まらず、物語の進行やキャラクターの内面表現に寄与しています。歌詞やアレンジは、キャラクターの成長やドラマを視覚的に補完し、視聴者の感情を一層引き立てる役割を果たしています。

    ## 6. マーケティングとコラボレーション戦略

    ### 6.1 企業との多角的コラボレーション

    『ぼっち・ざ・ろっく!』は、単なるアニメ作品に留まらず、数多くの企業とのコラボレーションを積極的に展開してきました。例えば、カラオケチェーンのビッグエコーとの提携により、全国の店舗でオリジナルグッズやコラボルームが展開され、ファンにとってリアルな体験の場を提供しています。また、ヤマハとの連携では、作中に登場する楽器や機器を実際に体験・展示するイベントが実施されるなど、音楽ファンへの訴求力が高まりました。

    ### 6.2 限定商品とプロモーション

    キャラクターグッズの展開や、特注モデルのエレキギター「PACIFICA」のプレゼントキャンペーン、さらにはパック酒の再現商品など、作品のキャラクターやアイコンを活かした商品が次々と登場しています。これらのプロモーションは、ファンの日常に作品の世界観を取り込む大きな要因となりました。

    ### 6.3 SNS時代のプロモーション

    ソーシャルメディアは、『ぼっち・ざ・ろっく!』の人気拡大において、極めて重要な役割を果たしました。公式Twitterアカウントの急激なフォロワー増加や、放送後も続くハッシュタグの盛り上がりは、オンライン上でのコミュニティ形成と口コミの力を象徴しています。SNSを通じたファンの声、演奏動画、ファンアートなど、双方向のコミュニケーションにより、作品は国際的な現象へと発展しました。

    ## 7. ファンとの双方向コミュニケーション

    ### 7.1 SNS上での反響

    放送中は、Twitter上で「#ぼっち・ざ・ろっく」がトレンド入りするなど、視聴者の熱い支持が伺えました。特に第4話の放送時には、ツイート数が6.4万件を超えるなど、他作品を凌駕する反響を生むなど、ソーシャルメディア上での存在感は圧倒的です。

    ### 7.2 ファンによる自主的な創作活動

    また、視聴者は演奏動画をYouTubeに投稿するなど、自ら作品の魅力を発信する動きが見られました。これにより、ファン活動はさらなる波及効果を生み、コンテンツとしての『ぼっち・ざ・ろっく!』の寿命を延ばす役割も果たしています。

    ### 7.3 国際的なファンネットワーク

    国内のみならず、海外のファンとの交流も加速。国際的な投票や受賞を通して、『ぼっち・ざ・ろっく!』はグローバルな人気を証明しました。こうした現象は、単一の文化圏に留まらず、世界規模での共感と影響力を示しています。

    ## 8. 結論と今後の展望

    ### 8.1 成功要因の総括

    本レポートで検証したように、『ぼっち・ざ・ろっく!』の成功は、アニメ制作全体の質の高さと、キャラクター、音楽、そしてマーケティングの各要素が高度に調和した結果です。監督やスタッフ、声優陣の卓越した才能、そして緻密に計算された企業とのコラボレーション戦略が、作品を単なるエンターテインメント以上の社会現象へと押し上げました。

    ### 8.2 今後の展開と業界への影響

    第2期においても制作陣の入れ替えや新しい試みが予定されており、『ぼっち・ざ・ろっく!』の影響は今後も続くと予想されます。また、本作の成功モデルは、今後のアニメ製作においても、視聴者の共感を得るための一つの指標となるでしょう。内向的な主人公を描くという一見ニッチなテーマも、適切な表現とマーケティング手法によって、広範な視聴者に支持される可能性を秘めています。

    ### 8.3 教訓と未来への示唆

    『ぼっち・ざ・ろっく!』は、単にエンターテインメントとしての成功だけではなく、現代のメディア環境においていかに多角的なアプローチが必要かを示してくれます。伝統的なアニメの枠を超え、音楽、SNS、そして企業コラボレーションといった多方面からのアプローチが、作品のブランド価値を高め、ファンとの強固な絆を作り上げています。これからのアニメ製作やマーケティング戦略において、その成功要因は多くのクリエイターやプロデューサーにとって、貴重な教訓となるでしょう。

    ---

    ## まとめ

    本レポートでは、『ぼっち・ざ・ろっく!』の成功の秘密について、受賞歴、キャラクターの魅力、緻密な制作プロセス、そして革新的なマーケティング戦略という多角的な視点から分析を行いました。各要素が互いに補完し合うことで、この作品は国内外で大きな評価を獲得し、ファンとの双方向コミュニケーションを通じて一大現象となっています。今後もその影響力は継続し、さらなる発展が期待されると同時に、アニメ業界全体に対する示唆に富んだ成功事例として評価されるでしょう。

    以上の分析から、『ぼっち・ざ・ろっく!』は単なるアニメ作品に留まらず、文化現象、マーケティングの革新、そしてファンとクリエイター間の新たなコミュニケーションモデルを示す、現代のエンターテインメントシーンにおける重要な存在であることが明らかとなります。

    *参考文献:*
    - gigazine.net
    - realsound.jp
    - anime-rating.com
    - prtimes.jp
    - bocchi.rocks
    - anime.eiga.com
    - animekansou.com
    - bibi-star.jp
    - mikiki.tokyo.jp
    - premium.kai-you.net
    - 10th-anniversary.com
    - mangazenkan.com
    - 0115765.com
    - ascii.jp
    - koku-byakunews.com
    - rimosblog.com
    - markezine.jp
    - xexeq.jp

    ---

    ## 今後の研究への課題と示唆

    この分析は、多角的な視点から『ぼっち・ざ・ろっく!』の成功を解明しましたが、将来的には、さらに進化するデジタルメディア環境や新たなコラボレーション戦略の変容、及び視聴者行動の変化に注目する必要があります。これにより、次世代アニメの成功要因や、ブランド戦略の新しい潮流が明らかになるでしょう。

    ---

    以上の考察を通じ、『ぼっち・ざ・ろっく!』の成功は、創作側の高い技術力と視聴者との共感、そして市場を超えた多面的なマーケティング戦略が融合した結果であると結論付けることができます。今後のアニメ業界における先駆的なモデルとして、本作の動向は注視されるべき重要なテーマとなるでしょう。



    =====FOLLOW UP QUESTIONS=====


    Follow up questions: 他のアニメ作品と『ぼっち・ざ・ろっく!』のマーケティング戦略を比較するとどのような違いがあるか?
    作品の受賞歴がファン層の拡大にどの程度寄与しているかをさらに掘り下げるには?
    ソーシャルメディアの反響が今後のプロモーション戦略にどのように影響し得るか?
    国内外での評価の違いが作品の今後の展開にどのように反映される可能性があるか?

    OpenAI Agents SDK で シンプルなDeep Researchクローンを実装する pic.twitter.com/yDxBFrJRbt

    — 布留川英一 / Hidekazu Furukawa (@npaka123) April 3, 2025

    次回



     
     

    npaka

     
     
    プログラマー。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガジェット。年2冊ペースで技術書を執筆。アニソン / カラオケ / ギター / 猫 twitter : @npaka123

    あなたへのおすすめ