OpenAi con Android (Retrofit + Dagger Hilt + Compose UI)

OpenAi con Android (Retrofit + Dagger Hilt + Compose UI)

En este artículo hablaremos sobre OpenAI y cómo integrar esta tecnología con Android usando bibliotecas Jetpack como Dagger Hilt, Compose UI y Retrofit para la capa de comunicación con servidores OpenAI.

En primer lugar, ¿qué es OpenAI y cuál es su importancia en la tecnología de aplicaciones?

OpenAI es una biblioteca de inteligencia artificial que proporciona varios modelos de lenguaje avanzados, como GPT-3 y sus variantes. La importancia de OpenAI en la tecnología de aplicaciones está relacionada con la capacidad de sus modelos de lenguaje para comprender y generar texto de forma sofisticada. Estos modelos pueden ser utilizados en una amplia gama de aplicaciones, como chatbots, asistentes virtuales, sistemas de traducción automática, generación de contenidos, análisis de datos, entre otros.

Al aprovechar los modelos OpenAI, los desarrolladores de aplicaciones pueden proporcionar una interacción más natural y efectiva con los usuarios, permitiendo una comprensión avanzada del lenguaje natural y una generación de texto más precisa y coherente. Esto puede mejorar la experiencia del usuario, aumentar la productividad y permitir el desarrollo de aplicaciones más potentes y complejas.

Además, OpenAI juega un papel importante en la investigación y el avance de la IA. Sus modelos de lenguaje se encuentran entre los más avanzados del mundo y han impulsado el desarrollo de nuevos enfoques y aplicaciones en el área. La compañía también está comprometida con la ética y seguridad de la IA, buscando garantizar que la tecnología se utilice de manera responsable y por el bien de la sociedad.

Para este artículo usaremos el modelo text-davinci-003, uno de los más efectivos y complejos, con un lenguaje fuerte y muy natural, pero también es uno de los más caros, por lo que usaremos el gratuito. Valor que ofrece OpenAI al crear nuestra cuenta. Crea el tuyo en https://platform.openai.com/.

Comunicarse con OpenAI es muy sencillo, utilizamos una solicitud que pone a disposición la plataforma y enviamos el atributo llamado prompt, correspondiente al input de la pregunta que queremos hacer a los modelos de IA (Inteligencia Artificial o Inteligencia Artificial) en el cuerpo de Esta petición. El resultado se devuelve en un formato de mensaje de cadena que podemos usar en nuestras aplicaciones y sitios web.

¿Qué es Jetpack de Android?

Jetpack es un conjunto de bibliotecas, herramientas y pautas ofrecidas por Google para facilitar el desarrollo de aplicaciones de Android. Su objetivo es simplificar las tareas comunes, proporcionar mejores prácticas y ofrecer componentes listos para usar, lo que permite a los desarrolladores crear aplicaciones de alta calidad de manera más eficiente. En este artículo usaremos 2 bibliotecas recomendadas por JetPack:

Dagger Hilt: Dagger Hilt es un marco de inyección de dependencias para el desarrollo de aplicaciones de Android. La inyección de dependencia es un patrón de diseño que permite que las dependencias de un objeto se proporcionen a través de una fuente externa en lugar de crearse internamente. Esto ayuda a mejorar la modularidad, la capacidad de prueba y la reutilización del código, además de hacer que la aplicación sea más fácil de mantener y comprender.

Compose: Compose es un framework de interfaz de usuario y ofrece un enfoque declarativo y reactivo para crear interfaces de usuario, lo que significa que usted describe cómo debe mostrarse la interfaz en función del estado actual de la aplicación. En lugar de crear y manipular manualmente vistas tradicionales de Android a través de diseños XML (como TextViews e ImageViews), utiliza los componentes dinámicos de Compose para definir la apariencia y el comportamiento de la interfaz.

Creación de la app

