🔍

Amplify AI Kit 解説:ゼロから作る Deep Search チャットアプリ

2025/04/01に公開

はじめに

「愛犬との毎日を楽しく便利にするアプリ オトとりっぷ」でエンジニアしています、足立です!

この記事では、AWS Amplify Gen2、Amplify AI Kit、そして LangChain を組み合わせて、Deep Search システムを構築する方法を詳しく解説します。前回の記事「Amplify AI Kit 解説:ゼロから作る AI チャットアプリ」をベースに、さらに発展させた内容となっています。

Deep Search システムとは?

今回構築するシステムは、単なるチャットボットではなく、複数の AI エージェントが連携して深い調査をサポートする高度なシステムです。具体的には以下の 3 つのエージェントとそれらをまとめる Supervisor Agent が協調して動作します:

  1. Planning Agent: ユーザーの質問から検索計画を作成するエージェント
  2. Search Agent: 情報検索を行うエージェント(Tavily API を使用)
  3. Writer Agent: 検索結果から記事を作成するエージェント

これらのエージェントが連携することで、単純な質問応答ではなく、深い調査と分析に基づいた高品質なコンテンツを生成できます。

今回作成するアプリケーション

この記事では、以下の機能を持つ Deep Search システムを構築します:

  1. ユーザー認証機能(サインアップ、ログイン)
  2. 複数の AI エージェントが連携する高度なチャット機能
  3. チャット履歴の保存と表示機能
  4. 過去の会話の続きを再開する機能
  5. 検索計画の作成、情報検索、コンテンツ作成の自動化

完成品はこちらのレポジトリで公開しています。

https://github.com/ototrip-lab/deep-research-with-amplify-ai/

それでは、実際に構築していきましょう!

開発環境のセットアップ

プロジェクトの作成

まずは、前回作成した「amplify-gen2-quickstart」をベースに新しいプロジェクトを作成します。

# リポジトリのクローン
$ git clone https://github.com/ototrip-lab/amplify-gen2-quickstart.git deep-research-with-amplify-ai
$ cd deep-research-with-amplify-ai

# 依存関係のインストール
$ npm install

次に、LangChain 関連のライブラリをインストールします:

# LangChain関連のライブラリをインストール
$ npm install @langchain/core @langchain/langgraph @langchain/aws @langchain/community

これらのライブラリは、複数の AI エージェントを連携させるために必要なものです:

  • @langchain/core: LangChain の基本機能を提供するライブラリ
  • @langchain/langgraph: エージェント間の連携を管理するためのライブラリ
  • @langchain/aws: AWS Bedrock との連携を行うためのライブラリ
  • @langchain/community: コミュニティが提供する様々なツールを使用するためのライブラリ

プロジェクト構造の確認

新しいプロジェクトの基本構造は以下のようになります:

├── amplify/
│   ├── auth/
│   │   └── resource.ts
│   ├── data/
│   │   └── resource.ts
│   ├── backend.ts
│   └── package.json
│
├── app/
│   ├── _components/
│   │   ├── AIConversationLayout.tsx
│   │   ├── BasicLayout.tsx
│   │   └── ConfigureAmplify.tsx
│   ├── chat/
│   │   ├── [id]/
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── history/
│   │   ├── page.tsx
│   │   └── useData.ts
│   ├── client.ts
│   ├── layout.tsx
│   └── page.tsx

この基本構造に、以下のディレクトリとファイルを追加していきます:

├── amplify/
│   ├── data/
│   │   ├── constants.ts           # 新規追加: Bedrockモデル定数
│   │   ├── chatHandler/           # 新規追加: チャットハンドラー
│   │   │   ├── index.ts
│   │   │   └── resource.ts
│   │   ├── planningAgent/         # 新規追加: 検索計画エージェント
│   │   │   ├── index.ts
│   │   │   └── resource.ts
│   │   ├── searchAgent/           # 新規追加: 情報検索エージェント
│   │   │   ├── index.ts
│   │   │   └── resource.ts
│   ├── prompts/                   # 新規追加: プロンプト定義
│   │   ├── index.ts
│   │   ├── planning.ts
│   │   ├── search.ts
│   │   ├── supervisor.ts
│   │   └── writer.ts

ちなみに、Writer Agent は Bedrock API を直接呼び出すので、追加のファイルは必要ありません。後ほど設定します。

バックエンドリソースの設定

