Skip to content

Commit

Permalink
Call Manager for New Tools (#3755)
Browse files Browse the repository at this point in the history
Couple additional tools landed today
  • Loading branch information
vowelparrot committed Apr 29, 2023
1 parent 290fe75 commit 20ba888
Show file tree
Hide file tree
Showing 21 changed files with 603 additions and 134 deletions.
89 changes: 55 additions & 34 deletions docs/modules/agents/toolkits/examples/playwright.ipynb

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions docs/modules/agents/tools/examples/sceneXplain.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# SceneXplain\n",
"\n",
"\n",
"[SceneXplain](https://scenex.jina.ai/) is an ImageCaptioning service accessible through the SceneXplain Tool.\n",
"\n",
"To use this tool, you'll need to make an account and fetch your API Token [from the website](https://scenex.jina.ai/api). Then you can instantiate the tool."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from langchain.tools import SceneXplainTool\n",
"\n",
"\n",
"os.environ[\"SCENEX_API_KEY\"] = \"<YOUR_API_KEY>\"\n",
"tool = SceneXplainTool()\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Usage in an Agent\n",
"\n",
"The tool can be used in any LangChain agent as follows:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3m\n",
"Thought: Do I need to use a tool? Yes\n",
"Action: Image Explainer\n",
"Action Input: https://storage.googleapis.com/causal-diffusion.appspot.com/imagePrompts%2F0rw369i5h9t%2Foriginal.png\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mIn a charmingly whimsical scene, a young girl is seen braving the rain alongside her furry companion, the lovable Totoro. The two are depicted standing on a bustling street corner, where they are sheltered from the rain by a bright yellow umbrella. The girl, dressed in a cheerful yellow frock, holds onto the umbrella with both hands while gazing up at Totoro with an expression of wonder and delight.\n",
"\n",
"Totoro, meanwhile, stands tall and proud beside his young friend, holding his own umbrella aloft to protect them both from the downpour. His furry body is rendered in rich shades of grey and white, while his large ears and wide eyes lend him an endearing charm.\n",
"\n",
"In the background of the scene, a street sign can be seen jutting out from the pavement amidst a flurry of raindrops. A sign with Chinese characters adorns its surface, adding to the sense of cultural diversity and intrigue. Despite the dreary weather, there is an undeniable sense of joy and camaraderie in this heartwarming image.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m Do I need to use a tool? No\n",
"AI: This image appears to be a still from the 1988 Japanese animated fantasy film My Neighbor Totoro. The film follows two young girls, Satsuki and Mei, as they explore the countryside and befriend the magical forest spirits, including the titular character Totoro.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"This image appears to be a still from the 1988 Japanese animated fantasy film My Neighbor Totoro. The film follows two young girls, Satsuki and Mei, as they explore the countryside and befriend the magical forest spirits, including the titular character Totoro.\n"
]
}
],
"source": [
"from langchain.llms import OpenAI\n",
"from langchain.agents import initialize_agent\n",
"from langchain.memory import ConversationBufferMemory\n",
"\n",
"llm = OpenAI(temperature=0)\n",
"memory = ConversationBufferMemory(memory_key=\"chat_history\")\n",
"tools = [\n",
" tool\n",
"]\n",
"\n",
"agent = initialize_agent(\n",
" tools, llm, memory=memory, agent=\"conversational-react-description\", verbose=True\n",
")\n",
"output = agent.run(\n",
" input=(\n",
" \"What is in this image https://storage.googleapis.com/causal-diffusion.appspot.com/imagePrompts%2F0rw369i5h9t%2Foriginal.png. \"\n",
" \"Is it movie or a game? If it is a movie, what is the name of the movie?\"\n",
" )\n",
")\n",
"\n",
"print(output)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
55 changes: 36 additions & 19 deletions langchain/agents/agent_toolkits/playwright/toolkit.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
"""Playwright web browser toolkit."""
from __future__ import annotations

from typing import TYPE_CHECKING, List, Type
from typing import TYPE_CHECKING, List, Optional, Type, cast

from pydantic import Extra, Field, root_validator
from pydantic import Extra, root_validator

from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.tools.base import BaseTool
from langchain.tools.playwright.base import BaseBrowserTool
from langchain.tools.playwright.base import (
BaseBrowserTool,
lazy_import_playwright_browsers,
)
from langchain.tools.playwright.click import ClickTool
from langchain.tools.playwright.current_page import CurrentWebPageTool
from langchain.tools.playwright.extract_hyperlinks import ExtractHyperlinksTool
from langchain.tools.playwright.extract_text import ExtractTextTool
from langchain.tools.playwright.get_elements import GetElementsTool
from langchain.tools.playwright.navigate import NavigateTool
from langchain.tools.playwright.navigate_back import NavigateBackTool
from langchain.tools.playwright.utils import create_playwright_browser

if TYPE_CHECKING:
from playwright.async_api import Browser as AsyncBrowser
from playwright.sync_api import Browser as SyncBrowser
else:
try:
# We do this so pydantic can resolve the types when instantiating
from playwright.async_api import Browser as AsyncBrowser
from playwright.sync_api import Browser as SyncBrowser
except ImportError:
pass


class PlayWrightBrowserToolkit(BaseToolkit):
"""Toolkit for web browser tools."""

browser: AsyncBrowser = Field(default_factory=create_playwright_browser)
sync_browser: Optional["SyncBrowser"] = None
async_browser: Optional["AsyncBrowser"] = None

class Config:
"""Configuration for this pydantic object."""
Expand All @@ -33,15 +44,11 @@ class Config:
arbitrary_types_allowed = True

@root_validator
def check_args(cls, values: dict) -> dict:
def validate_imports_and_browser_provided(cls, values: dict) -> dict:
"""Check that the arguments are valid."""
try:
from playwright.async_api import Browser as AsyncBrowser # noqa: F401
except ImportError:
raise ValueError(
"The 'playwright' package is required to use this tool."
" Please install it with 'pip install playwright'."
)
lazy_import_playwright_browsers()
if values.get("async_browser") is None and values.get("sync_browser") is None:
raise ValueError("Either async_browser or sync_browser must be specified.")
return values

def get_tools(self) -> List[BaseTool]:
Expand All @@ -56,11 +63,21 @@ def get_tools(self) -> List[BaseTool]:
CurrentWebPageTool,
]

