AI APIを本番環境に導入する際、最大の問題は「動くかどうか」ではなく「どう動いているか」です。私のプロジェクトでは、ECサイトのAIカスタマーサービスにHolySheep AIを採用しましたが、ピーク時に300リクエスト/秒を超えたときのプロンプトの投げ漏れやタイムアウトの可視化が死活問題になりました。本稿では、HolySheep AI APIを活用した堅牢な監視システムの実装方法をハンズオン形式で解説します。

なぜAI API監視が重要なのか

AI API監視を怠ると、以下のようなリスクが発生します:

HolySheep AIは今すぐ登録すれば無料クレジットが手に入り、¥1=$1という業界最安水準の料金体系で運用コストを85%削減できます。しかし、どんなに安いAPIでも監視なしでは単なる出費炸弹です。

監視アーキテクチャの設計

AI API監視は4つの層で構成されます:

実践的な監視システム実装

プロジェクト構成

ai-api-monitor/
├── src/
│   ├── client.ts          # HolySheep AI APIクライアント
│   ├── monitor.ts         # 監視ラッパー
│   ├── metrics.ts         # メトリクス収集
│   └── logger.ts          # 構造化ログ
├── config/
│   └── settings.json
├── package.json
└── tsconfig.json

依存関係のインストール

{
  "dependencies": {
    "axios": "^1.6.0",
    "prom-client": "^15.1.0",
    "winston": "^3.11.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "@types/node": "^20.10.0",
    "ts-node": "^10.9.2"
  }
}
npm install axios prom-client winston dotenv
npm install -D typescript @types/node ts-node

監視付きAPIクライアントの実装

import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
import { Registry, Counter, Histogram, Gauge } from 'prom-client';

// ============================================
// HolySheep AI API監視システム
// base_url: https://api.holysheep.ai/v1
// ============================================

class AIMonitor {
    private client: AxiosInstance;
    private registry: Registry;
    
    // Prometheusメトリクス
    private requestCounter: Counter;
    private latencyHistogram: Histogram;
    private errorCounter: Counter;
    private costGauge: Gauge;
    private tokensGauge: Gauge;
    
    // ログ用
    private requestLog: Array<{
        timestamp: Date;
        model: string;
        inputTokens: number;
        outputTokens: number;
        latencyMs: number;
        status: 'success' | 'error';
        errorType?: string;
        costUsd: number;
    }> = [];
    
    private totalCost: number = 0;
    private totalInputTokens: number = 0;
    private totalOutputTokens: number = 0;
    
    constructor(apiKey: string) {
        this.registry = new Registry();
        
        // 初期化
        this.initMetrics();
        
        // HolySheep AIクライアント設定
        this.client = axios.create({
            baseURL: 'https://api.holysheep.ai/v1',
            headers: {
                'Authorization': Bearer ${apiKey},
                'Content-Type': 'application/json'
            },
            timeout: 30000 // 30秒タイムアウト
        });
        
        // レイテンシー測定用
        this.client.interceptors.request.use((config) => {
            (config.metadata as any).startTime = Date.now();
            return config;
        });
    }
    
    private initMetrics() {
        // リクエストカウンター
        this.requestCounter = new Counter({
            name: 'ai_api_requests_total',
            help: 'Total number of AI API requests',
            labelNames: ['model', 'status'],
            registers: [this.registry]
        });
        
        // レイテンシ分布
        this.latencyHistogram = new Histogram({
            name: 'ai_api_latency_seconds',
            help: 'AI API response latency in seconds',
            labelNames: ['model'],
            buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
            registers: [this.registry]
        });
        
        // エラーカウンター
        this.errorCounter = new Counter({
            name: 'ai_api_errors_total',
            help: 'Total number of AI API errors',
            labelNames: ['model', 'error_type'],
            registers: [this.registry]
        });
        
        // コストゲージ
        this.costGauge = new Gauge({
            name: 'ai_api_total_cost_usd',
            help: 'Total cost in USD',
            registers: [this.registry]
        });
        
        // トークンゲージ
        this.tokensGauge = new Gauge({
            name: 'ai_api_total_tokens',
            help: 'Total tokens used',
            labelNames: ['type'],
            registers: [this.registry]
        });
    }
    
