02. AI Chat

雑誌Interface(2024/5)の記事を参考にAI Chatを作ってみた。

●AIエンジンの導入
LLM(Large Language Models: 大規模言語モデル)を導入する

Python用LLaMAパッケージのinstall $ sudo pip3 install llama-cpp-python --break-system-packages 以下URLよりLLMデータの'calm2-7b-chat.Q4_K_M.gguf'をdownloadし自作AP用ディレクトリに置く https://huggingface.co/TheBloke/calm2-7B-chat-GGUF/tree/main calm2-7b-chat.Q4_K_M.gguf 7b:7billion(70億パラメータ), Q4:量子化4bit, K_M:Middle size?

●動作確認
雑誌記事にあったショートプログラムで動作確認

chat_test.py -----------------ここから from llama_cpp import Llama llm = Llama(model_path="./calm2-7b-chat.Q4_K_M.gguf") while 1: print("質問してください") text = input() if text == 'end': break prompt = "\nUSER:" + text + "\nASSISTANT:" output = llm( prompt, temperature = 0.1, max_tokens = 128, stop = ["ASSISTANT:","USER:","SYSTEM:"], echo = True, ) print(output["choices"][0]["text"]) print('終了します') -----------------ここまで 上記作成して実行し、chatの動作確認をする
※ブラウザでchatできるように以下でUIを作成する

●node.jsの導入
raspi4で記載したここを参考にnode.js vue/cliを導入する

●index.js
node.jsで作成したディレクトリのindex.jsを作成する

//Import express.js module and create its variable. const express = require('express'); const app = express(); const http = require('http'); const server = http.createServer(app); const { Server } = require("socket.io"); const io = new Server(server); //Import PythonShell module. const {PythonShell} =require('python-shell'); app.use('/ai', express.static('ai')); // imgファイルのディレクトリ指定 app.get('/', (req, res) => { res.sendFile(__dirname + '/index.html'); }); io.on('connection', (socket) => { console.log('a user connected'); socket.on('disconnect', () => { console.log('user disconnected'); }); socket.on('chat message', (msg) => { console.log('message: ' + msg); io.emit('chat message', 'you: ' + msg); if(msg.length > 2){ var pyshell = new PythonShell('./ai/ai-chat.py'); pyshell.send(msg); //pythonコード実施後にpythonからjsにデータが引き渡される。 //pythonに引き渡されるデータは「data」に格納される。 pyshell.on('message', function (data) { console.log(data); io.emit('chat message', "ai: " + data); }); pyshell.on('stderr', (stderr) => { console.log(stderr); }); pyshell.on('error', (error) => { console.log(error); }); pyshell.on('pythonError', (error) => { console.log(error); }); } }); }); server.listen(3000, () => { console.log('listening on *:3000'); });

●index.html
ブラウザ表示用index.htmlを作成する

<!DOCTYPE html> <html> <head> <title>Socket.IO chat</title> <style> body { margin: 10px; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); } #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; } #input:focus { outline: none; } #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; } #messages { list-style-type: none; margin: 0; padding: 0; } /* 吹き出し本体 - 普通の吹き出し */ .you-div{ position: relative; padding: 20px; background-color: #fff3ad; border: 2px solid #ca8888; border-radius: 10px; /* 角丸を指定 */ display: inline-block; /* 横幅を自動で変更 */ text-align: left; } /* アイコンを右に表示 */ .you-div::before{ content: ''; position: absolute; display: block; width: 0; height: 0; right: -15px; top: 20px; border-left: 15px solid #ca8888; border-top: 15px solid transparent; border-bottom: 15px solid transparent; } .you-div::after{ content: ''; position: absolute; display: block; width: 0; height: 0; right: -12px; top: 20px; border-left: 15px solid #fff3ad; border-top: 15px solid transparent; border-bottom: 15px solid transparent; } .you { margin-left: auto; max-width: 50%; text-align: right; } /* 吹き出し本体 - 枠線付きの吹き出し */ .ai{ position: relative; padding: 20px; background-color: #ffadad; border: 2px solid #ca8888; border-radius: 10px; /* 角丸を指定 */ display: inline-block; /* 横幅を自動で変更 */ max-width: 50%; } /* 三角アイコン - 枠線付きの吹き出し */ .ai::before{ content: ''; position: absolute; display: block; width: 0; height: 0; left: -15px; top: 20px; border-right: 15px solid #ca8888; border-top: 15px solid transparent; border-bottom: 15px solid transparent; } .ai::after{ content: ''; position: absolute; display: block; width: 0; height: 0; left: -12px; top: 20px; border-right: 15px solid #ffadad; border-top: 15px solid transparent; border-bottom: 15px solid transparent; } </style> </head> <body> <ul id="messages"></ul> <form id="form" action=""> <input id="input" autocomplete="off" /><button>Send</button> </form> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); const audio = new Audio() var messages = document.getElementById('messages'); var form = document.getElementById('form'); var input = document.getElementById('input'); form.addEventListener('submit', function(e) { // push Send key e.preventDefault(); if (input.value) { socket.emit('chat message', input.value); // message to server input.value = ''; // clear } }); socket.on('chat message', function(msg) { // recieve message from server var regex = /^ai:/g; var item = document.createElement('li'); var msg2 = '' if(regex.test(msg)){ item.className = "ai"; msg2 = msg.replace("ai:", ""); item.innerHTML = msg2; item.value = msg2; console.log(item); audio.src = './ai/test.mp3'; audio.play(); }else{ item.className = "you"; item2 = document.createElement('div'); item2.className = "you-div"; msg2 = msg.replace("you:", ""); item2.textContent = msg2; item.appendChild(item2); } messages.appendChild(item); window.scrollTo(0, document.body.scrollHeight); }); </script> </body> </html>

