Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ This part of the knowledge bank is a Proxy server to analyze the traffic that go
## Docker setup
You can find a simplified setup using Docker Compose by SebbeJohansson here: https://github.com/SebbeJohansson/emo-proxy-docker
It uses nginx, dnsmasq, and mitmproxy to be able to pass through the api requests to the EMO Proxy.

## Experimental
### ChatGPT Speak server
One of the most common responses from EMO is to respond using ChatGPT.
An experimental feature is to use a local Speak server to generate EMO's chatgpt speak responses instead of using the living.ai servers.
You can find a proof of concept server here: https://github.com/SebbeJohansson/EmoChatGptSpeakPOC

Connect the server by adding the following line to your emoProxy.conf file:
`"chatGptSpeakServer": "http://localhost:8085"`
5 changes: 4 additions & 1 deletion emoProxy.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"postFS": "/tmp/",
"logFileName": "/var/log/emoProxyss.log",
"enableDatabaseAndAPI": false, # For now, default behavior is still without db and api.
"sqliteLocation": "/var/data/emo_logs.db"
"enableReplacements": false, # For now, default behavior is still without replacements.

"sqliteLocation": "/var/data/emo_logs.db",
"chatGptSpeakServer": ""
}
14 changes: 9 additions & 5 deletions emoProxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ type Configuration struct {
PostFS string `json:"postFS"`
LogFileName string `json:"logFileName"`
EnableDatabaseAndAPI bool `json:"enableDatabaseAndAPI"`
EnableReplacements bool `json:"enableReplacements"`
SqliteLocation string `json:"sqliteLocation"`
ChatGptSpeakServer string `json:"chatGptSpeakServer"`
}

