blob: a6b83e440d357d7822463c667cfcf7e8c43f034f [file] [log] [blame]
David Crawshaw3b4d2b82025-05-04 10:48:25 -07001package gemini
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10)
11
12// https://ai.google.dev/api/generate-content#request-body
13type Request struct {
14 Contents []Content `json:"contents"`
15 Tools []Tool `json:"tools,omitempty"`
16 SystemInstruction *Content `json:"systemInstruction,omitempty"`
17 GenerationConfig *GenerationConfig `json:"generationConfig,omitempty"`
18 CachedContent string `json:"cachedContent,omitempty"` // format: "cachedContents/{name}"
19 // ToolConfig has been left out because it does not appear to be useful.
20}
21
22// https://ai.google.dev/api/generate-content#response-body
23type Response struct {
24 Candidates []Candidate `json:"candidates"`
25}
26
27type Candidate struct {
28 Content Content `json:"content"`
29}
30
31type Content struct {
32 Parts []Part `json:"parts"`
33}
34
35// Part is a part of the content.
36// This is a union data structure, only one-of the fields can be set.
37type Part struct {
38 Text string `json:"text,omitempty"`
39 FunctionCall *FunctionCall `json:"functionCall,omitempty"`
40 FunctionResponse *FunctionResponse `json:"functionResponse,omitempty"`
41 ExecutableCode *ExecutableCode `json:"executableCode,omitempty"`
42 CodeExecutionResult *CodeExecutionResult `json:"codeExecutionResult,omitempty"`
43 // TODO inlineData
44 // TODO fileData
45}
46
47type FunctionCall struct {
48 Name string `json:"name"`
49 Args map[string]any `json:"args"`
50}
51
52type FunctionResponse struct {
53 Name string `json:"name"`
54 Response map[string]any `json:"response"`
55}
56
57type ExecutableCode struct {
58 Language Language `json:"language"`
59 Code string `json:"code"`
60}
61
62type Language int
63
64const (
65 LanguageUnspecified Language = 0
66 LanguagePython Language = 1 // python >= 3.10 with numpy and simpy
67)
68
69type CodeExecutionResult struct {
70 Outcome Outcome `json:"outcome"`
71 Output string `json:"output"`
72}
73
74type Outcome int
75
76const (
77 OutcomeUnspecified Outcome = 0
78 OutcomeOK Outcome = 1
79 OutcomeFailed Outcome = 2
80 OutcomeDeadlineExceeded Outcome = 3
81)
82
83// https://ai.google.dev/api/generate-content#v1beta.GenerationConfig
84type GenerationConfig struct {
85 ResponseMimeType string `json:"responseMimeType,omitempty"` // text/plain, application/json, or text/x.enum
86 ResponseSchema *Schema `json:"responseSchema,omitempty"` // for JSON
87}
88
89// https://ai.google.dev/api/caching#Tool
90type Tool struct {
91 FunctionDeclarations []FunctionDeclaration `json:"functionDeclarations"`
92 CodeExecution *struct{} `json:"codeExecution,omitempty"` // if present, enables the model to execute code
93 // TODO googleSearchRetrieval https://ai.google.dev/api/caching#GoogleSearchRetrieval
94}
95
96// https://ai.google.dev/api/caching#FunctionDeclaration
97type FunctionDeclaration struct {
98 Name string `json:"name"`
99 Description string `json:"description"`
100 Parameters Schema `json:"parameters"`
101}
102
103// https://ai.google.dev/api/caching#Schema
104type Schema struct {
105 Type DataType `json:"type"`
106 Format string `json:"string,omitempty"` // for NUMBER type: float, double for INTEGER type: int32, int64 for STRING type: enum
107 Description string `json:"description,omitempty"`
108 Nullable *bool `json:"nullable,omitempty"`
109 Enum []string `json:"enum,omitempty"`
110 MaxItems string `json:"maxItems,omitempty"` // for ARRAY
111 MinItems string `json:"minItems,omitempty"` // for ARRAY
112 Properties map[string]Schema `json:"properties,omitempty"` // for OBJECT
113 Required []string `json:"required,omitempty"` // for OBJECT
114 Items *Schema `json:"items,omitempty"` // for ARRAY
115}
116
117type DataType int
118
119const (
120 DataTypeUNSPECIFIED = DataType(0) // Not specified, should not be used.
121 DataTypeSTRING = DataType(1)
122 DataTypeNUMBER = DataType(2)
123 DataTypeINTEGER = DataType(3)
124 DataTypeBOOLEAN = DataType(4)
125 DataTypeARRAY = DataType(5)
126 DataTypeOBJECT = DataType(6)
127)
128
129const defaultEndpoint = "https://generativelanguage.googleapis.com/v1beta"
130
131type Model struct {
132 Model string // e.g. "models/gemini-1.5-flash"
133 APIKey string
134 HTTPC *http.Client // if nil, http.DefaultClient is used
135 Endpoint string // if empty, DefaultEndpoint is used
136}
137
138func (m Model) GenerateContent(ctx context.Context, req *Request) (*Response, error) {
139 reqBytes, err := json.Marshal(req)
140 if err != nil {
141 return nil, fmt.Errorf("marshaling request: %w", err)
142 }
143 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/%s:generateContent?key=%s", m.endpoint(), m.Model, m.APIKey), bytes.NewReader(reqBytes))
144 if err != nil {
145 return nil, fmt.Errorf("creating HTTP request: %w", err)
146 }
147 httpReq.Header.Add("Content-Type", "application/json")
148 httpResp, err := m.httpc().Do(httpReq)
149 if err != nil {
150 return nil, fmt.Errorf("GenerateContent: do: %w", err)
151 }
152 defer httpResp.Body.Close()
153 body, err := io.ReadAll(httpResp.Body)
154 if err != nil {
155 return nil, fmt.Errorf("GenerateContent: reading response body: %w", err)
156 }
157 if httpResp.StatusCode != http.StatusOK {
158 return nil, fmt.Errorf("GenerateContent: HTTP status: %d, %s", httpResp.StatusCode, string(body))
159 }
160 var res Response
161 if err := json.Unmarshal(body, &res); err != nil {
162 return nil, fmt.Errorf("GenerateContent: unmarshaling response: %w, %s", err, string(body))
163 }
164 return &res, nil
165}
166
167func (m Model) endpoint() string {
168 if m.Endpoint != "" {
169 return m.Endpoint
170 }
171 return defaultEndpoint
172}
173
174func (m Model) httpc() *http.Client {
175 if m.HTTPC != nil {
176 return m.HTTPC
177 }
178 return http.DefaultClient
179}