langchain-webmetadata-extractor 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_webmetadata_extractor-0.1.0/.gitignore +6 -0
- langchain_webmetadata_extractor-0.1.0/LICENSE +21 -0
- langchain_webmetadata_extractor-0.1.0/PKG-INFO +84 -0
- langchain_webmetadata_extractor-0.1.0/README.md +58 -0
- langchain_webmetadata_extractor-0.1.0/pyproject.toml +36 -0
- langchain_webmetadata_extractor-0.1.0/src/langchain_webmetadata_extractor/__init__.py +11 -0
- langchain_webmetadata_extractor-0.1.0/src/langchain_webmetadata_extractor/tools.py +118 -0
- langchain_webmetadata_extractor-0.1.0/tests/test_tools.py +110 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JosejuX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: langchain-webmetadata-extractor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain tools for the Web Metadata, OpenGraph & Contact Extractor API — give your agent URL-to-structured-data extraction, AI-ready Markdown, contact discovery, and SEO auditing.
|
|
5
|
+
Project-URL: Homepage, https://github.com/JosejuX/rapidapi-metadata-extractor
|
|
6
|
+
Project-URL: RapidAPI Listing, https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor
|
|
7
|
+
Project-URL: PyPI SDK, https://pypi.org/project/webmetadata-extractor/
|
|
8
|
+
Author-email: Juanjo <josejuanjo.coding@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,langchain,langchain-tool,metadata-extractor,rag,seo,web-scraping
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: langchain-core>=0.2
|
|
19
|
+
Requires-Dist: pydantic>=2
|
|
20
|
+
Requires-Dist: webmetadata-extractor>=0.1.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: responses>=0.23; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# langchain-webmetadata-extractor
|
|
28
|
+
|
|
29
|
+
LangChain tools for the [Web Metadata, OpenGraph & Contact Extractor API](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) — give an agent the ability to turn any URL into structured web intelligence, clean Markdown for RAG, contact signals for lead-gen, or a graded SEO audit.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install langchain-webmetadata-extractor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
Get a free API key on [RapidAPI](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) (1,000 requests/month, no credit card required).
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from langchain_webmetadata_extractor import get_tools
|
|
41
|
+
|
|
42
|
+
tools = get_tools(api_key="YOUR_RAPIDAPI_KEY")
|
|
43
|
+
# or leave api_key out and set the WEBMETADATA_API_KEY environment variable instead
|
|
44
|
+
|
|
45
|
+
agent = create_react_agent(llm, tools) # or bind_tools(tools), or any LangGraph node
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You can also import and use tools individually:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from langchain_webmetadata_extractor import WebMarkdownTool
|
|
52
|
+
|
|
53
|
+
reader = WebMarkdownTool(api_key="YOUR_RAPIDAPI_KEY")
|
|
54
|
+
print(reader.run("https://example.com"))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tools included
|
|
58
|
+
|
|
59
|
+
| Tool | Use it when the agent needs to... |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `WebMetadataExtractTool` | Get a broad, all-in-one picture of a page (SEO, contacts, tech stack, security, links) |
|
|
62
|
+
| `WebMarkdownTool` | Ingest a page's actual content into a RAG pipeline as clean Markdown |
|
|
63
|
+
| `WebContactsTool` | Find public emails/phones/social links for outreach or lead-gen workflows |
|
|
64
|
+
| `WebSEOAuditTool` | Grade a page's SEO with a score, warnings, and readability metrics |
|
|
65
|
+
|
|
66
|
+
Every tool supports both sync (`.run()` / `._run()`) and async (`.arun()` / `._arun()`) execution, and returns a JSON string — API errors come back as `{"error": true, "status_code": ..., "detail": ...}` instead of raising, so an agent loop can react to them without crashing.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Pass `api_key` explicitly per tool, or set it once via the `WEBMETADATA_API_KEY` environment variable and omit it everywhere:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export WEBMETADATA_API_KEY=your-rapidapi-key
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Links
|
|
77
|
+
|
|
78
|
+
- [Python SDK this wraps](https://pypi.org/project/webmetadata-extractor/) (`webmetadata-extractor`)
|
|
79
|
+
- [Full API documentation](https://github.com/JosejuX/rapidapi-metadata-extractor)
|
|
80
|
+
- [Live interactive demo](https://rapidapi-metadata-extractor.onrender.com) — no signup needed
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# langchain-webmetadata-extractor
|
|
2
|
+
|
|
3
|
+
LangChain tools for the [Web Metadata, OpenGraph & Contact Extractor API](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) — give an agent the ability to turn any URL into structured web intelligence, clean Markdown for RAG, contact signals for lead-gen, or a graded SEO audit.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install langchain-webmetadata-extractor
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
Get a free API key on [RapidAPI](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) (1,000 requests/month, no credit card required).
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from langchain_webmetadata_extractor import get_tools
|
|
15
|
+
|
|
16
|
+
tools = get_tools(api_key="YOUR_RAPIDAPI_KEY")
|
|
17
|
+
# or leave api_key out and set the WEBMETADATA_API_KEY environment variable instead
|
|
18
|
+
|
|
19
|
+
agent = create_react_agent(llm, tools) # or bind_tools(tools), or any LangGraph node
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You can also import and use tools individually:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from langchain_webmetadata_extractor import WebMarkdownTool
|
|
26
|
+
|
|
27
|
+
reader = WebMarkdownTool(api_key="YOUR_RAPIDAPI_KEY")
|
|
28
|
+
print(reader.run("https://example.com"))
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Tools included
|
|
32
|
+
|
|
33
|
+
| Tool | Use it when the agent needs to... |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `WebMetadataExtractTool` | Get a broad, all-in-one picture of a page (SEO, contacts, tech stack, security, links) |
|
|
36
|
+
| `WebMarkdownTool` | Ingest a page's actual content into a RAG pipeline as clean Markdown |
|
|
37
|
+
| `WebContactsTool` | Find public emails/phones/social links for outreach or lead-gen workflows |
|
|
38
|
+
| `WebSEOAuditTool` | Grade a page's SEO with a score, warnings, and readability metrics |
|
|
39
|
+
|
|
40
|
+
Every tool supports both sync (`.run()` / `._run()`) and async (`.arun()` / `._arun()`) execution, and returns a JSON string — API errors come back as `{"error": true, "status_code": ..., "detail": ...}` instead of raising, so an agent loop can react to them without crashing.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Pass `api_key` explicitly per tool, or set it once via the `WEBMETADATA_API_KEY` environment variable and omit it everywhere:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export WEBMETADATA_API_KEY=your-rapidapi-key
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Links
|
|
51
|
+
|
|
52
|
+
- [Python SDK this wraps](https://pypi.org/project/webmetadata-extractor/) (`webmetadata-extractor`)
|
|
53
|
+
- [Full API documentation](https://github.com/JosejuX/rapidapi-metadata-extractor)
|
|
54
|
+
- [Live interactive demo](https://rapidapi-metadata-extractor.onrender.com) — no signup needed
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "langchain-webmetadata-extractor"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "LangChain tools for the Web Metadata, OpenGraph & Contact Extractor API — give your agent URL-to-structured-data extraction, AI-ready Markdown, contact discovery, and SEO auditing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Juanjo", email = "josejuanjo.coding@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["langchain", "langchain-tool", "agent", "rag", "web-scraping", "seo", "metadata-extractor"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"webmetadata-extractor>=0.1.0",
|
|
23
|
+
"langchain-core>=0.2",
|
|
24
|
+
"pydantic>=2",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "responses>=0.23"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/JosejuX/rapidapi-metadata-extractor"
|
|
32
|
+
"RapidAPI Listing" = "https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor"
|
|
33
|
+
"PyPI SDK" = "https://pypi.org/project/webmetadata-extractor/"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/langchain_webmetadata_extractor"]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Optional, Type
|
|
6
|
+
|
|
7
|
+
from langchain_core.tools import BaseTool
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
from webmetadata_extractor import AsyncWebMetadataClient, WebMetadataClient
|
|
11
|
+
from webmetadata_extractor.exceptions import WebMetadataAPIError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _UrlInput(BaseModel):
|
|
15
|
+
url: str = Field(description="The fully-qualified URL to fetch and analyze, e.g. https://example.com")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _dumps(result: dict) -> str:
|
|
19
|
+
return json.dumps(result, ensure_ascii=False)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _error_payload(exc: WebMetadataAPIError) -> str:
|
|
23
|
+
return _dumps({"error": True, "status_code": exc.status_code, "detail": exc.body})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _BaseWebMetadataTool(BaseTool):
|
|
27
|
+
"""Shared config and dispatch logic for all tools in this package.
|
|
28
|
+
|
|
29
|
+
Subclasses only need to set `name`, `description`, and `client_method` (the
|
|
30
|
+
method name on `WebMetadataClient`/`AsyncWebMetadataClient` to call with `url`).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
34
|
+
args_schema: Type[BaseModel] = _UrlInput
|
|
35
|
+
client_method: str = ""
|
|
36
|
+
api_key: Optional[str] = Field(
|
|
37
|
+
default=None,
|
|
38
|
+
exclude=True,
|
|
39
|
+
description="RapidAPI key. If omitted, reads WEBMETADATA_API_KEY from the environment.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def _resolved_key(self) -> Optional[str]:
|
|
43
|
+
return self.api_key or os.environ.get("WEBMETADATA_API_KEY")
|
|
44
|
+
|
|
45
|
+
def _run(self, url: str) -> str:
|
|
46
|
+
client = WebMetadataClient(api_key=self._resolved_key())
|
|
47
|
+
try:
|
|
48
|
+
return _dumps(getattr(client, self.client_method)(url))
|
|
49
|
+
except WebMetadataAPIError as exc:
|
|
50
|
+
return _error_payload(exc)
|
|
51
|
+
finally:
|
|
52
|
+
client.close()
|
|
53
|
+
|
|
54
|
+
async def _arun(self, url: str) -> str:
|
|
55
|
+
async with AsyncWebMetadataClient(api_key=self._resolved_key()) as client:
|
|
56
|
+
try:
|
|
57
|
+
return _dumps(await getattr(client, self.client_method)(url))
|
|
58
|
+
except WebMetadataAPIError as exc:
|
|
59
|
+
return _error_payload(exc)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class WebMetadataExtractTool(_BaseWebMetadataTool):
|
|
63
|
+
"""Full extraction: SEO/OpenGraph metadata, contacts, tech stack, schema.org, security, links."""
|
|
64
|
+
|
|
65
|
+
name: str = "web_metadata_extract"
|
|
66
|
+
description: str = (
|
|
67
|
+
"Extract the full structured web intelligence payload for a URL: SEO/OpenGraph metadata, "
|
|
68
|
+
"public contact info (emails/phones/social links), detected tech stack, Schema.org product data, "
|
|
69
|
+
"a graded security-headers audit, and internal/external links. Use this when you need a broad "
|
|
70
|
+
"picture of a page rather than one specific facet. Input is a single URL."
|
|
71
|
+
)
|
|
72
|
+
client_method: str = "extract"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class WebMarkdownTool(_BaseWebMetadataTool):
|
|
76
|
+
"""Clean, AI/LLM-ready Markdown of a page's main content — for RAG ingestion pipelines."""
|
|
77
|
+
|
|
78
|
+
name: str = "web_markdown_reader"
|
|
79
|
+
description: str = (
|
|
80
|
+
"Fetch a URL and return its main article content as clean, LLM-ready Markdown (boilerplate, nav, "
|
|
81
|
+
"and ads stripped). Use this to ingest web pages into a RAG pipeline or to let an agent read a "
|
|
82
|
+
"page's actual content. Input is a single URL."
|
|
83
|
+
)
|
|
84
|
+
client_method: str = "markdown"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class WebContactsTool(_BaseWebMetadataTool):
|
|
88
|
+
"""Public contact discovery (emails, phone numbers, social links) — a raw signal for lead-gen agents."""
|
|
89
|
+
|
|
90
|
+
name: str = "web_contacts_extract"
|
|
91
|
+
description: str = (
|
|
92
|
+
"Find public contact signals on a URL: email addresses, phone numbers, and social media profile "
|
|
93
|
+
"links present in the page's HTML. This is a raw signal for a lead-generation or outreach workflow, "
|
|
94
|
+
"not verified company/people intelligence. Input is a single URL."
|
|
95
|
+
)
|
|
96
|
+
client_method: str = "contacts"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class WebSEOAuditTool(_BaseWebMetadataTool):
|
|
100
|
+
"""14-point automated SEO diagnostic with a warnings list and readability metrics."""
|
|
101
|
+
|
|
102
|
+
name: str = "web_seo_audit"
|
|
103
|
+
description: str = (
|
|
104
|
+
"Run a 14-point automated SEO audit on a URL: overall score, a list of warnings with severity and "
|
|
105
|
+
"evidence, and readability metrics (heading structure, sentence/paragraph counts). Use this when "
|
|
106
|
+
"asked to review, grade, or diagnose a page's SEO. Input is a single URL."
|
|
107
|
+
)
|
|
108
|
+
client_method: str = "seo_audit"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_tools(api_key: Optional[str] = None) -> list[BaseTool]:
|
|
112
|
+
"""Convenience helper: instantiate all four tools with a shared api_key, ready to hand to an agent."""
|
|
113
|
+
return [
|
|
114
|
+
WebMetadataExtractTool(api_key=api_key),
|
|
115
|
+
WebMarkdownTool(api_key=api_key),
|
|
116
|
+
WebContactsTool(api_key=api_key),
|
|
117
|
+
WebSEOAuditTool(api_key=api_key),
|
|
118
|
+
]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import responses
|
|
5
|
+
from webmetadata_extractor.client import DEFAULT_BASE_URL
|
|
6
|
+
|
|
7
|
+
from langchain_webmetadata_extractor import (
|
|
8
|
+
WebContactsTool,
|
|
9
|
+
WebMarkdownTool,
|
|
10
|
+
WebMetadataExtractTool,
|
|
11
|
+
WebSEOAuditTool,
|
|
12
|
+
get_tools,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@responses.activate
|
|
17
|
+
def test_extract_tool_run_returns_json_string():
|
|
18
|
+
responses.add(
|
|
19
|
+
responses.GET,
|
|
20
|
+
f"{DEFAULT_BASE_URL}/api/v1/extract",
|
|
21
|
+
json={"title": "GitHub"},
|
|
22
|
+
status=200,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
tool = WebMetadataExtractTool(api_key="fake-key")
|
|
26
|
+
result = tool._run("https://github.com")
|
|
27
|
+
|
|
28
|
+
assert json.loads(result) == {"title": "GitHub"}
|
|
29
|
+
assert responses.calls[0].request.headers["X-RapidAPI-Key"] == "fake-key"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@responses.activate
|
|
33
|
+
def test_markdown_tool_hits_markdown_endpoint():
|
|
34
|
+
responses.add(
|
|
35
|
+
responses.GET,
|
|
36
|
+
f"{DEFAULT_BASE_URL}/api/v1/markdown",
|
|
37
|
+
json={"markdown_content": "# Hello"},
|
|
38
|
+
status=200,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
tool = WebMarkdownTool(api_key="fake-key")
|
|
42
|
+
result = tool._run("https://example.com")
|
|
43
|
+
|
|
44
|
+
assert json.loads(result) == {"markdown_content": "# Hello"}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@responses.activate
|
|
48
|
+
def test_contacts_tool_returns_error_payload_on_api_error_instead_of_raising():
|
|
49
|
+
responses.add(
|
|
50
|
+
responses.GET,
|
|
51
|
+
f"{DEFAULT_BASE_URL}/api/v1/contacts",
|
|
52
|
+
json={"detail": "Invalid URL"},
|
|
53
|
+
status=400,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
tool = WebContactsTool(api_key="fake-key")
|
|
57
|
+
result = tool._run("not-a-url")
|
|
58
|
+
|
|
59
|
+
parsed = json.loads(result)
|
|
60
|
+
assert parsed["error"] is True
|
|
61
|
+
assert parsed["status_code"] == 400
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@responses.activate
|
|
65
|
+
def test_seo_audit_tool_reads_env_var_when_no_api_key_passed(monkeypatch):
|
|
66
|
+
monkeypatch.setenv("WEBMETADATA_API_KEY", "env-key")
|
|
67
|
+
responses.add(responses.GET, f"{DEFAULT_BASE_URL}/api/v1/seo-audit", json={"seo_score_percentage": 91.7}, status=200)
|
|
68
|
+
|
|
69
|
+
tool = WebSEOAuditTool()
|
|
70
|
+
tool._run("https://example.com")
|
|
71
|
+
|
|
72
|
+
assert responses.calls[0].request.headers["X-RapidAPI-Key"] == "env-key"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_get_tools_returns_all_four_with_shared_key():
|
|
76
|
+
tools = get_tools(api_key="shared-key")
|
|
77
|
+
assert {t.name for t in tools} == {
|
|
78
|
+
"web_metadata_extract",
|
|
79
|
+
"web_markdown_reader",
|
|
80
|
+
"web_contacts_extract",
|
|
81
|
+
"web_seo_audit",
|
|
82
|
+
}
|
|
83
|
+
assert all(t.api_key == "shared-key" for t in tools)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_arun_dispatches_to_the_configured_client_method(monkeypatch):
|
|
88
|
+
calls = {}
|
|
89
|
+
|
|
90
|
+
class FakeAsyncClient:
|
|
91
|
+
def __init__(self, api_key=None):
|
|
92
|
+
calls["api_key"] = api_key
|
|
93
|
+
|
|
94
|
+
async def __aenter__(self):
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
async def __aexit__(self, *exc_info):
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
async def extract(self, url):
|
|
101
|
+
calls["url"] = url
|
|
102
|
+
return {"title": "async result"}
|
|
103
|
+
|
|
104
|
+
monkeypatch.setattr("langchain_webmetadata_extractor.tools.AsyncWebMetadataClient", FakeAsyncClient)
|
|
105
|
+
|
|
106
|
+
tool = WebMetadataExtractTool(api_key="fake-key")
|
|
107
|
+
result = await tool._arun("https://example.com")
|
|
108
|
+
|
|
109
|
+
assert json.loads(result) == {"title": "async result"}
|
|
110
|
+
assert calls == {"api_key": "fake-key", "url": "https://example.com"}
|