Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8be0615
Added go server
priankakariat Oct 4, 2024
4be4456
Added readme for go server
priankakariat Oct 4, 2024
95b2d7e
Fixed documentation and read me of go files
priankakariat Oct 4, 2024
a7bc461
Updated readme for go server
priankakariat Oct 4, 2024
0231060
Updated documentation of json.go
priankakariat Oct 4, 2024
bb41cba
Updated formatting of license in go files
priankakariat Oct 4, 2024
233135c
Added cors middleware handler to go server
priankakariat Oct 4, 2024
c2ffef4
Fixed readme for go server
priankakariat Oct 4, 2024
c0b4793
Update README.md
priankakariat Oct 4, 2024
e6ae3ac
Update README.md
priankakariat Oct 4, 2024
b6e7341
Update README.md to skip to manual install for Go
priankakariat Oct 4, 2024
6b4c610
Update README.md
priankakariat Oct 4, 2024
f8cb2af
Update README.md
priankakariat Oct 4, 2024
a4d7518
Update README.md
priankakariat Oct 4, 2024
f2328a4
Update README.md
priankakariat Oct 4, 2024
9e82320
Update README.md
priankakariat Oct 4, 2024
9e383b5
Update README.md
priankakariat Oct 4, 2024
873f294
Update README.md
priankakariat Oct 4, 2024
7f68b8d
Update README.md
priankakariat Oct 4, 2024
7235a45
Update README.md
priankakariat Oct 4, 2024
61dbb0f
Update README.md
priankakariat Oct 4, 2024
af01e1f
Update README.md
priankakariat Oct 4, 2024
54e4b23
Update README.md
priankakariat Oct 4, 2024
46a2d48
Updated documentation of main.go
priankakariat Oct 4, 2024
5c5a7fd
Merge branch 'server-go' of https://github.com/priankakariatyml/examp…
priankakariat Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Updated documentation of main.go
  • Loading branch information
priankakariat committed Oct 4, 2024
commit 46a2d4809e519ab5872179b2981a23c9fc907c9f
67 changes: 37 additions & 30 deletions server-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,25 @@ const modelName = "gemini-1.5-flash"
const defaultPort = "9000"

// Server state holding the context of the Gemini client and the generative model.
type geminiServer struct {
type genaiServer struct {
ctx context.Context
model *genai.GenerativeModel
}

func main() {
ctx := context.Background()

// Access your API key as an environment variable to create a client.
apiKey := os.Getenv("GOOGLE_API_KEY")
client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey))
if err != nil {
log.Fatalf("Could not create Gemini client %v", err)
log.Fatalf("could not create Gemini client %v", err)
}
defer client.Close()

model := client.GenerativeModel(modelName)

server := &geminiServer{
server := &genaiServer{
ctx: ctx,
model: model,
}
Expand All @@ -64,7 +65,6 @@ func main() {
AllowedOrigins: []string{"*"},
AllowedHeaders: []string{"Access-Control-Allow-Origin", "Content-Type"},
})

handler := c.Handler(mux)

// Access preferred port the server must listen to as an environment variable if provided.
Expand All @@ -74,20 +74,26 @@ func main() {
log.Fatal(http.ListenAndServe(addr, handler))
}

// part is a piece of model content. It can hold only text pieces. Each item in the `Parts` of a
// JSON-encoded content in a history array must comply to it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand this, but it seems important: "Each item in the Parts of a
// JSON-encoded content in a history array must comply to it." Can you maybe clarify or rephrase?

type part struct {
// Piece of model content.
Text string
}

// content is the structure to which each item in the incoming JSON-encoded history must comply to.
// content is the structure to which each item in the incoming JSON-encoded history array must
// comply to.
type content struct {
Role string
// The producer of the content. Must be either 'user' or 'model'.
Role string
// Ordered `Parts` that constitute a single message.
Parts []part
}

