/ src / api / gemini.go
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  }