    // HolySheep AI価格表(2026年1月更新)
    private calculateCost(model: string, inputTokens: number, outputTokens: number): number {
        const pricing: Record<string, { input: number; output: number }> = {
            'gpt-4.1': { input: 8, output: 8 },        // $8/MTok
            'claude-sonnet-4.5': { input: 15, output: 15 }, // $15/MTok
            'gemini-2.5-flash': { input: 2.5, output: 2.5 }, // $2.50/MTok
            'deepseek-v3.2': { input: 0.42, output: 0.42 }  // $0.42/MTok
        };
        
        const rates = pricing[model] || { input: 10, output: 10 };
        const inputCost = (inputTokens / 1_000_000) * rates.input;
        const outputCost = (outputTokens / 1_000_000) * rates.output;
        
        return inputCost + outputCost;
    }
    
    async chatCompletion(
        model: string,
        messages: Array<{ role: string; content: string }>,
        options: { maxTokens?: number; temperature?: number } = {}
    ): Promise<{ content: string; usage: any; latencyMs: number; cost: number }> {
        const startTime = Date.now();
        let status: 'success' | 'error' = 'success';
        let errorType: string | undefined;
        
        try {
            const response = await this.client.post('/chat/completions', {
                model,
                messages,
                max_tokens: options.maxTokens || 1024,
                temperature: options.temperature ?? 0.7
            });
            
            const latencyMs = Date.now() - startTime;
            const data = response.data;
            
            const result = {
                content: data.choices[0]?.message?.content || '',
                usage: data.usage,
                latencyMs,
                cost: this.calculateCost(
                    model,
                    data.usage?.prompt_tokens || 0,
                    data.usage?.completion_tokens || 0
                )
            };
            
            // メトリクス更新
            this.requestCounter.inc({ model, status: 'success' });
            this.latencyHistogram.observe({ model }, latencyMs / 1000);
            
            this.totalCost += result.cost;
            this.totalInputTokens += data.usage?.prompt_tokens || 0;
            this.totalOutputTokens += data.usage?.completion_tokens || 0;
            
            this.costGauge.set(this.totalCost);
            this.tokensGauge.set({ type: 'input' }, this.totalInputTokens);
            this.tokensGauge.set({ type: 'output' }, this.totalOutputTokens);
            
            // ログ記録
            this.requestLog.push({
                timestamp: new Date(),
                model,
                inputTokens: data.usage?.prompt_tokens || 0,
                outputTokens: data.usage?.completion_tokens || 0,
                latencyMs,
                status: 'success',
                costUsd: result.cost
            });
            
            console.log(✅ [${model}] Latency: ${latencyMs}ms | Tokens: ${data.usage?.prompt_tokens}/${data.usage?.completion_tokens} | Cost: $${result.cost.toFixed(6)});
            
            return result;
            
        } catch (error) {
            const latencyMs = Date.now() - startTime;
            status = 'error';
            
            const axiosError = error as AxiosError;
            if (axiosError.response) {
                errorType = HTTP_${axiosError.response.status};
            } else if (axiosError.code === 'ECONNABORTED') {
                errorType = 'TIMEOUT';
            } else {
                errorType = 'NETWORK_ERROR';
            }
            
            this.requestCounter.inc({ model, status: 'error' });
            this.errorCounter.inc({ model, error_type: errorType });
            
            this.requestLog.push({
                timestamp: new Date(),
                model,
                inputTokens: 0,
                outputTokens: 0,
                latencyMs,
                status: 'error',
                errorType,
                costUsd: 0
            });
            
            console.error(❌ [${model}] Error: ${errorType} | Latency: ${latencyMs}ms);
            
            throw error;
        }
    }
    
    // Prometheusエンドポイント
    async getMetrics(): Promise<string> {
        return this.registry.metrics();
    }
    
