gemini.go
1 package api 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "os" 10 "time" 11 ) 12 13 type GeminiClient struct { 14 client *http.Client 15 projectID string 16 location string 17 } 18 19 func NewGeminiClient() (*GeminiClient, error) { 20 projectID := os.Getenv("GOOGLE_CLOUD_PROJECT") 21 if projectID == "" { 22 return nil, errors.New("GOOGLE_CLOUD_PROJECT environment variable not set") 23 } 24 25 return &GeminiClient{ 26 client: &http.Client{}, 27 projectID: projectID, 28 location: "us-central1", 29 }, nil 30 } 31 32 func (g *GeminiClient) ResearchTopic(topic string, depth uint32) (string, error) { 33 prompt := fmt.Sprintf( 34 `Research this topic in depth. 35 Topic: %s 36 Research depth level: %d 37 38 Requirements: 39 1. Find latest developments (last 6 months) 40 2. Identify key researchers and labs 41 3. Link to papers and code repositories 42 4. Note technical limitations 43 5. Suggest promising directions 44 45 Format as a detailed technical analysis. 46 Include links to sources.`, 47 topic, 48 depth, 49 ) 50 51 url := fmt.Sprintf( 52 "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/gemini-1.5-pro-002:streamGenerateContent", 53 g.location, g.projectID, g.location, 54 ) 55 56 payload := map[string]interface{}{ 57 "contents": []map[string]interface{}{ 58 { 59 "role": "user", 60 "parts": []map[string]interface{}{ 61 { 62 "text": prompt, 63 }, 64 }, 65 }, 66 }, 67 "generation_config": map[string]interface{}{ 68 "temperature": 0.5, 69 "topP": 0.95, 70 "maxOutputTokens": 8192, 71 }, 72 "safety_settings": []map[string]interface{}{ 73 { 74 "category": "HARM_CATEGORY_HATE_SPEECH", 75 "threshold": "BLOCK_NONE", 76 }, 77 { 78 "category": "HARM_CATEGORY_DANGEROUS_CONTENT", 79 "threshold": "BLOCK_NONE", 80 }, 81 { 82 "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", 83 "threshold": "BLOCK_NONE", 84 }, 85 { 86 "category": "HARM_CATEGORY_HARASSMENT", 87 "threshold": "BLOCK_NONE", 88 }, 89 }, 90 } 91 92 jsonPayload, err := json.Marshal(payload) 93 if err != nil { 94 return "", fmt.Errorf("error marshaling payload: %v", err) 95 } 96 97 req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload)) 98 if err != nil { 99 return "", fmt.Errorf("error creating request: %v", err) 100 } 101 102 req.Header.Set("Content-Type", "application/json") 103 104 timeout := 30 * time.Second 105 g.client.Timeout = timeout 106 107 resp, err := g.client.Do(req) 108 if err != nil { 109 return "", fmt.Errorf("request error: %v", err) 110 } 111 defer resp.Body.Close() 112 113 if resp.StatusCode != http.StatusOK { 114 return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) 115 } 116 117 var jsonResponse map[string]interface{} 118 if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { 119 return "", fmt.Errorf("error decoding JSON response: %v", err) 120 } 121 122 candidates, ok := jsonResponse["candidates"].([]interface{}) 123 if !ok || len(candidates) == 0 { 124 return "", errors.New("invalid response structure: no candidates") 125 } 126 127 firstCandidate, ok := candidates[0].(map[string]interface{}) 128 if !ok { 129 return "", errors.New("invalid response structure: first candidate not a map") 130 } 131 132 content, ok := firstCandidate["content"].(map[string]interface{}) 133 if !ok { 134 return "", errors.New("invalid response structure: no content") 135 } 136 137 parts, ok := content["parts"].([]interface{}) 138 if !ok || len(parts) == 0 { 139 return "", errors.New("invalid response structure: no parts") 140 } 141 142 part, ok := parts[0].(map[string]interface{}) 143 if !ok { 144 return "", errors.New("invalid response structure: first part not a map") 145 } 146 147 text, ok := part["text"].(string) 148 if !ok { 149 return "", errors.New("invalid response structure: no text in part") 150 } 151 152 return text, nil 153 }