●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時 | 3 | 0.08 |
AI動作中 | 10 | 0.16 |