    // 監視ダッシュボードデータ
    getDashboardData() {
        const recentLogs = this.requestLog.slice(-100);
        const successCount = recentLogs.filter(l => l.status === 'success').length;
        const errorCount = recentLogs.filter(l => l.status === 'error').length;
        
        const avgLatency = recentLogs.length > 0
            ? recentLogs.reduce((sum, l) => sum + l.latencyMs, 0) / recentLogs.length
            : 0;
        
        return {
            totalRequests: this.requestLog.length,
            successRate: recentLogs.length > 0 ? (successCount / recentLogs.length * 100).toFixed(2) + '%' : 'N/A',
            errorRate: recentLogs.length > 0 ? (errorCount / recentLogs.length * 100).toFixed(2) + '%' : 'N/A',
            totalCostUsd: this.totalCost.toFixed(6),
            totalInputTokens: this.totalInputTokens,
            totalOutputTokens: this.totalOutputTokens,
            avgLatencyMs: avgLatency.toFixed(2),
            p95LatencyMs: this.calculatePercentile(recentLogs.map(l => l.latencyMs), 95),
            recentErrors: recentLogs.filter(l => l.status === 'error').slice(-10)
        };
    }
    
    private calculatePercentile(values: number[], percentile: number): number {
        if (values.length === 0) return 0;
        const sorted = [...values].sort((a, b) => a - b);
        const index = Math.ceil(percentile / 100 * sorted.length) - 1;
        return sorted[Math.max(0, index)];
    }
}

export { AIMonitor };

実際の監視運用スクリプト

import { AIMonitor } from './src/monitor';
import * as dotenv from 'dotenv';

dotenv.config();

// ============================================
// HolySheep AI 監視システム デモ
// ============================================

