Skip to content

Commit

Permalink
📝 feat: enhance http utils (#900)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettladley authored May 25, 2024
1 parent b249013 commit 7f1762e
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 77 deletions.
39 changes: 9 additions & 30 deletions backend/integrations/search/ai.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package search

import (
"bytes"
"errors"
"fmt"
"net/http"
Expand All @@ -11,7 +10,6 @@ import (
"github.com/GenerateNU/sac/backend/config"

"github.com/GenerateNU/sac/backend/utilities"
"github.com/gofiber/fiber/v2"
)

type AIClientInterface interface {
Expand Down Expand Up @@ -55,24 +53,11 @@ func (c *OpenAIClient) CreateEmbedding(items []Searchable) ([]Embedding, error)
return nil, fmt.Errorf("failed to create embedding request body: %w", err)
}

req, err := http.NewRequest(fiber.MethodPost,
"https://api.openai.com/v1/embeddings",
bytes.NewBuffer(embeddingBody))
resp, err := utilities.Request(http.MethodPost, "https://api.openai.com/v1/embeddings", embeddingBody, openAIModifiers(c.Settings)...)
if err != nil {
return nil, fmt.Errorf("failed to create embedding: %w", err)
}

req = utilities.ApplyModifiers(req,
utilities.Authorization(c.Settings.APIKey.Expose()),
utilities.JSON(),
)

resp, err := utilities.HttpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to create embedding: %w", err)
}
defer resp.Body.Close()

var embeddingResultBody CreateEmbeddingResponseBody

err = json.NewDecoder(resp.Body).Decode(&embeddingResultBody)
Expand Down Expand Up @@ -115,23 +100,10 @@ func (c *OpenAIClient) CreateModeration(items []Searchable) ([]ModerationResult,
return nil, fmt.Errorf("failed to create moderation request body: %w", err)
}

req, err := http.NewRequest(fiber.MethodPost,
"https://api.openai.com/v1/moderations",
bytes.NewBuffer(moderationBody))
if err != nil {
return nil, fmt.Errorf("failed to create moderation: %w", err)
}

req = utilities.ApplyModifiers(req,
utilities.Authorization(c.Settings.APIKey.Expose()),
utilities.JSON(),
)

resp, err := utilities.HttpClient.Do(req)
resp, err := utilities.Request(http.MethodPost, "https://api.openai.com/v1/moderations", moderationBody, openAIModifiers(c.Settings)...)
if err != nil {
return nil, fmt.Errorf("failed to create moderation: %w", err)
}
defer resp.Body.Close()

var moderationResultBody CreateModerationResponseBody

Expand All @@ -146,3 +118,10 @@ func (c *OpenAIClient) CreateModeration(items []Searchable) ([]ModerationResult,

return moderationResultBody.Results, nil
}

func openAIModifiers(settings config.OpenAISettings) []utilities.RequestModifier {
return []utilities.RequestModifier{
utilities.Authorization(settings.APIKey.Expose()),
utilities.JSON(),
}
}
50 changes: 9 additions & 41 deletions backend/integrations/search/search.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package search

import (
"bytes"
"fmt"
"net/http"

"github.com/garrettladley/mattress"
"gorm.io/gorm"

"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"

"github.com/GenerateNU/sac/backend/config"
Expand Down Expand Up @@ -77,12 +75,12 @@ func (c *PineconeClient) Seed(db *gorm.DB) error {
return nil
}

func pineconeRequest(settings config.PineconeSettings, req *http.Request) *http.Request {
return utilities.ApplyModifiers(req,
func pineconeModifiers(settings config.PineconeSettings) []utilities.RequestModifier {
return []utilities.RequestModifier{
utilities.HeaderKV("Api-Key", settings.APIKey.Expose()),
utilities.AcceptJSON(),
utilities.JSON(),
)
}
}

type Vector struct {
Expand Down Expand Up @@ -123,21 +121,11 @@ func (c *PineconeClient) Upsert(items []Searchable) error {
return fmt.Errorf("failed to marshal upsert body: %w", err)
}

req, err := http.NewRequest(fiber.MethodPost,
fmt.Sprintf("%s/vectors/upsert", c.PineconeSettings.IndexHost.Expose()),
bytes.NewBuffer(upsertBody))
resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/vectors/upsert", c.PineconeSettings.IndexHost.Expose()), upsertBody, pineconeModifiers(c.PineconeSettings)...)
if err != nil {
return fmt.Errorf("failed to create upsert request: %w", err)
return fmt.Errorf("upsert request failed: %w", err)
}

req = pineconeRequest(c.PineconeSettings, req)

resp, err := utilities.HttpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to upsert to pinecone: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to upsert to pinecone: %s", resp.Status)
}
Expand Down Expand Up @@ -180,20 +168,10 @@ func (c *PineconeClient) Delete(items []Searchable) error {
return fmt.Errorf("failed to marshal delete body: %w", err)
}

req, err := http.NewRequest(fiber.MethodPost,
fmt.Sprintf("%s/vectors/delete", c.PineconeSettings.IndexHost.Expose()),
bytes.NewBuffer(deleteBody))
if err != nil {
return fmt.Errorf("failed to create delete request: %w", err)
}

req = pineconeRequest(c.PineconeSettings, req)

resp, err := utilities.HttpClient.Do(req)
resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/vectors/delete", c.PineconeSettings.IndexHost.Expose()), deleteBody, pineconeModifiers(c.PineconeSettings)...)
if err != nil {
return fmt.Errorf("failed to delete from pinecone: %w", err)
return fmt.Errorf("delete request failed: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to delete from pinecone: %s", resp.Status)
Expand Down Expand Up @@ -251,20 +229,10 @@ func (c *PineconeClient) Search(item Searchable) ([]string, error) {
return []string{}, fmt.Errorf("failed to marshal search body: %w", err)
}

req, err := http.NewRequest(fiber.MethodPost,
fmt.Sprintf("%s/query", c.PineconeSettings.IndexHost.Expose()),
bytes.NewBuffer(searchBody))
resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/query", c.PineconeSettings.IndexHost.Expose()), searchBody, pineconeModifiers(c.PineconeSettings)...)
if err != nil {
return []string{}, fmt.Errorf("failed to create search request: %w", err)
}

req = pineconeRequest(c.PineconeSettings, req)

resp, err := utilities.HttpClient.Do(req)
if err != nil {
return []string{}, fmt.Errorf("failed to search to pinecone: %w", err)
return []string{}, fmt.Errorf("search request failed: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return []string{}, fmt.Errorf("failed to search to pinecone: %w", err)
Expand Down
7 changes: 4 additions & 3 deletions backend/tests/search_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tests

import (
"fmt"
"testing"

"github.com/GenerateNU/sac/backend/config"
Expand Down Expand Up @@ -181,7 +182,7 @@ func TestPineconeSearchWorks(t *testing.T) {

gock.New("https://api.openai.com").
Post("/v1/embeddings").
MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()).
MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())).
MatchHeader("content-type", "application/json").
MatchType("json").
JSON(search.CreateEmbeddingRequestBody{
Expand All @@ -199,7 +200,7 @@ func TestPineconeSearchWorks(t *testing.T) {

gock.New("https://api.openai.com").
Post("/v1/moderations").
MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()).
MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())).
MatchHeader("Content-Type", "application/json").
MatchType("json").
JSON(search.CreateModerationRequestBody{
Expand Down Expand Up @@ -235,7 +236,7 @@ func TestOpenAIEmbeddingWorks(t *testing.T) {

gock.New("https://api.openai.com").
Post("/v1/embeddings").
MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()).
MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())).
MatchHeader("content-type", "application/json").
MatchType("json").
JSON(search.CreateEmbeddingRequestBody{
Expand Down
26 changes: 23 additions & 3 deletions backend/utilities/http.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package utilities

import (
"bytes"
"fmt"
"net/http"
)

var HttpClient *http.Client
var httpClient *http.Client

func init() {
HttpClient = http.DefaultClient
httpClient = &http.Client{}
}

func Request(method string, url string, body []byte, modifiers ...RequestModifier) (*http.Response, error) {
req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil {
return nil, err
}

req = ApplyModifiers(req, modifiers...)

return httpClient.Do(req)
}

type RequestModifier interface {
Expand All @@ -34,7 +47,7 @@ func HeaderKV(key, value string) RequestModifier {
func Authorization(apiKey string) RequestModifier {
return HeaderKVModifier{
Key: "Authorization",
Value: "Bearer " + apiKey,
Value: fmt.Sprintf("Bearer %s", apiKey),
}
}

Expand All @@ -45,6 +58,13 @@ func JSON() RequestModifier {
}
}

func FormURLEncoded() RequestModifier {
return HeaderKVModifier{
Key: "Content-Type",
Value: "application/x-www-form-urlencoded",
}
}

func AcceptJSON() RequestModifier {
return HeaderKVModifier{
Key: "Accept",
Expand Down

0 comments on commit 7f1762e

Please sign in to comment.