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

Community: Google Books API Tool #27307

Merged
merged 27 commits into from
Nov 7, 2024
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
270 changes: 270 additions & 0 deletions docs/docs/integrations/tools/google_books.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
from langchain_community.utilities.dataforseo_api_search import DataForSeoAPIWrapper
from langchain_community.utilities.duckduckgo_search import DuckDuckGoSearchAPIWrapper
from langchain_community.utilities.golden_query import GoldenQueryAPIWrapper
from langchain_community.utilities.google_books import GoogleBooksAPIWrapper
from langchain_community.utilities.google_finance import GoogleFinanceAPIWrapper
from langchain_community.utilities.google_jobs import GoogleJobsAPIWrapper
from langchain_community.utilities.google_lens import GoogleLensAPIWrapper
Expand Down Expand Up @@ -333,6 +334,12 @@ def _get_pubmed(**kwargs: Any) -> BaseTool:
return PubmedQueryRun(api_wrapper=PubMedAPIWrapper(**kwargs))


def _get_google_books(**kwargs: Any) -> BaseTool:
from langchain_community.tools.google_books import GoogleBooksQueryRun

return GoogleBooksQueryRun(api_wrapper=GoogleBooksAPIWrapper(**kwargs))


def _get_google_jobs(**kwargs: Any) -> BaseTool:
return GoogleJobsQueryRun(api_wrapper=GoogleJobsAPIWrapper(**kwargs))

Expand Down Expand Up @@ -490,6 +497,7 @@ def _get_reddit_search(**kwargs: Any) -> BaseTool:
"bing-search": (_get_bing_search, ["bing_subscription_key", "bing_search_url"]),
"metaphor-search": (_get_metaphor_search, ["metaphor_api_key"]),
"ddg-search": (_get_ddg_search, []),
"google-books": (_get_google_books, ["google_books_api_key"]),
"google-lens": (_get_google_lens, ["serp_api_key"]),
"google-serper": (_get_google_serper, ["serper_api_key", "aiosession"]),
"google-scholar": (
Expand Down
5 changes: 5 additions & 0 deletions libs/community/langchain_community/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
GmailSearch,
GmailSendMessage,
)
from langchain_community.tools.google_books import (
GoogleBooksQueryRun,
)
from langchain_community.tools.google_cloud.texttospeech import (
GoogleCloudTextToSpeechTool,
)
Expand Down Expand Up @@ -407,6 +410,7 @@
"GmailGetThread",
"GmailSearch",
"GmailSendMessage",
"GoogleBooksQueryRun",
"GoogleCloudTextToSpeechTool",
"GooglePlacesTool",
"GoogleSearchResults",
Expand Down Expand Up @@ -559,6 +563,7 @@
"GmailGetThread": "langchain_community.tools.gmail",
"GmailSearch": "langchain_community.tools.gmail",
"GmailSendMessage": "langchain_community.tools.gmail",
"GoogleBooksQueryRun": "langchain_community.tools.google_books",
"GoogleCloudTextToSpeechTool": "langchain_community.tools.google_cloud.texttospeech", # noqa: E501
"GooglePlacesTool": "langchain_community.tools.google_places.tool",
"GoogleSearchResults": "langchain_community.tools.google_search.tool",
Expand Down
38 changes: 38 additions & 0 deletions libs/community/langchain_community/tools/google_books.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Tool for the Google Books API."""

from typing import Optional, Type

from langchain_core.callbacks import CallbackManagerForToolRun
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field

from langchain_community.utilities.google_books import GoogleBooksAPIWrapper


class GoogleBooksQueryInput(BaseModel):
"""Input for the GoogleBooksQuery tool."""

query: str = Field(description="query to look up on google books")


class GoogleBooksQueryRun(BaseTool): # type: ignore[override]
"""Tool that searches the Google Books API."""

name: str = "GoogleBooks"
description: str = (
"A wrapper around Google Books. "
"Useful for when you need to answer general inquiries about "
"books of certain topics and generate recommendation based "
"off of key words"
"Input should be a query string"
)
api_wrapper: GoogleBooksAPIWrapper
args_schema: Type[BaseModel] = GoogleBooksQueryInput

def _run(
self,
query: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
"""Use the Google Books tool."""
return self.api_wrapper.run(query)
5 changes: 5 additions & 0 deletions libs/community/langchain_community/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
from langchain_community.utilities.golden_query import (
GoldenQueryAPIWrapper,
)
from langchain_community.utilities.google_books import (
GoogleBooksAPIWrapper,
)
from langchain_community.utilities.google_finance import (
GoogleFinanceAPIWrapper,
)
Expand Down Expand Up @@ -185,6 +188,7 @@
"DriaAPIWrapper",
"DuckDuckGoSearchAPIWrapper",
"GoldenQueryAPIWrapper",
"GoogleBooksAPIWrapper",
"GoogleFinanceAPIWrapper",
"GoogleJobsAPIWrapper",
"GoogleLensAPIWrapper",
Expand Down Expand Up @@ -248,6 +252,7 @@
"DriaAPIWrapper": "langchain_community.utilities.dria_index",
"DuckDuckGoSearchAPIWrapper": "langchain_community.utilities.duckduckgo_search",
"GoldenQueryAPIWrapper": "langchain_community.utilities.golden_query",
"GoogleBooksAPIWrapper": "langchain_community.utilities.google_books",
"GoogleFinanceAPIWrapper": "langchain_community.utilities.google_finance",
"GoogleJobsAPIWrapper": "langchain_community.utilities.google_jobs",
"GoogleLensAPIWrapper": "langchain_community.utilities.google_lens",
Expand Down
92 changes: 92 additions & 0 deletions libs/community/langchain_community/utilities/google_books.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Util that calls Google Books."""

