⽣成AIをコアとするプロダクト開発で使う技術

>100 Views

October 16, 25

スライド概要

DeNA × AI Talks #3
2025-10-14
Tomoki Yoshida『⽣成AIをコアとするプロダクト開発で使う技術』

Web version: https://birdwatcheryt.github.io/ai-talks/
Qiita version: https://qiita.com/birdwatcher/items/3333df5d046876205dc2

profile-image

DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

生成AIをコア技術に持つプロダクト開発の知見を公開 ⽣成AIをコアとするプロダクト開発で使う技術 複数AIと対話できるプロダクトと業務を通して Tomoki Yoshida DeNA IT本部AI・データ戦略統括部 AI技術開発部 AIイノベーショングループ Tomoki Yoshida - DeNA

2.

生成AIをコア技術に持つプロダクト開発の知見を公開 自己紹介 吉田 知貴 Tomoki Yoshida - DeNA 学生時代 機械学習凸最適化の高速化 (KDD2018, KDD2019) 2018年 DeNAサマーインターン 社会人 2020年 DeNA新卒入社 社外案件(組み合わせ最適化) Pococha(CS審査効率化、レコメンド) 新規LLM案件立ち上げ Qiita: @birdwatcher X: @birdwatcherYT 1 / 29

3.

生成AIをコア技術に持つプロダクト開発の知見を公開 今日学べる内容 生成AIをコアとするプロダクトの技術的工夫 マルチモーダル・マルチAI対話の実装工夫 LangChain構造化出力の詳しい理解 応答を高速化させる工夫 プロダクト開発全体の工夫 評価の重要性 人間の必要性 説明にはLangChainのコードが出てきます ※ Tomoki Yoshida - DeNA 2 / 29

4.

生成AIをコア技術に持つプロダクト開発の知見を公開 生成AIをコアとするプロダクトの技術的工夫 複数AIと対話できるプロダクトから学ぶ Tomoki Yoshida - DeNA

5.

生成AIをコア技術に持つプロダクト開発の知見を公開 今日扱う題材 https://github.com/birdwatcherYT/ai-chat 個人開発なのですべて無料枠で動かせます Tomoki Yoshida - DeNA テキスト生成(LLM/VLM): Gemini, Openrouter, Groq, Ollama 画像生成: Gemini 2.0 Flash Preview Image Generation, FastSD CPU 音声合成(TTS):: VOICEVOX, COEIROINK, AivisSpeech 音声認識(ASR): WebKit, Whisper, Vosk, Gemini 画像入力: 画像添付, Webカメラ 3 / 29

6.

生成AIをコア技術に持つプロダクト開発の知見を公開 デモ動画: マルチAIマルチモーダル対話 Tomoki Yoshida - DeNA 4 / 29

7.

生成AIをコア技術に持つプロダクト開発の知見を公開 システム構成 ユーザー⼊⼒ テキスト ⾳声 画像 Tomoki Yoshida - DeNA 話者決定LLM batch ⾳声認識 batch 対話履歴 mini-batch 発話⽣成LLM batch ⾳声合成 streaming 状況説明LLM batch 出⼒ 画像⽣成 base64 5 / 29

8.

生成AIをコア技術に持つプロダクト開発の知見を公開 LLMアーキテクチャ 1. 話者決定LLM 対話履歴から次の話者を判定 構造化出力を使用 2. 発話生成LLM 話者の発話内容を生成 ストリーミング出力対応 3. 状況説明LLM 画像生成用の状況説明文を生成 Tomoki Yoshida - DeNA 6 / 29

9.

生成AIをコア技術に持つプロダクト開発の知見を公開 話者決定LLM - 構造化出力を学ぼう 対話履歴から次に話すべき話者を判定 確実に次の話者を生成させるために構造化出力を使用 from pydantic import BaseModel from typing import Literal class SpeakerSchema(BaseModel): speaker: Literal["User", "Alice", "Bob"] prompt = PromptTemplate.from_template("...(略)...") llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash") chain = prompt | llm.with_structured_output(SpeakerSchema) llmにスキーマを与えている Tomoki Yoshida - DeNA 7 / 29