定数の設定

まず、amplify/data/constants.tsファイルを作成し、Bedrock モデルの定数を定義します:

amplify/data/constants.ts
amplify/data/constants.ts
export const BEDROCK_MODEL = "anthropic.claude-3-5-sonnet-20240620-v1:0";

この定数は、AWS Bedrock で使用するモデルの ID を指定しています。Claude 3.5 Sonnet を使用することで、高品質な応答を得ることができます。

プロンプトの設定

次に、各エージェントで使用するプロンプトを定義します。まず、amplify/prompts/index.tsファイルを作成します:

amplify/prompts/index.ts
amplify/prompts/index.ts
import { prompt as planningPrompt } from "./planning";
import { prompt as searchPrompt } from "./search";
import { prompt as supervisorPrompt } from "./supervisor";
import { prompt as writerPrompt } from "./writer";

export { planningPrompt, searchPrompt, supervisorPrompt, writerPrompt };

次に、各エージェント用のプロンプトファイルを作成します。ここでは例として、Supervisor Agent 用のプロンプトを示します:

amplify/prompts/supervisor.ts
amplify/prompts/supervisor.ts
export const prompt = `
# Research Assistant: Multi-Agent Coordinator

You are a sophisticated research assistant that coordinates a multi-agent workflow to provide comprehensive, high-quality research on user queries.

## Your Workflow

You manage a 3-step research process:

1. **PLANNING** (Planning Agent): Create a structured research plan with 5 distinct categories
2. **SEARCH** (Search Agent): Retrieve high-quality information for each category
3. **SYNTHESIS** (Writer Agent): Create a comprehensive, well-structured report

## Your Capabilities

You have access to three specialized tools:

1. **planning**: Creates a research plan with 5 distinct categories
2. **search**: Retrieves information based on specific queries
3. **writer**: Synthesizes information into a cohesive report

## Interaction Guidelines

1. **Initial Response**: When a user submits a query, explain your research process and that you'll work through multiple steps.

2. **Planning Phase**:
   - Call the planning tool with the user's query
   - Present the research plan to the user
   - Ask for confirmation or adjustments before proceeding

3. **Search Phase**:
   - After user approval, call the search tool for EACH category in the plan
   - Show progress updates between searches
   - Present a summary of findings for each category

4. **Synthesis Phase**:
   - After completing all searches, call the writer tool
   - Present the final comprehensive report
   - Format with clear headings, subheadings, and proper citations

5. **Transparency**:
   - Clearly indicate which phase you're in
   - Explain what you're doing at each step
   - Show when you're transitioning between tools

## Critical Requirements

- ALWAYS get user confirmation after the planning phase before proceeding
- Execute searches for ALL FIVE categories from the research plan
- Maintain a conversational, helpful tone throughout
- Format the final report with proper Markdown for readability
- Acknowledge limitations in your research when appropriate
- Consider current date (${new Date().toDateString()})

Remember: Your goal is to provide DEEP, THOROUGH research that goes beyond surface-level information. Guide the user through each step of the process while delivering comprehensive, high-quality results.
`;

同様に、planning Agent、Search Agent、Writer Agent 用のプロンプトも作成します。これらのプロンプトは、各エージェントの役割と振る舞いを定義する重要な要素です。

