Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SerpApi Example #289

Merged
merged 7 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.xebia.functional.xef.auto.reasoning

import com.xebia.functional.gpt4all.getOrThrow
import com.xebia.functional.xef.auto.ai
import com.xebia.functional.xef.reasoning.serpapi.SerpApiClient

suspend fun main() {
ai {
val client = SerpApiClient()

val searchData = SerpApiClient.SearchData(
search = "german+shepper",
location = "Villavicencio,+Meta,+Colombia",
language = "en",
region = "us",
googleDomain = "google.com"
)

val answer = client.search(searchData)

answer.searchResults.forEach {
println(
"\n\uD83E\uDD16 Search Information:\n\n" +
"Title: ${it.title}\n" +
"Document: ${it.document}\n" +
"Source: ${it.source}\n"
)
}
}.getOrThrow()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.xebia.functional.xef.gcp

import com.xebia.functional.xef.AIError
import com.xebia.functional.xef.auto.AutoClose
import com.xebia.functional.xef.auto.autoClose
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.*
Expand All @@ -23,7 +25,7 @@ class GcpClient(
private val projectId: String,
val modelId: String,
private val token: String
) : AutoCloseable {
) : AutoCloseable, AutoClose by autoClose() {
private val http: HttpClient = HttpClient {
install(HttpTimeout) {
requestTimeoutMillis = 60_000
Expand Down
38 changes: 32 additions & 6 deletions reasoning/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ repositories {

plugins {
base
alias(libs.plugins.kotlin.multiplatform)
id(libs.plugins.kotlin.multiplatform.get().pluginId)
alias(libs.plugins.kotest.multiplatform)
alias(libs.plugins.kotlinx.serialization)
id(libs.plugins.kotlinx.serialization.get().pluginId)
alias(libs.plugins.spotless)
alias(libs.plugins.dokka)
alias(libs.plugins.arrow.gradle.publish)
Expand Down Expand Up @@ -68,6 +68,7 @@ kotlin {
implementation(libs.okio)
implementation(libs.klogging)
implementation(libs.uuid)
implementation(libs.bundles.ktor.client)
}
}

Expand All @@ -84,20 +85,45 @@ kotlin {
implementation(libs.logback)
implementation(projects.xefPdf)
implementation(projects.xefFilesystem)
api(libs.ktor.client.cio)
}
}

val jsMain by getting
val jsMain by getting {
dependencies {
api(libs.ktor.client.js)
}
}

val jvmTest by getting {
dependencies {
implementation(libs.kotest.junit5)
}
}

val linuxX64Main by getting
val macosX64Main by getting
val mingwX64Main by getting
val linuxX64Main by getting {
dependencies {
api(libs.ktor.client.cio)
}
}

val macosX64Main by getting {
dependencies {
api(libs.ktor.client.cio)
}
}

val macosArm64Main by getting {
dependencies {
api(libs.ktor.client.cio)
}
}

val mingwX64Main by getting {
dependencies {
api(libs.ktor.client.winhttp)
}
}

create("nativeMain") {
dependsOn(commonMain)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.xebia.functional.xef.reasoning.serpapi

import com.xebia.functional.xef.auto.AutoClose
import com.xebia.functional.xef.auto.autoClose
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.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

class SerpApiClient : AutoCloseable, AutoClose by autoClose() {

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) {
maxRetries = 5
retryIf { _, response -> !response.status.isSuccess() }
retryOnExceptionIf { _, _ -> true }
delayMillis { retry -> retry * 3000L }
}
install(ContentNegotiation) {
json(
Json {
encodeDefaults = false
isLenient = true
ignoreUnknownKeys = true
}
)
}
}

data class SearchData(
val search: String,
val location: String? = null,
val language: String? = null,
val region: String? = null,
val googleDomain: String = "google.com"
)

@Serializable
data class SearchResults(@SerialName("organic_results") val searchResults: List<SearchResult>)

@Serializable
data class SearchResult(
val title: String,
@SerialName("snippet") val document: String,
@SerialName("link") val source: String
)

suspend fun search(searchData: SearchData): SearchResults {

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)
}
.body<SearchResults>()
}

class SerpApiClientException(
private val httpStatusCode: HttpStatusCode,
private val error: String
) : IllegalStateException("$httpStatusCode: $error")

override fun close() {
http.close()
}
}