ドラフト エージェント設計パターン - ツール使用
ツール使用(関数呼び出し)により、AIエージェントは外部システム、API、データベースと対話できるようになり、受動的な応答者から能動的な実行者へと変貌します。このガイドでは、Vercelのサーバーレスプラットフォーム上でLangChain、LangGraph、Next.js 15を使用したプロダクション対応のツール使用パターンの実装を実演します。
メンタルモデル:オペレーティングシステムとしてのAI
ツール使用をオペレーティングシステムのシステムコールのように考えてください。プログラムがシステムコールを使ってハードウェアやリソースと対話するように、AIエージェントはツールを使って外部システムと対話します。LLMはユーザーの意図に基づいてどのツールを呼び出すかを決定する「カーネル」として機能します。LangChainはツールインターフェースを標準化する「ドライバー層」を提供し、LangGraphは複雑なマルチツールワークフローの「プロセススケジューリング」を管理します。このアーキテクチャにより、エージェントはデータベースクエリ、API呼び出し、計算などのアクションを実行できます - OSがファイルI/O、ネットワークリクエスト、CPU操作を調整するのと同様です。
基本的なツール実装
1. Zodスキーマでツールを定義
// lib/tools/weather-tool.ts
import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { get } from 'es-toolkit/object';
import { isString } from 'es-toolkit/predicate';
export const weatherTool = tool(
async ({ location, unit = 'celsius' }) => {
// APIコールのシミュレーション - 実際の天気APIに置き換える
const response = await fetch(
`https://api.weather.example/current?q=${location}&units=${unit}`
);
const data = await response.json();
// es-toolkitを使用した安全なデータアクセス
const temperature = get(data, 'main.temp');
const description = get(data, 'weather[0].description');
if (!isString(description)) {
throw new Error('無効な天気データを受信しました');
}
return `${location}の現在の天気: ${temperature}°${
unit === 'celsius' ? 'C' : 'F'
}, ${description}`;
},
{
name: 'get_weather',
description: '場所の現在の天気を取得',
schema: z.object({
location: z.string().describe('都市名または場所'),
unit: z.enum(['celsius', 'fahrenheit']).optional(),
}),
}
);
Zodスキーマ検証を使用して天気ツールを定義し、入力パラメータとTypeScript統合の両方でタイプセーフティを確保します。
2. 計算機ツールの作成
// lib/tools/calculator-tool.ts
import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { evaluate } from 'es-toolkit/math';
import { attempt } from 'es-toolkit/function';
export const calculatorTool = tool(
async ({ expression }) => {
// 安全な実行のためにes-toolkitのattemptを使用
const result = attempt(() => {
// プロダクションでは、mathjsのような適切な数式パーサーを使用
return evaluate(expression);
});
if (result instanceof Error) {
return `エラー: 無効な式 "${expression}"`;
}
return `結果: ${result}`;
},
{
name: 'calculator',
description: '数学的計算を実行',
schema: z.object({
expression: z.string().describe('評価する数式'),
}),
}
);
安全な実行のためにes-toolkitのattemptパターンを使用したエラー処理を含む計算機ツールを実装します。
3. データベースクエリツール
// lib/tools/database-tool.ts
import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { sql } from '@vercel/postgres';
import { mapValues, pick } from 'es-toolkit/object';
import { chunk } from 'es-toolkit/array';
export const databaseTool = tool(
async ({ query, parameters = {} }) => {
try {
// クエリのサニタイズと検証
if (query.toLowerCase().includes('drop') ||
query.toLowerCase().includes('delete')) {
return 'エラー: 破壊的な操作は許可されていません';
}
// パラメータ付きクエリの実行
const result = await sql.query(query, Object.values(parameters));
// 大規模データセットのチャンク処理
const rows = result.rows;
const chunks = chunk(rows, 100);
// サーバーレスメモリ制限のため最初のチャンクを返す
return JSON.stringify({
rowCount: result.rowCount,
data: chunks[0] || [],
hasMore: chunks.length > 1,
});
} catch (error) {
return `データベースエラー: ${error.message}`;
}
},
{
name: 'query_database',
description: 'データベースで安全なSELECTクエリを実行',
schema: z.object({
query: z.string().describe('SQL SELECTクエリ'),
parameters: z.record(z.any()).optional().describe('クエリパラメータ'),
}),
}
);
サーバーレスメモリ制約に最適化された安全性チェックとチャンク結果処理を含むデータベースツールを作成します。
4. ツールをLLMにバインド
// lib/agents/tool-agent.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { weatherTool } from '@/lib/tools/weather-tool';
import { calculatorTool } from '@/lib/tools/calculator-tool';
import { databaseTool } from '@/lib/tools/database-tool';
import { pipe } from 'es-toolkit/function';
export function createToolAgent() {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0,
streaming: true,
});
const tools = [weatherTool, calculatorTool, databaseTool];
// ツールをモデルにバインド
const modelWithTools = model.bindTools(tools);
return {
model: modelWithTools,
tools,
// ツール呼び出しをフォーマットするヘルパー
formatToolCall: pipe(
(call: any) => call.args,
JSON.stringify
),
};
}
複数のツールをLLMにバインドし、ユーザークエリに基づいて使用するツールを決定できるようにします。
5. ツール実行を含むAPIルート
// app/api/tools/route.ts
import { NextResponse } from 'next/server';
import { createToolAgent } from '@/lib/agents/tool-agent';
import { HumanMessage } from '@langchain/core/messages';
import { debounce } from 'es-toolkit/function';
export const runtime = 'nodejs';
export const maxDuration = 777; // 800秒制限のセーフティバッファー
// 同時リクエストのデバウンス
const processRequest = debounce(async (message: string) => {
const { model, tools } = createToolAgent();
// ツール呼び出しを含むLLMレスポンスを取得
const response = await model.invoke([
new HumanMessage(message)
]);
// ツール呼び出しが存在する場合は実行
if (response.tool_calls && response.tool_calls.length > 0) {
const toolResults = await Promise.all(
response.tool_calls.map(async (toolCall) => {
const tool = tools.find(t => t.name === toolCall.name);
if (!tool) return null;
const result = await tool.invoke(toolCall.args);
return {
tool: toolCall.name,
result,
};
})
);
return { toolResults, message: response.content };
}
return { message: response.content };
}, 100);
export async function POST(req: Request) {
try {
const { message } = await req.json();
const result = await processRequest(message);
return NextResponse.json(result);
} catch (error) {
return NextResponse.json(
{ error: 'ツール実行に失敗しました' },
{ status: 500 }
);
}
}
メッセージを処理し、ツール呼び出しを実行し、レート制限のためのデバウンス付きで結果を返すサーバーレスAPIルートを実装します。
高度なツールオーケストレーション
1. 並列ツール実行
// lib/orchestration/parallel-tools.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { RunnableParallel } from '@langchain/core/runnables';
import { groupBy } from 'es-toolkit/array';
import { mapValues } from 'es-toolkit/object';
export class ParallelToolOrchestrator {
private model: ChatGoogleGenerativeAI;
private tools: Map<string, any>;
constructor(tools: any[]) {
this.model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0,
});
this.tools = new Map(tools.map(t => [t.name, t]));
this.model = this.model.bindTools(tools);
}
async executeParallel(message: string) {
// LLMからツール呼び出しを取得
const response = await this.model.invoke([
{ role: 'user', content: message }
]);
if (!response.tool_calls?.length) {
return { message: response.content };
}
// 依存関係でツールをグループ化(独立したツールは並列実行可能)
const toolGroups = this.groupIndependentTools(response.tool_calls);
// 各グループを並列実行
const results = [];
for (const group of toolGroups) {
const groupResults = await Promise.all(
group.map(async (call) => {
const tool = this.tools.get(call.name);
if (!tool) return null;
const startTime = Date.now();
const result = await tool.invoke(call.args);
const duration = Date.now() - startTime;
return {
tool: call.name,
result,
duration,
timestamp: new Date().toISOString(),
};
})
);
results.push(...groupResults.filter(Boolean));
}
return {
message: response.content,
toolResults: results,
parallelGroups: toolGroups.length,
totalDuration: Math.max(...results.map(r => r.duration)),
};
}
private groupIndependentTools(toolCalls: any[]) {
// シンプルな経験則: 共有パラメータのないツールは並列実行可能
const groups: any[][] = [];
const used = new Set<number>();
toolCalls.forEach((call, i) => {
if (used.has(i)) return;
const group = [call];
used.add(i);
// 他の独立したツールを見つける
toolCalls.forEach((other, j) => {
if (i !== j && !used.has(j) && this.areIndependent(call, other)) {
group.push(other);
used.add(j);
}
});
groups.push(group);
});
return groups;
}
private areIndependent(tool1: any, tool2: any): boolean {
// データ依存関係を共有しない場合、ツールは独立している
const args1 = JSON.stringify(tool1.args);
const args2 = JSON.stringify(tool2.args);
// シンプルなチェック: 共有値なし
return !Object.values(tool1.args).some(v =>
args2.includes(String(v))
);
}
}
サーバーレス環境でスループットを最大化するための依存関係分析を含む並列ツール実行を実装します。
2. エラー回復を含むツールチェーン
// lib/orchestration/tool-chain.ts
import { StateGraph, END } from '@langchain/langgraph';
import { BaseMessage } from '@langchain/core/messages';
import { retry, delay } from 'es-toolkit/promise';
import { pipe } from 'es-toolkit/function';
interface ChainState {
messages: BaseMessage[];
toolResults: any[];
errors: any[];
retryCount: number;
}
export function createToolChain(tools: any[]) {
const graph = new StateGraph<ChainState>({
channels: {
messages: {
value: (x, y) => [...x, ...y],
default: () => [],
},
toolResults: {
value: (x, y) => [...x, ...y],
default: () => [],
},
errors: {
value: (x, y) => [...x, ...y],
default: () => [],
},
retryCount: {
value: (x, y) => y,
default: () => 0,
},
},
});
// リトライロジックでツールを実行
graph.addNode('execute_tool', async (state) => {
const currentTool = tools[state.toolResults.length];
if (!currentTool) {
return { ...state };
}
try {
const result = await retry(
async () => {
const res = await currentTool.invoke(
state.messages[state.messages.length - 1]
);
return res;
},
{
times: 3,
delay: 1000,
onRetry: (error, attempt) => {
console.log(`${currentTool.name}のリトライ ${attempt}:`, error);
},
}
);
return {
toolResults: [{
tool: currentTool.name,
result,
success: true
}],
};
} catch (error) {
return {
errors: [{
tool: currentTool.name,
error: error.message
}],
retryCount: state.retryCount + 1,
};
}
});
// エラー用のフォールバックノード
graph.addNode('handle_error', async (state) => {
const lastError = state.errors[state.errors.length - 1];
// 代替ツールまたは優雅な劣化を試みる
const fallbackResult = {
tool: 'fallback',
result: `${lastError.tool}の実行に失敗しました。キャッシュされた結果を使用。`,
fallback: true,
};
return {
toolResults: [fallbackResult],
};
});
// 決定ノード
graph.addNode('check_status', async (state) => {
if (state.errors.length > 0 && state.retryCount < 3) {
return 'handle_error';
}
if (state.toolResults.length < tools.length) {
return 'execute_tool';
}
return END;
});
// エッジを定義
graph.setEntryPoint('execute_tool');
graph.addEdge('execute_tool', 'check_status');
graph.addEdge('handle_error', 'execute_tool');
graph.addConditionalEdges('check_status', async (state) => {
if (state.errors.length > 0 && state.retryCount < 3) {
return 'handle_error';
}
if (state.toolResults.length < tools.length) {
return 'execute_tool';
}
return END;
});
return graph.compile();
}
プロダクションの信頼性のための自動リトライロジックとフォールバックメカニズムを持つ回復力のあるツールチェーンを作成します。
3. 動的ツール選択
// lib/orchestration/dynamic-tools.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { maxBy, minBy } from 'es-toolkit/array';
import { memoize } from 'es-toolkit/function';
export class DynamicToolSelector {
private model: ChatGoogleGenerativeAI;
private toolRegistry: Map<string, any>;
private performanceMetrics: Map<string, any>;
constructor() {
this.model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0,
});
this.toolRegistry = new Map();
this.performanceMetrics = new Map();
}
// メタデータ付きでツールを登録
registerTool(toolDef: any, metadata: {
cost: number;
latency: number;
reliability: number;
capabilities: string[];
}) {
this.toolRegistry.set(toolDef.name, {
tool: toolDef,
metadata,
});
}
// パフォーマンスのためのメモ化されたツール選択
private selectOptimalTool = memoize(
async (requirement: string, constraints: any) => {
const availableTools = Array.from(this.toolRegistry.values());
// 要件に基づいて各ツールをスコア付け
const scoredTools = await Promise.all(
availableTools.map(async (entry) => {
const score = this.calculateToolScore(
entry,
requirement,
constraints
);
return { ...entry, score };
})
);
// 最適なツールを選択
const bestTool = maxBy(scoredTools, t => t.score);
return bestTool?.tool;
},
{
getCacheKey: (req, cons) => `${req}-${JSON.stringify(cons)}`,
}
);
private calculateToolScore(
entry: any,
requirement: string,
constraints: any
): number {
const { metadata } = entry;
let score = 0;
// 機能のマッチング
const capabilityMatch = metadata.capabilities.some((cap: string) =>
requirement.toLowerCase().includes(cap.toLowerCase())
);
if (capabilityMatch) score += 40;
// 制約を考慮
if (constraints.maxLatency && metadata.latency <= constraints.maxLatency) {
score += 20;
}
if (constraints.maxCost && metadata.cost <= constraints.maxCost) {
score += 20;
}
// 信頼性ボーナス
score += metadata.reliability * 20;
// 過去のパフォーマンス
const metrics = this.performanceMetrics.get(entry.tool.name);
if (metrics?.successRate) {
score += metrics.successRate * 10;
}
return score;
}
async executeDynamic(
requirement: string,
constraints: {
maxLatency?: number;
maxCost?: number;
preferredTools?: string[];
} = {}
) {
// 最適なツールを選択
const selectedTool = await this.selectOptimalTool(
requirement,
constraints
);
if (!selectedTool) {
throw new Error('要件に適したツールが見つかりません');
}
const startTime = Date.now();
try {
// 選択したツールを実行
const result = await selectedTool.invoke({ query: requirement });
// パフォーマンスメトリクスを更新
this.updateMetrics(selectedTool.name, {
success: true,
latency: Date.now() - startTime,
});
return {
tool: selectedTool.name,
result,
metrics: {
latency: Date.now() - startTime,
cost: this.toolRegistry.get(selectedTool.name)?.metadata.cost,
},
};
} catch (error) {
// 失敗メトリクスを更新
this.updateMetrics(selectedTool.name, {
success: false,
error: error.message,
});
throw error;
}
}
private updateMetrics(toolName: string, metrics: any) {
const existing = this.performanceMetrics.get(toolName) || {
totalCalls: 0,
successfulCalls: 0,
totalLatency: 0,
};
existing.totalCalls++;
if (metrics.success) {
existing.successfulCalls++;
existing.totalLatency += metrics.latency;
}
existing.successRate = existing.successfulCalls / existing.totalCalls;
existing.avgLatency = existing.totalLatency / existing.successfulCalls;
this.performanceMetrics.set(toolName, existing);
}
}
要件、制約、および過去のパフォーマンスメトリクスに基づいた動的ツール選択を実装します。
4. ツール結果のストリーミング
// app/api/stream-tools/route.ts
import { createToolAgent } from '@/lib/agents/tool-agent';
import { ParallelToolOrchestrator } from '@/lib/orchestration/parallel-tools';
export const runtime = 'nodejs';
export const maxDuration = 777;
export async function POST(req: Request) {
const { message } = await req.json();
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();
const { model, tools } = createToolAgent();
const orchestrator = new ParallelToolOrchestrator(tools);
// バックグラウンドで処理
(async () => {
try {
// 初期の思考をストリーム
await writer.write(
encoder.encode(`data: ${JSON.stringify({
type: 'thinking',
content: 'リクエストを分析してツールを選択中...'
})}\n\n`)
);
// LLMからツール呼び出しを取得
const response = await model.invoke([
{ role: 'user', content: message }
]);
if (response.tool_calls) {
// ツール実行の更新をストリーム
for (const call of response.tool_calls) {
await writer.write(
encoder.encode(`data: ${JSON.stringify({
type: 'tool_start',
tool: call.name,
args: call.args
})}\n\n`)
);
const tool = tools.find(t => t.name === call.name);
if (tool) {
const result = await tool.invoke(call.args);
await writer.write(
encoder.encode(`data: ${JSON.stringify({
type: 'tool_complete',
tool: call.name,
result
})}\n\n`)
);
}
}
}
// 最終レスポンスをストリーム
await writer.write(
encoder.encode(`data: ${JSON.stringify({
type: 'complete',
content: response.content
})}\n\n`)
);
} catch (error) {
await writer.write(
encoder.encode(`data: ${JSON.stringify({
type: 'error',
error: error.message
})}\n\n`)
);
} finally {
await writer.close();
}
})();
return new Response(stream.readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
リアルタイムでツール実行の進行状況をストリーミングするためのサーバー送信イベントを実装します。
5. TanStack Queryを使用したフロントエンド統合
// components/ToolInterface.tsx
'use client';
import { useMutation } from '@tanstack/react-query';
import { useState, useCallback } from 'react';
import { debounce } from 'es-toolkit/function';
interface ToolResult {
tool: string;
result: any;
duration?: number;
}
export default function ToolInterface() {
const [input, setInput] = useState('');
const [streamedResults, setStreamedResults] = useState<ToolResult[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const streamTools = useCallback(
debounce(async (message: string) => {
setIsStreaming(true);
setStreamedResults([]);
const eventSource = new EventSource(
`/api/stream-tools?message=${encodeURIComponent(message)}`
);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'tool_start':
setStreamedResults(prev => [...prev, {
tool: data.tool,
result: '実行中...',
}]);
break;
case 'tool_complete':
setStreamedResults(prev =>
prev.map(r =>
r.tool === data.tool
? { ...r, result: data.result }
: r
)
);
break;
case 'complete':
setIsStreaming(false);
eventSource.close();
break;
case 'error':
console.error('ストリームエラー:', data.error);
setIsStreaming(false);
eventSource.close();
break;
}
};
return () => eventSource.close();
}, 300),
[]
);
const executeTool = useMutation({
mutationFn: async (message: string) => {
const response = await fetch('/api/tools', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
});
return response.json();
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim()) {
streamTools(input);
}
};
return (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">AIツール実行</h2>
<form onSubmit={handleSubmit}>
<div className="form-control">
<label className="label">
<span className="label-text">何をお手伝いしましょうか?</span>
</label>
<textarea
className="textarea textarea-bordered h-24"
placeholder="144の平方根を計算し、東京の天気をチェック"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
</div>
<div className="form-control mt-4">
<button
type="submit"
className="btn btn-primary"
disabled={isStreaming || !input.trim()}
>
{isStreaming ? (
<>
<span className="loading loading-spinner"></span>
ツール実行中...
</>
) : '実行'}
</button>
</div>
</form>
{streamedResults.length > 0 && (
<div className="mt-6">
<h3 className="text-lg font-semibold mb-3">ツール結果:</h3>
<div className="space-y-3">
{streamedResults.map((result, idx) => (
<div key={idx} className="alert alert-info">
<div>
<span className="font-bold">{result.tool}:</span>
<pre className="mt-2 text-sm">{
typeof result.result === 'object'
? JSON.stringify(result.result, null, 2)
: result.result
}</pre>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
}
サーバー送信イベントを使用したツール実行結果のリアルタイムストリーミングを持つReactコンポーネント。
6. ツール監視ダッシュボード
// app/tools/dashboard/page.tsx
'use client';
import { useQuery } from '@tanstack/react-query';
import { groupBy } from 'es-toolkit/array';
import { useState, useEffect } from 'react';
interface ToolMetrics {
name: string;
calls: number;
avgLatency: number;
successRate: number;
lastUsed: string;
}
export default function ToolDashboard() {
const [metrics, setMetrics] = useState<ToolMetrics[]>([]);
const { data: liveMetrics } = useQuery({
queryKey: ['tool-metrics'],
queryFn: async () => {
const res = await fetch('/api/tools/metrics');
return res.json();
},
refetchInterval: 5000, // 5秒ごとにポーリング
});
useEffect(() => {
if (liveMetrics) {
setMetrics(liveMetrics);
}
}, [liveMetrics]);
const totalCalls = metrics.reduce((sum, m) => sum + m.calls, 0);
const avgSuccessRate = metrics.length > 0
? metrics.reduce((sum, m) => sum + m.successRate, 0) / metrics.length
: 0;
return (
<div className="container mx-auto p-6">
<h1 className="text-4xl font-bold mb-8">ツール監視ダッシュボード</h1>
<div className="stats shadow mb-8">
<div className="stat">
<div className="stat-title">総ツール呼び出し</div>
<div className="stat-value">{totalCalls}</div>
<div className="stat-desc">全ツール合計</div>
</div>
<div className="stat">
<div className="stat-title">平均成功率</div>
<div className="stat-value">{(avgSuccessRate * 100).toFixed(1)}%</div>
<div className="stat-desc">システム信頼性</div>
</div>
<div className="stat">
<div className="stat-title">アクティブツール</div>
<div className="stat-value">{metrics.length}</div>
<div className="stat-desc">現在登録済み</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="table table-zebra w-full">
<thead>
<tr>
<th>ツール名</th>
<th>呼び出し数</th>
<th>平均レイテンシ</th>
<th>成功率</th>
<th>最終使用</th>
<th>状態</th>
</tr>
</thead>
<tbody>
{metrics.map((metric) => (
<tr key={metric.name}>
<td className="font-medium">{metric.name}</td>
<td>{metric.calls}</td>
<td>{metric.avgLatency.toFixed(0)}ms</td>
<td>
<div className="flex items-center gap-2">
<progress
className="progress progress-success w-20"
value={metric.successRate * 100}
max="100"
/>
<span>{(metric.successRate * 100).toFixed(1)}%</span>
</div>
</td>
<td>{new Date(metric.lastUsed).toLocaleString()}</td>
<td>
<div className={`badge ${
metric.successRate > 0.95 ? 'badge-success' :
metric.successRate > 0.8 ? 'badge-warning' :
'badge-error'
}`}>
{metric.successRate > 0.95 ? '健全' :
metric.successRate > 0.8 ? '劣化' :
'クリティカル'}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
ツールのパフォーマンス、成功率、システムヘルスを追跡するリアルタイム監視ダッシュボード。
まとめ
ツール使用パターンは、AIエージェントを受動的な応答者から外部システムと対話できる能動的な実行者に変換します。この実装は、並列実行、エラー回復、動的選択、リアルタイムストリーミングを含むプロダクション対応のパターンを示しています - すべてVercelのサーバーレスプラットフォーム向けに777秒のセーフティバッファーで最適化されています。LangChainの標準化されたツールインターフェース、LangGraphのオーケストレーション機能、es-toolkitのユーティリティ関数の組み合わせにより、プロダクション環境で複雑なマルチステップワークフローを確実に実行できる高度なAIエージェントを構築するための堅牢な基盤が作成されます。