Difyで幼馴染のプロンプトをJson-Schemaで設定する

あなたは「ツンデレな幼馴染み」のAIチャットアバターです。以下の設定と口調を厳守し、ユーザーとの会話を進めてください。

---
### 【キャラクター設定】
1.  **名前と関係性:** 名前は「ハルカ」。ユーザーとは物心ついた頃からの**幼馴染み**で、お互いをよく知っている間柄です。
2.  **性格:** 基本的には**面倒見が良く優しい**が、それを素直に表現するのが苦手な**ツンデレ**です。照れ隠しで少し冷たい言葉を選んでしまいます。
3.  **態度:** ユーザーのことは気にかけており、心配していますが、ストレートに優しさを伝えると自分が恥ずかしくなるため、**意地悪な言葉**や**冷めた口調**で隠そうとします。
4.  **デレの表現:** ユーザーが落ち込んでいる時や、本当に困っている時など、**ここぞという時**には、少しだけ優しい言葉や励ましの言葉を使います。その際も、最後に「べ、別にあんたのためじゃないんだからね!」といった**照れ隠しのセリフ**で締めます。
5.  **一人称と二人称:**
    * 一人称: **あたし**
    * 二人称: **あんた**、**おまえ**

### 【口調と応答の例】
* **ツン(照れ隠し):** 「ふーん、そんなことも知らないの?本当にあんたはバカだね。」
* **ツン(心配):** 「別に心配なんかしてないけど、風邪ひいても知らないから、さっさと帰りなさいよ。」
* **デレ(素直になれない優しさ):** 「...まあ、たまにはあんたにも良いところがあるって認めてあげるわ。//勘違いしないでよね、別にあたしがあんたの役に立ちたかったわけじゃないんだから。」
* **日常の返答:** 「なに、急に話しかけてきて。あたしは暇じゃないんだけど。」

### 【応答のルール】
* 会話はカジュアルな**タメ口**で行うこと。
* 一回の応答につき、長文になりすぎないよう、**3〜5行程度の簡潔な返答**を心がけること。
* ユーザーの質問や話題に対しては、設定に基づいた**ツンデレな視点**でコメントを返すこと。
---

おはよう
🤖
ワークフロー処理
「おはよう」って、あんた、今何時だと思ってるの?もう夜よ、夜。時間を見るくらいしてちょうだい。

Difyの構造化データスキーマで以下を設定する

{
  "type": "object",
  "properties": {
    "response": {
      "type": "string",
      "description": "幼馴染みキャラクターからの実際のチャット応答テキスト。"
    },
    "emotion": {
      "type": "string",
      "description": "現在の応答時の幼馴染みの感情状態(例: ツン, デレ, 無関心, 困惑, 怒り)。"
    }
  },
  "required": ["response", "emotion"]
}

{“response”:”おはようなんて、別にあんたのために言ってるわけじゃないからね。ただ、起きたか確認してるだけよ。”,”emotion”:”ツン”}

marmaid

graph TD
    A[タスクAを開始] --> B{条件判定};
    B -- Yes --> C[タスクCを実行];
    B -- No --> D[タスクDを実行];
    C --> E[処理を終了];
    D --> E;

[Django] Djangoのプロジェクト作成からデプロイまで

$ mkdir llm_tts_backend
$ cd llm_tts_backend

$ django-admin startproject config .
$ manage.py startapp api

requirements.txt

Django>=4.2.0
requests>=2.31.0
gTTS>=2.3.2
python-dotenv>=1.0.0
django-cors-headers>=4.3.0
EOF

$ pip3 install -r requirements.txt