Crearemos una aplicación que recibe una pregunta escrita por el usuario a través de un campo de texto y proporciona un botón en el que el usuario puede hacer clic para obtener la respuesta a su pregunta. La pregunta se realizará mediante una petición con un prompt al modelo OpenAI text-davinci-003, que devolverá la respuesta que mostraremos en pantalla al usuario. Para crear esta aplicación usaremos Compose, Dagger Hilt y Retrofit para comunicarnos con OpenAI.

Etapa 1: Crea tu cuenta en la plataforma OpenAI y continúa el camino Personal > View API Keys > Create new secret key > Create secret key para generar tu clave de acceso. Es muy importante copiar y pegar el valor que aparecerá porque será tu única oportunidad de hacerlo. Si lo olvidas, borra la clave y crea una nueva.

Etapa 2: Crea un nuevo proyecto de Android y asegúrate de tener las siguientes dependencias en build.gradle a nivel de aplicación:

// Dependências do Android Compose

implementation 'androidx.compose.ui:ui:1.4.3'

implementation 'androidx.compose.material:material:1.4.3'

implementation 'androidx.compose.runtime:runtime:1.4.3'

implementation 'androidx.compose.ui:ui-tooling:1.4.3'

implementation 'androidx.compose.foundation:foundation:1.4.3'

implementation 'androidx.compose.material:material-icons-extended:1.4.3'

implementation 'androidx.activity:activity-compose:1.7.2'

implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'


// Dependências do Retrofit

implementation 'com.squareup.retrofit2:retrofit:2.9.0'

implementation 'com.squareup.retrofit2:converter-gson:2.9.0'


// Dagger Hilt

implementation "com.google.dagger:hilt-android:2.44"

kapt "com.google.dagger:hilt-compiler:2.44"


Igualmente con los plugins:

plugins {

   id 'com.android.application'

   id 'org.jetbrains.kotlin.android'

   id 'kotlin-kapt'

   id 'com.google.dagger.hilt.android'

}


Cerciórate de tener los plugins en build.gradle:

plugins {

   id 'com.android.application' version '8.0.1' apply false

   id 'com.android.library' version '8.0.1' apply false

   id 'org.jetbrains.kotlin.android' version '1.8.20' apply false

   id 'com.google.dagger.hilt.android' version '2.44' apply false

}


Etapa 3: Para crear la estructura de Dagger Hilt, primero crearemos una aplicación personalizada, extendiendo la aplicación llamada OpenAIApplication y añadiendo la anotación @HiltAndroidApp:

@HiltAndroidApp

class OpenAIApplication: Application()


En AndroidManifest.xml, debemos configurar <application …>...</application> con el nombre de nuestra clase de aplicación personalizada. Aprovechemos y agreguemos también el permiso para conectarnos a Internet, ya que será necesario comunicarnos con el servidor OpenAI.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

   xmlns:tools="http://schemas.android.com/tools">


   <uses-permission android:name="android.permission.INTERNET"/>


   <application

       android:name=".OpenAIApplication"

               ...>

         ...

       </application>

</manifest>


Etapa 4: Para crear la capa de comunicación con el servidor OpenAI, primero crearemos 2 modelos de datos, comenzando con los datos de solicitud para OpenAI:

data class CompletionRequest(

   @SerializedName("model")

   val model: String,

   @SerializedName("prompt")

   val prompt: String,

   @SerializedName("max_tokens")

   val maxTokens: Int,

   @SerializedName("temperature")

   var temperature: Float,

   @SerializedName("top_p")

   val topP: Float,

   @SerializedName("frequency_penalty")

   val frequencyPenalty: Int,

   @SerializedName("presence_penalty")

   val presencePenalty: Int,

   @SerializedName("stop")

   val stop: String

)


El atributo prompt será el valor que enviaremos como entrada para la pregunta que el usuario le hará a la IA. El atributo model Corresponde al modelo de IA (Inteligencia Artificial) que queremos utilizar. Los demás atributos son opcionales y se pueden utilizar para realizar solicitudes más detalladas y complejas.

