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.
@@ -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,13 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ .benchmarks/
8
+ dist/
9
+ .red_team_scratch/
10
+ red_team_report.*
11
+ .env
12
+ .env.*
13
+ !.env.example
@@ -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