10.
[beta]
生成AIをコア技術に持つプロダクト開発の知見を公開

動的スキーマによる工夫

連続して同じユーザーが発言しないように話者候補リストから動的にスキーマ作成
SpeakerSchema = type(
"SpeakerSchema", (BaseModel,), {
"__annotations__": {"speaker": Literal[tuple(candidates)]},
"speaker": ...,
}
)

リスト candidates からclassを作っている

Tomoki Yoshida - DeNA

8 / 29

11.

生成AIをコア技術に持つプロダクト開発の知見を公開 LangChainの構造化出力 1. StructuredOutputParser: シンプルjson 2. PydanticOutputParser: 複雑な構造対応 3. with_structured_output: 推奨されている いくつか方法があるけどどれを使うといいのか? Tomoki Yoshida - DeNA 9 / 29

12.
[beta]
生成AIをコア技術に持つプロダクト開発の知見を公開

PydanticOutputParser vs StructuredOutputParser
StructuredOutputParser
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
response_schemas = [
ResponseSchema(name="name", description="名前", type="string"),
ResponseSchema(name="length", description="体長(cm)", type="int"),
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
print(output_parser.get_format_instructions())

The output should be a markdown code snippet formatted in the following schema,
including the leading and trailing "```json" and "```":
```json
{
"name": string // 名前
"length": int // 体長(cm)
}
```

直感的かつシンプルな指示↑
長い指示→
体感StructuredOutputParserの方が失敗しにくい
Tomoki Yoshida - DeNA

PydanticOutputParser
from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser
class Bird(BaseModel):
name: str = Field(description="名前")
length: int = Field(description="体長(cm)")
output_parser = PydanticOutputParser(pydantic_object=Bird)
print(output_parser.get_format_instructions())

The output should be formatted as a JSON instance that conforms to the JSON schema
below.
As an example, for the schema {"properties": {"foo": {"title": "Foo", "description":
"a list of strings", "type": "array", "items": {"type": "string"}}}, "required":
["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The
object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.
Here is the output schema:
```
{"properties": {"name": {"description": "名前", "title": "Name", "type": "string"},
"length": {"description": "体長(cm)", "title": "Length", "type": "integer"}},
"required": ["name", "length"]}
```

10 / 29

13.

生成AIをコア技術に持つプロダクト開発の知見を公開 with_structured_outputの挙動チェック Gemini 2.0 Flash Gemma 3 → PydanticOutputParser → PydanticToolParser API提供元のツールを使おうとするが、無ければPydanticOutputParserが使われる API提供元のツールが使われる場合、内部処理は不明だが、プロンプトにも明示的に格納指示を書いたほうが体感失敗しない Tomoki Yoshida - DeNA 11 / 29

14.

生成AIをコア技術に持つプロダクト開発の知見を公開 構造化出力の信頼性向上 リトライ from langchain_core.exceptions import OutputParserException chain.with_retry( retry_if_exception_type=(OutputParserException,), # 構造化出力失敗した場合 stop_after_attempt=3, # リトライ回数 wait_exponential_jitter=False, # 指数バックオフをOFFに ) 別のLLMへ修正依頼 parser = OutputFixingParser.from_llm( parser=base_parser, llm=ChatGoogleGenerativeAI(model="gemini-2.0-flash") ) Tomoki Yoshida - DeNA 12 / 29

15.

生成AIをコア技術に持つプロダクト開発の知見を公開 発話生成LLM - マルチAI対話の課題 チャット形式のChatPromptTemplateを使うと、 from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import AIMessage, HumanMessage, SystemMessage prompt = ChatPromptTemplate([ SystemMessage(content="システムプロンプト"), MessagesPlaceholder(variable_name="history") # 次のように展開される: # HumanMessage(content="人間の発言"), # AIMessage(content="AI1の発言", name="AI1"), # HumanMessage(content="人間の発言"), # AIMessage(content="AI2の発言", name="AI2"), ]) このあとAI1に発言させようとするとエラーになる。さて、なぜでしょう? Tomoki Yoshida - DeNA 13 / 29

16.

生成AIをコア技術に持つプロダクト開発の知見を公開 エラーの原因と解決方法 原因:AIが連続で発話すると空文字列が返る マルチAI対話: ...→AI1→人間→AI2→AI1 解決: 対話履歴を文字列として与える シンプルな文字列を扱うPromptTemplateを使用 prompt = PromptTemplate.from_template("""次の会話履歴に続く発言を1人分生成してください。 # 会話履歴 {history} {next_speaker}: """) Tomoki Yoshida - DeNA 14 / 29

17.

生成AIをコア技術に持つプロダクト開発の知見を公開 マルチモーダル対応 画像入力も扱いたい! テンプレート 形式 マルチモーダル PromptTemplate 文字列 ChatPromptTemplate System/AI/Humanのリスト形式 マルチAIマルチモーダルならどうする? Tomoki Yoshida - DeNA 15 / 29

18.
[beta]
生成AIをコア技術に持つプロダクト開発の知見を公開

マルチAIマルチモーダル対応
ChatPromptTemplateで、会話履歴を文字
列にして、すべて人間の入力として与える
multimodal_history = [
{"type": "text", "text": "会話履歴"},
{"type": "image_url", "image_url": {"url": "base64文字列"}},
{"type": "text", "text": "続きの会話"},
]
prompt = ChatPromptTemplate.from_messages([
("human", multimodal_history)
])

で与えると変数を認識しないため
("human", リスト) 形式を採用

※ HumanMessage

Tomoki Yoshida - DeNA

16 / 29

19.

生成AIをコア技術に持つプロダクト開発の知見を公開 ローカルLLM(Ollama)サポート ローカルLLMには、指示に従わず、無限にテキストを生成し続けるモデルがある 1話者分の発話を生成してほしいのに無限に次々生成してしまう stopワード( \n )を指定して強制的に停止することで対応 ローカルで動作検証できるとAPI RateLimitを気にする必要がなくデバッグに便利 Tomoki Yoshida - DeNA 17 / 29

20.

生成AIをコア技術に持つプロダクト開発の知見を公開 音声合成 - レイテンシ削減の工夫 - 1. 発話生成LLMでストリーミング出力する chain.invoke() の代わりに chain.stream() を使う 2. 発話生成中に小さい単位で音声合成をリクエスト 区切り文字( 。、?! )で分割 3. 並列バックグラウンド実行 バックグラウンドキューで順序保証しながら管理 コラム 音声合成のソフトVOICEVOX, COEIROINK, AivisSpeechは、GUIを立ち上げるとlocalhostでAPIが立ち上がるた め、Pythonからrequestを投げるだけで、GUIを触ること無く音声合成ができる Tomoki Yoshida - DeNA 18 / 29

21.

生成AIをコア技術に持つプロダクト開発の知見を公開 プロダクト開発全体の工夫 Tomoki Yoshida - DeNA

22.

生成AIをコア技術に持つプロダクト開発の知見を公開 「最初は高性能なモデルでやって、後から安価なモデルに変えればいいや」 「プロンプトは後で洗練させればいいや」 本当にそれでいいですか? Tomoki Yoshida - DeNA 19 / 29

23.

生成AIをコア技術に持つプロダクト開発の知見を公開 評価の重要性 - 本来モデル変更は大変なもの 更 新 評 価 従来の機械学習モデル LLM利用 データ準備→学習→オフライン評価→オンライン評価 モデル名やプロンプトで簡単に変更が可能 というステップを踏む 自然言語の対話体験は評価が難しく、人間 回帰や分類など定量化しやすい による評価が必須 定量評価が難しいため、一度ユーザーに受け入れられた体験を後から変えることは、 従来のモデル変更よりもはるかに難易度が高い。 オンライン評価に頼るなら、ユーザーによる明示フィードバックやABテスト基盤を整 える必要がある。 Tomoki Yoshida - DeNA 20 / 29

24.

生成AIをコア技術に持つプロダクト開発の知見を公開 評価の重要性 - オフライン評価 - リリースしないとわからないオンライン評価だけに頼るわけにはいかないので、 モデル変更やプロンプト変更時にデグレが起きないかを評価する枠組みが必要 LLM as a Judgeやルールベースで各プロダクトに適した評価項目をチェック 比較的、ガードレール指標(何度も同じ発言、文の破綻など)は定めやすい 対話UXは自動判定だけでカバーしきれないため人間によるチェックも必要 → プロンプトは最初から洗練させておき、人間のチェックの手間を減らしたい Tomoki Yoshida - DeNA 21 / 29

25.

生成AIをコア技術に持つプロダクト開発の知見を公開 プロンプトエンジニアリング 基本テクニック Markdown/XML形式で構造化 入出力例を与える(Few-shot) ステップの明示(Chain of Thought) 構造化出力 役割付与(あなたは優秀な〇〇です) 適切な単位でプロンプトを分ける 理由を説明させてから回答させる 否定語の代わりに肯定文(しないで→禁止する) ハルシネーション対策(答えがない場合、無理に回答な禁止) いろいろあるけれど... Tomoki Yoshida - DeNA プロンプトの洗練 重複語彙 / 冗長な表現の削除 重要な指示を書く位置を調整 改行位置を意味のある単位で調整 長い文を箇条書きで整理する うまく動かない時、指示を足すのではな く、一度全体を見直すことが大切 無駄に長いプロンプトは、コストと応答時 間の増加、内容の把握が困難になる 22 / 29

26.

生成AIをコア技術に持つプロダクト開発の知見を公開 コンテキストキャッシュ - コストと速度の最適化へ LLMのプロンプトの先頭が同じなら差分計算で高速化できる技術 具体例 Ollama: 連続した推論は自動でキャッシュ対応 Gemini 2.5系: 暗黙的 / 明示的キャッシュ対応 ポイント prefixを不変にし、可変部分は後方に配置するとキャッシュが効きやすい 特に対話はインクリメンタルな情報増加で、速度が求められるため有効 Tomoki Yoshida - DeNA 23 / 29

27.

生成AIをコア技術に持つプロダクト開発の知見を公開 エピソード: LLMモデル変更の苦悩 当初、コスパの良いGemini 2.0 Flashを利用していたが、2〜6%の頻度で異言語が混入 20回の対話で一度でも異言語が混入する確率は、 変更候補 Gemini 2.5 Flash-Lite Gemini 2.5 Flash (思考オフ) 結果 同価格だが、体感性能が明らかに低下 コストが3倍以上。指示の忠実さ、長文コンテキストの扱いに弱く、体感性能は劣る印 象。同じ発言の繰り返し、古いメッセージへの返信、AI同士の対話で無限ループ。 Gemini 1.5から2.0への移行がアナウンスされたように、今後どんどんモデル更新が行 われる。そのたびにメンテナンスが必要になる → 評価の枠組みがあると楽になる Tomoki Yoshida - DeNA 24 / 29

28.

生成AIをコア技術に持つプロダクト開発の知見を公開 LangChainを使う理由 - 生のAPIを叩かない理由 - 1. 統一インターフェース: モデル切り替えが簡単 デバッグ時はOllama使用 (無料枠APIだとすぐにLimit来るので特に重要) 2. トレース機能: プロンプトやレイテンシ確認 LangSmith(クラウドのみ): 環境変数設定するだけでコード変更不要 LangFuse(ローカル可): invoke の callbacks に設定するだけ 3. テスト用モック FakeListLLM (LLMの応答文字列をモック) FakeMessagesListChatModel (より高度にLLMの応答情報をモック) Tomoki Yoshida - DeNA 25 / 29

29.

生成AIをコア技術に持つプロダクト開発の知見を公開 AI駆動開発について 利用経験: Claude Code, Cline, Cursor, Devin, Copilot, Gemini Canvas, Google AI Studio メリットは言うまでもないので懸念点の話: 人間がチェックしないと… どんどん汚れていく(変な/非推奨/未使用/冗長コード、README汚染) 把握できないコードが増えていく / 重要なコメントが勝手に消される 意図せぬデグレが起きる 判断する力が求められる DatabaseをPublicな状態で作ってくる 他のIAM権限を奪うコードを書いてくる 今後プログラミングできなくなりそう / 個人の能力の見極めが難しくなった Tomoki Yoshida - DeNA 26 / 29

30.

生成AIをコア技術に持つプロダクト開発の知見を公開 開発に便利ツール・サイト GitHub関連 uithub: GitHubをテキスト化 (任意のLLMへ入力しやすい) github.com → uithub.com deepwiki: 対話してリポジトリ理解 github.com → deepwiki.com MCP context7: 最新ドキュメント参照 Tomoki Yoshida - DeNA 27 / 29

31.

生成AIをコア技術に持つプロダクト開発の知見を公開 まとめ 生成AIをコアとするプロダクトの技術的工夫 マルチモーダル・マルチAI対話の実装工夫 → 文字列化してuserとして与える LangChain構造化出力の詳しい理解 → 使い分けたほうが良さそう 応答を高速化させる工夫 → ストリーミング、並列処理、コンテキストキャッシュ プロダクト開発全体の工夫 評価の重要性 → モデル/プロンプト更新時のデグレチェック 人間の必要性 → 性能チェック、セキュリティ、保守運用 他にもプロダクト開発では、レートリミット、コンテキストサイズの超過、プロンプトインジェクション対 策など考慮すべき点が多くある。 28 / 29 Tomoki Yoshida - DeNA

32.

生成AIをコア技術に持つプロダクト開発の知見を公開 ご清聴ありがとうございました GitHub: https://github.com/birdwatcherYT/ai-chat Qiita: https://qiita.com/birdwatcher/items/3333df5d046876205dc2 余談 このスライドはMarkdownでスライドを作るMarpで作りました。QiitaからAIで土台を作 らせ、時間をかけて直しました (ここでも人間チェック) Tomoki Yoshida - DeNA 29 / 29

33.

生成AIをコア技術に持つプロダクト開発の知見を公開 FYI: DeNA 生成AI新規プロダクト開発 興味のある方はこちらから: 新卒採用ページ 中途採用ページ DeNA AIの問い合わせ窓口 Tomoki Yoshida - DeNA

34.

生成AIをコア技術に持つプロダクト開発の知見を公開 Appendix Tomoki Yoshida - DeNA

35.

生成AIをコア技術に持つプロダクト開発の知見を公開 音声認識(ASR)- 実装パターン Web Speech APIでストリーミングASR Client ASR Server Client Server VAD [ で⾳声を送信 Web Speech API 検出された⾳声区間のみを送信 認識結果のテキスト ⾳声認識ライブラリで処理 認識結果のテキストを送信 Client ASR Server ブラウザ依存だが楽で高速 ASR Server: Googleなど Tomoki Yoshida - DeNA Server で⾳声区間を検出 ストリーミング⾳声認識] loop サーバーでバッチASR 認識結果のテキストを返信 Server Client Server 任意のASR: Whisper/Vosk/Gemini バッチ処理なので遅い

36.

生成AIをコア技術に持つプロダクト開発の知見を公開 音声認識 - 発話区間検知(VAD)周辺の工夫 - バッチでサーバーに音声を投げるためには、発話開始/終了を検知する必要がある 工夫 発話開始前からバッファリング (音声が途切れないように) 発話終了後の無音判定時間の調整 (話している途中に送信されないように) AI側が発話中は音声認識を切る (音を拾わないように) 音声認識結果が空なら再読み取り (エラー対応) Tomoki Yoshida - DeNA

37.

生成AIをコア技術に持つプロダクト開発の知見を公開 画像生成機能でシーン生成 対話履歴から状況説明LLMの出力を画像出力モデルに入れてシーン生成 Gemini 2.0 Flash Preview Image Generation テキストから画像生成: 初期シーン生成 画像とテキストで画像編集: キャラを保ったままシーン変更 いまだと Gemini 2.5 Flash Image (Nano Banana) ですね FastSD CPU CPUでも高速動作するStable Diffusion localhost:8000でAPI提供される image2imageにも対応 Tomoki Yoshida - DeNA