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".
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.
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.
Lines 25 to 65 in 4eca79e
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.
Lines 31 to 42 in 4eca79e
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.
Lines 45 to 51 in 4eca79e
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.
Lines 110 to 117 in 4eca79e
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:
Lines 128 to 138 in 4eca79e
In main.go, add a /search_chatgpt?q=<query> endpoint that calls the
SearchChatGPT method.
Solution.
Lines 59 to 61 in 4eca79e
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.