from typing import Dict, List, Optional

import requests
from langchain_core.utils import get_from_dict_or_env
from pydantic import BaseModel, ConfigDict, model_validator

GOOGLE_BOOKS_MAX_ITEM_SIZE = 5
GOOGLE_BOOKS_API_URL = "https://www.googleapis.com/books/v1/volumes"


class GoogleBooksAPIWrapper(BaseModel):
"""Wrapper around Google Books API.

To use, you should have a Google Books API key available.
This wrapper will use the Google Books API to conduct searches and
fetch books based on a query passed in by the agents. By default,
it will return the top-k results.

The response for each book will contain the book title, author name, summary, and
a source link.
"""

google_books_api_key: Optional[str] = None
top_k_results: int = GOOGLE_BOOKS_MAX_ITEM_SIZE

model_config = ConfigDict(
extra="forbid",
)

@model_validator(mode="before")
@classmethod
def validate_environment(cls, values: Dict) -> Dict:
"""Validate that api key exists in environment."""
google_books_api_key = get_from_dict_or_env(
values, "google_books_api_key", "GOOGLE_BOOKS_API_KEY"
)
values["google_books_api_key"] = google_books_api_key

return values

def run(self, query: str) -> str:
# build Url based on API key, query, and max results
params = (
("q", query),
("maxResults", self.top_k_results),
("key", self.google_books_api_key),
)

# send request
response = requests.get(GOOGLE_BOOKS_API_URL, params=params)
json = response.json()

# some error handeling
if response.status_code != 200:
code = response.status_code
error = json.get("error", {}).get("message", "Internal failure")
return f"Unable to retrieve books got status code {code}: {error}"

# send back data
return self._format(query, json.get("items", []))

def _format(self, query: str, books: List) -> str:
if not books:
return f"Sorry no books could be found for your query: {query}"

start = f"Here are {len(books)} suggestions for books related to {query}:"

results = []
results.append(start)
i = 1

for book in books:
info = book["volumeInfo"]
title = info["title"]
authors = self._format_authors(info["authors"])
summary = info["description"]
source = info["infoLink"]

desc = f'{i}. "{title}" by {authors}: {summary}\n'
desc += f"You can read more at {source}"
results.append(desc)

i += 1

return "\n\n".join(results)

def _format_authors(self, authors: List) -> str:
if len(authors) == 1:
return authors[0]
return "{} and {}".format(", ".join(authors[:-1]), authors[-1])
1 change: 1 addition & 0 deletions libs/community/tests/unit_tests/tools/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"GmailGetThread",
"GmailSearch",
"GmailSendMessage",
"GoogleBooksQueryRun",
"GoogleCloudTextToSpeechTool",
"GooglePlacesTool",
"GoogleSearchResults",
Expand Down
1 change: 1 addition & 0 deletions libs/community/tests/unit_tests/utilities/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"DuckDuckGoSearchAPIWrapper",
"DriaAPIWrapper",
"GoldenQueryAPIWrapper",
"GoogleBooksAPIWrapper",
"GoogleFinanceAPIWrapper",
"GoogleJobsAPIWrapper",
"GoogleLensAPIWrapper",
Expand Down
Loading