langchain-tabstack 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.
@@ -0,0 +1,35 @@
1
+ name: Lint
2
+
3
+ # Reusable workflow. Called by publish.yml and runs directly on PRs / pushes.
4
+ on:
5
+ workflow_call:
6
+ pull_request:
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint Check
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+ cache: pip
25
+
26
+ - name: Install dev dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install -e ".[dev]"
30
+
31
+ - name: Ruff lint
32
+ run: ruff check .
33
+
34
+ - name: Ruff format check
35
+ run: ruff format --check .
@@ -0,0 +1,81 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ permissions:
8
+ id-token: write # Required for trusted publishing to PyPI
9
+ contents: read
10
+
11
+ jobs:
12
+ # Lint must pass before publishing.
13
+ lint:
14
+ name: Lint Check
15
+ uses: ./.github/workflows/lint.yml
16
+ permissions:
17
+ contents: read
18
+
19
+ # Tests must pass on all supported Python versions before publishing.
20
+ test:
21
+ name: Test Check
22
+ uses: ./.github/workflows/test.yml
23
+ permissions:
24
+ contents: read
25
+
26
+ publish:
27
+ name: Build and Publish
28
+ needs: [lint, test]
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ id-token: write # Required for trusted publishing to PyPI
32
+ contents: read
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Set up Python
38
+ uses: actions/setup-python@v5
39
+ with:
40
+ python-version: "3.12"
41
+
42
+ - name: Set version from release tag
43
+ run: |
44
+ VERSION=${GITHUB_REF#refs/tags/v}
45
+ echo "Releasing version: $VERSION"
46
+ # Anchored to line start so `target-version`/`requires-python` are left alone.
47
+ sed -i 's/^version = ".*"/version = "'"$VERSION"'"/' pyproject.toml
48
+ sed -i 's/^__version__ = ".*"/__version__ = "'"$VERSION"'"/' langchain_tabstack/__init__.py
49
+ echo "pyproject.toml:"; grep '^version = ' pyproject.toml
50
+ echo "__init__.py:"; grep '^__version__ = ' langchain_tabstack/__init__.py
51
+
52
+ - name: Install build tooling
53
+ run: |
54
+ python -m pip install --upgrade pip
55
+ python -m pip install build twine
56
+
57
+ - name: Build package
58
+ run: python -m build
59
+
60
+ - name: Check package
61
+ run: python -m twine check dist/*
62
+
63
+ - name: Publish to TestPyPI
64
+ uses: pypa/gh-action-pypi-publish@release/v1
65
+ with:
66
+ repository-url: https://test.pypi.org/legacy/
67
+ attestations: false
68
+
69
+ - name: Verify TestPyPI installation
70
+ run: |
71
+ VERSION=${GITHUB_REF#refs/tags/v}
72
+ echo "Waiting for TestPyPI to process the upload..."
73
+ sleep 30
74
+ python -m pip install \
75
+ --index-url https://test.pypi.org/simple/ \
76
+ --extra-index-url https://pypi.org/simple/ \
77
+ "langchain-tabstack==$VERSION"
78
+ python -c "import langchain_tabstack; print(f'Imported langchain-tabstack {langchain_tabstack.__version__}')"
79
+
80
+ - name: Publish to PyPI
81
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,36 @@
1
+ name: Test
2
+
3
+ # Reusable workflow. Called by publish.yml and runs directly on PRs / pushes.
4
+ on:
5
+ workflow_call:
6
+ pull_request:
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ name: Test Check
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ cache: pip
29
+
30
+ - name: Install package and test dependencies
31
+ run: |
32
+ python -m pip install --upgrade pip
33
+ pip install -e ".[test]"
34
+
35
+ - name: Test
36
+ run: pytest -q
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .env
13
+ .env.*
14
+ !.env.example
@@ -0,0 +1,70 @@
1
+ # AGENTS.md: langchain-tabstack
2
+
3
+ Guide for AI agents working on this package. Humans: see `README.md`.
4
+
5
+ ## What this package is
6
+
7
+ Tabstack tools for LangChain (Python). It wraps the official `tabstack` SDK and exposes Tabstack's
8
+ extract / research / generate / automate capabilities as native LangChain tools. It mirrors the
9
+ TypeScript `@tabstack/langchain` and `@tabstack/ai` packages tool-for-tool: same names, descriptions,
10
+ and input fields.
11
+
12
+ ## Why use it
13
+
14
+ Schema-enforced web extraction and multi-source research as drop-in LangChain tools, backed by the
15
+ SDK (no bespoke HTTP, no Playwright). Consistent with the TypeScript packages so a cross-language
16
+ stack behaves the same.
17
+
18
+ ## How to use it (consumer)
19
+
20
+ ```python
21
+ from langchain_tabstack import TABSTACK_TOOLS, create_tabstack_tools
22
+
23
+ # TABSTACK_TOOLS: list using the default client (reads TABSTACK_API_KEY lazily)
24
+ # create_tabstack_tools(api_key=...) or (client=...) / (async_client=...) for a custom client
25
+ ```
26
+
27
+ Every tool supports both `invoke` (sync) and `ainvoke` (native async via `AsyncTabstack`).
28
+
29
+ ## Architecture (single source of truth, mirror of the TS `core`)
30
+
31
+ | File | Responsibility |
32
+ | --- | --- |
33
+ | `client.py` | Sync + async client construction, lazy defaults (read `TABSTACK_API_KEY`). |
34
+ | `schemas.py` | Pydantic input models. Single source of truth for the input surface. |
35
+ | `tools.py` | Tool names/descriptions, sync + async bodies, the `create_tabstack_tools` factory. |
36
+ | `errors.py` | `TabstackToolError` + `normalize_error`. |
37
+
38
+ ## Rules specific to this package
39
+
40
+ - **Python 3.9 is the floor.** Use `typing.Optional/Dict/List`, not PEP 604/585 (`X | None`,
41
+ `list[...]`). Pydantic evaluates these annotations at runtime and the newer syntax breaks on 3.9.
42
+ This is why ruff's `UP` rules are disabled in `pyproject.toml`.
43
+ - All network work goes through the SDK client. No bespoke HTTP.
44
+ - Tools return **JSON strings** (`json.dumps`), the LangChain-Python convention. `extract_page_content`
45
+ returns the markdown string directly. Keys are snake_case (the TS packages use camelCase objects).
46
+ - `automate_browser_task` runs non-interactively: it does not enable the human-in-the-loop
47
+ `agent.automate_input` flow.
48
+ - Keep names, descriptions, and fields in lockstep with the TypeScript packages.
49
+
50
+ ## Adding a tool
51
+
52
+ 1. `schemas.py`: pydantic input model (reuse the shared field-description constants).
53
+ 2. `tools.py`: `TOOL_NAMES`, `TOOL_DESCRIPTIONS`, a sync body, an async body, and `_build(...)` in
54
+ `create_tabstack_tools`.
55
+ 3. `__init__.py`: export anything new.
56
+ 4. Add tests, and mirror the tool into the TypeScript packages.
57
+
58
+ ## Build / test
59
+
60
+ ```bash
61
+ python -m venv .venv
62
+ .venv/bin/pip install -e ".[test,dev]"
63
+ .venv/bin/ruff check . && .venv/bin/ruff format --check .
64
+ .venv/bin/pytest -q
65
+ .venv/bin/python -m build # wheel + sdist
66
+ ```
67
+
68
+ Tests mock the SDK client (sync via `MagicMock`, async via coroutine side-effects) and assert names,
69
+ descriptions, schema fields, and execution routing, so drift fails CI. CI runs the matrix
70
+ 3.9 / 3.10 / 3.11 / 3.12.
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-tabstack
3
+ Version: 0.1.0
4
+ Summary: Tabstack tools for LangChain. Schema-enforced web extraction and research, backed by the official Tabstack SDK.
5
+ Project-URL: Homepage, https://tabstack.ai
6
+ Project-URL: Documentation, https://docs.tabstack.ai
7
+ Project-URL: Source, https://github.com/Mozilla-Ocho/tabstack-langchain-python
8
+ Author: Tabstack
9
+ License: MIT
10
+ Keywords: agent,extraction,langchain,tabstack,tools,web-scraping
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: langchain-core<2,>=0.3
21
+ Requires-Dist: pydantic>=2
22
+ Requires-Dist: tabstack>=2.6.1
23
+ Provides-Extra: dev
24
+ Requires-Dist: ruff>=0.6; extra == 'dev'
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest-mock>=3; extra == 'test'
27
+ Requires-Dist: pytest>=8; extra == 'test'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # langchain-tabstack
31
+
32
+ Give your [LangChain](https://python.langchain.com) agents reliable web access. Schema-enforced
33
+ extraction, multi-source research, AI transformation, and browser automation, all as native
34
+ LangChain tools backed by the official [Tabstack SDK](https://pypi.org/project/tabstack/).
35
+
36
+ ## Why Tabstack
37
+
38
+ LangChain's built-in loaders (`WebBaseLoader`, `PlaywrightURLLoader`) are fine for prototypes and
39
+ brittle in production: unpredictable parsing, Playwright binaries to install and maintain, and
40
+ behaviour that drifts across LangChain releases.
41
+
42
+ Tabstack replaces them with a hosted API:
43
+
44
+ - **Schema-enforced output.** You define the shape, you get that shape back. No prompt-engineering
45
+ the JSON out of raw text.
46
+ - **No browser to run.** JS-heavy pages render server-side. No Playwright, no binaries.
47
+ - **Version-independent.** It is an SDK call, not a loader coupled to your LangChain version.
48
+ - **Sync and async.** Every tool supports `invoke` and native `ainvoke`.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install langchain-tabstack
54
+ ```
55
+
56
+ Requires Python 3.9+. For the agent example below you also need a chat model, for example
57
+ `pip install langchain langchain-openai`. Set your API key (get one at
58
+ [console.tabstack.ai](https://console.tabstack.ai)):
59
+
60
+ ```bash
61
+ export TABSTACK_API_KEY="your-key-here"
62
+ ```
63
+
64
+ ## Quickstart
65
+
66
+ ```python
67
+ from langchain.agents import create_agent
68
+ from langchain_tabstack import TABSTACK_TOOLS
69
+
70
+ agent = create_agent(
71
+ "openai:gpt-4o",
72
+ tools=TABSTACK_TOOLS,
73
+ system_prompt=(
74
+ "You are a research assistant with web intelligence tools. "
75
+ "Use extract_structured_data for specific fields from a URL, "
76
+ "extract_page_content for full page text, and research_question for multi-source research."
77
+ ),
78
+ )
79
+
80
+ result = agent.invoke(
81
+ {"messages": [{"role": "user", "content": "What are Vercel's pricing plans?"}]}
82
+ )
83
+ print(result["messages"][-1].content)
84
+ ```
85
+
86
+ `TABSTACK_TOOLS` reads `TABSTACK_API_KEY` from the environment and is ready to drop into any agent.
87
+
88
+ > Using LangChain 0.x? `create_agent` is the LangChain 1.x replacement for `AgentExecutor` +
89
+ > `create_tool_calling_agent`. The tools themselves work with either.
90
+
91
+ ## The tools
92
+
93
+ | Tool | What it does |
94
+ | --- | --- |
95
+ | `extract_structured_data` | Pull specific fields from a URL into a JSON shape you define. |
96
+ | `extract_page_content` | Fetch a page as clean markdown. |
97
+ | `research_question` | Synthesised answer with cited sources across multiple pages. |
98
+ | `generate_structured_data` | Fetch a page, then AI-transform it into derived or reshaped JSON. |
99
+ | `automate_browser_task` | Run a multi-step, natural-language browser task. |
100
+
101
+ Every tool is exported individually too, so you can use one directly without an agent. Tools return
102
+ a JSON string (use `json.loads`); `extract_page_content` returns markdown text directly.
103
+
104
+ ### extract_structured_data
105
+
106
+ ```python
107
+ import json
108
+ from langchain_tabstack import extract_structured_data_tool
109
+
110
+ raw = extract_structured_data_tool.invoke(
111
+ {
112
+ "url": "https://news.ycombinator.com",
113
+ # json_schema_json is a JSON-encoded JSON Schema describing the fields you want.
114
+ "json_schema_json": json.dumps(
115
+ {
116
+ "type": "object",
117
+ "properties": {
118
+ "stories": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "object",
122
+ "properties": {
123
+ "title": {"type": "string"},
124
+ "points": {"type": "number", "description": "Story points"},
125
+ },
126
+ },
127
+ }
128
+ },
129
+ }
130
+ ),
131
+ }
132
+ )
133
+ data = json.loads(raw)
134
+ print(data["stories"])
135
+ ```
136
+
137
+ ### extract_page_content
138
+
139
+ ```python
140
+ from langchain_tabstack import extract_page_content_tool
141
+
142
+ markdown = extract_page_content_tool.invoke({"url": "https://example.com/blog/article"})
143
+ ```
144
+
145
+ ### research_question
146
+
147
+ ```python
148
+ import json
149
+ from langchain_tabstack import research_question_tool
150
+
151
+ result = json.loads(
152
+ research_question_tool.invoke(
153
+ {"query": "What are the latest developments in quantum error correction?"}
154
+ )
155
+ )
156
+ print(result["answer"])
157
+ for source in result["sources"]: # [{"title": ..., "url": ...}, ...]
158
+ print(source["url"])
159
+ ```
160
+
161
+ ### generate_structured_data
162
+
163
+ ```python
164
+ import json
165
+ from langchain_tabstack import generate_structured_data_tool
166
+
167
+ raw = generate_structured_data_tool.invoke(
168
+ {
169
+ "url": "https://news.ycombinator.com",
170
+ "instructions": "For each story, categorize it and write a one-sentence summary.",
171
+ "json_schema_json": json.dumps(
172
+ {
173
+ "type": "object",
174
+ "properties": {
175
+ "summaries": {
176
+ "type": "array",
177
+ "items": {
178
+ "type": "object",
179
+ "properties": {
180
+ "title": {"type": "string"},
181
+ "category": {"type": "string"},
182
+ "summary": {"type": "string"},
183
+ },
184
+ },
185
+ }
186
+ },
187
+ }
188
+ ),
189
+ }
190
+ )
191
+ ```
192
+
193
+ ### automate_browser_task
194
+
195
+ ```python
196
+ import json
197
+ from langchain_tabstack import automate_browser_task_tool
198
+
199
+ result = json.loads(
200
+ automate_browser_task_tool.invoke(
201
+ {
202
+ "task": "Find the top 3 trending repositories and their star counts",
203
+ "url": "https://github.com/trending",
204
+ "guardrails": "browse and extract only, do not submit forms",
205
+ }
206
+ )
207
+ )
208
+ # {"answer", "success", "iterations", "actions", "duration_ms", "extracted", "pages_visited"}
209
+ ```
210
+
211
+ ## Optional inputs
212
+
213
+ Pass these alongside the required inputs for finer control. They are sent only when provided, so
214
+ omitting them keeps Tabstack's defaults.
215
+
216
+ - `extract_structured_data`, `extract_page_content`, `generate_structured_data`:
217
+ - `effort`: `"min"` | `"standard"` | `"max"`. Use `"max"` for JS-heavy pages (full server-side
218
+ browser rendering, the `PlaywrightURLLoader` replacement).
219
+ - `nocache`: `True` to bypass the cache.
220
+ - `country`: ISO 3166-1 alpha-2 code (for example `"US"`) for geotargeted fetches.
221
+ - `research_question`: `mode` (`"fast"` | `"balanced"`), `nocache`.
222
+ - `automate_browser_task`: `data` (context for form filling), `country`, `max_iterations`,
223
+ `max_validation_attempts`.
224
+
225
+ ```python
226
+ # Render a JS-heavy SPA, fresh, from the UK:
227
+ extract_page_content_tool.invoke(
228
+ {"url": url, "effort": "max", "nocache": True, "country": "GB"}
229
+ )
230
+ ```
231
+
232
+ ## Async
233
+
234
+ Every tool supports `ainvoke` natively, backed by `AsyncTabstack`, so it runs in your event loop
235
+ without a thread-pool bounce:
236
+
237
+ ```python
238
+ markdown = await extract_page_content_tool.ainvoke({"url": "https://example.com"})
239
+ ```
240
+
241
+ ## Configuration
242
+
243
+ For a custom API key or base URL, or to reuse one client, build the tools explicitly:
244
+
245
+ ```python
246
+ from langchain_tabstack import create_tabstack_tools
247
+
248
+ tools = create_tabstack_tools(api_key="...")
249
+ # or pass clients you already have:
250
+ # create_tabstack_tools(client=..., async_client=...)
251
+ ```
252
+
253
+ ## Error handling
254
+
255
+ Tool calls raise `TabstackToolError` with a normalised message and, for API failures, an HTTP
256
+ `status`:
257
+
258
+ ```python
259
+ from langchain_tabstack import TabstackToolError, extract_page_content_tool
260
+
261
+ try:
262
+ extract_page_content_tool.invoke({"url": "https://example.com"})
263
+ except TabstackToolError as err:
264
+ print(f"Tabstack failed ({err.status or 'no status'}): {err}")
265
+ ```
266
+
267
+ ## Good to know
268
+
269
+ - **`automate_browser_task` runs non-interactively.** It does not pause for human-in-the-loop form
270
+ input, so it never blocks. It returns the final answer plus the data it extracted and the pages it
271
+ visited.
272
+ - The client is created lazily, so importing the package never requires an API key to be set.
273
+ - The tool names, descriptions, and inputs match the
274
+ [`@tabstack/langchain`](https://www.npmjs.com/package/@tabstack/langchain) (LangChain.js) and
275
+ [`@tabstack/ai`](https://www.npmjs.com/package/@tabstack/ai) (Vercel AI SDK) packages, so
276
+ behaviour is consistent across languages and frameworks. Those return native objects with
277
+ camelCase keys; this package returns JSON strings with snake_case keys, the LangChain-Python
278
+ convention. The data is the same.
279
+
280
+ ## Links
281
+
282
+ - [Tabstack docs](https://docs.tabstack.ai)
283
+ - [Tabstack SDK (`tabstack`)](https://pypi.org/project/tabstack/)
284
+ - [Get an API key](https://console.tabstack.ai)