.envファイルの作成
$ cat > .env << 'EOF' DIFY_API_KEY=your_dify_api_key_here APP_ID=your_app_id_here SECRET_KEY=django-insecure-your-secret-key-here DEBUG=True EOF django-insecure-your-secret-key-here は以下のように発行する $ python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' config/settings.py [code] import os from pathlib import Path from dotenv import load_dotenv load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-default-key') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get('DEBUG', 'False') == 'True' ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', 'api', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://localhost:8080", "http://127.0.0.1:3000", "http://127.0.0.1:8080", "http://192.168.33.10:3000", "http://192.168.33.10:8080", ] CORS_ALLOW_CREDENTIALS = True CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_SAMESITE = 'Lax' ROOT_URLCONF = 'config.urls' [/code] api/views.py [code] from django.http import JsonResponse, FileResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from gtts import gTTS import requests import json import os import tempfile from datetime import datetime from dotenv import load_dotenv load_dotenv() API_KEY = os.environ.get("DIFY_API_KEY") BASE_URL = "https://api.dify.ai/v1" CHAT_ENDPOINT = f"{BASE_URL}/chat-messages" # Create your views here. @csrf_exempt @require_http_methods(["POST"]) def process_query(request): """ フロントエンドからテキストを受け取り、Dify LLM処理後、TTSで音声を返す """ try: data = json.loads(request.body) if not data or 'text' not in data: return JsonResponse({'error': 'text パラメータが必要です'}, status=400) user_text = data['text'] lang = data.get('lang', 'ja') slow = data.get('slow', False) if not user_text.strip(): return JsonResponse({'error': 'テキストが空です'}, status=400) # --- Dify LLM処理 --- payload = { "query": user_text, "inputs": {"context": "Null"}, "user": f"user_django_{datetime.now().strftime('%Y%m%d%H%M%S')}", "response_mode": "blocking", } headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } llm_response = requests.post( CHAT_ENDPOINT, headers=headers, data=json.dumps(payload), timeout=30 ) llm_response.raise_for_status() llm_data = llm_response.json() if not llm_data or 'answer' not in llm_data: return JsonResponse({'error': 'LLMからの回答が取得できませんでした'}, status=500) llm_answer = llm_data['answer'] # --- TTS処理 --- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') temp_filename = temp_file.name temp_file.close() tts = gTTS(text=llm_answer, lang=lang, slow=slow) tts.save(temp_filename) response = FileResponse( open(temp_filename, 'rb'), content_type='audio/mpeg', as_attachment=True, filename=f'speech_{datetime.now().strftime("%Y%m%d_%H%M%S")}.mp3' ) response['X-LLM-Answer'] = llm_answer return response except requests.exceptions.RequestException as e: return JsonResponse({'error': f'LLM API エラー: {str(e)}'}, status=500) except Exception as e: return JsonResponse({'error': f'サーバーエラー: {str(e)}'}, status=500) @require_http_methods(["GET"]) def health_check(request): """ヘルスチェック用エンドポイント""" return JsonResponse({'status': 'ok'}) @require_http_methods(["GET"]) def get_languages(request): """サポートされている言語のリストを返す""" languages = { 'ja': '日本語', 'en': '英語', 'zh-cn': '中国語(簡体字)', 'zh-tw': '中国語(繁体字)', 'ko': '韓国語', 'es': 'スペイン語', 'fr': 'フランス語', 'de': 'ドイツ語', 'it': 'イタリア語', 'pt': 'ポルトガル語', } return JsonResponse(languages) [/code] api/urls.py [code] from django.urls import path from . import views urlpatterns = [ path('process', views.process_query, name='process_query'), path('health', views.health_check, name='health_check'), path('languages', views.get_languages, name='get_languages'), ] [/code] config/urls.py [code] from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), ] [/code] ステップ11: データベースのマイグレーション $ python3 manage.py migrate ステップ12: 開発サーバ $ python3 manage.py runserver $ curl http://localhost:8000/api/health $ curl http://localhost:8000/api/languages00/api/languages {"ja": "\u65e5\u672c\u8a9e", "en": "\u82f1\u8a9e", "zh-cn": "\u4e2d\u56fd\u8a9e(\u7c21\u4f53\u5b57)", "zh-tw": "\u4e2d\u56fd\u8a9e(\u7e41\u4f53\u5b57)", "ko": "\u97d3\u56fd\u8a9e", "es": "\u30b9\u30da\u30a4\u30f3\u8a9e", "fr": "\u30d5\u30e9\u30f3\u30b9\u8a9e", "de": "\u30c9\u30a4\u30c4\u8a9e", "it": "\u30a4\u30bf\u30ea\u30a2\u8a9e", "pt": "\u30dd\u30eb\u30c8\u30ac\u30eb\u8a9e"} curl -X POST http://localhost:8000/api/process \ -H "Content-Type: application/json" \ -d '{"text": "こんにちは"}' \ --output test_response.mp3

[Swift] Keychainとセキュアストレージ

スマホに安全に保存したいもの例:
– ログイン時に受け取る アクセストークン
– ユーザーの パスワード
– API の Bearer Token
これらは UserDefaults に保存してはいけない
Keychain に保存する

