การสร้างลูปสนทนาด้วยเสียง
วงจรบันทึก → ถอดเสียง → ให้เหตุผล → พูด พร้อมการจัดการการขัดจังหวะ
การสร้างลูปสนทนาด้วยเสียง เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
วงจรการสนทนาด้วยเสียง
วงจรการสนทนาด้วยเสียงที่สมบูรณ์จะเชื่อมอินพุตจากไมโครโฟนเข้ากับเอาต์พุตของเอเจนต์อย่างต่อเนื่อง: บันทึกเสียง → ถอดเสียง → เอเจนต์ → TTS → เล่นเสียง → ทำซ้ำ
บทเรียนนี้ครอบคลุมแต่ละองค์ประกอบ รวมถึงความท้าทายที่ทำให้วงจรเสียงแตกต่างจากเอเจนต์แบบใช้ข้อความ ได้แก่ การตรวจจับความเงียบ การจัดการการขัดจังหวะ และการตรวจจับจุดสิ้นสุดของเสียงพูด
การบันทึกเสียงจากไมโครโฟน
ใช้ sounddevice เพื่อบันทึกเสียงจากไมโครโฟนเริ่มต้น คุณสามารถบันทึกเป็นระยะเวลาที่กำหนด หรือตั้งให้หยุดเมื่อระบบตรวจพบความเงียบได้ ควรบันทึกเสียงแบบโมโนที่ 16kHz เสมอ ซึ่งเป็นอัตราการสุ่มตัวอย่างที่ Whisper ต้องการ
ติดตั้งด้วย pip install sounddevice soundfile numpy
import sounddevice as sd
import numpy as np
import tempfile
import soundfile as sf
SAMPLE_RATE = 16000 # 16kHz mono — optimal for Whisper
DURATION = 5 # seconds
def record_audio(duration=DURATION, sample_rate=SAMPLE_RATE):
print(f'Recording for {duration} seconds...')
audio_data = sd.rec(
int(duration * sample_rate),
samplerate=sample_rate,
channels=1,
dtype='float32'
)
sd.wait() # block until recording finishes
print('Recording complete')
return audio_data, sample_rate
def audio_to_file(audio_data, sample_rate, filepath='/tmp/recording.wav'):
sf.write(filepath, audio_data, sample_rate)
return filepathการตรวจจับความเงียบเพื่อระบุจุดสิ้นสุดของเสียงพูด
การบันทึกเป็นระยะเวลาคงที่ไม่สะดวก เพราะผู้ใช้ต้องรอแม้จะพูดจบภายใน 2 วินาที การตรวจจับความเงียบ จะหยุดบันทึกโดยอัตโนมัติเมื่อผู้ใช้เงียบเป็นเวลาถึงเกณฑ์ที่กำหนด
import sounddevice as sd
import numpy as np
from collections import deque
SAMPLE_RATE = 16000
CHUNK_SIZE = 1024 # samples per chunk
SILENCE_THRESHOLD = 0.02 # RMS volume below this = silence
SILENCE_DURATION = 1.5 # seconds of silence to end recording
MAX_DURATION = 30 # max recording length in seconds
def record_until_silence():
chunks = []
silent_chunks = 0
max_silent = int(SILENCE_DURATION * SAMPLE_RATE / CHUNK_SIZE)
max_chunks = int(MAX_DURATION * SAMPLE_RATE / CHUNK_SIZE)
print('Listening... (speak now)')
with sd.InputStream(samplerate=SAMPLE_RATE, channels=1,
blocksize=CHUNK_SIZE, dtype='float32') as stream:
while True:
chunk, _ = stream.read(CHUNK_SIZE)
chunks.append(chunk.copy())
rms = np.sqrt(np.mean(chunk**2))
if rms < SILENCE_THRESHOLD:
silent_chunks += 1
else:
silent_chunks = 0 # speech detected — reset counter
if silent_chunks >= max_silent and len(chunks) > max_silent:
break
if len(chunks) >= max_chunks:
break
audio = np.concatenate(chunks, axis=0)
return audio, SAMPLE_RATEวงจรเสียงแบบสมบูรณ์
เชื่อมองค์ประกอบทั้งหมดเข้าด้วยกันเป็นวงจรต่อเนื่อง หลังจากเอเจนต์ตอบกลับแล้ว ให้เริ่มรับฟังอีกครั้งทันที เพื่อสร้างการสนทนาโต้ตอบไปมาอย่างเป็นธรรมชาติ
import openai
import os
client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
conversation_history = []
def voice_conversation_loop():
print('Voice agent started. Say something (Ctrl+C to exit).')
while True:
# 1. Record user
audio_data, sample_rate = record_until_silence()
audio_path = audio_to_file(audio_data, sample_rate)
# 2. Transcribe
user_text = transcribe_file(audio_path)
print(f'You: {user_text}')
if not user_text.strip():
continue # empty — listen again
# 3. Run agent
conversation_history.append({'role': 'user', 'content': user_text})
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'system', 'content': 'You are a helpful voice assistant. Keep responses concise.'}] + conversation_history[-10:]
)
agent_text = response.choices[0].message.content
conversation_history.append({'role': 'assistant', 'content': agent_text})
print(f'Agent: {agent_text}')
# 4. TTS and play
say(agent_text) # speaks sentence by sentenceแนวคิดการตรวจจับคำปลุก
การรับฟังตลอดเวลาใช้ทรัพยากรมาก (ต้องเรียกใช้ Whisper อย่างต่อเนื่อง) และกระทบความเป็นส่วนตัว การตรวจจับคำปลุก จะใช้โมเดลภายในเครื่องที่มีขนาดเล็กและเริ่มประมวลผลเต็มรูปแบบก็ต่อเมื่อได้ยินวลีที่กำหนด เช่น «Hey Agent»
# Wake word detection with pvporcupine (Picovoice)
# pip install pvporcupine sounddevice
import pvporcupine
import sounddevice as sd
import numpy as np
import os
def listen_for_wake_word(wake_word='computer'):
porcupine = pvporcupine.create(
access_key=os.getenv('PICOVOICE_KEY'),
keywords=[wake_word] # built-in: computer, hey barista, hey google, jarvis...
)
print(f'Listening for wake word: "{wake_word}"...')
with sd.InputStream(
samplerate=porcupine.sample_rate,
channels=1,
dtype='int16',
blocksize=porcupine.frame_length
) as stream:
while True:
frame, _ = stream.read(porcupine.frame_length)
pcm = frame.flatten().astype('int16')
result = porcupine.process(pcm)
if result >= 0:
print(f'Wake word detected!')
porcupine.delete()
return Trueการจัดการการขัดจังหวะ
ผู้ใช้ควรสามารถขัดจังหวะเอเจนต์ระหว่างที่กำลังพูดได้ ให้จัดการการขัดจังหวะโดยเล่นเสียงในเธรดแยก และตรวจสอบระดับเสียงจากไมโครโฟน หากผู้ใช้เริ่มพูด ให้หยุดการเล่นเสียงทันที
import threading
import sounddevice as sd
import numpy as np
interrupt_event = threading.Event()
def monitor_for_interrupt(threshold=0.03):
def audio_callback(indata, frames, time_info, status):
rms = np.sqrt(np.mean(indata**2))
if rms > threshold:
print('Interrupt detected!')
interrupt_event.set()
with sd.InputStream(callback=audio_callback, channels=1,
samplerate=16000, blocksize=1024):
while not interrupt_event.is_set():
sd.sleep(50)
def speak_with_interrupt(text, voice='nova'):
interrupt_event.clear()
# Start interrupt monitor in background
monitor_thread = threading.Thread(target=monitor_for_interrupt, daemon=True)
monitor_thread.start()
# Play audio sentence by sentence
for sentence in split_into_sentences(text):
if interrupt_event.is_set():
print('Playback interrupted')
break
audio_path = cached_tts(sentence, voice)
play_audio_file(audio_path)การกรองเสียงรบกวน
เสียงจากไมโครโฟนมักมีเสียงรบกวนเบื้องหลัง เช่น เสียงพัดลม เสียงแป้นพิมพ์ และเสียงสะท้อนในห้อง ให้ใช้เกตเสียงเพื่อลดเสียงที่ต่ำกว่าเกณฑ์ และเลือกใช้ตัวตรวจจับกิจกรรมเสียง (VAD) เพื่อเพิ่มความแม่นยำ
import numpy as np
NOISE_GATE_THRESHOLD = 0.015 # RMS threshold
def apply_noise_gate(audio_data, threshold=NOISE_GATE_THRESHOLD):
rms = np.sqrt(np.mean(audio_data**2))
if rms < threshold:
return np.zeros_like(audio_data) # silence below threshold
return audio_data
def has_speech_content(audio_data, threshold=0.02):
rms_values = [
np.sqrt(np.mean(audio_data[i:i+1600]**2))
for i in range(0, len(audio_data), 1600)
]
speech_frames = sum(1 for r in rms_values if r > threshold)
speech_ratio = speech_frames / max(len(rms_values), 1)
return speech_ratio > 0.1 # at least 10% of frames have speech
# Use in loop before transcribing
audio, sr = record_until_silence()
if has_speech_content(audio):
text = transcribe_file(audio_to_file(audio, sr))
else:
print('No speech detected — listening again')เครื่องสถานะสำหรับวงจรการสนทนา
วงจรเสียงที่มีความทนทานจะใช้เครื่องสถานะเพื่อติดตามว่าเอเจนต์กำลังทำอะไรอยู่ในแต่ละขณะ และป้องกันภาวะการแข่งกันระหว่างการเล่นเสียงกับการรับฟัง
from enum import Enum
class AgentState(Enum):
IDLE = 'idle'
LISTENING = 'listening'
TRANSCRIBING = 'transcribing'
THINKING = 'thinking'
SPEAKING = 'speaking'
current_state = AgentState.IDLE
def set_state(new_state):
global current_state
print(f'State: {current_state.value} -> {new_state.value}')
current_state = new_state
def voice_loop_with_state():
while True:
set_state(AgentState.LISTENING)
audio, sr = record_until_silence()
if not has_speech_content(audio):
continue
set_state(AgentState.TRANSCRIBING)
text = transcribe_file(audio_to_file(audio, sr))
if not text.strip():
continue
print(f'You: {text}')
set_state(AgentState.THINKING)
agent_response = run_agent(text)
print(f'Agent: {agent_response}')
set_state(AgentState.SPEAKING)
speak_with_interrupt(agent_response)การจัดการบริบทการสนทนา
เอเจนต์เสียงจะเก็บประวัติการสนทนาไว้ เพื่อให้ตอบคำถามต่อเนื่อง เช่น «ช่วยเล่าเรื่องนั้นเพิ่มเติม» หรือ «เมื่อกี้คุณบอกว่าราคาเท่าไร» ให้เก็บประวัติไว้ในหน่วยความจำ และตัดเหลือเฉพาะการโต้ตอบ N ครั้งล่าสุดเพื่อป้องกันบริบทล้น
import openai
import os
client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
MAX_HISTORY_TURNS = 10
SYSTEM_PROMPT = 'You are a concise voice assistant. Keep responses under 3 sentences unless asked for detail.'
class VoiceAgent:
def __init__(self):
self.history = []
def respond(self, user_text):
self.history.append({'role': 'user', 'content': user_text})
# Trim history to last N turns
recent = self.history[-(MAX_HISTORY_TURNS * 2):]
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'system', 'content': SYSTEM_PROMPT}] + recent
)
agent_text = response.choices[0].message.content
self.history.append({'role': 'assistant', 'content': agent_text})
return agent_text
def reset(self):
self.history = []
print('Conversation history cleared')การปิดระบบและการล้างทรัพยากรอย่างเรียบร้อย
จัดการ Ctrl+C และสัญญาณการออกอื่น ๆ อย่างเรียบร้อย โดยหยุดสตรีมเสียง ล้างไฟล์ชั่วคราว และกล่าวลาก่อนปิดระบบ
import signal
import sys
import glob
import os
TMP_DIR = '/tmp'
agent = None
def cleanup_temp_files():
for f in glob.glob(os.path.join(TMP_DIR, 'recording_*.wav')):
os.unlink(f)
print('Temp files cleaned up')
def graceful_shutdown(signum, frame):
print('\nShutting down voice agent...')
if agent:
say('Goodbye! Have a great day.')
cleanup_temp_files()
sys.exit(0)
# Register signal handler
signal.signal(signal.SIGINT, graceful_shutdown)
signal.signal(signal.SIGTERM, graceful_shutdown)
# Main entry point
agent = VoiceAgent()
print('Voice agent ready. Press Ctrl+C to exit.')
voice_loop_with_state()การบันทึกการสนทนา
เก็บบันทึกการสนทนาแบบถาวร ซึ่งประกอบด้วยเวลา ข้อความถอดเสียง คำตอบของเอเจนต์ และข้อผิดพลาดต่าง ๆ สิ่งนี้จำเป็นอย่างยิ่งสำหรับการแก้ไขปัญหาเอเจนต์เสียงและการสร้างฟีเจอร์ตรวจสอบการสนทนา
import json
import os
from datetime import datetime
LOG_DIR = '/tmp/voice_agent_logs'
os.makedirs(LOG_DIR, exist_ok=True)
conversation_log = []
def log_turn(speaker, text, latency_ms=None, error=None):
entry = {
'timestamp': datetime.now().isoformat(),
'speaker': speaker, # 'user' or 'agent'
'text': text,
'latency_ms': latency_ms
}
if error:
entry['error'] = error
conversation_log.append(entry)
def save_conversation_log(session_id=None):
if not session_id:
session_id = datetime.now().strftime('%Y%m%d_%H%M%S')
log_path = os.path.join(LOG_DIR, f'session_{session_id}.json')
with open(log_path, 'w') as f:
json.dump({
'session_id': session_id,
'turns': len(conversation_log),
'log': conversation_log
}, f, indent=2)
print(f'Log saved: {log_path}')
return log_path
# Usage in voice loop
log_turn('user', transcribed_text, latency_ms=420)
log_turn('agent', agent_response, latency_ms=850)ตรวจสอบความเข้าใจ
การตรวจจับความเงียบมีจุดประสงค์อะไรในวงจรการสนทนาด้วยเสียง
สรุปทบทวน: การสร้างวงจรการสนทนาด้วยเสียง
วงจรการสนทนาด้วยเสียง: บันทึกเสียงจากไมโครโฟนพร้อมตรวจจับความเงียบ → ถอดเสียงด้วย Whisper → เรียกใช้เอเจนต์พร้อมประวัติการสนทนา → ตอบกลับด้วย TTS ทีละประโยค → เล่นเสียงพร้อมตรวจสอบการขัดจังหวะ → ทำซ้ำ
องค์ประกอบสำคัญ ได้แก่ เครื่องสถานะเพื่อป้องกันภาวะการแข่งกัน การตรวจจับคำปลุกสำหรับการรับฟังตลอดเวลา เกตเสียงเพื่อคุณภาพเสียง และการตัดประวัติการสนทนาเพื่อจัดการบริบท ให้จัดการ Ctrl+C อย่างเรียบร้อยด้วยการล้างทรัพยากรและข้อความกล่าวลา
คำถามที่พบบ่อย
บทเรียน “การสร้างลูปสนทนาด้วยเสียง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การสร้างลูปสนทนาด้วยเสียง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การสร้างลูปสนทนาด้วยเสียง”
วงจรบันทึก → ถอดเสียง → ให้เหตุผล → พูด พร้อมการจัดการการขัดจังหวะ คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การสร้างลูปสนทนาด้วยเสียง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม
ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การแปลงเสียงพูดเป็นข้อความด้วย Whisper และ Deepgram
- การแปลงข้อความเป็นเสียงพูดในคำตอบของตัวแทน
- การสร้างลูปสนทนาด้วยเสียง
- การปรับเวลาแฝงให้เหมาะที่สุดสำหรับตัวแทนเสียง