Skip to content
This repository was archived by the owner on Jan 9, 2026. It is now read-only.

Latest commit

 

History

History
192 lines (148 loc) · 7.54 KB

File metadata and controls

192 lines (148 loc) · 7.54 KB

ChatGPT

In this part, you will improve your emoji search engine using OpenAI's GPT API. Thus far, your emoji search engine implements lexical search. Given a query, it looks for all emojis with labels that match the literal strings in the query. In this part, you will use ChatGPT to implement semantic search. The search engine will return emojis that match the meaning of the query. Specifically, when you get a query, you will ask ChatGPT for emojis related to the query. For the query "summer vibes", for example, ChatGPT returns emojis like 🌞, 🌴, and 🍹. These emojis match the meaning of "summer vibes" even though they aren't labeled "summer" or "vibes".

OpenAI Account

First, visit https://platform.openai.com/signup and create an OpenAI account. OpenAI's GPT API is not free to use, but as of this writing, new OpenAI accounts automatically receive $5 in free credit. If you're reading this and the promotion no longer exists, you'll have to set up billing and purchase credits.

Next, visit https://platform.openai.com/account/api-keys and click "Create new secret key". When prompted, enter a name for the secret key (e.g., "emojis") and click "Create secret key". Finally, OpenAI will generate and show you your secret key. Copy this key and store it somewhere, as you won't be able to view the secret key again once you click "Done". If you forget to copy the key, simply delete it and create a new one.

⚠️ Note that this key is secret. Don't share it with anyone and be careful not to push it to a public repository. ⚠️

ChatGPT Component

Next, in a file called chatgpt.go, write a component called ChatGPT with the following interface:

// ChatGPT is a frontend to OpenAI's ChatGPT API.
type ChatGPT interface {
    // Complete returns the ChatGPT completion of the provided prompt.
    Complete(ctx context.Context, prompt string) (string, error)
}

The Complete method receives a prompt (e.g., "1 + 1 = ") and returns ChatGPT's completion of the prompt (e.g., "2"). To implement the method, we recommend using sashabaranov/go-openai, which is OpenAI's officially recommended Go library. In the implementation of the Complete method, first create an OpenAI client by passing your secret key to the openai.NewClient function. Hard code the secret key for now; later we'll provide the key via a config file. Next, call the client's CreateChatCompletion method on the following ChatCompletionRequest to receive ChatGPT's completion of the user provided prompt.

req := openai.ChatCompletionRequest{
    Model: openai.GPT3Dot5Turbo,
    Messages: []openai.ChatCompletionMessage{
        {Role: openai.ChatMessageRoleUser, Content: prompt},
    },
}

Finally, given a response resp from ChatGPT, return the completion resp.Choices[0].Message.Content.

Solution.

NOTE that this solution includes some configuration code (e.g., weaver.WithConfig) that won't make sense until the next section. Feel free to ignore it for now.

workshops/10/chatgpt.go

Lines 25 to 65 in 4eca79e

// ChatGPT is a frontend to OpenAI's ChatGPT API.
type ChatGPT interface {
// Complete returns the ChatGPT completion of the provided prompt.
Complete(ctx context.Context, prompt string) (string, error)
}
// chatgpt implements the ChatGPT component.
type chatgpt struct {
weaver.Implements[ChatGPT]
weaver.WithConfig[config]
}
// config configures the chatgpt component implementation.
type config struct {
// OpenAI API key. You can generate an API key at
// https://platform.openai.com/account/api-keys.
APIKey string `toml:"api_key"`
}
func (gpt *chatgpt) Complete(ctx context.Context, prompt string) (string, error) {
// Check for an API key.
if gpt.Config().APIKey == "" {
return "", fmt.Errorf("ChatGPT api_key not provided")
}
// Issue the ChatGPT request.
client := openai.NewClient(gpt.Config().APIKey)
req := openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleUser, Content: prompt},
},
}
resp, err := client.CreateChatCompletion(ctx, req)
if err != nil {
return "", fmt.Errorf("ChatGPT completion error: %w", err)
}
// Return the completion.
return resp.Choices[0].Message.Content, nil
}

Config

Now, you'll change the ChatGPT component to receive your OpenAI secret key via a config file rather than having to hard code it in the source code. Review the documentation on configuring components. Add a config struct to chatgpt.go with an APIKey field. Use a toml:"api_key" annotation to indicate that the field will be written api_key in the config file.

