Integração do Google Gemini no Kotlin
A integração de uma IA avançada como o Google Gemini com Kotlin abre um leque de possibilidades para desenvolvedores. Com essa combinação, é possível:
- Automatizar tarefas: Processamento de linguagem natural (NLP) para análise de textos, tradução ou geração de conteúdo dinâmico.
- Aplicações inteligentes: Chatbots, assistentes virtuais ou sistemas de recomendação em apps Android.
- Produtividade no desenvolvimento: Uso de IA para sugestão de código, debugging ou geração de templates em Kotlin.
- Integração com APIs: Acesso aos modelos do Gemini via Google AI SDK para respostas em tempo real.
Neste projeto, integraremos a API do Google Gemini ao nosso site com o objetivo de automatizar a geração de tags que classificam e relacionam experiências e histórias — as chamadas memórias.
O que é uma API e como funciona?
[editar | editar código]Uma API (Application Programming Interface, ou Interface de Programação de Aplicações) é um conjunto de protocolos, rotinas e ferramentas que permite a comunicação entre diferentes sistemas de software. Funcionando como uma "ponte", ela define como um aplicativo pode solicitar dados ou funcionalidades de outro serviço, sem precisar conhecer sua implementação interna.
No contexto deste projeto, a API do Google Gemini atua como intermediária entre nosso sistema em Kotlin e os modelos avançados de IA da Google. Quando nosso servidor envia uma requisição (por exemplo, um texto para análise), a API processa essa informação nos servidores do Gemini e retorna uma resposta estruturada (como tags classificadas ou insights gerados por IA). Essa troca acontece via chamadas HTTP (Protocolo de Transferência de Hipertexto), seguindo padrões RESTful, com dados geralmente trafegando em formatos como JSON (JavaScript Object Notation).
A grande vantagem é que, graças às APIs, podemos integrar inteligência artificial complexa em nosso site com apenas algumas linhas de código, aproveitando a infraestrutura e o poder de processamento da Google sem precisar construir um modelo do zero.
1. Diferenças entre tipos de APIs
[editar | editar código]| API Type | Formato | Protocolo | Flexibilidade | Complexidade | Ideal para... |
|---|---|---|---|---|---|
| REST | JSON/XML | HTTP | Alta | Baixa | Web/mobile APIs |
| SOAP | XML | HTTP/SMTP | Baixa | Alta | Sistemas legados |
| GraphQL | JSON | HTTP | Muito alta | Média | Front-ends dinâmicos |
| gRPC | Protobuf | HTTP/2 | Alta | Alta | Microserviços de alta performance |
O Google Gemini utiliza, por padrão, uma API no estilo RESTful para comunicação com os serviços. Motivos: simples, amplamente suportado, familiar para desenvolvedores e eficaz para a maioria dos casos de uso com IA generativa.
2. API do Google Gemini: Acesso Remoto e Integração
[editar | editar código]A API do Google Gemini permite interagir programaticamente com os modelos de IA generativa da Google de duas formas principais:
- Integração direta via código (nosso caso)
- Através de bots/conversacionais (como um chat integrado)
Fluxo Básico:
- Seu aplicativo → Envia solicitação HTTP para endpoints da API Gemini
- Servidores Google → Processam o prompt usando modelos avançados
- API → Retorna resposta estruturada → Seu aplicativo
// Exemplo em Kotlin
val response = geminiClient.generateContent(
"""
Classifique a seguinte memória em feliz ou triste:
"Meu primeiro dia na praia foi mágico, o pôr do sol parecia pintado"
"""
)
O SDK client (SDK client - pacote de ferramentas e bibliotecas criado para facilitar o uso de uma API específica em uma determinada linguagem de programação) acima faz automaticamente o trabalho HTTP por baixo dos panos:
- Monta o JSON corretamente.
- Envia a requisição HTTP.
- Interpreta a resposta.
- Retorna já convertido pra um objeto Kotlin (
response).
Como seria a chamada direta?
//define o método HTTP e o endpoint da API
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY
Content-Type: application/json //cabeçalho indicando o formato do corpo
Accept: application/json //cabeçalho indicando o formato da resposta
//corpo da requisição em JSON
{
"contents": [
{
"parts": [
{
"text": "Classifique a seguinte memória em feliz ou triste: \"Meu primeiro dia na praia foi mágico, o pôr do sol parecia pintado\""
}
]
}
]
}
3. Processamento de Requisições HTTP
[editar | editar código]Estrutura da Chamada:
- Método: POST (usado para enviar dados a um servidor)
- Host:
https://generativelanguage.googleapis.com - Endpoint:
/v1beta/models/gemini-pro:generateContent - Headers:
Content-Type: application/jsonAccept: application/json
Corpo da Requisição (JSON):
{
"contents": [{
"parts": [{
"text": "Seu prompt aqui..."
}]
}]
}
4. Formato das Respostas (c/d)
[editar | editar código]Estrutura Padrão (JSON):
{
"candidates": [{
"content": {
"parts": [{
"text": "Tags: privado, feliz, espaço aberto, conhecidos, coletiva"
}]
},
"safetyRatings": [...] // Análise de segurança
}]
}
Tags que poderiamos usar:
- Privacidade do usuário:
privado,público - Emoções associadas:
feliz,triste,raiva,esperança - Ambiente:
espaço aberto,espaço fechado - Relação com pessoas:
conhecidos,desconhecidos
- Tipo de experiência:
coletiva,individual
Por que JSON?
- Padrão universal para APIs
- Leve e fácil de parsear
- Estrutura hierárquica clara
- Suporte nativo na maioria das linguagens
5. Cuidados
[editar | editar código]É importante estabelecer uma padronização para o formato das respostas, a fim de facilitar seu processamento. Para isso, é fundamental que o prompt enviado à API seja claro, específico e contenha todo o contexto necessário em cada chamada.
Além disso, é recomendável que as classes responsáveis pelo consumo da API estejam preparadas para lidar com erros, como prompts mal formulados ou respostas em formatos inesperados.
Segurança e uso da API:
[editar | editar código]Para garantir a segurança e o controle individual de uso, recomenda-se que cada usuário crie sua própria chave de API. Isso evita o compartilhamento do mesmo limite diário entre diferentes pessoas e protege dados sensíveis que possam ser trafegados durante as requisições.
Na versão gratuita da API do Google Gemini, é possível utilizar aproximadamente 320.000 tokens por mês, o que equivale a mais de 1.000 prompts curtos. No entanto, alguns cuidados são fundamentais para evitar cobranças inesperadas:
- Acesse o Google Cloud Console;
- Vá em Faturamento > Orçamentos e alertas;
- Configure limites de gasto ou alertas de uso.
Dessa forma, você garante que, ao atingir a cota gratuita, o uso da API será apenas bloqueado temporariamente, e nenhum valor será cobrado automaticamente.
Por fim, é importante que o seu código esteja preparado para lidar com esse tipo de erro, identificando quando a cota for excedida e tratando a falha de forma adequada para o usuário.
Integração no kotlin
[editar | editar código]Agora será apresentado como podemos Acesssar na prática os modelos do Google Gemini no código kotlin usando as bibliotecas do Ktor client e Serialization.
importando dependencias
[editar | editar código]
Para gerenciar as bibliotecas do Projeto usamos o gradle. Inserindo apenas as dependência usadas no exemplo o arquivo build.gradle.kts fica da seguinte forma:
plugins {
kotlin("jvm") version "2.1.10"
kotlin("plugin.serialization") version "2.1.0"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
val ktor_version: String by project
val logback_version: String by project
dependencies {
testImplementation(kotlin("test"))
implementation("io.ktor:ktor-client-logging:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version")
}
tasks.test {
useJUnitPlatform()
}
então basta sincronizar as dependências para que o gradle instale os pacotes necessários no projeto, se você estiver utilizando a IDE IntelliJ você faz isso com o botão no canto superior esquerdo da do gradle
serialização e deserialização
[editar | editar código]
Para poder usar os dados fornecidos pela API do Gemini. A serialização pode converter objetos Kotlin em formato JSON (ou outros como XML), permitindo que os dados sejam transmitidos de forma estruturada por meio de requisições HTTP. Já a desserialização faz o processo inverso, transformando os dados recebidos da API em objetos Kotlin utilizáveis no código. Primeiro precisamos criar uma classe com a mesma estrutura do Objeto JSON colocando a anotação @Serializable indicando que aquela classe será ultilizada para serializar dados, as varaiveis nas classes devem obrigatóriamente ter os mesmos nomes das chaves usadas pela API para que o processo funcione. Como os objetos usados para comunicação com a API tem "sub-objetos" como conteúdo, devemos criar uma classe serializavel para cada objeto, mas o JSON da resposta tambêm contem algums conteudos que podem não ser relevantes como metadados, estes podem ser omitidos da classes se assim desejar.
package gemini_interaction
import kotlinx.serialization.Serializable
@Serializable
class GeminiSender{
val system_instruction: GeminiContent?
val contents: GeminiContent
constructor(instruction: String, content: String ) {
system_instruction = GeminiContent(
arrayOf(
GeminiParts(instruction),
),
role = null
)
contents = GeminiContent(
arrayOf<GeminiParts>(
GeminiParts(content),
),
role = null
)
}
constructor(content: String ) {
system_instruction = null
contents = GeminiContent(
arrayOf<GeminiParts>(
GeminiParts(content),
),
role = null
)
}
}
@Serializable
class GeminiResponse(
val candidates: Array<GeminiCandidate>,
// val promptFeedback: String,
// val usageMetadata: String,
val modelVersion: String,
){
fun firstText(): String {
return candidates[0].content.parts[0].text
}
}
@Serializable
class GeminiCandidate(
val content: GeminiContent,
)
@Serializable
class GeminiContent(
val parts: List<GeminiParts>,
val role : String
)
@Serializable
class GeminiParts(
val text : String
)
Note que a uma classe para enviar os dados e uma para receber, ambas tem uma estrutura similar, alem disso na classes em que os dados seram recebidos a uma função para que possamos utilizar o conteudo relevante de fato de uma maneira menos verbosa.
Encapsulando o Acesso ao cliente HTTP
[editar | editar código]Para facilitar o acesso a biblioteca é conveniente encapsular os detalhe de Acesso à API em uma unica classe usando como métodos os possiveis usos que o programa fará:
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class GeminiAcess(
private val apiKey: String,
private val modelAI: String = "gemini-2.0-flash",
private val apiUrl: String = "https://generativelanguage.googleapis.com"
)
{
private val client = HttpClient(CIO){
install(Logging) {
level = LogLevel.ALL
}
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}
suspend fun textRequest(instruction: String, content: String): String {
val resp = client.post("$apiUrl/v1beta/models/$modelAI:generateContent?key=$apiKey") {
contentType(ContentType.Application.Json)
setBody(
GeminiSender(instruction,content)
)
}
if (resp.status == HttpStatusCode.OK) {
return resp.body<GeminiResponse>().firstText()
}else{
println("error: ${resp.status}")
return "erro"
}
}
fun close() = client.close()
}
Na inicialização do cliente HTTP é colocado CIO como parametro que é a engine que vai fazer as requisições e é informado como será feito o Content Negotiation ou seja transformar strings contendo conteúdo JSON em objetos e vice-vesa. Tambem é possível colocar um logging para as requisições, e dessa forma aparecerá no terminal o JSON recebido e enviado além de outras informações úteis para debug.
Dentro do setbody poderia haver uma string com o JSON a ser usado, mas desta forma instanciando um objeto a legibilidade fica melhor.
Obs: a função que chama o método post é suspend pois é um método suspenso (funciona de maneira assincrona)
Usando a classe
[editar | editar código]depois de criar a classe, fica muito mais simples e intuitivo utilizar o Gemini no programa sem que o codigo principal fique muito poluído:
import org.example.aa.GeminiAcess
suspend fun main() {
val ga = GeminiAcess(System.getenv("GEMINI_API_KEY"))
println(ga.textRequest("Only speak portuguese","what is kotlin?"))
ga.close()
}