async function demo() {
    const monitor = new AIMonitor(process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY');
    
    console.log('🚀 HolySheep AI API監視システム デモ開始\n');
    console.log('═'.repeat(60));
    
    // シナリオ1: ECサイトのAIカスタマーサービス
    console.log('\n📦 シナリオ1: EC AIカスタマーサービス(DeepSeek V3.2)');
    console.log('-'.repeat(40));
    
    const customerQueries = [
        { role: 'system', content: 'あなたは優秀なECサイトの客服です。简潔に回答してください。' },
        { role: 'user', content: '注文した商品の配送状況を教えてください。注文番号: ORD-2024-78945' },
    ];
    
    await monitor.chatCompletion('deepseek-v3.2', customerQueries);
    
    // シナリオ2: RAGシステムでの文脈検索
    console.log('\n📚 シナリオ2: RAG文脈検索(Gemini 2.5 Flash)');
    console.log('-'.repeat(40));
    
    const ragQueries = [
        { role: 'system', content: 'あなたは企业内部文書検索システムです。関連文脈を示してください。' },
        { role: 'user', content: '2024年の売上高成長率と主要市場の実績を教えてください。' },
    ];
    
    await monitor.chatCompletion('gemini-2.5-flash', ragQueries);
    
    // シナリオ3: 高品質コンテンツ生成(Claude Sonnet 4.5)
    console.log('\n✍️ シナリオ3: 高品質記事生成(Claude Sonnet 4.5)');
    console.log('-'.repeat(40));
    
    const contentQueries = [
        { role: 'system', content: 'あなたは专业技术ライターです。准确で詳細な記事を作成してください。' },
        { role: 'user', content: 'AI API監視のベストプラクティスについて300語で解説してください。' },
    ];
    
    await monitor.chatCompletion('claude-sonnet-4.5', contentQueries, { maxTokens: 500 });
    
    // ダッシュボード表示
    console.log('\n' + '='.repeat(60));
    console.log('📊 監視ダッシュボード');
    console.log('='.repeat(60));
    
    const dashboard = monitor.getDashboardData();
    console.log(`
┌─────────────────┬─────────────────────────────────────┐
│ 総リクエスト数   │ ${dashboard.totalRequests.toString().padEnd(35)}│
├─────────────────┼─────────────────────────────────────┤
│ 成功率          │ ${dashboard.successRate.padEnd(35)}│
│ エラー率        │ ${dashboard.errorRate.padEnd(35)}│
├─────────────────┼─────────────────────────────────────┤
│ 総コスト        │ $${dashboard.totalCostUsd.padEnd(34)}│
│ 入力トークン    │ ${dashboard.totalInputTokens.toString().padEnd(35)}│
│ 出力トークン    │ ${dashboard.totalOutputTokens.toString().padEnd(35)}│
├─────────────────┼─────────────────────────────────────┤
│ 平均レイテンシ  │ ${dashboard.avgLatencyMs + 'ms'.padEnd(34)}│
│ P95レイテンシ   │ ${dashboard.p95LatencyMs + 'ms'.padEnd(34)}│
└─────────────────┴─────────────────────────────────────┘`);
    
    if (dashboard.recentErrors.length > 0) {
        console.log('\n⚠️ 最近のエラー:');
        dashboard.recentErrors.forEach((err, i) => {
            console.log(  ${i + 1}. [${err.timestamp.toISOString()}] ${err.model}: ${err.errorType});
        });
    }
    
    // Prometheusメトリクス出力
    console.log('\n📈 Prometheusメトリクス:');
    console.log('-'.repeat(40));
    const metrics = await monitor.getMetrics();
    console.log(metrics);
}

// 連続監視テスト
async function stressTest() {
    console.log('\n💥 連続監視テスト(10リクエスト)\n');
    const monitor = new AIMonitor(process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY');
    
    const startTime = Date.now();
    
    for (let i = 0; i < 10; i++) {
        try {
            await monitor.chatCompletion('deepseek-v3.2', [
                { role: 'user', content: ${i + 1} + 1 = ? }
            ]);
        } catch (e) {
            console.log(リクエスト${i + 1}失敗:, (e as Error).message);
        }
    }
    
    const totalTime = Date.now() - startTime;
    const dashboard = monitor.getDashboardData();
    
    console.log(\n📊 テスト結果:);
    console.log(  - 実行時間: ${totalTime}ms);
    console.log(  - 平均応答時間: ${dashboard.avgLatencyMs}ms);
    console.log(  - 成功率: ${dashboard.successRate});
    console.log(  - 総コスト: $${dashboard.totalCostUsd});
}

// メイン実行
if (require.main === module) {
    demo()
        .then(() => stressTest())
        .catch(console.error)
        .finally(() => {
            console.log('\n✅ 監視システム демо完了');
            console.log('👉 <https://www.holysheep.ai/register|HolySheep AI で監視を始めよう>');
        });
}

export { demo, stressTest };

リアルタイムダッシュボード

import express, { Request, Response } from 'express';
import { AIMonitor } from './monitor';

const app = express();
const monitor = new AIMonitor(process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY');

app.use(express.json());

// ヘルスチェック
app.get('/health', (_req: Request, res: Response) => {
    res.json({ 
        status: 'healthy', 
        timestamp: new Date().toISOString() 
    });
});

// Prometheusメトリクスエンドポイント
app.get('/metrics', async (_req: Request, res: Response) => {
    try {
        res.set('Content-Type', monitor.getMetrics().then(() => 'text/plain'));
        res.send(await monitor.getMetrics());
    } catch (error) {
        res.status(500).send('Metrics error');
    }
});

// ダッシュボードAPI
app.get('/api/dashboard', (_req: Request, res: Response) => {
    res.json(monitor.getDashboardData());
});

// AI Chat API(監視付き)
app.post('/api/chat', async (req: Request, res: Response) => {
    const { model, messages, options } = req.body;
    
    if (!model || !messages) {
        return res.status(400).json({ error: 'model and messages are required' });
    }
    
    try {
        const result = await monitor.chatCompletion(model, messages, options);
        res.json(result);
    } catch (error) {
        const axiosError = error as any;
        res.status(axiosError.response?.status || 500).json({
            error: axiosError.message,
            code: axiosError.code
        });
    }
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
    console.log(`
╔════════════════════════════════════════════════════════╗
║     HolySheep AI API Monitor Server                    ║
╠════════════════════════════════════════════════════════╣
║  Dashboard:  http://localhost:${PORT}/api/dashboard         ║
║  Metrics:   http://localhost:${PORT}/metrics               ║
║  Health:    http://localhost:${PORT}/health                 ║
║  Chat API:  POST http://localhost:${PORT}/api/chat          ║
╚════════════════════════════════════════════════════════╝
    `);
});

export { app };

監視结果的評価基準

HolySheep AIで運用する場合、以下の目標値を設定することをお勧めします:

指標 目標値 警告閾値 критический閾値
成功率 ≥99.5% <99% <95%
P95レイテンシ <500ms >1s >3s
P99レイテンシ <2s >5s >10s
エラー率 <0.5% >1% >5%
コスト增长率 <10%/日 >30%/日 >100%/日

HolySheep AI × 監視の相性

HolySheep AIの<50msレイテンシという特徴は、監視においても大きなメリットになります。私の実践では、DeepSeek V3.2モデル($0.42/MTokという破格の安さ)を平日日中的主力に据え、レイテンシ重要度の高い客服シナリオではGemini 2.5 Flashを選択しています。

特に重要なのは、レート¥1=$1という料金体系によるコスト予測の正確性です。監視システムでトークン消費量を把握できれば、月末の請求額を±5%の範囲で予測でき、予算超過の心配がありません。WeChat PayやAlipayでの支払いに対応しているのも、個人開発者には嬉しいポイントです。

よくあるエラーと対処法

1. 401 Unauthorized - 認証エラー

// ❌ 错误例: 環境変数の読み込み失敗
const apiKey = process.env.API_KEY; // undefined

// ✅ 正しい実装
import 'dotenv/config';

const apiKey = process.env.HOLYSHEEP_API_KEY;
if (!apiKey) {
    throw new Error('HOLYSHEEP_API_KEY is not set in environment variables');
}

// .envファイル確認
// HOLYSHEEP_API_KEY=your_actual_api_key_here

// APIキーの先頭5文字だけログに出力(安全確認用)
console.log(Using API Key: ${apiKey.substring(0, 5)}...);

原因: .envファイルの欠落、または環境変数の設定忘れ
解決: .envファイルを作成してHOLYSHEEP_API_KEYを設定、dotenvをインポートする

2. ECONNABORTED - タイムアウトエラー

// ❌ タイムアウト設定なし(デフォルト30秒だが明示的に)
const client = axios.create({
    baseURL: 'https://api.holysheep.ai/v1',
    timeout: 30000
});

// ✅  модели別タイムアウト設定
const TIMEOUTS: Record<string, number> = {
    'deepseek-v3.2': 15000,      // 15秒
    'gemini-2.5-flash': 10000,   // 10秒
    'claude-sonnet-4.5': 25000,  // 25秒
    'gpt-4.1': 30000             // 30秒
};

async function chatWithTimeout(model: string, messages: any[], timeout?: number) {
    const effectiveTimeout = timeout || TIMEOUTS[model] || 30000;
    
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), effectiveTimeout);
    
    try {
        return await client.post('/chat/completions', {
            model,
            messages
        }, { signal: controller.signal });
    } catch (error: any) {
        if (error.name === 'AbortError') {
            throw new Error(Request timeout after ${effectiveTimeout}ms for model ${model});
        }
        throw error;
    } finally {
        clearTimeout(timeoutId);
    }
}

原因: 長時間実行されるリクエスト、またはネットワーク遅延
解決: モデル別のタイムアウト設定とAbortControllerの実装

3. 429 Too Many Requests - レート制限

// ✅ レート制限対応の指数バックオフ実装
class RateLimitHandler {
    private retryAfter: number = 1000;
    private maxRetries: number = 5;
    
    async executeWithRetry<T>(
        fn: () => Promise<T>,
        attempt: number = 1
    ): Promise<T> {
        try {
            return await fn();
        } catch (error: any) {
            if (error.response?.status === 429 && attempt < this.maxRetries) {
                const retryAfter = error.response?.headers?.['retry-after'];
                const waitTime = retryAfter 
                    ? parseInt(retryAfter) * 1000 
                    : Math.min(this.retryAfter * Math.pow(2, attempt - 1), 30000);
                
                console.log(⚠️ Rate limited. Retrying in ${waitTime}ms (attempt ${attempt + 1}/${this.maxRetries}));
                
                await new Promise(resolve => setTimeout(resolve, waitTime));
                return this.executeWithRetry(fn, attempt + 1);
            }
            throw error;
        }
    }
}

// 使用例
const rateLimiter = new RateLimitHandler();

async function safeChat(model: string, messages: any[]) {
    return rateLimiter.executeWithRetry(() => 
        monitor.chatCompletion(model, messages)
    );
}

原因: 短時間内の大量リクエスト
解決: 指数バックオフによるリトライ、Retry-Afterヘッダの確認

4. Invalid JSON Response - パースエラー

// ❌ レスポンスパース前のアクセス
const response = await client.post('/chat/completions', data);
const content = response.data.choices[0].message.content; // undefinedの恐れ

// ✅ 安全なきめ細かいパース
function safeParseResponse(response: any) {
    try {
        const data = typeof response.data === 'string' 
            ? JSON.parse(response.data) 
            : response.data;
        
        const choice = data?.choices?.[0];
        if (!choice) {
            throw new Error('No choices in response');
        }
        
        const message = choice?.message;
        if (!message) {
            throw new Error('No message in choice');
        }
        
        const content = message?.content;
        if (typeof content !== 'string') {
            throw new Error('Invalid content type: ' + typeof content);
        }
        
        return {
            content,
            usage: data.usage || {},
            model: data.model,
            id: data.id
        };
    } catch (error) {
        console.error('Response parsing error:', error);
        console.log('Raw response:', JSON.stringify(response.data, null, 2));
        throw error;
    }
}

原因: APIの仕様変更、レスポンス形式の不一致
解決: 深い階層でのnull/undefinedチェック、ログ出力によるデバッグ

5. コスト計算の误差

// ❌ 固定レートでの計算
const cost = (tokens / 1000000) * 10; // 常に$10/MTok

// ✅ モデル别料金表を使用
const MODEL_PRICING_2026 = {
    'gpt-4.1': { input: 8, output: 8 },
    'claude-sonnet-4.5': { input: 15, output: 15 },
    'gemini-2.5-flash': { input: 2.5, output: 2.5 },
    'deepseek-v3.2': { input: 0.42, output: 0.42 },
    // フォールバック(未定義モデルの場合)
    'default': { input: 10, output: 10 }
};

function calculateCost(
    model: string, 
    inputTokens: number, 
    outputTokens: number
): number {
    const rates = MODEL_PRICING_2026[model as keyof typeof MODEL_PRICING_2026] 
        || MODEL_PRICING_2026.default;
    
    // 入力トークンは100トークンごとに计数(最小计费单位)
    const billableInput = Math.ceil(inputTokens / 100) * 100;
    const billableOutput = Math.ceil(outputTokens / 100) * 100;
    
    const inputCost = (billableInput / 1_000_000) * rates.input;
    const outputCost = (billableOutput / 1_000_000) * rates.output;
    
    return inputCost + outputCost;
}

// 使用検証
console.log(calculateCost('deepseek-v3.2', 150, 200)); // 入力: $0.000042, 出力: $0.000084
console.log(calculateCost('claude-sonnet-4.5', 150, 200)); // 入力: $0.00015, 出力: $0.0003

原因: API提供側の価格変更への対応漏れ
解決: 設定ファイルまたは環境変数で料金表を管理、定期的な更新

監視ベストプラクティス

  1. リアルタイムアラート設定 — P95レイテンシが閾値超過時にSlack/メール通知
  2. コスト上限設定 — 日次/月次でAPI呼び出し上限を設定し、予算超過を防止
  3. モデル别コスト分析 — 各モデルのROIを可视化して、本当に必要な高コストモデルの利用率を最適化
  4. リトライポリシー — 指数バックオフで一時的障害に対応、最大3回リトライ
  5. ログ保持ポリシー — 詳細なログは7日間保持、アグリゲートデータは1年間保管

まとめ

AI API監視は「動いているからOK」ではなく、「効率的に動いているか」を可視化することが重要です。本稿で示した監視システムを導入すれば、コストの暴走防止、レイテンシ劣化の早期検知、エラー発生時の素早い原因特定が可能になります。

HolySheep AIの<50msレイテンシと業界最安水準の料金を組み合わせれば、監視システム構築のコストさえ気にせず、高品質なAIサービスを維持できます。 注册すればもらえる無料クレジットで試すこともできるため、気軽に監視の導入効果を验证してみてはいかがでしょうか。

👉 HolySheep AI に登録して無料クレジットを獲得