langchain-keenable 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_keenable-0.1.0/.github/workflows/publish.yml +50 -0
- langchain_keenable-0.1.0/.gitignore +13 -0
- langchain_keenable-0.1.0/LICENSE +21 -0
- langchain_keenable-0.1.0/Makefile +27 -0
- langchain_keenable-0.1.0/PKG-INFO +97 -0
- langchain_keenable-0.1.0/PUBLISH.md +85 -0
- langchain_keenable-0.1.0/README.md +72 -0
- langchain_keenable-0.1.0/langchain_keenable/__init__.py +15 -0
- langchain_keenable-0.1.0/langchain_keenable/py.typed +0 -0
- langchain_keenable-0.1.0/langchain_keenable/tools.py +435 -0
- langchain_keenable-0.1.0/pyproject.toml +56 -0
- langchain_keenable-0.1.0/tests/__init__.py +0 -0
- langchain_keenable-0.1.0/tests/integration_tests/__init__.py +0 -0
- langchain_keenable-0.1.0/tests/integration_tests/test_standard.py +40 -0
- langchain_keenable-0.1.0/tests/unit_tests/__init__.py +0 -0
- langchain_keenable-0.1.0/tests/unit_tests/test_imports.py +14 -0
- langchain_keenable-0.1.0/tests/unit_tests/test_standard.py +35 -0
- langchain_keenable-0.1.0/tests/unit_tests/test_tools.py +447 -0
- langchain_keenable-0.1.0/uv.lock +1885 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes langchain-keenable to PyPI via Trusted Publishing (OIDC) — no API
|
|
4
|
+
# tokens or passwords. Runs when a GitHub Release is published. The release tag
|
|
5
|
+
# must match the version in pyproject.toml (e.g. tag v0.1.0 -> version 0.1.0).
|
|
6
|
+
#
|
|
7
|
+
# One-time setup on PyPI (see PUBLISH.md): register a Trusted Publisher /
|
|
8
|
+
# pending publisher for project `langchain-keenable` pointing at this repo,
|
|
9
|
+
# workflow `publish.yml`, environment `pypi`.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
workflow_dispatch: {}
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
name: Build distribution
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v5
|
|
24
|
+
- name: Build sdist + wheel
|
|
25
|
+
run: uv build
|
|
26
|
+
- name: Check metadata
|
|
27
|
+
run: uvx twine check dist/*
|
|
28
|
+
- name: Upload artifact
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
name: Publish to PyPI
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/langchain-keenable
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write # required for Trusted Publishing (OIDC)
|
|
43
|
+
steps:
|
|
44
|
+
- name: Download artifact
|
|
45
|
+
uses: actions/download-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
- name: Publish to PyPI (Trusted Publishing)
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Keenable
|
|
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,27 @@
|
|
|
1
|
+
.PHONY: all format lint test integration_test typing help
|
|
2
|
+
|
|
3
|
+
all: help
|
|
4
|
+
|
|
5
|
+
test:
|
|
6
|
+
uv run --group test python -m pytest tests/unit_tests -q
|
|
7
|
+
|
|
8
|
+
integration_test:
|
|
9
|
+
uv run --group test python -m pytest tests/integration_tests -q
|
|
10
|
+
|
|
11
|
+
lint:
|
|
12
|
+
uv run --group lint python -m ruff check .
|
|
13
|
+
uv run --group lint python -m ruff format --check .
|
|
14
|
+
|
|
15
|
+
format:
|
|
16
|
+
uv run --group lint python -m ruff format .
|
|
17
|
+
uv run --group lint python -m ruff check --fix .
|
|
18
|
+
|
|
19
|
+
typing:
|
|
20
|
+
uv run --group typing python -m mypy langchain_keenable
|
|
21
|
+
|
|
22
|
+
help:
|
|
23
|
+
@echo 'test - run unit + standard unit tests'
|
|
24
|
+
@echo 'integration_test - run live integration/standard tests'
|
|
25
|
+
@echo 'lint - run ruff check + format --check'
|
|
26
|
+
@echo 'format - apply ruff format + fixes'
|
|
27
|
+
@echo 'typing - run mypy'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-keenable
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting Keenable web search and LangChain
|
|
5
|
+
Project-URL: Homepage, https://keenable.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.keenable.ai
|
|
7
|
+
Project-URL: Repository, https://github.com/keenableai/langchain-keenable
|
|
8
|
+
Project-URL: Source Code, https://github.com/keenableai/langchain-keenable
|
|
9
|
+
Author-email: Keenable <hello@keenable.ai>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
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: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: <4.0,>=3.10
|
|
22
|
+
Requires-Dist: langchain-core<2.0,>=0.3
|
|
23
|
+
Requires-Dist: requests<3.0,>=2.32
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# langchain-keenable
|
|
27
|
+
|
|
28
|
+
This package contains the LangChain integration with [Keenable](https://keenable.ai), a web search
|
|
29
|
+
and page-fetch API built for AI agents.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -U langchain-keenable
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Optionally set a `KEENABLE_API_KEY` environment variable to use the authenticated endpoints. Without a
|
|
38
|
+
key, both search and fetch transparently fall back to their keyless public endpoints.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export KEENABLE_API_KEY="your-api-key" # optional; create one at https://keenable.ai/console
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The API endpoint defaults to `https://api.keenable.ai` and can be overridden (e.g. for staging) with the
|
|
45
|
+
`KEENABLE_API_URL` environment variable. It must be an `https://` URL.
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
|
|
49
|
+
### `KeenableSearch`
|
|
50
|
+
|
|
51
|
+
Queries the Keenable search API and returns a list of result dictionaries. All filters are
|
|
52
|
+
**per-invocation**, so an agent can vary them per query:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from langchain_keenable import KeenableSearch
|
|
56
|
+
|
|
57
|
+
# Works with no key (keyless public endpoint) or with KEENABLE_API_KEY set.
|
|
58
|
+
tool = KeenableSearch()
|
|
59
|
+
|
|
60
|
+
results = tool.invoke({
|
|
61
|
+
"query": "typescript best practices",
|
|
62
|
+
"site": "github.com", # optional: restrict to a domain
|
|
63
|
+
"published_after": "2026-01-01", # optional: YYYY-MM-DD date filters
|
|
64
|
+
# "published_before" / "acquired_after" / "acquired_before" also supported
|
|
65
|
+
# "mode": "realtime", # optional per-call override (needs an org key)
|
|
66
|
+
})
|
|
67
|
+
for result in results:
|
|
68
|
+
print(result["title"], result["url"])
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`mode` defaults to `"pro"` (deeper retrieval); use `"realtime"` for latency-sensitive cases such as
|
|
72
|
+
voice agents. It can be set as a class default and overridden per call. `realtime` requires an org key.
|
|
73
|
+
|
|
74
|
+
### `KeenableFetch`
|
|
75
|
+
|
|
76
|
+
Fetches a page via Keenable and returns its main content as markdown — pair it with `KeenableSearch` so
|
|
77
|
+
an agent can read the pages it discovers:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from langchain_keenable import KeenableFetch
|
|
81
|
+
|
|
82
|
+
tool = KeenableFetch()
|
|
83
|
+
page = tool.invoke({"url": "https://example.com/article"})
|
|
84
|
+
print(page["title"], page["content"])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Error handling
|
|
88
|
+
|
|
89
|
+
Both tools set `handle_tool_error = True`: rate limits (429), auth (401) and credit (402) errors, network
|
|
90
|
+
timeouts and malformed responses are surfaced to the agent as an error **string** (carrying the backend's
|
|
91
|
+
message) rather than raising and crashing the agent loop.
|
|
92
|
+
|
|
93
|
+
## Async
|
|
94
|
+
|
|
95
|
+
Both tools implement `_arun`, so `await tool.ainvoke({...})` works (the request runs in a worker thread).
|
|
96
|
+
|
|
97
|
+
The tools can be bound to any LangChain chat model that supports tool calling and used within an agent.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Publishing `langchain-keenable` to PyPI
|
|
2
|
+
|
|
3
|
+
This package publishes to PyPI via **Trusted Publishing (OIDC)** — GitHub Actions
|
|
4
|
+
mints a short-lived identity token and PyPI trusts it, so **no API tokens or
|
|
5
|
+
passwords are stored anywhere**. The workflow is
|
|
6
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml); it runs when a
|
|
7
|
+
GitHub **Release** is published.
|
|
8
|
+
|
|
9
|
+
## One-time setup (do this once, by a PyPI account owner)
|
|
10
|
+
|
|
11
|
+
The PyPI account needs a **verified email** and **2FA enabled** first
|
|
12
|
+
(https://pypi.org/manage/account/). Then:
|
|
13
|
+
|
|
14
|
+
1. Go to **https://pypi.org/manage/account/publishing/** ("Publishing" →
|
|
15
|
+
"Add a new pending publisher"). A *pending* publisher lets you wire this up
|
|
16
|
+
**before the project exists** — the first release creates the project.
|
|
17
|
+
2. Fill in exactly:
|
|
18
|
+
- **PyPI Project Name:** `langchain-keenable`
|
|
19
|
+
- **Owner:** `keenableai`
|
|
20
|
+
- **Repository name:** `langchain-keenable`
|
|
21
|
+
- **Workflow name:** `publish.yml`
|
|
22
|
+
- **Environment name:** `pypi`
|
|
23
|
+
3. Save. (Values must match the workflow exactly, including the environment name
|
|
24
|
+
`pypi` — a mismatch is the #1 reason publishing silently fails with
|
|
25
|
+
"not a trusted publisher".)
|
|
26
|
+
4. *(Recommended)* In this GitHub repo → **Settings → Environments → New
|
|
27
|
+
environment → `pypi`**, and optionally add required reviewers so a release
|
|
28
|
+
needs a human approval before it publishes.
|
|
29
|
+
|
|
30
|
+
That's it — no secrets to add to GitHub.
|
|
31
|
+
|
|
32
|
+
## Cutting a release
|
|
33
|
+
|
|
34
|
+
1. Bump `version` in `pyproject.toml` (PyPI never lets you re-upload the same
|
|
35
|
+
version). Commit + push to `main`.
|
|
36
|
+
2. Tag and create a GitHub Release:
|
|
37
|
+
```bash
|
|
38
|
+
git tag v0.1.0 # tag must match pyproject version
|
|
39
|
+
git push origin v0.1.0
|
|
40
|
+
gh release create v0.1.0 --title "v0.1.0" --notes "Initial release"
|
|
41
|
+
```
|
|
42
|
+
(Or use the GitHub UI: Releases → Draft a new release.)
|
|
43
|
+
3. The **Publish to PyPI** workflow runs: build → `twine check` → publish via
|
|
44
|
+
OIDC. Watch it under the repo's **Actions** tab. On success the package is
|
|
45
|
+
live at https://pypi.org/project/langchain-keenable/.
|
|
46
|
+
4. After the first successful publish you can tighten the PyPI publisher to be
|
|
47
|
+
project-scoped (it already is, by project name).
|
|
48
|
+
|
|
49
|
+
You can also trigger a (re)publish manually from **Actions → Publish to PyPI →
|
|
50
|
+
Run workflow** (`workflow_dispatch`) — useful once a tag/release already exists.
|
|
51
|
+
|
|
52
|
+
## Pre-release checks (run locally first)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
rm -rf dist && uv build
|
|
56
|
+
uvx twine check dist/*
|
|
57
|
+
uv run --group test pytest tests/unit_tests # 59 passing, offline
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Troubleshooting ("we tried and it was silent")
|
|
61
|
+
|
|
62
|
+
- **No verification email on signup:** check Spam; resend from
|
|
63
|
+
https://pypi.org/manage/account/ → Account emails; the link expires — request a
|
|
64
|
+
fresh one. Corporate mail (`@keenable.ai`) may filter it; try a personal email
|
|
65
|
+
and add the work address later.
|
|
66
|
+
- **2FA not enabled:** you cannot create tokens *or* publish without it. Enable a
|
|
67
|
+
TOTP app under account settings.
|
|
68
|
+
- **Workflow runs but PyPI rejects with "not a trusted publisher":** the pending
|
|
69
|
+
publisher fields don't match — most often the **environment name** (`pypi`) or
|
|
70
|
+
the **workflow filename** (`publish.yml`). Re-check step 2 above.
|
|
71
|
+
- **`Missing id-token: write`:** the `publish` job must keep
|
|
72
|
+
`permissions: id-token: write` (it does). Don't remove it.
|
|
73
|
+
- **Re-uploading the same version:** PyPI refuses silently-looking 400s — bump
|
|
74
|
+
the version; you can't overwrite `0.1.0` once published.
|
|
75
|
+
|
|
76
|
+
## Manual fallback (token, only if Actions is unavailable)
|
|
77
|
+
|
|
78
|
+
Trusted Publishing is preferred. If you must publish from a laptop:
|
|
79
|
+
```bash
|
|
80
|
+
rm -rf dist && uv build
|
|
81
|
+
UV_PUBLISH_TOKEN=pypi-XXXX uv publish # account- or project-scoped token; user is __token__
|
|
82
|
+
```
|
|
83
|
+
Create the token at https://pypi.org/manage/account/token/ (first one is
|
|
84
|
+
account-scoped because the project doesn't exist yet; switch to a
|
|
85
|
+
project-scoped token after the first publish). Prefer the OIDC workflow above.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# langchain-keenable
|
|
2
|
+
|
|
3
|
+
This package contains the LangChain integration with [Keenable](https://keenable.ai), a web search
|
|
4
|
+
and page-fetch API built for AI agents.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install -U langchain-keenable
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Optionally set a `KEENABLE_API_KEY` environment variable to use the authenticated endpoints. Without a
|
|
13
|
+
key, both search and fetch transparently fall back to their keyless public endpoints.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export KEENABLE_API_KEY="your-api-key" # optional; create one at https://keenable.ai/console
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The API endpoint defaults to `https://api.keenable.ai` and can be overridden (e.g. for staging) with the
|
|
20
|
+
`KEENABLE_API_URL` environment variable. It must be an `https://` URL.
|
|
21
|
+
|
|
22
|
+
## Tools
|
|
23
|
+
|
|
24
|
+
### `KeenableSearch`
|
|
25
|
+
|
|
26
|
+
Queries the Keenable search API and returns a list of result dictionaries. All filters are
|
|
27
|
+
**per-invocation**, so an agent can vary them per query:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from langchain_keenable import KeenableSearch
|
|
31
|
+
|
|
32
|
+
# Works with no key (keyless public endpoint) or with KEENABLE_API_KEY set.
|
|
33
|
+
tool = KeenableSearch()
|
|
34
|
+
|
|
35
|
+
results = tool.invoke({
|
|
36
|
+
"query": "typescript best practices",
|
|
37
|
+
"site": "github.com", # optional: restrict to a domain
|
|
38
|
+
"published_after": "2026-01-01", # optional: YYYY-MM-DD date filters
|
|
39
|
+
# "published_before" / "acquired_after" / "acquired_before" also supported
|
|
40
|
+
# "mode": "realtime", # optional per-call override (needs an org key)
|
|
41
|
+
})
|
|
42
|
+
for result in results:
|
|
43
|
+
print(result["title"], result["url"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`mode` defaults to `"pro"` (deeper retrieval); use `"realtime"` for latency-sensitive cases such as
|
|
47
|
+
voice agents. It can be set as a class default and overridden per call. `realtime` requires an org key.
|
|
48
|
+
|
|
49
|
+
### `KeenableFetch`
|
|
50
|
+
|
|
51
|
+
Fetches a page via Keenable and returns its main content as markdown — pair it with `KeenableSearch` so
|
|
52
|
+
an agent can read the pages it discovers:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from langchain_keenable import KeenableFetch
|
|
56
|
+
|
|
57
|
+
tool = KeenableFetch()
|
|
58
|
+
page = tool.invoke({"url": "https://example.com/article"})
|
|
59
|
+
print(page["title"], page["content"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Error handling
|
|
63
|
+
|
|
64
|
+
Both tools set `handle_tool_error = True`: rate limits (429), auth (401) and credit (402) errors, network
|
|
65
|
+
timeouts and malformed responses are surfaced to the agent as an error **string** (carrying the backend's
|
|
66
|
+
message) rather than raising and crashing the agent loop.
|
|
67
|
+
|
|
68
|
+
## Async
|
|
69
|
+
|
|
70
|
+
Both tools implement `_arun`, so `await tool.ainvoke({...})` works (the request runs in a worker thread).
|
|
71
|
+
|
|
72
|
+
The tools can be bound to any LangChain chat model that supports tool calling and used within an agent.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""LangChain integration for the Keenable web search API."""
|
|
2
|
+
|
|
3
|
+
from langchain_keenable.tools import (
|
|
4
|
+
KeenableFetch,
|
|
5
|
+
KeenableFetchInput,
|
|
6
|
+
KeenableSearch,
|
|
7
|
+
KeenableSearchInput,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"KeenableFetch",
|
|
12
|
+
"KeenableFetchInput",
|
|
13
|
+
"KeenableSearch",
|
|
14
|
+
"KeenableSearchInput",
|
|
15
|
+
]
|
|
File without changes
|