Skip to content

Commit

Permalink
Adds message history to the GenAIRequest
Browse files Browse the repository at this point in the history
Separates out the NPC Request from the GenAIRequest. Updates the NPC Request to include the now required FromID and ToID fields.
  • Loading branch information
igooch committed Mar 4, 2024
1 parent 7fc13bd commit 3af9245
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 24 deletions.
15 changes: 15 additions & 0 deletions examples/simple-genai-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ each request as part of the GenAIRequest structure. The default values for `GenA
to start the chat. The default values for the prompt is an empty string. `NumChats` is the number of
requests made to the `SimEndpoint` and `GenAiEndpoint`. The default value for is `NumChats` is `1`.

If you want to set up the chat with the npc-chat-api from the [Google for Games GenAI](https://github.com/googleforgames/GenAI-quickstart/genai/api/npc_chat_api)
you will need the Game Servers on the same cluster as the GenAI Inference Server. Set either the
`GenAiEndpoint` or `SimEndpoint` to the NPC service `"http://npc-chat-api.genai.svc.cluster.local:80"`.
Set whichever endpoint the pointing to the NPC service to be, either the `GenAiNpc` or `SimNpc`,
to be `"true"`. The `GenAIRequest` to the NPC endpoint only sends the message (prompt), so any
additional context outside of the prompt is ignored. `FromID` is the entity sending messages to NPC,
and `ToID` is the entity receiving the message (the NPC ID).
```
type NPCRequest struct {
Msg string `json:"message,omitempty"`
FromId int `json:"from_id,omitempty"`
ToId int `json:"to_id,omitempty"`
}
```

## Running the Game Server

Once you have modified the `gameserver_autochat.yaml` or `gameserver_manualchat.yaml` to use your
Expand Down
4 changes: 4 additions & 0 deletions examples/simple-genai-server/gameserver_npcchat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ spec:
# GenAiContext is not passed to the npc-chat-api endpoint.
- name: GenAiNpc # False by default. Use GenAiNpc "true" when using the npc-chat-api as the GenAiEndpoint.
value: "true"
- name: FromID # Default is "2".
value: "2"
- name: ToID # Default is "1".
value: "1"
- name: SimEndpoint
value: "http://vertex-chat-api.genai.svc.cluster.local:80"
- name: SimContext
Expand Down
90 changes: 66 additions & 24 deletions examples/simple-genai-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func main() {
numChats := flag.Int("NumChats", 1, "Number of back and forth chats between the sim and genAI")
genAiNpc := flag.Bool("GenAiNpc", false, "Set to true if the GenAIEndpoint is the npc-chat-api endpoint")
simNpc := flag.Bool("SimNpc", false, "Set to true if the SimEndpoint is the npc-chat-api endpoint")
fromID := flag.Int("FromID", 2, "Entity sending messages to the npc-chat-api")
toID := flag.Int("ToID", 1, "Entity receiving messages on the npc-chat-api (the NPC's ID)")

flag.Parse()
if ep := os.Getenv("PORT"); ep != "" {
Expand Down Expand Up @@ -88,6 +90,20 @@ func main() {
}
simNpc = &snpc
}
if fid := os.Getenv("FromID"); fid != "" {
num, err := strconv.Atoi(fid)
if err != nil {
log.Fatalf("Could not parse FromID: %v", err)
}
fromID = &num
}
if tid := os.Getenv("ToID"); tid != "" {
num, err := strconv.Atoi(tid)
if err != nil {
log.Fatalf("Could not parse ToID: %v", err)
}
toID = &num
}

log.Print("Creating SDK instance")
s, err := sdk.NewSDK()
Expand All @@ -101,14 +117,14 @@ func main() {
var simConn *connection
if *simEndpoint != "" {
log.Printf("Creating Sim Client at endpoint %s", *simEndpoint)
simConn = initClient(*simEndpoint, *simContext, "Sim", *simNpc)
simConn = initClient(*simEndpoint, *simContext, "Sim", *simNpc, *fromID, *toID)
}

if *genAiEndpoint == "" {
log.Fatalf("GenAiEndpoint must be specified")
}
log.Printf("Creating GenAI Client at endpoint %s", *genAiEndpoint)
genAiConn := initClient(*genAiEndpoint, *genAiContext, "GenAI", *genAiNpc)
genAiConn := initClient(*genAiEndpoint, *genAiContext, "GenAI", *genAiNpc, *fromID, *toID)

log.Print("Marking this server as ready")
if err := s.Ready(); err != nil {
Expand All @@ -124,7 +140,8 @@ func main() {
var wg sync.WaitGroup
// TODO: Add flag for creating X number of chats
wg.Add(1)
go autonomousChat(*prompt, genAiConn, simConn, *numChats, &wg, sigCtx)
chatHistory := []Message{{Author: simConn.name, Content: *prompt}}
go autonomousChat(*prompt, genAiConn, simConn, *numChats, &wg, sigCtx, chatHistory)
wg.Wait()
}

Expand All @@ -136,47 +153,68 @@ func main() {
os.Exit(0)
}

func initClient(endpoint string, context string, name string, npc bool) *connection {
func initClient(endpoint string, context string, name string, npc bool, fromID int, toID int) *connection {
// TODO: create option for a client certificate
client := &http.Client{}
return &connection{client: client, endpoint: endpoint, context: context, name: name, npc: npc}
return &connection{client: client, endpoint: endpoint, context: context, name: name, npc: npc, fromId: fromID, toId: toID}
}

type connection struct {
client *http.Client
endpoint string // full base URL for API requests
endpoint string // Full base URL for API requests
context string
name string // human readable name for the connection
npc bool // true if the endpoint is the NPC API
name string // Human readable name for the connection
npc bool // True if the endpoint is the NPC API
fromId int // For use with NPC API, sender ID
toId int // For use with NPC API, receiver ID
// TODO: create options for routes off the base URL
}

// For use with Vertex APIs
type GenAIRequest struct {
Context string `json:"context,omitempty"` // Optional, for use with Vertex APIs
Prompt string `json:"prompt,omitempty"` // For use with Vertex APIs
Message string `json:"message,omitempty"` // For use with NPC API
Context string `json:"context,omitempty"` // Optional
Prompt string `json:"prompt,omitempty"`
ChatHistory []Message `json:"messages,omitempty"` // Optional, stores chat history for use with Vertex Chat API
}

// For use with NPC API
type NPCRequest struct {
Msg string `json:"message,omitempty"`
FromId int `json:"from_id,omitempty"`
ToId int `json:"to_id,omitempty"`
}

// Expected format for the NPC endpoint response
type NPCResponse struct {
Response string `json:"response"`
}

func handleGenAIRequest(prompt string, clientConn *connection) (string, error) {
var jsonRequest GenAIRequest
// Conversation history provided to the model in a structured alternate-author form.
// https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-chat
type Message struct {
Author string `json:"author"`
Content string `json:"content"`
}

func handleGenAIRequest(prompt string, clientConn *connection, chatHistory []Message) (string, error) {
var jsonStr []byte
var err error
// If the endpoint is the NPC API, use the json request format specifc to that API
if clientConn.npc {
jsonRequest = GenAIRequest{
Message: prompt,
npcRequest := NPCRequest{
Msg: prompt,
FromId: clientConn.fromId,
ToId: clientConn.toId,
}
jsonStr, err = json.Marshal(npcRequest)
} else {
jsonRequest = GenAIRequest{
Context: clientConn.context,
Prompt: prompt,
genAIRequest := GenAIRequest{
Context: clientConn.context,
Prompt: prompt,
ChatHistory: chatHistory,
}
jsonStr, err = json.Marshal(genAIRequest)
}

jsonStr, err := json.Marshal(jsonRequest)
if err != nil {
return "", fmt.Errorf("unable to marshal json request: %v", err)
}
Expand Down Expand Up @@ -207,7 +245,7 @@ func handleGenAIRequest(prompt string, clientConn *connection) (string, error) {
}

// Two AIs (connection endpoints) talking to each other
func autonomousChat(prompt string, conn1 *connection, conn2 *connection, numChats int, wg *sync.WaitGroup, sigCtx context.Context) {
func autonomousChat(prompt string, conn1 *connection, conn2 *connection, numChats int, wg *sync.WaitGroup, sigCtx context.Context, chatHistory []Message) {
select {
case <-sigCtx.Done():
wg.Done()
Expand All @@ -218,7 +256,7 @@ func autonomousChat(prompt string, conn1 *connection, conn2 *connection, numChat
return
}

response, err := handleGenAIRequest(prompt, conn1)
response, err := handleGenAIRequest(prompt, conn1, chatHistory)
if err != nil {
log.Fatalf("Could not send request: %v", err)
}
Expand All @@ -233,9 +271,12 @@ func autonomousChat(prompt string, conn1 *connection, conn2 *connection, numChat
}
log.Printf("%d %s RESPONSE: %s\n", numChats, conn1.name, response)

chat := Message{Author: conn1.name, Content: response}
chatHistory = append(chatHistory, chat)

numChats -= 1
// Flip between the connection that the response is sent to.
autonomousChat(response, conn2, conn1, numChats, wg, sigCtx)
autonomousChat(response, conn2, conn1, numChats, wg, sigCtx, chatHistory)
}
}

Expand Down Expand Up @@ -266,7 +307,8 @@ func tcpHandleConnection(conn net.Conn, genAiConn *connection) {
txt := scanner.Text()
log.Printf("TCP txt: %v", txt)

response, err := handleGenAIRequest(txt, genAiConn)
// TODO: update with chathistroy
response, err := handleGenAIRequest(txt, genAiConn, nil)
if err != nil {
response = "ERROR: " + err.Error() + "\n"
}
Expand Down

0 comments on commit 3af9245

Please sign in to comment.