También necesitamos modelar el objeto de respuesta que recibiremos de la solicitud de OpenAI, por lo que crearemos el data class CompletionResponse que contiene los atributos que se muestran en la documentación de OpenAI.

data class CompletionResponse(

   @SerializedName("choices")

   val choices: List<Choice>,


   @SerializedName("created")

   val created: Int,


   @SerializedName("id")

   val id: String,


   @SerializedName("model")

   val model: String,


   @SerializedName("object")

   val `object`: String,

) {

   data class Choice(

       val text: String,

       val finishReason: String,

       val index: Int

   )

}


El subobjeto Choice y su atributo text será de hecho la respuesta dada por el modelo de IA a la pregunta formulada a través del mensaje. Ten en cuenta que OpenAI devuelve una lista con varias respuestas, pero siempre usaremos la primera para fines de estudio.

Ahora crearemos una interfaz OpenAiApi para crrar la requisición con el método POST:

interface OpenAiApi {

   companion object {

       const val BASE_URL = "https://api.openai.com/v1/"

       const val API_KEY = "..."

   }


   @POST("completions")

   suspend fun getMathAnswer(

       @Body completionRequest: CompletionRequest

   ): CompletionResponse

}


Nota: No es seguro tener tu Api Key explícitamente dentro del código, pero en este caso lo haremos así porque éste no es el asunto de este artículo.

Ahora creamos un objeto AppModule para ser el Singleton que construye el gráfico de dependencia. Para esto usamos la notación @Module y @InstallIn(SingletonComponent::class). Y dentro de este módulo proporcionaremos la @Provide del servicio Retrofit que implementa nuestra interfaz OpenAiApi.

@Module

@InstallIn(SingletonComponent::class)

object AppModule {


   @Provides

   @Singleton

   fun provideApiService(): OpenAiApi {

       val retrofit = Retrofit.Builder()

           .baseUrl(OpenAiApi.BASE_URL)

           .client(

               OkHttpClient.Builder()

                   .addInterceptor { chain ->

                       val newRequest = chain.request().newBuilder()

                           .addHeader("Authorization", "Bearer ${OpenAiApi.API_KEY}")

                           .build()

                       chain.proceed(newRequest)

                   }

                   .build()

           )

           .addConverterFactory(GsonConverterFactory.create())

           .build()


       return retrofit.create(OpenAiApi::class.java)

   }

}


Etapa 5: Para completar nuestra capa de comunicación con la API OpenAI, creemos nuestra Data Source (fuente de datos) y nuestro Repository (repositorio que dispone de datos a partir de alguna fuente).

Iniciaremos con OpenAiDataSource el cual necesita inyectar la dependencia OpenAiApi en tu constructor para que podamos realizar la solicitud al servidor OpenAI. Para esto usaremos la notación @Inject.

class OpenAiDataSource @Inject constructor(

   private val openAiApi: OpenAiApi

) {

   suspend fun getMathAnswer(equation: CompletionRequest): CompletionResponse {

       return openAiApi.getMathAnswer(equation)

   }

}

Ahora haremos el OpenAiRepository que necesita inyectar la dependencia del OpenAiDataSource utilizando la misma notación @Inject en el constructor. La capa Repository es una abstracción de la capa de comunicación con la fuente de datos, el resto de la aplicación puede solicitar desde la Repository la información que necesitas, sin preocuparte por la fuente de datos. Aquí en Repository podríamos tener además de OpenAiDataSource otra fuente de datos para nuestra requisición, como por ejemplo una base local.

class OpenAiRepository @Inject constructor(

   private val dataSource: OpenAiDataSource

) {

   suspend fun getMathAnswer(equation: CompletionRequest): CompletionResponse {

       return dataSource.getMathAnswer(equation)

   }

}