amplify/prompts/planning.ts
amplify/prompts/planning.ts
export const prompt = `
# Planning Agent: Strategic Research Architect

You are the Planning Agent, responsible for transforming user queries into structured research strategies.

## Core Mission
Create a comprehensive research plan with 5 distinct categories and optimized search queries that ensure DEEP and THOROUGH investigation.

## Key Responsibilities

### 1. Query Analysis
- Identify primary objectives and information needs
- Extract key concepts and relationships
- Identify ambiguities requiring clarification
- Determine scope boundaries
- Consider multiple dimensions of the topic (historical, technical, practical, theoretical, etc.)

### 2. Category Development
- Create **exactly five distinct categories** that collectively cover the topic
- Ensure categories are:
  - Complementary with minimal overlap
  - Strategically chosen for different aspects
  - DEEP rather than surface-level
  - Balanced in scope
  - Logically organized
  - Include both fundamental and advanced aspects
  - Consider contrasting perspectives when appropriate

### 3. Search Query Formulation
- Develop precise search queries for each category that:
  - Clearly define information to retrieve
  - Use optimal terminology including technical terms when appropriate
  - Include necessary context
  - Are structured for effective retrieval
  - Target authoritative and specialized sources
  - Use advanced search operators when beneficial
  - Avoid overly general terms that would yield shallow results

### 4. Rationale Documentation
- Explain why each category was selected
- Show how categories ensure comprehensive coverage
- Explain search query formulation strategy
- Justify the depth of investigation for each category

## Output Format

\`\`\`
RESEARCH PLAN FOR: [User's query]

CATEGORY 1: [Name]
Search Query: [Query]
Rationale: [Brief explanation of why this category is important and how it provides depth]
Expected Insights: [What deep insights this category should yield]

CATEGORY 2: [Name]
Search Query: [Query]
Rationale: [Brief explanation of why this category is important and how it provides depth]
Expected Insights: [What deep insights this category should yield]

[Continue for all five categories]

NOTE TO USER: Please review this research plan and confirm if it meets your needs. Would you like to proceed with these categories or would you like any adjustments?
\`\`\`

## Critical Guidelines
- Prioritize DEPTH over breadth in category selection
- Ensure five categories collectively cover all important aspects
- Make each category distinct while maintaining cohesive coverage
- Craft specific, relevant search queries that will yield substantive results
- Consider current date (${new Date().toDateString()})
- Use clear, unambiguous language
- Avoid superficial or obvious categories that would only yield basic information
- Include categories that explore nuanced or specialized aspects of the topic
`;
amplify/prompts/search.ts
amplify/prompts/search.ts
export const prompt = `
# Search Agent: Information Retrieval Specialist

You are the Search Agent, responsible for retrieving high-quality information based on specific search queries.

## Core Mission
Retrieve comprehensive, accurate, and relevant information from reliable sources to support deep research.

## Key Responsibilities

### 1. Query Processing
- Understand the intent and context of each search query
- Identify key concepts and relationships
- Recognize specialized terminology
- Determine appropriate information sources

### 2. Information Retrieval
- Execute searches across multiple reliable sources
- Prioritize authoritative, peer-reviewed, and specialized sources
- Retrieve both fundamental and advanced information
- Ensure comprehensive coverage of the topic
- Include diverse perspectives when appropriate

### 3. Information Evaluation
- Assess source credibility and authority
- Evaluate information accuracy and currency
- Identify potential biases or limitations
- Verify information through multiple sources when possible
- Distinguish between facts, opinions, and consensus views

### 4. Information Synthesis
- Organize retrieved information logically
- Identify key themes and patterns
- Recognize relationships between different pieces of information
- Highlight contradictions or disagreements in the literature
- Integrate information from multiple sources

## Output Format

\`\`\`
SEARCH RESULTS FOR: [Search Query]

SOURCE 1: [Source name/URL]
Key Information:
- [Concise summary of relevant information]
- [Additional key points]
- [Important data or statistics]
Credibility Assessment: [Brief evaluation of source reliability]

SOURCE 2: [Source name/URL]
Key Information:
- [Concise summary of relevant information]
- [Additional key points]
- [Important data or statistics]
Credibility Assessment: [Brief evaluation of source reliability]

[Continue for all relevant sources]

SYNTHESIS:
[Integrated summary of key findings across all sources]
[Identification of consensus views]
[Highlight of contradictions or gaps]
[Implications of the findings]

LIMITATIONS:
[Acknowledgment of any limitations in the retrieved information]
[Identification of areas requiring further investigation]
\`\`\`

## Critical Guidelines
- Prioritize DEPTH and QUALITY over quantity in information retrieval
- Ensure comprehensive coverage of the topic
- Maintain objectivity and avoid bias in information selection
- Provide proper attribution for all information
- Consider current date (${new Date().toDateString()})
- Use clear, precise language
- Include both established knowledge and recent developments
- Acknowledge limitations and uncertainties in the information
`;
amplify/prompts/writer.ts
amplify/prompts/writer.ts
export const prompt = `
# Writer Agent: Content Synthesis Specialist

You are the Writer Agent, responsible for synthesizing research findings into comprehensive, well-structured content.

## Core Mission
Transform raw research data into cohesive, insightful, and valuable content that thoroughly addresses the original query.

## Key Responsibilities

### 1. Content Organization
- Create a logical structure with clear sections and subsections
- Ensure smooth transitions between topics
- Balance depth and breadth of coverage
- Prioritize information based on relevance and importance
- Create an engaging flow that builds understanding progressively

### 2. Information Synthesis
- Integrate information from multiple sources
- Identify and highlight key themes and patterns
- Reconcile contradictory information when present
- Connect related concepts across different research categories
- Distinguish between established facts, emerging research, and speculative areas

### 3. Content Enhancement
- Provide context and background information where needed
- Include relevant examples, case studies, or applications
- Add explanations for technical concepts
- Incorporate visual elements (tables, lists) for clarity
- Balance theoretical knowledge with practical implications

### 4. Quality Assurance
- Ensure factual accuracy and logical consistency
- Maintain appropriate tone and style for the target audience
- Provide proper attribution for information sources
- Ensure comprehensive coverage of the original query
- Identify any remaining gaps or areas for further research

## Output Format

\`\`\`
# [Descriptive Title for the Research Topic]

## Introduction
[Engaging introduction that presents the topic, its importance, and an overview of what will be covered]

## [Main Section 1]
[Comprehensive content covering the first major aspect of the topic]

### [Subsection 1.1]
[Detailed exploration of a specific element]

### [Subsection 1.2]
[Detailed exploration of another specific element]

## [Main Section 2]
[Comprehensive content covering the second major aspect of the topic]

[Continue with additional main sections and subsections as appropriate]

## Conclusion
[Summary of key findings, implications, and potential applications or future directions]

## References
[Properly formatted citations for all sources used]
\`\`\`

## Critical Guidelines
- Prioritize DEPTH and INSIGHT over mere summarization
- Ensure comprehensive coverage of all research categories
- Maintain a cohesive narrative throughout the content
- Use clear, precise language appropriate for the topic
- Include proper citations and attributions
- Consider current date (${new Date().toDateString()})
- Balance accessibility with appropriate technical depth
- Address the original query thoroughly and directly
`;