return [tool_cls.from_browser(self.browser) for tool_cls in tool_classes]
tools = [
tool_cls.from_browser(
sync_browser=self.sync_browser, async_browser=self.async_browser
)
for tool_cls in tool_classes
]
return cast(List[BaseTool], tools)

@classmethod
def from_browser(cls, browser: AsyncBrowser) -> PlayWrightBrowserToolkit:
from playwright.async_api import Browser as AsyncBrowser

cls.update_forward_refs(AsyncBrowser=AsyncBrowser)
return cls(browser=browser)
def from_browser(
cls,
sync_browser: Optional[SyncBrowser] = None,
async_browser: Optional[AsyncBrowser] = None,
) -> PlayWrightBrowserToolkit:
"""Instantiate the toolkit."""
# This is to raise a better error than the forward ref ones Pydantic would have
lazy_import_playwright_browsers()
return cls(sync_browser=sync_browser, async_browser=async_browser)
5 changes: 3 additions & 2 deletions langchain/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from langchain.tools.openapi.utils.api_models import APIOperation
from langchain.tools.openapi.utils.openapi_utils import OpenAPISpec
from langchain.tools.playwright import (
BaseBrowserTool,
ClickTool,
CurrentWebPageTool,
ExtractHyperlinksTool,
Expand All @@ -26,12 +25,12 @@
NavigateTool,
)
from langchain.tools.plugin import AIPluginTool
from langchain.tools.scenexplain.tool import SceneXplainTool
from langchain.tools.shell.tool import ShellTool

