diff --git a/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/auto/reasoning/SerpApiExample.kt b/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/auto/reasoning/SerpApiExample.kt index 934fdd56f..3df2096a0 100644 --- a/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/auto/reasoning/SerpApiExample.kt +++ b/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/auto/reasoning/SerpApiExample.kt @@ -8,18 +8,17 @@ suspend fun main() { ai { val client = SerpApiClient() - val apiKey = "5edd66c5a7ebe4bd2a40d37a9900c88ac4807d8ddbca72fcd7705d313a696656" - val searchData = SerpApiClient.SearchData("german+shepper", "Villavicencio,+Meta,+Colombia", "en", "us", "google.com", apiKey) + val searchData = SerpApiClient.SearchData("german+shepper", "Villavicencio,+Meta,+Colombia", "en", "us", "google.com") //val searchData = SerpApiClient.SearchData("Coffee", "Austin,+Texas,+United+States", "en", "us", "google.com", apiKey) val answer = client.search(searchData) - answer.searchResult.forEach { + answer.searchResults.forEach { println( "\n\uD83E\uDD16 Search Information:\n\n" + "Title: ${it.title}\n" + "Document: ${it.document}\n" + - "Link: ${it.link}\n" + "Source: ${it.source}\n" ) } }.getOrThrow() diff --git a/reasoning/src/commonMain/kotlin/com/xebia/functional/xef/reasoning/serpapi/SerpApiClient.kt b/reasoning/src/commonMain/kotlin/com/xebia/functional/xef/reasoning/serpapi/SerpApiClient.kt index 16d8ed713..8008f4c15 100644 --- a/reasoning/src/commonMain/kotlin/com/xebia/functional/xef/reasoning/serpapi/SerpApiClient.kt +++ b/reasoning/src/commonMain/kotlin/com/xebia/functional/xef/reasoning/serpapi/SerpApiClient.kt @@ -1,25 +1,40 @@ package com.xebia.functional.xef.reasoning.serpapi +import com.xebia.functional.xef.env.getenv import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonNames class SerpApiClient : AutoCloseable { + private val serpApiKey: String? + private val SERP_API_KEY_NOT_FOUND = "Missing SERP_API_KEY env var" + + init { + serpApiKey = getenv("SERP_API_KEY") ?: throw SerpApiClientException(HttpStatusCode.Unauthorized, SERP_API_KEY_NOT_FOUND) + + if(serpApiKey.isEmpty()) + throw SerpApiClientException(HttpStatusCode.Unauthorized, SERP_API_KEY_NOT_FOUND) + } + private val http: HttpClient = HttpClient { install(HttpTimeout) { requestTimeoutMillis = 60_000 connectTimeoutMillis = 60_000 } - install(HttpRequestRetry) + install(HttpRequestRetry) { + maxRetries = 5 + retryIf { _, response -> !response.status.isSuccess() } + retryOnExceptionIf { _, _ -> true } + delayMillis { retry -> retry * 3000L } + } install(ContentNegotiation) { json( Json { @@ -31,35 +46,29 @@ class SerpApiClient : AutoCloseable { } } - @Serializable data class SearchData( - val q: String, + val search: String, val location: String? = null, - val hl: String? = null, - val gl: String? = null, - val googleDomain: String = "google.com", - val apiKey: String + val language: String? = null, + val region: String? = null, + val googleDomain: String = "google.com" ) - @Serializable data class SearchResults(@JsonNames("organic_results") val searchResult: List) + @Serializable data class SearchResults(@SerialName("organic_results") val searchResults: List) @Serializable data class SearchResult( val title: String, - @JsonNames("snippet") val document: String, - val link: String + @SerialName("snippet") val document: String, + @SerialName("link") val source: String ) suspend fun search(searchData: SearchData): SearchResults { - val response = - http.get( - "https://serpapi.com/search.json?q=${searchData.q}&location=${searchData.location}&hl=${searchData.hl}&gl=${searchData.gl}&google_domain=${searchData.googleDomain}&api_key=${searchData.apiKey}" + return http.get( + "https://serpapi.com/search.json?q=${searchData.search}&location=${searchData.location}&hl=${searchData.language}&gl=${searchData.region}" + + "&google_domain=${searchData.googleDomain}&api_key=${serpApiKey}" ) { contentType(ContentType.Application.Json) - } - - return if (response.status.isSuccess()) - response.body() - else throw SerpApiClientException(response.status, response.bodyAsText()) + }.body() } class SerpApiClientException(private val httpStatusCode: HttpStatusCode, private val error: String) :