From 863eeaa93cd863a1f8563f7ae4d0154829ca8245 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Tue, 30 Jul 2024 16:49:30 -0700 Subject: [PATCH 01/14] implement access control and test case --- chat-history/config/config.go | 14 +-- chat-history/db/db.go | 12 +++ chat-history/db/db_test.go | 23 +++++ chat-history/go.sum | 2 + chat-history/main.go | 3 +- chat-history/routes/routes.go | 153 +++++++++++++++++++++++++++++ chat-history/routes/routes_test.go | 129 ++++++++++++++++++++++++ 7 files changed, 329 insertions(+), 7 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 3185635d..547ff429 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -11,13 +11,15 @@ type LLMConfig struct { } type DbConfig struct { - Port string `json:"apiPort"` - DbPath string `json:"dbPath"` - DbLogPath string `json:"dbLogPath"` - LogPath string `json:"logPath"` + Port string `json:"apiPort"` + DbPath string `json:"dbPath"` + DbLogPath string `json:"dbLogPath"` + LogPath string `json:"logPath"` + TgDbPath string `json:"TGDBPath"` + ConversationAccessRoles []string `json:"tgDbPath"` // DbHostname string `json:"hostname"` - // Username string `json:"username"` - // Password string `json:"password"` + // Username string `json:"username"` + // Password string `json:"password"` // GetToken string `json:"getToken"` // DefaultTimeout string `json:"default_timeout"` // DefaultMemThreshold string `json:"default_mem_threshold"` diff --git a/chat-history/db/db.go b/chat-history/db/db.go index 9b7a0e10..28502249 100644 --- a/chat-history/db/db.go +++ b/chat-history/db/db.go @@ -131,6 +131,18 @@ func UpdateConversationById(message structs.Message) (*structs.Conversation, err return &convo, nil } +// GetAllMessages retrieves all messages from the database +func GetAllMessages() ([]structs.Message, error) { + var messages []structs.Message + + // Use GORM to query all messages + if err := db.Find(&messages).Error; err != nil { + return nil, err + } + + return messages, nil +} + func populateDB() { mu.Lock() defer mu.Unlock() diff --git a/chat-history/db/db_test.go b/chat-history/db/db_test.go index ed50f17c..ac7a63c4 100644 --- a/chat-history/db/db_test.go +++ b/chat-history/db/db_test.go @@ -213,6 +213,29 @@ func TestParallelWrites(t *testing.T) { } } +func TestGetAllMessages(t *testing.T) { + setupTest(t, true) + + messages, err := GetAllMessages() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Ensure that messages are returned + if len(messages) == 0 { + t.Fatalf("Expected some messages, got none") + } + + // Validate the structure of the messages + for _, m := range messages { + if uuid.Validate(m.ConversationId.String()) != nil || + uuid.Validate(m.MessageId.String()) != nil || + (m.Role != "system" && m.Role != "user") { + t.Fatalf("Invaid message structure: %v", m) + } + } +} + /* helper functions */ diff --git a/chat-history/go.sum b/chat-history/go.sum index cce54bb7..4c7b12d8 100644 --- a/chat-history/go.sum +++ b/chat-history/go.sum @@ -1,3 +1,5 @@ +github.com/GenericP3rson/TigerGo v0.0.4 h1:xI7d/cLJ6sRP4fzanInakARE0XGk1YAmvn5KrH1fwFU= +github.com/GenericP3rson/TigerGo v0.0.4/go.mod h1:PGpAFO9vNA7l34WSGYCtWb/eqVKHuIq1xqvizBlNhRM= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/httplog/v2 v2.0.11 h1:eu6kYksMEJzBcOP+ba/iYudc0m5rv4VvBAzroJMkaY4= diff --git a/chat-history/main.go b/chat-history/main.go index e913bcef..2f27109b 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -12,7 +12,7 @@ import ( ) func main() { - configPath:= os.Getenv("CONFIG") + configPath := os.Getenv("CONFIG") config, err := config.LoadConfig(configPath) if err != nil { panic(err) @@ -30,6 +30,7 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) + router.HandleFunc("GET /get_feedback", routes.GetFeedback) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index 076cb41a..b2ebd441 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -3,10 +3,12 @@ package routes import ( "chat-history/db" "chat-history/structs" + "encoding/base64" "encoding/json" "fmt" "io" "net/http" + "net/url" "slices" "strings" ) @@ -154,3 +156,154 @@ func auth(userId string, r *http.Request) (string, int, []byte, bool) { return usr, 0, nil, true } + +// executeGSQL sends a GSQL query to TigerGraph with basic authentication and returns the response +func executeGSQL(host, username, password, query string) (string, error) { + // Extract the hostname from the URL (ignoring the scheme and port) + parsedURL, err := url.Parse(host) + if err != nil { + return "", fmt.Errorf("invalid host URL: %w", err) + } + + // Construct the URL for the GSQL query endpoint + requestURL := fmt.Sprintf("https://%s:443/gsqlserver/gsql/file", parsedURL.Hostname()) + + // Prepare the query data + data := url.QueryEscape(query) // Encode query using URL encoding + reqBody := strings.NewReader(data) + + // Create the HTTP request + req, err := http.NewRequest("POST", requestURL, reqBody) + if err != nil { + return "", err + } + + // Set the required headers + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Set up basic authentication + auth := fmt.Sprintf("%s:%s", username, password) + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) + + // Execute the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Read and return the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} + +// hasAdminAccess checks if the user's roles include any of the admin roles +func hasAdminAccess(userRoles []string, adminRoles []string) bool { + for _, role := range userRoles { + for _, adminRole := range adminRoles { + if role == adminRole { + return true + } + } + } + return false +} + +// parseUserRoles extracts roles from the user information string +func parseUserRoles(userInfo string, userName string) []string { + lines := strings.Split(userInfo, "\n") + var roles []string + var isUserSection bool + + for _, line := range lines { + if strings.Contains(line, "Name:") { + isUserSection = strings.Contains(line, userName) + } + if isUserSection && strings.Contains(line, "- Global Roles:") { + parts := strings.Split(line, ":") + if len(parts) > 1 { + roles = append(roles, strings.Split(strings.TrimSpace(parts[1]), ", ")...) + } + } + } + + return roles +} + +// GetFeedback retrieves feedback data for conversations +// "Get /get_feedback" +func GetFeedback(w http.ResponseWriter, r *http.Request) { + usr, pass, ok := r.BasicAuth() + if !ok { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"reason":"missing Authorization header"}`)) + return + } + + // Verify if the user has the required role + userInfo, err := executeGSQL("https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io", usr, pass, "SHOW USER") + if err != nil { + reason := []byte(`{"reason":"failed to retrieve feedback data"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } + + // Parse and check roles + userRoles := parseUserRoles(userInfo, usr) + conversationAccessRoles := []string{"superuser", "globaldesigner"} + + if !hasAdminAccess(userRoles, conversationAccessRoles) { + // Fetch chat history messages for this specific user + conversations := db.GetUserConversations(usr) + + var allMessages []structs.Message + + for _, convo := range conversations { + messages := db.GetUserConversationById(usr, convo.ConversationId.String()) + allMessages = append(allMessages, messages...) + } + // Marshal and write the response + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + response, err := json.Marshal(allMessages) + if err != nil { + reason := []byte(`{"reason":"failed to marshal messages"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } + w.Write(response) + return + } + + // If the user has admin access, fetch all messages + messages, err := db.GetAllMessages() + if err != nil { + reason := []byte(`{"reason":"failed to retrieve feedback data"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + response, err := json.Marshal(messages) + if err != nil { + reason := []byte(`{"reason":"failed to marshal messages"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } + w.Write(response) +} diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index 1411e7b0..e75e17ac 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -12,6 +12,7 @@ import ( "net/http/httptest" "os" "slices" + "strings" "testing" "github.com/google/uuid" @@ -395,6 +396,134 @@ func messageEquals(m, msg structs.Message) bool { return false } +func TestExecuteGSQL(t *testing.T) { + // Replace these values with your actual TigerGraph details + host := "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io" + username := "supportai" + password := "supportai" + query := "SHOW USER" // Replace with an actual GSQL query + + response, err := executeGSQL(host, username, password, query) + if err != nil { + t.Fatalf("Failed to execute GSQL query: %v", err) + } + + // Check for common errors or issues in the response + if strings.Contains(response, "400 Bad Request") { + t.Error("Received '400 Bad Request' error. Please check the query and server configuration.") + } + + if strings.Contains(response, "401 Unauthorized") { + t.Error("Received '401 Unauthorized' error. Please check the credentials and access permissions.") + } + + if strings.Contains(response, "403 Forbidden") { + t.Error("Received '403 Forbidden' error. The user may not have sufficient permissions to execute the query.") + } + + if strings.Contains(response, "500 Internal Server Error") { + t.Error("Received '500 Internal Server Error'. This indicates a server-side issue.") + } + + // Add any additional checks on the response here + // For example: + if response == "" { + t.Error("Received empty response from GSQL query") + } + + // Check if the response contains "Name" and "Global Roles" + if !strings.Contains(response, "Name") { + t.Error("Response does not contain 'Name'.") + } + + if !strings.Contains(response, "Global Roles") { + t.Error("Response does not contain 'Global Roles'.") + } +} + +func TestParseUserRoles(t *testing.T) { + userInfo := ` + - Name: feedbackauthtest + - Global Roles: globalobserver + - Graph 'EarningsCallRAG' Roles: queryreader + - Graph 'Transaction_Fraud' Roles: designer, queryreader + - Graph 'pyTigerGraphRAG' Roles: queryreader, querywriter + - Secret: ad9****v7p + - Alias: AUTO_GENERATED_ALIAS_suv6mm5 + - GraphName: Transaction_Fraud + - LastSuccessLogin: Mon Jul 22 06:57:29 UTC 2024 + - NextValidLogin: Mon Jul 22 06:57:29 UTC 2024 + - FailedAttempts: 0 + - ShowAlterPasswordWarning: false + + - Name: richard@epsilla.com + - Global Roles: globalobserver + - LastSuccessLogin: Tue Jul 23 16:35:45 UTC 2024 + - NextValidLogin: Tue Jul 23 16:35:45 UTC 2024 + - FailedAttempts: 0 + - ShowAlterPasswordWarning: false + ` + + expectedRoles := []string{"globalobserver"} + + roles := parseUserRoles(userInfo, "feedbackauthtest") + + fmt.Println("Extracted Roles:", roles) + + if len(roles) != len(expectedRoles) { + t.Fatalf("expected %d roles, got %d", len(expectedRoles), len(roles)) + } + + for i, role := range expectedRoles { + if roles[i] != role { + t.Errorf("expected role %s, got %s", role, roles[i]) + } + } +} + +func TestGetFeedback(t *testing.T) { + setupDB(t, true) + // Create a request with Basic Auth + req, err := http.NewRequest("GET", "/get_feedback", nil) + if err != nil { + t.Fatal(err) + } + req.SetBasicAuth("supportai", "supportai") // Use a valid username and password for testing + + // Record the response + rr := httptest.NewRecorder() + handler := http.HandlerFunc(GetFeedback) + + // Serve the request + handler.ServeHTTP(rr, req) + + // Check the response status code + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + + // Check the response body for expected messages + var messages []structs.Message + if err := json.Unmarshal(rr.Body.Bytes(), &messages); err != nil { + t.Errorf("Failed to parse response body: %v", err) + } + + // Print the messages for debugging + fmt.Println("Retrieved messages:", messages) + // Validate that the messages are as expected + expectedMessagesCount := 2 // Based on populateDB function + if len(messages) != expectedMessagesCount { + t.Errorf("Expected %d messages, got %d", expectedMessagesCount, len(messages)) + } + + // Additional checks to ensure the response contains the correct data + if len(messages) > 0 { + if messages[0].Content != "This is the first message, there is no parent" { + t.Errorf("Unexpected message content: %v", messages[0].Content) + } + } +} + // helpers func basicAuthSetup(user, pass string) string { return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))) From 1c43a24d82998371e4d0ccd6e6f45b2d1a0b2856 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 04:35:49 -0700 Subject: [PATCH 02/14] clean up notebook, add get_feedback endpoint --- copilot/app/routers/ui.py | 25 ++++ copilot/docs/notebooks/FeedbackAnalysis.ipynb | 112 +++++++----------- docker-compose.yml | 2 +- 3 files changed, 66 insertions(+), 73 deletions(-) diff --git a/copilot/app/routers/ui.py b/copilot/app/routers/ui.py index 12f4db42..9707976f 100644 --- a/copilot/app/routers/ui.py +++ b/copilot/app/routers/ui.py @@ -168,6 +168,31 @@ async def get_conversation_contents( return res.json() +@router.get(route_prefix + "/get_feedback") +async def get_conversation_feedback( + creds: Annotated[tuple[list[str], HTTPBasicCredentials], Depends(ui_basic_auth)], +): + creds = creds[1] + auth = base64.b64encode(f"{creds.username}:{creds.password}".encode()).decode() + try: + async with httpx.AsyncClient() as client: + res = await client.get( + f"{db_config['chat_history_api']}/get_feedback", + headers={"Authorization": f"Basic {auth}"}, + ) + res.raise_for_status() + except httpx.HTTPStatusError as e: + logger.error(f"HTTP error occurred: {e}") + raise HTTPException(status_code=e.response.status_code, detail="Failed to fetch feedback") + except Exception as e: + exc = traceback.format_exc() + logger.debug_pii( + f"/get_feedback request_id={req_id_cv.get()} Exception Trace:\n{exc}" + ) + raise HTTPException(status_code=500, detail="Internal server error") + + return res.json() + async def emit_progress(agent: TigerGraphAgent, ws: WebSocket): # loop on q until done token emit events through ws diff --git a/copilot/docs/notebooks/FeedbackAnalysis.ipynb b/copilot/docs/notebooks/FeedbackAnalysis.ipynb index 447882c8..025d5d06 100644 --- a/copilot/docs/notebooks/FeedbackAnalysis.ipynb +++ b/copilot/docs/notebooks/FeedbackAnalysis.ipynb @@ -9,54 +9,32 @@ "import requests\n", "import base64\n", "\n", - "def create_headers(username, password):\n", + "def fetch_conversation_feedback(username, password): \n", " \"\"\"Create headers with Base64 encoded credentials.\"\"\"\n", " credentials = f\"{username}:{password}\"\n", " encoded_credentials = base64.b64encode(credentials.encode(\"utf-8\")).decode(\"utf-8\")\n", - " return {\n", + " headers = {\n", " 'accept': 'application/json',\n", " 'Authorization': f'Basic {encoded_credentials}'\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def get_user_conversation_ids(username, password):\n", - " \"\"\"Fetch conversation IDs for a given user.\"\"\"\n", - " headers = create_headers(username, password)\n", - " user_url = f'http://COPILOT_ADDRESS/ui/user/{username}'\n", - " \n", - " response = requests.get(user_url, headers=headers)\n", - " \n", - " if response.status_code == 200:\n", - " data = response.json()\n", - " return [item['conversation_id'] for item in data]\n", - " else:\n", - " print(f\"Request failed with status code {response.status_code}\")\n", - " print(response.text)\n", - " return None" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def get_conversation_data(username, password, conversation_id):\n", - " \"\"\"Fetch conversation data for a given conversation ID.\"\"\"\n", - " headers = create_headers(username, password)\n", - " conversation_url = f'http://COPILOT_ADDRESS/ui/conversation/{conversation_id}'\n", - " \n", - " response = requests.get(conversation_url, headers=headers)\n", - " \n", - " if response.status_code == 200:\n", - " data = response.json()\n", + " }\n", "\n", + " \"\"\"Fetch conversation and feedback data.\"\"\"\n", + " feedback_url = f'http://localhost:8000/ui/get_feedback'\n", + " \n", + " try:\n", + " response = requests.get(feedback_url, headers=headers)\n", + " response.raise_for_status()\n", + "\n", + " try:\n", + " data = response.json()\n", + " except ValueError:\n", + " print(\"Error decoding JSON from response.\")\n", + " return None\n", + " \n", + " if not data:\n", + " print(\"No data received.\")\n", + " return None\n", + " \n", " # Create dictionaries to hold user questions and system answers\n", " questions = {message[\"message_id\"]: message for message in data if message[\"role\"] == \"user\"}\n", " answers = {message[\"parent_id\"]: message for message in data if message[\"role\"] == \"system\"}\n", @@ -73,50 +51,40 @@ " })\n", " \n", " return qa_pairs\n", - " else:\n", - " print(f\"Request failed with status code {response.status_code}\")\n", - " print(response.text)\n", + " except requests.RequestException as e:\n", + " print(f\"Request failed: {e}\")\n", " return None" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "def fetch_user_conversations(username, password, conversation_id=None):\n", - " if conversation_id:\n", - " conversations = {}\n", - " # Fetch a specific conversation\n", - " data = get_conversation_data(username, password, conversation_id)\n", - " \n", - " if data:\n", - " conversations[conversation_id] = data\n", - " return conversations\n", - " else:\n", - " return \"Conversation not found or could not be retrieved.\"\n", - " \n", - " else:\n", - " # Fetch all conversations\n", - " conversation_ids = get_user_conversation_ids(username, password)\n", - " conversations = {}\n", - " \n", - " for conv_id in conversation_ids:\n", - " data = get_conversation_data(username, password, conv_id)\n", - " if data:\n", - " conversations[conv_id] = data\n", - " \n", - " return conversations" + "conversation_data = fetch_conversation_feedback(\"supportai\", \"supportai\")" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[{'question': 'How many transactions are there?',\n", + " 'answer': 'The total number of transactions is 860142.',\n", + " 'feedback': 1}]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "conversation_data = fetch_user_conversations(\"YOUR_DB_USERNAME\", \"YOUR_DB_PASSWORD\")" + "conversation_data" ] }, { diff --git a/docker-compose.yml b/docker-compose.yml index ac77e058..a865711c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: ports: - 8002:8002 environment: - CONFIG: "/configs/config.json" + CONFIG: "/configs/chat_config.json" LOGLEVEL: "INFO" volumes: - ./configs/:/configs From afa02f27c81b7cf617cb0761c70f8e4b81ea8857 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 09:58:54 -0700 Subject: [PATCH 03/14] read variables from config file --- chat-history/config/config.go | 4 +- chat-history/go.mod | 4 +- chat-history/main.go | 2 +- chat-history/routes/routes.go | 104 +++++++++--------- chat-history/routes/routes_test.go | 13 ++- copilot/docs/notebooks/FeedbackAnalysis.ipynb | 26 +---- 6 files changed, 67 insertions(+), 86 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 547ff429..b4e75288 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -15,8 +15,8 @@ type DbConfig struct { DbPath string `json:"dbPath"` DbLogPath string `json:"dbLogPath"` LogPath string `json:"logPath"` - TgDbPath string `json:"TGDBPath"` - ConversationAccessRoles []string `json:"tgDbPath"` + TgDbHost string `json:"tgDbHost"` + ConversationAccessRoles []string `json:"conversationAccessRoles"` // DbHostname string `json:"hostname"` // Username string `json:"username"` // Password string `json:"password"` diff --git a/chat-history/go.mod b/chat-history/go.mod index f132c6bc..cc9328ab 100644 --- a/chat-history/go.mod +++ b/chat-history/go.mod @@ -3,14 +3,14 @@ module chat-history go 1.22.3 require ( - github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/httplog/v2 v2.0.11 github.com/google/uuid v1.6.0 gorm.io/driver/sqlite v1.5.5 gorm.io/gorm v1.25.10 ) require ( - github.com/go-chi/httplog/v2 v2.0.11 // indirect + github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect diff --git a/chat-history/main.go b/chat-history/main.go index 2f27109b..0907e42d 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -30,7 +30,7 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) - router.HandleFunc("GET /get_feedback", routes.GetFeedback) + router.HandleFunc("GET /get_feedback", routes.GetFeedback(config.TgDbHost, config.ConversationAccessRoles)) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index b2ebd441..d1cadac2 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -237,43 +237,66 @@ func parseUserRoles(userInfo string, userName string) []string { // GetFeedback retrieves feedback data for conversations // "Get /get_feedback" -func GetFeedback(w http.ResponseWriter, r *http.Request) { - usr, pass, ok := r.BasicAuth() - if !ok { - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(`{"reason":"missing Authorization header"}`)) - return - } +func GetFeedback(tgDbHost string, conversationAccessRoles []string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + usr, pass, ok := r.BasicAuth() + if !ok { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"reason":"missing Authorization header"}`)) + return + } - // Verify if the user has the required role - userInfo, err := executeGSQL("https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io", usr, pass, "SHOW USER") - if err != nil { - reason := []byte(`{"reason":"failed to retrieve feedback data"}`) - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - w.Write(reason) - return - } + // Verify if the user has the required role + userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER") + if err != nil { + reason := []byte(`{"reason":"failed to retrieve feedback data"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } - // Parse and check roles - userRoles := parseUserRoles(userInfo, usr) - conversationAccessRoles := []string{"superuser", "globaldesigner"} + // Parse and check roles + userRoles := parseUserRoles(userInfo, usr) + if !hasAdminAccess(userRoles, conversationAccessRoles) { + // Fetch chat history messages for this specific user + conversations := db.GetUserConversations(usr) - if !hasAdminAccess(userRoles, conversationAccessRoles) { - // Fetch chat history messages for this specific user - conversations := db.GetUserConversations(usr) + var allMessages []structs.Message - var allMessages []structs.Message + for _, convo := range conversations { + messages := db.GetUserConversationById(usr, convo.ConversationId.String()) + allMessages = append(allMessages, messages...) + } + // Marshal and write the response + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + response, err := json.Marshal(allMessages) + if err != nil { + reason := []byte(`{"reason":"failed to marshal messages"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return + } + w.Write(response) + return + } - for _, convo := range conversations { - messages := db.GetUserConversationById(usr, convo.ConversationId.String()) - allMessages = append(allMessages, messages...) + // If the user has admin access, fetch all messages + messages, err := db.GetAllMessages() + if err != nil { + reason := []byte(`{"reason":"failed to retrieve feedback data"}`) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + w.Write(reason) + return } - // Marshal and write the response + w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - response, err := json.Marshal(allMessages) + response, err := json.Marshal(messages) if err != nil { reason := []byte(`{"reason":"failed to marshal messages"}`) w.Header().Add("Content-Type", "application/json") @@ -282,28 +305,5 @@ func GetFeedback(w http.ResponseWriter, r *http.Request) { return } w.Write(response) - return - } - - // If the user has admin access, fetch all messages - messages, err := db.GetAllMessages() - if err != nil { - reason := []byte(`{"reason":"failed to retrieve feedback data"}`) - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - w.Write(reason) - return - } - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - response, err := json.Marshal(messages) - if err != nil { - reason := []byte(`{"reason":"failed to marshal messages"}`) - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - w.Write(reason) - return } - w.Write(response) } diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index e75e17ac..d4d835f0 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -20,7 +20,7 @@ import ( const ( USER = "sam_pull" - PASS = "pass" + PASS = "sam_pull" CONVO_ID = "601529eb-4927-4e24-b285-bd6b9519a951" ) @@ -425,8 +425,7 @@ func TestExecuteGSQL(t *testing.T) { t.Error("Received '500 Internal Server Error'. This indicates a server-side issue.") } - // Add any additional checks on the response here - // For example: + // Add any additional checks on the response if response == "" { t.Error("Received empty response from GSQL query") } @@ -483,16 +482,20 @@ func TestParseUserRoles(t *testing.T) { func TestGetFeedback(t *testing.T) { setupDB(t, true) + + testTgDbHost := "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io" + testConversationAccessRoles := []string{"superuser", "globaldesigner"} + // Create a request with Basic Auth req, err := http.NewRequest("GET", "/get_feedback", nil) if err != nil { t.Fatal(err) } - req.SetBasicAuth("supportai", "supportai") // Use a valid username and password for testing + req.SetBasicAuth("supportai", "supportai") // Record the response rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback) + handler := http.HandlerFunc(GetFeedback(testTgDbHost, testConversationAccessRoles)) // Serve the request handler.ServeHTTP(rr, req) diff --git a/copilot/docs/notebooks/FeedbackAnalysis.ipynb b/copilot/docs/notebooks/FeedbackAnalysis.ipynb index 025d5d06..85b3df1e 100644 --- a/copilot/docs/notebooks/FeedbackAnalysis.ipynb +++ b/copilot/docs/notebooks/FeedbackAnalysis.ipynb @@ -19,7 +19,7 @@ " }\n", "\n", " \"\"\"Fetch conversation and feedback data.\"\"\"\n", - " feedback_url = f'http://localhost:8000/ui/get_feedback'\n", + " feedback_url = f'http://COPILOT_HOST/ui/get_feedback'\n", " \n", " try:\n", " response = requests.get(feedback_url, headers=headers)\n", @@ -62,29 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "conversation_data = fetch_conversation_feedback(\"supportai\", \"supportai\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'question': 'How many transactions are there?',\n", - " 'answer': 'The total number of transactions is 860142.',\n", - " 'feedback': 1}]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "conversation_data" + "conversation_data = fetch_conversation_feedback(\"DB_USERNAME\", \"DB_PASSWORD\")" ] }, { From d534eb455656f2258028b04ecaf5d2ed49bab051 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 10:24:30 -0700 Subject: [PATCH 04/14] add tgcloud parameter to determine the url --- chat-history/config/config.go | 1 + chat-history/main.go | 2 +- chat-history/routes/routes.go | 24 +++++++++------ chat-history/routes/routes_test.go | 4 +-- copilot/docs/notebooks/FeedbackAnalysis.ipynb | 30 ++++++++++++++++--- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index b4e75288..3cf3b324 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -16,6 +16,7 @@ type DbConfig struct { DbLogPath string `json:"dbLogPath"` LogPath string `json:"logPath"` TgDbHost string `json:"tgDbHost"` + TgCloud bool `json:tgCloud` ConversationAccessRoles []string `json:"conversationAccessRoles"` // DbHostname string `json:"hostname"` // Username string `json:"username"` diff --git a/chat-history/main.go b/chat-history/main.go index 0907e42d..8cf8e4ed 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -30,7 +30,7 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) - router.HandleFunc("GET /get_feedback", routes.GetFeedback(config.TgDbHost, config.ConversationAccessRoles)) + router.HandleFunc("GET /get_feedback", routes.GetFeedback(config.TgDbHost, config.ConversationAccessRoles, config.TgCloud)) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index d1cadac2..818f2903 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -158,16 +158,22 @@ func auth(userId string, r *http.Request) (string, int, []byte, bool) { } // executeGSQL sends a GSQL query to TigerGraph with basic authentication and returns the response -func executeGSQL(host, username, password, query string) (string, error) { +func executeGSQL(host, username, password, query string, tgcloud bool) (string, error) { // Extract the hostname from the URL (ignoring the scheme and port) - parsedURL, err := url.Parse(host) - if err != nil { - return "", fmt.Errorf("invalid host URL: %w", err) - } + // parsedURL, err := url.Parse(host) + // if err != nil { + // return "", fmt.Errorf("invalid host URL: %w", err) + // } + var requestURL string // Construct the URL for the GSQL query endpoint - requestURL := fmt.Sprintf("https://%s:443/gsqlserver/gsql/file", parsedURL.Hostname()) - + if tgcloud { + // requestURL = fmt.Sprintf("https://%s:443/gsqlserver/gsql/file", parsedURL.Hostname()) + requestURL = fmt.Sprintf("%s:443/gsqlserver/gsql/file", host) + } else { + // requestURL = fmt.Sprintf("http://%s:14240/gsqlserver/gsql/file", parsedURL.Hostname()) + requestURL = fmt.Sprintf("%s:14240/gsqlserver/gsql/file", host) + } // Prepare the query data data := url.QueryEscape(query) // Encode query using URL encoding reqBody := strings.NewReader(data) @@ -237,7 +243,7 @@ func parseUserRoles(userInfo string, userName string) []string { // GetFeedback retrieves feedback data for conversations // "Get /get_feedback" -func GetFeedback(tgDbHost string, conversationAccessRoles []string) http.HandlerFunc { +func GetFeedback(tgDbHost string, conversationAccessRoles []string, tgCloud bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { usr, pass, ok := r.BasicAuth() if !ok { @@ -248,7 +254,7 @@ func GetFeedback(tgDbHost string, conversationAccessRoles []string) http.Handler } // Verify if the user has the required role - userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER") + userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER", tgCloud) if err != nil { reason := []byte(`{"reason":"failed to retrieve feedback data"}`) w.Header().Add("Content-Type", "application/json") diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index d4d835f0..6bfa7187 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -403,7 +403,7 @@ func TestExecuteGSQL(t *testing.T) { password := "supportai" query := "SHOW USER" // Replace with an actual GSQL query - response, err := executeGSQL(host, username, password, query) + response, err := executeGSQL(host, username, password, query, true) if err != nil { t.Fatalf("Failed to execute GSQL query: %v", err) } @@ -495,7 +495,7 @@ func TestGetFeedback(t *testing.T) { // Record the response rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback(testTgDbHost, testConversationAccessRoles)) + handler := http.HandlerFunc(GetFeedback(testTgDbHost, testConversationAccessRoles, true)) // Serve the request handler.ServeHTTP(rr, req) diff --git a/copilot/docs/notebooks/FeedbackAnalysis.ipynb b/copilot/docs/notebooks/FeedbackAnalysis.ipynb index 85b3df1e..b3c84505 100644 --- a/copilot/docs/notebooks/FeedbackAnalysis.ipynb +++ b/copilot/docs/notebooks/FeedbackAnalysis.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -19,7 +19,7 @@ " }\n", "\n", " \"\"\"Fetch conversation and feedback data.\"\"\"\n", - " feedback_url = f'http://COPILOT_HOST/ui/get_feedback'\n", + " feedback_url = f'http://localhost:8000/ui/get_feedback'\n", " \n", " try:\n", " response = requests.get(feedback_url, headers=headers)\n", @@ -58,11 +58,33 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ - "conversation_data = fetch_conversation_feedback(\"DB_USERNAME\", \"DB_PASSWORD\")" + "conversation_data = fetch_conversation_feedback(\"supportai\", \"supportai\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'question': 'How many transactions are there?',\n", + " 'answer': 'The total number of transactions is 860142.',\n", + " 'feedback': 1}]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversation_data" ] }, { From 198ac5a1e4808e0c5da78b9316f0a0e99c04401e Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 11:43:53 -0700 Subject: [PATCH 05/14] load configs from multiple files --- chat-history/config/config.go | 42 +++++++++++-------- chat-history/main.go | 15 ++++--- chat-history/routes/routes.go | 8 ---- chat-history/routes/routes_test.go | 2 +- copilot/docs/notebooks/FeedbackAnalysis.ipynb | 28 ++----------- docker-compose.yml | 2 +- 6 files changed, 39 insertions(+), 58 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 3cf3b324..63727f32 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -15,8 +15,8 @@ type DbConfig struct { DbPath string `json:"dbPath"` DbLogPath string `json:"dbLogPath"` LogPath string `json:"logPath"` - TgDbHost string `json:"tgDbHost"` - TgCloud bool `json:tgCloud` + TgDbHost string `json:"hostname"` + TgCloud bool `json:"tgCloud"` ConversationAccessRoles []string `json:"conversationAccessRoles"` // DbHostname string `json:"hostname"` // Username string `json:"username"` @@ -32,25 +32,33 @@ type Config struct { // LLMConfig } -func LoadConfig(path string) (Config, error) { - var b []byte - if _, err := os.Stat(path); os.IsNotExist(err) { - // file doesn't exist read from env - cfg := os.Getenv("CONFIG") - if cfg == "" { - fmt.Println("CONFIG path is not found nor is the CONFIG json env variable defined") - os.Exit(1) +func LoadConfig(paths ...string) (Config, error) { + var cfg Config + for _, path := range paths { + var b []byte + if _, err := os.Stat(path); os.IsNotExist(err) { + // file doesn't exist read from env + cfg := os.Getenv("CONFIG") + if cfg == "" { + fmt.Println("CONFIG path is not found nor is the CONFIG json env variable defined") + os.Exit(1) + } + b = []byte(cfg) + } else { + b, err = os.ReadFile(path) + if err != nil { + return Config{}, err + } } - b = []byte(cfg) - } else { - b, err = os.ReadFile(path) - if err != nil { + + if err := json.Unmarshal(b, &cfg); err != nil { return Config{}, err } } + return cfg, nil - var cfg Config - json.Unmarshal(b, &cfg) + // var cfg Config + // json.Unmarshal(b, &cfg) - return cfg, nil + // return cfg, nil } diff --git a/chat-history/main.go b/chat-history/main.go index 8cf8e4ed..72180e2f 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -12,12 +12,15 @@ import ( ) func main() { - configPath := os.Getenv("CONFIG") - config, err := config.LoadConfig(configPath) + configPath := os.Getenv("CONFIG_FILES") + // Split the paths into a slice + configPaths := strings.Split(configPath, ",") + + cfg, err := config.LoadConfig(configPaths...) if err != nil { panic(err) } - db.InitDB(config.DbPath, config.DbLogPath) + db.InitDB(cfg.DbPath, cfg.DbLogPath) // make router router := http.NewServeMux() @@ -30,15 +33,15 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) - router.HandleFunc("GET /get_feedback", routes.GetFeedback(config.TgDbHost, config.ConversationAccessRoles, config.TgCloud)) + router.HandleFunc("GET /get_feedback", routes.GetFeedback(cfg.TgDbHost, cfg.ConversationAccessRoles, cfg.TgCloud)) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" var port string if dev { - port = fmt.Sprintf("localhost:%s", config.Port) + port = fmt.Sprintf("localhost:%s", cfg.Port) } else { - port = fmt.Sprintf(":%s", config.Port) + port = fmt.Sprintf(":%s", cfg.Port) } handler := middleware.ChainMiddleware(router, diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index 818f2903..37f51a2a 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -159,19 +159,11 @@ func auth(userId string, r *http.Request) (string, int, []byte, bool) { // executeGSQL sends a GSQL query to TigerGraph with basic authentication and returns the response func executeGSQL(host, username, password, query string, tgcloud bool) (string, error) { - // Extract the hostname from the URL (ignoring the scheme and port) - // parsedURL, err := url.Parse(host) - // if err != nil { - // return "", fmt.Errorf("invalid host URL: %w", err) - // } - var requestURL string // Construct the URL for the GSQL query endpoint if tgcloud { - // requestURL = fmt.Sprintf("https://%s:443/gsqlserver/gsql/file", parsedURL.Hostname()) requestURL = fmt.Sprintf("%s:443/gsqlserver/gsql/file", host) } else { - // requestURL = fmt.Sprintf("http://%s:14240/gsqlserver/gsql/file", parsedURL.Hostname()) requestURL = fmt.Sprintf("%s:14240/gsqlserver/gsql/file", host) } // Prepare the query data diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index 6bfa7187..e3c8dd3e 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -455,7 +455,7 @@ func TestParseUserRoles(t *testing.T) { - FailedAttempts: 0 - ShowAlterPasswordWarning: false - - Name: richard@epsilla.com + - Name: Lu Zhou - Global Roles: globalobserver - LastSuccessLogin: Tue Jul 23 16:35:45 UTC 2024 - NextValidLogin: Tue Jul 23 16:35:45 UTC 2024 diff --git a/copilot/docs/notebooks/FeedbackAnalysis.ipynb b/copilot/docs/notebooks/FeedbackAnalysis.ipynb index b3c84505..8967fab9 100644 --- a/copilot/docs/notebooks/FeedbackAnalysis.ipynb +++ b/copilot/docs/notebooks/FeedbackAnalysis.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -19,7 +19,7 @@ " }\n", "\n", " \"\"\"Fetch conversation and feedback data.\"\"\"\n", - " feedback_url = f'http://localhost:8000/ui/get_feedback'\n", + " feedback_url = f'http://COPILOT_HOST/ui/get_feedback'\n", " \n", " try:\n", " response = requests.get(feedback_url, headers=headers)\n", @@ -62,29 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "conversation_data = fetch_conversation_feedback(\"supportai\", \"supportai\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'question': 'How many transactions are there?',\n", - " 'answer': 'The total number of transactions is 860142.',\n", - " 'feedback': 1}]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "conversation_data" + "conversation_data = fetch_conversation_feedback(\"DB_USERNAME\", \"DB_PASSWORD\")" ] }, { diff --git a/docker-compose.yml b/docker-compose.yml index a865711c..ea5c9144 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: ports: - 8002:8002 environment: - CONFIG: "/configs/chat_config.json" + CONFIG_FILES: "/configs/chat_config.json,/configs/db_config.json" LOGLEVEL: "INFO" volumes: - ./configs/:/configs From 6691e71635092412b3690a769e3d67d5b05d630b Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 13:18:43 -0700 Subject: [PATCH 06/14] update test case --- chat-history/Makefile | 2 +- chat-history/config/config.go | 14 ++++-------- chat-history/config/config_test.go | 34 +++++++++++++++------------- chat-history/routes/routes_test.go | 36 +++++++++++++++++++++--------- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/chat-history/Makefile b/chat-history/Makefile index 27100d98..09ae863d 100644 --- a/chat-history/Makefile +++ b/chat-history/Makefile @@ -14,6 +14,6 @@ clean: run: clean test build clear - CONFIG="config.json" DEV=true ./chat-history + CONFIG_FILES="chat_config.json,db_config.json" DEV=true ./chat-history diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 63727f32..28a2aebd 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -15,12 +15,11 @@ type DbConfig struct { DbPath string `json:"dbPath"` DbLogPath string `json:"dbLogPath"` LogPath string `json:"logPath"` - TgDbHost string `json:"hostname"` TgCloud bool `json:"tgCloud"` ConversationAccessRoles []string `json:"conversationAccessRoles"` - // DbHostname string `json:"hostname"` - // Username string `json:"username"` - // Password string `json:"password"` + TgDbHost string `json:"hostname"` + Username string `json:"username"` + Password string `json:"password"` // GetToken string `json:"getToken"` // DefaultTimeout string `json:"default_timeout"` // DefaultMemThreshold string `json:"default_mem_threshold"` @@ -38,7 +37,7 @@ func LoadConfig(paths ...string) (Config, error) { var b []byte if _, err := os.Stat(path); os.IsNotExist(err) { // file doesn't exist read from env - cfg := os.Getenv("CONFIG") + cfg := os.Getenv("CONFIG_FILES") if cfg == "" { fmt.Println("CONFIG path is not found nor is the CONFIG json env variable defined") os.Exit(1) @@ -56,9 +55,4 @@ func LoadConfig(paths ...string) (Config, error) { } } return cfg, nil - - // var cfg Config - // json.Unmarshal(b, &cfg) - - // return cfg, nil } diff --git a/chat-history/config/config_test.go b/chat-history/config/config_test.go index ea4c8c0c..91f9b12b 100644 --- a/chat-history/config/config_test.go +++ b/chat-history/config/config_test.go @@ -8,6 +8,10 @@ import ( func TestLoadConfig(t *testing.T) { pth := setup(t) + + // Print the path for debugging + fmt.Println("Configuration file path:", pth) + cfg, err := LoadConfig(pth) if err != nil { t.Fatal(err) @@ -23,25 +27,23 @@ func TestLoadConfig(t *testing.T) { func setup(t *testing.T) string { tmp := t.TempDir() - pth := fmt.Sprintf("%s/%s", tmp, "config.json") + pth := fmt.Sprintf("%s/%s", tmp, "chat_config.json") dat := ` - { - "apiPort":"8000", - "hostname": "http://localhost:14240", - "dbPath": "chats.db", - "dbLogPath": "db.log", - "logPath": "requestLogs.jsonl", - "username": "tigergraph", - "password": "tigergraph", - "getToken": false, - "default_timeout": 300, - "default_mem_threshold": 5000, - "default_thread_limit": 8 + "apiPort":"8000", + "dbPath": "chats.db", + "dbLogPath": "db.log", + "logPath": "requestLogs.jsonl", + "tgCloud": true, + "conversationAccessRoles": ["superuser", "globaldesigner"] + "username": "tigergraph", + "password": "tigergraph", }` - err := os.WriteFile(pth, []byte(dat), 0644) - if err != nil { - t.Fatal("error setting up config.json") + + if err := os.WriteFile(pth, []byte(dat), 0644); err != nil { + t.Fatal("error setting upconfig.json") } + return pth + } diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index e3c8dd3e..db77c4ba 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -2,6 +2,7 @@ package routes import ( "bytes" + "chat-history/config" "chat-history/db" "chat-history/structs" "encoding/base64" @@ -397,13 +398,20 @@ func messageEquals(m, msg structs.Message) bool { } func TestExecuteGSQL(t *testing.T) { - // Replace these values with your actual TigerGraph details - host := "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io" - username := "supportai" - password := "supportai" - query := "SHOW USER" // Replace with an actual GSQL query - response, err := executeGSQL(host, username, password, query, true) + os.Setenv("CONFIG_FILES", "../chat_config.json,../db_config.json") + + configPath := os.Getenv("CONFIG_FILES") + // Split the paths into a slice + configPaths := strings.Split(configPath, ",") + + cfg, err := config.LoadConfig(configPaths...) + if err != nil { + panic(err) + } + query := "SHOW USER" + + response, err := executeGSQL(cfg.TgDbHost, cfg.Username, cfg.Password, query, cfg.TgCloud) if err != nil { t.Fatalf("Failed to execute GSQL query: %v", err) } @@ -481,21 +489,29 @@ func TestParseUserRoles(t *testing.T) { } func TestGetFeedback(t *testing.T) { + + os.Setenv("CONFIG_FILES", "../chat_config.json,../db_config.json") + setupDB(t, true) - testTgDbHost := "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io" - testConversationAccessRoles := []string{"superuser", "globaldesigner"} + configPath := os.Getenv("CONFIG_FILES") + // Split the paths into a slice + configPaths := strings.Split(configPath, ",") + cfg, err := config.LoadConfig(configPaths...) + if err != nil { + panic(err) + } // Create a request with Basic Auth req, err := http.NewRequest("GET", "/get_feedback", nil) if err != nil { t.Fatal(err) } - req.SetBasicAuth("supportai", "supportai") + req.SetBasicAuth(cfg.Username, cfg.Password) // Record the response rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback(testTgDbHost, testConversationAccessRoles, true)) + handler := http.HandlerFunc(GetFeedback(cfg.TgDbHost, cfg.ConversationAccessRoles, cfg.TgCloud)) // Serve the request handler.ServeHTTP(rr, req) From 01c022ecdb5066a98fb50a4ac2e9932b7a3e774e Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 16:59:55 -0700 Subject: [PATCH 07/14] set gsPort dynamically --- chat-history/config/config.go | 1 + chat-history/config/config_test.go | 1 + chat-history/main.go | 2 +- chat-history/routes/routes.go | 8 ++++---- chat-history/routes/routes_test.go | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 28a2aebd..2a6edf41 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -20,6 +20,7 @@ type DbConfig struct { TgDbHost string `json:"hostname"` Username string `json:"username"` Password string `json:"password"` + GsPort string `json:"gsPort"` // GetToken string `json:"getToken"` // DefaultTimeout string `json:"default_timeout"` // DefaultMemThreshold string `json:"default_mem_threshold"` diff --git a/chat-history/config/config_test.go b/chat-history/config/config_test.go index 91f9b12b..c0fb50bd 100644 --- a/chat-history/config/config_test.go +++ b/chat-history/config/config_test.go @@ -38,6 +38,7 @@ func setup(t *testing.T) string { "conversationAccessRoles": ["superuser", "globaldesigner"] "username": "tigergraph", "password": "tigergraph", + "gsPort": "14240" }` if err := os.WriteFile(pth, []byte(dat), 0644); err != nil { diff --git a/chat-history/main.go b/chat-history/main.go index 72180e2f..01d1183d 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -33,7 +33,7 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) - router.HandleFunc("GET /get_feedback", routes.GetFeedback(cfg.TgDbHost, cfg.ConversationAccessRoles, cfg.TgCloud)) + router.HandleFunc("GET /get_feedback", routes.GetFeedback(cfg.TgDbHost, cfg.GsPort, cfg.ConversationAccessRoles, cfg.TgCloud)) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index 37f51a2a..ddca3968 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -158,13 +158,13 @@ func auth(userId string, r *http.Request) (string, int, []byte, bool) { } // executeGSQL sends a GSQL query to TigerGraph with basic authentication and returns the response -func executeGSQL(host, username, password, query string, tgcloud bool) (string, error) { +func executeGSQL(host, username, password, query, gsPort string, tgcloud bool) (string, error) { var requestURL string // Construct the URL for the GSQL query endpoint if tgcloud { requestURL = fmt.Sprintf("%s:443/gsqlserver/gsql/file", host) } else { - requestURL = fmt.Sprintf("%s:14240/gsqlserver/gsql/file", host) + requestURL = fmt.Sprintf("%s:%s/gsqlserver/gsql/file", host, gsPort) } // Prepare the query data data := url.QueryEscape(query) // Encode query using URL encoding @@ -235,7 +235,7 @@ func parseUserRoles(userInfo string, userName string) []string { // GetFeedback retrieves feedback data for conversations // "Get /get_feedback" -func GetFeedback(tgDbHost string, conversationAccessRoles []string, tgCloud bool) http.HandlerFunc { +func GetFeedback(tgDbHost, gsPort string, conversationAccessRoles []string, tgCloud bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { usr, pass, ok := r.BasicAuth() if !ok { @@ -246,7 +246,7 @@ func GetFeedback(tgDbHost string, conversationAccessRoles []string, tgCloud bool } // Verify if the user has the required role - userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER", tgCloud) + userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER", gsPort, tgCloud) if err != nil { reason := []byte(`{"reason":"failed to retrieve feedback data"}`) w.Header().Add("Content-Type", "application/json") diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index db77c4ba..8a3990e8 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -411,7 +411,7 @@ func TestExecuteGSQL(t *testing.T) { } query := "SHOW USER" - response, err := executeGSQL(cfg.TgDbHost, cfg.Username, cfg.Password, query, cfg.TgCloud) + response, err := executeGSQL(cfg.TgDbHost, cfg.Username, cfg.Password, query, cfg.GsPort, cfg.TgCloud) if err != nil { t.Fatalf("Failed to execute GSQL query: %v", err) } @@ -511,7 +511,7 @@ func TestGetFeedback(t *testing.T) { // Record the response rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback(cfg.TgDbHost, cfg.ConversationAccessRoles, cfg.TgCloud)) + handler := http.HandlerFunc(GetFeedback(cfg.TgDbHost, cfg.GsPort, cfg.ConversationAccessRoles, cfg.TgCloud)) // Serve the request handler.ServeHTTP(rr, req) From 79b297e0416ee29aa505527605efc2e0651ec0e9 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 17:08:16 -0700 Subject: [PATCH 08/14] fix the typo in test case --- chat-history/config/config_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chat-history/config/config_test.go b/chat-history/config/config_test.go index c0fb50bd..baf5e5e6 100644 --- a/chat-history/config/config_test.go +++ b/chat-history/config/config_test.go @@ -36,13 +36,10 @@ func setup(t *testing.T) string { "logPath": "requestLogs.jsonl", "tgCloud": true, "conversationAccessRoles": ["superuser", "globaldesigner"] - "username": "tigergraph", - "password": "tigergraph", - "gsPort": "14240" }` if err := os.WriteFile(pth, []byte(dat), 0644); err != nil { - t.Fatal("error setting upconfig.json") + t.Fatal("error setting up chat_config.json") } return pth From d8a109c3e2be7d766b7c4d5fb8a71df97e3b584a Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 17:33:32 -0700 Subject: [PATCH 09/14] fix the packages conflicts --- copilot/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copilot/requirements.txt b/copilot/requirements.txt index d45f2a60..19a5e52e 100644 --- a/copilot/requirements.txt +++ b/copilot/requirements.txt @@ -54,7 +54,7 @@ h11==0.14.0 httpcore==0.18.0 httptools==0.6.0 httpx==0.25.0 -huggingface-hub==0.23.0 +huggingface-hub==0.23.2 ibm-cos-sdk==2.13.6 ibm-cos-sdk-core==2.13.6 ibm-cos-sdk-s3transfer==2.13.6 From 98ddacab418aad071ee630588da88dca3df2b691 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Wed, 31 Jul 2024 17:57:26 -0700 Subject: [PATCH 10/14] update the langchain-milvus package --- copilot/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copilot/requirements.txt b/copilot/requirements.txt index 19a5e52e..da31d5d7 100644 --- a/copilot/requirements.txt +++ b/copilot/requirements.txt @@ -75,7 +75,7 @@ langchain-experimental==0.0.63 langchain-groq==0.1.8 langchain-ibm==0.1.11 langchain-text-splitters==0.2.2 -langchain_milvus==0.1.3 +langchain-milvus==0.1.3 langchain_openai==0.1.19 langchainhub==0.1.20 langdetect==1.0.9 From 7f2f0b12cfd06e7f017218668a995c70adc86217 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Thu, 1 Aug 2024 13:44:05 -0700 Subject: [PATCH 11/14] update the loadconfig file --- chat-history/config/config.go | 79 ++++++++++++++++++++---------- chat-history/config/config_test.go | 52 +++++++++++++------- chat-history/main.go | 13 +++-- chat-history/routes/routes.go | 10 ++-- chat-history/routes/routes_test.go | 16 ++++-- chat-history/structs/structs.go | 2 +- 6 files changed, 112 insertions(+), 60 deletions(-) diff --git a/chat-history/config/config.go b/chat-history/config/config.go index 2a6edf41..e067be00 100644 --- a/chat-history/config/config.go +++ b/chat-history/config/config.go @@ -2,7 +2,6 @@ package config import ( "encoding/json" - "fmt" "os" ) @@ -10,17 +9,20 @@ type LLMConfig struct { ModelName string `json:"model_name"` } -type DbConfig struct { +type ChatDbConfig struct { Port string `json:"apiPort"` DbPath string `json:"dbPath"` DbLogPath string `json:"dbLogPath"` LogPath string `json:"logPath"` - TgCloud bool `json:"tgCloud"` ConversationAccessRoles []string `json:"conversationAccessRoles"` - TgDbHost string `json:"hostname"` - Username string `json:"username"` - Password string `json:"password"` - GsPort string `json:"gsPort"` +} + +type TgDbConfig struct { + Hostname string `json:"hostname"` + Username string `json:"username"` + Password string `json:"password"` + GsPort string `json:"gsPort"` + TgCloud bool `json:"tgCloud"` // GetToken string `json:"getToken"` // DefaultTimeout string `json:"default_timeout"` // DefaultMemThreshold string `json:"default_mem_threshold"` @@ -28,32 +30,55 @@ type DbConfig struct { } type Config struct { - DbConfig + ChatDbConfig + TgDbConfig // LLMConfig } -func LoadConfig(paths ...string) (Config, error) { - var cfg Config - for _, path := range paths { - var b []byte - if _, err := os.Stat(path); os.IsNotExist(err) { - // file doesn't exist read from env - cfg := os.Getenv("CONFIG_FILES") - if cfg == "" { - fmt.Println("CONFIG path is not found nor is the CONFIG json env variable defined") - os.Exit(1) - } - b = []byte(cfg) - } else { - b, err = os.ReadFile(path) - if err != nil { - return Config{}, err - } +func LoadConfig(paths map[string]string) (Config, error) { + var config Config + + // Load database config + if dbConfigPath, ok := paths["chatdb"]; ok { + dbConfig, err := loadChatDbConfig(dbConfigPath) + if err != nil { + return Config{}, err } + config.ChatDbConfig = dbConfig + } - if err := json.Unmarshal(b, &cfg); err != nil { + // Load TigerGraph config + if tgConfigPath, ok := paths["tgdb"]; ok { + tgConfig, err := loadTgDbConfig(tgConfigPath) + if err != nil { return Config{}, err } + config.TgDbConfig = tgConfig + } + + return config, nil +} + +func loadChatDbConfig(path string) (ChatDbConfig, error) { + var dbConfig ChatDbConfig + b, err := os.ReadFile(path) + if err != nil { + return ChatDbConfig{}, err + } + if err := json.Unmarshal(b, &dbConfig); err != nil { + return ChatDbConfig{}, err + } + return dbConfig, nil +} + +func loadTgDbConfig(path string) (TgDbConfig, error) { + var tgConfig TgDbConfig + b, err := os.ReadFile(path) + if err != nil { + return TgDbConfig{}, err + } + if err := json.Unmarshal(b, &tgConfig); err != nil { + return TgDbConfig{}, err } - return cfg, nil + return tgConfig, nil } diff --git a/chat-history/config/config_test.go b/chat-history/config/config_test.go index baf5e5e6..9ddce8c8 100644 --- a/chat-history/config/config_test.go +++ b/chat-history/config/config_test.go @@ -7,41 +7,59 @@ import ( ) func TestLoadConfig(t *testing.T) { - pth := setup(t) + chatConfigPath, tgConfigPath := setup(t) - // Print the path for debugging - fmt.Println("Configuration file path:", pth) - - cfg, err := LoadConfig(pth) + cfg, err := LoadConfig(map[string]string{ + "chatdb": chatConfigPath, + "tgdb": tgConfigPath, + }) if err != nil { t.Fatal(err) } - if cfg.Port != "8000" || - cfg.DbPath != "chats.db" || - cfg.DbLogPath != "db.log" || - cfg.LogPath != "requestLogs.jsonl" { - t.Fatalf("config is wrong, %v", cfg) + if cfg.ChatDbConfig.Port != "8002" || + cfg.ChatDbConfig.DbPath != "chats.db" || + cfg.ChatDbConfig.DbLogPath != "db.log" || + cfg.ChatDbConfig.LogPath != "requestLogs.jsonl" { + t.Fatalf("config is wrong, %v", cfg.ChatDbConfig) + } + + if cfg.TgDbConfig.Hostname != "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io" || + cfg.TgDbConfig.GsPort != "14240" || + cfg.TgDbConfig.TgCloud != true { + t.Fatalf("TigerGraph config is wrong, %v", cfg.TgDbConfig) } } -func setup(t *testing.T) string { +func setup(t *testing.T) (string, string) { tmp := t.TempDir() - pth := fmt.Sprintf("%s/%s", tmp, "chat_config.json") - dat := ` + + chatConfigPath := fmt.Sprintf("%s/%s", tmp, "chat_config.json") + chatConfigData := ` { - "apiPort":"8000", + "apiPort":"8002", "dbPath": "chats.db", "dbLogPath": "db.log", "logPath": "requestLogs.jsonl", - "tgCloud": true, "conversationAccessRoles": ["superuser", "globaldesigner"] }` - if err := os.WriteFile(pth, []byte(dat), 0644); err != nil { + if err := os.WriteFile(chatConfigPath, []byte(chatConfigData), 0644); err != nil { t.Fatal("error setting up chat_config.json") } - return pth + tgConfigPath := fmt.Sprintf("%s/%s", tmp, "db_config.json") + tgConfigData := ` +{ + "hostname": "https://tg-0cdef603-3760-41c3-af6f-41e95afc40de.us-east-1.i.tgcloud.io", + "gsPort": "14240", + "username": "supportai", + "password": "supportai", + "tgCloud": true +}` + if err := os.WriteFile(tgConfigPath, []byte(tgConfigData), 0644); err != nil { + t.Fatal("error setting up tg_config.json") + } + return chatConfigPath, tgConfigPath } diff --git a/chat-history/main.go b/chat-history/main.go index 01d1183d..1f82effd 100644 --- a/chat-history/main.go +++ b/chat-history/main.go @@ -16,11 +16,14 @@ func main() { // Split the paths into a slice configPaths := strings.Split(configPath, ",") - cfg, err := config.LoadConfig(configPaths...) + cfg, err := config.LoadConfig(map[string]string{ + "chatdb": configPaths[0], + "tgdb": configPaths[1], + }) if err != nil { panic(err) } - db.InitDB(cfg.DbPath, cfg.DbLogPath) + db.InitDB(cfg.ChatDbConfig.DbPath, cfg.ChatDbConfig.DbLogPath) // make router router := http.NewServeMux() @@ -33,15 +36,15 @@ func main() { router.HandleFunc("GET /user/{userId}", routes.GetUserConversations) router.HandleFunc("GET /conversation/{conversationId}", routes.GetConversation) router.HandleFunc("POST /conversation", routes.UpdateConversation) - router.HandleFunc("GET /get_feedback", routes.GetFeedback(cfg.TgDbHost, cfg.GsPort, cfg.ConversationAccessRoles, cfg.TgCloud)) + router.HandleFunc("GET /get_feedback", routes.GetFeedback(cfg.TgDbConfig.Hostname, cfg.TgDbConfig.GsPort, cfg.ChatDbConfig.ConversationAccessRoles, cfg.TgDbConfig.TgCloud)) // create server with middleware dev := strings.ToLower(os.Getenv("DEV")) == "true" var port string if dev { - port = fmt.Sprintf("localhost:%s", cfg.Port) + port = fmt.Sprintf("localhost:%s", cfg.ChatDbConfig.Port) } else { - port = fmt.Sprintf(":%s", cfg.Port) + port = fmt.Sprintf(":%s", cfg.ChatDbConfig.Port) } handler := middleware.ChainMiddleware(router, diff --git a/chat-history/routes/routes.go b/chat-history/routes/routes.go index ddca3968..0524f7a4 100644 --- a/chat-history/routes/routes.go +++ b/chat-history/routes/routes.go @@ -158,13 +158,13 @@ func auth(userId string, r *http.Request) (string, int, []byte, bool) { } // executeGSQL sends a GSQL query to TigerGraph with basic authentication and returns the response -func executeGSQL(host, username, password, query, gsPort string, tgcloud bool) (string, error) { +func executeGSQL(hostname, username, password, query, gsPort string, tgcloud bool) (string, error) { var requestURL string // Construct the URL for the GSQL query endpoint if tgcloud { - requestURL = fmt.Sprintf("%s:443/gsqlserver/gsql/file", host) + requestURL = fmt.Sprintf("%s:443/gsqlserver/gsql/file", hostname) } else { - requestURL = fmt.Sprintf("%s:%s/gsqlserver/gsql/file", host, gsPort) + requestURL = fmt.Sprintf("%s:%s/gsqlserver/gsql/file", hostname, gsPort) } // Prepare the query data data := url.QueryEscape(query) // Encode query using URL encoding @@ -235,7 +235,7 @@ func parseUserRoles(userInfo string, userName string) []string { // GetFeedback retrieves feedback data for conversations // "Get /get_feedback" -func GetFeedback(tgDbHost, gsPort string, conversationAccessRoles []string, tgCloud bool) http.HandlerFunc { +func GetFeedback(hostname, gsPort string, conversationAccessRoles []string, tgCloud bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { usr, pass, ok := r.BasicAuth() if !ok { @@ -246,7 +246,7 @@ func GetFeedback(tgDbHost, gsPort string, conversationAccessRoles []string, tgCl } // Verify if the user has the required role - userInfo, err := executeGSQL(tgDbHost, usr, pass, "SHOW USER", gsPort, tgCloud) + userInfo, err := executeGSQL(hostname, usr, pass, "SHOW USER", gsPort, tgCloud) if err != nil { reason := []byte(`{"reason":"failed to retrieve feedback data"}`) w.Header().Add("Content-Type", "application/json") diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index 8a3990e8..a2f76aca 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -405,13 +405,16 @@ func TestExecuteGSQL(t *testing.T) { // Split the paths into a slice configPaths := strings.Split(configPath, ",") - cfg, err := config.LoadConfig(configPaths...) + cfg, err := config.LoadConfig(map[string]string{ + "chatdb": configPaths[0], + "tgdb": configPaths[1], + }) if err != nil { panic(err) } query := "SHOW USER" - response, err := executeGSQL(cfg.TgDbHost, cfg.Username, cfg.Password, query, cfg.GsPort, cfg.TgCloud) + response, err := executeGSQL(cfg.TgDbConfig.Hostname, cfg.TgDbConfig.Username, cfg.TgDbConfig.Password, query, cfg.TgDbConfig.GsPort, cfg.TgDbConfig.TgCloud) if err != nil { t.Fatalf("Failed to execute GSQL query: %v", err) } @@ -498,7 +501,10 @@ func TestGetFeedback(t *testing.T) { // Split the paths into a slice configPaths := strings.Split(configPath, ",") - cfg, err := config.LoadConfig(configPaths...) + cfg, err := config.LoadConfig(map[string]string{ + "chatdb": configPaths[0], + "tgdb": configPaths[1], + }) if err != nil { panic(err) } @@ -507,11 +513,11 @@ func TestGetFeedback(t *testing.T) { if err != nil { t.Fatal(err) } - req.SetBasicAuth(cfg.Username, cfg.Password) + req.SetBasicAuth(cfg.TgDbConfig.Username, cfg.TgDbConfig.Password) // Record the response rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback(cfg.TgDbHost, cfg.GsPort, cfg.ConversationAccessRoles, cfg.TgCloud)) + handler := http.HandlerFunc(GetFeedback(cfg.TgDbConfig.Hostname, cfg.TgDbConfig.GsPort, cfg.ChatDbConfig.ConversationAccessRoles, cfg.TgDbConfig.TgCloud)) // Serve the request handler.ServeHTTP(rr, req) diff --git a/chat-history/structs/structs.go b/chat-history/structs/structs.go index 8a34249e..9f5a3300 100644 --- a/chat-history/structs/structs.go +++ b/chat-history/structs/structs.go @@ -52,7 +52,7 @@ type Message struct { Content string `json:"content"` Role MessagengerRole `json:"role"` ResponseTime float64 `json:"response_time"` - Feedback Feedback `json:"feedback"`// time in fractional seconds (i.e., 1.25 seconds) + Feedback Feedback `json:"feedback"` // time in fractional seconds (i.e., 1.25 seconds) Comment string `json:"comment"` } From 7b8b0450b0145f2aa65a938ccbb425720a56a107 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Thu, 1 Aug 2024 15:23:21 -0700 Subject: [PATCH 12/14] add test case for non-admin and non-existent users --- chat-history/db/db.go | 19 ++++++-- chat-history/routes/routes_test.go | 76 +++++++++++++++++------------- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/chat-history/db/db.go b/chat-history/db/db.go index 28502249..71554076 100644 --- a/chat-history/db/db.go +++ b/chat-history/db/db.go @@ -149,9 +149,10 @@ func populateDB() { // init convos conv1 := uuid.MustParse("601529eb-4927-4e24-b285-bd6b9519a951") + conv2 := uuid.MustParse("601529eb-4927-4e24-b285-bd6b9519a952") db.Create(&structs.Conversation{UserId: "sam_pull", ConversationId: conv1, Name: "conv1"}) - db.Create(&structs.Conversation{UserId: "sam_pull", ConversationId: uuid.New(), Name: "conv2"}) - db.Create(&structs.Conversation{UserId: "Miss_Take", ConversationId: uuid.New(), Name: "conv3"}) + db.Create(&structs.Conversation{UserId: "Miss_Take", ConversationId: conv2, Name: "conv2"}) + // db.Create(&structs.Conversation{UserId: "Miss_Take", ConversationId: uuid.New(), Name: "conv3"}) // add message to convos message := structs.Message{ @@ -164,8 +165,8 @@ func populateDB() { Feedback: structs.NoFeedback, Comment: "", } - db.Create(&message) + m2 := structs.Message{ ConversationId: conv1, MessageId: uuid.New(), @@ -177,4 +178,16 @@ func populateDB() { Comment: "", } db.Create(&m2) + + m3 := structs.Message{ + ConversationId: conv2, + MessageId: uuid.New(), + ParentId: &message.MessageId, + ModelName: "GPT-4o", + Content: "How many transactions?", + Role: structs.SystemRole, + Feedback: structs.NoFeedback, + Comment: "", + } + db.Create(&m3) } diff --git a/chat-history/routes/routes_test.go b/chat-history/routes/routes_test.go index a2f76aca..075bfe49 100644 --- a/chat-history/routes/routes_test.go +++ b/chat-history/routes/routes_test.go @@ -508,45 +508,57 @@ func TestGetFeedback(t *testing.T) { if err != nil { panic(err) } - // Create a request with Basic Auth - req, err := http.NewRequest("GET", "/get_feedback", nil) - if err != nil { - t.Fatal(err) - } - req.SetBasicAuth(cfg.TgDbConfig.Username, cfg.TgDbConfig.Password) + testFeedback := func(t *testing.T, username, password string, expectedStatus int, expectedMessagesCount int, expectedFirstMessageContent string) { + // Create a request with Basic Auth + req, err := http.NewRequest("GET", "/get_feedback", nil) + if err != nil { + t.Fatal(err) + } + req.SetBasicAuth(username, password) - // Record the response - rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetFeedback(cfg.TgDbConfig.Hostname, cfg.TgDbConfig.GsPort, cfg.ChatDbConfig.ConversationAccessRoles, cfg.TgDbConfig.TgCloud)) + // Record the response + rr := httptest.NewRecorder() + handler := http.HandlerFunc(GetFeedback(cfg.TgDbConfig.Hostname, cfg.TgDbConfig.GsPort, cfg.ChatDbConfig.ConversationAccessRoles, cfg.TgDbConfig.TgCloud)) - // Serve the request - handler.ServeHTTP(rr, req) + // Serve the request + handler.ServeHTTP(rr, req) - // Check the response status code - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + // Check the response status code + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + if expectedStatus == http.StatusOK { + // Check the response body for expected messages + var messages []structs.Message + if err := json.Unmarshal(rr.Body.Bytes(), &messages); err != nil { + t.Errorf("Failed to parse response body: %v", err) + } + + // Print the messages for debugging + // fmt.Println("Retrieved messages:", messages) + // Validate that the messages are as expected + // expectedMessagesCount := 2 // Based on populateDB function + if len(messages) != expectedMessagesCount { + t.Errorf("Expected %d messages, got %d", expectedMessagesCount, len(messages)) + } + + // Additional checks to ensure the response contains the correct data + if expectedMessagesCount > 0 && len(messages) > 0 { + if messages[0].Content != expectedFirstMessageContent { + t.Errorf("Unexpected message content: %v", messages[0].Content) + } + } + } } - // Check the response body for expected messages - var messages []structs.Message - if err := json.Unmarshal(rr.Body.Bytes(), &messages); err != nil { - t.Errorf("Failed to parse response body: %v", err) - } + // Test case for admin user + testFeedback(t, "supportai", "supportai", http.StatusOK, 3, "This is the first message, there is no parent") - // Print the messages for debugging - fmt.Println("Retrieved messages:", messages) - // Validate that the messages are as expected - expectedMessagesCount := 2 // Based on populateDB function - if len(messages) != expectedMessagesCount { - t.Errorf("Expected %d messages, got %d", expectedMessagesCount, len(messages)) - } + // Test case for non-admin user + testFeedback(t, "sam_pull", "sam_pull", http.StatusOK, 2, "This is the first message, there is no parent") - // Additional checks to ensure the response contains the correct data - if len(messages) > 0 { - if messages[0].Content != "This is the first message, there is no parent" { - t.Errorf("Unexpected message content: %v", messages[0].Content) - } - } + // Test case for non-existent user + testFeedback(t, "nonexistentuser", "password", http.StatusUnauthorized, 0, "") } // helpers From 4e0fe310bd59da2fe2d5234798d2fdd89f367175 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Fri, 2 Aug 2024 09:33:15 -0700 Subject: [PATCH 13/14] revert pymilvus to 2.3.6 --- copilot/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copilot/requirements.txt b/copilot/requirements.txt index da31d5d7..54b3fd6c 100644 --- a/copilot/requirements.txt +++ b/copilot/requirements.txt @@ -108,7 +108,7 @@ pycryptodome==3.20.0 pydantic==2.3.0 pydantic_core==2.6.3 pygit2==1.13.2 -pymilvus==2.4.4 +pymilvus==2.3.6 pytest==8.2.0 python-dateutil==2.9.0.post0 python-dotenv==1.0.0 From 0ca2da8fb9e282e51ec175aeeaac589e1f9cf1e8 Mon Sep 17 00:00:00 2001 From: Lu Zhou Date: Fri, 2 Aug 2024 09:46:07 -0700 Subject: [PATCH 14/14] test pytest --- copilot/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/copilot/requirements.txt b/copilot/requirements.txt index 54b3fd6c..7a8bd83f 100644 --- a/copilot/requirements.txt +++ b/copilot/requirements.txt @@ -54,7 +54,7 @@ h11==0.14.0 httpcore==0.18.0 httptools==0.6.0 httpx==0.25.0 -huggingface-hub==0.23.2 +huggingface-hub==0.23.0 ibm-cos-sdk==2.13.6 ibm-cos-sdk-core==2.13.6 ibm-cos-sdk-s3transfer==2.13.6 @@ -75,7 +75,7 @@ langchain-experimental==0.0.63 langchain-groq==0.1.8 langchain-ibm==0.1.11 langchain-text-splitters==0.2.2 -langchain-milvus==0.1.3 +langchain_milvus==0.1.3 langchain_openai==0.1.19 langchainhub==0.1.20 langdetect==1.0.9 @@ -108,7 +108,7 @@ pycryptodome==3.20.0 pydantic==2.3.0 pydantic_core==2.6.3 pygit2==1.13.2 -pymilvus==2.3.6 +pymilvus==2.4.4 pytest==8.2.0 python-dateutil==2.9.0.post0 python-dotenv==1.0.0 @@ -152,4 +152,4 @@ wandb==0.15.12 watchfiles==0.20.0 websockets==11.0.3 yarl==1.9.2 -zipp==3.19.2 +zipp==3.19.2 \ No newline at end of file