Etapa 6: Ahora haremos la última capa lógica antes de la capa UI, prepararemos los datos consumidos desde el Repository para ser accedido por la interfaz de usuario a través de una entidad de tipo ViewModel, y para esto usaremos la notación @HiltViewModel. Para inyectar el Repository, usaremos la notación @Inject.

@HiltViewModel

class MainViewModel @Inject constructor(

   private val openAiRepository: OpenAiRepository,

): ViewModel() {


   private var _answer = mutableStateOf<CompletionResponse?>(null)

   var answer: State<CompletionResponse?> = _answer


   fun getMathAnswer(prompt: CompletionRequest) {

       viewModelScope.launch {

           _answer.value = openAiRepository.getMathAnswer(prompt)

       }

   }

}

En este ViewModel, tendremos el Mutable State privado _answer que corresponde al valor que recibe el resultado de la requisición hecha a través de openAiRepository.getMathAnswer(prompt). El state público answer observará el resultado de _answer. Hacemos este wrap para imposibilitar a View de alterar el valor de _answer, pues View solo debe reaccionar a los resultados sin interferir en ellos.

Etapa 7: Finalmente, vamos a crear nuestra View alterando MainActivity y preparándolo para recibir una inyección del MainViewModel. Para eso, usaremos la notación @AndroidEntryPoint y la inyección by viewModels() para adquirir una instancia de MainViewModel.

@AndroidEntryPoint

class MainActivity : ComponentActivity() {

   private val viewModel: MainViewModel by viewModels()


   override fun onCreate(savedInstanceState: Bundle?) {

       super.onCreate(savedInstanceState)

       setContent {

           MyApp(viewModel)

       }

   }

}


Ahora, en el setContent dentro de onCreate tenemos MyApp, unaa función @Composable que define los componentes visuales para esta app. Para construir la interfaz como describí inicialmente, vamos a utilizar un TextField para campo de texto, Button para permitir el clic del usuario y Text para mostrar el resultado final.

En medio de esto usaremos también Surface como base de todo el componente, una Column para definir el layout vertical de los componentes y Spacer para añadir espaciado entre los componentes.

@Composable

fun MyApp(viewModel: MainViewModel) {

   var inputText by remember { mutableStateOf("") }

   val result by viewModel.answer


   Surface(color = MaterialTheme.colors.background) {

       Column(

           modifier = Modifier.fillMaxSize(),

           verticalArrangement = Arrangement.Center,

           horizontalAlignment = Alignment.CenterHorizontally

       ) {

           TextField(

               value = inputText,

               onValueChange = { inputText = it },

               label = { Text("Ask something") }

           )

           Spacer(modifier = Modifier.height(16.dp))

           Button(onClick = {

               if (inputText.isNotBlank()) {

                   viewModel.getMathAnswer(

                       CompletionRequest(

                           "text-davinci-003", inputText, 150,

                           0f, 1f, 0, 0, ""

                       )

                   )

               }

           }) {

               Text("Get Answer")

           }

           Spacer(modifier = Modifier.height(16.dp))

           result?.choices?.get(0)?.let { Text(text = it.text) }

       }

   }

}


Como parámetro de la funcion MyApp, agregaremos el MainViewModel que será usado en el atributo result para obtener el state answer que, una vez actualizado, hará lo mismo con el atributo result de View, que muestra el texto (text) de la primera opción (choices?.get(0)) del resultado de la requisición.

Resultado

Escribe algo en el campo de texto y presiona el botón, espera un breve momento y, finalmente, ¡obtén la respuesta procesada por inteligencia artificial!

Conclusión

Usar OpenAI es tan simple como consultar cualquier API remota. Con este artículo, aprendiste cómo utilizar uno de los modelos más robustos disponibles en OpenAI, con las funciones más modernas disponibles para Android (Dagger Hilt e Compose).

¡Utiliza esta poderosa combinación para crear aplicaciones utilizando inteligencia artificial y sorprende a tus clientes o gerentes! Guarda esta página en tus favoritos y vuelve cuando necesites consultar información importante sobre este tema.

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.