var (
Expand Down Expand Up @@ -104,7 +106,9 @@ func loadConfig(filename string) error {
PostFS: "/tmp/",
LogFileName: "/var/log/emoProxy.log",
EnableDatabaseAndAPI: false,
EnableReplacements: false,
SqliteLocation: "/var/data/emo_logs.db",
ChatGptSpeakServer: "",
}

bytes, err := os.ReadFile(filename)
Expand Down Expand Up @@ -317,6 +321,11 @@ func makeApiRequest(r *http.Request) string {

logResponse(response)

if conf.EnableReplacements {
log.Println("Replacements enabled, checking for replacements...")
body = runReplacementsAndReturnModifiedBody(body, r)
}

if useDatabaseAndAPI {
saveRequest(r.URL.RequestURI(), string(requestBody), string(body))
}
Expand Down Expand Up @@ -346,10 +355,8 @@ func makeTtsRequest(r *http.Request) string {
}
defer response.Body.Close()

// read response
body, _ := io.ReadAll(response.Body)

// write post request body to fs
logBody(response.Header.Get("Content-Type"), body, "tts_")
logResponse(response)

Expand Down Expand Up @@ -382,10 +389,8 @@ func makeApiTtsRequest(r *http.Request) string {
}
defer response.Body.Close()

// read response
body, _ := io.ReadAll(response.Body)

// write post request body to fs
logBody(response.Header.Get("Content-Type"), body, "apitts_")
logResponse(response)

Expand Down Expand Up @@ -418,7 +423,6 @@ func makeResRequest(r *http.Request, w http.ResponseWriter) string {
}
defer response.Body.Close()

// read response
body, _ := io.ReadAll(response.Body)

logBody(response.Header.Get("Content-Type"), body, "res_")
Expand Down
130 changes: 130 additions & 0 deletions replacements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
"net/url"
)

func runReplacementsAndReturnModifiedBody(body []byte, r *http.Request) []byte {
typedBody := QueryResponse{}
decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()

err := decoder.Decode(&typedBody)
if err != nil {
log.Println("Error when decoding JSON (", string(body), ") will return unhandled:", err)
return body
} else {
if typedBody.QueryResult == nil || typedBody.QueryId == "" {
log.Println("Unexpected query response, will return unhandled.")
return body
}

if typedBody.QueryResult.Intent.Name == "chatgpt_speak" && conf.ChatGptSpeakServer != "" {
speakResponse := makeChatGptSpeakRequest(typedBody.QueryResult.QueryText, typedBody.LanguageCode, typedBody.QueryResult.BehaviorParas.Txt, r)
if speakResponse.Url != "" && speakResponse.Txt != "" {
log.Println("Successfully replaced chatgpt_speak response for request.")
typedBody.QueryResult.BehaviorParas.Url = speakResponse.Url
typedBody.QueryResult.BehaviorParas.Txt = speakResponse.Txt
} else {
log.Println("Failed to get valid response from ChatGptSpeakServer, keeping original response.")
}
}
modifiedBody, err := json.Marshal(typedBody)
if err != nil {
log.Println("Error when marshaling modified JSON, will return unhandled:", err)
return body
}
return modifiedBody
}
}

func makeEmoSpeechRequest(text string, languageCode string, r *http.Request) EmoSpeechResponse {
request, _ := http.NewRequest("GET", "https://"+conf.Livingio_API_Server+"/emo/speech/tts?q="+url.QueryEscape(text)+"&l="+url.QueryEscape(languageCode), nil)

val, exists := r.Header["Authorization"]
if exists {
request.Header.Add("Authorization", val[0])
}

val, exists = r.Header["Secret"]
if exists {
request.Header.Add("Secret", val[0])
}

request.Header.Del("User-Agent")

httpclient := &http.Client{}
response, err := httpclient.Do(request)

if err != nil {
log.Fatalf("An Error Occured %v", err)
}
defer response.Body.Close()

body, _ := io.ReadAll(response.Body)

var emoSpeechResponse EmoSpeechResponse
if err := json.Unmarshal([]byte(body), &emoSpeechResponse); err != nil {
log.Printf("Error unmarshaling ChatGptSpeakServer response: %v\n", err)
return EmoSpeechResponse{}
}

return emoSpeechResponse
}

func makeChatGptSpeakRequest(queryText string, languageCode string, fallbackResponse string, r *http.Request) BehaviorParas {
type ChatGptSpeakRequest struct {
QueryText string `json:"queryText"`
LanguageCode string `json:"languageCode"`
FallbackResponse string `json:"fallbackResponse,omitempty"`
}
type ChatGptSpeakResponse struct {
ResponseText string `json:"responseText"`
}

chatGptRequestData := ChatGptSpeakRequest{
QueryText: queryText,
LanguageCode: languageCode,
FallbackResponse: fallbackResponse,
}
chatGptRequestBody, _ := json.Marshal(chatGptRequestData)
chatGptRequest, _ := http.NewRequest("POST", conf.ChatGptSpeakServer+"/speak", bytes.NewBuffer(chatGptRequestBody))
chatGptRequest.Header.Add("Content-Type", "application/json")

chatGptClient := &http.Client{}
chatGptResponse, err := chatGptClient.Do(chatGptRequest)
if err != nil {
log.Fatalf("An Error Occured while calling ChatGptSpeakServer %v", err)
}
defer chatGptResponse.Body.Close()

chatGptResponseBody, _ := io.ReadAll(chatGptResponse.Body)

var chatGptTypedResponse ChatGptSpeakResponse
if err := json.Unmarshal([]byte(chatGptResponseBody), &chatGptTypedResponse); err != nil {
log.Printf("Error unmarshaling ChatGptSpeakServer response: %v\n", err)
return BehaviorParas{}
}

if chatGptTypedResponse.ResponseText == "" {
log.Println("ChatGptSpeakServer returned empty response text")
return BehaviorParas{}
}

emoSpeechResponse := makeEmoSpeechRequest(chatGptTypedResponse.ResponseText, languageCode, r)
if emoSpeechResponse.Code != 200 || emoSpeechResponse.Url == "" {
log.Printf("Error in EmoSpeechResponse: Code %d, Errmessage: %s\n", emoSpeechResponse.Code, emoSpeechResponse.Errmessage)
return BehaviorParas{}
}
behaviorParasResponse := BehaviorParas{
Txt: chatGptTypedResponse.ResponseText,
Url: emoSpeechResponse.Url,
}

return behaviorParasResponse
}
48 changes: 48 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

type Intent struct {
Name string `json:"name,omitempty"`
Confidence float64 `json:"confidence,omitempty"`
}

type BehaviorParas struct {
UtilityType string `json:"utility_type,omitempty"`
Time []string `json:"time,omitempty"`
Txt string `json:"txt,omitempty"`
Url string `json:"url,omitempty"`
PreAnimation string `json:"pre_animation,omitempty"`
PostAnimation string `json:"post_animation,omitempty"`
PostBehavior string `json:"post_behavior,omitempty"`
RecBehavior string `json:"rec_behavior,omitempty"`
BehaviorParas *BehaviorParas `json:"behavior_paras,omitempty"`
Sentiment string `json:"sentiment,omitempty"`
Listen int `json:"listen,omitempty"`
AnimationName string `json:"animation_name,omitempty"`
}

type QueryResult struct {
ResultCode string `json:"resultCode,omitempty"`
QueryText string `json:"queryText,omitempty"`
Intent *Intent `json:"intent,omitempty"`
RecBehavior string `json:"rec_behavior,omitempty"`
BehaviorParas *BehaviorParas `json:"behavior_paras,omitempty"`
}

type QueryResponse struct {
QueryId string `json:"queryId,omitempty"`
QueryResult *QueryResult `json:"queryResult,omitempty"`
LanguageCode string `json:"languageCode,omitempty"`
Index int `json:"index,omitempty"`
}

type TokenResponse struct {
AccessToken string `json:"access_token,omitempty"`
ExpireIn int `json:"expire_in,omitempty"`
Type string `json:"type,omitempty"`
}

type EmoSpeechResponse struct {
Code int64 `json:"code"`
Errmessage string `json:"errmessage"`
Url string `json:"url"`
}