// chatRequest is the structure to which the incoming JSON-encoded value in the response body is
// decoded.
type chatRequest struct {
// The query from the user to the model and the history
// The query from the user to the model.
Chat string
// The history of the conversation between the user and the model in the current session.
History []content
Expand All @@ -97,12 +103,12 @@ type chatRequest struct {
// the request with the following format:
// Request:
// - chat: string
// - history: Array
// - history: []
//
// Returns a JSON payload containing the model response with the following format.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not actually returning anything, right? It's writing the response to http.ResponseWriter. Consider clarifying the comment.

// Response:
// - text: string
func (gs *geminiServer) chatHandler(w http.ResponseWriter, r *http.Request) {
func (gs *genaiServer) chatHandler(w http.ResponseWriter, r *http.Request) {
cr := &chatRequest{}
if err := parseRequestJSON(r, cr); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
Expand All @@ -128,10 +134,10 @@ func (gs *geminiServer) chatHandler(w http.ResponseWriter, r *http.Request) {
// JSON payload in the request with the following format:
// Request:
// - chat: string,
// - history: Array,
// - history: [],
//
// A partial response from the model is text.
func (gs *geminiServer) streamingChatHandler(w http.ResponseWriter, r *http.Request) {
// A partial response from the model contains a piece of text.
func (gs *genaiServer) streamingChatHandler(w http.ResponseWriter, r *http.Request) {
cr := &chatRequest{}
if err := parseRequestJSON(r, cr); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
Expand All @@ -149,13 +155,13 @@ func (gs *geminiServer) streamingChatHandler(w http.ResponseWriter, r *http.Requ
break
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
break
}

resTxt, err := responseString(res)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
break
}

Expand All @@ -167,24 +173,24 @@ func (gs *geminiServer) streamingChatHandler(w http.ResponseWriter, r *http.Requ
}

// startChat starts a chat session with the model using the given history.
func (gs *geminiServer) startChat(hist []content) *genai.ChatSession {
func (gs *genaiServer) startChat(hist []content) *genai.ChatSession {
cs := gs.model.StartChat()
cs.History = encodeHistory(hist)
cs.History = transform(hist)
return cs
}

// encodeHistory converts []content to a []*genai.Content that is accepted by the model's chat session.
func encodeHistory(cs []content) []*genai.Content {
// transform converts []content to a []*genai.Content that is accepted by the model's chat session.
func transform(cs []content) []*genai.Content {
gcs := make([]*genai.Content, len(cs))
for i, c := range cs {
gcs[i] = c.geminiCompatible()
gcs[i] = c.transform()
}

return gcs
}

// geminiCompatible converts content to genai.Content accepted by the chat session.
func (c *content) geminiCompatible() *genai.Content {
// transform converts content to genai.Content that is accepted by the model's chat session.
func (c *content) transform() *genai.Content {
gc := &genai.Content{}
gc.Role = c.Role
ps := make([]genai.Part, len(c.Parts))
Expand All @@ -195,33 +201,34 @@ func (c *content) geminiCompatible() *genai.Content {
return gc
}

// responseString parses the model response of type genai.GenerateContentResponse to a string.
func responseString(resp *genai.GenerateContentResponse) (string, error) {
// responseString converts the model response of type genai.GenerateContentResponse to a string.
func responseString(res *genai.GenerateContentResponse) (string, error) {
// Only taking the first candidate since GenerationConfig.CandidateCount defaults to 1.
if len(resp.Candidates) > 0 {
if cs := contentString(resp.Candidates[0].Content); cs != nil {
if len(res.Candidates) > 0 {
if cs := contentString(res.Candidates[0].Content); cs != nil {
return *cs, nil
}
}

return "", fmt.Errorf("invalid response from Gemini model")
}

// contentString converts genai.Content to a string.
// contentString converts genai.Content to a string. If the parts in the input content are of type
// text, they are concatenated with new lines in between them to form a string.
func contentString(c *genai.Content) *string {
if c == nil || c.Parts == nil {
return nil
}

contStrs := make([]string, len(c.Parts))
cStrs := make([]string, len(c.Parts))
for i, part := range c.Parts {
if pt, ok := part.(genai.Text); ok {
contStrs[i] = string(pt)
cStrs[i] = string(pt)
} else {
return nil
}
}

contStr := strings.Join(contStrs, "\n")
return &contStr
cStr := strings.Join(cStrs, "\n")
return &cStr
}