エージェントの実装

次に、各エージェントの実装を行います。まず、Planning Agent の実装から始めましょう。

Planning Agent の実装

Planning Agent は、ユーザーの質問から検索の計画を作成するエージェントです。amplify/data/planningAgent/resource.tsファイルを作成します:

amplify/data/planningAgent/resource.ts
amplify/data/planningAgent/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const planningAgent = defineFunction({
  name: 'planningAgent',
  entry: './index.ts',
  memoryMB: 512,
  timeoutSeconds: 60,
  runtime: 22,
});

次に、amplify/data/planningAgent/index.tsファイルを作成します:

amplify/data/planningAgent/index.ts
amplify/data/planningAgent/index.ts
import { BedrockChat } from '@langchain/community/chat_models/bedrock';

import { planningPrompt } from '../../prompts';
import { BEDROCK_MODEL } from '../constants';
import type { Schema } from '../resource';

// AWS SDK
const model = new BedrockChat({
  model: BEDROCK_MODEL,
});

export const handler: Schema['planningAgent']['functionHandler'] = async (
  event
) => {
  console.log({ event });

  const authorization = event.request.headers.authorization;
  const identity = event.identity as any;
  const username = identity?.username;
  const message = event.arguments.message;

  if (!authorization || !username || !message) {
    throw new Error('Unauthorized');
  }

  const answer = await model.invoke([
    ['system', planningPrompt],
    ['human', message],
  ]);
  console.log({ answer });

  return {
    value: answer.content as string,
  };
};

ここでは、以下のことを行っています:

  1. Bedrock モデルの初期化

    • BedrockChatクラスを使用して、AWS Bedrock のモデルを初期化
    • 先ほど定義したBEDROCK_MODEL定数を使用してモデルを指定
  2. ハンドラー関数の定義

    • ユーザーからのメッセージを受け取り、認証情報を確認
    • システムプロンプトとユーザーメッセージを組み合わせてモデルに送信

Search Agent の実装

次に、Search Agent を実装します。amplify/data/searchAgent/resource.tsファイルを作成します:

amplify/data/searchAgent/resource.ts
amplify/data/searchAgent/resource.ts
import { defineFunction, secret } from '@aws-amplify/backend';

export const searchAgent = defineFunction({
  name: 'searchAgent',
  entry: './index.ts',
  memoryMB: 512,
  timeoutSeconds: 60,
  runtime: 22,
  environment: {
    TAVILY_API_KEY: secret('TAVILY_API_KEY'),
  },
});

