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