// config configures the chatgpt component implementation.
type config struct {
    // OpenAI API key. You can generate an API key at
    // https://platform.openai.com/account/api-keys.
    APIKey string `toml:"api_key"`
}

Then, embed weaver.WithConfig[config] in the struct that implements the ChatGPT component.

Solution.

workshops/10/chatgpt.go

Lines 31 to 42 in 4eca79e

// chatgpt implements the ChatGPT component.
type chatgpt struct {
weaver.Implements[ChatGPT]
weaver.WithConfig[config]
}
// config configures the chatgpt component implementation.
type config struct {
// OpenAI API key. You can generate an API key at
// https://platform.openai.com/account/api-keys.
APIKey string `toml:"api_key"`
}

Next, add your secret key to the api_key field in the "emojis/ChatGPT" section of config.toml. Again, be careful not to push your secret key to a public repository.

[serviceweaver]
binary = "./emojis"

["emojis/ChatGPT"]
api_key = "YOUR SECRET KEY GOES HERE"

Finally, update the implementation of the Complete method to read your secret key from the config file, returning an error if the key is missing.

Solution.

workshops/10/chatgpt.go

Lines 45 to 51 in 4eca79e

// Check for an API key.
if gpt.Config().APIKey == "" {
return "", fmt.Errorf("ChatGPT api_key not provided")
}
// Issue the ChatGPT request.
client := openai.NewClient(gpt.Config().APIKey)

Putting It All Together

In searcher.go, add a SearchChatGPT(ctx context.Context, query string) ([]string, error) method to the Searcher component. The SearchChatGPT method acts like the Search method—it receives a query and returns a list of matching emojis—but it returns the emojis suggested by ChatGPT.

To implement the SearchChatGPT method, form a ChatGPT prompt from the user's query and pass it to the ChatGPT.Complete method. You can get creative with the wording of your prompt, but we went with "Give me a list of emojis that related to the query $QUERY. Don't give an explanation.".

Solution.

workshops/10/searcher.go

Lines 110 to 117 in 4eca79e

func (s *searcher) SearchChatGPT(ctx context.Context, query string) ([]string, error) {
// Issue a completion request to ChatGPT.
prompt := fmt.Sprintf("Give me a list of emojis that related to the query %q. Don't give an explanation.", query)
completion, err := s.chatgpt.Get().Complete(ctx, prompt)
if err != nil {
return nil, fmt.Errorf("chatgpt: %w", err)
}
s.Logger(ctx).Debug("ChatGPT completion", "query", query, "completion", completion)

Next, parse and return the emojis from ChatGPT's response. This is surprisingly tricky, as some emojis are composed of multiple unicode code points. We recommend using the https://github.com/rivo/uniseg library to segment ChatGPT's response into a set of graphemes and checking to see if each grapheme is in the emojis map. Here's our solution:

workshops/10/searcher.go

Lines 128 to 138 in 4eca79e

var results []string
seen := map[string]bool{}
graphemes := uniseg.NewGraphemes(completion)
for graphemes.Next() {
emoji := graphemes.Str()
if _, ok := emojis[emoji]; ok && !seen[emoji] {
results = append(results, emoji)
}
seen[emoji] = true
}
return results, nil

In main.go, add a /search_chatgpt?q=<query> endpoint that calls the SearchChatGPT method.

Solution.

workshops/10/main.go

Lines 59 to 61 in 4eca79e

http.HandleFunc("/search_chatgpt", func(w http.ResponseWriter, r *http.Request) {
a.handleSearch(a.searcher.Get().SearchChatGPT, w, r)
})

Finally, build and run your application:

$ weaver generate .
$ go build .
$ weaver multi deploy config.toml

You can curl the /search_chatgpt endpoint directly:

$ curl "localhost:9000/search_chatgpt?q=happy"
["😀","😁","😃","😄","😊","😍","😎","🤗","😻","🌞","🎉","🎊","🎁","🎈","💐","👍","✨","🌟","💫","🌈"]

Though, we strongly recommend you use the web UI provided in Part 4. The UI sends every query to the /search and /search_chatgpt endpoints in parallel and merges the results together.

emoji_search_demo.webm

⬅️ Previous Part     ⚫     Home 🏠