このコードでは、Tavily API を使用するための API キーを環境変数として設定しています。secret関数を使用することで、API キーを安全に管理できます。

次に、amplify/data/searchAgent/index.tsファイルを作成します。このファイルでは、LangGraph を使用して検索エージェントを実装します:

amplify/data/searchAgent/index.ts
amplify/data/searchAgent/index.ts
import { env } from '$amplify/env/searchAgent';
import { ChatBedrockConverse } from '@langchain/aws';
import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
import {
  AIMessage,
  HumanMessage,
  SystemMessage,
} from '@langchain/core/messages';
import { MessagesAnnotation, StateGraph } from '@langchain/langgraph';
import { ToolNode } from '@langchain/langgraph/prebuilt';

import { searchPrompt } from '../../prompts';
import { BEDROCK_MODEL } from '../constants';
import type { Schema } from '../resource';

const tavilyTool = new TavilySearchResults({
  maxResults: 3,
  apiKey: env.TAVILY_API_KEY,
});
const model = new ChatBedrockConverse({
  model: BEDROCK_MODEL,
});
const modelWithTavily = model.bindTools([tavilyTool]);
const toolNode = new ToolNode([tavilyTool]);

// Define the function that determines whether to continue or not
const shouldContinue = ({ messages }: typeof MessagesAnnotation.State) => {
  const lastMessage = messages[messages.length - 1] as AIMessage;

  // If the LLM makes a tool call, then we route to the "tools" node
  if (lastMessage.tool_calls?.length) {
    return 'tools';
  }
  // Otherwise, we stop (reply to the user) using the special "__end__" node
  return '__end__';
};

// Define the function that calls the model
const callModel = async (state: typeof MessagesAnnotation.State) => {
  const response = await modelWithTavily.invoke(state.messages);
  console.log({ response });

  // We return a list, because this will get added to the existing list
  return { messages: [response] };
};

export const handler: Schema['searchAgent']['functionHandler'] = async (
  event
) => {
  console.log({ event });

  const authorization = event.request.headers.authorization;
  const identity = event.identity as any;
  const username = identity?.username;
  const message = event.arguments.message;

  if (!authorization || !username || !message) {
    throw new Error('Unauthorized');
  }

  // Define a new graph
  const workflow = new StateGraph(MessagesAnnotation)
    .addNode('agent', callModel)
    .addEdge('__start__', 'agent')

    .addNode('tools', toolNode)
    .addEdge('tools', 'agent')

    .addConditionalEdges('agent', shouldContinue);

  // Finally, we compile it into a LangChain Runnable.
  const app = workflow.compile();

  // Use the agent
  const finalState = await app.invoke({
    messages: [new SystemMessage(searchPrompt), new HumanMessage(message)],
  });
  const answer = finalState.messages[finalState.messages.length - 1];

  return {
    value: answer.content as string,
  };
};

この Search Agent の実装では、LangGraph を使用して検索ワークフローを構築しています。主な特徴は以下の通りです:

  1. 状態管理:

    • LangGraph のStateGraphを使用してワークフローを定義
  2. 検索プロセス:

    • Tavily API を使用して情報検索を実行
    • 検索結果を JSON 形式で保存
  3. 要約プロセス:

    • 検索結果を Bedrock モデルに送信
    • 事前に定義したsearchPromptを使用して結果を要約

このように、LangGraph を使用することで、複雑な AI ワークフローを簡潔に定義できます。

Chat Handler の実装

次に、チャットハンドラーを実装します。チャットハンドラーは、ユーザーとの会話を管理し、適切なエージェントに処理を委譲する役割を担います。

まず、amplify/data/chatHandler/resource.tsファイルを作成します:

amplify/data/chatHandler/resource.ts
amplify/data/chatHandler/resource.ts
import { a } from '@aws-amplify/backend';
import { defineConversationHandlerFunction } from '@aws-amplify/backend-ai/conversation';

export const chatHandler = defineConversationHandlerFunction({
  entry: './index.ts',
  name: 'customChatHandler',
  models: [{ modelId: a.ai.model('Claude 3.5 Sonnet') }],
  timeoutSeconds: 60 * 10, // 10 minutes
});

このコードでは、defineConversationHandlerFunctionを使用してチャットハンドラー関数を定義しています。タイムアウトを 10 分に設定することで、長時間の処理にも対応できるようにしています。