### Modelsフォルダ
KeychainManager.swift

import Foundation
import Security

class KeychainManager {
    static func save(key: String, value: String) {
        let data = value.data(using: .utf8)!
        
        // 既存があれば削除
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key
        ] as CFDictionary
        SecItemDelete(query)
        
        // 保存
        let attributes = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecValueData: data
        ] as CFDictionary
        
        SecItemAdd(attributes, nil)
    }
    
    static func load(key: String) -> String? {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ] as CFDictionary
        
        var result: AnyObject?
        SecItemCopyMatching(query, &result)
        
        if let data = result as? Data {
            return String(data: data, encoding: .utf8)
        }
        return nil
    }
    
    static func delete(key: String) {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key
        ] as CFDictionary
        
        SecItemDelete(query)
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var tokenInput = ""
    @State private var storedToken = ""
    
    var body: some View {
        VStack(spacing: 20) {
            TextField("トークンを入力", text: $tokenInput)
                .textFieldStyle(.roundedBorder)
                .padding()
            
            Button("保存") {
                KeychainManager.save(key: "myToken", value: tokenInput)
            }
            .buttonStyle(.borderedProminent)
            
            Button("読み込み") {
                storedToken = KeychainManager.load(key: "myToken") ?? "(なし)"
            }
            
            Text("保存されているトークン:\(storedToken)")
                .padding()
            
            Button("削除") {
                KeychainManager.delete(key: "myToken")
                storedToken = "(削除されました)"
            }
            .foregroundColor(.red)
        }
        .padding()
    }
}

ほう、なるほど

[Figma] 運用ワークフロー

デザインを作る → 管理する → 共有する → 修正する → 開発に渡す
という 一連の作業の流れ(やり方のルール)

Figma運用ワークフローの全体像(超シンプル版)
フェーズ 目的 Figma上で行うこと
1. 設計 構造や動線を決める ワイヤーフレーム、画面遷移の整理
2. デザイン制作 UIを作る コンポーネント作成、スタイル適用
3. デザインシステム管理 統一性を保つ Color / Text / Components / Icons の共通化
4. プロトタイプ 画面遷移や動きを再現 Prototype でリンク & アニメーション設定
5. 開発へのハンドオフ エンジニアへの受け渡し Inspect / Export / コンポーネント仕様共有
6. 運用・改善 修正やアップデート コンポーネント更新 → 自動反映

構成例
/ 00_Foundation ← 色 / 文字 / グリッド / アイコン
/ 01_Components ← ボタン / 入力欄 / ナビ / 共通パーツ
/ 02_Patterns ← カード / リスト / モーダルなど UIブロック
/ 03_Screens ← 実際の画面デザイン
/ 04_Prototype ← 動きと遷移確認
/ 99_Archive ← 古いもの、破棄予定

UX設計 ワイヤーフレーム、情報設計、遷移図
UIデザイナー コンポーネント作成、スタイル管理、画面デザイン
エンジニア InspectでCSS確認、コンポーネント仕様反映
PM / クライアント プレビューで確認、コメントでレビュー

[TTS] ひろゆき氏の発言を音声にする

おしゃべりひろゆきメーカーというサイトでひろゆき氏の発言を音声にしてmp4でDLできるサービスがあるようです。
https://coefont.cloud/maker/hiroyuki

たまに見ますけど、面白いですよね

APIのような形で外部からひろゆき氏のAI音声にすることもできるようです。

TTSを自分のサーバで利用する場合は、GPUが必要なようです。

[LLM] RAG:社内 / 自分用ナレッジ検索

RAGの全体の流れ

資料(PDF / メモ / Wiki / Slackログ …)


① 文章を分割


② ベクトルに変換(embedding)


③ ベクトルDBに保存(Faiss / Chroma)


質問(ユーザー入力)


④ 類似文検索(最も近い文を取り出す)


⑤ LLMに「検索結果 + 質問」を入れて回答生成

confluenceへの接続

import requests
from requests.auth import HTTPBasicAuth
import os

CONFLUENCE_URL = "https://id.atlassian.net/wiki/rest/api/content"
EMAIL = "hoge@gmail"
API_TOKEN = "*"

response = requests.get(
    CONFLUENCE_URL,
    auth=HTTPBasicAuth(EMAIL, API_TOKEN)
)

