mirror of
https://github.com/hwchase17/langchain.git
synced 2025-07-13 08:27:03 +00:00
Merriam-Webster Dictionary Tool (#12044)
# Description We implemented a simple tool for accessing the Merriam-Webster Collegiate Dictionary API (https://dictionaryapi.com/products/api-collegiate-dictionary). Here's a simple usage example: ```py from langchain.llms import OpenAI from langchain.agents import load_tools, initialize_agent, AgentType llm = OpenAI() tools = load_tools(["serpapi", "merriam-webster"], llm=llm) # Serp API gives our agent access to Google agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True ) agent.run("What is the english word for the german word Himbeere? Define that word.") ``` Sample output: ``` > Entering new AgentExecutor chain... I need to find the english word for Himbeere and then get the definition of that word. Action: Search Action Input: "English word for Himbeere" Observation: {'type': 'translation_result'} Thought: Now I have the english word, I can look up the definition. Action: MerriamWebster Action Input: raspberry Observation: Definitions of 'raspberry': 1. rasp-ber-ry, noun: any of various usually black or red edible berries that are aggregate fruits consisting of numerous small drupes on a fleshy receptacle and that are usually rounder and smaller than the closely related blackberries 2. rasp-ber-ry, noun: a perennial plant (genus Rubus) of the rose family that bears raspberries 3. rasp-ber-ry, noun: a sound of contempt made by protruding the tongue between the lips and expelling air forcibly to produce a vibration; broadly : an expression of disapproval or contempt 4. black raspberry, noun: a raspberry (Rubus occidentalis) of eastern North America that has a purplish-black fruit and is the source of several cultivated varieties —called also blackcap Thought: I now know the final answer. Final Answer: Raspberry is an english word for Himbeere and it is defined as any of various usually black or red edible berries that are aggregate fruits consisting of numerous small drupes on a fleshy receptacle and that are usually rounder and smaller than the closely related blackberries. > Finished chain. ``` # Issue This closes #12039. # Dependencies We added no extra dependencies. <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://github.com/langchain-ai/langchain/blob/master/.github/CONTRIBUTING.md If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> --------- Co-authored-by: Lara <63805048+larkgz@users.noreply.github.com> Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
This commit is contained in:
parent
f3dd4a10cf
commit
c2e3963da4
@ -58,6 +58,7 @@ from langchain.tools.searx_search.tool import SearxSearchResults, SearxSearchRun
|
||||
from langchain.tools.shell.tool import ShellTool
|
||||
from langchain.tools.sleep.tool import SleepTool
|
||||
from langchain.tools.stackexchange.tool import StackExchangeTool
|
||||
from langchain.tools.merriam_webster.tool import MerriamWebsterQueryRun
|
||||
from langchain.tools.wikipedia.tool import WikipediaQueryRun
|
||||
from langchain.tools.wolfram_alpha.tool import WolframAlphaQueryRun
|
||||
from langchain.tools.openweathermap.tool import OpenWeatherMapQueryRun
|
||||
@ -85,6 +86,7 @@ from langchain.utilities.searx_search import SearxSearchWrapper
|
||||
from langchain.utilities.serpapi import SerpAPIWrapper
|
||||
from langchain.utilities.stackexchange import StackExchangeAPIWrapper
|
||||
from langchain.utilities.twilio import TwilioAPIWrapper
|
||||
from langchain.utilities.merriam_webster import MerriamWebsterAPIWrapper
|
||||
from langchain.utilities.wikipedia import WikipediaAPIWrapper
|
||||
from langchain.utilities.wolfram_alpha import WolframAlphaAPIWrapper
|
||||
from langchain.utilities.openweathermap import OpenWeatherMapAPIWrapper
|
||||
@ -232,6 +234,10 @@ def _get_google_search(**kwargs: Any) -> BaseTool:
|
||||
return GoogleSearchRun(api_wrapper=GoogleSearchAPIWrapper(**kwargs))
|
||||
|
||||
|
||||
def _get_merriam_webster(**kwargs: Any) -> BaseTool:
|
||||
return MerriamWebsterQueryRun(api_wrapper=MerriamWebsterAPIWrapper(**kwargs))
|
||||
|
||||
|
||||
def _get_wikipedia(**kwargs: Any) -> BaseTool:
|
||||
return WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(**kwargs))
|
||||
|
||||
@ -434,6 +440,7 @@ _EXTRA_OPTIONAL_TOOLS: Dict[str, Tuple[Callable[[KwArg(Any)], BaseTool], List[st
|
||||
"dalle-image-generator": (_get_dalle_image_generator, ["openai_api_key"]),
|
||||
"twilio": (_get_twilio, ["account_sid", "auth_token", "from_number"]),
|
||||
"searx-search": (_get_searx_search, ["searx_host", "engines", "aiosession"]),
|
||||
"merriam-webster": (_get_merriam_webster, ["merriam_webster_api_key"]),
|
||||
"wikipedia": (_get_wikipedia, ["top_k_results", "lang"]),
|
||||
"arxiv": (
|
||||
_get_arxiv,
|
||||
|
@ -326,6 +326,12 @@ def _import_json_tool_JsonListKeysTool() -> Any:
|
||||
return JsonListKeysTool
|
||||
|
||||
|
||||
def _import_merriam_webster_tool() -> Any:
|
||||
from langchain.tools.merriam_webster.tool import MerriamWebsterQueryRun
|
||||
|
||||
return MerriamWebsterQueryRun
|
||||
|
||||
|
||||
def _import_metaphor_search() -> Any:
|
||||
from langchain.tools.metaphor_search import MetaphorSearchResults
|
||||
|
||||
@ -791,6 +797,8 @@ def __getattr__(name: str) -> Any:
|
||||
return _import_json_tool_JsonGetValueTool()
|
||||
elif name == "JsonListKeysTool":
|
||||
return _import_json_tool_JsonListKeysTool()
|
||||
elif name == "MerriamWebsterQueryRun":
|
||||
return _import_merriam_webster_tool()
|
||||
elif name == "MetaphorSearchResults":
|
||||
return _import_metaphor_search()
|
||||
elif name == "O365CreateDraftMessage":
|
||||
@ -979,6 +987,7 @@ __all__ = [
|
||||
"ListPowerBITool",
|
||||
"ListSQLDatabaseTool",
|
||||
"ListSparkSQLTool",
|
||||
"MerriamWebsterQueryRun",
|
||||
"MetaphorSearchResults",
|
||||
"MoveFileTool",
|
||||
"NavigateBackTool",
|
||||
|
@ -0,0 +1 @@
|
||||
"""Merriam-Webster API toolkit."""
|
27
libs/langchain/langchain/tools/merriam_webster/tool.py
Normal file
27
libs/langchain/langchain/tools/merriam_webster/tool.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Tool for the Merriam-Webster API."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from langchain.callbacks.manager import CallbackManagerForToolRun
|
||||
from langchain.tools.base import BaseTool
|
||||
from langchain.utilities.merriam_webster import MerriamWebsterAPIWrapper
|
||||
|
||||
|
||||
class MerriamWebsterQueryRun(BaseTool):
|
||||
"""Tool that searches the Merriam-Webster API."""
|
||||
|
||||
name: str = "MerriamWebster"
|
||||
description: str = (
|
||||
"A wrapper around Merriam-Webster. "
|
||||
"Useful for when you need to get the definition of a word."
|
||||
"Input should be the word you want the definition of."
|
||||
)
|
||||
api_wrapper: MerriamWebsterAPIWrapper
|
||||
|
||||
def _run(
|
||||
self,
|
||||
query: str,
|
||||
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||
) -> str:
|
||||
"""Use the Merriam-Webster tool."""
|
||||
return self.api_wrapper.run(query)
|
@ -134,6 +134,12 @@ def _import_max_compute() -> Any:
|
||||
return MaxComputeAPIWrapper
|
||||
|
||||
|
||||
def _import_merriam_webster() -> Any:
|
||||
from langchain.utilities.merriam_webster import MerriamWebsterAPIWrapper
|
||||
|
||||
return MerriamWebsterAPIWrapper
|
||||
|
||||
|
||||
def _import_metaphor_search() -> Any:
|
||||
from langchain.utilities.metaphor_search import MetaphorSearchAPIWrapper
|
||||
|
||||
@ -291,6 +297,8 @@ def __getattr__(name: str) -> Any:
|
||||
return _import_jira()
|
||||
elif name == "MaxComputeAPIWrapper":
|
||||
return _import_max_compute()
|
||||
elif name == "MerriamWebsterAPIWrapper":
|
||||
return _import_merriam_webster()
|
||||
elif name == "MetaphorSearchAPIWrapper":
|
||||
return _import_metaphor_search()
|
||||
elif name == "OpenWeatherMapAPIWrapper":
|
||||
@ -355,6 +363,7 @@ __all__ = [
|
||||
"JiraAPIWrapper",
|
||||
"LambdaWrapper",
|
||||
"MaxComputeAPIWrapper",
|
||||
"MerriamWebsterAPIWrapper",
|
||||
"MetaphorSearchAPIWrapper",
|
||||
"OpenWeatherMapAPIWrapper",
|
||||
"OutlineAPIWrapper",
|
||||
|
108
libs/langchain/langchain/utilities/merriam_webster.py
Normal file
108
libs/langchain/langchain/utilities/merriam_webster.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""Util that calls Merriam-Webster."""
|
||||
import json
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
|
||||
from langchain.pydantic_v1 import BaseModel, Extra, root_validator
|
||||
from langchain.utils import get_from_dict_or_env
|
||||
|
||||
MERRIAM_WEBSTER_API_URL = (
|
||||
"https://www.dictionaryapi.com/api/v3/references/collegiate/json"
|
||||
)
|
||||
MERRIAM_WEBSTER_TIMEOUT = 5000
|
||||
|
||||
|
||||
class MerriamWebsterAPIWrapper(BaseModel):
|
||||
"""Wrapper for Merriam-Webster.
|
||||
|
||||
Docs for using:
|
||||
|
||||
1. Go to https://www.dictionaryapi.com/register/index and register an
|
||||
developer account with a key for the Collegiate Dictionary
|
||||
2. Get your API Key from https://www.dictionaryapi.com/account/my-keys
|
||||
3. Save your API Key into MERRIAM_WEBSTER_API_KEY env variable
|
||||
|
||||
"""
|
||||
|
||||
merriam_webster_api_key: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
|
||||
@root_validator()
|
||||
def validate_environment(cls, values: Dict) -> Dict:
|
||||
"""Validate that api key exists in environment."""
|
||||
merriam_webster_api_key = get_from_dict_or_env(
|
||||
values, "merriam_webster_api_key", "MERRIAM_WEBSTER_API_KEY"
|
||||
)
|
||||
values["merriam_webster_api_key"] = merriam_webster_api_key
|
||||
|
||||
return values
|
||||
|
||||
def run(self, query: str) -> str:
|
||||
"""Run query through Merriam-Webster API and return a formatted result."""
|
||||
quoted_query = quote(query)
|
||||
|
||||
request_url = (
|
||||
f"{MERRIAM_WEBSTER_API_URL}/{quoted_query}"
|
||||
f"?key={self.merriam_webster_api_key}"
|
||||
)
|
||||
|
||||
response = requests.get(request_url, timeout=MERRIAM_WEBSTER_TIMEOUT)
|
||||
|
||||
if response.status_code != 200:
|
||||
return response.text
|
||||
|
||||
return self._format_response(query, response)
|
||||
|
||||
def _format_response(self, query: str, response: requests.Response) -> str:
|
||||
content = json.loads(response.content)
|
||||
|
||||
if not content:
|
||||
return f"No Merriam-Webster definition was found for query '{query}'."
|
||||
|
||||
if isinstance(content[0], str):
|
||||
result = f"No Merriam-Webster definition was found for query '{query}'.\n"
|
||||
if len(content) > 1:
|
||||
alternatives = [f"{i + 1}. {content[i]}" for i in range(len(content))]
|
||||
result += "You can try one of the following alternative queries:\n\n"
|
||||
result += "\n".join(alternatives)
|
||||
else:
|
||||
result += f"Did you mean '{content[0]}'?"
|
||||
else:
|
||||
result = self._format_definitions(query, content)
|
||||
|
||||
return result
|
||||
|
||||
def _format_definitions(self, query: str, definitions: List[Dict]) -> str:
|
||||
formatted_definitions: List[str] = []
|
||||
for definition in definitions:
|
||||
formatted_definitions.extend(self._format_definition(definition))
|
||||
|
||||
if len(formatted_definitions) == 1:
|
||||
return f"Definition of '{query}':\n" f"{formatted_definitions[0]}"
|
||||
|
||||
result = f"Definitions of '{query}':\n\n"
|
||||
for i, formatted_definition in enumerate(formatted_definitions, 1):
|
||||
result += f"{i}. {formatted_definition}\n"
|
||||
|
||||
return result
|
||||
|
||||
def _format_definition(self, definition: Dict) -> Iterator[str]:
|
||||
if "hwi" in definition:
|
||||
headword = definition["hwi"]["hw"].replace("*", "-")
|
||||
else:
|
||||
headword = definition["meta"]["id"].split(":")[0]
|
||||
|
||||
if "fl" in definition:
|
||||
functional_label = definition["fl"]
|
||||
|
||||
if "shortdef" in definition:
|
||||
for short_def in definition["shortdef"]:
|
||||
yield f"{headword}, {functional_label}: {short_def}"
|
||||
else:
|
||||
yield f"{headword}, {functional_label}"
|
@ -0,0 +1,32 @@
|
||||
"""Integration test for Merriam Webster API Wrapper."""
|
||||
import pytest
|
||||
|
||||
from langchain.utilities.merriam_webster import MerriamWebsterAPIWrapper
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_client() -> MerriamWebsterAPIWrapper:
|
||||
return MerriamWebsterAPIWrapper()
|
||||
|
||||
|
||||
def test_call(api_client: MerriamWebsterAPIWrapper) -> None:
|
||||
"""Test that call gives correct answer."""
|
||||
output = api_client.run("LLM")
|
||||
assert "large language model" in output
|
||||
|
||||
|
||||
def test_call_no_result(api_client: MerriamWebsterAPIWrapper) -> None:
|
||||
"""Test that non-existent words return proper result."""
|
||||
output = api_client.run("NO_RESULT_NO_RESULT_NO_RESULT")
|
||||
assert "No Merriam-Webster definition was found for query" in output
|
||||
|
||||
|
||||
def test_call_alternatives(api_client: MerriamWebsterAPIWrapper) -> None:
|
||||
"""
|
||||
Test that non-existent queries that are close to an
|
||||
existing definition return proper result.
|
||||
"""
|
||||
output = api_client.run("It's raining cats and dogs")
|
||||
assert "No Merriam-Webster definition was found for query" in output
|
||||
assert "You can try one of the following alternative queries" in output
|
||||
assert "raining cats and dogs" in output
|
@ -112,6 +112,7 @@ EXPECTED_ALL = [
|
||||
"authenticate",
|
||||
"format_tool_to_openai_function",
|
||||
"tool",
|
||||
"MerriamWebsterQueryRun",
|
||||
]
|
||||
|
||||
|
||||
|
@ -67,6 +67,7 @@ _EXPECTED = [
|
||||
"ListPowerBITool",
|
||||
"ListSQLDatabaseTool",
|
||||
"ListSparkSQLTool",
|
||||
"MerriamWebsterQueryRun",
|
||||
"MetaphorSearchResults",
|
||||
"MoveFileTool",
|
||||
"NavigateBackTool",
|
||||
|
@ -44,6 +44,7 @@ EXPECTED_ALL = [
|
||||
"WikipediaAPIWrapper",
|
||||
"WolframAlphaAPIWrapper",
|
||||
"ZapierNLAWrapper",
|
||||
"MerriamWebsterAPIWrapper",
|
||||
]
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user