次に、amplify/data/chatHandler/index.tsファイルを作成します:

amplify/data/chatHandler/index.ts
amplify/data/chatHandler/index.ts
import {
  ConversationTurnEvent,
  handleConversationTurnEvent,
} from '@aws-amplify/backend-ai/conversation/runtime';

export const handler = async (event: ConversationTurnEvent) => {
  await handleConversationTurnEvent(event, {});
};

このハンドラーは非常にシンプルで、Amplify AI Kit のhandleConversationTurnEvent関数を使用してイベントを処理しています。これにより、会話の状態管理やツールの呼び出しなどが自動的に行われます。

データリソースの更新

データ(Data)と AI Kit の設定

次に、amplify/data/resource.tsファイルを更新して、作成したエージェントとプロンプトを統合します:

amplify/data/resource.ts
amplify/data/resource.ts
import { a, defineData, type ClientSchema } from "@aws-amplify/backend";

import { supervisorPrompt, writerPrompt } from "../prompts";
import { chatHandler } from "./chatHandler/resource";
import { planningAgent } from "./planningAgent/resource";
import { searchAgent } from "./searchAgent/resource";

const schema = a.schema({
  searchAgent: a
    .query()
    .arguments({
      message: a.string(),
    })
    .returns(
      a.customType({
        value: a.string(),
      }),
    )
    .handler(a.handler.function(searchAgent))
    .authorization((allow) => allow.authenticated()),

  planningAgent: a
    .query()
    .arguments({
      message: a.string(),
    })
    .returns(
      a.customType({
        value: a.string(),
      }),
    )
    .handler(a.handler.function(planningAgent))
    .authorization((allow) => allow.authenticated()),

  // Define AI Kit
  chat: a
    .conversation({
      aiModel: a.ai.model("Claude 3.5 Sonnet"),
      systemPrompt: supervisorPrompt,
      handler: chatHandler,
      inferenceConfiguration: {
        temperature: 0.5,
      },
      tools: [
        a.ai.dataTool({
          name: "planning",
          description: "Planning tool",
          query: a.ref("planningAgent"),
        }),
        a.ai.dataTool({
          name: "search",
          description: "Search tool",
          query: a.ref("searchAgent"),
        }),
        a.ai.dataTool({
          name: "writer",
          description: "Writer tool",
          query: a.ref("writerAgent"),
        }),
      ],
    })
    .authorization((allow) => allow.owner()),
  writerAgent: a
    .generation({
      aiModel: a.ai.model("Claude 3.5 Sonnet"),
      systemPrompt: writerPrompt,
    })
    .arguments({ description: a.string() })
    .returns(a.string())
    .authorization((allow) => allow.authenticated()),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
  },
});

この更新されたリソース定義では、以下の変更が行われています:

  1. プロンプトとエージェントのインポート:

    • supervisorPromptwriterPromptをインポート
    • 各エージェントのリソース定義をインポート
  2. エージェントのクエリ定義:

    • searchAgentplanningAgentをクエリとして定義
    • 各エージェントは文字列メッセージを受け取り、結果を返す
  3. チャット会話の拡張:

    • システムプロンプトをsupervisorPromptに変更
    • カスタムハンドラーを設定
    • 推論設定で temperature を 0.5 に設定(より一貫性のある応答を生成)
    • ツールとして各エージェントを追加
  4. Writer Agent の定義:

    • テキスト生成用のエージェントを定義
    • システムプロンプトをwriterPromptに設定

これらの変更により、複数の AI エージェントが連携して動作する Deep Search システムが構築されます。

バックエンドの統合

最後に、定義したデータリソースを統合するために、amplify/backend.tsファイルを設定します:

amplify/backend.ts
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';

import { auth } from './auth/resource';
import { chatHandler } from './data/chatHandler/resource';
import { planningAgent } from './data/planningAgent/resource';
import { data } from './data/resource';
import { searchAgent } from './data/searchAgent/resource';

const backend = defineBackend({
  auth,
  data,
  planningAgent,
  searchAgent,
  chatHandler,
});

backend.planningAgent.resources.lambda.addToRolePolicy(
  new PolicyStatement({
    actions: ['bedrock:InvokeModel'],
    resources: ['*'],
  })
);

backend.searchAgent.resources.lambda.addToRolePolicy(
  new PolicyStatement({
    actions: ['bedrock:InvokeModel'],
    resources: ['*'],
  })
);

