-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.go
More file actions
138 lines (119 loc) · 4.19 KB
/
api.go
File metadata and controls
138 lines (119 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package genact provides a way for interacting with the Google Genesis
// AI API for text only prompts using the text chat interface. This
// module provides a simple mechanism for sending prompts and recorded
// history (if applicable) and receiving a response and the new history,
// suitable for iterative interactions with Genesis Pro 2.5 taking
// advantage of large Genesis token windows.
package genact
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/google/generative-ai-go/genai"
"google.golang.org/api/option"
)
type ApiResponse struct {
TokenCount int32
LatestResponse string
FullHistory string
}
var logger *log.Logger
// initalise logging
func newLogger(enabled bool) {
writer := io.Discard
if enabled {
writer = os.Stdout
}
logger = log.New(writer, "", log.LstdFlags)
}
// startChat starts a client/model/chat.
func startChat(ctx context.Context, settings map[string]string) (*genai.Client, *genai.ChatSession, error) {
client, err := genai.NewClient(ctx, option.WithAPIKey(settings["apiKey"]))
if err != nil {
return nil, nil, fmt.Errorf("could not create client: %v", err)
}
model := client.GenerativeModel(settings["modelName"])
chat := model.StartChat()
return client, chat, nil
}
// endChat closes a client session.
func endChat(client *genai.Client) {
_ = client.Close()
}
// runAPI runs the api given a *genai.ChatSession, history (if any) and
// a prompt string.
func runAPI(ctx context.Context, chat *genai.ChatSession, history []*genai.Content, prompt string) (*genai.GenerateContentResponse, error) {
chat.History = history
logger.Println("Sending prompt to Gemini API...")
resp, err := chat.SendMessage(ctx, genai.Text(prompt))
if err != nil {
return nil, fmt.Errorf("failed to send message: %v", err)
}
if resp.PromptFeedback != nil && resp.PromptFeedback.BlockReason > 0 {
return nil, fmt.Errorf("received BlockReason: %d", resp.PromptFeedback.BlockReason)
}
if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
return nil, errors.New("received an empty response from the API")
}
logger.Println("response ok")
return resp, nil
}
// parseResponse takes a *genai.ChatSession and a
// *genai.GenerateContentResponse to parse a responseinto a local
// ApiResponse struct for easier handling. Presently only the first
// Candidate is considered (resp.Candidates[0]).
//
// Todo: deal with "isThinking"/"Thinking" responses from the AI API,
// which may not be useful to put into history.
func parseResponse(chat *genai.ChatSession, resp *genai.GenerateContentResponse) (*ApiResponse, error) {
thisResponse := ApiResponse{
TokenCount: resp.UsageMetadata.PromptTokenCount,
}
// expecting only 1 candidate in this code
if len(resp.Candidates) != 1 {
return nil, fmt.Errorf("expected only 1 candidate, got %d", len(resp.Candidates))
}
var LatestResponse strings.Builder
for _, part := range resp.Candidates[0].Content.Parts {
if txt, ok := part.(genai.Text); ok {
LatestResponse.WriteString(string(txt))
}
}
thisResponse.LatestResponse = LatestResponse.String()
if thisResponse.LatestResponse == "" {
return nil, errors.New("latest response had no text content")
}
FullHistory := chat.History
historyJSON, err := json.MarshalIndent(FullHistory, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal new history: %v", err)
}
thisResponse.FullHistory = string(historyJSON)
return &thisResponse, nil
}
// APIGetResponse creates a genai client and chat session, runs the api
// to receive a response, and then puts the response into a local
// ApiResponse struct for convenient processing.
func APIGetResponse(settings map[string]string, history []*genai.Content, prompt string) (*ApiResponse, error) {
if settings == nil {
return nil, errors.New("settings not provided")
}
logging := settings["logging"] != "false"
newLogger(logging)
ctx := context.Background()
client, chat, err := startChat(ctx, settings)
defer endChat(client)
if err != nil {
return nil, fmt.Errorf("could not start chat: %w", err)
}
response, err := runAPI(ctx, chat, history, prompt)
if err != nil {
return nil, fmt.Errorf("chat response error: %w", err)
}
return parseResponse(chat, response)
}