●音声合成ソフトの導入
ブラウザでAI返答表示と同時に音声で読み上げるために、テキストから音声に変換するソフトを導入する

$ sudo apt-get install open-jtalk $ sudo apt-get install open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001 $ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.8/MMDAgent_Example-1.8.zip $ unzip ./MMDAgent_Example-1.8.zip $ sudo cp -r ./MMDAgent_Example-1.8/Voice/mei /usr/share/hts-voice

●ai-chat.py
chat入力データをAIで解析して応答を返すプログラム(index.jsから呼ばれる)

#!/home/pi/anaconda3/bin/python #-*- encoding: utf-8 -*- import sys import os import re from llama_cpp import Llama import subprocess as proc from pydub import AudioSegment aidir = "/home/pi/pywork/test/ai-chat/ai/" # 音声データの作成 def speak_ojtalk(text): open_jtalk = ['open_jtalk'] mecab_dict = ['-x','/var/lib/mecab/dic/open-jtalk/naist-jdic'] htsvoice = ['-m','/usr/share/hts-voice/mei/mei_normal.htsvoice'] speed = ['-r','0.8'] outwav = ['-ow',aidir+'test.wav'] cmd = open_jtalk + mecab_dict + htsvoice + speed + outwav c = proc.Popen(cmd, stdin = proc.PIPE) c.stdin.write(text.encode()) c.stdin.close() c.wait() AudioSegment.from_wav(aidir + "./test.wav").export(aidir + "./test.mp3", format="mp3") try: os.rename(aidir + 'test.mp3',aidir + 'test.mp3') except: aplay = ['mpg123','-q',aidir + 'test.mp3'] wr = proc.Popen(aplay, stdin = proc.PIPE) wr.stdin.write(text.encode()) wr.stdin.close() wr.wait() # chatの入力を得てAI応答作成 llm = Llama(model_path = aidir + "calm2-7b-chat.Q4_K_M.gguf", chat_format="llama-2", n_ctx=2048) text = sys.stdin.readline().rstrip() # chat入力データ。最後の改行コードは.rstrip()で取り除く prompt = "\nUSER:" + text + "\nASSISTANT:" output = llm( prompt, temperature = 0, max_tokens = 0, seed = 0, stop = ["ASSISTANT:","USER:","SYSTEM:"], echo = False, ) out_text = output["choices"][0]["text"] result = out_text.replace("\n","") speak_ojtalk(result) print(re.sub('\n','<br>',out_text))

●作成したchatのブラウザ画面

※AI機能はfreeで導入でき簡単に利用できたが、応答に時間がかかるのはraspi5の能力の関係で仕方ない。(音声合成は文字数制限が有る)
また、間違った回答をする時も有るが、お試しAIと思えばこれも仕方ない。

●消費電力
取り敢えず消費電力を測ってみた。

Raspberry 5 消費電力
状態消費電力(W)電流(A)
idle時30.08
AI動作中100.16

【参考】

  1. llama-cpp-python
  2. llama.cppのテキスト生成パラメータを調整してみる
  3. CSSだけで吹き出しをつくる!
このページは"Vue.js"を利用してみました。
2024/05/23