この更新されたリソース定義では、以下の変更が行われています:

  1. defineBackend へのリソース追加:

    • planningAgentsearchAgentchatHandlerをインポート
    • 各エージェントのバックエンドを統合
  2. addToRolePolicy の追加:

    • planningAgentsearchAgentが Bedrock へのアクセス権を付与

これでバックエンドの設定は完了です。

フロントエンドの実装

フロントエンドの基本的な構造はamplify-gen2-quickstartと同じですが、バックエンドの変更により、ユーザーエクスペリエンスは大きく異なります。ここでは、主要なコンポーネントの変更点について説明します。

クライアントの設定

app/client.tsファイルは変更なしで、以下のようになっています:

app/client.ts
app/client.ts
import { Schema } from "@/amplify/data/resource";
import { createAIHooks } from "@aws-amplify/ui-react-ai";
import { generateClient } from "aws-amplify/api";

export const client = generateClient<Schema>({ authMode: "userPool" });
export const { useAIConversation, useAIGeneration } = createAIHooks(client);

このファイルでは、Amplify AI Kit のクライアントを設定しています。useAIConversationフックを使用して、チャット会話を管理します。

AIConversationLayout コンポーネント

app/_components/AIConversationLayout.tsxファイルも変更なしで、以下のようになっています:

app/_components/AIConversationLayout.tsx
app/_components/AIConversationLayout.tsx
"use client";

import { View, useTheme } from "@aws-amplify/ui-react";
import { AIConversation } from "@aws-amplify/ui-react-ai";
import Markdown from "react-markdown";

import { useAIConversation } from "@/app/client";

export const AIConversationLayout = ({ id }: { id?: string }) => {
  const { tokens } = useTheme();
  const [
    {
      data: { messages },
      isLoading,
    },
    handleSendMessage,
  ] = useAIConversation("chat", { id });

  return (
    <View padding={tokens.space.large}>
      <AIConversation
        messages={messages}
        isLoading={isLoading}
        handleSendMessage={handleSendMessage}
        messageRenderer={{
          text: ({ text }) => <Markdown>{text}</Markdown>,
        }}
      />
    </View>
  );
};

このコンポーネントは、Amplify AI Kit のAIConversationコンポーネントを使用してチャット UI を表示しています。useAIConversationフックを使用して、チャット会話を管理しています。

バックエンドの変更により、このコンポーネントは以下のような動作をします:

  1. ユーザーがメッセージを送信すると、supervisorPromptに基づいて処理が行われる
  2. AI アシスタントは必要に応じて各エージェント(planning、search、writer)を呼び出す
  3. 各エージェントの結果を統合して、最終的な応答を生成する

これにより、単純なチャットボットではなく、深い研究を支援する高度なシステムとして機能します。

アプリケーションの実行と動作確認

すべてのコンポーネントの実装が完了したら、アプリケーションを起動して動作確認を行います。

開発サーバーの起動

# ターミナル1: Amplify Sandboxの起動
$ npx ampx sandbox

# ターミナル2: Next.jsの開発サーバーを起動
$ npm run dev

シークレットの設定

Search Agent で使用する Tavily API キーを設定する必要があります。Amplify Gen2 では、シークレットを以下のように設定できます:

# Tavily APIキーの設定
$ npx ampx sandbox secret set TAVILY_API_KEY
? Enter secret value: ###
Done!

コマンドを実行すると、API キーの入力を求められるので、Tavily から取得した API キーを入力します。
Tavily API キーそのものはTavily のサイトにログインすることで取得できますので、詳細な取得方法はここでは割愛します。

アプリケーションの使用方法

  1. ブラウザで http://localhost:3000 にアクセス
  2. 認証画面が表示されるので、サインアップしてアカウントを作成
  3. ログイン後、チャット画面が表示される
  4. 研究したいトピックについてメッセージを入力して送信

Deep Search プロセスの流れ

