├── README.md
├── requirements.txt
├── microservice
├── RAG
│ ├── README.md
│ ├── test.py
│ ├── templates
│ │ └── chat.html
│ ├── static
│ │ └── style.css
│ └── app.py
├── ASR
│ ├── test.py
│ ├── README.md
│ └── app.py
└── LLM
│ ├── test.py
│ └── README.md
├── app.py
└── templates
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | https://youtu.be/uyUsmA7xosw?si=0GfhygY_oveeb6iC
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask
2 | Flask-SocketIO
3 | llama-index-core
4 | llama-index-readers-file
5 | llama-index-llms-ollama
6 | llama-index-embeddings-huggingface
--------------------------------------------------------------------------------
/microservice/RAG/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ```sh
5 | pip3 install --no-cache-dir --verbose \
6 | llama-index-core \
7 | llama-index-readers-file \
8 | llama-index-llms-ollama \
9 | llama-index-embeddings-huggingface
10 | ```
--------------------------------------------------------------------------------
/microservice/ASR/test.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | # Flask 服务器的 URL
4 | url = 'http://127.0.0.1:7771//api/get-text'
5 |
6 | # 发送 GET 请求
7 | response = requests.get(url)
8 |
9 | # 检查响应状态码
10 | if response.status_code == 200:
11 | # 获取并打印 JSON 响应内容
12 | data = response.json()
13 | print(data)
14 | else:
15 | print('Failed to retrieve data:', response.status_code)
16 |
--------------------------------------------------------------------------------
/microservice/RAG/test.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | # Flask 应用的 URL
4 | url = 'http://127.0.0.1:7774/api/rag-query'
5 |
6 | # 发送请求的数据
7 | params = {'prompt': 'What wish did Leo make in the end, and why did he choose that particular wish?'}
8 |
9 | # 发起 GET 请求
10 | response = requests.get(url, params=params)
11 |
12 | # 检查响应状态码
13 | if response.status_code == 200:
14 | # 获取并打印 JSON 响应内容
15 | data = response.json()
16 | print(data)
17 | else:
18 | print('Failed to retrieve data:', response.status_code)
19 |
20 |
--------------------------------------------------------------------------------
/microservice/ASR/README.md:
--------------------------------------------------------------------------------
1 | # Automatic Speech Recognition
2 |
3 | In this project, we use [Nvidia Riva](https://www.nvidia.com/en-us/ai-data-science/products/riva/) to implement ASR functionality, which can perform real-time audio transcription.
4 |
5 |
6 | First, please refer to [this document](https://wiki.seeedstudio.com/Local_Voice_Chatbot/#install-riva-server) to install Nvidia Riva Server.
7 |
8 | Then run the following script to start Riva microservice:
9 |
10 | ``` shell
11 | python3 app.py # microservice/ASR/app.py
12 | ```
13 |
14 |
15 |
--------------------------------------------------------------------------------
/microservice/LLM/test.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 |
4 |
5 | url = 'http://localhost:11434/api/generate'
6 |
7 | data = {
8 | "model": "llama3",
9 | "prompt": "Why is the sky blue?"
10 | }
11 |
12 | response = requests.post(url, json=data, stream=True)
13 |
14 | if response.status_code == 200:
15 | for line in response.iter_lines():
16 | if line:
17 | json_data = json.loads(line.decode('utf-8'))
18 | print(json_data)
19 | else:
20 | print("Failed to get valid response, status code:", response.status_code)
21 |
--------------------------------------------------------------------------------
/microservice/RAG/templates/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ChatGPT Interface
7 |
8 |
9 |
10 |
11 |
RAG with Ollama and LlamaIndex
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/microservice/RAG/static/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, sans-serif;
3 | background-color: #333;
4 | color: white;
5 | margin: 0;
6 | padding: 0;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | height: 100vh;
11 | }
12 |
13 | .chat_container {
14 | width: 600px;
15 | text-align: center;
16 | }
17 |
18 | .chatbox input {
19 | width: 80%;
20 | padding: 10px;
21 | margin: 20px 0;
22 | border: none;
23 | border-radius: 4px;
24 | }
25 |
26 | button {
27 | padding: 10px 20px;
28 | border: none;
29 | background-color: #4CAF50;
30 | color: white;
31 | border-radius: 4px;
32 | cursor: pointer;
33 | }
34 |
--------------------------------------------------------------------------------
/microservice/LLM/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 启动本地大模型服务
4 | ```sh
5 | jetson-containers run --name ollama $(autotag ollama)
6 | ```
7 |
8 |
9 | reComputer J4012 with Jetpack 5.1.2
10 | ```sh
11 | docker run --runtime nvidia -it --rm --network host --volume /tmp/argus_socket:/tmp/argus_socket --volume /etc/enctune.conf:/etc/enctune.conf --volume /etc/nv_tegra_release:/etc/nv_tegra_release --volume /tmp/nv_jetson_model:/tmp/nv_jetson_model --volume /var/run/dbus:/var/run/dbus --volume /var/run/avahi-daemon/socket:/var/run/avahi-daemon/socket --volume /var/run/docker.sock:/var/run/docker.sock --volume /home/seeed/aa/microservice/LLM/jetson-containers/data:/data --device /dev/snd --device /dev/bus/usb --name ollama dustynv/ollama:r35.4.1
12 | ```
13 |
14 |
15 |
16 | ```sh
17 | curl http://localhost:11434/api/generate -d '{
18 | "model": "llama2",
19 | "prompt": "Why is the sky blue?"
20 | }'
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/microservice/RAG/app.py:
--------------------------------------------------------------------------------
1 | from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
2 | from llama_index.core.embeddings import resolve_embed_model
3 | from llama_index.llms.ollama import Ollama
4 | from flask import Flask, jsonify, request, render_template
5 |
6 |
7 | class RAG:
8 | def __init__(self):
9 | self.ragapp = Flask(__name__)
10 | self.port=7774
11 | self.setup_routes()
12 |
13 | self.input_dir_doc = '/home/seeed/aa/raw_meeting_transcript'
14 | self.documents = self.read_directory(input_dir=self.input_dir_doc)
15 |
16 | Settings.embed_model = resolve_embed_model("local:BAAI/bge-small-en-v1.5")
17 | Settings.llm = Ollama(model="llama3", request_timeout=120.0)
18 |
19 | self.index = VectorStoreIndex.from_documents(self.documents)
20 | self.query_engine = self.index.as_query_engine()
21 |
22 | def setup_routes(self):
23 | @self.ragapp.route('/')
24 | def index():
25 | return render_template('chat.html')
26 |
27 | @self.ragapp.route('/api/update-doc')
28 | def update_doc():
29 | self.documents = self.read_directory(input_dir=self.input_dir_doc)
30 | self.index = VectorStoreIndex.from_documents(self.documents,)
31 | self.query_engine = self.index.as_query_engine()
32 | return jsonify({"message": "documents update!"})
33 |
34 | @self.ragapp.route('/api/rag-query')
35 | def rag_query():
36 | usr_prompt = request.args.get('prompt', 'Hello!')
37 | print(usr_prompt)
38 | response = self.query_engine.query(usr_prompt)
39 | print(response)
40 | return jsonify({"message": str(response)})
41 |
42 | def run(self):
43 | self.ragapp.run(port=self.port)
44 |
45 | def read_directory(self, input_dir):
46 | reader = SimpleDirectoryReader(input_dir=input_dir)
47 | documents = reader.load_data()
48 | print(f"Loaded {len(documents)} docs from {input_dir}")
49 | return documents
50 |
51 |
52 | if __name__ == '__main__':
53 | re = RAG()
54 | re.run()
55 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import time
2 | import os
3 | import datetime
4 | import webbrowser
5 | import glob
6 | import json
7 | from flask import render_template, Flask, jsonify, request
8 | from flask_socketio import SocketIO
9 | from threading import Thread
10 | import requests
11 |
12 |
13 | class WebUI:
14 | def __init__(self):
15 | # WebUI
16 | self.webapp = Flask(__name__)
17 | # self.socketio = SocketIO(self.webapp)
18 | self.messages = ["hello, this is a test string", "another line"] # a list to cache recorded sentence
19 |
20 | # 创建相应的文件夹
21 | self.recorder_path = '/home/seeed/aa/raw_meeting_transcript'
22 | self.summary_path = '/home/seeed/aa/meeting_summary'
23 | if not os.path.exists(self.recorder_path):
24 | os.makedirs(self.recorder_path)
25 | print(f"Folder '{self.recorder_path}' was created.")
26 | else:
27 | print(f"Folder '{self.recorder_path}' already exists.")
28 | if not os.path.exists(self.summary_path):
29 | os.makedirs(self.summary_path)
30 | print(f"Folder '{self.summary_path}' was created.")
31 | else:
32 | print(f"Folder '{self.summary_path}' already exists.")
33 |
34 | # ASR
35 | self.asr_server_url = 'http://127.0.0.1:7771'
36 | self.asr_flag = 0
37 |
38 | # LLM
39 | self.llm_url = 'http://localhost:11434/api/generate'
40 |
41 | self.save_record_name = None
42 | self.webapp_thread = None
43 | self.setup_routes()
44 |
45 | def setup_routes(self):
46 | @self.webapp.route('/')
47 | def index():
48 | return render_template('index.html')
49 |
50 | @self.webapp.route('/chat-rag', methods=['POST'])
51 | def chat_rag():
52 | data = request.get_json()
53 |
54 | url = 'http://127.0.0.1:7774/api/rag-query'
55 | params = {'prompt': data['text']}
56 |
57 | response = requests.get(url, params=params)
58 |
59 | if response.status_code == 200:
60 | rst = response.json()
61 | print(rst)
62 | else:
63 | print('Failed to retrieve data:', response.status_code)
64 |
65 | return jsonify(response=rst['message'])
66 |
67 | @self.webapp.route('/get_recorder_messages', methods=['GET'])
68 | def get_messages():
69 | response = requests.get('http://127.0.0.1:7771/api/get-text')
70 | if response.status_code == 200:
71 | data = response.json()
72 | if self.asr_flag == 1:
73 | if data['content'] != self.messages[-1] and data['content'] not in self.messages:
74 | self.messages[-1] = data['content']
75 |
76 | if data['final'] is True and self.messages[-1] != ' ':
77 | self.messages.append(' ')
78 | else:
79 | print('Failed to retrieve data:', response.status_code)
80 | return jsonify(self.messages)
81 |
82 | @self.webapp.route('/btn-start-record', methods=['POST'])
83 | def btn_start_record():
84 | self.messages.clear()
85 | self.messages.append('')
86 | self.asr_flag = 1
87 | return '', 200
88 |
89 | @self.webapp.route('/btn-stop-record', methods=['POST'])
90 | def btn_stop_record():
91 | self.asr_flag = 0
92 | return '', 200
93 |
94 | @self.webapp.route('/save-record', methods=['POST'])
95 | def save_record():
96 | now = datetime.datetime.now()
97 | formatted_time = now.strftime('%Y-%m-%d_%H-%M-%S')
98 | self.save_record_name = f'record_{formatted_time}.txt'
99 | print(f'{self.recorder_path}/{self.save_record_name}')
100 | with open(f'{self.recorder_path}/{self.save_record_name}', 'w') as file:
101 | for item in self.messages:
102 | file.write(item + '\n')
103 | return '', 200
104 |
105 | @self.webapp.route('/gen-summary', methods=['POST'])
106 | def gen_summary():
107 | self.asr_flag = 0
108 |
109 | file_path = self.get_latest_txt_file(self.recorder_path)
110 | print(file_path)
111 | with open(file_path, 'r', encoding='utf-8') as file:
112 | content = file.read()
113 |
114 | data = {
115 | "model": "llama3",
116 | "prompt": f"Please summarise this meeting transcript:{content}"
117 | }
118 | response = requests.post(self.llm_url, json=data, stream=True)
119 | if response.status_code == 200:
120 | self.messages.clear()
121 | self.messages.append('')
122 | for line in response.iter_lines():
123 | if line:
124 | json_data = json.loads(line.decode('utf-8'))
125 | self.messages[-1] += json_data['response']
126 | print(json_data)
127 | else:
128 | print("Failed to get valid response, status code:", response.status_code)
129 | return '', 200
130 |
131 | @self.webapp.route('/save-summary', methods=['POST'])
132 | def save_summary():
133 | self.asr_flag = 0
134 | record_file_path = self.get_latest_txt_file(self.recorder_path)
135 | filename = os.path.basename(record_file_path)
136 | filename = 'summary_' + filename
137 | save_path = os.path.join(self.summary_path, filename)
138 | print(save_path)
139 |
140 | with open(save_path, 'w') as file:
141 | for item in self.messages:
142 | file.write(item + '\n')
143 |
144 | return '', 200
145 |
146 | @self.webapp.route('/get_messages', methods=['GET'])
147 | def get_messages1():
148 | return jsonify(self.messages)
149 |
150 | def run(self):
151 | self.webapp.run()
152 |
153 | def get_asr_message(self):
154 | url = self.asr_server_url + '/api/get-text'
155 | while True:
156 | response = requests.get(url)
157 | if response.status_code == 200:
158 | data = response.json()
159 | if self.asr_flag == 1:
160 | if data['content'] != self.messages[-1] and data['content'] not in self.messages:
161 | self.messages[-1] = data['content']
162 | if data['final'] is True and self.messages[-1] != '':
163 | self.messages.append('')
164 | # print(self.messages[-1])
165 | else:
166 | print('Failed to retrieve data:', response.status_code)
167 | time.sleep(1)
168 |
169 | @staticmethod
170 | def get_latest_txt_file(directory):
171 | txt_files = {}
172 |
173 | for filename in os.listdir(directory):
174 | if filename.endswith('.txt'):
175 | full_path = os.path.join(directory, filename)
176 | mod_time = os.path.getmtime(full_path)
177 | txt_files[full_path] = mod_time
178 |
179 | if txt_files:
180 | latest_file = max(txt_files, key=txt_files.get)
181 | return latest_file
182 | else:
183 | return None
184 |
185 | if __name__ == '__main__':
186 | re = WebUI()
187 | re.run()
188 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Chatbot Interface
7 |
8 |
9 |
10 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/microservice/ASR/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | import time
4 | import threading
5 | import pyaudio
6 | from flask import Flask, jsonify, request, Response, stream_with_context
7 | import riva.client
8 | import riva.client.audio_io
9 |
10 |
11 | class RivaBase(threading.Thread):
12 |
13 | def __init__(self, auth=None, input_device=None, sample_rate_hz=16000, audio_chunk=1600, audio_channels=1,
14 | automatic_punctuation=True, verbatim_transcripts=True, profanity_filter=False,
15 | language_code='en-US', boosted_lm_words=None, boosted_lm_score=4.0, callback=None):
16 | super(RivaBase, self).__init__()
17 |
18 | assert auth is not None, f"Invalid parameter: {auth}"
19 | self.asr_service = riva.client.ASRService(auth)
20 |
21 | self.input_device = input_device
22 | self.sample_rate_hz = sample_rate_hz
23 | self.audio_chunk = audio_chunk
24 | self.callback = callback
25 | if self.callback is None:
26 | self.callback = self.callback_example
27 |
28 | self.asr_config = riva.client.StreamingRecognitionConfig(
29 | config=riva.client.RecognitionConfig(
30 | encoding=riva.client.AudioEncoding.LINEAR_PCM,
31 | language_code=language_code,
32 | max_alternatives=1,
33 | profanity_filter=profanity_filter,
34 | enable_automatic_punctuation=automatic_punctuation,
35 | verbatim_transcripts=verbatim_transcripts,
36 | sample_rate_hertz=sample_rate_hz,
37 | audio_channel_count=audio_channels,
38 | ),
39 | interim_results=True,
40 | )
41 |
42 | riva.client.add_word_boosting_to_config(self.asr_config, boosted_lm_words, boosted_lm_score)
43 | self.mute_flag = False
44 | self.stop_flag = False
45 |
46 | def run(self):
47 | try:
48 | with riva.client.audio_io.MicrophoneStream(
49 | self.sample_rate_hz,
50 | self.audio_chunk,
51 | device=self.input_device,
52 | ) as audio_chunk_iterator:
53 | responses = self.asr_service.streaming_response_generator(
54 | audio_chunks=audio_chunk_iterator, streaming_config=self.asr_config
55 | )
56 | for response in responses:
57 | if self.stop_flag:
58 | audio_chunk_iterator.close()
59 | if self.get_mute_state():
60 | continue
61 | else:
62 | if not response.results:
63 | continue
64 | self.callback(response)
65 | print('ASR Server Stop!')
66 | except:
67 | sys.exit(0)
68 |
69 | @staticmethod
70 | def callback_example(response):
71 | try:
72 | for result in response.results:
73 | if not result.alternatives:
74 | continue
75 | transcript = result.alternatives[0].transcript
76 | if result.is_final:
77 | print("## " + transcript)
78 | print(f"Confidence:{result.alternatives[0].confidence:9.4f}" + "\n")
79 | return True
80 | else:
81 | print(">> " + transcript)
82 | print(f"Stability:{result.stability:9.4f}" + "\n")
83 | finally:
84 | pass
85 |
86 | def get_mute_state(self):
87 | return self.mute_flag
88 |
89 | @staticmethod
90 | def list_devices():
91 | riva.client.audio_io.list_input_devices()
92 |
93 |
94 | class ASR_Riva:
95 | def __init__(self, input_device=None, sample_rate_hz=None):
96 | self.asrapp = Flask(__name__)
97 | self.port=7771
98 | self.setup_routes()
99 |
100 | self.riva_server = "127.0.0.1:50051"
101 |
102 | if input_device is None:
103 | target_device = self.get_asr_devices_list()
104 | input_device = target_device['index']
105 | sample_rate_hz = int(target_device['defaultSampleRate'])
106 | print(f"Microphone parameters : {input_device}, {sample_rate_hz}")
107 |
108 | auth = riva.client.Auth(uri=self.riva_server)
109 | self.asr = RivaBase(auth, input_device, sample_rate_hz, callback=self.asr_callback)
110 | self.asr.start()
111 |
112 | self.text_cache = []
113 | self.text_stream = {'content':'', 'final': True, 'push': True, 'asr_state': self.asr.stop_flag}
114 |
115 | def asr_callback(self, response):
116 | try:
117 |
118 | for result in response.results:
119 | if not result.alternatives:
120 | continue
121 | transcript = result.alternatives[0].transcript
122 |
123 | if result.is_final:
124 | print("## " + transcript)
125 | print(f"Confidence:{result.alternatives[0].confidence:9.4f}" + "\n")
126 | self.text_stream['content'] = transcript
127 | self.text_stream['final'] = True
128 | self.text_stream['push'] = False
129 | self.text_cache.append(self.text_stream)
130 | if len(self.text_cache) > 100:
131 | del self.text_cache[:50]
132 | else:
133 | if result.stability > 0.8:
134 | print(">> " + transcript)
135 | print(f"Stability:{result.stability:9.4f}" + "\n")
136 | self.text_stream['content'] = transcript
137 | self.text_stream['final'] = False
138 | self.text_stream['push'] = False
139 | finally:
140 | pass
141 |
142 | def setup_routes(self):
143 |
144 | @self.asrapp.route('/api/get-text')
145 | def get_text():
146 | self.text_stream['push'] = True
147 | return jsonify(self.text_stream)
148 |
149 | @self.asrapp.route('/api/get-cache')
150 | def get_cache():
151 | return jsonify(self.text_cache[-1])
152 |
153 | @self.asrapp.route('/api/asr-stop')
154 | def asr_stop():
155 | self.asr.stop_flag=True
156 | print('Stop!')
157 | return jsonify({"response": 'stop'})
158 |
159 | @self.asrapp.route('/api/asr-start')
160 | def update_doc():
161 | if self.asr.stop_flag == True:
162 | self.asr.stop_flag=False
163 | self.asr.start()
164 | print('Start!')
165 | return jsonify({"response": 'start'})
166 | else:
167 | return jsonify({"response": 'running'})
168 |
169 | @self.asrapp.route('/stream')
170 | def stream():
171 | def generate():
172 | while True:
173 | if self.text_stream['push'] is False:
174 | yield self.text_stream['content'] + '\n'
175 | time.sleep(1)
176 | return Response(stream_with_context(generate()), mimetype='text/plain')
177 |
178 |
179 | def run(self):
180 | self.asrapp.run(port=self.port)
181 |
182 | @staticmethod
183 | def get_asr_devices_list():
184 | target_device = None
185 | p = pyaudio.PyAudio()
186 | print("Input audio devices:")
187 | for i in range(p.get_device_count()):
188 | info = p.get_device_info_by_index(i)
189 | if info['maxInputChannels'] < 1:
190 | continue
191 | if 'USB' in info["name"]:
192 | target_device = info
193 | print(f"{info['index']}: {info['name']}")
194 | p.terminate()
195 | if target_device is None:
196 | print('No available input device found, please manually config an device.')
197 | sys.exit(0)
198 | else:
199 | return target_device
200 |
201 | if __name__ == '__main__':
202 | re = ASR_Riva()
203 | re.run()
204 |
--------------------------------------------------------------------------------