__all__ = [
"AIPluginTool",
"APIOperation",
"BaseBrowserTool",
"BaseTool",
"BaseTool",
"BingSearchResults",
Expand Down Expand Up @@ -59,4 +58,6 @@
"ReadFileTool",
"ShellTool",
"WriteFileTool",
"BaseTool",
"SceneXplainTool",
]
18 changes: 14 additions & 4 deletions langchain/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,25 @@ def get_filtered_args(
"""Get the arguments from a function's signature."""
schema = inferred_model.schema()["properties"]
valid_keys = signature(func).parameters
return {k: schema[k] for k in valid_keys}
return {k: schema[k] for k in valid_keys if k != "run_manager"}


class _SchemaConfig:
"""Configuration for the pydantic model."""

extra = Extra.forbid
arbitrary_types_allowed = True


def create_schema_from_function(
model_name: str,
func: Callable,
) -> Type[BaseModel]:
"""Create a pydantic schema from a function's signature."""
inferred_model = validate_arguments(func).model # type: ignore
validated = validate_arguments(func, config=_SchemaConfig) # type: ignore
inferred_model = validated.model # type: ignore
if "run_manager" in inferred_model.__fields__:
del inferred_model.__fields__["run_manager"]
# Pydantic adds placeholder virtual fields we need to strip
filtered_args = get_filtered_args(inferred_model, func)
return _create_subset_model(
Expand Down Expand Up @@ -143,8 +153,8 @@ def args(self) -> dict:
if self.args_schema is not None:
return self.args_schema.schema()["properties"]
else:
inferred_model = validate_arguments(self._run).model # type: ignore
return get_filtered_args(inferred_model, self._run)
schema = create_schema_from_function(self.name, self._run)
return schema.schema()["properties"]

def _parse_input(
self,
Expand Down
6 changes: 5 additions & 1 deletion langchain/tools/google_places/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Optional

from pydantic import Field
from pydantic import BaseModel, Field

from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
Expand All @@ -12,6 +12,10 @@
from langchain.utilities.google_places_api import GooglePlacesAPIWrapper


class GooglePlacesSchema(BaseModel):
query: str = Field(..., description="Query for goole maps")


class GooglePlacesTool(BaseTool):
"""Tool that adds the capability to query the Google places API."""

Expand Down
2 changes: 0 additions & 2 deletions langchain/tools/playwright/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Browser tools and toolkit."""

from langchain.tools.playwright.base import BaseBrowserTool
from langchain.tools.playwright.click import ClickTool
from langchain.tools.playwright.current_page import CurrentWebPageTool
from langchain.tools.playwright.extract_hyperlinks import ExtractHyperlinksTool
Expand All @@ -15,7 +14,6 @@
"ExtractTextTool",
"ExtractHyperlinksTool",
"GetElementsTool",
"BaseBrowserTool",
"ClickTool",
"CurrentWebPageTool",
]
65 changes: 36 additions & 29 deletions langchain/tools/playwright/base.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,55 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Optional, Tuple, Type

from pydantic import Field, root_validator
from pydantic import root_validator

from langchain.callbacks.manager import (
CallbackManagerForToolRun,
)
from langchain.tools.base import BaseTool
from langchain.tools.playwright.utils import create_playwright_browser, run_async

if TYPE_CHECKING:
from playwright.async_api import Browser as AsyncBrowser
from playwright.sync_api import Browser as SyncBrowser
else:
try:
# We do this so pydantic can resolve the types when instantiating
from playwright.async_api import Browser as AsyncBrowser
from playwright.sync_api import Browser as SyncBrowser
except ImportError:
pass


def lazy_import_playwright_browsers() -> Tuple[Type[AsyncBrowser], Type[SyncBrowser]]:
try:
from playwright.async_api import Browser as AsyncBrowser # noqa: F401
from playwright.sync_api import Browser as SyncBrowser # noqa: F401
except ImportError:
raise ValueError(
"The 'playwright' package is required to use the playwright tools."
" Please install it with 'pip install playwright'."
)
return AsyncBrowser, SyncBrowser


class BaseBrowserTool(BaseTool):
"""Base class for browser tools."""

browser: AsyncBrowser = Field(default_factory=create_playwright_browser)
sync_browser: Optional["SyncBrowser"] = None
async_browser: Optional["AsyncBrowser"] = None

@root_validator
def check_args(cls, values: dict) -> dict:
def validate_browser_provided(cls, values: dict) -> dict:
"""Check that the arguments are valid."""
try:
from playwright.async_api import Browser as AsyncBrowser # noqa: F401
except ImportError:
raise ValueError(
"The 'playwright' package is required to use this tool."
" Please install it with 'pip install playwright'."
)
lazy_import_playwright_browsers()
if values.get("async_browser") is None and values.get("sync_browser") is None:
raise ValueError("Either async_browser or sync_browser must be specified.")
return values

def _run(
self,
*args: Any,
run_manager: Optional[CallbackManagerForToolRun] = None,
**kwargs: Any,
) -> str:
"""Use the tool."""
return run_async(self._arun(*args, **kwargs))

@classmethod
def from_browser(cls, browser: AsyncBrowser) -> BaseBrowserTool:
from playwright.async_api import Browser as AsyncBrowser

cls.update_forward_refs(AsyncBrowser=AsyncBrowser)
return cls(browser=browser)
def from_browser(
cls,
sync_browser: Optional[SyncBrowser] = None,
async_browser: Optional[AsyncBrowser] = None,
) -> BaseBrowserTool:
"""Instantiate the tool."""
lazy_import_playwright_browsers()
return cls(sync_browser=sync_browser, async_browser=async_browser)
Loading

0 comments on commit 20ba888

Please sign in to comment.