Skip to content

Commit

Permalink
Support GoogleSearchAPIWrapper HamaWhiteGG#110
Browse files Browse the repository at this point in the history
  • Loading branch information
HamaWhiteGG committed Sep 28, 2023
1 parent a92a484 commit d815b75
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 4 deletions.
10 changes: 10 additions & 0 deletions langchain-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@
<artifactId>rxjava</artifactId>
</dependency>

<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
</dependency>

<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-customsearch</artifactId>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import com.hw.langchain.chains.llm.math.base.LLMMathChain;
import com.hw.langchain.tools.base.BaseTool;
import com.hw.langchain.tools.base.Tool;
import com.hw.langchain.utilities.serpapi.SerpAPIWrapper;
import com.hw.langchain.utilities.SerpAPIWrapper;

import org.apache.commons.lang3.tuple.Pair;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hw.langchain.utilities;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.customsearch.v1.CustomSearchAPI;
import com.google.api.services.customsearch.v1.CustomSearchAPIRequestInitializer;
import com.google.api.services.customsearch.v1.model.Result;
import com.google.common.collect.Maps;

import org.apache.commons.collections4.CollectionUtils;

import lombok.SneakyThrows;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.hw.langchain.utils.Utils.getOrEnvOrDefault;

/**
* Wrapper for Google Search API.
*
* <ol>
* <li>Create an API key</li>
* <li>Setup Custom Search Engine to search the entire web</li>
* <li>Enable the Custom Search API</li>
* </ol>
*
* @author HamaWhite
*/
public class GoogleSearchAPIWrapper {

private final CustomSearchAPI customSearch;

private final String googleCseId;

/**
* Number of search results to return.
*/
private final int num;

@SneakyThrows
private GoogleSearchAPIWrapper(Builder builder) {
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
HttpRequestInitializer httpRequestInitializer = request -> {
request.setConnectTimeout(builder.connectTimeout);
request.setReadTimeout(builder.readTimeout);
};
String googleApiKey = getOrEnvOrDefault(builder.googleApiKey, "GOOGLE_API_KEY");

this.customSearch = new CustomSearchAPI.Builder(transport, new GsonFactory(), httpRequestInitializer)
.setApplicationName("Google Custom Search")
.setGoogleClientRequestInitializer(new CustomSearchAPIRequestInitializer(googleApiKey))
.build();

this.googleCseId = getOrEnvOrDefault(builder.googleCseId, "GOOGLE_CSE_ID");
this.num = builder.num;
}

@SneakyThrows
private List<Result> googleSearchResults(String query, int num) {
return customSearch.cse()
.list()
.setCx(googleCseId)
.setQ(query)
.setNum(num)
.execute()
.getItems();
}

/**
* Run a query through Google Search and parse the results.
*
* @param query The search query to be executed.
* @return A string containing snippets from the Google Search results, joined with spaces.
*/
public String run(String query) {
List<Result> results = googleSearchResults(query, num);
if (CollectionUtils.isEmpty(results)) {
return "No good Google Search Result was found";
}
return results.stream()
.map(Result::getSnippet)
.filter(Objects::nonNull)
.collect(Collectors.joining(" "));
}

/**
* Run query through GoogleSearch and return metadata.
*
* @param query The query to search for.
* @param num The number of results to return.
* @return A list of dictionaries with the following keys:
* <ul>
* <li>snippet - The description of the result.</li>
* <li>title - The title of the result.</li>
* <li>link - The link to the result.</li>
* </ul>
*/
public List<Map<String, String>> results(String query, int num) {
List<Result> results = googleSearchResults(query, num);

if (CollectionUtils.isEmpty(results)) {
return List.of(Map.of("Result", "No good Google Search Result was found"));
}
return results.stream().map(result -> {
Map<String, String> metadataResult = Maps.newHashMap();
metadataResult.put("title", result.getTitle());
metadataResult.put("link", result.getLink());

if (result.getSnippet() != null) {
metadataResult.put("snippet", result.getSnippet());
}
return metadataResult;
}).toList();
}

public static final class Builder {

/**
* Configure or set the environment variable GOOGLE_API_KEY.
*/
private String googleApiKey;

/**
* Configure or set the environment variable GOOGLE_CSE_ID.
*/
private String googleCseId;

/**
* Number of search results to return.
*/
private int num = 10;

/**
* Timeout in milliseconds to establish a connection or {@code 0} for an infinite timeout.
*/
private int connectTimeout = 20 * 1000;

/**
* Timeout in milliseconds to read data from an established connection or {@code 0} for an infinite timeout.
*/
private int readTimeout = 20 * 1000;

public Builder() {
}

public Builder googleApiKey(String googleApiKey) {
this.googleApiKey = googleApiKey;
return this;
}

public Builder googleCseId(String googleCseId) {
this.googleCseId = googleCseId;
return this;
}

public Builder num(int num) {
this.num = num;
return this;
}

public Builder connectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}

public Builder readTimeout(int readTimeout) {
this.readTimeout = readTimeout;
return this;
}

public GoogleSearchAPIWrapper build() {
return new GoogleSearchAPIWrapper(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

package com.hw.langchain.utilities.serpapi;
package com.hw.langchain.utilities;

import com.google.gson.JsonObject;
import com.hw.langchain.utils.Utils;
Expand All @@ -29,8 +29,7 @@
/**
* Wrapper around SerpAPI.
* <p>
* To use, you should have the google-search-results python package installed,
* and the environment variable SERPAPI_API_KEY set with your API key, or pass
* Use the environment variable SERPAPI_API_KEY set with your API key, or pass
* serpapi_api_key as a named parameter to the constructor.
* <p>
* <a href="https://serpapi.com/integrations/java">SerpApi in Java </a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hw.langchain.utilities;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Set the environment variable GOOGLE_API_KEY and GOOGLE_CSE_ID.
*
* @author HamaWhite
*/
@Disabled("Test requires Google Search Key , can be run manually.")
class GoogleSearchAPIWrapperTest {

@Test
void testGoogleSearch() {
String query = "2022 USA national auto sales by brand";

GoogleSearchAPIWrapper searchWrapper = new GoogleSearchAPIWrapper.Builder()
.connectTimeout(10 * 1000)
.build();

List<Map<String, String>> results = searchWrapper.results(query, 10);
assertThat(results).isNotEmpty().hasSize(10);

assertThat(results.get(0)).isEqualTo(Map.of(
"title", "Full-Year 2022 National Auto Sales By Brand",
"link", "https://www.carpro.com/blog/full-year-2022-national-auto-sales-by-brand",
"snippet",
"Jan 12, 2023 ... Full-Year 2022 National Auto Sales By Brand ; 1. Toyota, 1,849,751 ; 2. Ford, 1,767,439 ; 3. Chevrolet, 1,502,389 ; 4. Honda, 881,201 ..."));
}
}
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
<reactor-core.version>3.5.8</reactor-core.version>
<reactor-adapter.version>3.5.1</reactor-adapter.version>
<mockito-core.version>3.12.4</mockito-core.version>
<google-api-client.version>2.2.0</google-api-client.version>
<google-api-customsearch.version>v1-rev20230702-2.0.0</google-api-customsearch.version>

<scala.binary.version>2.12</scala.binary.version>
<target.java.version>17</target.java.version>
Expand Down Expand Up @@ -282,6 +284,18 @@
<version>${rxjava.version}</version>
</dependency>

<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>${google-api-client.version}</version>
</dependency>

<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-customsearch</artifactId>
<version>${google-api-customsearch.version}</version>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down

0 comments on commit d815b75

Please sign in to comment.