このアプリケーションでは、ユーザーの質問に対して以下のような 3 段階のプロセスで研究を行います:

  1. 検索計画の作成(Planning Agent):

    • ユーザーの質問から 5 つのカテゴリに分類された検索計画を作成
    • 各カテゴリには、検索クエリ、根拠、期待される洞察が含まれる
    • ユーザーに検索計画を提示し、承認を求める
  2. 情報検索(Search Agent):

    • 承認された検索計画の各カテゴリについて情報検索を実行
    • Tavily API を使用して信頼性の高い情報源から情報を取得
    • 検索結果を要約して提示
  3. コンテンツ作成(Writer Agent):

    • 検索結果を統合して、包括的なレポートを作成
    • 論理的な構造と適切な引用を含む高品質なコンテンツを生成
    • マークダウン形式で整形された最終レポートを提示

このプロセスにより、単純な質問応答ではなく、深い調査と分析に基づいた高品質なコンテンツを生成できます。

システムの拡張性

このシステムは非常に拡張性が高く、以下のような方向で機能を追加できます:

1. 追加のエージェント

現在のシステムには 3 つのエージェント(Planning、Search、Writer)がありますが、以下のようなエージェントを追加することで機能を拡張できます:

  • Fact-Checking Agent: 情報の正確性を検証するエージェント
  • Visualization Agent: データを視覚化するエージェント
  • Critique Agent: 生成されたコンテンツを批評し、改善点を提案するエージェント

2. 外部データソースの統合

現在は Tavily API を使用していますが、以下のようなデータソースを追加することで、より多様な情報を取得できます:

  • 学術論文データベース: ArXiv、PubMed など
  • ニュース API: NewsAPI など
  • 専門データベース: 特定の分野に特化したデータベース

3. ユーザーフィードバックの活用

ユーザーからのフィードバックを収集し、システムの改善に活用する機能を追加できます:

  • フィードバックループ: 生成されたコンテンツに対するユーザー評価を収集
  • パーソナライゼーション: ユーザーの好みや過去の検索履歴に基づいて Deep プロセスをカスタマイズ
  • 協調フィルタリング: 類似したユーザーの検索パターンを活用して推奨検索クエリを提案

技術的な考察

LangChain と LangGraph の利点

このプロジェクトでは、LangChain と LangGraph を使用して AI エージェントを実装しています。これらのフレームワークを使用する主な利点は以下の通りです:

  1. モジュール性: 各エージェントを独立したモジュールとして実装でき、システム全体の保守性が向上します。

  2. ワークフロー管理: LangGraph を使用することで、複雑な AI ワークフローを視覚的に理解しやすい形で定義できます。

  3. 状態管理: エージェント間の状態遷移を明示的に定義でき、デバッグや拡張が容易になります。

  4. 再利用性: 一度定義したエージェントやワークフローは、他のプロジェクトでも再利用できます。

Amplify AI Kit との統合

Amplify AI Kit と LangChain/LangGraph を統合することで、以下のような利点があります:

  1. 認証との連携: Amplify Auth を使用して、ユーザーごとの会話履歴を管理できます。

  2. スケーラビリティ: AWS Lambda を使用してエージェントを実行するため、トラフィックの増加に応じて自動的にスケールします。

  3. セキュリティ: シークレット管理や IAM ポリシーなど、AWS のセキュリティ機能を活用できます。

  4. モニタリング: CloudWatch を使用して、エージェントのパフォーマンスやエラーを監視できます。

まとめ

この記事では、AWS Amplify Gen2、Amplify AI Kit、そして LangChain を組み合わせて、Deep Search システムを構築する方法を詳しく解説しました。主なポイントは以下の通りです:

  1. 複数の AI エージェントの連携:

    • Planning Agent: 検索計画の作成
    • Search Agent: 情報検索(Tavily API を使用)
    • Writer Agent: コンテンツ作成
  2. LangChain と LangGraph の統合:

    • 複雑な AI ワークフローの構築
    • エージェント間の連携
    • 状態管理と遷移の定義
  3. カスタムプロンプト:

    • 各エージェント用に最適化されたプロンプト
    • Deep プロセスの各ステップをガイド
  4. Amplify Gen2 の利点:

    • TypeScript ベースのインフラストラクチャ定義
    • 型安全性による開発効率の向上
    • ローカル開発環境(サンドボックス)での迅速なテスト

このシステムを使用することで、単純な質問応答ではなく、深い調査と分析に基づいた高品質なコンテンツを生成できます。また、モジュール性と拡張性が高いため、様々なユースケースに対応できます。


もし犬専用の音楽アプリ「オトとりっぷ」に興味を持っていただけたら、ぜひダウンロードしてみてください。

https://www.oto-trip.com/

Discussion