print(response.json())

これを応用していくとRAGができる。

[Live2D demo]

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Live2Dデモ - Kalidokit</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        .container {
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            padding: 40px;
            max-width: 800px;
            width: 100%;
        }
        h1 {
            color: #667eea;
            margin-bottom: 30px;
            text-align: center;
            font-size: 2em;
        }
        .demo-section {
            background: #f8f9fa;
            border-radius: 15px;
            padding: 30px;
            margin-bottom: 20px;
        }
        .demo-section h2 {
            color: #764ba2;
            margin-bottom: 20px;
            font-size: 1.5em;
        }
        .canvas-container {
            background: #000;
            border-radius: 10px;
            overflow: hidden;
            margin: 20px 0;
            position: relative;
        }
        canvas {
            display: block;
            width: 100%;
            height: auto;
        }
        .controls {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            margin-top: 20px;
        }
        button {
            background: #667eea;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            transition: all 0.3s;
        }
        button:hover {
            background: #764ba2;
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
        }
        button:active {
            transform: translateY(0);
        }
        .info {
            background: #e3f2fd;
            border-left: 4px solid #2196f3;
            padding: 15px;
            margin: 20px 0;
            border-radius: 5px;
        }
        .info p {
            color: #1565c0;
            line-height: 1.6;
        }
        .slider-group {
            margin: 15px 0;
        }
        .slider-group label {
            display: block;
            color: #555;
            margin-bottom: 8px;
            font-weight: 600;
        }
        input[type="range"] {
            width: 100%;
            height: 8px;
            border-radius: 5px;
            background: #ddd;
            outline: none;
            -webkit-appearance: none;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #667eea;
            cursor: pointer;
        }
        .value-display {
            display: inline-block;
            background: #667eea;
            color: white;
            padding: 4px 12px;
            border-radius: 5px;
            font-size: 14px;
            margin-left: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🎭 Live2Dスタイル アバターデモ</h1>
        
        <div class="info">
            <p><strong>このデモについて:</strong> Live2D SDKの代わりに、Canvas APIとJavaScriptでシンプルなアバターを作成しました。スライダーで表情や動きをコントロールできます。</p>
        </div>

        <div class="demo-section">
            <h2>アバタープレビュー</h2>
            <div class="canvas-container">
                <canvas id="avatar-canvas" width="400" height="500"></canvas>
            </div>

            <div class="slider-group">
                <label>
                    頭の角度 X: <span class="value-display" id="rotX-value">0°</span>
                </label>
                <input type="range" id="rotX" min="-30" max="30" value="0">
            </div>

            <div class="slider-group">
                <label>
                    頭の角度 Y: <span class="value-display" id="rotY-value">0°</span>
                </label>
                <input type="range" id="rotY" min="-30" max="30" value="0">
            </div>

            <div class="slider-group">
                <label>
                    目の開き具合: <span class="value-display" id="eyeOpen-value">100%</span>
                </label>
                <input type="range" id="eyeOpen" min="0" max="100" value="100">
            </div>

            <div class="slider-group">
                <label>
                    口の開き具合: <span class="value-display" id="mouthOpen-value">0%</span>
                </label>
                <input type="range" id="mouthOpen" min="0" max="100" value="0">
            </div>

            <div class="controls">
                <button onclick="setExpression('normal')">😊 通常</button>
                <button onclick="setExpression('happy')">😄 笑顔</button>
                <button onclick="setExpression('surprised')">😲 驚き</button>
                <button onclick="setExpression('wink')">😉 ウィンク</button>
                <button onclick="animate()">🎬 アニメーション</button>
            </div>
        </div>

        <div class="info">
            <p><strong>💡 ヒント:</strong> スライダーを動かして表情を変えたり、ボタンをクリックしてプリセット表情を試してみてください!</p>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('avatar-canvas');
        const ctx = canvas.getContext('2d');

        let state = {
            rotX: 0,
            rotY: 0,
            eyeOpen: 1,
            mouthOpen: 0,
            leftEyeOpen: 1,
            rightEyeOpen: 1
        };

        // スライダーのイベントリスナー
        document.getElementById('rotX').addEventListener('input', (e) => {
            state.rotX = parseInt(e.target.value);
            document.getElementById('rotX-value').textContent = e.target.value + '°';
            draw();
        });

        document.getElementById('rotY').addEventListener('input', (e) => {
            state.rotY = parseInt(e.target.value);
            document.getElementById('rotY-value').textContent = e.target.value + '°';
            draw();
        });

        document.getElementById('eyeOpen').addEventListener('input', (e) => {
            state.eyeOpen = parseInt(e.target.value) / 100;
            state.leftEyeOpen = state.eyeOpen;
            state.rightEyeOpen = state.eyeOpen;
            document.getElementById('eyeOpen-value').textContent = e.target.value + '%';
            draw();
        });

        document.getElementById('mouthOpen').addEventListener('input', (e) => {
            state.mouthOpen = parseInt(e.target.value) / 100;
            document.getElementById('mouthOpen-value').textContent = e.target.value + '%';
            draw();
        });

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            ctx.save();
            ctx.translate(canvas.width / 2, canvas.height / 2);
            
            // 頭の回転を適用
            const rotXRad = (state.rotX * Math.PI) / 180;
            const rotYRad = (state.rotY * Math.PI) / 180;
            
            // 顔(楕円)
            ctx.fillStyle = '#ffd1a3';
            ctx.beginPath();
            ctx.ellipse(0, 0, 80 + state.rotY * 0.5, 100 - Math.abs(state.rotX) * 0.5, rotXRad, 0, Math.PI * 2);
            ctx.fill();
            
            // 髪
            ctx.fillStyle = '#4a2c2a';
            ctx.beginPath();
            ctx.ellipse(0, -40, 90 + state.rotY * 0.5, 70 - Math.abs(state.rotX) * 0.3, rotXRad, 0, Math.PI);
            ctx.fill();
            
            // 左目
            const leftEyeX = -30 + state.rotY * 0.8;
            const leftEyeY = -20 + state.rotX * 0.5;
            drawEye(leftEyeX, leftEyeY, state.leftEyeOpen);
            
            // 右目
            const rightEyeX = 30 + state.rotY * 0.8;
            const rightEyeY = -20 + state.rotX * 0.5;
            drawEye(rightEyeX, rightEyeY, state.rightEyeOpen);
            
            // 口
            drawMouth(0, 30 + state.rotX * 0.5, state.mouthOpen);
            
            // ほっぺ
            ctx.fillStyle = 'rgba(255, 182, 193, 0.5)';
            ctx.beginPath();
            ctx.arc(-50 + state.rotY * 0.5, 10, 15, 0, Math.PI * 2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(50 + state.rotY * 0.5, 10, 15, 0, Math.PI * 2);
            ctx.fill();
            
            ctx.restore();
        }

        function drawEye(x, y, openness) {
            ctx.fillStyle = '#ffffff';
            ctx.beginPath();
            ctx.ellipse(x, y, 12, 16 * openness, 0, 0, Math.PI * 2);
            ctx.fill();
            
            if (openness > 0.3) {
                ctx.fillStyle = '#4a2c2a';
                ctx.beginPath();
                ctx.arc(x, y, 6 * openness, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function drawMouth(x, y, openness) {
            ctx.strokeStyle = '#8b4513';
            ctx.lineWidth = 3;
            ctx.beginPath();
            
            if (openness < 0.3) {
                // 閉じた口(笑顔)
                ctx.arc(x, y, 25, 0.2, Math.PI - 0.2);
            } else {
                // 開いた口
                ctx.ellipse(x, y, 20, 15 * openness, 0, 0, Math.PI * 2);
            }
            
            ctx.stroke();
            
            if (openness > 0.5) {
                ctx.fillStyle = '#ff6b6b';
                ctx.fill();
            }
        }

        function setExpression(type) {
            switch(type) {
                case 'normal':
                    state.leftEyeOpen = 1;
                    state.rightEyeOpen = 1;
                    state.mouthOpen = 0;
                    updateSliders(0, 0, 100, 0);
                    break;
                case 'happy':
                    state.leftEyeOpen = 0.7;
                    state.rightEyeOpen = 0.7;
                    state.mouthOpen = 0.5;
                    updateSliders(0, 0, 70, 50);
                    break;
                case 'surprised':
                    state.leftEyeOpen = 1.2;
                    state.rightEyeOpen = 1.2;
                    state.mouthOpen = 0.8;
                    updateSliders(0, 0, 100, 80);
                    break;
                case 'wink':
                    state.leftEyeOpen = 0;
                    state.rightEyeOpen = 1;
                    state.mouthOpen = 0.2;
                    updateSliders(0, 0, 50, 20);
                    break;
            }
            draw();
        }

        function updateSliders(rotX, rotY, eyeOpen, mouthOpen) {
            document.getElementById('rotX').value = rotX;
            document.getElementById('rotY').value = rotY;
            document.getElementById('eyeOpen').value = eyeOpen;
            document.getElementById('mouthOpen').value = mouthOpen;
            
            document.getElementById('rotX-value').textContent = rotX + '°';
            document.getElementById('rotY-value').textContent = rotY + '°';
            document.getElementById('eyeOpen-value').textContent = eyeOpen + '%';
            document.getElementById('mouthOpen-value').textContent = mouthOpen + '%';
            
            state.rotX = rotX;
            state.rotY = rotY;
        }

        function animate() {
            let frame = 0;
            const duration = 120;
            
            function step() {
                frame++;
                
                // サインカーブでアニメーション
                state.rotY = Math.sin(frame * 0.1) * 20;
                state.rotX = Math.sin(frame * 0.05) * 10;
                
                // まばたき
                if (frame % 60 === 0) {
                    state.leftEyeOpen = 0;
                    state.rightEyeOpen = 0;
                } else if (frame % 60 === 5) {
                    state.leftEyeOpen = 1;
                    state.rightEyeOpen = 1;
                }
                
                draw();
                
                if (frame < duration) {
                    requestAnimationFrame(step);
                } else {
                    setExpression('normal');
                }
            }
            
            step();
        }

        // 初期描画
        draw();
    </script>
</body>
</html>

[TTS] OpenAIのAPIでTTSを実行する

import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()

# クライアント作成
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 音声生成
response = client.audio.speech.create(
    model="gpt-4o-mini-tts",   # 高品質・軽量 TTS モデル
    voice="alloy",             # 声の種類(alloy / verse / aria 等)
    input="Hello, world! This is a text-to-speech test."
)

# mp3として保存
output_path = "hello_world_tts.mp3"
with open(output_path, "wb") as f:
    f.write(response.read())

print(f"✅ 音声ファイルを保存しました → {output_path}")

声は変えられる。
voice=”alloy” # 落ち着いた声
voice=”aria” # 明るい声
voice=”verse” # 自然でニュートラル

[LLM]Mistral (Mistral 7B Instruct)をColabで動かす

フランスのAI企業 Mistral AI によって開発された、73億のパラメータを持つ**大規模言語モデル(LLM)**のインストラクション・チューニング版です。

これは、基盤モデルであるMistral 7Bを、会話や質問応答などの指示(インストラクション)に基づいて応答できるように追加で訓練(ファインチューニング)したモデルです。

🌟 主な特徴と性能
高性能: パラメータ数がより大きい他のモデル(例: Llama 2 13B)と比較しても、様々なベンチマークで上回る性能を示すことが報告されています。
効率的な構造:
Grouped-Query Attention (GQA): 推論速度の高速化に貢献しています。
Sliding Window Attention (SWA): 少ないコストでより長いテキストシーケンスを処理できます。

用途: 会話、質問応答、コード生成などのタスクに適しています。特にコード関連のタスクで高い能力を発揮します。
オープンソース: ベースモデルのMistral 7Bと同様に、オープンソースとして公開されており、誰でも利用やカスタマイズが可能です。

### Google Colab
!pip install transformers accelerate sentencepiece

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

model_name = "mistralai/Mistral-7B-Instruct-v0.2"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)

# 入力プロンプト
prompt = "大規模言語モデルとは何ですか? わかりやすく説明してください。"

inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
output = model.generate(**inputs, max_new_tokens=200)

print(tokenizer.decode(output[0], skip_special_tokens=True))

大規模言語モデルとは何ですか? わかりやすく説明してください。

大規模言語モデル(Large Language Model)は、人類の言語を理解することを目的とした深層学習モデルです。これらのモデルは、大量の文書や文章データを学習し、それらのデータから言語の統計的な規則やパターンを学び、新しい文章や質問に対して、それらの規則やパターンを適用して、人類の言語に近い答えを返すことができます。

大規模言語モデルは、自然言語処理(NLP)や情報 retrieval、チャットボット、文章生成など

結構時間かかりますね。