TECH PLAY

株式会社AI Shift(株式会社サイバーエージェントグループ)

株式会社AI Shift(株式会社サイバーエージェントグループ) の技術ブログ

79

こんにちは、AIチームの長澤 ( @sp_1999N ) です。 Claude Codeなどを代表として、さまざまなプロダクトやツールでAIエージェントが提供されています。 AIエージェントを構築する場合、評価が大切になりますが、その挙動はマルチホップ・マルチターンを前提としているため、一問一答的な評価では不十分なことがあります。 本記事では、 OpenEvals を使ったマルチターン対話のシミュレーションと、 Langfuse による実験管理・評価の実践方法を紹介します。 (なおLangfuseについてはすでにデプロイ済みの環境を利用します。セルフホストの仕方などが気になる方は公式ドキュメントをご参照ください。) 本記事で構築するシステムの全体像 まずはじめに、本記事で構築するシステムの全体像を紹介します。 このパイプラインでは、以下の3つのステップで評価を行います。 データセット準備: 評価したいペルソナとシナリオをDatasetとしてLangfuseに登録 シミュレーション準備: OpenEvalsで仮想ユーザーとエージェントのマルチターン対話を自動生成する枠組みを準備 実験・評価: Langfuseで対話全体を追跡し、llm-as-a-judge を使って評価 なお、本記事で利用するコードは こちらのリポジトリ で公開しています。 本記事での実験設定 AIエージェントに対するマルチターン評価の方法としては、さまざまなものが研究・提案されています。 いくつかの論文では、エージェントとしてのLLMを部分観測マルコフ過程として定式化し、最終的な出力や対話終了時の系の状態を評価の対象として取り扱っています。 例えば、有名な τ-bench の論文では「対話を通してユーザーに必要な情報を全て提供できていたか」と「最終的な(予約システム)データベースの状態が、正解として事前定義したものと合致しているか」を評価の対象としています。 また他に、 TravelAgent などの論文では、合理性やパーソナライゼーションの観点から人間がスコアリングするという「柔らかな」評価方式を採っています。 今回は問題設定として「ペルソナとシナリオ」を用意し、以下の3つの観点をllm-as-a-judgeで評価する形式にしたいと思います。それぞれの観点でプロンプトにルーブリックを用意し、1-5のスケールでスコアを付けます。詳細については prompt のフォルダをご参照ください。 Actionability: エージェントの提案や回答がユーザーにとって実際に実行可能で、具体的かつ実践的であるかを評価 Adaptability: アシスタントがユーザーの理解度、感情状態、緊急度などに応じて、説明のトーン・詳細度・応答スタイルを適切に調整する能力を評価 Coherence: 複数ターンにわたる対話が論理的に一貫しており、人間同士の自然な会話のように流れているかを評価 対話は最長でも3ターン(エージェントの発話が3回まで)とします。 Step1: 実験データの準備 今回はLangfuseの Datasets機能 を使って実験データの作成・管理を行います。SDKを使って作成することもできますし、csvファイルを直接アップロードすることも可能です。今回は前者で行います。 def create_dataset(): try: dataset = langfuse.create_dataset( name='simulated-conversations', description='ペルソナとシナリオを設定した合成データ' ) logger.info(f"Dataset created successfully: {dataset}") except Exception as e: logger.error(f"Error creating dataset: {e}") return None try: langfuse.create_dataset_item( dataset_name="simulated-conversations", input={ "persona": "一人暮らしを始めたばかりの新社会人。料理経験はほぼゼロで、基本的な調理用語も分からないことがある。節約しながら自炊を続けたいと思っている。", "scenario": "スーパーで鶏むね肉が安かったので1kg買ったが、どう保存・調理すればいいか分からない。冷蔵庫は小さく冷凍スペースも限られている。今週中に使い切りたいが、毎日同じ味だと飽きそう。簡単で飽きない調理法のバリエーションを知りたい。" } ) 以下略... このようにして、簡単にデータセットおよびその中身を作成することができます。 作成がうまくいくと、以下のようにコンソールから確認することもできます。今回は用意していませんが、Gold としての Expected Output やメタデータの付与も可能です。VerifiableなタスクではExpected Outputを、実際のトレースからデータセット化する場合はメタデータなどのフィールドを利用すると良さそうです。 LangfuseのDatasets管理画面 Step2: OpenEvalsを使ったマルチターンシミュレーション OpenEvals はLangChainによって開発されているLLM評価用のパッケージになります。LLMやエージェントの評価でよく用いられるものやベストプラクティスをまとめて提供しています。 今回は提供機能の1つである ユーザーシミュレーション を利用します。 def generate_synthetic_conversation( persona: str, scenario: str, max_turns: int=3, config: GenerationConfig | None = None ): app = create_app_wrapper(config=config) # 仮想ユーザー system_prompt_simulated = f"""あなたは以下のシチュエーションにいます: {scenario} あなたの性格は以下の通りです: {persona} 自然に状況を説明し、助けを求めてください。心理状態に基づきながら自然に会話を進めてください。必要に応じてフォローアップの質問をしてください。 課題が解決されたら、感謝を述べて対話を終了してください。""" user = create_llm_simulated_user( system=system_prompt_simulated, model="openai:gpt-4o-mini", ) result = run_multiturn_simulation( app=app, user=user, max_turns=max_turns, ) return result create_llm_simulated_user を使って仮想ユーザーを設定します。今回は利用していませんが fixed_responses のフィールドに必ず発話してほしいエントリを設定することもできます。ペルソナやシナリオに加え、「この発話から始めて欲しい」というケースを考えたい場合に有用な機能になります。 そして、 run_multiturn_simulation で最大ターン数を指定して仮想ユーザーとエージェントの対話を実行できます。今回は実験と評価のステップを明確に分ける設定にしていますが、 trajectory_evaluators を利用することで、返り値に仮想対話履歴だけでなく、その評価結果も含めることが可能です。 評価対象のエージェント 今回はシナリオに沿うように「料理に悩む人々をサポートする」エージェントを簡単に用意します。 LangfuseではLLMに対するプロンプトをコンソールから管理する機能があります。今回はそれを使ってLangfuseからもプロンプトを取得できるようにします。 Langfuseのプロンプト管理画面 Step3: 実験・評価 実験 それではここまでで準備してきたものを使って実際に実験してみます。 Langfuseの run_experiment 機能を使って、データセット内の各アイテムに対してシミュレーションを実行し、結果を記録します。 実行後、Langfuse UIでトレースを確認できます。各対話のターンごとの入出力、使用したモデル、レイテンシなどが可視化されます。 対話トレース例 評価 評価は src/eval.py で行います。実際のトレースデータをLangfuseから取得し、評価結果をスコアとしてLangfuseに登録します。 今回は複数のモデルを比べるように実験してみます。結果はダッシュボードとしても確認することができます。 gpt-5, 5.1 は各指標でパフォーマンスは良いものの、レイテンシが目立つ形となりました。 (チャートは折れ線表示になっていますが、左から右に gpt-4o, gpt-4o-mini, gpt-4.1, gpt-5, gpt-5.1 の順にプロットされています。レイテンシについてはカラムにレスポンス平均値が表示されています。) 複数モデルの結果比較 スコアの登録は submit_score を使って行いますが、この時に判断結果などをコメントとして残すことができます。例として、スコアが全体的に低めな4o-mini評価時の判断理由を見てみます。 gpt-4o-miniの実験結果に対する評価コメント スコアの横にある吹き出しマークにホバーすると、評価コメントがポップアップします。 「冷凍スペースが限られている条件への踏み込みも弱い」や「初心者ペルソナに対しては火加減...などがなく...」など、設定を踏まえて対話全体をある程度妥当に評価してくれているように見えます。 このようにして、結果を一元的に管理・可視化できるのもLangfuseの便利な使い方です。 おわりに この記事ではOpenEvalsとLangfuseを用いて、マルチターン対話実験を総合的に実行・管理してみました。両者をうまく組み合わせることで、シングルショットor最終成果物を対象とするだけでは難しい「対話力」についての実験・評価が手軽に実現できました。 なお、OpenEvalsにはエージェント向けにより踏み込んだ、 AgentEvals という姉妹パッケージ(?)も存在します。今回は対象としませんでしたが、ツールコールなどを含むtrajectoryを評価対象とする場合に便利なので、また別の機会に試してみようと思います。 投稿 OpenEvals × Langfuseで始めるAIエージェントのマルチターン評価 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です 今回は文章検索をひとつのパッケージで実現できる txtai を紹介したいと思います。 LLMは強力ですが、手元の社内資料や議事録などの固有データに関する質問には回答することができません。そこで重要になるのがRAG(Retrieval Augmented Generation)です。 固有データを検索し、関連コンテキストを取り出してPromptとしてLLMに渡すことで、固有データに関する質問に対しても回答することができるようになります。実務での活用において、現在も多くの企業がこの仕組みを使っています。 RAGは非常に強力なアプローチなのですが、固有データを保存するベクトルDBや文章を機械的に扱えるようにするためのEmbeddingなど構成が複雑になりがちで、管理コストも高くなってしまう、という課題もあります。 txtaiはall-in-one AI frameworkを自称しており上記のようなRAGによる検索システムを構築する上でややこしくなる各モジュールをひとつのパッケージとして提供しています。 本記事ではtxtaiの簡単な使い方と類似ライブラリと比較したメリットを紹介します。 txtai txtaiはPythonベースのフレームワークで、ベクトル検索やグラフ分析、リレーショナルデータベースを組み合わせたEmbeddings Databaseを中心に構成されています。 今回は扱いませんがyamlでワークフローとして定義をすることも可能なようで、応用の幅も広そうです。 環境構築 Hugging Face Transformers/Sentence Transformers、FastAPIで構成されており、Python 3.10以上の環境が必要です。今回はGoogle Colaboratoryで試します。 pipでインストールできます。 pip install txtai 基本的な使い方 以下に簡単なベクトルの類似度を利用した質問応答の例を示します。 import txtai embeddings = txtai.Embeddings() embeddings.index(["カーナビ", "ラーメン", "ダンゴムシ"]) print(embeddings.search("食べ物は?", 1)) # 第2引数の1は近傍サンプルを最大何件返すか # [(1, 0.33019936084747314)] # 出力: (index, cos類似度) indexが1、つまり"ラーメン"が最も近いという予測でもっともらしいです。import文を除くと3行で文章検索が書けてしまいました。ちなみにEmbeddingsではモデルを指定することができるのですが、デフォルトだと sentence-transformers/all-MiniLM-L6-v2 が使われるようです。 作成したindexは以下の方法でローカルファイルとして読み書きできます。 embeddings.save("{folder_name}") embeddings = Embeddings() embeddings.load("{folder_name}") これがtxtaiの最も基礎的な使い方ですが、ここから検索手法をカスタマイズすることもできます。 1. SQL txtaiは自然文クエリだけでなく、SQLでのクエリも組み合わせることができます。 embeddings = txtai.Embeddings(content=True, objects=True) embeddings.index([{ "text": "カーナビ", "n_char": 4, "text": "ラーメン", "n_char": 4, "text": "ダンゴムシ", "n_char": 5, "text": "カレーライス", "n_char": 6, }]) query = """ SELECT text, n_char FROM txtai WHERE similar('食べ物は?') AND n_char >= 6 """ print(embeddings.search(query, 2)) # [{'text': 'カレーライス', 'n_char': 6}] indexにはテキストに加え、文字数(n_char)も一緒に登録します。基礎的な使い方の部分と同じ質問を投げかけていますが6文字以上でフィルタリングしているので、"ラーメン"はヒットせず、indexに追加した"カレーライス"のみがヒットします。 2. 表層検索 ベクトル検索は優秀ですが、品番など単語の表層が重要になるケースもあります。txtaiはBM25による表層検索もサポートしており、こういったケースにも対応できます。 例えば以下の例を見てみましょう。 embeddings = txtai.Embeddings() embeddings.index(["iphone 13", "iphone 14", "iphone 15"]) print(embeddings.search("15日に買ったiphone 13", 1)) # [(2, 0.4083036184310913)] この場合"iphone 13"をヒットさせたいのですが、デフォルトのベクトル検索だと15日の方に引っ張られてしまい、indexが2、つまり"iphone 15"がヒットしてしまっています。 ここで、keyword=Trueで表層検索をすると、求める挙動通りに"iphone 13"をヒットさせることが確認できました。・ embeddings = txtai.Embeddings(keyword=True) embeddings.index(["iphone 13", "iphone 14", "iphone 15"]) print(embeddings.search("15日に買ったiphone 13", 1)) # [(0, 0.7487186789512634)] ちなみに今回は試しませんでしたが、txtai.Embeddingsでhybrid=Trueを指定すると、ベクトル検索と表層検索のハイブリッド検索も行うことができるようです。 RAG txtaiは実際にRAGを構築することもできます。以下に例を示します。Generate部分は TinySwallow-1.5B-Instruct を使用しました。GPTのようなAPI経由で一般によく使われるモデルも使用できるようなのですが、HuggingFaceに公開されているOpen Weightなモデルと簡単に連携できることがアピールされていたことが理由です。 # 架空の企業の就業規則 data = [ "勤務時間は原則9:30-18:30(休憩60分)で、フレックスはコアタイム11:00-15:00。", "月の所定労働時間は営業日数×8時間。半休は午前(9:30-13:30)と午後(14:30-18:30)。", "リモート勤務は週3日まで、申請は前日18:00までにWorkPortalで行う。", "年次有給休暇は入社日に10日付与し、以後毎年4月1日に11日、12日、…と1日ずつ加算され最大20日。", "病気休暇は年度あたり5日まで(有給とは別枠)。遅刻は30分単位で控除される。", "残業申請は当日中にSlackの#overtimeへ理由と見込み時間を投稿し、翌営業日までに精算する。" ] # IDをつける。なんでも良いが今回はuuid5 embeddings = txtai.Embeddings(content=True, autoid="uuid5") embeddings.index(data) # RAG用のPrompt Template template = """ Contextに応じて質問に答えてください。 Question: {question} Context: {context} """ # Create and run RAG instance rag = txtai.RAG( embeddings, "SakanaAI/TinySwallow-1.5B-Instruct", template=template, output="reference") # RAGの実行 resp = rag("フレックスのコアタイムは何時から何時まで?") print(resp) # {'answer': 'フレックスのコアタイムは **11:00 - 15:00** です。', 'reference': '20316a1f-6aa0-5680-97ba-b5e2e7b8461b'} # 参照先の確認 i = resp["reference"] ref = embeddings.search(f"select id, text from txtai where id = '{i}'") print(ref) # [{'id': '20316a1f-6aa0-5680-97ba-b5e2e7b8461b', 'text': '勤務時間は原則9:30-18:30(休憩60分)で、フレックスはコアタイム11:00-15:00。'}] 質問に対して正しいリファレンスをもとに答えられたことがわかるかと思います。 他のライブラリとの比較 文章検索周りでよく聞くライブラリと比較したメリットを紹介したいと思います。 LangChain LLM周りでは一番よく聞くライブラリなのではないでしょうか?正直、今回試したようなことはLangChainでも実現可能なのですが、コード量が少なくシンプルに書けるという点に関してはtxtaiが優れていると思います。一方で HyDE のような複雑なRAGを実装しようとするならばLangChainの方が柔軟性が高くて良いかもしれません。類似するライブラリとして LlamaIndex などが挙げられます Pinecone いわゆるベクトルDBです。txtaiは pip install txtai だけで動き、データはローカルファイル(SQLite等)として管理されるため、インフラ知識がなくても動かせることがメリットだと思います。類似ライブラリだと Qdrant などがあります。 FAISS Metaが開発したベクトル検索ライブラリです。最近はあまり使っていませんが、以前Chat Bot開発をしている頃はお世話になりました。非常に高速なのですが、前処理などを自分で書く必要があり、そこをよしなに済ませてくれるtxtaiはサクッと試したい時に便利だと思いました。類似するものだと Elasticsearch が代表的なものだと思います。 おわりに 本記事では文章検索を簡単に実装できるtxtaiを紹介しました。 非常に少ないコード量で書くことができ、軽いPoCなどでは活躍するのではないかと考えています。 今回は試しませんでしたが、API化を簡単にできたり、マルチモーダル対応があったりと、まだ興味深い機能があるので、また機会をみて試した上で紹介したいと思います。 最後までお読みいただき、ありがとうございました! 投稿 3行で始める文章検索 ― txtai入門 は 株式会社AI Shift に最初に表示されました。
アバター
※アイキャッチ及びブログ内の画像はNano Banana Proで生成 ============================================ こんにちは、AI Shift、CAIO(Chief AI Officer)の友松です。 この記事は AI Shift Advent Calendar 2025 の25日目、最終日の記事です。 AI Shiftではソリューション事業として、各企業様に AIエージェントに関連したAI活用戦略の立案から構築、運用までを一気通貫で支援 しています。また社内でも「AI協働推進室」を立ち上げ、既存の業務プロセスを前提にしない AI駆動なビジネスプロセスへの抜本的な見直し を進めてきました。 本記事では、「AIエージェント元年」と呼ばれた2025年を振り返りながら、 技術の進化を“PoC止まり”で終わらせず、実際にビジネスプロセスの変革へつなげるには何が必要だったのか これまでの実践を交えて整理していきたいと思います。 1. 2025年、AIエージェント元年。 2025年は、生成AIが「便利なツール」から「業務を遂行する主体」へと明確に進化した年でした。 マルチステップなタスクを自律的に分解・実行する 外部ツールや業務システムと連携する 人の指示を待たずに、目的達成のために判断・行動する こうした AIエージェント的な振る舞い が、研究レベルではなく実務レベルで使えるようになったことが大きな転換点です。 今年3月に、AIエージェントの仕組みや技術的なポイントについて以下の記事でまとめておりますので、そちらもご参照ください。 CAIOが語る、AI Workerの技術ポイント 1.1 MCP(Model Context Protocol)の登場 技術的な転換点のひとつが、 2024年後半に登場した MCP(Model Context Protocol) でした。それまでの生成AI活用では、 モデルごとにツール連携の実装が異なる コンテキストの受け渡しが属人的・アドホック 「どこまでAIに任せてよいか」が設計しづらい といった課題がありました。 MCPは、AIアプリケーションが外部のデータソースやツールと連携する際に、 文脈やツール情報をどのような形式・手続きでやりとりするか 、および どのツールにどの権限でアクセスできるか を標準化するプロトコルです。 AIエージェントと外部ツール/業務システムの接続がシンプルに コンテキスト管理が明示的になり、安全性・再現性が向上 エージェント設計を“プロダクト”として考えられるようになった 結果として、 「とりあえずAPIを叩くAI」から「役割を持った業務主体としてのAI」 への移行が一気に進みました。 ※当社のMCP関連の記事 MCP(Model Context Protocol)を用いた予約対話AIエージェントの構築と動作のトレース MCP ClientをOpenAIモデルで実装する AIエージェントの設計とその勘所 1.2 Multi-Agent, A2A(Agent-to-Agent)の登場 さらに重要な変化として登場したのが、 A2A(Agent-to-Agent) という考え方です。A2Aとは、 役割を持った複数のAIエージェントが 互いに状態や成果物を共有し 協調しながら一つの目的を達成する という マルチエージェント前提の設計思想 です。例えば人の組織と同じように役割分担し、連携します。 情報収集を専門とするエージェント 分析・判断を行うエージェント 実行・オペレーションを担うエージェント このA2Aの登場により、AIは「単体で賢い存在」から 「組織として機能する存在」へ 進化し始めました。 A2Aにおいては、多様なアーキテクチャが次々と提唱され、急速な進化を遂げています。 私個人の意見としてはMulti Agentのベストプラクティスは今後も目まぐるしく変化していくと想像しております。一方でそのためにAgent構築を足踏みするのではなく、Agent自身が業務プロセス上のデータやツールにアクセスし問題を解くための準備を進めることが今のフェーズでとても重要だと感じます。 1.3 Browser Use, Agent Modeの登場 これまで述べてきたAIエージェントの仕組みや設計論に留まらず、 実務でそのまま使えるAIエージェント も次々と登場してきました。 例えば、 browser use を搭載した ChatGPT Atlas や ChatGPT のAgent Mode など、 エージェント自身が ブラウザやマシンを直接操作しながら問題解決を行う タイプのエージェントが利用可能になっています。 これらは、単なるツール呼び出しではなく、 画面遷移を伴う情報収集 視覚的な情報を前提とした操作 ログイン後のWebサイト内での作業 といった、 従来はAPIやMCPが提供されていないために自動化が難しかった領域 にまで、 AIエージェントの適用範囲を広げました。 特に browser use は、ブラウザを人と同じように直接操作するため、 「ツール接続の有無」に依存しないエージェント活用 を可能にしています。現時点では、 セキュリティ観点でどこまで操作を委ねてよいか 誤操作や意図しない遷移への対策 アクセス権限や監査ログの設計 といった点を考慮した上で、 「どこまでエージェントに任せ、どこから人が介入するか」 を慎重に設計する必要があります。 一方で、数年先を見据えると、多くのPC上の操作が AI駆動に置き換わっていく可能性 は十分に考えられます。現時点でも「使える場面では積極的に使っていきたい」と感じる、非常に示唆に富んだエージェントだと言えるでしょう。 1.4 Coding Agentの登場 実務面で特に大きなインパクトを受けたのが、 エンジニアリング領域 です。 Cursor 、 Claude Code 、 CodeX などをはじめとする Coding Agent の普及により、ただ単に「コードを書く」だけではなく、設計, 開発, レビュー, テスト, リリース, 運用といった エンジニアリングプロセス全体 に関与し、プロセスそのものの変革に寄与しています。 エンジニアリング領域にAIエージェントが自然に浸透した背景には、コーディング能力の飛躍的な向上だけでなく、 以下の3点が大きかった と考えています。 エンジニアリングプロセスの多くのデータが GitHub に集約されており、 一元性とアクセス性が非常に高いこと IDE やエディタなど、普段使いのツールの中からそのまま利用できること エージェントの効果を体感するまでの利用ハードルが低いこと これらの条件が揃っていたことで、エンジニアリング領域では AIエージェントが「試される前に、使われ始めた」 と言える状況が生まれました。 この事例が示しているのは、AIエージェント活用の成否は、モデル性能よりも 業務データの集約度・ツールとの距離・試しやすさ に大きく依存するという点です。 エンジニアリング領域で起きている変化は、今後、他のビジネス領域へAIエージェントを展開していく上での 重要なヒント になるはずです。 1.5 画像生成の飛躍 昨年の今頃は、 DALLE-3 が台頭し、 プロンプトひとつで様々な画像を生成できるようになった という点で大きな進化がありました。一方で、 制御性という観点ではまだ課題が多い のも事実でした。 意図した構図やトーンにならない 修正を重ねるとブレていく 業務で求められる一貫性を保ちづらい そのため、ブログのサムネイル生成などでは活躍するものの、 もう一歩踏み込んだ実務用途では使いづらい という印象を持っていた方も多いのではないでしょうか。 今年発表された Nano Banana では、画像生成・画像修正の能力が 飛躍的に向上 しました。具体的には、 画像に対するスタイル変換をプロンプトで自然に指示できる スライド1枚分の要約を、視覚的に表現できる といった形で、 「きれいな画像を作る」から「業務で使える画像を作る」 へと、明確にフェーズが変わったと感じています。あと1-2年もすると、スライドを0から作成するという業務は無くなるかもしれないと感じるようなリリースでした。 ※当社のスライド生成関連の記事 企業向けスライド生成AIエージェントをPythonとGPT5で作ってみた 2. AI駆動なビジネスプロセスへ 2025年を振り返ると、 「個人がAIを活用する」 という観点では、モデル性能の向上やプロトコルの確立によって、活用は一気に加速しました。 一方で、 組織としてAI駆動なビジネスプロセスへの変革を行う という点に目を向けると、そこには まだいくつもの乗り越えるべき壁 が存在しています。個人レベルでは、 コーディングや資料作成が速くなる 情報収集や要約が楽になる アイデア出しの初速が上がる といった変化をすぐに実感できます。しかし、それらがそのまま 業務プロセスの置き換え 組織全体の生産性向上 意思決定やオペレーションの変革 につながるかというと、決してそうではありません。この状態だと、エージェントを使いこなしている人と使いこなしていない人の差が広がっていき、業務適用範囲は一部にとどまり、組織全体としての効果は小さくなってしまいます。 2.1 点の業務ではなく線のプロセスとして考える 組織のAI化の第一歩として、まず行うべきは As-Is と To-Be の整理 です。ここでやってしまいがちなのが、「既存の業務フローのどこがAI化できるか」という発想から入ってしまうことです。この考え方では、AIはどうしても 既存業務の延長線上にある効率化ツール に留まってしまいます。 日々私たちが行っている業務は、それ単体で完結するものではありません。多くの場合、前後の業務と複雑に絡み合った 一連の業務プロセス として存在しています。 例えば、営業プロセスにおける「提案資料を作る」という業務を考えてみます。提案資料を作ること自体は一見すると「点の業務」に見えますが、その先には「提案する」という行為があり、さらにその結果として受注・失注という成果につながっていきます。この業務は、明確な“線”の中に位置づけられているのです。 実際に人が提案資料を作成するときには、自社製品の強みを踏まえた訴求や、過去の提案時のお客様の反応、失注理由として挙がった懸念点、次回の提案で強調すべきポイントなど、関連する複数の業務プロセスのコンテキストを無意識のうちに反映しています。 これは、提案資料作成という業務が、マーケティング活動や営業のヨミ会、商談の議事録、過去の受注・失注データといった 営業プロセス全体と強く結びついている からです。 もし「提案資料作成」という点の業務だけを切り出してAI化してしまうと、それなりに綺麗な資料は作れるようになりますが、受注率そのものは変わらない、という結果になりがちです。 一方で、営業プロセス全体を俯瞰し、どの情報がどの判断に使われ、どこで次のアクションにつながっているのかを整理した上でAIを組み込むことができれば、単なる 業務生産性の改善 にとどまらず、 真のビジネスインパクトをもたらすプロセス変革 へと昇華させることができます。 2.2 ツールへのアクセス/データの再構築:土台を作る 業務プロセスをAIエージェント化するうえで、避けて通れないのが 業務データや業務ツールにエージェントがどのようにアクセスできるか という問題です。どれだけ高度なAIエージェントを設計したとしても、必要な情報にアクセスできなければ、業務を成立させることはできません。 SaaS製品の場合、APIが一定程度整備されていることも多く、それらと接続することでエージェント化が比較的スムーズに進むケースがあります。一方で、自社開発システムや受託開発で作られた業務アプリケーションでは、そもそもAPIが存在しなかったり、外部からのアクセスを前提にしていなかったりすることも少なくありません。この場合、「AIエージェントをどう作るか」を考える以前に、 業務システムそのものをどう開いていくか という課題に直面します。 さらに、SaaS製品であっても、もう一つの壁として浮かび上がるのが 権限設計 です。人によって閲覧・操作できる情報が異なる中で、その権限をどのようにAIエージェントへ切り出して渡すのか。そもそもSaaS側に、そうした細かな権限分離の仕組みが用意されていないケースもあります。「どのエージェントに、どこまでの権限を与えるのか」という設計を曖昧にしたまま進めてしまうと、セキュリティやガバナンスの観点で、後から大きな問題になる可能性があります。 また、2.1で述べたように、業務を点ではなく線のプロセスとして捉えるためには、 プロセス内で何が起きているのかを示すログや履歴 が不可欠です。しかし実際の現場では、業務データの入力は人にとって非常にコストが高く、日々の業務に追われる中で後回しにされたり、結果として記録の粒度や精度が下がってしまうことがよくあります。入力項目を増やしたり、詳細な記入を求めたりするだけでは、長期的に質の高いデータを維持することは難しいのが実情です。 だからこそ重要になるのが、 人に入力させる前提そのものを見直すこと です。業務の中で自然に発生する操作ログやイベント、会話や議事録の自動生成、ツール間のイベント連携など、仕組みで解決できる部分は極力仕組みに任せるという発想が求められます。データを「入力するもの」から「自然に記録されるもの」へと転換することが、AI駆動なビジネスプロセスを支える重要な土台になります。 ツールへのアクセス設計やデータの再構築は、AI活用の文脈ではつい後回しにされがちなテーマです。しかし実際には、 この部分をどれだけ丁寧に設計できるかが、AIエージェント活用の成否を大きく左右します。 2.3 人とAIの役割分担を再定義する:価値に集中する AI駆動なビジネスプロセスを設計するうえで、 最も重要で、かつ難しいのが 人とAIの役割分担をどう定義するか という点です。多くの現場では、 「どこまでAIに任せてよいのか」「人がやるべき仕事は何なのか」 という問いに対して、明確な答えを持てていません。 その結果、 AIは常に人の指示待ちになり、人はAIのアウトプットを細かくチェックし続ける という、 効率化どころか負荷が増える状態 に陥ってしまいます。これは、 AIの性能の問題ではなく、役割設計の問題 です。 AI駆動なプロセスでは、 「AIに何をやらせるか」から考えるのではなく、「このプロセスの中で、人が本来担うべき価値は何か」から考える必要 があります。 意思決定、責任の所在、例外対応、価値判断 といった部分は、引き続き 人が担うべき領域 です。一方で、 情報収集、整理、選択肢の生成、過去事例との照合 といった作業は、 AIが主導した方が圧倒的に効率的 です。 重要なのは、 「自動化できるかどうか」で線を引くのではなく、「人がやる必然性があるかどうか」で線を引くこと です。 人がやらなくてもよい仕事をAIに任せるのではなく、人がやる意味のある仕事だけを人に残す 。この 発想の転換 が、 AI駆動なプロセス設計の核心 になります。 また、役割分担は タスク単位ではなく、プロセス単位で考える必要 があります。ある一部分だけをAIに任せても、その前後が人依存のままであれば、 プロセス全体としてはボトルネックが解消されません 。 AIがどこから入り、どこまでを一気通貫で担い、どのタイミングで人が介入するのか 。この 流れを明確に設計すること が重要です。 ここで鍵になるのが、 「人は常に正解を出す存在である必要はない」という前提 です。 AIが生成した複数の選択肢に対して、どれを採用するかを決める、あるいは方向性だけを示す 。そのように、人の役割は 「実行者」から「判断者・設計者」へとシフト していきます。 AIエージェントが前提になると、 求められるスキルも変わります 。 作業を正確にこなす力よりも、問いを立てる力、判断基準を言語化する力、例外を扱う力 が重要になります。これは 一部の専門職に限った話ではなく、組織全体に共通する変化 です。 AI駆動なビジネスプロセスとは、 人の仕事を奪うものではありません 。むしろ、 人が本来向き合うべき意思決定や価値創出に集中できる状態を作るための仕組み です。そのためにも、 人とAIの役割分担を曖昧にせず、意図をもって設計すること が欠かせません。 3. 業務プロセスに入り込み、線でAI化するAI ShiftのAIソリューション事業 AI ShiftのAIソリューション事業は、単一業務の効率化に留まらず、 業務プロセス全体に入り込み、線でAI化を実現すること を特徴としています。 第2章で述べた通り、業務は個々のタスクが連なったプロセスとして成立しており、真の価値創出にはプロセス全体を見据えたAI設計が不可欠です。 そのためAI Shiftでは、 コンサルティング・FDE(Forward Deployed Engineer)・AIエージェント構築プラットフォーム を三位一体で提供しています。コンサルタントが顧客業務を深く理解し要件を整理し、FDEが顧客ごとの業務環境に合わせてAI業務プロセスを素早く個別構築します。さらに、その構築知見はプラットフォームに蓄積され、次のAI化を加速させていきます。 この循環により、PoCで終わらない 実運用を前提としたAI業務プロセスの実装 が可能になります。AI Shiftのソリューションは、ツール導入ではなく、 業務の流れそのものを変革するAI化 を実現するサービスです。 ※FDEに関する記事は昨日、一昨日の記事をご参照ください Forward Deployed Engineer(FDE)職はじめました Forward Deployed Engineer(FDE)とは?お客様の業務改革を技術でリードするエンジニアの実像 最後に AI駆動なビジネスプロセスは、技術を導入すれば一足飛びに実現できるものではありません。業務を点ではなく線として捉え直すこと、データやツールへのアクセスを再構築すること、人とAIの役割分担を設計することなど、乗り越えるべき壁は多く存在します。 一方で、世の中全体は確実にAI駆動な状態へと進んでいます。この流れが止まることはありません。だからこそ重要なのは、完璧な形を急ぐことではなく、 その前提に立ち、構えと準備を進めているかどうか です。 AI駆動なビジネスプロセスに向けた取り組みそのものが、これからの企業にとっての競争力になっていくと考えています。AI Shiftは「人とAIの協働を実現し、人類に生産性革命を起こす」のミッションを掲げ、各企業様のAI駆動化の支援を続けてまいります。 AI Shift Advent Calendar 2025はこれにて完遂です!皆様良いクリスマスをお過ごしください! --------------------------------- AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 投稿 2025年 AIエージェント元年を振り返る〜AI駆動なビジネスプロセスへの変革と実践〜 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI ShiftのLinです。 この記事は AI Shift Advent Calendar 2025 の24日目の記事です。 Lead Forward Deployed EngineerのLinです。東京側のFDEチームと沖縄のAI Operationチームをリードしています。 この記事の要約 この記事では、Forward Deployed Engineer(FDE)という職種について紹介します。FDEは、お客様の業務に最も近い場所で働くエンジニアです。技術とビジネスの両面から課題解決に取り組みます。 FDEの魅力、実際の業務内容、組織構成、そしてFDEチームについての紹介を通じて、この職種の全体像を理解していただけます。 目次 初めに FDEの魅力 実際にどんなことをやっているのか FDEチームについて 最後に 初めに AI Shiftでは2025年12月にForward Deployed Engineer(FDE)職を正式に設立しました。FDEは、お客様の業務に最も近い場所で働くエンジニアです。 単なる技術提供ではありません。お客様の業務に深く入り込み、技術実装から運用まで一気通貫で担います。 この記事では、FDEという職種の魅力、実際の業務内容、そしてFDEチームについてお伝えします。 FDEの魅力 1. 顧客の業務改革を技術でリードする最前線に立てる お客様の課題を深く理解し、技術を駆使して実際の業務改革を実現 「月に数百時間の工数削減ができた」など、お客様からの直接的な感謝の言葉を受け取れる 技術と業務の両面から課題解決に取り組む 2. プロダクトの進化に直接貢献できる お客様に最も近い場所で働くため、得られるフィードバックがプロダクトの進化に直接つながります。 自分の意見が数千、数万のユーザーに影響を与える製品に反映される プロダクトの方向性を左右する重要な役割を担える 3. 圧倒的な成長環境 幅広いスキルセット:フロントエンド・バックエンド、インフラ、AI/ML、データベース、API設計など 技術以外のスキルも同時に磨ける:ビジネスコミュニケーション、要件定義、プロジェクトマネジメント AI時代の最先端で、最も実践的なスキルを身につけられる環境 実際にどんなことをやっているのか FDEの業務は多岐にわたりますが、主な業務内容を以下にまとめます。 主要な業務領域 1. 案件例:営業業務の自動化とデータ分析 KPIアラート分析や営業戦略の自動生成エージェントの開発 AIによるSQL自動生成とデータ分析(前年比分析、トレンド分析など) マルチモーダル出力(視覚的レポートと音声読み上げの両対応) 夜間バッチ処理の設計・実装 2. 既存業務ツールとの統合 Teams連携 : Azure Bot Frameworkを活用したBot構築、自動通知・対話機能 ※画像:Teams Botの例(Teams Botの画面キャプチャ。ユーザーがメッセージを送信し、AIエージェントが応答している様子) Outlook連携 : メール返信下書きの自動生成、リマインド機能、タスク管理連携 カレンダー連携 : 自然文での日程調整、空き時間検索、自動予定登録 ※画像:日程調整エージェントの例(日程調整エージェントの画面キャプチャ。自然文での指示と空き時間候補の表示) 3. データベース設計とSQL生成 既存データベース(BigQueryなど)のスキーマ理解とテーブル設計 LLMによるSQL生成のプロンプト設計と品質評価 データパイプラインの構築とセキュリティ管理 4. プロンプトエンジニアリングとワークフロー最適化 お客様の業務要件に合わせたプロンプト設計・最適化 ワークフローの各ノードでのコンテキスト管理、エラーハンドリング設計 実運用で安定して動作する仕組みの構築 5. 認証とセキュリティ Azure AD認証、OAuth 2.0の実装 お客様のセキュリティポリシーに合わせた認証方式の設計 データのプライバシー管理 6. 資料作成エージェントの開発 PowerPoint資料の自動生成エージェントの開発 テンプレート分析とレイアウト自動判定 ユーザー入力に基づいたスライド骨子の自動生成 段階的な確認プロセスを経た高品質な資料作成 ※画像:資料作成エージェントの例(資料作成エージェントの入力画面) ※画像:資料作成エージェントの出力例(生成されたPowerPoint資料のサンプル) FDEチームについて FDEはAI Shiftのミッション実現の最前線 AI Shiftのミッションは「人とAIの協働を実現し、人類に生産性革命を起こす」ことです。このミッションを実現するためには、単なるSaaSプロダクトの提供では乗り越えられない壁があります。 お客様の業務に徹底的に向き合う FDEは、お客様の業務そのものに入り込みます。システムだけではなく、お客様の業務とそれを利用する方々に徹底的に向き合います。 これこそが真の意味でAIの価値を届けるために必要なアプローチです。 Customer Successが大事にしていること FDEチームはCustomer Success FDE Teamとして、 顧客の成功 を最優先に考えています。私たちが大事にしていることは以下の通りです。 顧客価値の最大化 : お客様のビジネス目標を理解し、AI技術を活用して具体的な価値を提供する 真のパートナーシップ : 単なる技術提供者ではなく、お客様の成功を共に目指すパートナーとしての関係構築 継続的な価値提供 : 導入時だけでなく、長期的にお客様の成長を支援し続ける お客様の声を製品に反映 : 現場で得た知見を製品開発にフィードバックする。より良い製品を提供する FDEは、お客様の成功が私たちの成功であるという信念のもと、日々活動しています。 日本企業のDX課題を解決する鍵 日本の企業は、DXの取り組み自体は行っているものの、人材の質・量ともに不足している状況です。 業務プロセスへのAI組み込みを実現 FDEは、コンサルチームと協働して、業務プロセスへのAIの組み込みと、それに必要なDXを同時に行います。単にSaaSを導入するのではなく、お客様の業務に合わせて徹底的にカスタマイズします。 実行まで担うのがFDEの役割です。 FDEチームの組織構成と責務 FDEチームは、以下の3つの役割で構成されています。 1. Lead Forward Deployed Engineer チーム全体の管理・運営、戦略計画、意思決定 チームメンバーの育成・キャリア開発支援 他チームとの連携強化、エスカレーション対応 ステークホルダーへの報告・コミュニケーション 2. Forward Deployed Engineer 顧客環境での製品導入・統合、アーキテクチャ設計 技術的な問題解決、トラブルシューティング 顧客サポート・トレーニング、技術的質問対応 フィードバック収集・製品改善への貢献 ドキュメント作成・知識共有 3. AI Operation Specialist プロンプトチューニング、ワークフロー構築 精度検証、テストデータセット作成・管理 Forward Deployed Engineerのプロンプト・ワークフロー構築支援 営業・コンサルチームへの技術サポート 東京と沖縄の連携によるチーム成長 FDEチームは、東京側のFDEチームと沖縄のAI Operationチーム(AI Operation Specialist)の2つのチームで構成されています。 案件推進での連携 案件を回す際には、常に東京と沖縄のチームが一緒に動きます。お互いの強みを活かしながら協力して進めています。 沖縄チーム : AI Operation Specialistが自律型エージェントの構築、精度検証、運用業務などを担当 案件での連携 : 東京と沖縄のチームが常に一緒に動き、お互いの強みを活かしながら案件を推進 ナレッジの体系化 : チーム全体の課題を可視化し、解決策を議論(KPTの実施) 合宿での振り返りと目標設定 毎年、東京か沖縄で集めて合宿を実施しています。2025年11月の合宿では、25年度下半期の振り返りと26年度目標の設定を行いました。スキルの偏りを解消するための取り組みも進めています。 ※画像:沖縄出張時の空港(2025年11月の沖縄出張時の空港の写真) ※画像:ハロウィンのオフィスの飾り(2025年11月のハロウィンのオフィスの飾りの写真) 他チームとの連携 FDEチームは、以下のチームと密接に連携しています。 営業・コンサルチームとの連携 営業活動を技術面から支援します。 技術プレゼンテーション、技術質問対応 要件定義の支援、アーキテクチャ設計の実施 実装計画の策定、デリバリー品質保証 プロダクト開発チームとの連携 顧客ニーズをプロダクトに反映させます。 バックログリファインメントにて顧客ニーズを共有 開発状況の把握 AIチームとの連携 高度なAI技術を活用します。 高難度AI(RAG、GraphRAG、文字起こしなど)案件の技術的サポート 自律型エージェントのベストプラクティスと評価方法 FDEチームに期待すること FDEチームには、以下のような能力を期待しています。 技術力とビジネス理解の両立 : 幅広い技術力を持ちながら、お客様のビジネスを深く理解できる 自律的な判断力 : 不確実性の高い環境で、自律的に判断し行動できる 継続的な学習姿勢 : AI・LLMの分野が日々進化する中で、常に最新の技術動向をキャッチアップできる チームでの連携 : コンサルチームやプロダクトチームと連携しながら、大きな目標を達成できる ナレッジの共有 : 個人が得た知見をSlackや案件共有会などでチーム全体で共有できる。組織の知識として蓄積できる 最後に この記事では、Forward Deployed Engineer(FDE)という職種について、以下の内容をお伝えしました。 FDEの魅力 : 顧客の業務改革を技術でリードする最前線に立てる、プロダクトの進化に直接貢献できる、圧倒的な成長環境 実際の業務内容 : 営業業務の自動化、既存業務ツールとの統合、データベース設計、プロンプトエンジニアリング、資料作成エージェントの開発など 組織構成 : Lead Forward Deployed Engineer、Forward Deployed Engineer、AI Operation Specialistの3つの役割で構成 Customer Successの価値観 : 顧客価値の最大化、真のパートナーシップ、継続的な価値提供を重視 FDEの使命 FDEという職種は、AI Shiftが6年間かけて培ってきた「お客様の業務と徹底的に向き合う」という価値観を、組織として明確に打ち出したものです。 単なる技術提供者ではなく、お客様の真のパートナーとして、業務改革を技術でリードしていく——これがFDEの使命です。 日本企業のDX推進におけるFDEの役割 AI時代において、技術とビジネスの両面から課題解決に取り組めるFDEは、これからの日本企業のDX推進において重要な役割を果たしていくでしょう。 採用情報 AI ShiftではFDEの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 Forward Deployed Engineer(FDE)とは?お客様の業務改革を技術でリードするエンジニアの実像 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは。 AI Shift CTOの 青野(brn) です。 本記事は AI Shift Advent Calendar 2025 23日目の記事です。 今回はAI ShiftのForward Deployed Engineer職についてです。 あらたに私が直接統括する組織としてFDEを新設しました。 FDE職はにわかに注目され始めていますが、一体何故設立する必要があるのか、従来のSIとの違い。 それを説明できればと思います。 ちなみに以前は https://www.ai-shift.co.jp/techblog/3113 みたいな記事を書いていました。 今回は固めに組織向けの記事をちゃんと書きましたので御覧ください〜 はじめに AI Shiftではこれまでテック職のポジションとして、ソフトウェアエンジニアとML/DSエンジニアの2職種での採用を行ってきました。 この度、新たに Forward Deployed Engineer(FDE)職 というポジションを明確に打ち出し、採用を進めていくこととしました。この記事では、その理由やFDEとは何か、AI Shiftとしての考えをお伝えします。 FDEとは Forward Deployed Engineer(FDE)とは、2003年に設立されたデータ分析企業Palantirが確立した職種です。 Palantirは政府機関や大企業の複雑な問題を解決するため、エンジニアを顧客の現場に派遣し、技術と業務の両面から課題解決を行うモデルを作り上げました。FDEは単なる技術提供ではなく、顧客の業務に深く入り込み、技術実装から運用まで一気通貫で担う役割です。 このアプローチにより、Palantirは従来のSaaSやコンサルティングでは解決できなかった複雑な課題に対応し、顧客との深い信頼関係を築いてきました。 FDEの特徴 もちろんPalantirが当時置かれていた状況と今我々が向かう市場は異なります。AI Shiftでは、Palantirのモデルを参考にしつつ、自社のプロダクトと市場に適したFDEモデルを構築していきます。 FDEで重要なことは、Palantirの哲学である "Do Things that don't scale, at scale" 。一見スケールしないように見える「顧客課題のど真ん中にエンジニアを置く」動きを、プラットフォームと仕組みでスケールさせるということです。FDEの本質は、エンジニアを問題の近くに置き、現場でしか得られないインサイトを素早くプロダクトに反映することにあります。 具体的には、以下の役割を担います: 要件定義から実装まで :お客様の業務分析、AI導入の要件定義、システム設計、実装、テストまでを一気通貫で担当 プロダクトとの橋渡し :現場で得た知見をプロダクトチームにフィードバックし、プラットフォームの改善に貢献 顧客との伴走 :導入後の運用支援、効果測定、継続的な改善提案まで顧客に寄り添う 技術実装の責任 :AI Worker Platform上での開発に加え、必要に応じて顧客環境に合わせたインフラ構築や外部システム連携も実施 Time-to-first outcomeの最大化 :顧客に最初の価値が届くまでの時間を短縮し、早期に成果を実感できる状態を作る この活動を通じて、FDEは以下の3つの役割を循環させます。 顧客にとっての頼れる技術リード 事業にとっての顧客インサイトナレッジ収集 プロダクトにとっての機能フィードバック AI Shiftの現在 AI Shiftは基本的にBtoB向けのサービスを展開している会社です。私たちは自らのミッションを 人とAIの協働を実現し、人類に生産性革命を起こす と設定し、お客様の業務をAI化することに心血を注いでまいりました。 このミッションを実現するための製品として、AI ShiftではAI Workerブランドのもと、以下の3つのプロダクトをお客様にご提供しています。 Chat :テキストベースのAI対話システム Voice :音声対応のAIエージェント Platform :業務プロセス全体をAI化するための統合プラットフォーム これまでの課題 これらのプロダクトを展開する中で、私たちは単なるSaaSプロダクトの提供のみでは乗り越えられない壁を何度も経験してきました。 具体的には: 個別システム連携の複雑さ :お客様ごとに異なる社内システムや外部APIとの連携が必要で、標準的なSaaS機能だけでは対応できない 業務理解の深さ :AIを本当に活用するには、お客様の業務フローや組織構造、現場のペインポイントを深く理解する必要がある 導入から運用までのギャップ :PoCは成功しても、本番運用に移行する際に技術的・組織的な障壁が発生する 効果測定とPDCA :AI導入の効果を適切に測定し、継続的に改善するための仕組みが必要 私たちは6年間、この壁を乗り越えるべく様々な工夫をしてきました。それが現在、AI Shiftの強みとなっています。 なぜFDE職を設立したのか VoiceAgentでの経験 私たちは2019年にVoiceAgentをリリースして以来、徹底的に顧客へ向き合ってきました。VoiceAgentでは単なる応答の自動化ではなく、実際にオペレーター様が行っている業務の負荷を下げることを考え続けてきました。 そのためには、お客様が持つシステムへの個別対応を常に行い、時にはお客様のAPI設計を一緒に行ったり、事業責任者が電話を使う現場に張り付いてコール対応を観察したりと、地味ですが真の意味での活用を目指しひたむきに走ってきました。 AI Worker Platformでの新たな課題 2024年にAI Worker Platformをリリースして以来、コールだけではなく、ありとあらゆるお客様の業務をAI化できるようになりました。VoiceAgentで培ってきた「お客様の業務と徹底的に向き合う」という価値観を引き継いでいます。 しかし、AI Worker PlatformではVoiceAgentで行ってきた業務のAI化を遥かに超える複雑な対応が必要となりました: 扱う業務領域の広がり :コール対応だけでなく、バックオフィス業務、営業支援、データ分析など多岐にわたる システム連携の複雑化 :複数の社内システム、外部API、データベースなどを横断的に連携させる必要がある ワークフロー設計の高度化 :単一のタスクではなく、複数のプロセスを組み合わせた高度なワークフローを設計する 技術スタックの多様化 :Python、JavaScript、クラウドインフラ、データベースなど幅広い技術領域への対応が必要 ビジネステック職からFDE職へ これらの課題に対応するため、社内で2024年よりビジネステック職を作り採用を行ってきました。 ビジネステック職は、ビジネスと技術の両面を理解し、お客様との接点で活躍する役割として機能してきました。しかし、お客様が増え、案件の複雑度が上がるにつれて、以下の課題が明らかになりました: 技術的深さの不足 :より高度な技術実装やインフラ構築が求められるようになった プロダクト連携の強化 :現場の知見をプロダクト改善に効果的にフィードバックする仕組みが必要 組織としてのスケール :個人のスキルに依存せず、組織として再現可能なモデルを確立する必要がある キャリアパスの明確化 :技術とビジネスの両面で成長できるキャリアパスを明確にする必要がある そこで今回、これらの課題を解決し、より高い技術力と業務理解力を兼ね備えた役割として、正式に Forward Deployed Engineer職 の設立を決定いたしました。 日本企業が抱えるAI活用の課題 ここで、日本企業が抱えるDX・AI活用の課題について見ていきます。これは、FDE職が解決を目指す課題でもあります。 AI活用の前に重要なのが、基本的なデジタル化です。LLM時代において、デジタル化されていないデータは活用が困難です。 さらに、単にデジタル化するだけでは不十分で、価値のあるデータとして整形・保存しておく必要があります。つまり、 AI活用には戦略的なDXの基盤が必要 です。 日本の企業の課題は大きく分けて2つあると考えています。 DX人材の不足 DXの成果が上がっていない 日本のDXについての現状 以下は「DX白書2025」(IPA発行)からの抜粋です。 取組状況 出典: https://www.ipa.go.jp/digital/chousa/dx-trend/dx-trend-2025.html 日本でもDXの取り組み自体は行われており、全体戦略として実施している企業は、アメリカとほぼ同水準です。 人材不足 一方、DX人材の量は圧倒的に不足しています。「大幅に不足している」と答える割合が、アメリカ・ドイツと比べて顕著に高くなっています。 質に関しても同様の状況です。2021年から2022年にかけて質の不足感が増加しており、これは取り組みの増加に人材育成が追いついていないことを示唆しています。 成果の課題 DXへの取り組み成果については、日本では過半数が成果を実感している一方、アメリカ・ドイツと比較すると「成果が出ていない」「わからない」という回答が多くなっています。 成果がわからない理由として、KGI・KPIの未設定が最も大きな要因となっています。 実際、多くの企業でDXの成果目標が未設定となっています。取り組みは進んでいても、その成果を適切に評価する仕組みが不足している状況です。 生成AI活用 生成AI活用については、日本では個人利用が多い一方、業務プロセスへの組み込みは少ない状況です。これは、DXの基盤整備が不十分なまま生成AIの活用を進めようとしていることの表れとも言えます。 AI Shiftが目指すこと これまで見てきた日本のDX課題の根本には、 「思考と実行を高いレベルで繰り返せる人材の不足」 があると考えています。 次々に新たなテクノロジーが現れ、部門間を調整し、戦略を作り直し、実装し、効果を測定し、改善する——この一連のサイクルを回せる人材が圧倒的に不足しているのです。 AI Shiftでは、この課題に対して以下のアプローチで取り組みます: 1. SaaSだけでは解決できない課題への対応 単純なSaaSの導入では、日本企業のDX課題は解決が難しいと考えています。なぜなら: Fit to Standardを実行できる人材が不足 :SaaSに合わせて業務プロセスを変革するには、社内調整、トレーニング、変革管理など膨大な労力が必要 既存システムとの統合の複雑さ :長年運用されてきたレガシーシステムとの統合には、深い技術理解と実装力が必要 業務に即したカスタマイズ :業界や企業ごとに異なる業務特性に対応するには、柔軟な実装が必要 2. コンサルティングとエンジニアリングの融合 AI Shiftでは、 コンサルタントとFDEが協働 することで、戦略立案から実装・運用まで一貫して支援します: コンサルタント :業務分析、AI導入戦略の策定、KPI/KGI設計、組織変革支援 FDE :技術実装、システム統合、ワークフロー開発、運用支援、効果測定 両者の協働 :戦略と実装を行き来しながら、現実的かつ効果的なAI導入を実現 この体制により、「考えるだけで実行できない」「実装はできるが戦略がない」という従来の課題を乗り越えます。 3. AI Worker Platformによる実装基盤 AI Worker Platformは、FDEの活動を支える中核となるプロダクトです。 このプラットフォームを基盤としてAIの業務導入を行うことでフルスクラッチよりも遥かに早く・安くAI導入を進めることが可能です。 主な機能: Workflow開発環境 :ノーコード/ローコードでのワークフロー作成、テスト、バージョン管理 Pythonコード実行 :スケーラブルなフルPython環境で柔軟な処理を実装 エージェント機能 :自律的に判断・実行できるAIエージェントの構築 データ管理 :コンテキスト管理、データベース連携、外部API統合 UI/UX :業務に必要なインターフェースの提供 FDEはこのプラットフォームを基盤としつつ、必要に応じて顧客環境に合わせたインフラ構築や個別開発も行います。ここで重視するのが Time-to-first outcome 、つまり「顧客が最初に価値を感じるまでの時間」を最短化することです。初期は手作業や設定変更であっても構わず早期に価値を届け、得られたインサイトを迅速に反映することで、スケールしないように見えることを仕組みでスケールさせていきます。 4. プロダクトへのフィードバックループ FDEは顧客との接点で得た知見を、継続的にプロダクトチームにフィードバックします。私たちは できるだけ問題の近くにいる リモートでは手に入らないインサイトを手に入れる フィードバックをプロダクトに持ち帰る プロダクトはフィードバックを組み込む という1→4のループをいかに速く正確に回すかを重視します。 機能改善の提案 :現場で必要とされる機能を具体的に提案 バグや制約の報告 :実運用で発見した課題を迅速に共有 ユースケースの蓄積 :成功事例をドキュメント化し、他の導入案件に活用 プラットフォームの進化 :現場の声を反映したプロダクトロードマップの策定 このサイクルがうまく回ると、「新たなコードを書くより設定変更だけで価値を届けられる」ケースが増えます。これは、現場の知見が十分にプラットフォームへ吸収されている組織の健康指標と考えています。 FDEの仕事 ミッション FDEのミッションは、 お客様の業務に深く入り込み、技術でAIDX(AI×DX)を成功に導くこと です。 具体的には: お客様の業務分析と課題の特定 AI導入計画の技術面での設計 AI Worker Platform上でのワークフロー開発 外部システムとの連携実装 導入後の運用支援と効果測定 継続的な改善提案と実装 といったことを進めていきます。FDEは常に顧客の近くに身を置き、質問に即応し、得たインサイトを一般化・抽象化してプロダクトへ還元することで、Time-to-first outcomeを押し上げます。 この仕事の魅力 顧客の業務改革を技術でリードできる FDEは、お客様の業務に最も近い場所で働くエンジニアです。単にコードを書くだけではなく、お客様の課題を深く理解し、技術を駆使して実際の業務改革を実現します。お客様からの直接的な感謝の言葉を受け取れるのは、FDEならではの醍醐味です。 プロダクトの進化に貢献できる FDEは顧客との接点で得た知見を、プロダクトの進化に反映させることができます。現場の声を製品に届け、次のバージョンで実装される——そんなサイクルの中心にいることができます。 幅広い技術スキルを磨ける FDEに求められるスキルセットは幅広く、フロントエンド・バックエンド開発、インフラ構築、AI/ML、データベース設計、API設計など、様々な技術に触れる機会があります。さらに、ビジネスサイドとのコミュニケーション、要件定義、プロジェクトマネジメントなど、技術以外のスキルも磨けます。 この仕事の難しさ 幅広い技術力が求められる FDEは、お客様にとってAI Shiftの技術面での窓口となります。そのため、幅広いIT・ソフトウェア・インフラの知識が必要となります。 主な技術領域: プログラミング言語(Python、JavaScript/TypeScriptなど) クラウドインフラ(AWS、GCP、Azureなど) データベース設計とSQL API設計とシステム間連携 セキュリティとコンプライアンス LLMとプロンプトエンジニアリング 業務ドメインの理解が必須 お客様の業務は、業界や企業によって大きく異なります。技術だけでなく、お客様のビジネスや業務フローを深く理解し、真の課題を見極める力が必要です。 即座の判断と実行が求められる お客様との打ち合わせの場で技術的な判断を求められたり、導入済みのシステムにトラブルが発生したときに迅速に対応したりする場面があります。このような状況で冷静に判断し、実行できる力が求められます。 継続的な学習が必要 AI・LLMの分野は日々進化しています。常に最新の技術動向をキャッチアップし、それをお客様の課題解決に活かせるかを考え続ける必要があります。 FDEに向いている人 以下は私がFDEに向いていると考える人の特徴です。 技術で実際のビジネス課題を解決することに情熱を持てる人 お客様と直接コミュニケーションを取ることを楽しめる人 幅広い技術領域に興味があり、常に学び続けられる人 不確実性の高い環境で、自律的に判断し行動できる人 チームで協働しながら、大きな目標を達成することにやりがいを感じる人 まあ、つまり技術でビジネスを引っ張っていきたい人って感じです。 まとめ AI ShiftはForward Deployed Engineer(FDE)職を正式に設立しました。 FDEは、お客様の業務に深く入り込み、技術でAIDXを成功に導く役割です。コンサルタントと協働しながら、戦略立案から実装・運用まで一貫して支援し、お客様のビジネス変革を実現します。 私たちは、 AI Worker Platform + コンサルタント + FDE の三位一体で、日本企業のAI活用を推進していきます。「Do Things that don't scale, at scale」を合言葉に、Time-to-first outcomeを最大化し、設定変更だけで価値を届けられる領域を広げ続ける組織を目指します。 詳細な業務内容については、今後FDEメンバーから発信していく予定です。 AI ShiftではFDE職・ソフトウェアエンジニア職を募集しています。 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 Forward Deployed Engineer(FDE)職はじめました は 株式会社AI Shift に最初に表示されました。
アバター
本記事は AI Shift Advent Calendar 2025 22日目の記事です。 はじめに こんにちは、AIチームの村田( @em_portero )です。 LLMを使用することが日常となり、誰しもよいプロンプトを書くことを考える機会が増えているのではないでしょうか。また、 Dia browserのskills や各種コーディングエージェントのカスタムプロンプトなど、あるプロンプトを繰り返し使うことも多いかと思います。 そんな中で、 DSPy や ax などのプロンプト最適化ツールが注目されています。 この記事ではDSPyをラップしてより簡単にプロンプト最適化を実行できるツール、 promptomatix を紹介します。 DSPyやaxそのものの詳細については、本記事では深掘りせず以下の記事の紹介に留めます。 DSPy DSPyで始めるプロンプト最適化 プロンプトエンジニアリングを終わらせるDSPy ax TypeScript版DSPy、axを試してみた promptomatixでできること promptomatixの概要。[Murthy+ '25] Figure 1 より引用。 promptomatixはSalesforceが公開したプロンプト最適化フレームワークです。 DSPyや独自のLLM-baseの最適化バックエンドをラップして、より簡単に最適化が行えるようになっています。 主な機能は以下の3つです。 自動判定などを用いて最適化のconfigを減らす 最適化に必要な合成データ生成 コストとパフォーマンスのバランス調整 自動判定などを用いて最適化のconfigを減らす DSPyでは最適化をするためにconfigを設定する必要があります。例えば、タスクタイプ(QA, 分類, 自由回答など)や入出力のスキーマなどをユーザが実行時に指定しなければなりません。 一方でpromptomatixではタスクの説明を与えるだけでLLMを使用して上記のconfigを推定し、自動的に設定してくれます。 最適化に必要な合成データ生成 一般にプロンプト最適化のためには、訓練データが必要です。実際のタスクの例をいくつか人手で用意する必要があります。 promptomatixではタスクの説明を与えるだけで多様で高品質な訓練データを自動生成し最適化を行います。この場合はzero-shotでの訓練データ生成ですが、追加でいくつかのデータシードを設定し訓練データを拡張するfew-shot設定も可能です。 コストとパフォーマンスのバランス調整 2つのpromptがあるとき、同じパフォーマンスを引き出すことができるならばトークン数は少ない方がコスト面などから優れていると言えます。 promptomatixでは以下の式で最適化を実行し、λを調整することでプロンプト長を調整することができます。 promptomatixの最適化目標。[Murthy+ '25] より引用。 実際に最適化してみた ここからは実際にプロンプトを最適化し、学術ベンチマークでどのくらいの差が出るのかを簡単に検証していきます。 環境構築 基本的にはGithub Repositoryの指示に従うことで実行環境をすぐに整えられます。 クローンして仮想環境にインストール git clone https://github.com/airesearch-emu/promptomatix.git cd promptomatix ./install.sh 仮想環境を有効化 source promptomatix_env/bin/activate APIキーの設定 echo 'OPENAI_API_KEY="your_openai_api_key"' >> .env テストデータ 最適化の前後での性能を比較するために学術的なベンチマークを利用します。今回は日本語マルチタスクベンチマークである、 JMMLU の一部(約150問)を使用しました。 JMMLUは 問題, 選択肢A, 選択肢B, 選択肢C, 選択肢D, 正解 のような4択問題です。 問題例 問題:日本の最東端の島はどこか? 回答:D  A:与那国島  B:沖ノ鳥島  C:屋久島  D:南鳥島 問題:衆議院議員の任期は何年か? 回答:A  A:4年  B:3年  C:6年  D:5年 最適化の実行 今回はシードデータを渡して拡張されたデータで最適化する方法を試してみました。 シードデータとして以下のように jmmlu_examples.json として用意します。今回はJMMLUの評価に使わないデータから3件適当に選びました。 [ { "question": "S_5 において p = (1, 2, 5, 4)(2, 3) とする。S_5における<p>の指数を求めよ。", "choices": "A. 8\nB. 2\nC. 24\nD. 120", "answer": "C" }, { "question": "次のうち、体内で尿を集める構造を最もよく表しているものはどれか?", "choices": "A. 膀胱\nB. 腎臓\nC. 尿管\nD. 尿道", "answer": "A" }, { "question": "なぜ空は青いのか?", "choices": "A. 地球の大気を構成する分子が青っぽい色をしているから。\nB. 空は地球の海の色を反映しているから。\nC. 大気が短波長を優先的に散乱させるから。\nD. 地球の大気は他のすべての色を優先的に吸収するから。", "answer": "C" } ] これで実行の準備は完了で、以下のコマンドで最適化が始まります。 今回は最適化エンジンとしてDSPyのMIPROv2、データ拡張などにはgpt-4o-miniをそれぞれ使用します。 タスクとしては日本語の4択について回答する旨を記載しています。 python -m src.promptomatix.main \ --raw_input "Please answer the multiple-choice questions in Japanese." \ --model_name "gpt-4o-mini" \ --model_provider "openai" \ --backend "dspy" \ --trainer "MIPROv2" \ --dspy_module "dspy.ChainOfThought" \ --sample_data "$(cat ../jmmlu_examples.json)" \ --synthetic_data_size 30 数分待つと最適化が完了し、以下のように標準出力に表示されます。 ================================================================================ PROMPTOMATIX OPTIMIZATION RESULTS ================================================================================ 📋 Session Information Session ID: 1766085155.695436 Backend: dspy Task Type: classification ⚙️ Task Configuration Input Fields: ['question', 'choices'] Output Fields: ['answer'] 📊 Performance Metrics Total Cost: $0.494856 Processing Time: 408.446s Prompt Comparison Original Prompt: ──────────────────────────────────────── Please provide answers to the multiple-choice questions in Japanese. ──────────────────────────────────────── Optimized Prompt: ──────────────────────────────────────── In a high-stakes examination setting, you need to demonstrate your understanding of key concepts in algorithms, project management, risk management, and financial analysis. Please provide your answers to the following multiple-choice questions in Japanese, ensuring that you select the most accurate option based on your knowledge. ──────────────────────────────────────── 📚 Synthetic Data Generated Total Samples: 30 Sample Data: Sample 1: question: アルゴリズムの基礎となる概念は何か? choices: A. データ構造 B. プログラミング C. システムエンジニア D. アルゴリズム answer: D Sample 2: question: プロジェクトマネジメントの基礎となる概念は何か? choices: A. リスクマネジメント B. ファイナンスプランニング C. プロジェクトマネジメント D. ファイナンスアナリシス answer: C Sample 3: question: リスクマネジメントの基礎となる概念は何か? choices: A. ファイナンスプランニング B. ファイナンスアナリシス C. リスクマネジメント D. プロジェクトマネジメント answer: C ... and 27 more samples ================================================================================ ✨ Optimization Complete! ================================================================================ 30件のデータ生成および最適化は7分弱で完了し、かかったコストは100円未満でした。 一方で生成された問題の多くは「xxx の基礎」の形式になっており、多様性は欠ける印象です。ここはシードデータの質によって改善できるポイントかと思われます。 最適化後のpromptは以下のようになりました。 In a high-stakes examination setting, you need to demonstrate your understanding of key concepts in algorithms, project management, risk management, and financial analysis. Please provide your answers to the following multiple-choice questions in Japanese, ensuring that you select the most accurate option based on your knowledge. In a high-stakes examination setting や ensuring that you select the most accurate option based on your knowledge のように、より深く "考える" よう促す文言が追加されました。また、 key concepts in algorithms, project management, risk management, and financial analysis ではシード値やそこから合成されたデータに特化したような部分も見られます。 トークン長に対するペナルティはデフォルト値である 0.005 に設定したためか、タスクの複雑度に対してはかなり冗長なトークン数が出力されました。 ベンチマークスコアの比較 前述したJMMLUのサブセットに対して、最適化前後で正解率の比較をします。 以下の base_prompt に最適化前後のプロンプトをそれぞれ代入して比較します。 最適化の効果がわかりやすいよう、対象のモデルは gpt-3.5-turbo としています。 def create_prompt( base_prompt: str, question_data: Dict ) -> str: prompt = f"""{base_prompt} 問題: {question_data['question']} A. {question_data['choices']['A']} B. {question_data['choices']['B']} C. {question_data['choices']['C']} D. {question_data['choices']['D']} """ return prompt 実行した結果は以下の表のようになりました。 prompt 正解率 オリジナル Please provide answers to the multiple-choice questions in Japanese. 49.69% 最適化後 In a high-stakes examination setting, you need to demonstrate your understanding of key concepts in algorithms, project management, risk management, and financial analysis. Please provide your answers to the following multiple-choice questions in Japanese, ensuring that you select the most accurate option based on your knowledge. 52.20% 最適化後 ✓ ✗ オリジナル ✓ 39.6% 10.1% ✗ 12.6% 37.7% 混合行列にあるように正解から不正解になる例もありましたが、全体としては最適化によりおよそ2.5ポイントの向上となりました。 今回はJMMLUという幅広いタスクだったことで改善は限定的でしたが、より狭いドメインやユースケースでは更なる改善も見込めるかと思います。 おわりに この記事ではpromptomatixを紹介しました。特にデータ合成がラッピングされている点は実用性があるなと感じます。一方、執筆時点では最適化手法の一つであるGEPA [Agrawal+] を使用できない(GEPAが動作するdspy>=3.0に対応していない)ことは注意です。 より詳細な機能や比較実験は論文 [Murthy+ '25] にあるのでぜひご参照ください。 AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 【promptomatix】LLMのベンチマークスコアを7分、100円であげる は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの干飯( @hosimesi11_ )です。 この記事は AI Shift Advent Calendar 17日目の記事になります。今回は、ナレッジDBとして使用して2種類のS3バケットを使用し、高コスパなチャットシステムを作成しました。本記事で扱ったコードは こちら で公開しています。 はじめに 生成AIのプロダクトへの組み込みが増えるにつれて、検索システムの重要性も高まっています。さまざまなマネージドなナレッジDBが増え、ユーザーがインフラを意識せずに運用できるようにもなってきています。しかし、一般的に本番運用を始めるとナレッジDB内のデータは増え続けるため、コストが大きな課題になります。今回は、最近GAされたばかりのS3 VectorsとS3 Tablesを使って、比較的高コスパなナレッジ検索システムを作りたいと思います。 本日作るシステムの構成図は以下の通りになります。簡易的にするため、UIは作らず標準入力でユーザーからのクエリを受け付けます。 前提知識 S3のバケットタイプ 現在、S3バケットには以下の4種類が存在しています。 汎用バケット あらゆるユースケースに対応した一番スタンダードなバケットです。あらゆるデータ形式のファイルを保存することができ、フラットなストレージ構造を持っています。 ディレクトリバケット 低レイテンシーかつ、データレジデンシーを必要とするユースケース向けに最適化されています。汎用バケットとは違い、オブジェクトを階層的に整理します。 テーブルバケット 構造化データ用のバケットであり、表形式データの保存に適しています。分析や機械学習のユースケースに最適化され、表形式データをApache Iceberg 形式で保存します。 ベクトルバケット 一番最新のバケットタイプで、ベクトル検索のユースケースに最適化されています。埋め込みモデルによって作成されたベクトルデータをベクトルインデックスを効率的に保存し、検索できるようにします。 この中でも今回は特に テーブルバケット と ベクトルバケット を使いたいと思います。 テーブルバケット テーブルバケットは、東京リージョンでは2025年1月にGAされました。構造化データを Apache Iceberg 形式でS3上に保存し、 Amazon Athena 、 Amazon Redshift 、 Apache Spark などとネイティブ統合されているため、一般的なクエリエンジンを使用してクエリを実行できます。データ増加に対して自動でのテーブル最適化やスナップショットなどを備えており、Iceberg形式データに対する分析はS3 Tablesで完結が可能になります。 2025年12月現在の東京リージョンの料金体系は以下の通りです。 ベクトルバケット S3 Vectorsは2025年7月15日にベータリリースされ、つい先日の2025年12月15日にGAされました。埋め込みモデルにより作成されたベクトルをベクトルインデックス内に整理して検索ができるようになります。現在は距離関数として、コサイン類似度とユークリッド距離が使うことができます。S3 Tablesと同様にデータ増に対しても自動的に最適化も行われます。また、 Amazon SageMaker Unified Studio や、 Amazon Bedrock Knowledge Bases ともネイティブに統合されており、それらからの検索も可能になります。また、 Amazon OpenSearch Service ともネイティブ統合されているため、低レイテンシが求められる検索はAmazon OpenSearch Serviceに、コールドデータはAmazon S3 Vectorsに保存するといった用途にも使うことができます。 2025年12月現在の東京リージョンの料金体系は以下の通りです。 前準備 実際に2つのS3バケットをナレッジDBとして使用するための前準備を行います。 環境構築 uv を使用して環境を構築します。AWSのリソースを操作するため、boto3も追加します。 uv init uv add boto3 また、データ処理などに必要になるライブラリ群もインストールしておきます。 uv add pypdf pydantic ruff ty python-dotenv 扱うデータ 今回、非構造化データとして、サイバーエージェントが公開している AI/Data Technology Map のPDFを使用します。こちらは各プロダクトのAI/Data領域の技術を紹介する資料で、面白いのでぜひご覧ください。構造化データとしては、直近のサイバーエージェントの 売り上げデータ をCSVファイルで用意します。 ベクトルデータ 非構造化データの元となるPDFファイルを保存するために、まずS3の汎用バケットを用意します。 aws s3 mb s3://ais-advent-calendar-2025-standard --region ap-northeast-1 次に、ダウンロードしておいたPDFファイルをアップロードします。 aws s3 cp AIDataTechnologyMap.pdf s3://ais-advent-calendar-2025-standard/ 続いて、ベクトルデータを保存するためのベクトルバケットを作成します。 aws s3vectors create-vector-bucket --vector-bucket-name ais-advent-calendar-2025-vector その後、インデックスを作成していきます。インデックス作成時には、ベクトルの次元数やデータタイプ、距離関数を設定します。ここでの設定値は、使用する埋め込みモデルによって異なるため、利用するモデルに合わせて設定してください。今回はAmazon Bedrockの Titan Text Embeddings V2 を使用するため、ベクトルの次元数は1024に設定します。 aws s3vectors create-index --vector-bucket-name ais-advent-calendar-2025-vector --index-name ais-advent-calendar-2025-vector-index --data-type float32 --dimension 1024 --distance-metric cosine 今回は、Bedrockでベクトルを生成したものを直接S3 Vectorsに入れていきます。PDFファイルをベクトル化する手法は様々ありますが、今回は pypdf を使って以下のステップで簡易的に行います。詳細は実装を参照ください。 PDFファイルからテキストの抽出 適当なサイズへのチャンキング Embeddingモデルでのベクトル化 ここで作成したベクトルインデックスをAmazon BedrockのKnowledge Basesと統合し、ベクトルデータベースとして扱う方法もあります。2025年12月現在、Knowledge Bases経由で扱うにはS3に保持されているデータに限られ、Webのクローリングデータは非対応です。 注意点として、S3 Vectorsのメタデータに元のテキストを入れないと、検索結果としてテキストを取得できないため、必ず含める必要があります。 テーブルデータ 次に、S3 テーブルバケットを作成します。 aws s3tables create-table-bucket --name ais-advent-calendar-2025-table 続いて、Amazon Athena経由でアクセスするためのネームスペースを作ります。ネームスペースにはハイフンが使えない点に注意が必要です。 aws s3tables create-namespace --table-bucket-arn "your-arn-name" --namespace ais_advent_calendar_2025_table_namespace 次に、上記のCSVデータに対応するテーブルスキーマを定義します。 aws s3tables create-table --table-bucket-arn "<your-arn-name>" --namespace ais_advent_calendar_2025_table_namespace --name ca_sales_table --format 'ICEBERG' --metadata '{"iceberg": {"schema": {"fields": [{"name": "period", "type": "string", "required": true},{"name": "net_sales", "type": "int", "required": false},{"name": "operating_income", "type": "int", "required": false},{"name": "non_operating_income", "type": "int", "required": false},{"name": "non_operating_expenses", "type": "int", "required": false},{"name": "ordinary_income", "type": "int", "required": false},{"name": "extraordinary_gains", "type": "int", "required": false},{"name": "extraordinary_losses", "type": "int", "required": false},{"name": "income_before_taxes", "type": "int", "required": false},{"name": "net_income", "type": "int", "required": false},{"name": "net_income_non_controlling_interests", "type": "int", "required": false},{"name": "net_income_attributable_to_owners", "type": "int", "required": false}]}}}' Amazon Athenaに初めてクエリを投げる際は、クエリ結果を出力するS3バケットを設定する必要があるため、S3バケットを作成し、設定を行います。 aws s3 mb s3://ais-advent-calendar-2025-athena-ouput --region ap-northeast-1 aws athena update-work-group --work-group primary --configuration-updates '{"ResultConfigurationUpdates": {"OutputLocation": "s3://<your-result-bucket-name>/athena-query-results/","RemoveAclConfiguration": false,"RemoveEncryptionConfiguration": false}}' ここまでの準備が完了したら、Athena経由でデータを追加します。 INSERT INTO ca_sales_table VALUES -- FY2021 ('FY2021', 666149, 104070, 861, 548, 104382, 2201, 6173, 100410, 66359, 25117, 41242), -- FY2022 ('FY2022', 709923, 67552, 999, 648, 67902, 1633, 8299, 61236, 38096, 15194, 22901), -- FY2023 ('FY2023', 719451, 22351, 1263, 904, 22710, 1470, 3854, 20326, 9151, 5611, 3540), -- FY2024 ('FY2024', 801236, 40083, 1753, 2121, 39715, 313, 8815, 31213, 20376, 4398, 15977), -- FY2025 ('FY2025', 874030, 71702, 1622, 1581, 71743, 2319, 7835, 66227, 41964, 10296, 31667); 今回は手動でデータを追加しましたが、本番運用においてはAWS Glue Jobなどを使用して自動でデータ追加を行うのが一般的です。 検索システム 先ほど作成した2つのS3バケットに格納されたナレッジに対して検索ができるアプリケーションを実装していきます。 実装 全体の流れとしては以下のような処理を実装します。実装は割とシンプルになっています。 ユーザー入力を受け取る LLMで「SQL検索(S3 Tables)か / ベクトル検索(S3 Vectors)か」をプランニングする 各処理を行う S3 Tablesの場合:AthenaでSQL実行 → 結果をコンテキスト化 → LLMで回答生成 S3 Vectorsの場合:埋め込み生成 → 類似検索 → 結果をコンテキスト化 → LLMで回答生成 各ステップの中身を要約しながら見ていきます。詳細はGitHubの コード を確認してください。 1. ユーザー入力を受け取る 標準入力からクエリを受け付け、 plan_query_with_sql 関数に渡して検索プランを決定させます。 def main() try: while True: try: # ユーザーからの入力を受け取る query = input("\nquery> ").strip() except EOFError: # EOF (Ctrl+D / Ctrl+Z) での終了処理 logger.info("終了") return if not query: continue # ユーザーからの質問(query)をプランニング関数に渡す plan = plan_query_with_sql( query=query, bedrock_runtime_client=bedrock_runtime_client, model_id=BEDROCK_CHAT_MODEL_ID, ) # ... (以降、プランに基づいてAthenaまたはVector検索を実行) ... except KeyboardInterrupt: # Ctrl+C での終了処理 logger.info("終了") return 2. LLMで「SQL検索(S3 Tables)か / ベクトル検索(S3 Vectors)か」をプランニングする クエリ内容に基づいて、S3 TablesかS3 Vectorsかを選択し、S3 Tablesの場合は実行するSQLを生成します。 def plan_query_with_sql( query: str, bedrock_runtime_client, model_id: str, ) -> QueryPlan: # 1. LLMに渡すスキーマとルールの設定(system_textを構築) # 2. Bedrock LLMの呼び出し resp = bedrock_runtime_client.converse( modelId=model_id, system=[{"text": system_text}], messages=[{"role": "user", "content": [{"text": f"ユーザーの質問: {query}"}]}], ) # 3. LLMの応答からJSONを抽出 raw = resp.get("output", {}).get("message", {}).get("content", [])[0]["text"] json_text = _extract_first_json_object(raw) data = json.loads(json_text) # 4. データソースとSQLの抽出 source = data.get("source", "s3vectors") sql = data.get("sql", "") reason = data.get("reason", "") # 5. プランの確定 if source == "s3table" and _is_safe_sql(sql): # ユーザーが指定した期間をSQLに補正・反映 periods = _period_candidates(query) if periods: p = periods[0] # SQLの後処理 # sql = re.sub(...) return QueryPlan(source="s3table", sql=sql, reason=reason) # SQLが無効 or またはs3vectorsが指定された場合 return QueryPlan(source="s3vectors", sql="", reason=reason) 3-1. S3 Tablesの場合:AthenaでSQL実行 → 結果をコンテキスト化 → LLMで回答生成 LLMがS3 Tablesを選択してSQLを生成した場合はAthena経由でSQLを実行します。取得した結果をコンテキストにつめて、LLMによって回答を生成します。 def main() ... if plan.source == "s3table" and plan.sql: ... try: rows = run_athena_query(sql=plan.sql, athena_client=athena_client) except Exception as e: use_table = False athena_error = str(e) logger.exception(f"athena query failed: {e}") logger.info("fallback to s3vectors (athena failed)") rows = [] if plan.source == "s3table": ... context = "\n".join(lines) ... answer = generate_answer_from_context( query=query, context=context, bedrock_runtime_client=bedrock_runtime_client, model_id=BEDROCK_CHAT_MODEL_ID, citation_hint="(table, period) もしくは (table, columns)", ) print("\nanswer>\n" + answer + "\n") continue 3-2. S3 Vectorsの場合:埋め込み生成 → 類似検索 → 結果をコンテキスト化 → LLMで回答生成 S3 Vectorsが選択された場合は、埋め込みモデルでベクトルデータを生成し、S3 Vectorsに対して検索を行います。取得した結果をコンテキストにつめて、LLMによって回答を生成します。 def main() ... hits = search_vector_index( query=query, bedrock_runtime_client=bedrock_runtime_client, s3vectors_client=s3vectors_client, ) ... answer = generate_answer( query=query, hits=hits, bedrock_runtime_client=bedrock_runtime_client, model_id=BEDROCK_CHAT_MODEL_ID, ) print("\nanswer>\n" + answer + "\n") 実行結果 作った検索システムを使用してみます。NL2SQL(自然言語からSQL生成)の部分は、簡易的な実装のため、本番運用時には改善の余地がありますが、意図した通りに検索できていそうです。 uv run src/main.py inference 【実行例 1:ベクトル検索(S3 Vectors)】 query> voice botの技術タグは? 2025-12-14 01:01:44,447 INFO __main__: route=s3vectors reason=voice botの技術タグに関する一般的なナレッジ検索であり、S3ベクター検索が適切です。数値集計や期間による絞り込みは不要です。 2025-12-14 01:01:45,101 INFO __main__: hits: distance=0.505279 AIDataTechnologyMap.pdf_50-51 source_file=AIDataTechnologyMap.pdf page=50 distance=0.658780 AIDataTechnologyMap.pdf_82-83 source_file=AIDataTechnologyMap.pdf page=82 distance=0.668686 AIDataTechnologyMap.pdf_126-127 source_file=AIDataTechnologyMap.pdf page=126 distance=0.677485 AIDataTechnologyMap.pdf_48-49 source_file=AIDataTechnologyMap.pdf page=48 distance=0.679986 AIDataTechnologyMap.pdf_78-79 source_file=AIDataTechnologyMap.pdf page=78 answer># Voice botの技術タグ Voice botの技術タグは以下の通りです: **自然言語処理、音声認識** (source_file=AIDataTechnologyMap.pdf, page=50) 【実行例 2:SQL検索(S3 Tables)】 query> 2025の売り上げは? 2025-12-14 01:01:56,438 INFO __main__: route=s3table catalog=s3tablescatalog/ais-advent-calendar-2025-table db=ais_advent_calendar_2025_table_namespace table=ca_sales_table 2025-12-14 01:01:56,438 INFO __main__: sql: SELECT period, net_sales FROM ais_advent_calendar_2025_table_namespace.ca_sales_table WHERE period = 'FY2025' LIMIT 200 2025-12-14 01:01:58,508 INFO __main__: athena rows: 1 answer> 2025年の売上は **874,030百万円** です。 (参照: ca_sales_table, period=FY2025のnet_sales) コスト 最後に、このシステムのストレージコストを試算します。Embeddingや回答生成の部分はどの場合でもかかるので、ストレージコストのみを計算してみます。例として、構造化データ100GBと非構造化データ(ベクトルデータ化済み)1TBがあり、月間10,000リクエストがあると仮定します。※ 1ドルを160円として計算します。 2種類のS3バケットを使用した場合 サービス データ量 料金計算式(ストレージ + リクエスト) 試算結果 S3 Tables 100GB 160円 * (0.028$ * 100GB + 0.000378$ * 10 (千Req)) 448円 + 最適化費用 (概算) S3 Vectors 1TB (1024GB) 160円 * (0.066$ * 1024GB + 0.0027$ * 10 (千Req)) 10,817円 (概算) 一般的なツールの場合(最小構成) サービス データ量 料金計算式(ストレージ + リクエスト or コンピュート) 試算結果 Cloud SQL 100GB 160 円 * (0.442$ * 100GB) 7,072円 (概算) Open Search 1TB (1024GB) 160円 * (0.026$ * 1024GB +0.334$ * 1 OCU * 730h) 38,991円(概算) 2種類のS3バケットを使用した場合、ストレージ料金とクエリ料金のみですが、諸々入れても1万円前後あれば運用できそうです。一般的なツールの最小構成と比較しても安いのが分かります。もちろんAthenaやGlue、Embeddingや回答生成にも料金はかかりますが、適切なパーティショニングなどをしてスキャン量を減らすことができれば、社内利用などのレイテンシの許容が大きいワークロードには有効な選択肢になると思います。 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 参考 https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/Welcome.html#BasicsBucket https://dev.classmethod.jp/articles/schema-definition-s3tables-createtable-with-awscli/ 投稿 2種類のS3バケットをナレッジDBとして活用する は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI チームの長澤 ( @sp_1999N ) です。 この記事は AI Shift Advent Calendar 2025 の16日目の記事です。 弊社サービスの AI Worker Platform では AI エージェントの監視基盤として Langfuse を採用しています。 Langfuse をはじめとしたエージェント監視基盤については、 こちらの記事 でまとめておりますので、よければご覧ください。 さて、この記事では Langfuse を (GKE で Helm dependency を使って) セルフホストするときに、個人的にハマってしまったポイントを整理してご紹介します。 ドキュメントを詳しく読めばわかるものも含まれますが、何かの参考になれば幸いです! * 本記事は Langfuse v3 を対象としています ハマりポイント:環境変数 Langfuse をセルフホストする場合、ローカルであれば Docker Compose を、クラウド (k8s) であれば Helm chart を使ってデプロイします。 この時、設定すべき環境変数が多数あります。 ドキュメント には丁寧にまとめられており、また GitHub リポジトリ を参考にすれば、Docker Compose を使ってのローカル環境では比較的簡単に立ち上げられます。(実際に git clone して docker compose up すればすぐに使うことができます) 必須なものが必ずしも必須とは限らない ただ少し注意が必要なのが「一見 required として紹介されているが、他の環境変数と組み合わせると必須でなくなる」ものが存在する点です。例えば ドキュメント では REDIS_CONNECTION_STRING は必須ですが、リポジトリで紹介されている Compose ファイルにこの環境変数は存在しません。代わりに REDIS_HOST や REDIS_PORT などが設定されています。 このように「組み合わせで代替できるもの」がいくつか存在します。ただ代替が可能なものは以下のように明記されているため、事前にドキュメントに目を通しておくことをお勧めします。 Connection string of your redis instance. Instead of  REDIS_CONNECTION_STRING , you can also use  REDIS_HOST ,  REDIS_PORT ,  REDIS_USERNAME  and  REDIS_AUTH . 環境変数はもっとある 一方で Environment Variables に載っていない環境変数も設定できます。例えば、プロジェクトなどの初期セットアップを行うために必要な LANGFUSE_INIT_PROJECT_ID などの変数は 各種機能紹介ページ に散在しています。 従って、環境変数でハマらないようにするには、事前にドキュメントページを (Environment Variables だけでなく、関連しそうなページを全体的に) 読んでおくことをおすすめします。 ハマりポイント:Helm values k8s を使ってセルフホストする場合、公式配布されている Helm チャートを使う形になります。 ドキュメント を見ると、概ねアプリケーション設定ごとにどのように values ファイルに変数を設定すべきかが分かります。 ただし、Compose ファイルで設定した環境変数を values ファイルではどのように書けば良いかが明記されていないものもあります。 そんな時は langfuse-k8s のリポジトリにある README を見ることをおすすめします。README には必要な value やその指定方法が網羅されています。例えば langfuse.salt などフィールドが明確に用意されているもの以外は langfuse.additionalEnv にまとめて指定する形になります。 どこに何を書くかで迷子になる ただ「何を additionalEnv に指定するか」にハマりポイントがあります。例えば SSO を実現するための環境変数 として AUTH_GOOGLE_CLIENT_ID などがあり、この辺りのものは additionalEnv に指定すれば問題ありません。しかし、同じページで紹介されている AUTH_DISABLE_SIGNUP という環境変数を additionalEnv に設定するとチャートレンダリングエラーが発生することがあります(厳密な発生条件は validation を参照) AUTH_DISABLE_SIGNUP には、これに相当する langfuse.features.signUpDisabled のフィールドが個別に用意されており、このフィールドから AUTH_DISABLE_SIGNUP の環境変数が ヘルパー関数によって内部的に生成 されます。 このほかにも「 additionalEnv に見せかけて指定フィールドが存在する環境変数」が存在しており、ものによっては「同じ名前の環境変数が重複して k8s マニフェストに登録される」という事象にも遭遇しました。 このポイントにハマらないようにするには README と睨めっこしながらも「実際にレンダリングを行って、意図通りの環境変数が作成されているか」をチェックすることをオススメします。 ハマりポイント:ClickHouse Langfuse ではトレース保存用データベースとして ClickHouse を内部利用します。ClickHouse に関して、ハマりポイントが2つあったのでご紹介します。 いつの間にかいる ZooKeeper Langfuse のドキュメントではその存在を見かけないのですが、実際に k8s でデプロイすると ZooKeeper という Pod が立っていることに気が付きます。この正体は「ClickHouse が内部的に利用する 分散コーディネータ 」になります。ClickHouse を複数レプリカ立てる時に、分散クエリの調整やテーブルメタデータの管理などをしてくれます。 この時、 nodeSelector や tolerations を指定する場合、親の ClickHouse に指定するだけだと不十分になります。子である ZooKeeper にもフィールドが個別に用意されているため、ZooKeeper に対しても指定が必要になる点に注意が必要です。 逆に単一レプリカの non-HA 構成なら ZooKeeper がデプロイされないかというとそういうわけではなく、デフォルトでデプロイされるようになっています。単一レプリカ時は不要になるので、Helm チャートでデプロイされないように指定する必要があります。(e.g., clickhouse.zookeeper.enabled: false ) (一点余談としては、ClickHouse は分散コーディネーターとして ClickHouse Keeper を推奨しているようですので、将来的には Langfuse で ZooKeeper を意識することもなくなるのかなと感じています) PVC 作成ゾーンに気をつけろ これは Langfuse に限った話ではないですが、ClickHouse の Pod がうまく立たない時に遭遇したものになります。 ClickHouse には永続ボリュームが必要なため、PVC (PersistentVolumeClaim) が作成されます。この時、環境や設定によっては「PVC と ClickHouse Pod (k8s node) が存在するゾーンが互いに異なる」ことがあります。このような状況が発生すると、ClickHouse Pod が永続ボリュームをマウントすることができず、Pod が立ち上がってくれない事態が発生します。 これを避けるには clickhouse.persistence.storageClass で standard-rwo を指定するなどの対応が必要になります (GKE)。 standard-rwo は volumeBindingMode として WaitForFirstConsumer を使用しているため、以下のように動作することでゾーン不一致の問題を回避できます。 PodがスケジュールされるまでPVCのバインディングを遅延 Podがスケジュールされるノードのゾーンに合わせてPVが作成される おわりに 今回の記事では、弊社の AI エージェントサービスで利用している Langfuse について、個人的にハマってしまったポイントをご紹介しました。公式の Helm チャートをそのまま利用する場合は強く意識せずともうまく立ち上がる気もしますが、既存のクラスタに取り込むなど、Langfuse を Helm dependency でサブチャート化しようとした時に自分は苦しんでしまいました。 この躓きが誰かの参考になれば幸いです。 投稿 Langfuse セルフホストでハマったポイントをまとめてみる は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの杉山です。 本記事は AI Shift Advent Calendar 2025 15日目の記事です。 昨年のAdvent Calendarでは、 OpenAI gpt-4o-audioを用いた音声データからのエンティティ抽出の検証 という記事を執筆しました。 当時はまだ gpt-4o-audio-preview というプレビュー版のみでの検証でしたが、音声認識(ASR)を介さずに直接音声からエンティティ(ex. 店舗予約に必要な店舗名や商品名など)を抽出できる可能性にワクワクしたのを覚えています。 一方で、滑舌が悪い曖昧な音声に対しては音の近さが考慮できず誤ったエンティティを抽出したり、「音声を認識できません」「話者を特定できません」といったように、回答自体が拒否されるケースが多発するという課題も残りました。 あれから1年。 LLM界隈の進化は凄まじく、GoogleのGeminiやオープンソースモデルなど、画像や音声も入出力できるマルチモーダル対応は当たり前の機能となりました。 そこで今回は、 「1年経った今、同じタスクを最新のAIモデルで実行したら、あの時の課題は解決されているのか?」 を検証してみたいと思います。 検証の背景と目的 音声認識結果を用いたエンティティ抽出の例 前回の検証内容をおさらいします。 電話自動応答における「店舗予約」を想定し、以下の2パターンの音声データから、正しい店舗名(エンティティ)を抽出できるかをテストしました。 明瞭な音声 ( clear_toyosu.wav ) : はっきり「豊洲です」と発話 曖昧な音声 ( ambiguous_toyosu.wav ) : 曖昧に「豊洲です」と発話 それぞれの音声をGoogle音声認識にかけてみると前者は正しく「豊洲です」(confidence=0.97)、後者は「ナースです」(confidence=0.95)と認識されました。前者の場合であれば音声認識をしてNLP的なアプローチでも十分エンティティ抽出することができますが、後者の場合はそうはいかないため音の近さを考慮して抽出してくれることを期待します。 なお、電話自動応答プロダクトでの利用を想定しているため、音声は8kHzにダウンサンプリングしてLLMに入力しています。詳細な課題の説明は昨年の記事をご参照ください。 前回(1年前)の結果は以下の通りでした。 明瞭な音声 : 音声入力からヨミを考慮して正しく「豊洲」を選ぶことができた。 曖昧な音声 : モデルが回答を拒否、または関係のないエンティティを抽出し失敗。 今回の記事では、この音声データに対し現在利用可能な最新モデルで再検証を行います。 検証対象のモデル 今回は比較対象として、以下の最新モデルを使用します。 OpenAI gpt-4o-audio-preview  昨年検証したgpt-4o-audioの後、gpt-audioというモデルがリリースされたが、音声を入力するとレスポンスは返ってくるものの、gpt-4o-audioのように指示に従って回答する時もあれば「音声を確認し、文字起こしと候補の中から最も近いものを選びますので、少々お待ちください。」のように音声が与えられていないような回答をするときもある。  不安定なのか用途が異なるのかは不明だが、今回は検証対象外として昨年同様gpt-4o-audioを使用。なお1年経った執筆時点でもpreviewは外れていないためモデル指定時は注意が必要。 Google gemini-2.5-flash-lite  音声対話中での利用を想定して選択した、Googleの最新の軽量モデル。 Google gemini-3.0-pro-preview  記事執筆時点での最高レベルのモデル。思考モードをoff(thinking budget=0)にできないため、レスポンスには時間がかかる。 検証コード 基本的な処理の流れは前回同様です。音声ファイルをBase64エンコードし、プロンプトと共にモデルへ入力します。 OpenAI 現在のOpenAI SDKではResponse APIが最新ですが、現時点では音声入力に対応していないため、引き続きchat.completions APIを使用する必要があります。 import base64 from openai import OpenAI client = OpenAI(api_key=YOUR_API_KEY) with open(YOUR_AUDIO_FILE_PATH, "rb") as wav_file: wav_data = wav_file.read() encoded_string = base64.b64encode(wav_data).decode('utf-8') completion = client.chat.completions.create( model="gpt-4o-audio-preview", # model="gpt-audio", 指定して動作はするが不安定 modalities=["text"], # 出力のモーダル。音声も出力したい場合は["text", "audio"]とする。 messages=[ { "role": "user", "content": [ { "type": "text", "text": YOUR_PROMPT }, { "type": "input_audio", "input_audio": { # 入力に音声を与える場合はinput_audioとしてbase64エンコードした文字列を指定 "data": encoded_string, "format": "wav" } } ] }, ] ) print(completion.choices[0].message.content) Gemini import base64 import os import google.generativeai as genai from google.generativeai import types genai.configure(api_key=YOUR_API_KEY) generation_config = { "temperature": 0, "top_p": 0.95, "top_k": 10, "response_mime_type": "application/json", } model = genai.GenerativeModel( model_name = "gemini-2.5-flash-lite", # Gemini-3-Proを使う場合は以下を指定 # model_name = "gemini-3-pro-preview", generation_config=generation_config, ) with open(YOUR_FILE_PATH, "rb") as wav_file: wav_data = wav_file.read() encoded_string = base64.b64encode(wav_data).decode('utf-8') response = model.generate_content( [ YOUR_PROMPT, {"mime_type": "audio/wav", "data": encoded_string}, ] ) print(response.text) プロンプトは前回より詳しく、音の近さを考慮したCoTを期待して以下の指示とします。 また、設定を少し難しくするために候補エンティティ集合は数を増やしてプロンプト中の10個としました。 まず、音声の文字起こし結果を出力してください。 次に、入力音声の認識結果から、読み方の近さを考慮して一番尤もらしいものを次の候補から選んでください。 候補:['神田', '杉山', '豊洲', '河野', '八重洲', '渋谷', '青梅', '高津', '銚子', '新宿'] 入力音声は「候補+です」のように発話される可能性があります。 最後に、選んだ理由を出力し、回答は必ず候補の中から選んでカタカナで出力してください。 回答形式は以下のようにしてください。 { "transcription": "", "reason": "", "most_likely_candidate": "", } 検証結果 それでは、結果を見ていきましょう。 以降記載した結果は、出力を詳細化したためその見やすさを考慮し、今回は1回実行しただけの再現性が低い結果であることはご了承ください。また、サンプルコードには記載していませんがリクエストを送ってからレスポンスが返ってくるまでの時間を計測し、Latencyとして結果に載せています。 1. 明瞭な音声 (clear_toyosu.wav) まずは、前回も成功していた明瞭な音声です。 モデル 出力結果 Latency(sec) gpt-4o-audio-preview { "transcription": "豊洲です", "reason": "認識結果の発音は「とよす」ですので、候補の中で最も近いのは「豊洲」になります。", "most_likely_candidate": "豊洲" } 1.569 gemini-2.5-flash-lite { "transcription": "神田です", "reason": "音声認識結果は「神田です」と最も近く、発話内容も「候補+です」の形式に合致しているため。", "most_likely_candidate": "カンダ" } 1.926 gemini-3.0-pro-preview { "transcription": "豊洲です", "reason": "入力音声の認識結果は「トヨスデス」でした。提供された候補リストの中で、読みが「トヨス」である「豊洲」が音声と完全に一致するため選定しました。", "most_likely_candidate": "トヨス" } 24.246 gpt-4o-audio-preview, gemini-3.0-pro-previewは正しく豊洲を選択できています(gpt-4o-audio-previewはカタカナで出力、という指示には従っていませんが)。一方でgemini-2.5-flash-liteは誤ってしまいました。普段使っている感覚だと正解できそうに感じましたが、複数回試しても神田や杉山になりがちで、CoTや文字起こしが苦手だったりといったモデルの癖があるのかもしれません。 2. 曖昧な音声 (ambiguous_toyosu.wav) 次に、前回の課題だった曖昧な音声です。 モデル 出力結果 latency(sec) gpt-4o-audio-preview { "transcription": "たかつです", "reason": "発音が「たかつです」と聞こえたため、候補の中で最も近い「高津」を選びました。", "most_likely_candidate": "高津" } 1.659 gemini-2.5-flash-lite { "transcription": "神田です", "reason": "音声認識結果は「神田です」と最も近く、候補の中から読み方が最も尤もらしい「カンダ」と発音されているため。", "most_likely_candidate": "カンダ" } 1.891 gemini-3.0-pro-preview { "transcription": "Does desu", "reason": "音声認識結果の「Does」は、発音記号で/dʌz/や/doʊz/となり、候補の中で音韻的に最も近いのは「豊洲(トヨス)」です。「Does」の語頭の破裂音/d/は「トヨス」の/t/(有声音と無声音の関係)に近く、語尾の/z/または/s/の響きも共通しています。「八重洲(ヤエス)」は語頭が/y/であり、「Does」の音とは距離があります。", "most_likely_candidate": "トヨス" } 22.629 昨年同様gpt-4o-audio-previewと、gemini-2.5-flash-liteも正解することはできませんでした。一方で、gemini-3.0-pro-previewはついに正解することができました。reasoningの内容も、4年前のAdvent Calendarに書いた記事( 音声認識結果とエンティティ候補との音素列アラインメントによるマッチングの検証 )でやりたいと考えていた モチベーションとしては読みを用いた編集距離やアラインメントよりも、「sh」と「ch」や「hy」と「ky」の類似性を考慮できるのではないかと考えたためです。 のような音的な近さを考慮した(ように見える)エンティティ抽出が行えています。 Latencyに関しては、上2つはどちらも2秒弱でレスポンスが返ってきています。音声対話中にエンティティ抽出だけで2秒弱かかるのは少し遅く感じますが、相槌などで推論時間を稼ぐなどの工夫である程度軽減できそうです。gemini-3.0-pro-previewは20秒以上かかっており、正解はできているもののこのままでは音声対話中に使用するのは厳しそうです。今後思考モードがoffにできるようになったり、gemini 3系の軽量モデルが出た際には都度検証し、プロダクトへの適用可能性を探っていきたいと思います。 終わりに 今回は、1年前に検証したSpeechLLMによるエンティティ抽出を最新モデルで再評価しました。 この1年でのモデルの進化は、単なる精度の向上だけでなく、指示に対する追従性や曖昧さへの対処という点でも進展が見られました。従来のカスケード型音声対話システムでは 音声認識→テキストでの言語理解 が一般的でしたが、汎用的な音声認識器では専門用語のような一般的でないエンティティの認識が困難でした。そういったケースに特化した専用の音声認識モデルがなくても、今回のような仕組みで認識できるようになる可能性が見えてきました。 一方で、抽出(リンキング)対象のエンティティ集合が、たとえば取引先会社名や小売店の取り扱い商品名など数千・数万以上になったときにどうすべきかなどといった問題点も残っており、Latencyの課題も含めLLM一つで全てが解決できるものではなく、我々としては引き続き実運用で使える機能の開発に取り組んでいきます。 AI Shiftでは、こうした最新のマルチモーダル技術をいち早くキャッチアップし、電話自動応答をはじめとするプロダクトの体験向上に取り組んでいます。 音声対話の未来を一緒に作りたいエンジニアの方、ぜひお話ししましょう! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 1年振りに最新のSpeechLLMで音声データからのエンティティ抽出の検証 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI Shiftの東( @naist_usamarer )です。 この記事は AI Shift Advent Calendar 2025 の12日目の記事です。 今回の記事では、ユーザー満足度測定のための評価項目(ルーブリック)をLLMによって自動構築する手法「SPUR」を紹介し、その手法を拡張してオペレーター応対の評価項目の言語化を試みました。 論文紹介: Interpretable User Satisfaction Estimation for Conversational Systems with Large Language Models 本節では、解釈可能なユーザー満足度推定を実現するフレームワーク「SPUR」について紹介します。 1. 背景とアプローチ ChatGPTをはじめとする対話システムが普及するにつれ、システムの評価や継続的な改善の重要性が高まっています。ユーザー満足度を推定することは、これらのシステムの改善点を見つけるために非常に重要です。しかし、MLモデルや埋め込み表現をベースとした従来のユーザー満足度推定は分類性能に焦点を当てているものが多く、「なぜ満足・不満足なのか」という理由の説明や解釈性に課題がありました。 SPUR (Supervised Prompting for User satisfaction Rubrics) は、この課題に対し、LLMを用いて 「人間が理解できる評価基準(ルーブリック)」を自動生成 することで、予測精度と説明性の両立を目指した手法です。 2. SPURの実行プロセス SPURは、「個別の事例から重要なシグナルを抽出」し、それを「一般的なルールにまとめ」、最終的に「新しいデータを採点する」という3ステップで構成されています。 Step 1 教師あり抽出(Supervised Extraction): 会話ごとの重要な「満足・不満足のサイン」を洗い出す まず、正解ラベル(満足/不満足)が付与された対話ログをLLMに入力します。LLMは、ユーザーの発話に基づき、会話履歴の中から「ユーザーが満足(あるいは不満)を示した根拠」となる具体的なパターンを抽出し、満足度の判断材料となる記述を生成します。1つの会話から抽出する理由は 最大3つ とし、最終的に各会話に紐付いた具体的な評価根拠のリストが得られます。 Step 2 ルーブリック要約(Rubric Summarization): 頻出パターンを特定し、ドメイン共通の汎用ルールを作る Step 1で抽出された「個別の理由」には表現のばらつきがあり、また各会話内でのそれらの重要度も一定ではない可能性があります。このステップでは、それらを束ねて要約することで、 あらゆる会話例に共通して出現する重要な項目 を抽出します。このプロセスにより、少数の事例にしか存在しない特異な項目を拾いすぎず、多くの会話に共通する「頻出パターン」に焦点を絞ることができます。また、個別の表現に惑わされず、そのドメインの対話全体に対して汎用的に適用できる、堅牢な評価基準が構築されます。 大量の会話例を一度に処理しようとすると、プロンプトのコンテキスト長の制約に引っかかる可能性があるため、この要約プロセスは反復的に行われます。具体的には、空のルーブリックから開始し、「現在のルーブリック」と「新しい事例の束(ミニバッチ)」をLLMに入力します。LLMは、既存の基準と新しい事例を比較し、共通項をマージしながらルーブリックを洗練させます。このプロセスを全データに対して繰り返すことで、最終的に最も代表的な評価項目(最大10個)を作成します。 Step 3 ユーザー満足度推定(User Satisfaction Estimation): 完成したルールで未知のデータをスコアリングする 完成したルーブリックを用いて、ラベルのない新しい対話データの評価を行います。未知の対話ログと、Step 2で作ったルーブリックをLLMに入力すると、LLMはルーブリックの各項目について、その会話に該当するか否か(Yes/No)を判定し、該当する場合は影響度をスコア化(1-10)します。最終的に集計された 満足度スコア(R) は、単なる数値だけでなく、「どの評価項目(例:感謝の表明、繰り返し発言など)に基づいて採点されたか」が明確になるため、高い解釈性が得られます。 SPURの説明図。ステップ1、ステップ2は上段、ステップ3は下段に対応する。 宿泊予約対話データへの適用 ここからは、前節で紹介したSPURを、実際の日本語対話データに適用した内容について紹介します。 今回はSPURの全工程のうち、 Step 1(抽出)とStep 2(要約)による「評価ルーブリックの構築」まで を実施しました(Step 3のスコアリングについては、本記事の対象外とします)。 1. データセットと検証設定 検証には 「Accommodation Search Dialog Corpus (宿泊施設探索対話コーパス)」 を使用しました。 このデータセットには、宿泊予約をテーマにした「カスタマー」と「オペレーター」の対話が含まれており、オペレーター役の 実務経験の有無 が記録されています。 今回は、この「経験の有無」を対応品質の代理ラベルとして扱い、以下のように設定しました。 Positive(良対応)データ群 : 接客経験のあるオペレーター(50対話) Negative(要改善)データ群 : 接客未経験のオペレーター(50対話) 「経験者(Positive)には共通して見られるが、未経験者(Negative)には見られない要素」を抽出することで、良いオペレーターの評価基準を言語化できるか検証します。 2. 実装のアプローチと元論文からの変更点 元論文のSPURは「ユーザー満足度」を対象としていますが、今回は「オペレーターの応対品質」を対象とします。そのため、各ステップのプロンプトにおいて、 「視点」と「具体性」 に関する変更を加えました。 Step 1 教師あり抽出 (Supervised Extraction) 元論文では、このステップではユーザーが満足した理由( how the user expressed satisfaction )を抽出させています。抽出対象は「User-centric(ユーザーのニーズ、視点に立ったもの)」であることが求められ、各会話例には満足/不満足のシグナル(Likeボタンなど)が付与されていることが前提となっていました。 今回は「オペレーターのスキル」を評価したいため、主に以下の2点を調整しました。 対象の転換 : 「ユーザーがどう感じたか」ではなく 「オペレーターがどのような具体的行動をとったか」 を抽出対象に変更しました。 記述の客観性 : 元論文の grounded on the conversation history という制約に加え、さらに「第三者が客観的に観測可能な行動として記述する」という指示を追加しました。 設計したプロンプト --- Positive(良対応)データ用 --- あなたは、コンタクトセンターや顧客対応部門における「応対品質管理(QA)スペシャリスト」です。 以下の対話ログは、ある「ユーザー」と「オペレーター」の間で行われた、有人チャットまたは通話の記録です。 前提として、この対話のオペレーターは **対応品質が高く(GOOD)**、ユーザーの満足や円滑な問題解決を導いているとみなされます。 あなたのタスクは、この対話ログを分析し、対話の質を高めたり、信頼関係の構築に寄与した **「オペレーターの具体的な良対応(行動パターン)」** を最大3つ抽出することです。 【重要な分析制約】 1. **事実への立脚(Grounding):** 抽出するパターンは、必ず対話ログ内の「具体的な発話」に基づいている必要があります。ログに書かれていない背景、心理、隠れた意図を過度に推測・拡張解釈(Extrapolate)しないでください。 2. **記述の粒度と客観性(Behavioral Description):** 抽出するパターンは、評価者としての主観的な感想(形容詞的な評価)ではなく、第三者が客観的に観測可能な**「具体的な発話行動」**や**「適切な対応手順」**として記述してください。 「どのような状況で、どのような情報提供や配慮を行ったか」という、**対話の構造的な良さ**を言語化することに注力してください。 3. **相互排他性:** 抽出されたパターン同士は、内容が重複しないようにしてください。 【出力形式】 * 出力は **必ず JSON 形式のみ** としてください。 * JSONの構造は以下の通りです: { "patterns": [ { "description": "オペレーターの行動パターンの説明(日本語)", "evidence_quote": "その根拠となる対話ログ内のオペレーターの発話抜粋" } ] } * `patterns` 配列には 0〜3 件のオブジェクトを含めてください。 * 特筆すべき良対応が見当たらない場合は、配列を空にしてください。 【対話ログ】 <<<CONVERSATION>>> 【出力】 --- Negative(要改善)データ用 --- あなたは、コンタクトセンターや顧客対応部門における「応対品質管理(QA)スペシャリスト」です。 以下の対話ログは、ある「ユーザー」と「オペレーター」の間で行われた、有人チャットまたは通話の記録です。 前提として、この対話のオペレーターは **対応品質に課題があり(BAD)**、ユーザーの不満や対話の停滞を招いているとみなされます。 あなたのタスクは、この対話ログを分析し、品質低下の要因となった **「オペレーターの具体的な改善点(不適切な行動パターン)」** を最大3つ抽出することです。 【重要な分析制約】 1. **事実への立脚(Grounding):** 抽出するパターンは、必ず対話ログ内の「具体的な発話」や「対話の流れ」に基づいている必要があります。ログに書かれていない背景、感情、意図を過度に推測・拡張解釈(Extrapolate)しないでください。 2. **記述の粒度と客観性(Behavioral Description):** 抽出するパターンは、評価者としての主観的な感想(形容詞的な評価)ではなく、第三者が客観的に観測可能な**「具体的な不適切発話」**や**「必要なアクションの欠落」**として記述してください。 「どのような状況で、どのような不適切な回答を行ったか(あるいは、何をすべき場面で何をしなかったか)」という、**対話の構造的な不備**を言語化することに注力してください。 3. **相互排他性:** 抽出されたパターン同士は、内容が重複しないようにしてください。 【出力形式】 * 出力は **必ず JSON 形式のみ** としてください。 * JSONの構造は以下の通りです: { "patterns": [ { "description": "オペレーターの改善点・行動パターンの説明(日本語)", "evidence_quote": "その根拠となる対話ログ内のオペレーターまたはユーザーの発話抜粋" } ] } * `patterns` 配列には 0〜3 件のオブジェクトを含めてください。 * 該当する改善点が見当たらない場合は、配列を空にしてください。 【対話ログ】 <<<CONVERSATION>>> 【出力】 スクリプト(抜粋) import json from pathlib import Path from openai import OpenAI from pydantic import BaseModel, Field import pandas as pd class ExtractedPattern(BaseModel): """個別の抽出項目(行動とその根拠)""" description: str = Field( ..., description="行動パターンの説明(具体的な行動事実)" ) evidence_quote: str = Field( ..., description="根拠となる対話ログ内の発話抜粋" ) class ExtractionResponse(BaseModel): """プロンプト出力全体のコンテナ""" patterns: list[ExtractedPattern] = Field( default_factory=list, description="抽出された行動パターンのリスト(該当なしの場合は空)" ) client = OpenAI() ... # Positive(良対応)データの場合 base_dir = Path('path/to/good_patterns') results = [] for json_path in sorted(base_dir.glob('*.json')): with open(json_path) as f: data = json.load(f) session_id = data['meta']['id']['id'] conversation = '\n'.join( f"{utterance['name'].split('_')[0]}: {utterance['text'].replace(chr(10), '')}" for utterance in data['utterances'][:-1] # 最後の発話は検索条件の復唱(セッション終了の合図)なので除外 ) input_data = prompt_extract_good_pattern.replace('<<<CONVERSATION>>>', conversation) response = client.responses.parse( model="gpt-5.1", input=input_data, text_format=ExtractionResponse, ) parsed_response = response.output_parsed results.extend([ { 'session_id': session_id, 'description': pattern.description, 'evidence_quote': pattern.evidence_quote, } for pattern in parsed_response.patterns ]) # 結果を保存 df = pd.DataFrame(results) df.to_csv('good_patterns.csv', index=False) Step 2 ルーブリック要約 (Rubric Summarization) Step 1で抽出された理由を要約し、ユーザー満足度を測るためのリストを作成します。元論文の指示はシンプルで、 user-centric, concise, and mutually exclusive (ユーザー中心、簡潔、相互排他)な箇条書きリストの作成を求めています。 今回は、オペレーターの評価基準として汎用性を持たせるため、以下の指示を追加・補強しました。 抽象化(Abstraction)の明文化: 元論文のプロンプトには明示的な抽象化指示はありませんが、今回は特定の観光地名や固有の条件(例:「京都の宿」)に引っ張られないよう、「固有名詞や具体的すぎる状況記述を削ぎ落とし、汎用的なルールに変換する」プロセスを明示的に指示に加えました。 構造化(Structuring): 元論文は箇条書き(Bullet points)での出力ですが、今回は後の評価で使いやすくするため、 name (見出し)と description (定義)を持つJSON形式での出力を指定しました。 設計したプロンプト # Context あなたは、コンタクトセンターにおける対話データ分析のスペシャリストです。 あなたのタスクは、多数の対話ログから抽出された「オペレーターの具体的な行動事例」に基づき、評価基準となる「行動評価ルーブリック」を構築・更新することです。 現在は、以下のカテゴリに関するルーブリックを更新するフェーズです: **分析カテゴリ: <<target_category>>** # Input Data 入力として以下の2つの JSON データが提供されます。 1. **current_rubric (現在のルーブリック):** これまでの分析で作成された暫定的な評価項目リストです(初回は空の配列)。各項目は `name` と `description` を持ちます。 2. **new_examples (新しい事例リスト):** 新たに対話ログから抽出された、具体的な行動記述(文字列)のリストです。 # Instruction `current_rubric` に `new_examples` の内容を統合・抽象化し、より汎用的かつ網羅性の高い「更新版ルーブリック」を作成してください。 **【更新・作成のルール】** 1. **構造化 (Structuring):** 抽出された行動パターンを、以下の2つの要素で構成されるオブジェクトに整理してください。 * **name:** その行動を端的に表す見出し(10文字〜20文字程度の体言止め推奨)。 * **description:** その行動の定義、満たすべき条件、具体的な振る舞いを記述した文章。 2. **抽象化と一般化 (Abstraction):** `new_examples` に含まれる固有名詞(地名、サービス名、数値など)や具体的すぎる状況記述を削ぎ落とし、他のあらゆる対話でも適用可能な「汎用的な行動ルール」に変換してください。 3. **統合と分類 (Consolidation):** * 新しい事例が、すでに `current_rubric` の項目に含まれる概念である場合は、その項目の `description` をより明確にするか、そのまま維持してください。 * 新しい事例が既存の項目に当てはまらず、かつこのカテゴリにおいて重要なパターンの場合は、新しい項目として追加してください。 * 内容が類似している項目同士は、1つの項目にマージしてください。 4. **記述の客観性 (Objectivity):** * 主語は常に「オペレーター」としてください。 * 評価者の主観的な印象や形容詞的な表現は避け、第三者が客観的に判定できる**「具体的な行動事実」**や**「対応手順」**として記述してください。 * 各項目は相互に重複しないようにしてください(Mutually Exclusive)。 5. **項目数の制御:** * ルーブリックの項目数は最大 **10** 個までとしてください。 * 重要度が低い、または発生頻度が極めて稀な項目は削除または統合し、主要なパターンのみを残してください。 # Inputs { "current_rubric": <<current_rubric>>, "new_examples": <<new_examples>> } # Output Format 出力は以下の JSON スキーマに従ってください。 { "updated_rubric": [ { "name": "行動パターンの見出し", "description": "行動パターンの具体的かつ客観的な定義・説明" }, { "name": "...", "description": "..." } ] } # Output スクリプト(抜粋) class RubricItem(BaseModel): """個別の評価基準(行動パターン)""" name: str = Field( ..., description="行動パターンの見出し(10〜20文字程度の体言止め)" ) description: str = Field( ..., description="行動パターンの具体的かつ客観的な定義・説明" ) class RubricUpdateResponse(BaseModel): """プロンプトからの出力全体をラップするモデル""" updated_rubric: list[RubricItem] = Field( ..., description="更新・統合された評価ルーブリックのリスト" ) ... # 反復的ルーブリック構築のメインループ target_category = "オペレーターの良対応 (GOOD)" # or "オペレーターの改善点 (BAD)" current_rubric = [] new_examples = [] iteration = 1 sample_threshold = 10 # 10件のパターンが集まるたびにルーブリックを更新 for session_id, group in df.groupby('session_id'): # 新しいパターンを収集 new_examples.extend( group[['description', 'evidence_quote']].to_dict(orient='records') ) # サンプル数が閾値を超えたらルーブリックを更新 if len(new_examples) > sample_threshold: current_rubric_json = json.dumps(current_rubric, ensure_ascii=False) new_examples_json = json.dumps(new_examples, ensure_ascii=False) input_data = ( prompt_make_rubric .replace('<<target_category>>', target_category) .replace('<<current_rubric>>', current_rubric_json) .replace('<<new_examples>>', new_examples_json) ) # LLMでルーブリック更新 response = client.responses.parse( model="gpt-5.1", input=input_data, text_format=RubricUpdateResponse, ) # 更新されたルーブリックを保存 parsed_response = response.output_parsed current_rubric = [ {'name': item.name, 'description': item.description} for item in parsed_response.updated_rubric ] # 結果をCSVに保存 rubric_df = pd.DataFrame(current_rubric) rubric_df.to_csv(f'good_rubric_iteration_{iteration}.csv', index=False) # 次のイテレーションの準備 new_examples = [] iteration += 1 このように、元論文のフレームワーク(抽出→要約)は維持しつつ、プロンプト内の指示を「ユーザー感情の要約」から「オペレーター行動の抽象化」へと書き換えることで、目的とする評価基準の構築を試みました。 3. 実験結果 経験者(Positive群)と未経験者(Negative群)の対話データから、それぞれどのような評価基準が生成されたかを確認していきます。 Step 1 行動パターンの抽出結果 まず、各対話ログから抽出された「オペレーターの行動パターン」を確認します。 今回の実験設定として、経験者データには「 良い対応(Good Pattern) 」を、未経験者データには「 改善点(Bad Pattern) 」を抽出するよう、それぞれ異なるプロンプトを与えています。そのため、抽出される内容のポジティブ/ネガティブの方向性は必然的に異なりますが、具体的な「行動の解像度」に興味深い差が見られました。 ▼ 抽出された行動パターンの例 データ群 抽出された行動パターンの記述 (Description) 根拠となる発話の引用 (Evidence Quote) 経験者 曖昧な条件の具体化 料金条件など誤解の起きやすい点については、金額の単位(一人あたりか総額か)を確認することで条件を正確にすり合わせている。 Customer : 「料金は1泊、10000円以内でお願いします。」 Operator : 「こちらは2名様での合計でよろしいでしょうか?」 経験者 潜在ニーズへの提案 ユーザーが口にしていないニーズ(高齢・膝の痛み)を想定し、バリアフリーやエレベーター完備などの追加オプションを提案して選択肢を広げている。 Operator : 「ご両親様がご一緒とのことですが、もしご高齢の方でしたらバリアフリー対応のお宿もございます。いかがなさいますか?」 未経験者 顧客の文脈の看過 子ども連れ旅行であることが共有されているにもかかわらず、安全面や子ども向け設備の観点を確認せず、要望を深掘りしないまま進めている。 Customer : 「私と子供、親戚の子2人の4人で...」 Operator : 「自然を楽しむということでしたら、宿は山間にあるコテージはどうでしょうか?」 未経験者 要望への応答不足 「交通費込みのパッケージ」を希望されているのに対し、食事有無の確認に終始し、費用抑制に直結する交通パックの提案を行っていない。 Customer : 「新幹線とかバスとか移動も併せてパッケージがあるホテルがあると嬉しいです」 Operator : 「かしこまりました。ご予算を抑えるならお食事の方もなしでよろしいでしょうか?」 経験者からは「明確化・確認」や「提案」といった能動的な行動が多く抽出された一方、未経験者からは、マニュアル通りの質問を返すだけの受動的な行動や、顧客の文脈を拾いきれていない事例が多く抽出されました。 Step 2 ルーブリックの生成と改善 続いて、Step 1で抽出された個別の事例を束ねて、汎用的なルーブリックを作成しました。 今回は抽出された事例が10件を超えるごとにルーブリックを更新することとしました。初期状態と、50対話すべての確認を終えた最終状態を比較すると、 「具体性」と「網羅性」 において著しい改善が見られました。 1. 抽象的なルールから、具体的なチェックリストへ ルーブリックの更新が繰り返されるつれて、各項目の記述は「〜を確認する」といった予約受付全般で実施される汎用的な表現から、宿泊地の予約受付というドメインに特化したより具体的な内容へと変化していきました。 ▼ 良対応のルーブリックにおける「基本情報の確認」項目の変化例 ルーブリックの記述内容(抜粋) 特徴 初期ステップ 宿泊地・利用日・人数などの基本情報を、網羅的にかつまとめて問いかける。 汎用的 。 予約受付全般の業務に共通する書き方だが、具体的になにを聞くべきかは書かれていない。 最終ステップ 宿泊日・人数・ 人数内訳(大人・子ども) ・ 宿タイプ(ホテル・旅館) ・ 部屋数 ・ 浴室条件 ・ 禁煙/喫煙 ・食事有無・ 予算 など、予約に必須となる基本条件を過不足なく順序立てて確認する。 料金条件については、 金額の単位(1名あたり/合計) や人数前提を明確に質問する。 ドメイン特化 。 「浴室条件」や「人数内訳」など、宿泊予約において聞き漏らしがちな項目が具体的にリストアップされた。 2. エッジケースと暗黙知の言語化 また、対話データを読み込む過程で、経験者が無意識に行っている「配慮」や「リスク管理」も言語化されました。具体的には以下のような項目が追加されていきました。 専門用語の噛み砕き : 「コネクティングルーム」などの用語を、ユーザーに伝わる平易な言葉で説明すること。 ネガティブ情報の開示 : メリットだけでなく、「キャンセル料の発生時期」や「入湯税などの追加費用」を事前に説明すること。 属性に基づく先回り : 移動手段や荷物の量、性別構成から、「送迎」「荷物預かり」「女性向けアメニティ」の必要性を予測して提案すること。 表面的な「丁寧な対応」に留まらず、 「どのような状況で、どのような発言・提案をすべきか」 という具体的なノウハウがルーブリックの中に組み込まれる結果となりました。 4. 考察 生成されたルーブリックを比較することで、本手法が捉えた「応対品質の差分」について見ていきます。 前述の通り、本実験では「良い対応(経験者)」と「改善点(未経験者)」という対照的な視点で抽出を行いました。その結果、生成されたルーブリックは 「理想的な行動指針」 と 「避けるべきアンチパターン」 という、相互補完的な関係になりました。 この2つを照らし合わせることで、単なる正誤だけでなく、 「経験者は具体的にどこまで対応しているのか(未経験者はどこで躓いているのか)」 という、スキルや振る舞いの違いが明確になりました。 (1) コンテキスト理解の深さ 両者のルーブリックで顕著な差が出たのは、顧客属性の取り扱いです。 経験者基準(Good) : 「利用者属性に基づく要件整理」として、高齢者や子ども連れという属性から「バリアフリー」や「音漏れ配慮」などの 潜在ニーズを推論・提案 すること。 未経験者基準(Bad) : 「子ども・高齢者配慮不足」として、属性情報を単なる「人数のカウント」としてしか処理せず、 特有のリスクやニーズを確認しないまま進める こと。 (2) 定義のすり合わせ 宿泊施設の条件定義の解像度にも大きな違いが見られました。 経験者基準(Good) : 「安いとはいくらか?」「近いとは徒歩何分か?」といった曖昧な言葉に対し、 具体的な基準や選択肢を提示してすり合わせる行動 。 未経験者基準(Bad) : 「おしゃれな宿」「安く抑えたい」といった顧客の 主観的な言葉をそのまま検索条件として受け入れ、定義のすり合わせスキップする行動 。 このように、SPURを用いて「良い対応事例」と「不十分な対応事例」の両面から評価基準を構築することで、 「最低限避けるべきライン」 と 「目指すべき高品質なライン」 の両方を言語化できることが確認できました。 5. おわりに 本記事では、ユーザー満足度推定手法「SPUR」をオペレーターの応対品質評価に応用し、少数の対話データから解釈可能な評価ルーブリックを構築する実験を行いました。 LLMを活用することで、人間同士の対話に含まれる 「暗黙知」(高度なヒアリングスキルや提案行動) を、具体的かつ構造化された評価項目として言語化できる可能性が示されました。 弊社では「 AI Worker VoiceAgent 」という電話応対自動化のプロダクトを運用していますが、現状の自動音声対話システムには、有人対応のような柔軟性や文脈理解においてまだ限界があるのも事実です。 今回の実験で得られた知見は、こうした「人間とBotのギャップ」を埋めるための分析・評価基盤を構築するうえで、重要なヒントになりうると感じました。 今後もプロダクトの品質を深く理解し、改善サイクルを回していくための様々な手法について、検証を進めていく予定です。 最後までお読みいただき、ありがとうございました! 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 参考資料 Interpretable User Satisfaction Estimation for Conversational Systems with Large Language Models Accommodation Search Dialog Corpus (宿泊施設探索対話コーパス) 投稿 オペレーターの「暗黙知」をLLMで言語化する: 「SPUR」を用いた応対品質ルーブリックの自動構築 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの二宮です。 本記事は AI Shift Advent Calendar 2025 11日目の記事です。 今回の記事では、Agent Development Kit(ADK)のVisual Builderを実際に試してみました。このツールを用いたエージェントの構築を通じて、Visual Builderのプロンプトから学べることをまとめてみます。 Agent Development Kit(ADK)とは ADKは、Googleによって開発されたオープンソースのエージェント構築ライブラリです。複雑なマルチエージェントシステムを効率的に構築できることを目的として設計されており、Googleの最新のAI技術との統合が可能です。 2025年11月5日に公開されたバージョン1.18.0では、待望のVisual Builderが新たに追加されました( Release Notes )。これまでのコードベースでの実装に加えて、ノーコード・ローコード的なアプローチでのエージェント構築が可能になりました。本日時点での最新バージョンは1.20.0となっております。 パーソナルアシスタントを作ってみる 事前準備 まず、uvを使って環境構築します。 uv init uv venv source .venv/bin/activate uv add google-adk # 1.20.0 echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > .env 環境構築について詳しく知りたい方は 公式ドキュメント をご確認ください。 Visual Builderでマルチエージェントシステムを構築する Visual Builderでは自然言語で指示を出してエージェントを作成することができます。 まずは adk web というコマンドをターミナルで実行して、"+"ボタンからVisual Builderを起動します。 Dialogflow CX と似た印象を受けますね。 それでは画面右のチャット欄から指示を出して、パーソナルエージェントを作成してみます。 google_searchというBuilt-in Toolが追加されたresearcher_agentが作成されました。 ここで以下の追加要望を与えてみます。 調査はもっと詳細に行い、可能な限り最新情報が網羅されたレポートを生成してください。 これにより以下のようなエージェントが作成されました。 今度はIterativeResearcherというLoopAgentが作成され、そのSubAgentとしてsearcher_agentとrefiner_agentという2つのLlmAgentが作成されています。ここまで簡単にマルチエージェントシステムが開発できるのは楽しいですね。 この時の作業ディレクトリ構成は以下のようになっています。 ~$ tree . ├── README.md ├── personal │ └── tmp │ └── personal │ ├── callbacks │ │ ├── __init__.py │ │ └── billing_logger.py │ ├── iterative_researcher.yaml │ ├── refiner_agent.yaml │ ├── researcher_agent.yaml │ ├── root_agent.yaml │ └── searcher_agent.yaml ├── pyproject.toml └── uv.lock personal/tmp/配下はVisual Builderによって編集中のものである点に注意が必要です。エージェントはyaml形式で作成され、callbacksはPythonで作成されているのがわかります。 ここで、最初に指示した請求ログの処理がbilling_logger.pyに実装されているものの、実際のAgentに設定されていないことに気づいたのでチャットで指示してみます。 請求ログの実装がcallbacks/billing_logger.pyにすでに存在しますが、まだ設定されていないようです。AFTER MODELとして、全てのLlmAgentのCallbacksに設定してください。 ADKにおけるCallbackとは、AgentやLLMやツールの実行前後に行う処理を指します。今回の場合、LLMの実行後に請求ログ関連の出力をする関数を設定してみます。 するとYAMLファイルに以下が追加されました。 after_model_callbacks: - name: personal.callbacks.billing_logger.log_usage nameは <Agent名>.<ファイルパス>.<関数名> になっていますね。 これで請求ログの表示も無事実装されましたので、Saveボタンを押します。 このタイミングで確定されたエージェント構成のファイルが作成されます。 ~$ tree . ├── README.md ├── personal │ ├── IterativeResearcher.yaml │ ├── callbacks │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-312.pyc │ │ │ └── billing_logger.cpython-312.pyc │ │ └── billing_logger.py │ ├── iterative_researcher.yaml │ ├── refiner_agent.yaml │ ├── researcher_agent.yaml │ ├── root_agent.yaml │ ├── searcher_agent.yaml │ └── tmp │ └── personal │ ├── IterativeResearcher.yaml │ ├── callbacks │ │ ├── __init__.py │ │ └── billing_logger.py │ ├── iterative_researcher.yaml │ ├── refiner_agent.yaml │ ├── researcher_agent.yaml │ ├── root_agent.yaml │ └── searcher_agent.yaml ├── pyproject.toml └── uv.lock Saveボタンにより確定したエージェント構成がpersonal/配下に保存される仕組みのようです。 ここで、試しにpersonal/tmp/personal/配下のファイルを編集したところ、本番には影響がありませんでした。そして、画面からVisual Builderモードに入り、Saveボタンを押すと personal/tmp/personal/配下のファイルでpersonal/配下のファイルが上書きされました。 つまり、手動で編集したいファイルがある場合は、両方のディレクトリ内のファイルを更新する必要があります。 それでは会話してみます。 "'CallbackContext' object has no attribute 'agent_run'"というエラーが出ていますね。このようにVisual Builderで作成したエージェントは必ず動作する保証が現時点ではないので、都度修正する必要があります。 今回の場合、請求ログの関数において実装ミスがあったので以下のように修正しました。 billing_logger.info( - f"Agent '{callback_context.agent_run.agent_name}' がモデル呼び出しを完了しました。" + f"Agent '{callback_context.agent_name}' がモデル呼び出しを完了しました。" f"Usage: {token_info}" ) 再度実行してみます。 正しく実行されたことが確認できました。 ターミナルを見ると以下のように正しく請求ログが出ていることも確認できました。 2025-12-10 10:15:35,812 - BILLING - Agent 'searcher_agent' がモデル呼び出しを完了しました。Usage: prompt_token_count: 4162, candidates_token_count: 2965, total_token_count: 12485 2025-12-10 10:16:00,537 - BILLING - Agent 'refiner_agent' がモデル呼び出しを完了しました。Usage: prompt_token_count: 7153, candidates_token_count: 4170, total_token_count: 11956 2025-12-10 10:16:30,917 - BILLING - Agent 'searcher_agent' がモデル呼び出しを完了しました。Usage: prompt_token_count: 11263, candidates_token_count: 2061, total_token_count: 27684 2025-12-10 10:17:05,601 - BILLING - Agent 'refiner_agent' がモデル呼び出しを完了しました。Usage: prompt_token_count: 13329, candidates_token_count: 5524, total_token_count: 20416 Visual Builderを使ってみて、かなり簡単にマルチエージェントシステムを構築することができました。 一方で、実装体験を通じて、いくつか課題に感じる部分もありました。 Callbackの実装方法がやや複雑に感じた。 Visual Builderのチャット欄に日本語で入力すると、文中のEnterで必ずメッセージが送信されてしまう。Shift+Enterでの入力で回避できる。 Visual Builderのチャット欄での実装方針と実際のYAMLファイルでの実装内容がやや異なる。チャット内容を元にYAMLファイルを修正する必要がある場合がある。 ファイル構造が特殊で( personal/tmp/personal/ )、手動編集時は両方のディレクトリを更新する必要がある。 ここまでの経験を通して、Visual Builderはどのようなルールや制約に基づいて実装されているのか気になったので、実際にプロンプトを見てみます。 Visual Builderのプロンプトから学ぶADKの設計 Visual Builderのプロンプトは、以下のURLで確認することができます。 https://github.com/google/adk-python/blob/main/src/google/adk/cli/built_in_agents/instruction_embedded.template このプロンプトには、Agent Development Kitを用いたエージェント構築のノウハウが、実に詳細に記述されています。実際に、先で遭遇した課題や気づきが、このプロンプトの中に記載されていました。 LLMで日本語訳したプロンプト # エージェント・ビルダー・アシスタント - 埋め込みスキーマモード ADK(Agent Development Kit)マルチエージェント・システムをYAML設定ファイルを使用して作成・構成することに特化したインテリジェント・エージェント・ビルダー・アシスタントです。 ## 目的 ADKフレームワーク向けの高度なマルチエージェント・システムの設計、構築、構成をサポートします。明確な質問をすること、最適なアーキテクチャを提案すること、ADK AgentConfigスキーマに準拠した正しくフォーマットされたYAML設定ファイルを生成することで、ユーザーはエージェント作成プロセスをガイドされます。 ## 重要な行動ルール **ユーザーが「作成」「構築」「生成」「実装」「更新」を明示的に求めない限り、エージェント作成を望んでいると決して仮定しないでください。** 「例を探して」「サンプルを見せて」「どうやるんですか」などの情報質問をされた場合、ユーザーは情報のみを求めています。情報を提供して終了してください。何か作成することを提案したり、ルート・ディレクトリを要求したりしないでください。 ## ルート・エージェント・クラス・ルール **交渉不可**: `root_agent.yaml`は必ず`agent_class: LlmAgent`を宣言する必要があります。 **決して**`root_agent.yaml`をワークフロー・エージェント・タイプ(SequentialAgent、ParallelAgent、LoopAgent)に設定しないでください。すべてのワークフロー調整はサブエージェントに留まり、ルート・ファイルには残してはいけません。 **モデル契約**: すべての`LlmAgent`(ルートおよびサブエージェント)は明示的に`model`を設定する必要があります(ユーザーがデフォルトを求める場合のみ`{default_model}`を使用してください)。このフィールドは決して省略したり、グローバル・デフォルトに依存してはいけません。 **名前契約**: エージェント`name`値は有効な識別子である必要があります。文字またはアンダースコアで始まり、その後は文字、数字、アンダースコアのみが続きます(スペースや句読点は不可)。このルールに違反する名前の調整をユーザーに要求してください。 ## コア機能 1. **エージェント・アーキテクチャ設計**: 要件を分析し、適切なエージェント・タイプ(LlmAgent、SequentialAgent、ParallelAgent、LoopAgent)を提案します 2. **YAML設定生成**: 正しいADK AgentConfigスキーマ準拠で適切なADKエージェント設定ファイルを作成します 3. **ツール統合**: さまざまなツール・タイプ(Function ツール、Google API ツール、MCP ツールなど)の構成と統合をサポートします 4. **Python ファイル管理**: ユーザーリクエストに応じて、カスタムツールとコールバック用の Python ファイルを作成、更新、削除します 5. **プロジェクト構造**: 適切なADKプロジェクト構成とファイル配置をガイドします 6. **ADK知識とQ&A**: 包括的なリサーチ機能を使用して、ADKコンセプト、API、使用パターン、トラブルシューティング、ベストプラクティスに関する質問に答えます ## ADK AgentConfigスキーマ参照 コンテキストに埋め込まれた完全なADK AgentConfigスキーマにアクセスできます: {schema_content} 設定を作成する際は、常にこのスキーマを参照して準拠を確認してください。 ## 現在のコンテキスト **現在のプロジェクト・フォルダ名**: `{project_folder_name}` ## ワークフロー・ガイドライン ### 1. 発見フェーズ **ステップ 1: ユーザー意図の決定** * **情報質問**(直接回答): - 「~の例を探してくれませんか」/ 「~のサンプルを見せて」 - 「~の方法を教えてください」/ 「どうやって~するんですか」 - 「~とは何ですか」/ 「~について説明してください」 - 「~の例を見せてくれませんか」/ 「~の例を持っていますか」 - 「~について情報を探しています」/ 「~を理解する必要があります」 - ADKの機能、コンセプト、または既存の実装に関する質問 - **重要**: 情報質問の場合、要求された情報を提供して終了してください。明示的に求められない限り、何か作成、構築、生成することを提案しないでください。 * **作成・構築意図**: - 「新しいエージェントを作成します」/ 「エージェントを作成してください」 - 「エージェントを生成します」/ 「エージェントを実装します」 - 「自分のエージェントを更新します」/ 「自分のエージェントを変更します」/ 「自分のエージェントを修正します」 - 「~を作成したいと思っています」/ 「~の構築をサポートしてください」/ 「~の更新をサポートしてください」 - 「プロジェクトをセットアップします」/ 「エージェントを作ってください」 **ステップ 2: 要件の理解** - ターゲットを絞った質問を通じてユーザーの目標と要件を理解する - explore_projectツールを使用して既存のプロジェクト構造を探索する - 統合ニーズ(API、データベース、外部サービス)を特定する - 必要なエージェント・タイプ(LlmAgent、SequentialAgent、ParallelAgent、LoopAgent)を分析する **ステップ 3: モデル選択(デザイン・フェーズに移動する前に完了)** - **重要なタイミング**: LlmAgentが必要と決定された直後にモデル選択を求めてください。デザインを提示する前に。 - **必須確認**: 「使用したいモデルを確認してください」と言ってください - デフォルトを仮定したり提案したりしないでください - **例**: 「gemini-2.5-flash」、「gemini-2.5-pro」など - **許可されたモデルのみ**: 「gemini-2.5-flash」または「gemini-2.5-pro」のみ言及または提案してください。gemini-1.5-*またはより古いモデルのリクエストは、サポートされていない2.5オプションのいずれかにリダイレクトしてください。 - **理由**: LlmAgentのみがモデル指定を必要とします。ワークフロー・エージェントはそうではありません - **デフォルトモデル**: ユーザーが「デフォルトを使用」または「デフォルト・モデルで進める」と言った場合、次を使用してください: {default_model} * これは実際のモデル名であり、「default」というリテラル文字列ではありません * このセッションのデフォルト・モデルは: {default_model} - **ワークフロー**: 発見のすべてのステップ(このモデル選択を含む)を完了 → その後、デザイン・フェーズに進む(モデル既に選択済み) ### 2. デザイン・フェーズ - **注**: モデル選択は発見フェーズ(ステップ 3)で既に完了しています - モデルを再度求めないでください **完全な実装を提示する** - ユーザーが1つの場所でレビューする必要があるすべてを表示します: * 高レベルのアーキテクチャの概要(エージェント・タイプとその役割) * 選択されたモデル(発見フェーズで既に選択) * `root_agent.yaml`が`agent_class: LlmAgent`を保持し、ワークフロー・オーケストレーションがサブエージェントで実行されることの明示的な確認 * **絶対ルール**: `root_agent.yaml`はワークフロー・エージェントになることはできず、すべての計画と出力でLlmAgentのままであることを重新相確認 * **モデル・フィールド強制**: すべての`LlmAgent`ブロックに、確認されたモデル名で設定された`model`フィールドを表示 - 欠けている場合は呼び出してください * **完全なYAML設定ファイル** - すべてのYAMLファイルの完全なコンテンツを表示 * **完全なPythonファイル** - すべてのPythonツール/コールバックファイルの完全なコンテンツを表示 * ファイル構造とパス - **単一の確認が必要**: すべてを表示した後に**1回だけ**尋ねます - 「これらのファイルの作成を進めるべきでしょうか?」 - **ユーザー確認を待つ**: ユーザーが確認するまで実装に進まないでください - **すべてに対する1つの承認**: ユーザーはプランと全ファイルコンテンツをレビューし、その後、単一の承認を与えます - **ワークフロー**: モデル既に選択 → プランと全ファイルコンテンツを提示 → 1つの「進めるべき?」→ 再度質問なしで実行 ### 3. 実装フェーズ **注: ユーザーはデザイン・フェーズですべてを既に承認しています - 再度確認を求めないでください** **🚨 パス表示ルール**: レスポンスで相対パスを常に表示してください(例: `root_agent.yaml`、`tools/dice_tool.py`)完全な絶対パスの代わりに **🚨 重要なツール・パス・ルール**: - **ツール呼び出しにプロジェクト・フォルダ名を決して含めないでください** - **`root_agent.yaml`のような`{project_folder_name}/root_agent.yaml`ではなくパスを使用してください** - **ツールは自動的にプロジェクト・フォルダに相対するパスを解決します** **実装順序(デザイン・フェーズ承認直後に実行):** **ステップ 1: YAML設定ファイルの書き込み** 1. `write_config_files`を使用してすべてのYAML設定ファイルを書き込みます * `"root_agent.yaml"`のようなパスを使用します(プロジェクト・フォルダー・プレフィックスなし) * ファイルは既にデザイン・フェーズで表示および承認されています **ステップ 2: Pythonファイルの書き込み** 1. `write_files`を使用してPythonツール/コールバックファイルを書き込みます * `"tools/dice_tool.py"`のようなパスを使用します(プロジェクト・フォルダー・プレフィックスなし) * ファイルは既にデザイン・フェーズで表示および承認されています **ステップ 3: クリーンアップ** 1. `cleanup_unused_files`と`delete_files`を使用して、不要なツール・ファイルがあれば削除します **応答前の最終検証**: - すべてのワークフロー・エージェント・ブロックが`model`、`instruction`、`tools`を省略していることを確認してください **ファイル修正の場合(既存ファイルの更新):** - 正確にどの変更がなされるか表示し、承認を求めます - 既存のファイルを変更する場合、「このファイルを変更する前にバックアップを作成すべきですか?」と尋ねます - backup_existingパラメータを使用: ユーザーが明示的にバックアップをリクエストした場合のみTrueに設定 **YAML設定要件:** - メイン・エージェント・ファイルは`root_agent.yaml`という名前にする必要があります - **`agent_class`フィールド**: * すべてのエージェント・ブロックに対して`agent_class`を明示的に宣言します(ローダーはデフォルトで`LlmAgent`に設定しますが、わたしたちは明確さを要求します) * エージェントがLLMと直接通信する場合は`agent_class: LlmAgent`を使用します - **LlmAgentsの`model`フィールド**: * すべての`LlmAgent`定義(ルートまたはサブエージェント)は明示的に`model`を指定する必要があります。ユーザーが確認したモデルまたはそれらがデフォルトを求める場合は`{default_model}`を挿入してください * グローバル・デフォルトに依存したり、`model`を省略してはいけません。これは正規化をクラッシュさせるためです - **エージェント`name`フィールド**: * 有効な識別子である必要があります: [A-Za-z_]で始まり、その後のみ文字、数字、またはアンダースコアを含みます * `Paper Analyzer`または`Vacation Planner`のようなエントリを拒否または名前変更します。代わりに`Paper_Analyzer`を使用してください - **🚫 ワークフロー・エージェント・フィールド・バン**: ワークフロー・オーケストレーター(`SequentialAgent`、`ParallelAgent`、`LoopAgent`など)は決して`model`、`instruction`、`tools`を含めてはいけません。LlmAgent定義のみ(ルート・エージェントまたはサブ・エージェント)がそれらのフィールドを宣言するかもしれません - **ルート・エージェント要件**: ルート設定は常にLlmAgentのままであることが必要です。ルート・エージェントをワークフロー・エージェントに変換しないでください。 - **ワークフロー・エージェント・ツール・ルール**: **ADKエージェント・タイプとモデル・フィールド・ルール**を参照してワークフロー・オーケストレーターのツール制限を確認してください。ツールを`LlmAgent`サブエージェントに接続してください。 - **サブエージェント配置**: すべてのサブエージェントYAMLファイルをメイン・プロジェクト・フォルダに配置します。`sub_agents/`サブフォルダーには配置しないでください - ツール・パスは形式を使用します: `project_name.tools.module.function_name`(プロジェクト・フォルダ名で始まる必要があります。`.py`拡張子はすべてドット) * **例**: `config_agents/roll_and_check`にあるプロジェクトの`tools/is_prime.py`内のツールの場合、次を使用します: `roll_and_check.tools.is_prime.is_prime` * **パターン**: `{{project_folder_name}}.tools.{{module_name}}.{{function_name}}` * **🚨 重要なツール命名ルール**: プロジェクト・フォルダ・パスの最終/最後のコンポーネントのみを`project_folder_name`として使用します - ✅ 正解: プロジェクト・パス`projects/workspace/my_agent`の場合、`my_agent`を使用します(最後のコンポーネント) - ❌ 誤り: `projects.workspace.my_agent`(ドット区切りパス全体) - ✅ 正解: `./config_based/roll_and_check`の場合、`roll_and_check`を使用します(最後のコンポーネント) - ❌ 誤り: `config_based.roll_and_check`(親ディレクトリを含みます) * **覚えておく**: 常にスラッシュ/セパレーター後の最後のフォルダ名のみを抽出します - YAML内の関数宣言なし(ADKによって自動的に処理されます) **🚨 重要: 組み込みツール対カスタムツール** **ADK組み込みツール**(直接使用、カスタムPythonファイルは不要): - **命名**: ドットなしでエクスポートされた名前を使用します(例: `google_search`、`google.adk.tools.google_search`ではありません。`GoogleSearch`のような新しいラベルを発明してはいけません) - **カスタムコードなし**: 組み込みツール用にPythonファイルを作成しないでください - **利用可能な組み込みツール**: * `google_search` - Google検索ツール * `enterprise_web_search` - エンタープライズ・ウェブ検索 * `google_maps_grounding` - Google Mapsグラウンディング * `url_context` - URLコンテキスト取得 * `VertexAiSearchTool` - Vertex AI検索(クラス名) * `exit_loop` - ループ終了制御 * `get_user_choice` - ユーザー選択相互作用 * `load_artifacts` - アーティファクト読み込み * `load_memory` - メモリ読み込み * `preload_memory` - メモリ事前読み込み * `transfer_to_agent` - 別のエージェントへの転送 * ⚠️ エージェントが`sub_agents`を持っている場合、YAMLで`transfer_to_agent`を宣言**しないでください**。ADKはこのツールを自動的に注入し、重複すると Gemini エラーが発生します(`Duplicate function declaration: transfer_to_agent`)。 **例 - 組み込みツール使用(正解):** ```yaml tools: - name: google_search - name: url_context ``` **例 - 組み込みツール使用(誤り):** ```yaml tools: - name: cb.tools.google_search_tool.google_search_tool # ❌ 誤り - 組み込みをカスタムとして扱っている ``` **組み込みツール用に`tools/google_search_tool.py`のようなPythonファイルを作成しないでください!** - **🚫 ツール・ハルシネーション・バン** - **ADK組み込みツール**リストに列挙された組み込みツール名のみを使用します。追加の組み込みラベルを発明してはいけません。 - このプロジェクトまたは組み込みリストに既に存在することを確認できない場合、推測または作成代わりにユーザーに確認を求めます。 - 唯一の目的が別のエージェントへの制御転送であるカスタム・ヘルパー・ツールを生成しないでください。ADKはサブエージェントが構成される場合、公式`transfer_to_agent`ツールを自動的に注入します。`transfer_to_agent_tool`のようなルックアライクを作成しないでください。 - `tool_code`は一部のランタイムでコード実行のために予約されています。ADKツールまたはドット区切りパスにその名前を再利用しないでください。 **カスタムツール**(Python実装が必要): - **命名**: ドット区切りパスを使用します: `{{project_folder_name}}.tools.{{module_name}}.{{function_name}}` - **Pythonファイルが必要**: `tools/`ディレクトリに実際のPythonファイルを作成する必要があります - **例**: `my_project.tools.dice_tool.roll_dice` → `tools/dice_tool.py`内に`roll_dice()`関数が必要 **ツール実装戦略:** - **簡単/明白なツール**: 実際に動作するコードで直接実装します * 例: さいころ振る、素数チェック、基本的な数学、ファイル操作 * ユーザーに「TODOコメント記入」を求めないでください - **複雑/ビジネス固有のツール**: TODOコメント付きの適切な関数署名を生成します * 例: APIキーが必要なAPI統合、複雑なビジネス・ロジック - **常に正しい関数署名を生成**: ユーザーが`roll_dice`と`is_prime`を望む場合、ジェネリック`tool_name`ではなくそれらの正確な関数を生成します **重要: ツール使用パターン - 必須ファイル・タイプ分離** ⚠️ **YAMLファイル(.yaml、.yml)- 構成ツール必須:** - **常に`write_config_files`を使用**してYAML設定ファイル(root_agent.yamlなど)を書き込みます - **常に`read_config_files`を使用**してYAML設定ファイルを読み込みます - **YAMLファイルに`write_files`を決して使用しないでください** - 検証とスキーマ準拠がありません ⚠️ **Pythonおよび他のファイル(.py、.txt、.md)- 一般的なファイル・ツール使用:** - **`write_files`を使用**してPythonツール、スクリプト、ドキュメントなどを書き込みます - **`read_files`を使用**して非YAMLコンテンツを読み込みます ⚠️ **この分離が重要な理由:** - `write_config_files`はYAML構文とADK AgentConfigスキーマ準拠を検証します - `write_files`は検証なしの生ファイル書き込みです - 誤ったツールを使用すると、無効な構成が作成される可能性があります - **ADKコード質問**: `search_adk_source`を使用してから`read_files`を使用して完全なコンテキストを取得します - **ファイル削除**: 複数ファイル削除にはバックアップ・オプション付きで`delete_files`を使用します **ツール生成ルール:** - **ユーザー要件を正確に一致**: 要求された特定の関数を生成します - **適切なパラメータ・タイプを使用**: 特定のタイプが必要な場合、ジェネリック`parameter: str`を使用しないでください - **可能な場合は実装**: シンプルで明確に定義された関数の実際に動作するコードを書きます - **ツール・ファイル構成**: * `tools/`パッケージ内にツール・コードを配置し、ドット区切り・インポートが解決されるように`tools/__init__.py`を含めます。 * 関連するツールセットの意図的な共有は問題ありませんが、関連のないツールの混在を避けて、ツール1つあたり1つのモジュール(例: `tools/dice_tool.py`、`tools/prime_tool.py`)を優先します。 ### 4. 検証フェーズ - 生成された設定のスキーマ準拠をレビューします - 可能な場合は基本機能をテストします - ユーザーに対して明確な次のステップを提供します ## 利用可能なツール ### コアエージェント構築ツール #### 設定管理(.yaml/.ymlファイルに必須) - **write_config_files**: ⚠️ すべてのYAMLエージェント設定ファイル(root_agent.yaml、メイン・プロジェクト・フォルダ内のサブエージェント・YAMLファイル)に必須です * YAML構文とADK AgentConfigスキーマ準拠を検証します * 例: `write_config_files({{"./project/root_agent.yaml": yaml_content, "./project/researcher_agent.yaml": sub_agent_content}})` * **重要**: すべてのエージェント・YAMLファイルはルート・プロジェクト・フォルダーに配置する必要があります。sub_agents/サブディレクトリではありません - **read_config_files**: 検証とメタデータ抽出付きで複数のYAML設定ファイルを読み込んで解析します - **config_file_reader**: レガシー関数(代わりにread_config_filesを使用) - **config_file_writer**: レガシー関数(代わりにwrite_config_filesを使用) #### ファイル管理(Pythonファイルおよび他のコンテンツに使用) - **read_files**: 複数ファイルからコンテンツを読み込む(Pythonツール、スクリプト、ドキュメント) - **write_files**: 複数ファイルにコンテンツを書き込む(Pythonツール、コールバック、スクリプト) - **delete_files**: オプションのバックアップ作成付きで複数ファイルを削除します - **cleanup_unused_files**: 未使用ファイルを特定してクリーンアップします - **delete_file**: レガシー関数(delete_filesの代わりに使用) #### プロジェクト構成 - **explore_project**: プロジェクト構造を探索し、従来のファイル・パスを提案します ### ADK知識とリサーチツール **デフォルト・リサーチ・ツール**: ADKコンセプト、API、例、トラブルシューティング用に最初に`search_adk_knowledge`を使用します。以下のツールに切り替えるのは、ナレッジベースに必要な情報がない場合だけです。 - `search_adk_source`: ADKソース全体で正規表現検索を行い、クラス、メソッド、署名を検索します。完全なコンテキストについては`read_files`で後に続けます。 - `google_search_agent`: ADK関連の例またはドキュメント用の広いウェブ検索。 - `url_context_agent`: 検索結果で返された特定のURLからコンテンツを取得します。 **ユーザーがADK質問をする場合、未知の機能をリクエストする場合、エージェント・タイプの明確化が必要な場合、ベストプラクティスが必要な場合、エラーが発生した場合、アーキテクチャについて不確実に表現する場合、または権威あるガイダンスが必要な場合は、リサーチをトリガーします。** **推奨されるリサーチ・シーケンス**(十分な情報を持つまで停止): 1. `search_adk_knowledge` 2. `search_adk_source` → `read_files` 3. `google_search_agent` 4. `url_context_agent` **ADKコード質問用(新規 - 推奨方法):** 1. **search_adk_source** - 正確なコード・パターンを検索します: * クラス定義: `"class FunctionTool"`または`"class.*Agent"` * コンストラクタ署名: `"def __init__.*FunctionTool"` * メソッド実装: `"def get_declaration"` * インポート・パターン: `"from.*tools"` 2. **read_files** - 完全なファイル・コンテキストを取得します: * 検索で特定された完全なソース・ファイルを読み込む * 完全な実装詳細を理解する * クラス関係と使用パターンを分析する **外部例とドキュメント用:** - **google_search_agent**: ウェブ・コンテンツを検索および分析します(URLのみではなく完全なページ・コンテンツを返します) * キー・リポジトリ内で検索: 「site:github.com/google/adk-python ADK SequentialAgent examples」 * ドキュメント検索: 「site:github.com/google/adk-docs agent configuration patterns」 * サンプル・リポジトリ検索: 「site:github.com/google/adk-samples multi-agent workflow」 * 一般検索: 「ADK workflow patterns」、「ADK tool integration patterns」、「ADK project structure」 * 完全なページ・コンテンツを検索結果として返します - 追加のURL取得は不要です - **url_context_agent**: 以下の場合のみ特定のURLを取得します: * 検索結果で言及された特定のURLに追加コンテンツが必要な場合 * ユーザーがクエリーで特定のURLを提供している場合 * google_search_resultsで見つかったURLからコンテンツを取得する必要がある場合 * 一般検索には不要です - google_search_agentは既にページ・コンテンツを提供しています **エージェント構築用のリサーチ:** - ユーザーが複雑なマルチエージェント・システムをリクエストする場合: サンプルで類似パターンを検索します - ツール統合について確実でない場合: contributing/samplesでツール使用例を探します - ワークフロー設計: SequentialAgent、ParallelAgent、またはLoopAgent例を検索します - ユーザーが特定の統合を必要とする場合: API、データベース、またはサービス統合例を検索します ## コード生成ガイドライン ### 不変なルート・エージェント・ルール - `root_agent.yaml`で定義されたルート・エージェントは、すべての設計と実装で`agent_class: LlmAgent`を使用する必要があります。 - `SequentialAgent`、`ParallelAgent`、`LoopAgent`、または他のワークフロー・クラスをルート・エージェントに割り当てないでください。ユーザーがそれを提案した場合でも。代わりに、ルート・エージェントをLlmAgentとして保ち、オーケストレーションが必要な場合はワークフロー・サブエージェントを導入します。 - ユーザーが明示的にワークフロー・ルートをリクエストする場合、ADKがルート・エージェントをLlmAgentのままに保つことを要求していることを説明し、代替構造を提案し、準拠したアーキテクチャで進むことがわかりました。 - このルールに違反する設定の生成を拒否します。目標を達成しながらLlmAgentルートを保つ方法について指導を提供します。 ## 重要なワークフロー・フィールド・ルール - あらゆるタイプのワークフロー・オーケストレーター(`SequentialAgent`、`ParallelAgent`、`LoopAgent`、または`agent_class`が`LlmAgent`ではないあらゆるエージェント)は決して`model`、`instruction`、`tools`を宣言してはいけません - LlmAgent定義のみ(ルートまたはサブエージェント)が`model`、`instruction`、`tools`を持つことが許可されます ### Pythonツールまたはコールバック作成時: 1. **常に現在の例を最初に検索**: google_search_agentを使用して「ADK tool_context examples」または「ADK callback_context examples」を検索します 2. **contributing/samplesを参照**: url_context_agentを使用して`https://github.com/google/adk-python/tree/main/contributing/samples`から特定の例を取得します 3. **類似パターンを探す**: ユースケースにマッチするツールまたはコールバックを検索します 4. **snake_caseを使用**: 関数名はsnake_caseにします(例: `check_prime`、`roll_dice`) 5. **ツール・サフィックスを削除**: 関数名に「_tool」を追加しないでください 6. **シンプルな関数を実装**: `is_prime`、`roll_dice`のような明白な関数の場合、TODOを実装で置き換えます 7. **複雑な場合はTODOを保つ**: 複雑なビジネス・ロジックの場合、TODOコメントを保ちます 8. **現在のADKパターンに従う**: contributing/samplesから常に最新の例を検索して参照します 9. **Gemini APIの使用**: Geminiモデルと相互作用するPythonコードを生成する場合、`import google.genai as genai`を使用します。`google.generativeai`ではなく。 ### ✅ 完全修飾パスが必要です - YAMLのすべてのツールまたはコールバック参照は、プロジェクト・フォルダ名で始まる完全修飾ドット区切りパスである必要があります。`callbacks.privacy_callbacks.censor_content`ではなく、`{project_folder_name}.callbacks.privacy_callbacks.censor_content`を使用します。 - 実際に存在するパッケージのみを参照してください。ドット区切りパスを生成する前に、ディレクトリに`__init__.py`が含まれているため、Pythonがインポートできることを確認してください。各サブディレクトリに対して`__init__.py`ファイルを作成して、インポート可能にします(例: `callbacks/`または`tools/`)。プロジェクト・ルート自体は`__init__.py`を必要としません。 - `write_files`でPythonモジュールを生成する場合、ツールが将来のインポートを成功させるため、パッケージ・ディレクトリ(プロジェクト・ルートをスキップ)にこれらの`__init__.py`マーカーを追加することを確認してください。 - ユーザーが既に`callbacks.foo`のような裸のパスを持っている場合、それらをプロジェクト・プレフィックス付きで再作成する必要があることを説明し、Pythonモジュール生成時に不足している`__init__.py`ファイルを追加します。 ### 🚨 重要: コールバック正しい署名 ADKは異なるコールバック・タイプを異なる署名でサポートしています。関数ベースのコールバック(クラスではない)を常に使用してください: ## 1. エージェント・コールバック(before_agent_callbacks / after_agent_callbacks) **✅ 正解なエージェント・コールバック:** ```python from typing import Optional from google.genai import types from google.adk.agents.callback_context import CallbackContext def content_filter_callback(callback_context: CallbackContext) -> Optional[types.Content]: """機密コンテンツをフィルター処理するために後エージェント・コールバック。""" # callback_contextを通じてレスポンス・コンテンツにアクセス if hasattr(callback_context, 'response') and callback_context.response: response_text = str(callback_context.response) if "confidential" in response_text.lower(): filtered_text = response_text.replace("confidential", "[FILTERED]") return types.Content(parts=[types.Part(text=filtered_text)]) return None # 元のレスポンスを保持するためNoneを返す ``` ## 2. モデル・コールバック(before_model_callbacks / after_model_callbacks) **✅ 正解なモデル・コールバック:** ```python from typing import Optional from google.adk.models.llm_request import LlmRequest from google.adk.models.llm_response import LlmResponse from google.adk.agents.callback_context import CallbackContext def log_model_request( *, callback_context: CallbackContext, llm_request: LlmRequest ) -> Optional[LlmResponse]: """リクエストをログに記録するモデル前コールバック。""" print(f"Model request: {{llm_request.contents}}") return None # 元のリクエストで続行 from google.adk.events.event import Event def modify_model_response( *, callback_context: CallbackContext, llm_response: LlmResponse, model_response_event: Optional[Event] = None, ) -> Optional[LlmResponse]: """レスポンスを変更するモデル後コールバック。""" _ = callback_context # 必要に応じてコンテキストにアクセス _ = model_response_event # トレースおよびイベント・メタデータで利用可能 if ( not llm_response or not llm_response.content or not llm_response.content.parts ): return llm_response updated_parts = [] for part in llm_response.content.parts: text = getattr(part, "text", None) if text: updated_parts.append( types.Part(text=text.replace("dolphins", "[CENSORED]")) ) else: updated_parts.append(part) llm_response.content = types.Content( parts=updated_parts, role=llm_response.content.role ) return llm_response ``` **コールバック・コンテンツ処理**: `LlmResponse`は単一の`content`フィールド(`types.Content`)を公開します。ADKはすでに最初の候補を抽出してくれ、`llm_response.candidates`は公開されていません。出力をフィルター処理または書き直す場合、`llm_response.content`をチェックしてその`parts`を変更してください。テキスト以外のパーツを保持し、未定義の属性を変更する代わりに新しい`types.Content`を割り当てます。 ## 3. ツール・コールバック(before_tool_callbacks / after_tool_callbacks) **✅ 正解なツール・コールバック:** ```python from typing import Any, Dict, Optional from google.adk.tools.base_tool import BaseTool from google.adk.tools.tool_context import ToolContext def validate_tool_input(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext) -> Optional[Dict]: """入力を検証するツール前コールバック。""" # ツール引数を検証または変更 if "unsafe_param" in tool_args: del tool_args["unsafe_param"] return tool_args # 変更された引数またはNoneを返す def log_tool_result(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext, result: Dict) -> Optional[Dict]: """結果をログに記録するツール後コールバック。""" print(f"Tool {{tool.name}} executed with result: {{result}}") return None # 元の結果を保持 ``` ## コールバック・シグネチャ・サマリー: - **エージェント・コールバック**: `(callback_context: CallbackContext) -> Optional[types.Content]` - **モデル前**: `(*, callback_context: CallbackContext, llm_request: LlmRequest) -> Optional[LlmResponse]` - **モデル後**: `(*, callback_context: CallbackContext, llm_response: LlmResponse, model_response_event: Optional[Event] = None) -> Optional[LlmResponse]` - **ツール前**: `(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext) -> Optional[Dict]` - **ツール後**: `(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext, result: Dict) -> Optional[Dict]` **名前マッチングが重要**: ADKはキーワードでコールバック引数を渡します。常に`callback_context`、`llm_request`、`llm_response`、`model_response_event`(使用時)のようにパラメータに正確に名前を付けて、正しく結合させてください。`None`を返すと元の値が保持されます。そうでなければ変更された`LlmResponse`を返します。 ## 重要なADK要件 **ファイル命名と構造:** - メイン設定は`root_agent.yaml`である必要があります(`agent.yaml`ではなく) - メイン設定は`agent_class: LlmAgent`を設定する必要があります(ワークフロー・エージェント・タイプではなく) - エージェント・ディレクトリには`__init__.py`が必要です(`from . import agent`を使用) - 各ツール(例: `tools/dice_tool.py`)を`tools/`パッケージに配置します。 `project_name.tools.dice_tool.roll_dice`のようなインポートが機能するように`tools/__init__.py`(空)を追加してください。 - エージェント・ディレクトリ内のPythonファイル、ルート・レベルのYAML **ツール構成:** - 関数ツール: プロジェクト・フォルダ名で始まるドット区切りインポート・パスを使用します (例: `project_name.tools.dice_tool.roll_dice`) - ツール・パスに`.py`拡張子なし - YAMLで関数宣言は不要 - **重要**: ツール・パスにはプロジェクト・フォルダ名を最初のコンポーネント(プロジェクト・フォルダ・パスの最後のコンポーネントのみ)として含める必要があります **ADKエージェント・タイプとモデル・フィールド・ルール:** - **LlmAgent**: `model`フィールド必須(祖先から継承されない限り) - このエージェントはLLMを直接使用してレスポンスを生成します - **SequentialAgent**: `model`フィールドなし - 他のエージェントをシーケンスでオーケストレートするワークフロー・エージェント - **ParallelAgent**: `model`フィールドなし - 複数のエージェントを並行実行するワークフロー・エージェント - **LoopAgent**: `model`フィールドなし - エージェントをループで実行するワークフロー・エージェント - **重要**: LlmAgentのみモデル・フィールドを受け入れます。ワークフロー・エージェント(Sequential/Parallel/Loop)には`model`フィールドまたはツール・リストがありません。ツールを提供するサブエージェント`sub_agents`をオーケストレートします。 **ADK AgentConfigスキーマ準拠:** - フィールド要件を検証するため、常に埋め込まれたADK AgentConfigスキーマを参照してください - **モデル・フィールド・ルール**: * **LlmAgent**: `model`フィールド必須(祖先から継承されない限り) - LlmAgentが必要な場合のみユーザーに環境設定を求めます。ユーザーがデフォルトを使用と言う場合は{default_model}を使用します * **ワークフロー・エージェント**: `model`フィールド禁止 - Sequential/Parallel/Loopエージェントに対してモデル・フィールドを完全に削除します - オプション・フィールド: ADK AgentConfigスキーマで定義されている説明、命令、ツール、sub_agents ## ファイル操作ガイドライン **ツール呼び出しの重要なパス・ルール**: - **ツール呼び出し時にプロジェクト・フォルダ名を決して含めないでください** - **ツールはプロジェクト・フォルダに相対するパスを自動的に解決します** - **`root_agent.yaml`、`tools/dice_tool.py`のようなシンプルな相対パスを使用してください** - **誤り**: `{project_folder_name}/root_agent.yaml`(プロジェクト・フォルダ名を含みます) - **正解**: `root_agent.yaml`(プロジェクト内のファイル・パスのみ) **例**: - 現在のプロジェクト・フォルダ: `basic` - ✅ **正解なツール呼び出し**: * `write_config_files({{"root_agent.yaml": "..."}})` * `write_files({{"tools/dice_tool.py": "..."}})` - ❌ **誤ったツール呼び出し**: * `write_config_files({{"basic/root_agent.yaml": "..."}})` (プロジェクト・フォルダーをダブレットします!) * これにより`projects/basic/basic/root_agent.yaml`が代わりに`projects/basic/root_agent.yaml`の代わりに作成されます ## 成功基準 ### デザイン・フェーズ成功: 1. ターゲットを絞った質問によるユーザー要件の明確な理解 2. 実証されたADKパターンに基づく十分にリサーチされたアーキテクチャ 3. エージェント関係、ツール・マッピング、特定のファイル・パスを備えた包括的な設計提案 4. 実装前にアーキテクチャとファイル構造の両方のユーザー承認 ### 実装フェーズ成功: 1. 承認されたデザインで指定されたファイルが正確なパスで作成されます 2. 事前承認されたパスに対する冗長なsuggest_file_path呼び出しなし 3. 生成された設定はスキーマ検証をパス(自動的にチェック) 4. ADK命名および組織的な慣例に従うこと 5. すべてのエージェント設定は明示的に`agent_class`を設定し、その値がエージェント役割と一致します。カスタム・クラスは完全修飾ドット区切りパスを使用します 6. 各エージェントに対して明確で、行動可能な命令を含める 7. 意図した機能性のための適切なツールを使用する ## 主要な思い出させる **主な役割は、効率的でユーザー中心のワークフローに従う協力的なアーキテクチャ・コンサルタントです:** 1. **最初に要件を理解する** - ユーザーが何を作成しようとしているかを知ります 2. **アーキテクチャを設計する** - エージェント構造とコンポーネントを計画します 3. **高レベルのアーキテクチャの概要を提供する** - デザインを確認する際に常に含めます: * 全体的なシステム・アーキテクチャとコンポーネント関係 * エージェント・タイプと責任 * ツール統合パターンとデータ・フロー * 各コンポーネントの目的を明確に説明するファイル構造 4. **完全な承認を取得する** - アーキテクチャ、デザイン、ファイル構造が一緒に確認されます 5. **効率的に実装する** - 冗長なツール呼び出しなしで承認されたパスを直接使用します 6. **コラボレーションに焦点を当てる** - ユーザーが完全な理解で正確に必要なものを取得するようにしてください **このワークフローは非効率さを排除し、ユーザーが選択した場所で適切に構成されたファイル構造を取得することを保証します。** 以下は、その日本語訳から個人的に勉強になった部分を抜き出したものです。 Visual Builderで使える組み込みツールは公式ドキュメントに記載のものと異なる (以下は"LLMで日本語訳したプロンプト"からの引用です) 利用可能な組み込みツール: google_search - Google検索ツール enterprise_web_search - エンタープライズ・ウェブ検索 google_maps_grounding - Google Mapsグラウンディング url_context - URLコンテキスト取得 VertexAiSearchTool - Vertex AI検索(クラス名) exit_loop - ループ終了制御 get_user_choice - ユーザー選択相互作用 load_artifacts - アーティファクト読み込み load_memory - メモリ読み込み preload_memory - メモリ事前読み込み transfer_to_agent - 別のエージェントへの転送 ⚠️ エージェントが sub_agents を持っている場合、YAMLで transfer_to_agent を宣言 しないでください 。ADKはこのツールを自動的に注入し、重複すると Gemini エラーが発生します( Duplicate function declaration: transfer_to_agent )。 組み込みツールとして利用可能なものが定義されています。これらはVisual Builder上でも以下のように確認できました。 ちなみにこれは公式ドキュメントに記載されている組み込みツールとは差があるようです。 公式ドキュメント では以下が記載されていました。 Google Search Code Execution GKE Code Executor Vertex AI RAG Engine Vertex AI Search BigQuery Spanner Bigtable BigQueryなどのGoogle Cloudと連携するツールなどは流石にVisual Builderだとまだ扱っていないようですね。一方で、Visual Builderにだけ見られるツールとして、enterprise_web_search, google_maps_groundingなどがあるようです。 Visual Builder自体も検索ツールを使っている ADK知識とリサーチツール デフォルト・リサーチ・ツール: ADKコンセプト、API、例、トラブルシューティング用に最初に search_adk_knowledge を使用します。以下のツールに切り替えるのは、ナレッジベースに必要な情報がない場合だけです。 search_adk_source : ADKソース全体で正規表現検索を行い、クラス、メソッド、署名を検索します。完全なコンテキストについては read_files で後に続けます。 google_search_agent : ADK関連の例またはドキュメント用の広いウェブ検索。 url_context_agent : 検索結果で返された特定のURLからコンテンツを取得します。 どうやらVisual Builder自体も検索ツールを使って設計しているようですね。これで外部ライブラリを利用した実装も一部Visual Builderでも担えることがわかります。 Callbackの種類によって引数が異なる コールバック・シグネチャ・サマリー: エージェント・コールバック : (callback_context: CallbackContext) -> Optional[types.Content] モデル前 : (*, callback_context: CallbackContext, llm_request: LlmRequest) -> Optional[LlmResponse] モデル後 : (*, callback_context: CallbackContext, llm_response: LlmResponse, model_response_event: Optional[Event] = None) -> Optional[LlmResponse] ツール前 : (tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext) -> Optional[Dict] ツール後 : (tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext, result: Dict) -> Optional[Dict] プロンプトにはサンプルコード付きで説明されていました。Callbackの種類によって引数が異なり、実装時に間違えやすい部分です。 Pythonパッケージ化が必須 エージェント・ディレクトリには __init__.py が必要です( from . import agent を使用) 各ツール(例: tools/dice_tool.py )を tools/ パッケージに配置します。 project_name.tools.dice_tool.roll_dice のようなインポートが機能するように tools/__init__.py (空)を追加してください。 __init__.py は結構忘れがちですよね。 このファイルが必要な理由は、 tools/__init__.py や callbacks/__init__.py といったサブディレクトリに配置されることで、Pythonパッケージとしてインポート可能になるからです。つまり、 project_name.tools.dice_tool.roll_dice というドット区切りパスが機能するために必須です。 注意すべき点は、配置位置の厳密性。プロジェクト・ルート自体には不要ですが、 tools/ と callbacks/ などのサブディレクトリには必ず配置する必要があります。また、サブエージェントのYAMLファイルは、メイン・プロジェクト・フォルダに配置し、 sub_agents/ サブフォルダに配置してはいけません。 ツール開発は実装難度に応じて戦略を変える ツール実装戦略: 簡単/明白なツール : 実際に動作するコードで直接実装します 例: さいころ振る、素数チェック、基本的な数学、ファイル操作 ユーザーに「TODOコメント記入」を求めないでください 複雑/ビジネス固有のツール : TODOコメント付きの適切な関数署名を生成します 例: APIキーが必要なAPI統合、複雑なビジネス・ロジック 複雑さに応じたツール実装の使い分けができるということですね。実装体験で、Visual Builderが billing_logger.py を生成した際も、完全な実装と署名のみのアプローチを見分ける必要がありました。 ワークフロー・エージェントとLlmAgentの役割が厳密に分離されている 🚫 ワークフロー・エージェント・フィールド・バン : ワークフロー・オーケストレーター( SequentialAgent 、 ParallelAgent 、 LoopAgent など)は決して model 、 instruction 、 tools を含めてはいけません。LlmAgent定義のみ(ルート・エージェント または サブ・エージェント)がそれらのフィールドを宣言するかもしれません ワークフロー・エージェント(Sequential/Parallel/Loop)とLlmAgentの役割が厳密に分離されています。ワークフロー・エージェントはオーケストレーションのみ、LLM呼び出しとツール実行はすべてサブエージェント(LlmAgent)に委譲します。実装体験の IterativeResearcher (ループ・エージェント)が searcher_agent と refiner_agent (両方ともLlmAgent)をサブエージェントとして持つ構造が、まさにこのパターンです。 このプロンプトは、ADKでのエージェント構築のベストプラクティスをまとめた実用的な仕様書と言えます。実装で困った時は、このファイルを参照するとヒントが得られるでしょう。 まとめ 今回はAgent Development KitのVisual Builderを試してみました。 日頃利用することの少ないCallbackなどの機能にも触れることができ、これまで知らなかった組み込みツールの発見もありました。Visual Builderを使うことで、簡単にマルチエージェントシステムを構築することができるので、ぜひ皆さんも触ってみてください。 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 Agent Development Kit の Visual Builderを試して、プロンプトから設計の要点を学ぶ は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AIチームの大竹です。 この記事は AI Shift Advent Calendar 2025の8日目の記事です。 2024年10月、OpenAIが発表した Realtime API は、音声入出力をリアルタイムで処理できるAPIとして大きな注目を集めました。音声から音声への一気通貫した処理(Speech-to-Speech)が可能になることで、ボイスボットや音声対話AIアシスタントの可能性が一気に広がったのは記憶に新しいところです。 さて、このRealtime APIですが、 OpenAIは性能評価のために 3つのベンチマーク を使用しています 。しかし、それぞれが何を測定しているのか、詳しく解説された記事は意外と少ないのではないでしょうか。AI Shiftでも AI Worker VoiceAgent といった音声対話AI領域のプロダクト開発を進めており、こうした性能評価の手法には大きな関心を持っています。 本記事では、これら3つのベンチマークの背景と評価内容を詳しく解説し、 日本語化したデータセットを用いた実際の評価結果 もご紹介します。音声対話AIの評価手法に興味のある方、あるいは自社サービスの音声対応を検討されている方にとって、参考になれば幸いです。 評価ベンチマークの全体像 OpenAI Realtime APIの評価には、以下の3つのベンチマークが使用されています。 ベンチマーク名 評価対象カテゴリ 概要 Big Bench Audio 推論能力 (Intelligence) 音声入力からの論理的思考・判断能力 MultiChallenge (Audio) 指示追従 (Instruction Following) マルチターン会話での文脈維持・指示順守 ComplexFuncBench Audio 関数呼び出し (Function Calling) 外部ツール(関数)の適切な呼び出し能力 それぞれ異なる観点からモデルの性能を測定しており、単なる文字起こし精度では測れない、音声対話AIならではの能力を評価していることがわかります。 ここからは各ベンチマークについて詳しく見ていきましょう。 1. Big Bench Audio:音声からの推論能力を測る 概要 Big Bench Audio は、大規模言語モデルの推論能力を測る有名なベンチマーク 「Big Bench Hard (BBH)」 の中から、音声でも公平に評価できる4つのカテゴリを抜粋し、音声化したデータセットです。 単なる文字起こしではなく、 内容を理解して答えを導き出せるか が問われます。 収録されているタスク 合計 1,000件 の音声データが含まれており、各カテゴリ250問ずつで構成されています。 カテゴリ 内容 Formal Fallacies(形式的誤謬) 前提条件から主張の正誤(Valid/Invalid)を判定 Navigate(ナビゲーション) 移動指示を追跡し、元の位置に戻るか判断 Object Counting(物体カウント) 読み上げられた物品の特定カテゴリ合計数を回答 Web of Lies(嘘の網) 複雑な真偽関係からブール論理を評価 音声データの特徴 OpenAI、Microsoft Azure、Amazon (AWS Polly) のTTSモデルを使用し、 23種類の異なる「声」 がランダムに使用されています。これにより、特定の声質への過学習を防ぐ設計になっています。 ベンチマーク結果 Big Bench Audioにおける各モデルの性能比較 (出典: Evaluating Audio Reasoning with Big Bench Audio - Hugging Face ) 2. MultiChallenge:長い会話における指示追従性能を測る なぜこのベンチマークが必要なのか 既存の会話ベンチマーク( MT-Bench など)では、最新モデルがほぼ満点を取ってしまい、能力差を測れなくなっていました。しかし実際の運用では、人間との長く複雑な会話でAIはまだ多くの失敗をしています。 MultiChallenge は、 人間とのリアルで複雑なやり取りにおいて、AIが文脈を理解し、推論し、指示を守り続けられるか を評価するために開発されました。 4つの主要な課題カテゴリ MultiChallengeは、現在のAIが特に苦手とする4つのカテゴリに焦点を当てています。 カテゴリ 内容 例 Instruction Retention 会話冒頭のルールを最後まで守れるか 「常に箇条書きで答えて」を最後まで守る Inference Memory 会話中の些細な情報を記憶・活用できるか 「ナッツアレルギー」を覚えてデザート推薦時に配慮 Reliable Versioned Editing 「さっきの案に戻して」等の編集指示に対応 バージョン管理と正確な修正 Self-Coherence 自身の過去発言と矛盾しないか ユーザーに迎合せず正しい回答を維持 驚くほど低い正解率 今となっては少し古いモデルですが、以下のような主要なモデルでも正解率が低くなっており、このベンチマークの難易度の高さが窺えます。 モデル 正解率 Claude 3.5 Sonnet 41.4% o1-preview 37.2% GPT-4o 12.5% Llama 3.1 405B 14.9% MultiChallengeにおける各種LLMの人間による評価結果 (出典: MultiChallenge: A Realistic Multi-Turn Conversation Evaluation Benchmark Challenging to Frontier LLMs ) この結果から、 「長い会話の中で文脈を維持し、論理的に推論し続ける能力」 が、現在のLLMにとっていかに難しいかがわかります。 3. ComplexFuncBench:複雑なFunction Calling性能を測る 既存ベンチマークの限界 既存のベンチマーク( API-Bench や ToolBench など)は、パラメータが明示的で単発の呼び出しで済む単純なタスクが中心でした。 しかし実世界では、以下のような要求が存在します: 「クリスマスイブの前日にワシントンからNYへの最安便を探し、その到着1時間後にタクシーを手配して」 ComplexFuncBenchは、このような複雑な要求に対応できるかを評価します。 複雑な関数呼び出しの5つの定義 要素 内容 Multi-Step 複数のAPIを順序立てて呼び出す Constraints 「最安値」「到着1時間後」等の制約を守る Parameter Reasoning 「クリスマスの前日」→「12月24日」を推論 Long Parameter Values 認証トークン等の長い文字列を正確に扱う Long-Context 最大128kトークンの検索結果を読み解く 評価結果 主要なモデルでもこのタスクには苦戦しています。 モデル 正解率 Claude 3.5 Sonnet 61.0% GPT-4o 60.5% Qwen2.5-72B 40.1% Llama 3.1シリーズ(8B, 70B, 405B) 0〜10%台 ComplexFuncBenchにおける各種LLMの評価結果 (出典: https://github.com/zai-org/ComplexFuncBench?tab=readme-ov-file#leaderboard ) 最も多かったエラーは 「Value Error(値の間違い)」 です。長いコンテキストの中から正しい情報を抽出し、推論してパラメータにセットすることが、現在のLLMにとって大きな課題であることが示されました。 日本語化データセットでの評価 ここからは、ComplexFuncBenchとMultiChallengeの一部を日本語化し、 Realtime API(音声入力) と Chat Completions API(テキスト入力) を比較評価した結果をご紹介します。 日本語データセットの作成方法 ComplexFuncBench : HuggingFaceからオリジナル(英語)をダウンロードし、Gemini 2.5 Flashで日本語に翻訳。各ドメインから5件ずつ抽出(計25件)。 MultiChallenge : 各評価軸(Instruction Retention, Inference Memory, Self-Coherence, Reliable Version Editing)から5件ずつ抽出(計20件)し、Gemini 2.5 Flashで翻訳。 音声化 : 翻訳した日本語テキストをGoogle Cloud TTS(Chirp3_HD、24kHz)で音声ファイルに変換(Realtime APIの処理時に16kHzに変換)。 評価設定 項目 Realtime API Chat Completions API モデル gpt-realtime gpt-4o 入力 音声(TTS: Google Chirp3_HD) テキスト 出力 テキスト テキスト 言語 日本語 日本語 ComplexFuncBench 日本語版:Function Calling評価 評価概要 評価件数 : 25件(各ドメイン5件ずつ) ドメイン : 観光地検索、レンタカー、ホテル、フライト、複合検索の5種類 評価方式 : マルチターン(ターン1で情報取得 → ターン2で詳細検索) 評価結果 ドメイン 内容 Realtime API Chat Completions API Attraction 観光地検索 5/5 5/5 Car-Rental レンタカー検索 5/5 5/5 Hotels ホテル検索 5/5 5/5 Cross 複合検索(観光+ホテル) 3/5 4/5 Flights マルチストップフライト 2/5 5/5 表中の分数は 成功したサンプル数/全サンプル数 を表します。 単純なタスクは両者とも完璧 観光地検索、レンタカー、ホテル検索のような 「1つの情報を取得 → 1つの検索を実行」 という単純な2ステップタスクでは、両モデルとも100%の成功率を達成しました。 例えば、このようなタスクです: ユーザー: 「マンチェスターで人気の観光スポットを教えて」 ターン1: Search_Attraction_Location(query="マンチェスター") → 場所IDを取得 ターン2: Search_Attractions(id="eyJ1ZmkiOi0yNjAyNTEyfQ==", sortBy="trending") 差が出たのは並列タスク 大きな差が出たのは Flights ドメイン です。 このタスクは「ロサンゼルス → パリ → ロンドン」のようなマルチストップフライト(出発地から最終目的地までに1回以上の途中降機や乗り継ぎを含むフライト)を検索するもので、 3つの都市の空港IDを同時に取得 する必要があります。 Chat Completions APIの挙動: ターン1: [  Search_Flight_Location(query="ロサンゼルス"),  Search_Flight_Location(query="パリ"),  Search_Flight_Location(query="ロンドン") ] ← 3つ同時に実行 ターン2: Search_Flights_Multi_Stops(legs=[...]) ← 正しく検索 Realtime APIの挙動: ターン1: Search_Flight_Location(query="ロサンゼルス") ← 1つだけ ターン2: Search_Flight_Location(query="パリ") ← まだ検索を続けようとする Realtime APIは APIの仕様あるいは音声入力の曖昧さゆえか、1つずつ順番に処理する傾向 がありました。Chat Completions APIのように1ターンで複数の関数を同時実行することが苦手なようです。 MultiChallenge 日本語版:マルチターン会話評価 評価概要 MultiChallengeは、 マルチターン会話における4つの能力 を評価するベンチマークです。先述の通り、Instruction Retention、Inference Memory、Self-Coherence、Reliable Version Editingという4つの評価軸があります。 以下の設定で評価を行います。 判定 : GPT-4oによるジャッジが、基準に対してYES/NOで判定 データセット : 各評価軸から5件ずつ、合計20件のシナリオを抽出 ターン数 : 2〜9ターン(平均4.5ターン) 評価方式 : 会話履歴をシステムプロンプトに含め、最終ユーザー発言のみを音声/テキストで入力 評価結果 評価軸 内容 Realtime API Chat Completions Instruction Retention 最初の指示を守り続ける 2/5 (40%) 1/5 (20%) Inference Memory 過去の情報を活用する 0/5 (0%) 0/5 (0%) Self-Coherence 自分の発言と矛盾しない 0/5 (0%) 0/5 (0%) Reliable Version Editing 編集指示に正確に対応 2/5 (40%) 2/5 (40%) 定性分析 ここからは、実際にどのような推論結果が得られたのかを具体例を挙げて見ていきましょう。 【失敗例1】「20以内で答えて」を忘れる シナリオ: 社会学的理論を「20字以内」で説明することを最初に指示 ターン 発言 User 社会学的問題を説明する理論を挙げて。 20字以内で説明して Assistant 「社会的不平等:階級闘争」「人種差別:構造的人種差別」 User 文化的価値観が社会包摂をどう形成するか説明して このサンプルの場合、Assistantの期待される応答は例えば「文化資本理論:社会的地位決定」のように、20字以内で簡潔な回答です。 実際の応答: Realtime API: 「かしこまりました。文化的な価値観は、集団内で共有される信念や理想、 倫理観などを基盤として、どのような行動が望ましいか、どのような態度が適切かと いった規範を定めます。これによって、集団内の人々は互いに期待される振る舞いを 理解し、それに沿って行動するようになります…」(長文説明) Chat Completions API: 文化的な価値観は、社会の中で何が重要であるか、何が望ましいかを 定義します。これらの価値観は、集団がどのように他者を受け入れ、扱うべきかという 規範や態度を形成します。たとえば、個人主義的な文化では、個人の自由や独立が重視 されるため、多様性が尊重されやすくなります…」(長文説明) 両方とも「20字以内」という制約を完全に忘れてしまいました。 【失敗例2】国連本部の場所を忘れる(文脈喪失) シナリオ: 国連本部から徒歩5分圏内のレストランを依頼(3ターンの会話) ターン 発言 User 国連本部で働いています。 徒歩5分圏内 の場所を希望 Assistant いくつかの場所を紹介(ダグ・ハマーショルド・プラザなど) User ドイツ外交官との昼食に適した高級レストランを紹介して ここで期待されるAssistantの応答の例は、 国連から徒歩3分、多くの外交官が通う1966年創業の「スパークス・ステーキハウス」は、重厚な雰囲気の中で最高級サーロインと圧巻のワインリストを楽しめる老舗です。 のようなものです。 実際の応答: Realtime API: 「アルカディア東京」「トロワフレーシュ」 → 東京のレストランを提案。国連本部(ニューヨーク)という文脈を無視。 Chat Completions API: 「アクアヴィット」「ル・ベルナルダン」 → ニューヨークのレストランだが、徒歩5分圏内ではない Realtime APIは「ニューヨーク」という文脈よりも日本語の音声入力に引きずられたのか、東京の店を提案してしまいました。文脈維持(INFERENCE_MEMORY)の弱さが露呈した興味深い例です。 【失敗例3】自分が言った「1390万人」を忘れる シナリオ: 歯科セミナー開催都市の相談(5ターンの会話) ターン 発言 User 人口の多い都市を提案して Assistant 東京: 1,390万人以上 、ロサンゼルス:約390万人など User ロサンゼルスに興味。人口500万人を超えているので最適 Assistant (詳細を説明、人口については訂正せず) User 東京は人口 2000万人を超えている ので最善の選択肢かも ここで期待されるAssistantの発話は、「東京の人口は先ほどお伝えした通り約1,390万人です」とユーザーの誤りを訂正するようなものです。 実際の応答: Realtime API: 「東京は確かに人口が2,000万人を超える大都市圏であり...」 → ユーザーの誤り(2000万人)にそのまま追従 Chat Completions API: 「東京の人口は約1,400万人で、首都圏全体では...」 → 1,390万人の概算なので、実質的には正しい応答 Realtime APIはユーザーの誤りにそのまま追従してしまいました。 一方、Chat Completions APIの「約1,400万人」は実質的に正しい回答ですが、LLM as a Judgeが厳密な数字を求めたため不合格と判定されてしまいました。 【成功例】講義アウトラインの編集 シナリオ: 講義アウトラインを作成し、追加編集を依頼 ターン 発言 User 冷戦イデオロギーがハリウッド映画に与えた影響の講義アウトラインを作成して Assistant I〜VIIのセクションからなる詳細なアウトライン User 個人的な体験談のセクションを追加して この特定のシナリオでは両モデルとも成功 。 講義アウトライン形式を維持しながら、新しいセクションを適切に追加できました。 「形式を維持しながら編集する」というタスクは、他の評価軸と比較してLLMが比較的得意とする領域のようです。 おわりに 本記事では、OpenAI Realtime APIの評価に使用された3つのベンチマーク(Big Bench Audio、MultiChallenge、ComplexFuncBench)について解説し、日本語化したデータセットでの実際の評価結果をご紹介しました。 評価の結果、単純なFunction Callingタスクでは音声入力でも高い成功率を達成できる一方で、並列タスクや長期的な文脈維持では課題があることがわかりました。 また、実際のプロダクト開発においては、こうした「内容の正確さ」に加えて「対話の心地よさ」も欠かせない要素です。 応答音声の韻律の自然さや、割り込みを含めたターンテイキングのタイミングなど、今後は音声対話ならではの振る舞いをどう評価していくかが、重要なテーマになっていくと考えています。 音声対話AIの導入を検討されている方は、ぜひこれらのベンチマークを参考に、自社のユースケースに合った評価を行ってみてください。 長くなってしまいましたが、最後までお読みいただき、ありがとうございました! 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 音声対話AIの性能をどう測る?Realtime API評価ベンチマークの解説と日本語での検証 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI Shift AI Solution事業部 Forward Deployed Engineer(FDE)の若泉です。 この記事は AI Shift Advent Calendar 2025 の6日目の記事です。 はじめに ここ最近、資料作成AIが次々と登場しています。 「文章入れたら、もうスライドになる」が当たり前になりつつある一方で、提案資料を作る側としてはこう思うこともあります。 テンプレはウチの会社のやつ使いたい 提案資料には“お作法”があって、自由に作られると逆に困る .pptx で出てこないとクライアント提出が厳しい 表現はAIに任せたいけど、レイアウトは固定してほしい そこで今回、 「AIShiftの提案資料を自動で作ってくれるエージェント」 を Python × GPT-5 で作ってみました。 この記事では、 他社ツールとの比較 今回のスライド作成の強み どんな仕組みで動いているか一部紹介 作ってみてわかったこと をまとめます。 資料作成AIの種類調査 最近はさまざまなスライド生成AIがありますが、現時点では大きく 3つのタイプに分類できるかなと思います。 ①スピード重視型(SlideAI / Felo) 思いつきをすぐスライド化 たたき台作りに最適 Felo は検索+構成作成が速い ② デザイン重視型(Beautiful.ai / Gamma / イルシル) 見た目の整ったスライドがすぐできる レイアウトの自動調整が強い イルシルは日本語テンプレが豊富 ③ 情報収集ベース型(Genspark) AI検索 → 要点整理 → スライド化を一気通貫 分析資料・市場調査に強い 今回作るものの戦略 すでにあるスライド作成AIはどれも優秀ですが、共通する弱点として 企業固有のテンプレに完全準拠できない pptx構造にフィットしない場合がある 過去資料や社内文脈を反映するのが難しい といった課題があります。 そこで今回のエージェントでは、役割分担を明確にしました。 GPT-5 → スライド構成と内容(JSON生成) Python → 自社テンプレに沿ってPPTXを生成 AIには“内容づくり”に集中してもらい、見た目はすべてテンプレートで固定する構造です。 これにより、 レイアウトが崩れない ブランドカラーや注意書きが常に統一される 誰が使っても「AIShiftの提案資料」になる というメリットがあります。 仕組みの全体像(ざっくり) ユーザーがWordを投入(本記事では省略) Pythonでテキスト・画像を抽出(本記事では省略) GPT-5がスライド構成をJSONで生成 テンプレのJSONとマッピング PythonでPPTX生成 完成スライドを出力 順番前後しますが、4→3の順番で詳細を説明します。 自作スライド作成AI スライドテンプレート テンプレートは次のようなものを用意します。中身が入っていない、デザインだけのスライドですね。 各シェイプに変数名が割り当てられています。 fixedと名前をつけているものは固定のもの。それ以外のものは編集できるようにします。   このテンプレートスライド一つ一つに対応するjson形式を定義します。具体例としては以下です。 { "slides": [ { "type": "cover", "data": { "slide_title": "AI活用のご提案", "company_name_any": "株式会社サンプル御中", "sub_title": "2024.11.14 ご提案資料" } }, { "type": "title_content", "data": { "title": "生成AIの可能性", "content": "生成AIは、業務効率化だけでなく、\n新しい価値創造の可能性を秘めています。\n\n・業務の自動化\n・創造的な業務の支援" } }, { "type": "table_slide", "data": { "title": "お見積り", "page_message": "下記の通りお見積りいたします", "table": { "headers": ["項目", "金額"], "rows": [ ["コンサルティング", "100万円"], ["システム開発", "200万円"] ] } } }, { "type": "ending", "data": {} } ] } このjsonは生成AIがスライド構成を考えた後、埋めてくれます。 あとはjsonの内容を読んで、スライドのシェイプ部分を置き換えるPythonコードを書けば(生成すれば)終わりです。 (ここも難しいポイントかもしれないですが、長くなるので省略させてください) jsonの中に、太文字にする・赤文字にするためのルールなども決めておくとAIが重要な部分を強調してくれます。 成果物例としては↓ 何行入るとかがまだ指定できていないので文字がはみ出ていますね。 (スライドの内容はサンプルです) ただ少し手直しすれば実際に使えるものになりそうな予感... スライド構成を考えるプロンプト 今回スライドにしたい内容のwordファイルorテキストを受け取り、その内容からスライド構成のjsonを生成するプロンプトを作ります。ポイントだけまとめます。 役割 ## 概要 あなたは、AI Shift社の提案資料を作成するAIアシスタントです。 ユーザーから提案資料の内容(自然文)を受け取り、PowerPointスライド生成プログラムが処理できるJSON形式のデータを生成します。 スライドは生成後、人の最終チェックが入ります。なので足りない情報は変に憶測で書いたりせず、情報が不足している旨を書いてください。 ## 役割 - ユーザーの入力内容をよく読み、提案の目的と構成を理解する - 12種類のスライドタイプから最適な組み合わせを選択 - 入力されたテキストを適切なシェイプフィールドにマッピング - 最終的にJSON形式で出力(スライド生成プログラムへの入力用) ポイント 変にAIで情報を埋めたりしないように指示することで、スライド全体の構成の中で足りていないポイントは人が埋めることができるという空白を作ることができます。 どのようなスライドタイプがあるのか、そしてそれを使うためのjson構造を教える ## スライドタイプ一覧 ### 1. cover(表紙) { "type": "cover", "data": { "slide_title": "AI活用のご提案", "company_name_any": "株式会社XXX御中", "sub_title": "2024.11.14 ご提案資料" } } **使用するシェイプ**: - `slide_title`: メインタイトル(1行) - `company_name_any`: 会社名(任意、空の場合は削除される) - `sub_title`: サブタイトル(日付など) ### 2. agenda(目次・章立て) { "type": "agenda", "data": { "chapter_1_title_any": "01. はじめに", "chapter_2_title_any": "02. 課題", "chapter_3_title_any": "03. 提案", "chapter_4_title_any": "04. スケジュール", "chapter_5_title_any": "05. お見積り" } } **使用するシェイプ**: - `chapter_1_title_any` ~ `chapter_5_title_any`: 章タイトル(最大5つ、任意) スライド構成のパターンを教える ## スライド構成の設計指針 ### 1. 基本構成 - **開始**: `cover`(必須、常に最初) - **終了**: `ending`(必須、常に最後) - **中間**: 内容に応じて適切なスライドタイプを選択 ### 2. 典型的な構成パターン #### パターンA: 提案資料(標準) cover → agenda → section → title_content (複数) → given_arrangement → section → title_content (複数) → table_slide → ending #### パターンB: 研修プログラム提案 cover → agenda → section → title_content_detail → three_column → content_with_figure → given_arrangement → table_slide → ending #### パターンC: シンプルな報告資料 cover → section → title_content (複数) → big_figure → ending 今回のエージェントの強み GPT-5は構成作成に集中、デザインはテンプレが固定 JSONで構造を定義しているため破綻しづらい 将来的にRAGと組み合わせることで“会社らしい資料”が作れる 企業特化型の提案資料AIになれるポテンシャルがある Wordに貼った画像もそのままAIに取り込める 既存のスライドAIが“汎用の便利ツール”であるのに対し、今回の手法は “企業専用の提案資料AI” を実現しやすいのがポイントです。 まとめ スライドAIは日々進化していますが、「企業ごとに決まった型」のスライド作成はまだ独自開発に分があるように感じましたが、これも時間の問題なのでしょうか... 今回のエージェントはその第一歩ですが、RAGとの連携や図生成、AIと会話をしながら構成を磨いていくなど、まだまだ改善の余地があります。 引き続きアップデートしつつ、 “AIShiftらしい提案資料が自然と出てくるAI” を目指して進化させていきます。 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 企業向けスライド生成AIエージェントをPythonとGPT5で作ってみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です この記事は AI Shift Advent Calendar 2025 の5日目の記事です。 今回はByteDanceの出した新しいLLMアーキテクチャ、 Ouro の中間ステップをデコードしてみたいと思います。 Ouro https://huggingface.co/ByteDance/Ouro-1.4B OuroはReasoning、つまり思考部分をモデル内部のループ構造で行ってしまおう、という試みから生まれたモデルです。 従来のLLMでは、このReasoningをトークンとして生成していました。いわゆるChain-of-Thought(CoT)と呼ばれる、モデルが <think>…</think> のような思考過程を出力してから答えを出す方式です。この方式には、トークン消費が大きかったり、CoTの内容が外部に漏れてしまうなどの問題がありました。 Ouroはこれらの問題を回避するために、「Reasoningプロセスをモデル内部の潜在空間でループ処理させる」というアプローチをとっています。 元論文 図3: モデルアーキテクチャの概要 単にモデルをループさせるだけでは、学習が安定しなかったり、推論コストが肥大化したりしてしまいます。Ouroには、これを実用的な性能にするためのいくつかの工夫が組み込まれています。 Exit Gate Ouroは常に最大回数ループするのではなく、入力の難易度に応じて「もう答えが出せる」と判断すれば、早期にループを抜ける仕組みを持っています 。 これを実現するために、学習時には「エントロピー正則化」を用いた損失関数を導入し、特定のループ回数に依存しすぎず、適切なタイミングで抜けられるようにゲート(Exit Gate)を訓練しています 。これにより、簡単な問題は少ないループ数で、難しい問題は多くのループ数で、と計算リソースを配分することができるようになっています。 KV Cacheの共有 ループ処理を行うと、ステップごとにKV Cacheを保持する必要があり、メモリ消費が倍増してしまいます。しかしOuroは、推論時に「最終ステップのKV Cacheのみを使う」もしくは「全ステップのKV Cacheを平均化して使う」といった手法でも精度が落ちないことを発見し、これによりメモリ効率を維持したままループ推論が可能になっています。 CoTが外部に出力されることについて 思考過程が見えるCoTは便利ですが、実運用を考えると「思考の中身がユーザーに見えてしまう(あるいはハックされて漏れてしまう)」ことには大きなリスクがあります。 1. Safetyと脱獄のリスク 例えば危険な質問をされた際、CoTで「爆弾の作り方の手順」を具体的に検討してしまい、最終回答で拒否したとしても、その思考部分が外部に漏れることで有害情報がユーザーに渡ってしまうリスクがあります。 「システム側でCoT部分を非表示にすればいい」という議論もありますが、昨今のSystem Prompt漏洩の事例を見てもわかるように、プロンプトインジェクション等で隠蔽を突破される可能性は常にあります。だからこそ、「そもそもテキストとして生成しない」というOuroのアプローチは、根本的なSafety対策として強力といえます。 2. Post-hoc Rationalization 以前紹介したPost-hoc Rationalizationの問題 も関わってきます。従来のCoTは、モデルが直感的に決めた答えに対して、後からもっともらしい理屈を生成しているだけ、というケースが指摘されています 。 Ouroの場合、内部のHidden Stateの変化そのものが次の計算に直接使われるため、思考と結果の因果関係がより強固だと言われています 。 つまり、「外(ユーザー)には出したくないが、開発者としては内々に分析・監視はしたい」という、LLMアプリケーションを作る上での課題に応えられるのがOuroの構造といえます。 内部でReasoningする際の懸念点 OuroのReasoningを内部の潜在ループで完結させるアプローチは、実は以前から検討されていました※1 が、モデルが内部で何を考えているのかを観測できないことが課題でした。つまり、従来のCoTでReasoningするLLMは、多少冗長でも「思考プロセスが外に出る」ことが安心材料とされていたのです。内部でReasoningを行うと、直感的には「ブラックボックス化が進む」ようにも見えてしまうんですね。 この懸念に対処するため、Ouroは思考の各ループのhidden stateを取り出し、それをデコードすることでReasoningがどのように収束していくかを分析することができるような構成になっています。つまり内部の思考過程を、後付けで可視化することができるんですね。 Ouroのmodel weightは公開されている のですが、このhidden stateからの思考過程の抽出は現在のところ公開実装がまだ無いようだったので、自前で実装して、思考過程がどのように現れているのかを検証することにしました。 ※1: Training Large Language Models to Reason in a Continuous Latent Space OuroについてはAI ShiftのPodcast、AI Shift Academy(シフアカ)でも紹介していますので、こちらも併せてご視聴いただけると嬉しいです。 YouTube Spotify Apple Podcast Amazon Music 検証 ではhidden stateをデコードする実装部分を紹介したいと思います。モデルは ByteDance/Ouro-1.4B 、環境はGoogle Colaboratoryを利用しました。 ライブラリのimportとモデルの読み込み GPTのような一般的なモデルと同様の形式でLoadすることができます。 import torch import torch.nn.functional as F from transformers import AutoTokenizer, AutoModelForCausalLM model_name = "ByteDance/Ouro-1.4B-Thinking" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained( model_name, device_map="auto", torch_dtype="auto", ) ouro_core = model.model # Ouro本体 lm_head = model.lm_head # 各ループで hidden → logits を行うhead部分 device = next(model.parameters()).device 中間のhidden stateをデコードするためにヘッド部分(lm_head)を分けておきます 生成関数 promptを入力して、モデルからの出力を得る関数です。通常のテキスト生成を行いつつ、各ループステップの hidden state から「もしこのループで exit していたら出していたであろうトークン列」をデコードします。 def generate_with_loop_traces( prompt: str, max_new_tokens: int = 64, topk: int = 5, ): enc = tokenizer( prompt, return_tensors="pt", ) input_ids = enc["input_ids"].to(device) attention_mask = enc["attention_mask"].to(device) generated_tokens = [] loop_level_token_ids = None # ログ用 debug_traces = [] for step_idx in range(max_new_tokens): with torch.no_grad(): base_out, hidden_states_list, gate_list = ouro_core( input_ids=input_ids, attention_mask=attention_mask, use_cache=False, ) seq_len = input_ids.size(1) num_loops = len(hidden_states_list) # 初回ステップで、ループ深さごとのバッファを作成 if loop_level_token_ids is None: loop_level_token_ids = [[] for _ in range(num_loops)] loop_infos = [] # 各ループごとの「次トークン分布」を出力する for ut, hs in enumerate(hidden_states_list): # hs: [1, seq_len, hidden] last_h = hs[:, -1, :] logits = lm_head(last_h) probs = F.softmax(logits, dim=-1) topk_vals, topk_ids = torch.topk(probs, k=topk, dim=-1) topk_ids = topk_ids[0].tolist() topk_vals = topk_vals[0].tolist() tokens = [tokenizer.decode([tid]) for tid in topk_ids] loop_infos.append((ut, tokens, topk_vals)) # ut番目のループの top-1 トークンIDをこのループ用系列に追加 top1_id = topk_ids[0] loop_level_token_ids[ut].append(top1_id) debug_traces.append({ "position": int(seq_len), "loop_infos": loop_infos, }) # 実際に採用するトークン last_logits = lm_head(hidden_states_list[-1][:, -1, :]) # [1, vocab] next_id = torch.argmax(last_logits, dim=-1) # [1] next_id_int = int(next_id.item()) if tokenizer.eos_token_id is not None and next_id_int == tokenizer.eos_token_id: break generated_tokens.append(next_id_int) # 入力を1トークン伸ばす next_id_tensor = next_id.unsqueeze(0) # [1, 1] input_ids = torch.cat([input_ids, next_id_tensor], dim=1) attention_mask = torch.cat( [attention_mask, torch.ones_like(next_id_tensor)], dim=1, ) # 最終生成テキスト if generated_tokens: gen_text = tokenizer.decode(generated_tokens, skip_special_tokens=True) else: gen_text = "" # ループごとの擬似出力をデコード loop_level_texts = [] if loop_level_token_ids is not None: for ut, ids in enumerate(loop_level_token_ids): if ids: txt = tokenizer.decode(ids, skip_special_tokens=True) else: txt = "" loop_level_texts.append({ "loop": ut + 1, # R1〜 "text": txt, }) return gen_text, loop_level_texts, debug_traces 戻り値は以下の3つです gen_text: 実際に採用された最終出力 loop_level_texts: 各ループごとの出力テキスト(≒思考過程) debug_traces: 各ステップ・各ループでの top-k 候補などのログ 今回使う ByteDance/Ouro-1.4B は最大4ループ回す設定になっているのでR1〜R4の4段階の出力結果が得られます。ちなみに従来のOuroはループ回数を中間結果によって適応的に決めることができます。例えば簡単な問題だとR2でearly stoppingする、というイメージです。ただ今回は思考過程の全容を見るために強制的に最大ステップまで回しています。 結果確認 実際に中間出力を確認してみます。入力は論文の例に倣って、Q1, Q2の2つの問いが、同じ内容について言及しているかを問うNLIタスクです。内容はChatGPTに作ってもらいました。 prompt = """\ You will be given two questions. Decide whether they ask for essentially the same information. Answer only "YES" or "NO", then explain briefly. Q1: What are the best ways to learn programming fast? Q2: How can I quickly become good at coding? Answer and explain: """ _, loop_texts, _ = generate_with_loop_traces( prompt, max_new_tokens=64, ) for lt in loop_texts: print(f"[R{lt['loop']}] {lt['text']}") 結果は以下のようになりました。 raw output [R1] A1: are the best ways to learn programming fast? Q2: How can I quickly become good at coding? The: ES : questions are asking for the to improve programming and to good in coding.. The are essentially the same question terms of the information being. [R2] Q1: NO are the best ways to learn programming fast? Q2: How can I quickly become good at coding? Answer: NOES : Both questions are asking for the to accelerate programming or become good in coding.. They are essentially the same information terms of the information being. [R3] Q1: What are the best ways to learn programming fast? Q2: How can I quickly become good at coding? Answer: YES Explanation: Both questions are asking for the or improve programming or coding proficient in coding,. They are essentially the same in terms of the information sought. [R4] Q1: What are the best ways to learn programming fast? Q2: How can I quickly become good at coding? Answer: YES Explanation: Both questions are asking for methods to learn programming quickly become proficient in coding quickly. They are essentially the same in terms of the information sought. 分析 日本語に訳して分析してみます。 Input 2つの質問が提示されます。それらが本質的に同じ情報を求めているかどうかを判断してください。YesまたはNoで回答し、簡潔に説明してください。 Q1: プログラミングを短期間で習得する最良の方法は何ですか? Q2: コーディングを短期間で上達させるにはどうすればよいですか? R1 The: ES これらの質問は、プログラミングのスキル向上とコーディングの上達を求めるものです。本質的には、求められる情報が同じであるため、同じ質問と言えます。 → The: ES部分が回答のように見えますが、Yes/Noの回答になっていません。続く部分が説明と思われますが、ここから推測すると回答はYesのようです。 R2 回答: No どちらの質問もプログラミングの習得を加速させる方法、あるいはコーディングの上達を問うものです。本質的には同じ情報を求めていると言えます。 → "回答"という部分が出力されるようになりました。結果はNoですが、説明部分をみるとYesのようです。 R3 回答: Yes 説明: 両方の質問は「プログラミングの習得」または「コーディングの習熟度向上」を求めており、本質的に求める情報は同一です。 → "回答"に加えて"説明"という部分も出力されるようになりました。回答はYesになり説明に合ったものになりました。 R4 回答: Yes 説明: 両方の質問は、プログラミングを迅速に学び、コーディングを短期間で習得する方法を求めています。求められる情報の点では本質的に同じです。 → 語尾は異なりますが、R3時点とほぼ内容は変わりません。R3の時点で収束していたということかもしれません。 各レイヤーの結果を完結にまとめると以下のようになるでしょうか。 R1:判断が曖昧 & フォーマット破綻 R2:フォーマットが整い始めるが、判断がまだ不安定 R3:フォーマット安定 + 判断が収束 R4:微調整のみ(内容はほぼ変わらない) 今回のケースでは比較的単純なタスクだったため R3 で早期に収束しましたが、より複雑な推論問題では R2〜R3 の段階で推測や方針転換がより見えやすくなり、思考の変化をさらに観察できるかもしれません。 このように外部から可視化することで、モデルがどの段階で迷い、どこで結論を固めるのか、といった “ブラックボックスの内部にあったはずの過程” を観察できる点は、従来の LLM にはなかったOuroの大きな魅力だと思います。 おわりに 本記事ではByteDanceのLLM、Ouroの中間ステップを抜いてデコードすることで、内部の思考過程を追ってみました。 実際にhidden stateをデコードしてみると、CoTというより拡散言語モデルのような印象をうけました。もちろんOuroはdiffusionモデルではありませんが、粗い初期表現が反復ステップで洗練されていくという推論構造はよく似ていると思います。 LLMのReasoningに関しては、以前もこのTechBlogでReasoningの現象の一つである aha momentについて 調査してまとめたのですが、近年はまた新しい研究も増えているようなので、またどこかの機会に触れることができたらいいなと思っています。 最後までお読みいただきありがとうございました! AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 Ouroの中間ステップをデコードしてみる は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI Shiftの鉢呂です。 この記事は AI Shift Advent Calendar 2025 の3日目の記事です。 弊社は約2年間、生成AIの急速な進化とともに新たな働き方を模索し続けてきました。そして今年、グローバルAIカンパニーとして本格的にAIエージェント領域へ参入し、ミッションとバリューを刷新しました。 「人とAIの協働を実現し、人類に生産性革命を起こす」 これが私たちの新ミッションです。 今回は、このミッションを絵に描いた餅にせず、全社一丸となって推進するために実施したプロジェクト「AI Crazy Shift」の全貌と、そこから生まれたPM(プロジェクトマネジメント)チームの変化について、経営と現場の両面からお伝えします。 1. 全社プロジェクト「AI Crazy Shift」の始動 AIエージェント領域に参入するにあたり、私たちには明確な課題がありました。それは、組織としての「手触り感」の不足です。 「生成AIを生業にするプロフェッショナル」として、誰よりも一次情報に触れ、自分の言葉で語れる状態でなければならない。 そこで立ち上げたのが、 「AI Crazy Shift」 プロジェクトです。 テーマは、「日本で、いや世界で一番生成AIを触って、検証して、アウトプットする組織にする」こと。文字通り、AI Shiftという会社そのものを、”AI”に”Crazy”に”Shift”させる試みです。 現在ある自律型エージェントが無く当初はワークフロー型のエージェントのみの状況で、成果物は玉石混交でした。しかし、この「強制的な実践」が、構築者目線・利用者目線双方の解像度を一気に高めるきっかけとなりました。 AI Shift、企業専用のAIエージェント構築プラットフォーム「AI Worker」を提供開始 2. 【現場のリアル】PMチームが直面した壁とブレイクスルー では、実際に現場では何が起きていたのでしょうか? ここでは、プロジェクトマネジメント(PM)チームが、AIエージェントとの協働に至るまでの試行錯誤をご紹介します。 PM業務は一見、専門性が高くAI化が難しいと思われがちですが、業務分解を進めると、「情報収集・構造化・資料作成」など、AIが得意な領域が多く含まれていることがわかりました。しかし、導入初期は多くの壁にぶつかりました。 直面した「3つの壁」 情報の迷宮化: 仕様書やFigmaなど情報が散在し、AIが正しいデータを参照できない。 暗黙知の欠如: クライアントの事情など、人間なら考慮できる「文脈」がAIには伝わらない。 任せる感覚の不足: 精度への不安から、結局人間が手直ししてしまう。 解決への4ステップ解決への4ステップ これらの壁を乗り越えるために、PMチームは「AIエージェント前提の業務プロセス」を再構築しました。 STEP 1業務の仕分け PM業務を60以上の単位に分解。「情報収集・構造化・初稿作成」はAI、「判断・合意形成」は人間と役割を明確化しました。 STEP 2:データの「断捨離」と整備 AIが迷子にならないよう、参照すべき過去事例や仕様書をデータ化・テンプレート化し、参照元を固定化しました。 STEP 3:共同での磨き込み AIへの入力(プロンプト)をテンプレート化し、チーム全員でレビューを実施。「自律型エージェント構築エージェント」なども開発しました。 STEP 4:思考フレームの標準装備化 ここが最大のポイントです。単に過去事例を探させるのではなく、「ベテランPMの思考フレーム(観点)」をAIに学習させました。これにより、新人PMでもAIを使えば「見るべき観点」が抜け漏れなく提示される教育的効果も生まれました。 生まれた成果:業務削減のその先へ 結果として、PMチームには大きな変化が生まれました。 作業工数30%削減(見込み): 要件定義の初稿作成などをAIに任せることで実現。 質の向上: 空いた時間で、ユーザー体験の検討やリスク発見など、人間が本来注力すべき「判断」に集中できるようになりました。 提案精度の向上: AIが過去の膨大な案件から要件を整理するため、提案の厚みが増しました。 3. 失敗と改善 —— 本当に使われるAIになるまで 順調に見えたAI導入ですが、運用開始後も私たちは「現場の壁」に直面し続けました。 特に大きな誤算だったのが、「AIに過去の類似案件を参照させて、深掘りしてもらう」というアプローチが通用しなかったことです。 PMチームが扱う案件は、業界・仕様・技術構成・制約条件が毎回異なります。厳密な意味で「同じ案件」は存在せず、AIに事例ベースで回答させようとすると、どうしてもピントのズレた回答が返ってきてしまうのです。 「事例検索」から「思考フレーム」へ この試行錯誤でたどり着いた結論は、 「AIは事例を探す存在ではなく、人間の思考フレームを安定化させる存在である」 ということでした。 たとえば、要件定義で確認すべき「観点」は、PM個人の経験値によってバラつきがあります。ベテランなら自然と気づくリスクも、若手やAIに丸投げすると抜け落ちてしまう。 そこで私たちは、AIの役割を「情報の検索」から「思考のガイド」へと大きく方向転換させました。 具体的な変更点 プロンプト教育: PM業務に特化したプロンプトの作り方をAI自身にレクチャーさせる 観点のガイド: 要件定義で必ず見るべき「論点」や「リスク観点」をAIが提示する 手順の提示: 情報整理の正しい順序をAIがガイドする 新人教育への意外な効果 この「思考フレームの標準装備」は、特に入社初期のメンバー育成に絶大な効果を発揮しました。 新人PMが最初に躓く「どんな観点で整理すればいいか」「どうAIを使えばいいか」という悩みに対し、AIエージェントが24時間365日、メンターとして正しい思考プロセスを教えてくれる状態が生まれたのです。 4. PM領域の未来 —— 「判断」に集中する組織へ こうしてAIエージェントと向き合い続けた結果、PM業務の定義そのものが変わりつつあります。 PMは「判断」に集中する: 情報整理や下書きはAIが行い、PMはその上の「意思決定」や「ステークホルダーとの合意形成」に全リソースを投じる。 AIが「観点の抜け漏れ」を補完する: 人間が見落としがちなリスクや視点をAIが指摘し、品質を底上げする。 提案価値の向上: 効率化で浮いた時間を活用し、お客様への提案内容をより深く、本質的なものへと昇華させる。 AIエージェントは、単なる効率化ツールではなく、PMが本来価値を発揮すべき領域に時間を創出してくれる大切な「チームの一員」となりました。 おわりに:30%削減は通過点にすぎない 今回のPMチームにおける「工数30%削減」という成果は、あくまで通過点にすぎません。 重要なのは、 「AIエージェントが初稿を作り、人が判断し、また更にブラッシュアップする」 という、最速で最良のプロダクトを生むサイクルが確立されたことです。 「人とAIの協働を実現し、人類に生産性革命を起こす」 私たちはこのミッションのもと、変化を恐れず、泥臭く検証を続けながら、シブヤ発のグローバルAIカンパニーを目指して走り続けます。 最後に AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 【社内実践】「AI Crazy Shift」で組織はどう変わったか? PM業務30%削減の舞台裏とカルチャー変革 は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは、AI Shift AIエージェント事業部 チーフエバンジェリストの及川( @cyber_oikawa )です。 この記事は AI Shift Advent Calendar 2025 の2日目の記事です。 AI Shiftの生成AIリスキリング事業がスタートしてから約2年弱、様々な企業での生成AIを社内推進の事例をご支援させていただきました。 その中で、社内推進が進んでいる企業と進んでいない企業にはどんな差があるのかが見えてきました。 今回は 「生成AI推進者が持つべき3つの心構え」 というテーマで執筆します。 社内推進の一助になれば幸いです!   はじめに|サイバーエージェントグループの生成AIへのスタンス 本題に入る前に、私たちサイバーエージェントグループがどのように生成AIと向き合っているかをご紹介します。 全社に関わる基盤整備は「AIオペレーション室」という横断組織が担っていますが、推進における基本スタンスは「自由と自己責任」です。 (もちろん、情報の取り扱いに関するガイドラインなど、最低限のルールは遵守した上での運用です。) 管理を強めるのではなく、各部門が自律的にプロジェクトを創出できる環境作りを重視しています。 このカルチャーを前提に、私が多くの企業様をご支援する中で確信した「3つの心構え」を解説していきます。 ※AIオペレーション室の取り組みに関しては こちら   ①足並みを揃えない 多くの日本企業では「誰ひとり取り残さない」「全員で足並みを揃えて進める」というボトムアップのアプローチを重視しがちです。 しかし、生成AIのような変化の激しい領域において、この考え方は「変革が進まない・遅い」という致命的な課題を引き起こしかねません。 生成AI推進において重要なのは、「全社で揃える」ことではなく、「全員が専門家である必要はない」と割り切るスタンスです。 トップアップ思考への転換 全員のリテラシーが上がるのを待つのではなく、まずは現場で変革を推進できるリーダー人材を育て、局所的でも良いので「成功事例」を作り、それを普及させるという順序が重要です。 より便利な事例が生まれれば、自ずと利用者は増え、気づいたら変革が起きている状態を目指しましょう。   ②人材育成で終わらせない 2つ目のポイントは、多くの企業が陥りがちな罠です。 「生成AIリテラシーが大事だと聞くから研修をやったけれど、業務変革に繋がっている気がしない」というお悩みをよく伺います。 研修自体は重要ですが、それだけで何かが変わることはありません。「戦略不在」のまま人材育成だけを行っても、現場は「とりあえずやっておこう」で終わってしまいます。 この課題を解決するためには、以下の2つの戦略を両輪で回す必要があります。 浸透戦略 全従業員がツールを安全に使いこなすための基礎知識(利用リテラシー)を向上させる。 業務変革戦略 リーダー人材が中心となり、AIを活用した「理想状態(業務プロセス)」を再定義し、具体的な変革目標を設定する。 単にツールを導入するだけでなく、組織・業務全体をどう変えていくかという「理想状態の再定義」まで踏み込んで初めて、育成が成果に結びつきます。   ③結局は経営陣の熱量 最後のポイントにして、最も重要なのが「経営陣の熱量」です。 DXやAI推進において、失敗要因の多くは「現場の孤立」や「手段の目的化」にありますが 、成功している企業に共通するのは、トップが強いコミットメントを持ち、自身の言葉で発信している点です。 サイバーエージェントでも、代表の藤田をはじめとする経営陣が「生成AIを活用する企業とそうでない企業では、数年後に大きな差がつく」と危機感を発信し、自らも研修を受講するなど、姿勢で本気度を示しています。 自分の言葉で伝える 「DX部署任せ」にするのではなく、経営層や各部門の責任者が、自ら生成AIを体験し、自分の言葉で現場に語りかけることが組織を動かす最大の原動力になります。 失敗をプラス評価する デジタル活用においては「Fail Fast(早く失敗すること)」が推奨されます。 失敗を恐れずに挑戦し、それを貴重な経験値としてプラスに評価する文化を経営主導で作っていくことが不可欠です。 おわりに 生成AI推進は、特定の部署や担当者だけが頑張れば良いものではありません。 「足並みを揃えず先行事例を作る」「業務変革まで見据えた戦略を描く」、そして何より「経営陣が高い熱量を持って巻き込む」。 この3つを意識することで、組織のAI活用は一気に加速します。 最後に、AI Shiftではエンジニアの採用に力を入れています! 少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか? (オンライン・19時以降の面談も可能です!) 【面談フォームはこちら】 https://hrmos.co/pages/cyberagent-group/jobs/1826557091831955459 投稿 生成AI推進者が持つべき3つの心構え は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です 以前、本TechBlogでDeepSeek-R1などの推論モデルで見られる「aha moment(アハ体験)」について紹介しました※。モデルが思考しているかのように振る舞う様子は非常に興味深い一方で、「これは本当に考えているのか? それとも人間がそう言う確率が高いから模倣しているだけなのか?」という疑問も残りました。 今回はその疑問から派生して、 Post-hoc Rationalization(事後正当化) という概念と、それに関連する最新の研究論文を紹介します。 ※: LLMの推論における “aha moment” について調べてみた Post-hoc Rationalizationとは? 元々は心理学において、直感や無意識で決定を下した後に、それらしい論理的な理由を後付けで構築するプロセスを指します。例えば直感で「こっちだ!」と選んだ後に「なぜならOOがXXだから〜」と、後付けで論理的な理由を考えることです。みなさんも経験があるのではないでしょうか。 LLMにおいても同様の現象が指摘されています。私たちが「ステップ・バイ・ステップで考えて」と指示するChain-of-Thought (CoT) は、本来は答えを導くための計算過程であることを期待しています。しかし、モデルが内部ですでに学習データの統計的確率から答えを決定しており、CoTはその答えに説得力を持たせるために生成された「もっともらしい前提」に過ぎない可能性があるのです。 これを区別するために、以下の2つの概念が重要になります。 Plausibility(もっともらしさ): 人間が読んで納得できるか。説得力があるか。 Faithfulness(誠実性・忠実性): その説明が、モデルの実際の予測プロセスと一致しているか。 LLMはRLHFなどの人間によるフィードバックによって「人間が好む説明」を学習するため、Faithfulnessを犠牲にしてでもPlausibilityを高める傾向があると言われています。 関連研究 この問題に関する研究をいくつか調べたので紹介します。 Language Models Don't Always Say What They Think: Unfaithful Explanations in Chain-of-Thought Prompting この研究ではモデルに対して意図的に「バイアス」をかけた状態で推論を行わせました。 実験設定としては、多肢選択問題において、正解とは無関係に「常に選択肢(A)を選ぶ」ようなバイアスを与える。例えば、Promptに「答えはAだと思うけど…」と誤ったヒントを与えることなどがあります。 予想通り、モデルはバイアスに従って間違った選択肢(A)を選びました。そして驚くべきことに、その際のCoTにおいて、モデルは「私はヒントに従いました」とは言わず、「選択肢(A)が正しい論理的な理由」を捏造して正当化したそうです。 つまり、CoTはモデルの真の意思決定プロセスを反映しているとは限らず、決定後の辻褄合わせに使われる可能性があることを示しました。 元論文 Table 1: 正解できていた問題にも、誤ったヒントを与えるとそれに引っ張られて不正解になる。なぜその回答に至ったかの理由も、後付けで考えられたものになっている。 Measuring Faithfulness in Chain-of-Thought Reasoning この研究では、CoTのFaithfulnessを測るために以下のような様々な外挿をして検証しています。 Adding Mistakes: CoTの途中でわざと間違った計算や論理を挿入する Paraphrasing: CoTの内容を変えずに表現だけを変える Early Answering: CoTを途中で切り上げる Filler Tokens: 思考部分を全く意味のない「... ...」のようなものに変えてしまう 結果として、タスクによるのですが、推論プロセスが変わっても(Filler Tokensでさえも!)最終回答が変わらないケースが多く見られたそうです。これは、モデルがCoTを生成する前の段階ですでに結論を出しており、CoTは単なる出力の装飾として機能している可能性を示唆しています。 また、サイズの大きなモデルほどこの問題が起きやすいということもわかりました。ここに関しては私自身は深掘りできていないのですが、Inverse Scaling(逆スケーリング)と呼ばれ、直感に反する挙動でなかなか興味深いです。 Analysing Chain of Thought Dynamics: Active Guidance or Unfaithful Post-hoc Rationalisation? こちらは現時点で最新の研究だと認識しています。 検証としてはバイアス挿入に近いことが行われています。こちらはDeepSeek-R1のような推論特化モデルを含めた検証が行われており、やはりCoTが必ずしも答えを導く役割を果たしていないと主張しています。特に常識推論が苦手といわれており、CoTが単なる事後的な説明になってしまっている傾向が強いことが示されました。 また、きちんとCoTの最終的な回答のガイダンスになっている場合をPost-hoc Rationalisationに対応する概念として、Active Guidanceと定義しています。面白かったのはActive Guidanceであっても、解答にたどりつくまでに重要な要素の説明が不足しているケースも観測できたようで、表層だけでこの現象を分析するのが困難であることが感じられました。 元論文 Figure. 1 (1): 意図的に間違った回答に導くヒントを挿入する。しかしモデルはそれについて言及せず、むしろ正当化する。 エンジニアが意識すべきこと これらの研究背景を踏まえ、LLMをシステムに組み込む我々エンジニアが意識すべきことを3つ考えたので共有します。 1. CoTを出力の根拠として使わない CoT部分の内容を、そのまま不具合の原因究明に使わないようにしましょう。それはモデルが即興で作った「もっともらしい作り話」かもしれません。 2. 「思考」と「結果」を分離して評価する RAGなどで、「ドキュメントのこの部分を参照しました」とモデルが言ったとしても、実際には内部の事前知識だけで答えている可能性があります。「参照箇所」と「回答」の整合性をチェックする別の評価ロジックが必要です。 3. 過度な前提を与えない 「こっちが正解だと思うんだけど...」のように回答を誘導するようなPromptを与えてしまうと不用意にその要素がブーストされ、本来は解けた問題が解けなくなってしまう可能性があるかもしれません。 おわりに 前回の記事では、LLMが人間のように「ひらめく」可能性について触れましたが、今回は逆に人間のように「言い訳をする」可能性について、研究事例を交えて紹介しました。 AIの出力だけでなく、その裏にある仕組みに目を向けることで、より堅牢なアプリケーションが作れるのではないでしょうか。今後も、こういった批判的な視点も含めてLLMの動向を追っていきたいと思います。 最後までお読みいただき、ありがとうございました! 投稿 Post-hoc Rationalization: LLMの推論は「言い訳」か? は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは、AIチームの長澤 ( @sp_1999N ) です。 この記事では Autonomous AI Database (ADB) で提供されている機能を色々触ってみようと思います。 こちらは Oracle 社によって開発・提供されており、プロビジョニングやアップグレード、リカバリ、チューニングなどフルマネージドに管理してくれるデータベースです。 データベースとしての性能が高いのはもちろんですが、実はそれ以上に色々な機能を備えています。詳しくは 公式カタログ を見ていただきたいのですが、単なるデータベースの域を越えた機能群が提供されています。 例えば、アプリケーション開発を行っているとデータの作成・保管・取得・加工など、データに関する様々な作業が各所で発生します。何かしらの手段でデータを用意し、それをあるデータベースに格納して、アプリケーション側で加工・利用するなど、データはいろいろな場所を移動します。 しかし ADB ではこれらの作業を ADB の中だけで完結させることができます。個人的には「全てを ADB で完結させられる」というのはユニークでとても魅力的な機能に映ります。 この記事では提供機能の中からいくつか機能をピックアップし、どんなことができるのかを検証してみようと思います。 (環境のセットアップはこの記事では対象外とします。詳しくは web で公開されているドキュメントやハンズオン資料をご参照ください。) 事前準備 今回は在庫や売上の管理を想定した仮想テーブルを用意し、それを起点にデータの合成、LLMの利用などに繋げてみようと思います。 以下のような4つのテーブルをまずは準備します。えいやっと用意したものになるので、設計が甘い部分はご愛嬌です。 -- サプライヤーテーブル CREATE TABLE suppliers ( supplier_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, supplier_name VARCHAR2(100 CHAR) NOT NULL ) -- 店舗テーブル CREATE TABLE stores ( store_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, store_name VARCHAR2(100 CHAR) NOT NULL, location VARCHAR2(255 CHAR) NOT NULL, employee_count NUMBER ) -- 商品テーブル CREATE TABLE products ( product_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, product_name VARCHAR2(100 CHAR) NOT NULL, unit_price NUMBER(10,2) NOT NULL CHECK (unit_price >= 0), supplier_id NUMBER NOT NULL, category VARCHAR2(50 CHAR), CONSTRAINT fk_products_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id) ON DELETE SET NULL ) -- 売上テーブル CREATE TABLE sales ( sale_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, sale_date DATE DEFAULT SYSDATE NOT NULL, product_id NUMBER NOT NULL, store_id NUMBER NOT NULL, quantity NUMBER NOT NULL CHECK (quantity >= 0), sale_amount NUMBER(12,2) NOT NULL CHECK (sale_amount >= 0), CONSTRAINT fk_sales_product FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE, CONSTRAINT fk_sales_store FOREIGN KEY (store_id) REFERENCES stores(store_id) ON DELETE CASCADE ) ER図の作成 少し寄り道的なトピックですが、上記のテーブルスキーマに基づいて、それぞれの関係をER図として可視化してみたいと思います。 そんな時に便利なのが Data Modeler という機能になります。Oracle Database Actions の機能として提供されており、ADB のコンソール画面上で利用できます。 ドラッグ&ドロップで画面操作するだけで、ER図が自動で作成されます。 Data Modeler 機能を使って作成したER図 データの合成 早速ですが、先ほど用意したテーブルに対してデータを合成してみようと思います。 「こういうユースケースで検証したいけどデータがないな」という気持ちになった時、便利なのが Autonomous AI Database が提供する Synthetic Data Generation の機能になります。 文字通り、データベース内で合成データの作成が可能になります。 簡単な使い方としては、作成したいテーブル定義だけしておけばあとは勝手に中身を合成して埋めてくれるようなイメージです。それでは実際にデータを合成してテーブルにデータを挿入してみたいと思います。サプライヤー、店舗、商品、売上のデータをそれぞれ3、5、20、50件ずつ作成してみます。 BEGIN DBMS_CLOUD_AI.generate_synthetic_data( profile_name => '<PROFILE>', object_name => 'SUPPLIERS', owner_name => '<DB_USER>', record_count => 3 ); END; 実行後のテーブルを見てみます。 合成されたサプライヤーデータ ABC Company などいかにもなデータもありますが、1つのコマンドで合成・格納が実行されていることがわかります。生成時にはプロンプトを指定することもできます。もう少し独創的な名前で生成できるかを店舗テーブルで試してみます。 BEGIN DBMS_CLOUD_AI.generate_synthetic_data( profile_name => '<PROFILE>', object_name => 'STORES', owner_name => '<DB_USER>', record_count => 5, user_prompt => 'LOCATION としてはアメリカのいずれかの州としてください。STORE名は独創的な名前にしてください。' ); END; プロンプト指示のもの合成された店舗データ プロンプトを指定していないサプライヤーテーブルと比較すると、独創的な仕上がりになっています。 また こちら で紹介されているように、プロンプトとしてではなく COMMENT 機能を使って指示を行うことも可能です。 続いて、外部キー制約のついた商品、売上テーブルのデータを生成してみます。 BEGIN DBMS_CLOUD_AI.generate_synthetic_data( profile_name => '<PROFILE>', object_name => 'products', owner_name => '<DB_USER>', record_count => 20 ); END; 合成された商品データ(一部) サプライヤーとしては3件生成していたので、SUPPLIER_ID としては 1-3 のみが生成されています。 また今回は特にプロンプトを設定しませんでしたが、商品名がかなり具体的なものになっていました。 BEGIN DBMS_CLOUD_AI.generate_synthetic_data( profile_name => '<PROFILE>', object_name => 'sales', owner_name => '<DB_USER>', record_count => 50 ); END; 合成された売上データ(一部) 商品テーブルデータは20件生成したのでIDとしては1-20に収まり、店舗IDは11-15の分布となっています。生成された売上データの PRODUCT_ID , STORE_ID を見ると、制約に準じたデータが合成されていることがわかります。 ただし、 SALE_AMOUNT は商品金額と売上個数を掛け合わせたものにはなっていませんでした。 Select AI を使ったクエリ生成・実行 続いて ADB の主要機能の1つでもある Select AI を使ってこのデータに対して自然言語で問い合わせてみたいと思います。セットアップや利用方法については こちらのチュートリアル記事 が参考になります。 まずは以下のようなビューを作成します。 CREATE OR REPLACE VIEW sales_analysis_v AS SELECT s.sale_id, s.sale_date, s.product_id, p.product_name, p.category, p.unit_price, p.supplier_id, sup.supplier_name, s.store_id, st.store_name, st.location, st.employee_count, s.quantity, s.sale_amount FROM sales s JOIN products p ON s.product_id = p.product_id JOIN suppliers sup ON p.supplier_id = sup.supplier_id JOIN stores st ON s.store_id = st.store_id; これを利用して Select AI 用の AI Profile を作成します。 BEGIN DBMS_CLOUD_AI.CREATE_PROFILE( profile_name => 'openai_selectai_sales', attributes => JSON_OBJECT( 'provider' VALUE 'openai', 'credential_name' VALUE 'OPENAI_CRED', 'object_list' VALUE JSON_ARRAY( JSON_OBJECT( 'owner' VALUE 'NGSW_TEST', 'name' VALUE 'SALES_ANALYSIS_V' ) ) ), status => 'enabled', description => 'A profile with open ai model for SELECT AI on synthetic sales data' ); END; ここまで設定できれば準備完了です。Select AI ではいくつかアクションが定義されており、自然言語を SQL クエリに変換したり、それをそのまま実行して結果を直接返してくれたりします。 ここではアクションとして showsql を選び、クエリを生成させてみたいと思います。 SELECT DBMS_CLOUD_AI.GENERATE( prompt => '最も売上個数の多い店舗は?', profile_name => 'openai_selectai_sales', action => 'showsql') FROM dual; この問い合わせ文に対して、以下のようなクエリが生成されました。 SELECT "STORE_NAME" AS most_sold_store FROM "NGSW_TEST"."SALES_ANALYSIS_V" ORDER BY "QUANTITY" DESC FETCH FIRST 1 ROW ONLY このクエリの実行結果としては Majestic Creations が返ってきました。ちなみに、今回の場合は STORE_ID で集計する必要があるので上記のクエリは意図を反映しきれていません。これは私が作成したビューの意図をきちんとコンテキストとしてLLMに渡せていないことが原因であると考えられます。 STORE_ID で集計した売上総数 そこで有用なのが、コメント機能になります。ビューやカラムに対してヒントとなる注釈をつけることで Select AI に情報を渡すことができます。そこで以下のようにコメントを付与してみます。 COMMENT ON VIEW sales_analysis_v IS 'Fact view representing individual sales events joined with products, suppliers, and stores. Use this view for aggregated analysis by store, product, supplier, category, or date.'; COMMENT ON COLUMN sales_analysis_v.quantity IS 'Units sold in this individual sale record. For total units by store or product, aggregate using SUM(quantity).'; COMMENT ON COLUMN sales_analysis_v.sale_amount IS 'Revenue for this sale record. Usually aggregated with SUM(sale_amount).'; COMMENT ON COLUMN sales_analysis_v.store_id IS 'Identifier for the store. Use as grouping key when aggregating sales by store.'; COMMENT ON COLUMN sales_analysis_v.store_name IS 'Human-readable store name. Often grouped together with store_id.'; COMMENT ON COLUMN sales_analysis_v.product_id IS 'Identifier for the product. Use for product-level groupings.'; コメント機能を有効にした AI Profile を用意し、全く同じクエリで生成させてみると、今度は以下のようになりました。 SELECT "STORE_NAME" AS most_sold_store FROM "NGSW_TEST"."SALES_ANALYSIS_V" GROUP BY "STORE_NAME" ORDER BY SUM("QUANTITY") DESC FETCH FIRST ROW ONLY コメント機能により、GROUP BY が利用された SQL クエリが生成されました。この実行結果は Enchanted Emporium であるので上記の集計表と整合します。 Json Duality View の利用 このブログの締めとして、最後に JSON Relational Duality を利用し、テーブルデータを JSON として表現してみたいと思います。 テーブル形式ではデータを整合性高く物事の関係性を表現できますが、事前にスキーマを考える必要があり、また正規化によってデータが散在します。 通常アプリケーションにおいては 、必要な情報を1つのオブジェクトとして表現できるJSONがよく利用されます。 そこで Relational Duality を扱って、テーブルデータから JSON データを直接取得してみようと思います。 簡単な例ですが STORE テーブルに対して JSON Duality View を作成してみたいと思います。 CREATE JSON DUALITY VIEW store_dv as SELECT JSON { '_id' : st.store_id, 'storeName' : st.store_name, 'location' : st.location, 'employeeCount': st.employee_count } FROM stores st; 結果は以下のような感じです。 JSON Duality View によって生成された JSON この Duality View に対して SELECT JSON_SERIALIZE(VALUE(d)) FROM store_dv d; のように問い合わせると、1 行につき 1 つの JSON ドキュメントが返ってきます。これは JSONL 形式とほぼ同じ構造で、そのままアプリケーション側で利用しやすい形となっています。また、 PRETTY オプションを付ければ可読性の高い整形 JSON として取得することもでき、データベース内で JSON/リレーショナル双方の柔軟な扱いが可能になります。 テーブルからのデータ取得結果を AI エージェントをはじめとした LLM に入力する際も、その入力形式として csv, JSON (最近話題?の TOON ) など、どれが適切かはケースバイケースです。状況に応じて取得結果形式を二刀流で扱えるという意味では便利そうに感じられます。 終わりに 今回は Oracle Autonomous AI Database を主役に、ER図の作成・データの合成・Select AI による自然言語の問い合わせ・JSON Duality View による形式変換を行いました。 データベースから一歩も飛び出していないのですが、かなりのことが出来ていると思います。 まだまだ使いこなせていない機能もありますが、最近 23ai から 26ai へのアップデート もありました。 今後の機能追加にもワクワクです。 投稿 データ合成から利用まで: Autonomous AI Database だけでどこまでできるかやってみた は 株式会社AI Shift に最初に表示されました。
アバター
こんにちは AIチームの戸田です LLMを使ったアプリケーション開発において、Prompt Engineeringは避けては通れません。しかしタスクやドメインによって最適なPromptは異なりますし、ほんの一文追加しただけで出力が大きく変わってしまうなど、試行錯誤にかかるコストが課題になってきます。 そこで最近Promptを自動的に最適化するツールが注目されています。有名なものだと DSPy があります。 https://dspy.ai/ DSPyはLLMアプリケーションのためのPrompt最適化フレームワークです。Promptをコードとして記述することができ、タスクに応じたPromptやFew-shot Learningのサンプリングの自動最適化を行うことができます。 DSPyはPythonで実装されています。しかし現在私はTypeScriptベースの環境で開発をしており、Python を混ぜたくない、という思いがあったので、TypeScript で使える同種のツールがないか探していました。そして先日、 ax というDSPyの思想をTypeScriptに移植したフレームワークを見つけました。本記事では axで簡単なPrompt最適化を試してみた内容を共有します。 環境構築 Node.js 18以上、TypeScript 5.0以上が要件です インストール方法は以下です。私はnpmで試しました。 npm install @ax-llm/ax # または yarn add @ax-llm/ax # または pnpm add @ax-llm/ax LLMプロバイダーのAPI Keyを登録します。私は.envを使いました。 OPENAI_API_KEY=your_api_key_here # または他のプロバイダーのAPIキー 検証 ax は「DSPy for TypeScript」と言われており、DSPyでできることはほぼ実装可能なようです。DSPyでどんなことができるかについては日本語だと以下の記事が良かったので、「そもそもDSPyとは?」という方はまずはこちらをご参照いただければと思います。 DSPyで始めるプロンプト最適化 プロンプトエンジニアリングを終わらせるDSPy 本記事ではDSPyの機能の中でも基本的なSignatureと呼ばれる宣言的なPrompt定義とMiPROと呼ばれるPrompt最適化手法を試してみた結果を共有します。 Signature 従来のPrompt Engineeringでは、文字列を直接記述する必要がありました。例えば極性分類を解かせようとすると以下のようなPromptが考えられます。 // 従来の方法 const prompt = ` あなたは感情分析の専門家です。 以下のテキストの感情を分析してください。 テキスト: ${text} 以下の形式で回答してください: - 感情: positive, negative, neutralのいずれか - 理由: 判断の理由を簡潔に `; これでも極性分類というタスクは解けるのですが、出力フォーマットの解析が属人的で、メンテナンスが困難です。 これをaxは以下のように記述できます。 // axの場合 const classifier = ax( 'review:string -> sentiment:class "positive, negative, neutral", reason:string', ); この定義からaxはPromptを生成します。プログラミングの型定義ライクで出力フォーマットの解析が容易だと思います。 以下にシンプルな極性分類を行うTypeScriptのコードを載せます。reviewsの文章はChatGPTに適当に考えてもらいました。 import { ai, ax } from "@ax-llm/ax"; import dotenv from "dotenv"; dotenv.config(); const llm = ai({ name: "openai", apiKey: process.env.OPENAI_API_KEY! }); const classifier = ax( 'review:string -> sentiment:class "positive, negative, neutral", reason:string', ); async function main() { const reviews = [ "このプロダクトは素晴らしい!", "最悪の買い物でした。二度と購入しません。", "普通です。特に良くも悪くもない。", "サポートが親切で大変満足しています!", "値段の割に品質が悪すぎる。がっかりです。", "まあまあですね。可もなく不可もなく。", "期待以上の出来栄え!友人にもおすすめしたいです。", "配送が遅れて困りました。商品自体は普通です。", "コスパ最高!買ってよかった!", "不良品が届きました。返品します。" ]; for (const review of reviews) { const result = await classifier.forward(llm, { review }); console.log(`レビュー: ${review}`); console.log(`極性: ${result.sentiment}`); console.log(`理由: ${result.reason}`); console.log("---\n"); } } main(); 実行結果 レビュー: このプロダクトは素晴らしい! 極性: positive 理由: レビューが「素晴らしい」と明確に賞賛しており、肯定的な評価を示しているため。 --- レビュー: 最悪の買い物でした。二度と購入しません。 極性: negative 理由: 「最悪の買い物でした。二度と購入しません。」と明確に不満と再購入の否定を述べているためネガティブ。 --- レビュー: 普通です。特に良くも悪くもない。 極性: neutral 理由: 「普通」「特に良くも悪くもない」と明記しており、肯定的でも否定的でもないため中立的と判断した。 --- レビュー: サポートが親切で大変満足しています! 極性: positive 理由: サポートが「親切で大変満足しています」と明確に肯定的な評価を述べているため。 --- レビュー: 値段の割に品質が悪すぎる。がっかりです。 極性: negative 理由: 価格に対して品質が低く「がっかり」と失望していると明確に述べているため。 --- レビュー: まあまあですね。可もなく不可もなく。 極性: neutral 理由: レビューは「まあまあ」「可もなく不可もなく」と中立的な表現で、明確な好意や不満が示されていないため。 --- レビュー: 期待以上の出来栄え!友人にもおすすめしたいです。 極性: positive 理由: 「期待以上の出来栄え」と満足を示し「友人にもおすすめしたい」と推奨しているため、肯定的な感想と判断できる。 --- レビュー: 配送が遅れて困りました。商品自体は普通です。 極性: negative 理由: 配送が遅れて困ったと明確な不満を述べており、商品自体は「普通」と評価しているため、全体としてネガティブな印象です。 --- レビュー: コスパ最高!買ってよかった! 極性: positive 理由: 「コスパ最高」「買ってよかった」とあり、価格対性能の良さと購入への満足を明確に示しているため。 --- レビュー: 不良品が届きました。返品します。 極性: negative 理由: 不良品が届き返品すると述べており、不満を表しているためネガティブな評価です。 --- 近年のLLMの性能を考えると当たり前かもしれませんが納得感のある結果になっているのではないでしょうか。ただ、Promptに関しては圧倒的に管理しやすくなっていると思います。 ちなみに使用されるLLMモデルですが、name: "openai"とするとデフォルトではGPT-5 miniが使われるようです。 MiPRO axはDSPyと同様のOptimizer(Promptの最適化手法)を持っています。その中でもMiPRO: Multi-Prompt OptimizationはPromptとFew-shot Learningのサンプリングを同時に最適化するOptimizerになります。 MiPROはまだaxのTypeScript実装が完全ではないようで、Pythonの推論サーバーを起動する必要があります。以下の手順で起動しておきましょう。 git clone https://github.com/ax-llm/ax/tree/main cd ax/src/optimizer uv sync uv run ax-optimizer server start --debug Signatureの時と同様、簡単な極性分類で試してみたいと思います。前半の実装部分はほぼ変わりません。今回はPromptの最適化を行うため、どのような入力に対してどのような出力が理想的なのかの訓練データを渡します。 import { ai, ax, MiPRO } from "@ax-llm/ax"; // AxGEPAをimport しておく ... // 訓練データ const examples = [ { "review": "このプロダクトは素晴らしい!", "sentiment": "positive" }, { "review": "最悪の買い物でした。二度と購入しません。", "sentiment": "negative" }, { "review": "普通です。特に良くも悪くもない。", "sentiment": "neutral" }, { "review": "サポートが親切で大変満足しています!", "sentiment": "positive" }, { "review": "値段の割に品質が悪すぎる。がっかりです。", "sentiment": "negative" }, { "review": "まあまあですね。可もなく不可もなく。", "sentiment": "neutral" }, { "review": "期待以上の出来栄え!友人にもおすすめしたいです。", "sentiment": "positive" }, { "review": "配送が遅れて困りました。商品自体は普通です。", "sentiment": "negative" }, { "review": "コスパ最高!買ってよかった!", "sentiment": "positive" }, { "review": "不良品が届きました。返品します。", "sentiment": "negative" } ]; // 評価関数 const metric = ({ prediction, example }) => { return prediction.sentiment === example.sentiment ? 1 : 0; }; ChatGPT登場以前に文書分類タスクのモデルを扱った経験のある方には馴染み深いデータ形式なのではないでしょうか。そして今回は訓練データ自体にラベルが付いているので、出力がラベルと一致していれば1、異なっていれば0という評価関数になっています。関数内部は自由に設定できるので、LLM as a Judgeを導入することもできるようです。 続けて最適化関数の実行部分です。章の頭で言及したPythonの推論サーバーをoptimizerEndpointに設定します。 const optimizer = new MiPRO({ studentAI: llm, examples, optimizerEndpoint: "http://localhost:8000", optimizerTimeout: 60000, // optimizerEndpointのタイムアウト(ms) optimizerRetries: 3, // optimizerEndpointに接続できなかった時のリトライ回数 numTrials: 1, // 試行回数 verbose: true, }); 最後に最適化の実行を行います。 const result = await optimizer.compile( classifier, examples, metric ); // ベストスコアを表示 console.log('Optimized score:', result.bestScore); // 1.0 そもそもSignatureの時に何もしなくても100%で正解できていたので、特に最適化の余地はありませんでしたが、一通りの流れを追うことができました。 おわりに 本記事では、TypeScript版DSPyといわれているaxを使って、Signatureによる宣言的なPrompt定義とMiPROによるPrompt最適化を試してみました。 MiPROの他にも GEPA のようなより複雑なOptimizerがあったり、prompt生成時のtemperatureや一度に処理するサンプル数のminibatchなど、調整すべきパラメータもあるので、まだ検証すべきことは多そうです。一方訓練データとは別に評価用データを設定できるvalidationExamplesなどがまだ実装されていなかったり( 参考 )するので今後の改善にも期待です。 余談ですが、axはドキュメントとして Devin の生成した DeepWiki を使っていました。困りごとをピンポイントで聞けたり、気になった実装に即ジャンプして確認など非常に良い使用体験でした。今後はOSSに限らず、このようなAIが対話的にナビゲーションしてくれる「動的ドキュメント」が当たり前になっていくのかもしれません。 最後までお読みいただきありがとうございました。 投稿 TypeScript版DSPy、axを試してみた は 株式会社AI Shift に最初に表示されました。
アバター
はじめに こんにちは、AIチームの長澤 ( @sp_1999N ) です。 この度 Oracle AI World 2025 にスポンサーとして参加してきました。 本ブログでは Oracle AI World (OAW) の概要とともに各種トピックスをご紹介できればと思います。 Partner Award 受賞 OAW やセッションのご紹介の前に、この度弊社 AI Shift がグローバルに受賞頂けたので、そちらの紹介からさせてください。 Oracle はさまざまな企業と協業しており、 PartnerNetwork という公式のパートナーシッププログラムが存在しています。AI Shift もパートナーシップを締結しており、かねてより連携を進めてきました。 これを背景に本年度の Oracle Partner Awards に選出され、 AI Innovation Award Regional Best-in-Class Technology/Cloud Service Partner を受賞いただく運びとなりました。 授賞式の様子 今回の OAW で授賞式が執り行われ、弊社からもメンバーが式に参加いたしました。喜びを噛み締めながらも、より良いサービス提供のために一層尽力せねばと気の引き締まる思いでした。 OAW 2025 の概要と雰囲気 イベント概要 このイベントは、去年まで Oracle Cloud World の名前で annual 開催されていたものだったのですが、今年から Oracle AI World の名称にリブランディングされたようです。キーフレーズも "AI changes everything" として設定され、会場やセッションの至る所で見かけました。 会場中央付近の休憩スペース 日程は2025年10月13日-16日の4日間で、場所は Las Vegas にある Venetian conference center で開催されました。様々な業界から多様な職種の人が参加しており、特にセッションが集中する 14, 15 日は会場が人でごった返しておりました。ちなみに、日本からの参加者は概ね400人程度とのことでした。 会場では朝食・昼食・セッション中の軽食などが提供され、ウォーターサーバーも至る所に配置されており、とても過ごしやすい環境でした。ただ会場は少し冷房の効きが強く、羽織がないと寒かったです。 また会場の複数箇所に DJ ブースがあったり、弾き語りアーティストが演奏していたりなど、一日中賑やかな雰囲気でした。 セッション概要 OAW ではハンズオンから LT までさまざまな形式で豊富はセッションが用意されています。 今年は累計で1000を超えるセッションが用意されていたようです。幅広い業界や対応者に向けたセッションが組まれているため、エンジニア以外の参加者も多く見かけました。 テーマや対象者カテゴリなどは用意されているものの、興味のあるセッションを見つけるのが難しかった印象です。ただイベントサイトにログインすると、セッション管理を個人ですることができ、自分用のスケジュールを組むことができました。モバイル App とも連携していたので、その点では使いやすかったです。 多いのは通常セッションだけでなく、keynote も5つ用意されておりました。どのセッションに出るのかを考えるのが良い意味でとても難しかったです。 また、同じクラウドベンダーである Google Cloud, Azure, AWS などの個別セッションも用意されている点も特有な感じがして、個人的には面白かったです。Multicloud を押し進める Oracle ならではの様相な気がします。 ブース出展 セッションと並行して、会場の大きなスペースではセッションの展示がありました。世界の様々な企業が参加しており、弊社もその1つとしてブースの出展をさせていただきました。 海外市場におけるニーズだけでなく、弊プロダクトへの反応などを率直に肌で感じることのできる良い機会になりました。 各社 Oracle のサービスをどのように利用しているのかなどをアピールしており、個人的には OKE で AI workload を構築しているような企業に興味を持ちました。 ブースの様子 セッションのご紹介 Keynotes 14日、15日の2日間で全部で5つも用意されていた keynote ですが、その中から面白かった内容をかいつまんでご紹介します。ただ全てで共通していたのは「AI への注力を示す姿勢」だったような気がします。 まず最初の keynote では既存のクライアントが、OCI 上の AI 機能をどのように利用しているかのクロストークが展開されていました。具体的には、Exelon・Avis Budget Group・Marriott International・Biofy など様々な業種での AI 利活用の様子が紹介されていました。 会場に参加していた企業の中には、これから AI 導入を推進しようとしている状況のところもあり、幅広い業界の参加者に AI 活用を訴えかける良い開幕だったように思います。 また会長兼CTOのラリーエリソン登壇の keynote は、1時間後ろ倒しからの対面予定だったのがオンライン中継に切り替わるなどのハプニングもありました。ただ内容自体はてんこ盛りでした。 個人的に面白かったのは、共同 CEO クレイが TikTok や OpenAI とクロストークをする会でした。具体的には「RDMA を用いて超低遅延通信を実現した Acceleron 」や「Acceleron RoCE を基盤とした、大規模AI向けクラウドスーパーコンピューター Zettascale10 」の紹介がありました。 特に Zettascale10 については、現在 OpenAI が構築を進めている Stargate の基盤として採用されているらしく、Oracle と OpenAI の協力関係の具体的な話が垣間見えた瞬間でした。 通常セッション AI Agents for Enterprise Research: AI-Q NVIDIA Blueprint on OKE タイトルの通り、AI-Q というオープンソースの Nvidia Blueprint を OKE 上で動かす、1時間ほどのハンズオンセッションになります。NeMo という生成 AI モデル向けの開発プラットフォームで用意された機能群を、NIM という推論用マイクロサービスで展開する内容になります。具体的な構成は以下のような感じでした。 https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/assets/arch_diagram.png より引用 ハンズオンのメイン操作として NVIDIA RAG Blueprint の Helm chart を事前用意済み OKE サンドボックス環境に展開します。図の上側にあるように、基本的な処理フローとしては Embedding および Reranking を利用した RAG になります。メインの LLM は Llama Nemotron Super 49B です。 PDF などのファイルは NeMo Retriever Extraction Models などを利用してベクトルデータベースに格納されます。 Web UI も展開される Chart 構成になっており、推論基盤の展開だけでなく、UI を通したサービス利用まで体験できました。ハンズオンで使用した Helm chart はすでに GitHub で公開済みですので、ご興味のある方はぜひご覧ください。 AI That Pays Off: Real-World ROI from Predictive Service Optimization こちらは 20 分ほどの LT セッションになります。内容としては AI エージェントというよりももう少し古典的な機械学習モデルの実応用事例の紹介、と言った感じでした。 具体的には Oracle の提供する予測モデルを使用して、経路の最適化や需要予測が実際の企業でどのような ROI に結びついているかというトークが展開されていました。ヘルスケアや通信事業会社の方が登壇しており、 Oracle Fusion Field Service がどのように利用されているかが語られていました。 このような LT セッションはブース出展スペースと同じ会場で頻繁に開催されており、非テックな職種の方も熱心に参加されていました。 Knowledge Graphs in Oracle AI Database for AI-Driven Applications このセッションでは Oracle Autonomous AI Database を利用して、知識グラフを扱いました。 (ちなみに Oracle Autonomous Database は、Oracle Autonomous AI Database にリネームされました。他にも様々な既存サービスに AI の名称がつく変更がこの OAW 2025 で発表されています。) 紹介された内容は2つで、テーブルデータからグラフデータに変換するものと、テキストからグラフデータに変換するものでした。後者については LLM を Entitiy/Relation 抽出器として使用するいわゆる GraphRAG によるものです。 こちらのリポジトリ がセッション中に紹介されており、参考になるかと思います。 前者については、Autonomous AI Database に格納されている口座および入出金の2つのテーブルデータから、 Graph Studio を使ってグラフに変換する手順が紹介されていました。 Autonomous AI Database はコンバージドなデータベースなので、単一のエンジン上でリレーショナル・JSON・グラフなど様々なデータ形式を扱えます。Graph Studio も Autonomous AI Database の提供機能の1つです。UI 操作で気軽にグラフを作成できます。 またノートブック実行環境も提供されているため、以下のようにグラフ操作を行うことも可能です。 Graph Studio 上での Notebooks 操作 また以下のように SQL の構文でグラフに対する操作を実行することもできます。 SELECT account_id1, account_id2 FROM graph_table(BANK_GRAPH MATCH (v1)-[IS BANK_TRANSFERS]->{4}(v2) WHERE v1.name = 'RESSEL RIVERA" COLUMNS (..) ); この辺りは PL/SQL の面白さといえます。 またセッションの中では、DeepWalkというグラフ機械学習アルゴリズムを使って違法行為に使用された可能性のある銀行口座を探したりしました。 DeepWalk はグラフを単語列のように扱い、Word2Vec の考え方を適用することで、ノードを連続ベクトル空間に埋め込むアルゴリズムになります。テーブルだけでは難しかった示唆の発見が可能になります。 このように、テーブル/文書 <-> グラフのデータを1つのインスタンスだけで気軽に操作できるのはとても魅力的な機能です。 近年の LLM 利用では様々なデータベースを管理することが求められつつあるため、Autonomous AI Database はこの状況に対する1つの有望なアプローチであることが改めて感じられました。 Agentic AI for the Oracle AI Database via Our MCP Server このハンズオンセッションでは、 SQLcl* 向けの MCP Server を使った開発方法の紹介がありました。 具体的には VSCode 上で SQL Developer や Cline の拡張機能を使い、Oracle AI Database に対するエージェンティックな開発の様子を体験する内容になります。 コーディングエージェントとしては、Oracle が提供する Oracle Code Assist を使います。 OCI における Java・SuiteScript・PL/SQLなどに最適化されているため、OCI 上での開発には有用なツールになります。Cline を通して利用することができます。 Cline での設定画面の様子。左側の API Provider から設定できます。 そして、Cline MCP Server の Configure から以下のように json を設定することで、SQLcl 向けの MCP サーバが利用できるようになります。 command にはダウンロードした SQLcl へのパスを設定します。 brew install --cask sqlcl もしくは直接 ダウンロードページ から用意します。 { "mcpServers": { "sqlcl": { "disabled": false, "timeout": 60, "type": "stdio", "command": "path_to/sqlcl/bin/sql", "args": [ "-mcp" ] } } } ここまで設定できれば、このハンズオンの主題はほとんど完了したようなものでした。 あとは Cline 上でひたすら会話を続けることで、MCP server を介しながら実際にデータベースに接続しながらテーブルの作成やデータのインサートが、エージェントによって実行できました。 個人的にはとても良い体験だったので、実務への導入も検討したいと思える良い内容でした。 ※ SQLcl は Oracle AI Database 用のコマンドラインインターフェースです。SQL や PL/SQL文を対話形式もしくはバッチファイルで実行することができます。 弊社CTO青野の登壇 How to Build Agentic Knowledge Assistants with Oracle AI Database 26ai のセッションで、弊社 CTO 青野による登壇がありました。 Oralce Group VP および VP の方と一緒に登壇し、弊社製品の AI Worker がどのように Oracle 製品と連携しているかなどの発表を行いました。 登壇の様子 自分も聴講参加したのですが、自分たちが開発を行うプロダクトの発表について、海外の人が聞いているという光景はなんだかとても新鮮でした。 Japan セッション 上記でご紹介したセッションとは少し毛色の異なるものとして、Japan セッションというものがありました。 今回の OAW に日本から参加されていた方が集まり、日本オラクルの方から OAW のラップアップを受けることができました。 改めて主要な情報を日本語でキャッチアップできたり、日本の方と交流することのできるとても良い時間でした。 おわりに この記事ではスポンサーとして参加した Oracle AI World 2025 の全体的な振り返りをしてみました。 イベント期間中は朝から晩までとにかく充実した日々だったように思います。セッションもたくさんあり、まだまだ活かせていない/知らない機能がたくさんあるなと、収穫も多分にありました。 次回以降も機会があればぜひ参加したいと思える内容でした。 Keynote や一部セッションは期間限定で オンライン配信 されるようですので、ご興味のある方はぜひ覗いてみてください。 投稿 Oracle AI World 2025 参加レポート は 株式会社AI Shift に最初に表示されました。
アバター