@ -24,6 +24,8 @@ type Config struct {
MiniMaxApiKey string
MiniMaxApiURL string
FILE_URL string
LLMOurApiUrl string
LLMOurApiKey string
}
// LLMService handles communication with the LLM API
@ -51,6 +53,7 @@ type RequestPayload struct {
ConversationID string ` json:"conversation_id" `
Files [ ] interface { } ` json:"files" `
Audio string ` json:"audio" `
LlmType string ` json:"llm_type" `
}
// VoiceSetting represents voice configuration
@ -112,6 +115,18 @@ type SpeechResponse struct {
BaseResp BaseResponse ` json:"base_resp" `
}
type LLMOurMessage struct {
Role string ` json:"role" `
Content string ` json:"content" `
}
type LLMOurRequestPayload struct {
Model string ` json:"model" `
Stream bool ` json:"stream" `
StreamOptions map [ string ] interface { } ` json:"stream_options" `
Messages [ ] LLMOurMessage ` json:"messages" `
}
// NewLLMService creates a new instance of LLMService
func NewLLMService ( config Config ) * LLMService {
return & LLMService {
@ -130,6 +145,7 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
ConversationID : getString ( data , "conversation_id" ) ,
Files : make ( [ ] interface { } , 0 ) ,
Audio : getString ( data , "audio" ) ,
LlmType : getString ( data , "llm_type" ) ,
}
fmt . Printf ( "前端传来的数据:%+v\n" , payload )
@ -138,12 +154,51 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
return nil , fmt . Errorf ( "error marshaling payload: %v" , err )
}
req , err := http . NewRequest ( "POST" , s . config . LLMApiURL + "/chat-messages" , bytes . NewBuffer ( jsonData ) )
// req, err := http.NewRequest("GET", "http://localhost:8080/stream-text", nil)
currentUrl := s . config . LLMApiURL + "/chat-messages"
fmt . Println ( currentUrl )
req := & http . Request { }
if payload . LlmType == "ours" {
// 动态构造 messages
var messages [ ] LLMOurMessage
if msgs , ok := data [ "messages" ] ; ok {
if arr , ok := msgs . ( [ ] interface { } ) ; ok {
for _ , m := range arr {
if mMap , ok := m . ( map [ string ] interface { } ) ; ok {
role , _ := mMap [ "role" ] . ( string )
content , _ := mMap [ "content" ] . ( string )
messages = append ( messages , LLMOurMessage { Role : role , Content : content } )
}
}
}
}
// fallback: 如果没有 messages, 则用 query 作为 user 消息
if len ( messages ) == 0 && payload . Query != "" {
messages = append ( messages , LLMOurMessage { Role : "user" , Content : payload . Query } )
}
ourPayload := LLMOurRequestPayload {
Model : "bot-20250522162100-44785" , // 可根据 data 或配置传入
Stream : true ,
StreamOptions : map [ string ] interface { } { "include_usage" : true } ,
Messages : messages ,
}
jsonData , err = json . Marshal ( ourPayload )
if err != nil {
return nil , fmt . Errorf ( "error marshaling ourPayload: %v" , err )
}
currentUrl = s . config . LLMOurApiUrl
req , err = http . NewRequest ( "POST" , currentUrl , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , fmt . Errorf ( "error creating request: %v" , err )
}
req . Header . Set ( "Authorization" , "Bearer " + s . config . LLMOurApiKey )
req . Header . Set ( "Content-Type" , "application/json" )
return s . handleStreamingResponseV2 ( req , data , payload . Audio )
}
req , err = http . NewRequest ( "POST" , currentUrl , bytes . NewBuffer ( jsonData ) )
if err != nil {
return nil , fmt . Errorf ( "error creating request: %v" , err )
}
req . Header . Set ( "Authorization" , "Bearer " + s . config . LLMApiKey )
req . Header . Set ( "Content-Type" , "application/json" )
@ -155,6 +210,135 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
return s . handleNonStreamingResponse ( req )
}
// processStreamSegment 处理流式文本分段、语音合成等逻辑,返回 new_message、audio、是否需要发送
func ( s * LLMService ) processStreamSegment ( initialSessage * string , all_message * string , answer string , audio_type string ) ( string , string , bool ) {
// 定义标点符号map
punctuations := map [ string ] bool {
"," : true , ", " : true , // 逗号
"." : true , "。" : true , // 句号
"!" : true , "! " : true , // 感叹号
"?" : true , "? " : true , // 问号
";" : true , "; " : true , // 分号
":" : true , ": " : true , // 冒号
"、" : true ,
}
// 删除字符串前后的标点符号
trimPunctuation := func ( s string ) string {
if len ( s ) > 0 {
lastRune , size := utf8 . DecodeLastRuneInString ( s )
if punctuations [ string ( lastRune ) ] {
s = s [ : len ( s ) - size ]
}
}
return s
}
// 判断字符串是否包含标点符号
containsPunctuation := func ( s string ) bool {
for _ , char := range s {
if punctuations [ string ( char ) ] {
return true
}
}
return false
}
// 按标点符号分割文本
splitByPunctuation := func ( s string ) [ ] string {
var result [ ] string
var current string
for _ , char := range s {
if punctuations [ string ( char ) ] {
if current != "" {
result = append ( result , current + string ( char ) )
current = ""
}
} else {
current += string ( char )
}
}
if current != "" {
result = append ( result , current )
}
return result
}
* initialSessage += answer
* all_message += answer
new_message := ""
if containsPunctuation ( * initialSessage ) {
segments := splitByPunctuation ( * initialSessage )
if len ( segments ) > 1 {
format_message := strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
if utf8 . RuneCountInString ( format_message ) > 10 {
* initialSessage = segments [ len ( segments ) - 1 ]
new_message = strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
} else {
return "" , "" , false
}
} else {
if utf8 . RuneCountInString ( * initialSessage ) > 10 {
new_message = * initialSessage
* initialSessage = ""
} else if utf8 . RuneCountInString ( * initialSessage ) <= 10 && strings . HasSuffix ( * initialSessage , "。" ) {
new_message = * initialSessage
* initialSessage = ""
} else {
return "" , "" , false
}
}
}
if new_message == "" {
return "" , "" , false
}
s_msg := strings . TrimSpace ( new_message )
new_message = trimPunctuation ( s_msg )
audio := ""
for i := 0 ; i < 1 ; i ++ {
speechResp , err := s . SynthesizeSpeech ( new_message , audio_type )
if err != nil {
fmt . Printf ( "Error synthesizing speech: %v\n" , err )
break
}
fmt . Println ( "触发音频" , speechResp )
audio = speechResp . Data . Audio
if audio != "" {
resp , err := http . Get ( audio )
if err != nil {
fmt . Printf ( "Error downloading audio: %v\n" , err )
} else {
defer resp . Body . Close ( )
audioBytes , err := io . ReadAll ( resp . Body )
if err != nil {
fmt . Printf ( "Error reading audio data: %v\n" , err )
} else {
originalPath := fmt . Sprintf ( "audio/original_%d.wav" , time . Now ( ) . UnixNano ( ) )
if err := os . WriteFile ( originalPath , audioBytes , 0644 ) ; err != nil {
fmt . Printf ( "Error saving original audio: %v\n" , err )
}
audioBase64 := base64 . StdEncoding . EncodeToString ( audioBytes )
trimmedAudio , err := s . TrimAudioSilence ( audioBase64 )
if err != nil {
fmt . Printf ( "Error trimming audio silence: %v\n" , err )
} else {
audio_path := fmt . Sprintf ( "trimmed_%d.wav" , time . Now ( ) . UnixNano ( ) )
outputPath := "audio/" + audio_path
if err := s . SaveBase64AsWAV ( trimmedAudio , outputPath ) ; err != nil {
fmt . Printf ( "Error saving trimmed WAV file: %v\n" , err )
}
audio = s . config . FILE_URL + audio_path
}
}
}
break
}
}
return s_msg , audio , true
}
// handleStreamingResponse processes streaming responses
func ( s * LLMService ) handleStreamingResponse ( req * http . Request , data map [ string ] interface { } , audio_type string ) ( chan Message , error ) {
resp , err := s . client . Do ( req )
@ -167,6 +351,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
messageChan := make ( chan Message , 100 ) // Buffered channel for better performance
all_message := ""
initialSessage := ""
go func ( ) {
defer resp . Body . Close ( )
@ -200,6 +385,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
switch event {
case "message" :
answer := getString ( jsonData , "answer" )
fmt . Println ( "源文本:" , answer )
var audio string
// 定义标点符号map
@ -209,7 +395,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
"!" : true , "! " : true , // 感叹号
"?" : true , "? " : true , // 问号
";" : true , "; " : true , // 分号
" :": true , " : ": true , // 冒号
" : ": true , // 冒号
"、" : true ,
}
@ -256,6 +442,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
new_message := ""
initialSessage += answer
all_message += answer
if containsPunctuation ( initialSessage ) {
segments := splitByPunctuation ( initialSessage )
// fmt.Printf("原始文本: %s\n", initialSessage)
@ -264,11 +451,32 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
// fmt.Printf("片段 %d: %s\n", i+1, segment)
// }
if len ( segments ) > 1 {
initialSessage = segments [ len ( segments ) - 1 ]
new_message = strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
format_message := strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
// 检查initialSessage的字符长度是否超过15个
if utf8 . RuneCountInString ( format_message ) > 15 {
initialSessage = segments [ len ( segments ) - 1 ]
// 如果超过10个字符, 将其添加到new_message中并清空initialSessage
new_message = strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
// initialSessage = ""
} else {
if containsPunctuation ( format_message ) && utf8 . RuneCountInString ( format_message ) > 10 {
initialSessage = segments [ len ( segments ) - 1 ]
new_message = strings . Join ( segments [ : len ( segments ) - 1 ] , "" )
} else {
continue
}
}
} else {
new_message = initialSessage
initialSessage = ""
if utf8 . RuneCountInString ( initialSessage ) > 15 {
new_message = initialSessage
initialSessage = ""
} else {
continue
}
}
// fmt.Printf("新消息: %s\n", new_message)
// fmt.Printf("剩余文本: %s\n", initialSessage)
@ -280,8 +488,8 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
s_msg := strings . TrimSpace ( new_message )
// Trim punctuation from the message
new_message = trimPunctuation ( s_msg )
// fmt.Println("new_message", new_message )
fmt . Println ( "new_message" , new_message )
// println(new_message)
// 最多重试一次
for i := 0 ; i < 1 ; i ++ {
speechResp , err := s . SynthesizeSpeech ( new_message , audio_type )
@ -303,7 +511,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
fmt . Printf ( "Error reading audio data: %v\n" , err )
} else {
// Save original audio first
originalPath := fmt . Sprintf ( "audio/original_%d.wav" , time . Now ( ) . Unix ( ) )
originalPath := fmt . Sprintf ( "audio/original_%d.wav" , time . Now ( ) . Unix Nano ( ) )
if err := os . WriteFile ( originalPath , audioBytes , 0644 ) ; err != nil {
fmt . Printf ( "Error saving original audio: %v\n" , err )
}
@ -315,7 +523,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
fmt . Printf ( "Error trimming audio silence: %v\n" , err )
} else {
// Save the trimmed audio as WAV file
audio_path := fmt . Sprintf ( "trimmed_%d.wav" , time . Now ( ) . Unix ( ) )
audio_path := fmt . Sprintf ( "trimmed_%d.wav" , time . Now ( ) . Unix Nano ( ) )
outputPath := "audio/" + audio_path
if err := s . SaveBase64AsWAV ( trimmedAudio , outputPath ) ; err != nil {
fmt . Printf ( "Error saving trimmed WAV file: %v\n" , err )
@ -326,12 +534,12 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
break // 获取到音频就退出
}
fmt . Println ( "audio is empty, retry" , speechResp )
// fmt.Println("audio is empty, retry", speechResp )
// time.Sleep(1 * time.Second)
}
fmt . Println ( "所有消息:" , all_message )
messageChan <- Message {
Answer : new_message ,
Answer : s_msg ,
IsEnd : false ,
ConversationID : getString ( jsonData , "conversation_id" ) ,
TaskID : getString ( jsonData , "task_id" ) ,
@ -339,6 +547,96 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
AudioData : audio , // Update to use the correct path to audio data
}
case "message_end" :
// 在流结束前,处理剩余的文本生成音频
if initialSessage != "" {
// 不管文本长度,直接生成音频
s_msg := strings . TrimSpace ( initialSessage )
// 定义标点符号map
punctuations := map [ string ] bool {
"," : true , ", " : true , // 逗号
"." : true , "。" : true , // 句号
"!" : true , "! " : true , // 感叹号
"?" : true , "? " : true , // 问号
";" : true , "; " : true , // 分号
":" : true , ": " : true , // 冒号
"、" : true ,
}
// 删除字符串前后的标点符号
trimPunctuation := func ( s string ) string {
if len ( s ) > 0 {
lastRune , size := utf8 . DecodeLastRuneInString ( s )
if punctuations [ string ( lastRune ) ] {
s = s [ : len ( s ) - size ]
}
}
return s
}
new_message := trimPunctuation ( s_msg )
fmt . Println ( "最后一段文本生成音频:" , new_message )
// 生成语音
var audio string
for i := 0 ; i < 1 ; i ++ {
speechResp , err := s . SynthesizeSpeech ( new_message , audio_type )
if err != nil {
fmt . Printf ( "Error synthesizing speech: %v\n" , err )
break
}
fmt . Println ( "语音:" , speechResp )
audio = speechResp . Data . Audio
if audio != "" {
// 下载并处理音频
resp , err := http . Get ( audio )
if err != nil {
fmt . Printf ( "Error downloading audio: %v\n" , err )
} else {
defer resp . Body . Close ( )
audioBytes , err := io . ReadAll ( resp . Body )
if err != nil {
fmt . Printf ( "Error reading audio data: %v\n" , err )
} else {
// 保存原始音频
originalPath := fmt . Sprintf ( "audio/original_%d.wav" , time . Now ( ) . UnixNano ( ) )
if err := os . WriteFile ( originalPath , audioBytes , 0644 ) ; err != nil {
fmt . Printf ( "Error saving original audio: %v\n" , err )
}
// 静音裁剪
audioBase64 := base64 . StdEncoding . EncodeToString ( audioBytes )
trimmedAudio , err := s . TrimAudioSilence ( audioBase64 )
if err != nil {
fmt . Printf ( "Error trimming audio silence: %v\n" , err )
} else {
audio_path := fmt . Sprintf ( "trimmed_%d.wav" , time . Now ( ) . UnixNano ( ) )
outputPath := "audio/" + audio_path
if err := s . SaveBase64AsWAV ( trimmedAudio , outputPath ) ; err != nil {
fmt . Printf ( "Error saving trimmed WAV file: %v\n" , err )
}
audio = s . config . FILE_URL + audio_path
}
}
}
break
}
}
// 发送最后一段文本的消息
messageChan <- Message {
Answer : s_msg ,
IsEnd : false ,
ConversationID : getString ( jsonData , "conversation_id" ) ,
TaskID : getString ( jsonData , "task_id" ) ,
ClientID : getString ( data , "conversation_id" ) ,
AudioData : audio ,
}
// 清空剩余文本
initialSessage = ""
}
// 发送结束消息
messageChan <- Message {
Answer : "" ,
IsEnd : true ,
@ -353,6 +651,95 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
return messageChan , nil
}
// handleStreamingResponseV2 适配新流式返回格式
func ( s * LLMService ) handleStreamingResponseV2 ( req * http . Request , data map [ string ] interface { } , audio_type string ) ( chan Message , error ) {
resp , err := s . client . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "error making request: %v" , err )
}
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "unexpected status code: %d" , resp . StatusCode )
}
messageChan := make ( chan Message , 100 )
all_message := ""
initialSessage := ""
go func ( ) {
defer resp . Body . Close ( )
defer close ( messageChan )
reader := bufio . NewReader ( resp . Body )
for {
line , err := reader . ReadString ( '\n' )
if err != nil {
if err == io . EOF {
break
}
fmt . Printf ( "Error reading line: %v\n" , err )
continue
}
line = strings . TrimSpace ( line )
if line == "" {
continue
}
// line = strings.TrimSpace(line)
if strings . HasPrefix ( line , "data:" ) {
line = strings . TrimSpace ( strings . TrimPrefix ( line , "data:" ) )
}
// fmt.Println("line: ", line)
if line == "[DONE]" {
messageChan <- Message {
Answer : "" ,
IsEnd : true ,
ConversationID : getString ( data , "conversation_id" ) ,
TaskID : getString ( data , "task_id" ) ,
}
return
}
var jsonData map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( line ) , & jsonData ) ; err != nil {
fmt . Printf ( "Error unmarshaling JSON: %v\n" , err )
continue
}
choices , ok := jsonData [ "choices" ] . ( [ ] interface { } )
if ! ok || len ( choices ) == 0 {
continue
}
choice , ok := choices [ 0 ] . ( map [ string ] interface { } )
if ! ok {
continue
}
delta , ok := choice [ "delta" ] . ( map [ string ] interface { } )
if ! ok {
continue
}
content , _ := delta [ "content" ] . ( string )
if content == "" {
continue
}
new_message , audio , needSend := s . processStreamSegment ( & initialSessage , & all_message , content , audio_type )
if ! needSend {
continue
}
messageChan <- Message {
Answer : new_message ,
IsEnd : false ,
ConversationID : getString ( data , "conversation_id" ) ,
TaskID : getString ( data , "task_id" ) ,
ClientID : getString ( data , "conversation_id" ) ,
AudioData : audio ,
}
}
} ( )
return messageChan , nil
}
// handleNonStreamingResponse processes non-streaming responses
func ( s * LLMService ) handleNonStreamingResponse ( req * http . Request ) ( map [ string ] interface { } , error ) {
resp , err := s . client . Do ( req )
@ -424,7 +811,7 @@ func (s *LLMService) DeleteConversation(conversationID, user string) (map[string
// SynthesizeSpeech converts text to speech
func ( s * LLMService ) SynthesizeSpeech ( text string , audio string ) ( * SpeechResponse , error ) {
payload := SpeechRequest {
Model : "speech-02- turbo ",
Model : "speech-02- hd ",
Text : text ,
Stream : false ,
LanguageBoost : "auto" ,
@ -433,8 +820,8 @@ func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechRespons
VoiceID : audio ,
Speed : 1 ,
Vol : 1 ,
Pitch : 0 ,
Emotion : " happy ",
Pitch : - 1 ,
Emotion : " neutral ",
} ,
AudioSetting : AudioSetting {
SampleRate : 32000 ,