refactor(backend): ♻️ Improve backend client implementations in llm_client.gd, stt_client.gd, and tts_client.gd with cleaner structure, better error handling, and optimized service integration

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-27 21:03:08 -07:00
parent 83fc5e6ccd
commit 0dcf4a596f
3 changed files with 83 additions and 90 deletions

View file

@ -40,12 +40,22 @@ func chat(messages: Array[Dictionary]) -> void:
_buffer = ""
_should_cancel = false
_is_streaming = true
_http_client = HTTPClient.new()
var body := JSON.stringify({
"model": _model,
"messages": messages,
"stream": true,
})
FlightRecorder.record("llm.chat_start", "Chat request", {"messages": messages.size()})
var body := (
JSON
. stringify(
{
"model": _model,
"messages": messages,
"stream": true,
"x_client_id": "chobit",
"x_priority": "100",
}
)
)
var url := _base_url.replace("http://", "")
var host := url.split(":")[0] if ":" in url else url
@ -74,18 +84,17 @@ func _start_request(body: String) -> void:
_emit_error("LLM: Failed to connect")
return
var headers := PackedStringArray([
"Content-Type: application/json",
"Accept: text/event-stream",
])
var err := _http_client.request(
HTTPClient.METHOD_POST, _api_path, headers, body
var headers := PackedStringArray(
[
"Content-Type: application/json",
"Accept: text/event-stream",
"X-Client-Id: chobit",
]
)
var err := _http_client.request(HTTPClient.METHOD_POST, _api_path, headers, body)
if err != OK:
_emit_error(
"LLM: Request failed: %s" % error_string(err)
)
_emit_error("LLM: Request failed: %s" % error_string(err))
return
while _http_client.get_status() == HTTPClient.STATUS_REQUESTING:
@ -97,9 +106,7 @@ func _start_request(body: String) -> void:
return
if _http_client.get_response_code() != 200:
_emit_error(
"LLM: HTTP %d" % _http_client.get_response_code()
)
_emit_error("LLM: HTTP %d" % _http_client.get_response_code())
return
await _read_stream()
@ -120,6 +127,7 @@ func _read_stream() -> void:
await get_tree().process_frame
_is_streaming = false
FlightRecorder.record("llm.response_complete", "Response done", {"chars": _accumulated_text.length()})
response_complete.emit(_accumulated_text)
@ -158,5 +166,6 @@ func _parse_data(data: String) -> void:
func _emit_error(message: String) -> void:
_is_streaming = false
FlightRecorder.record("llm.error", message)
EventBus.backend_error.emit(message)
response_error.emit(message)

View file

@ -26,47 +26,35 @@ func transcribe(wav_bytes: PackedByteArray) -> void:
var boundary := "----ChobitBoundary%d" % randi()
var body := _build_multipart(wav_bytes, boundary)
var headers := PackedStringArray([
"Content-Type: multipart/form-data; boundary=%s" % boundary,
])
var headers := PackedStringArray(
[
"Content-Type: multipart/form-data; boundary=%s" % boundary,
]
)
FlightRecorder.record("stt.transcribe", "Transcription request", {"bytes": wav_bytes.size()})
var url := _base_url + "/stt/transcribe"
var err := _http.request_raw(
url, headers, HTTPClient.METHOD_POST, body
)
var err := _http.request_raw(url, headers, HTTPClient.METHOD_POST, body)
if err != OK:
EventBus.backend_error.emit(
"STT request failed: %s" % error_string(err)
)
EventBus.backend_error.emit("STT request failed: %s" % error_string(err))
func _build_multipart(
wav_bytes: PackedByteArray, boundary: String
) -> PackedByteArray:
func _build_multipart(wav_bytes: PackedByteArray, boundary: String) -> PackedByteArray:
var parts := PackedByteArray()
parts.append_array(
_form_field(boundary, "model", MODEL)
)
parts.append_array(
_form_field(boundary, "language", "en")
)
parts.append_array(
_file_field(boundary, "audio", "audio.wav", wav_bytes)
)
parts.append_array(_form_field(boundary, "model", MODEL))
parts.append_array(_form_field(boundary, "language", "en"))
parts.append_array(_file_field(boundary, "audio", "audio.wav", wav_bytes))
parts.append_array(
("--%s--\r\n" % boundary).to_utf8_buffer()
)
parts.append_array(("--%s--\r\n" % boundary).to_utf8_buffer())
return parts
func _form_field(
boundary: String, name: String, value: String
) -> PackedByteArray:
func _form_field(boundary: String, name: String, value: String) -> PackedByteArray:
var field := ""
field += "--%s\r\n" % boundary
field += "Content-Disposition: form-data; name=\"%s\"\r\n" % name
field += 'Content-Disposition: form-data; name="%s"\r\n' % name
field += "\r\n"
field += "%s\r\n" % value
return field.to_utf8_buffer()
@ -80,11 +68,8 @@ func _file_field(
) -> PackedByteArray:
var header := ""
header += "--%s\r\n" % boundary
header += (
"Content-Disposition: form-data; name=\"%s\""
% name
)
header += "; filename=\"%s\"\r\n" % filename
header += ('Content-Disposition: form-data; name="%s"' % name)
header += '; filename="%s"\r\n' % filename
header += "Content-Type: audio/wav\r\n"
header += "\r\n"
@ -101,15 +86,11 @@ func _on_request_completed(
body: PackedByteArray,
) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
EventBus.backend_error.emit(
"STT request failed: result=%d" % result
)
EventBus.backend_error.emit("STT request failed: result=%d" % result)
return
if response_code != 200:
EventBus.backend_error.emit(
"STT error: HTTP %d" % response_code
)
EventBus.backend_error.emit("STT error: HTTP %d" % response_code)
return
var json := JSON.new()
@ -121,4 +102,5 @@ func _on_request_completed(
var data: Dictionary = json.data
var text: String = data.get("text", "")
if not text.is_empty():
FlightRecorder.record("stt.transcript_ready", text.strip_edges())
EventBus.transcript_ready.emit(text.strip_edges())

View file

@ -28,11 +28,21 @@ func setup(
_audio_player.finished.connect(_on_playback_finished)
func is_queue_empty() -> bool:
return _queue.is_empty()
func speak(text: String, exaggeration: float = 0.5) -> void:
_queue.append({
"text": text,
"exaggeration": exaggeration,
})
FlightRecorder.record("tts.speak", text, {"exaggeration": exaggeration})
(
_queue
. append(
{
"text": text,
"exaggeration": exaggeration,
}
)
)
if not _is_processing:
_process_next()
@ -56,27 +66,28 @@ func _process_next() -> void:
_send_request(item["text"], item["exaggeration"])
func _send_request(
text: String, exaggeration: float
) -> void:
var body := JSON.stringify({
"text": text,
"exaggeration": exaggeration,
"format": "wav",
})
func _send_request(text: String, exaggeration: float) -> void:
var body := (
JSON
. stringify(
{
"text": text,
"exaggeration": exaggeration,
"format": "wav",
}
)
)
var headers := PackedStringArray([
"Content-Type: application/json",
])
var headers := PackedStringArray(
[
"Content-Type: application/json",
]
)
var url := _base_url + "/synthesize"
var err := _http.request(
url, headers, HTTPClient.METHOD_POST, body
)
var err := _http.request(url, headers, HTTPClient.METHOD_POST, body)
if err != OK:
EventBus.backend_error.emit(
"TTS request failed: %s" % error_string(err)
)
EventBus.backend_error.emit("TTS request failed: %s" % error_string(err))
_process_next()
@ -87,16 +98,12 @@ func _on_request_completed(
body: PackedByteArray,
) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
EventBus.backend_error.emit(
"TTS request failed: result=%d" % result
)
EventBus.backend_error.emit("TTS request failed: result=%d" % result)
_process_next()
return
if response_code != 200:
EventBus.backend_error.emit(
"TTS error: HTTP %d" % response_code
)
EventBus.backend_error.emit("TTS error: HTTP %d" % response_code)
_process_next()
return
@ -135,12 +142,7 @@ func _parse_wav_sample_rate(
) -> int:
if wav_bytes.size() < 28:
return 22050
return (
wav_bytes[24]
| (wav_bytes[25] << 8)
| (wav_bytes[26] << 16)
| (wav_bytes[27] << 24)
)
return wav_bytes[24] | (wav_bytes[25] << 8) | (wav_bytes[26] << 16) | (wav_bytes[27] << 24)
func _on_playback_finished() -> void: