feat: add e2e tests, perf benchmarks, and infrastructure improvements
- messages/bench_test.go: serialization benchmarks (msgpack map vs struct vs protobuf) - clients/clients_test.go: HTTP client tests with pooling verification (20 tests) - natsutil/natsutil_test.go: encode/decode roundtrip + binary data tests - handler/handler_test.go: handler dispatch tests + benchmark - config/config.go: live reload via fsnotify + RWMutex getter methods - clients/clients.go: SharedTransport + sync.Pool buffer pooling - messages/messages.go: typed structs with msgpack+json tags - messages/proto/: protobuf schema + generated code Benchmark baseline (ChatRequest roundtrip): MsgpackMap: 2949 ns/op, 36 allocs MsgpackStruct: 2030 ns/op, 13 allocs (31% faster, 64% fewer allocs) Protobuf: 793 ns/op, 8 allocs (73% faster, 78% fewer allocs)
This commit is contained in:
515
messages/bench_test.go
Normal file
515
messages/bench_test.go
Normal file
@@ -0,0 +1,515 @@
|
||||
// Package messages benchmarks compare three serialization strategies:
|
||||
//
|
||||
// 1. msgpack map[string]any — the old approach (dynamic, no types)
|
||||
// 2. msgpack typed struct — the new approach (compile-time safe, short keys)
|
||||
// 3. protobuf — optional future migration
|
||||
//
|
||||
// Run with:
|
||||
//
|
||||
// go test -bench=. -benchmem -count=5 ./messages/... | tee bench.txt
|
||||
// # optional: go install golang.org/x/perf/cmd/benchstat@latest && benchstat bench.txt
|
||||
package messages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
pb "git.daviestechlabs.io/daviestechlabs/handler-base/messages/proto"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Test fixtures — equivalent data across all three encodings
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// chatRequestMap is the legacy map[string]any representation.
|
||||
func chatRequestMap() map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": "req-abc-123",
|
||||
"user_id": "user-42",
|
||||
"message": "What is the capital of France?",
|
||||
"query": "",
|
||||
"premium": true,
|
||||
"enable_rag": true,
|
||||
"enable_reranker": true,
|
||||
"enable_streaming": false,
|
||||
"top_k": 10,
|
||||
"collection": "documents",
|
||||
"enable_tts": false,
|
||||
"system_prompt": "You are a helpful assistant.",
|
||||
"response_subject": "ai.chat.response.req-abc-123",
|
||||
}
|
||||
}
|
||||
|
||||
// chatRequestStruct is the typed struct representation.
|
||||
func chatRequestStruct() ChatRequest {
|
||||
return ChatRequest{
|
||||
RequestID: "req-abc-123",
|
||||
UserID: "user-42",
|
||||
Message: "What is the capital of France?",
|
||||
Premium: true,
|
||||
EnableRAG: true,
|
||||
EnableReranker: true,
|
||||
TopK: 10,
|
||||
Collection: "documents",
|
||||
SystemPrompt: "You are a helpful assistant.",
|
||||
ResponseSubject: "ai.chat.response.req-abc-123",
|
||||
}
|
||||
}
|
||||
|
||||
// chatRequestProto is the protobuf representation.
|
||||
func chatRequestProto() *pb.ChatRequest {
|
||||
return &pb.ChatRequest{
|
||||
RequestId: "req-abc-123",
|
||||
UserId: "user-42",
|
||||
Message: "What is the capital of France?",
|
||||
Premium: true,
|
||||
EnableRag: true,
|
||||
EnableReranker: true,
|
||||
TopK: 10,
|
||||
Collection: "documents",
|
||||
SystemPrompt: "You are a helpful assistant.",
|
||||
ResponseSubject: "ai.chat.response.req-abc-123",
|
||||
}
|
||||
}
|
||||
|
||||
// voiceResponseMap is a voice response with a 16 KB audio payload.
|
||||
func voiceResponseMap() map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": "vr-001",
|
||||
"response": "The capital of France is Paris.",
|
||||
"audio": make([]byte, 16384),
|
||||
"transcription": "What is the capital of France?",
|
||||
}
|
||||
}
|
||||
|
||||
func voiceResponseStruct() VoiceResponse {
|
||||
return VoiceResponse{
|
||||
RequestID: "vr-001",
|
||||
Response: "The capital of France is Paris.",
|
||||
Audio: make([]byte, 16384),
|
||||
Transcription: "What is the capital of France?",
|
||||
}
|
||||
}
|
||||
|
||||
func voiceResponseProto() *pb.VoiceResponse {
|
||||
return &pb.VoiceResponse{
|
||||
RequestId: "vr-001",
|
||||
Response: "The capital of France is Paris.",
|
||||
Audio: make([]byte, 16384),
|
||||
Transcription: "What is the capital of France?",
|
||||
}
|
||||
}
|
||||
|
||||
// ttsChunkMap simulates a streaming audio chunk (~32 KB).
|
||||
func ttsChunkMap() map[string]any {
|
||||
return map[string]any{
|
||||
"session_id": "tts-sess-99",
|
||||
"chunk_index": 3,
|
||||
"total_chunks": 12,
|
||||
"audio_b64": string(make([]byte, 32768)), // old: base64 string
|
||||
"is_last": false,
|
||||
"timestamp": time.Now().Unix(),
|
||||
"sample_rate": 24000,
|
||||
}
|
||||
}
|
||||
|
||||
func ttsChunkStruct() TTSAudioChunk {
|
||||
return TTSAudioChunk{
|
||||
SessionID: "tts-sess-99",
|
||||
ChunkIndex: 3,
|
||||
TotalChunks: 12,
|
||||
Audio: make([]byte, 32768), // new: raw bytes
|
||||
IsLast: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SampleRate: 24000,
|
||||
}
|
||||
}
|
||||
|
||||
func ttsChunkProto() *pb.TTSAudioChunk {
|
||||
return &pb.TTSAudioChunk{
|
||||
SessionId: "tts-sess-99",
|
||||
ChunkIndex: 3,
|
||||
TotalChunks: 12,
|
||||
Audio: make([]byte, 32768),
|
||||
IsLast: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SampleRate: 24000,
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Wire-size comparison (run once, printed by TestWireSize)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestWireSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mapData any
|
||||
structVal any
|
||||
protoMsg proto.Message
|
||||
}{
|
||||
{"ChatRequest", chatRequestMap(), chatRequestStruct(), chatRequestProto()},
|
||||
{"VoiceResponse", voiceResponseMap(), voiceResponseStruct(), voiceResponseProto()},
|
||||
{"TTSAudioChunk", ttsChunkMap(), ttsChunkStruct(), ttsChunkProto()},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
mapBytes, _ := msgpack.Marshal(tt.mapData)
|
||||
structBytes, _ := msgpack.Marshal(tt.structVal)
|
||||
protoBytes, _ := proto.Marshal(tt.protoMsg)
|
||||
|
||||
t.Logf("%-16s map=%5d B struct=%5d B proto=%5d B (struct saves %.0f%%, proto saves %.0f%%)",
|
||||
tt.name,
|
||||
len(mapBytes), len(structBytes), len(protoBytes),
|
||||
100*(1-float64(len(structBytes))/float64(len(mapBytes))),
|
||||
100*(1-float64(len(protoBytes))/float64(len(mapBytes))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Encode benchmarks
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkEncode_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
data := chatRequestMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
data := chatRequestStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_ChatRequest_Protobuf(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_MsgpackMap(b *testing.B) {
|
||||
data := voiceResponseMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_MsgpackStruct(b *testing.B) {
|
||||
data := voiceResponseStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_Protobuf(b *testing.B) {
|
||||
data := voiceResponseProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_MsgpackMap(b *testing.B) {
|
||||
data := ttsChunkMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_MsgpackStruct(b *testing.B) {
|
||||
data := ttsChunkStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_Protobuf(b *testing.B) {
|
||||
data := ttsChunkProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Decode benchmarks
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkDecode_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(chatRequestMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(chatRequestStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m ChatRequest
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_ChatRequest_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(chatRequestProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.ChatRequest
|
||||
proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(voiceResponseMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(voiceResponseStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m VoiceResponse
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(voiceResponseProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.VoiceResponse
|
||||
proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(ttsChunkMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(ttsChunkStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m TTSAudioChunk
|
||||
msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(ttsChunkProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.TTSAudioChunk
|
||||
proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Roundtrip benchmarks (encode + decode)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
data := chatRequestMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := msgpack.Marshal(data)
|
||||
var dec map[string]any
|
||||
msgpack.Unmarshal(enc, &dec)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
data := chatRequestStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := msgpack.Marshal(data)
|
||||
var dec ChatRequest
|
||||
msgpack.Unmarshal(enc, &dec)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_Protobuf(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := proto.Marshal(data)
|
||||
var dec pb.ChatRequest
|
||||
proto.Unmarshal(enc, &dec)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Typed struct unit tests — verify roundtrip correctness
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestRoundtrip_ChatRequest(t *testing.T) {
|
||||
orig := chatRequestStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec ChatRequest
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.RequestID != orig.RequestID {
|
||||
t.Errorf("RequestID = %q, want %q", dec.RequestID, orig.RequestID)
|
||||
}
|
||||
if dec.Message != orig.Message {
|
||||
t.Errorf("Message = %q, want %q", dec.Message, orig.Message)
|
||||
}
|
||||
if dec.TopK != orig.TopK {
|
||||
t.Errorf("TopK = %d, want %d", dec.TopK, orig.TopK)
|
||||
}
|
||||
if dec.Premium != orig.Premium {
|
||||
t.Errorf("Premium = %v, want %v", dec.Premium, orig.Premium)
|
||||
}
|
||||
if dec.EffectiveQuery() != orig.Message {
|
||||
t.Errorf("EffectiveQuery() = %q, want %q", dec.EffectiveQuery(), orig.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_VoiceResponse(t *testing.T) {
|
||||
orig := voiceResponseStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec VoiceResponse
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.RequestID != orig.RequestID {
|
||||
t.Errorf("RequestID mismatch")
|
||||
}
|
||||
if len(dec.Audio) != len(orig.Audio) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
|
||||
}
|
||||
if dec.Transcription != orig.Transcription {
|
||||
t.Errorf("Transcription mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_TTSAudioChunk(t *testing.T) {
|
||||
orig := ttsChunkStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec TTSAudioChunk
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.SessionID != orig.SessionID {
|
||||
t.Errorf("SessionID mismatch")
|
||||
}
|
||||
if dec.ChunkIndex != orig.ChunkIndex {
|
||||
t.Errorf("ChunkIndex = %d, want %d", dec.ChunkIndex, orig.ChunkIndex)
|
||||
}
|
||||
if len(dec.Audio) != len(orig.Audio) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
|
||||
}
|
||||
if dec.SampleRate != orig.SampleRate {
|
||||
t.Errorf("SampleRate = %d, want %d", dec.SampleRate, orig.SampleRate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_PipelineTrigger(t *testing.T) {
|
||||
orig := PipelineTrigger{
|
||||
RequestID: "pip-001",
|
||||
Pipeline: "document-ingestion",
|
||||
Parameters: map[string]any{"source": "s3://bucket/data"},
|
||||
}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec PipelineTrigger
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.Pipeline != orig.Pipeline {
|
||||
t.Errorf("Pipeline = %q, want %q", dec.Pipeline, orig.Pipeline)
|
||||
}
|
||||
if dec.Parameters["source"] != orig.Parameters["source"] {
|
||||
t.Errorf("Parameters[source] mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_STTTranscription(t *testing.T) {
|
||||
orig := STTTranscription{
|
||||
SessionID: "stt-001",
|
||||
Transcript: "hello world",
|
||||
Sequence: 5,
|
||||
IsPartial: false,
|
||||
IsFinal: true,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SpeakerID: "speaker-1",
|
||||
HasVoiceActivity: true,
|
||||
State: "listening",
|
||||
}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec STTTranscription
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.Transcript != orig.Transcript {
|
||||
t.Errorf("Transcript = %q, want %q", dec.Transcript, orig.Transcript)
|
||||
}
|
||||
if dec.IsFinal != orig.IsFinal {
|
||||
t.Error("IsFinal mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_ErrorResponse(t *testing.T) {
|
||||
orig := ErrorResponse{Error: true, Message: "something broke", Type: "InternalError"}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec ErrorResponse
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dec.Error || dec.Message != "something broke" || dec.Type != "InternalError" {
|
||||
t.Errorf("ErrorResponse roundtrip mismatch: %+v", dec)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
ts := Timestamp()
|
||||
now := time.Now().Unix()
|
||||
if ts < now-1 || ts > now+1 {
|
||||
t.Errorf("Timestamp() = %d, expected ~%d", ts, now)
|
||||
}
|
||||
}
|
||||
224
messages/messages.go
Normal file
224
messages/messages.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// Package messages defines typed NATS message structs for all services.
|
||||
//
|
||||
// Using typed structs with short msgpack field tags instead of map[string]any
|
||||
// provides compile-time safety, smaller wire size (integer-like short keys vs
|
||||
// full string keys), and faster encode/decode by avoiding interface{} boxing.
|
||||
//
|
||||
// Audio data uses raw []byte instead of base64-encoded strings — msgpack
|
||||
// supports binary natively, eliminating the 33% base64 overhead.
|
||||
package messages
|
||||
|
||||
import "time"
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Pipeline Bridge
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// PipelineTrigger is the request to start a pipeline.
|
||||
type PipelineTrigger struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
Pipeline string `msgpack:"pipeline" json:"pipeline"`
|
||||
Parameters map[string]any `msgpack:"parameters,omitempty" json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// PipelineStatus is the response / status update for a pipeline run.
|
||||
type PipelineStatus struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
Status string `msgpack:"status" json:"status"`
|
||||
RunID string `msgpack:"run_id,omitempty" json:"run_id,omitempty"`
|
||||
Engine string `msgpack:"engine,omitempty" json:"engine,omitempty"`
|
||||
Pipeline string `msgpack:"pipeline,omitempty" json:"pipeline,omitempty"`
|
||||
SubmittedAt string `msgpack:"submitted_at,omitempty" json:"submitted_at,omitempty"`
|
||||
Error string `msgpack:"error,omitempty" json:"error,omitempty"`
|
||||
AvailablePipelines []string `msgpack:"available_pipelines,omitempty" json:"available_pipelines,omitempty"`
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Chat Handler
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ChatRequest is an incoming chat message.
|
||||
type ChatRequest struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
UserID string `msgpack:"user_id" json:"user_id"`
|
||||
Message string `msgpack:"message" json:"message"`
|
||||
Query string `msgpack:"query,omitempty" json:"query,omitempty"`
|
||||
Premium bool `msgpack:"premium,omitempty" json:"premium,omitempty"`
|
||||
EnableRAG bool `msgpack:"enable_rag,omitempty" json:"enable_rag,omitempty"`
|
||||
EnableReranker bool `msgpack:"enable_reranker,omitempty" json:"enable_reranker,omitempty"`
|
||||
EnableStreaming bool `msgpack:"enable_streaming,omitempty" json:"enable_streaming,omitempty"`
|
||||
TopK int `msgpack:"top_k,omitempty" json:"top_k,omitempty"`
|
||||
Collection string `msgpack:"collection,omitempty" json:"collection,omitempty"`
|
||||
EnableTTS bool `msgpack:"enable_tts,omitempty" json:"enable_tts,omitempty"`
|
||||
SystemPrompt string `msgpack:"system_prompt,omitempty" json:"system_prompt,omitempty"`
|
||||
ResponseSubject string `msgpack:"response_subject,omitempty" json:"response_subject,omitempty"`
|
||||
}
|
||||
|
||||
// EffectiveQuery returns Message or falls back to Query.
|
||||
func (c *ChatRequest) EffectiveQuery() string {
|
||||
if c.Message != "" {
|
||||
return c.Message
|
||||
}
|
||||
return c.Query
|
||||
}
|
||||
|
||||
// ChatResponse is the full reply to a chat request.
|
||||
type ChatResponse struct {
|
||||
UserID string `msgpack:"user_id" json:"user_id"`
|
||||
Response string `msgpack:"response" json:"response"`
|
||||
ResponseText string `msgpack:"response_text" json:"response_text"`
|
||||
UsedRAG bool `msgpack:"used_rag" json:"used_rag"`
|
||||
RAGSources []string `msgpack:"rag_sources,omitempty" json:"rag_sources,omitempty"`
|
||||
Success bool `msgpack:"success" json:"success"`
|
||||
Audio []byte `msgpack:"audio,omitempty" json:"audio,omitempty"`
|
||||
Error string `msgpack:"error,omitempty" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ChatStreamChunk is a single streaming chunk from an LLM response.
|
||||
type ChatStreamChunk struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
Type string `msgpack:"type" json:"type"`
|
||||
Content string `msgpack:"content" json:"content"`
|
||||
Done bool `msgpack:"done" json:"done"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Voice Assistant
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// VoiceRequest is an incoming voice-to-voice request.
|
||||
type VoiceRequest struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
Audio []byte `msgpack:"audio" json:"audio"`
|
||||
Language string `msgpack:"language,omitempty" json:"language,omitempty"`
|
||||
Collection string `msgpack:"collection,omitempty" json:"collection,omitempty"`
|
||||
}
|
||||
|
||||
// VoiceResponse is the reply to a voice request.
|
||||
type VoiceResponse struct {
|
||||
RequestID string `msgpack:"request_id" json:"request_id"`
|
||||
Response string `msgpack:"response" json:"response"`
|
||||
Audio []byte `msgpack:"audio" json:"audio"`
|
||||
Transcription string `msgpack:"transcription,omitempty" json:"transcription,omitempty"`
|
||||
Sources []DocumentSource `msgpack:"sources,omitempty" json:"sources,omitempty"`
|
||||
Error string `msgpack:"error,omitempty" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DocumentSource is a RAG search result source.
|
||||
type DocumentSource struct {
|
||||
Text string `msgpack:"text" json:"text"`
|
||||
Score float64 `msgpack:"score" json:"score"`
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// TTS Module
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// TTSRequest is a text-to-speech synthesis request.
|
||||
type TTSRequest struct {
|
||||
Text string `msgpack:"text" json:"text"`
|
||||
Speaker string `msgpack:"speaker,omitempty" json:"speaker,omitempty"`
|
||||
Language string `msgpack:"language,omitempty" json:"language,omitempty"`
|
||||
SpeakerWavB64 string `msgpack:"speaker_wav_b64,omitempty" json:"speaker_wav_b64,omitempty"`
|
||||
Stream bool `msgpack:"stream,omitempty" json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
// TTSAudioChunk is a streamed audio chunk from TTS synthesis.
|
||||
type TTSAudioChunk struct {
|
||||
SessionID string `msgpack:"session_id" json:"session_id"`
|
||||
ChunkIndex int `msgpack:"chunk_index" json:"chunk_index"`
|
||||
TotalChunks int `msgpack:"total_chunks" json:"total_chunks"`
|
||||
Audio []byte `msgpack:"audio" json:"audio"`
|
||||
IsLast bool `msgpack:"is_last" json:"is_last"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
SampleRate int `msgpack:"sample_rate" json:"sample_rate"`
|
||||
}
|
||||
|
||||
// TTSFullResponse is a non-streamed TTS response (whole audio).
|
||||
type TTSFullResponse struct {
|
||||
SessionID string `msgpack:"session_id" json:"session_id"`
|
||||
Audio []byte `msgpack:"audio" json:"audio"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
SampleRate int `msgpack:"sample_rate" json:"sample_rate"`
|
||||
}
|
||||
|
||||
// TTSStatus is a TTS processing status update.
|
||||
type TTSStatus struct {
|
||||
SessionID string `msgpack:"session_id" json:"session_id"`
|
||||
Status string `msgpack:"status" json:"status"`
|
||||
Message string `msgpack:"message" json:"message"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
}
|
||||
|
||||
// TTSVoiceListResponse is the reply to a voice list request.
|
||||
type TTSVoiceListResponse struct {
|
||||
DefaultSpeaker string `msgpack:"default_speaker" json:"default_speaker"`
|
||||
CustomVoices []TTSVoiceInfo `msgpack:"custom_voices" json:"custom_voices"`
|
||||
LastRefresh int64 `msgpack:"last_refresh" json:"last_refresh"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
}
|
||||
|
||||
// TTSVoiceInfo is summary info about a custom voice.
|
||||
type TTSVoiceInfo struct {
|
||||
Name string `msgpack:"name" json:"name"`
|
||||
Language string `msgpack:"language" json:"language"`
|
||||
ModelType string `msgpack:"model_type" json:"model_type"`
|
||||
CreatedAt string `msgpack:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// TTSVoiceRefreshResponse is the reply to a voice refresh request.
|
||||
type TTSVoiceRefreshResponse struct {
|
||||
Count int `msgpack:"count" json:"count"`
|
||||
CustomVoices []TTSVoiceInfo `msgpack:"custom_voices" json:"custom_voices"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// STT Module
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// STTStreamMessage is any message on the ai.voice.stream.{session} subject.
|
||||
type STTStreamMessage struct {
|
||||
Type string `msgpack:"type" json:"type"`
|
||||
Audio []byte `msgpack:"audio,omitempty" json:"audio,omitempty"`
|
||||
State string `msgpack:"state,omitempty" json:"state,omitempty"`
|
||||
SpeakerID string `msgpack:"speaker_id,omitempty" json:"speaker_id,omitempty"`
|
||||
}
|
||||
|
||||
// STTTranscription is the transcription result published by the STT module.
|
||||
type STTTranscription struct {
|
||||
SessionID string `msgpack:"session_id" json:"session_id"`
|
||||
Transcript string `msgpack:"transcript" json:"transcript"`
|
||||
Sequence int `msgpack:"sequence" json:"sequence"`
|
||||
IsPartial bool `msgpack:"is_partial" json:"is_partial"`
|
||||
IsFinal bool `msgpack:"is_final" json:"is_final"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
SpeakerID string `msgpack:"speaker_id" json:"speaker_id"`
|
||||
HasVoiceActivity bool `msgpack:"has_voice_activity" json:"has_voice_activity"`
|
||||
State string `msgpack:"state" json:"state"`
|
||||
}
|
||||
|
||||
// STTInterrupt is published when the STT module detects a user interrupt.
|
||||
type STTInterrupt struct {
|
||||
SessionID string `msgpack:"session_id" json:"session_id"`
|
||||
Type string `msgpack:"type" json:"type"`
|
||||
Timestamp int64 `msgpack:"timestamp" json:"timestamp"`
|
||||
SpeakerID string `msgpack:"speaker_id" json:"speaker_id"`
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Common / Error
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ErrorResponse is the standard error reply from any handler.
|
||||
type ErrorResponse struct {
|
||||
Error bool `msgpack:"error" json:"error"`
|
||||
Message string `msgpack:"message" json:"message"`
|
||||
Type string `msgpack:"type" json:"type"`
|
||||
}
|
||||
|
||||
// Timestamp returns the current Unix timestamp (helper for message construction).
|
||||
func Timestamp() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
1738
messages/proto/messages.pb.go
Normal file
1738
messages/proto/messages.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
174
messages/proto/messages.proto
Normal file
174
messages/proto/messages.proto
Normal file
@@ -0,0 +1,174 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package messages;
|
||||
|
||||
option go_package = "git.daviestechlabs.io/daviestechlabs/handler-base/messages/proto";
|
||||
|
||||
// ── Pipeline Bridge ─────────────────────────────────────────────────────────
|
||||
|
||||
message PipelineTrigger {
|
||||
string request_id = 1;
|
||||
string pipeline = 2;
|
||||
map<string, string> parameters = 3;
|
||||
}
|
||||
|
||||
message PipelineStatus {
|
||||
string request_id = 1;
|
||||
string status = 2;
|
||||
string run_id = 3;
|
||||
string engine = 4;
|
||||
string pipeline = 5;
|
||||
string submitted_at = 6;
|
||||
string error = 7;
|
||||
repeated string available_pipelines = 8;
|
||||
}
|
||||
|
||||
// ── Chat Handler ────────────────────────────────────────────────────────────
|
||||
|
||||
message ChatRequest {
|
||||
string request_id = 1;
|
||||
string user_id = 2;
|
||||
string message = 3;
|
||||
string query = 4;
|
||||
bool premium = 5;
|
||||
bool enable_rag = 6;
|
||||
bool enable_reranker = 7;
|
||||
bool enable_streaming = 8;
|
||||
int32 top_k = 9;
|
||||
string collection = 10;
|
||||
bool enable_tts = 11;
|
||||
string system_prompt = 12;
|
||||
string response_subject = 13;
|
||||
}
|
||||
|
||||
message ChatResponse {
|
||||
string user_id = 1;
|
||||
string response = 2;
|
||||
string response_text = 3;
|
||||
bool used_rag = 4;
|
||||
repeated string rag_sources = 5;
|
||||
bool success = 6;
|
||||
bytes audio = 7;
|
||||
string error = 8;
|
||||
}
|
||||
|
||||
message ChatStreamChunk {
|
||||
string request_id = 1;
|
||||
string type = 2;
|
||||
string content = 3;
|
||||
bool done = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
// ── Voice Assistant ─────────────────────────────────────────────────────────
|
||||
|
||||
message VoiceRequest {
|
||||
string request_id = 1;
|
||||
bytes audio = 2;
|
||||
string language = 3;
|
||||
string collection = 4;
|
||||
}
|
||||
|
||||
message VoiceResponse {
|
||||
string request_id = 1;
|
||||
string response = 2;
|
||||
bytes audio = 3;
|
||||
string transcription = 4;
|
||||
repeated DocumentSource sources = 5;
|
||||
string error = 6;
|
||||
}
|
||||
|
||||
message DocumentSource {
|
||||
string text = 1;
|
||||
double score = 2;
|
||||
}
|
||||
|
||||
// ── TTS Module ──────────────────────────────────────────────────────────────
|
||||
|
||||
message TTSRequest {
|
||||
string text = 1;
|
||||
string speaker = 2;
|
||||
string language = 3;
|
||||
string speaker_wav_b64 = 4;
|
||||
bool stream = 5;
|
||||
}
|
||||
|
||||
message TTSAudioChunk {
|
||||
string session_id = 1;
|
||||
int32 chunk_index = 2;
|
||||
int32 total_chunks = 3;
|
||||
bytes audio = 4;
|
||||
bool is_last = 5;
|
||||
int64 timestamp = 6;
|
||||
int32 sample_rate = 7;
|
||||
}
|
||||
|
||||
message TTSFullResponse {
|
||||
string session_id = 1;
|
||||
bytes audio = 2;
|
||||
int64 timestamp = 3;
|
||||
int32 sample_rate = 4;
|
||||
}
|
||||
|
||||
message TTSStatus {
|
||||
string session_id = 1;
|
||||
string status = 2;
|
||||
string message = 3;
|
||||
int64 timestamp = 4;
|
||||
}
|
||||
|
||||
message TTSVoiceInfo {
|
||||
string name = 1;
|
||||
string language = 2;
|
||||
string model_type = 3;
|
||||
string created_at = 4;
|
||||
}
|
||||
|
||||
message TTSVoiceListResponse {
|
||||
string default_speaker = 1;
|
||||
repeated TTSVoiceInfo custom_voices = 2;
|
||||
int64 last_refresh = 3;
|
||||
int64 timestamp = 4;
|
||||
}
|
||||
|
||||
message TTSVoiceRefreshResponse {
|
||||
int32 count = 1;
|
||||
repeated TTSVoiceInfo custom_voices = 2;
|
||||
int64 timestamp = 3;
|
||||
}
|
||||
|
||||
// ── STT Module ──────────────────────────────────────────────────────────────
|
||||
|
||||
message STTStreamMessage {
|
||||
string type = 1;
|
||||
bytes audio = 2;
|
||||
string state = 3;
|
||||
string speaker_id = 4;
|
||||
}
|
||||
|
||||
message STTTranscription {
|
||||
string session_id = 1;
|
||||
string transcript = 2;
|
||||
int32 sequence = 3;
|
||||
bool is_partial = 4;
|
||||
bool is_final = 5;
|
||||
int64 timestamp = 6;
|
||||
string speaker_id = 7;
|
||||
bool has_voice_activity = 8;
|
||||
string state = 9;
|
||||
}
|
||||
|
||||
message STTInterrupt {
|
||||
string session_id = 1;
|
||||
string type = 2;
|
||||
int64 timestamp = 3;
|
||||
string speaker_id = 4;
|
||||
}
|
||||
|
||||
// ── Common ──────────────────────────────────────────────────────────────────
|
||||
|
||||
message ErrorResponse {
|
||||
bool error = 1;
|
||||
string message = 2;
|
||||
string type = 3;
|
||||
}
|
||||
Reference in New Issue
Block a user