llm-tool-brave 0.9.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,60 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ release:
6
+ types: [published]
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ uses: actions/setup-python@v6
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Set up uv
28
+ uses: astral-sh/setup-uv@v6
29
+ with:
30
+ enable-cache: true
31
+
32
+ - name: Install dependencies
33
+ run: uv sync --locked --all-groups
34
+
35
+ - name: Run tests
36
+ run: uv run pytest
37
+
38
+ publish:
39
+ runs-on: ubuntu-latest
40
+ needs: [test]
41
+ environment: pypi
42
+
43
+ steps:
44
+ - uses: actions/checkout@v5
45
+
46
+ - name: Set up Python
47
+ uses: actions/setup-python@v6
48
+ with:
49
+ python-version: "3.x"
50
+
51
+ - name: Set up uv
52
+ uses: astral-sh/setup-uv@v6
53
+ with:
54
+ enable-cache: true
55
+
56
+ - name: Build
57
+ run: uv build
58
+
59
+ - name: Publish to PyPI
60
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,32 @@
1
+ name: Test
2
+
3
+ on: [push, pull_request]
4
+
5
+ permissions:
6
+ contents: read
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v6
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Set up uv
24
+ uses: astral-sh/setup-uv@v6
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --locked --all-groups
30
+
31
+ - name: Run tests
32
+ run: uv run pytest
@@ -0,0 +1,12 @@
1
+ .env
2
+ .venv/
3
+ venv/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ .pytest_cache/
8
+ .eggs/
9
+ *.egg-info/
10
+ dist/
11
+ build/
12
+ .DS_Store
@@ -0,0 +1 @@
1
+ README.md
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-tool-brave
3
+ Version: 0.9.0
4
+ Summary: brave Search API tool for Simon Willison's LLM
5
+ Project-URL: Homepage, https://github.com/rdslw/llm-tool-brave
6
+ Project-URL: Changelog, https://github.com/rdslw/llm-tool-brave/releases
7
+ Project-URL: Issues, https://github.com/rdslw/llm-tool-brave/issues
8
+ Project-URL: CI, https://github.com/rdslw/llm-tool-brave/actions
9
+ Author: llm-tool-brave contributors, Radoslaw Stachowiak
10
+ License-Expression: Apache-2.0
11
+ Keywords: brave,llm,llm-plugin,search,web-search
12
+ Requires-Python: >=3.9
13
+ Requires-Dist: llm>=0.27
14
+ Description-Content-Type: text/markdown
15
+
16
+ # llm-tool-brave
17
+
18
+ brave Search API plugin/tool for [Simon Willison's LLM](https://llm.datasette.io/).
19
+
20
+ Default api method: `context`, backed by brave's LLM Context API.
21
+ It returns pre-extracted, token-budgeted grounding content for the model running in `llm`.
22
+ **NB:** This plugin does **not** shell out to `bx`;
23
+ it calls `https://api.search.brave.com/res/v1` directly with the Python standard library.
24
+
25
+ `AGENTS.md` points at this file, so keep operational project knowledge here.
26
+
27
+ ## Quick Start
28
+
29
+ ### Quick in-place usage
30
+
31
+ ```bash
32
+ uv sync # see explanation below
33
+ uv run pytest # optional
34
+ uv run llm plugins # shall show llm-tool-brave
35
+ uv run llm tools list | grep -E '^ +brave'
36
+ ```
37
+
38
+ `uv sync` installs this package into `.venv` because `[tool.uv] package = true`.
39
+ The default `dev` dependency group makes `uv run pytest` work without extra flags.
40
+
41
+ Runtime-only local check:
42
+
43
+ ```bash
44
+ uv run --no-dev llm plugins
45
+ uv run --no-dev llm tools list | grep brave
46
+ ```
47
+
48
+ ### Install into a global `llm` environment from local dir:
49
+
50
+ ```bash
51
+ llm install -e .
52
+ llm plugins
53
+ ```
54
+
55
+ ### Install into a global `llm` after PyPi publishing:
56
+
57
+ ```bash
58
+ llm install llm-tool-brave
59
+ ```
60
+
61
+ ## Configuration (one-time)
62
+
63
+ Set a brave Search API key:
64
+
65
+ ```bash
66
+ llm keys set brave
67
+ ```
68
+
69
+ Lookup order: stored key alias `brave`, `brave-search`, then env BRAVE_SEARCH_API_KEY.
70
+
71
+ Avoid raw API keys in toolbox constructor arguments. LLM may log those; `llm keys`
72
+ or `BRAVE_SEARCH_API_KEY` keeps secrets out of prompts and tool-call logs.
73
+
74
+ ## Usage
75
+
76
+ ```bash
77
+ # default: only context tool is enabled; --td optional: shows tools calls by llm
78
+ llm -T Brave --td 'Search current web context for the latest llm tool plugin documentation and summarize it.'
79
+
80
+ # selected tools
81
+ llm -T 'Brave("context,web,news")' 'Find current uv release notes and summarize the important changes.'
82
+
83
+ # all implemented endpoints
84
+ llm -T 'Brave("all")' 'Find coffee shops near Warsaw city center and summarize the options.'
85
+ ```
86
+
87
+ ## Tools
88
+
89
+ | Tool | Endpoint | Plan | Use |
90
+ | --- | --- | --- | --- |
91
+ | `context` | `/llm/context` | Search | Main RAG/LLM grounding tool with extracted text chunks, tables, code, and source metadata. |
92
+ | `web` | `/web/search` | Search | Traditional web results with URLs, snippets, and rich result types. |
93
+ | `news` | `/news/search` | Search | Recent news with freshness filters. |
94
+ | `images` | `/images/search` | Search | Image results and thumbnails. |
95
+ | `videos` | `/videos/search` | Search | Video metadata. |
96
+ | `places` | `/local/place_search` | Search | Businesses, landmarks, POIs, cities, and addresses. |
97
+ | `suggest` | `/suggest/search` | Suggest | Query autocomplete and rich entity suggestions. |
98
+ | `spellcheck` | `/spellcheck/search` | Spellcheck | Pre-search query correction. |
99
+ | `answers` | `/chat/completions` | Answers | brave-generated answer. Prefer `context` when your current LLM should synthesize. |
100
+
101
+ Enable selected tools with `Brave("context,web,news")`; enable all with
102
+ `Brave("all")`.
103
+
104
+ ## Context Tool
105
+
106
+ Use `brave_context` first for web research, docs lookup, fact-checking, and RAG.
107
+
108
+ ```bash
109
+ llm -T Brave --td 'Use brave context to find Python 3.13 pathlib changes and explain the practical impact.'
110
+ llm -T Brave --td 'Use brave context, restricted to docs.python.org and peps.python.org, to answer: what changed in Python 3.13 typing?'
111
+ ```
112
+
113
+ | Parameter | brave API field | Default | Range / values |
114
+ | --- | --- | --- | --- |
115
+ | `query` | `q` | required | search query |
116
+ | `max_tokens` | `maximum_number_of_tokens` | `8192` | `1024-32768` |
117
+ | `max_urls` | `maximum_number_of_urls` | `20` | `1-50` |
118
+ | `max_snippets` | `maximum_number_of_snippets` | `50` | `1-100` |
119
+ | `max_tokens_per_url` | `maximum_number_of_tokens_per_url` | `4096` | `512-8192` |
120
+ | `max_snippets_per_url` | `maximum_number_of_snippets_per_url` | `50` | `1-100` |
121
+ | `threshold` | `context_threshold_mode` | `balanced` | `strict`, `balanced`, `lenient`, `disabled` |
122
+ | `freshness` | `freshness` | none | `pd`, `pw`, `pm`, `py`, or `YYYY-MM-DDtoYYYY-MM-DD` |
123
+ | `include_sites`, `exclude_sites`, `goggles` | `goggles` | none | inline brave Goggles rules |
124
+ | `lat`, `long`, `city`, `state`, `loc_country`, `postal_code` | location headers | none | location hints |
125
+
126
+ Additional params: `country` (`US`), `search_lang` (`en`), `count` (`20`, `1-50`), `spellcheck` (`true`), `enable_local` (none).
127
+
128
+ Each call is bounded by `max_tokens` before returning to `llm`. Multiple searches
129
+ consume multiple tool responses plus JSON/source metadata. Invalid documented brave
130
+ ranges are rejected before the API request.
131
+
132
+ ## Untrusted Web Content
133
+
134
+ Search result text is external and untrusted. The plugin wraps snippets and
135
+ descriptions with compact `BRAVE_UNTRUSTED_CONTENT` markers and adds one
136
+ `security_notice` field. URLs, titles, source metadata, and structured fields stay
137
+ intact.
138
+
139
+ This is a prompt-level guardrail, not a security boundary. Be careful combining
140
+ search tools with tools that can read private data, execute commands, send messages,
141
+ or exfiltrate information.
142
+
143
+ ## Python API
144
+
145
+ ```python
146
+ import llm
147
+ from llm_tool_brave import Brave
148
+
149
+ model = llm.get_model("gpt-5.4-mini")
150
+ response = model.chain(
151
+ "Use brave context to find the current LLM tool plugin docs and summarize the API shape.",
152
+ tools=[Brave()],
153
+ )
154
+ print(response.text())
155
+
156
+ tools = [Brave("context,web,news")]
157
+ ```
158
+
159
+ ## Build And Release
160
+
161
+ ```bash
162
+ uv build
163
+ ```
164
+
165
+ Publishing uses `.github/workflows/publish.yml`: full Python test matrix, `uv build`,
166
+ then PyPI Trusted Publishing. Before the first release, configure PyPI for repository
167
+ `rdslw/llm-tool-brave`, workflow `publish.yml`, environment `pypi`.
168
+
169
+ Release:
170
+
171
+ 1. Update `version` in `pyproject.toml`. PyPI versions are immutable.
172
+ 2. Run `uv sync --locked --all-groups`, `uv run pytest`, and `uv build`.
173
+ 3. Commit, tag, push, then create a GitHub release from that tag.
174
+
175
+ ```bash
176
+ git add pyproject.toml
177
+ git commit -m "Release 0.1.0"
178
+ git tag -a 0.1.0 -m "Release 0.1.0"
179
+ git push origin master
180
+ git push origin 0.1.0
181
+ ```
182
+
183
+ The release event triggers `Publish to PyPI`. `workflow_dispatch` is available for
184
+ manual publishing, but normal versions should publish from release tags.
185
+
186
+ Post-publish smoke test:
187
+
188
+ ```bash
189
+ uvx --from llm llm install llm-tool-brave
190
+ llm plugins
191
+ ```
192
+
193
+ ## Direct API Calls vs. `bx`
194
+
195
+ This plugin manually constructs HTTPS requests to the brave Search API instead of
196
+ invoking `bx`. Tradeoffs:
197
+
198
+ 1. **Packaging:** direct API calls avoid requiring a separate `bx` binary.
199
+ 2. **LLM ergonomics:** direct Python methods expose cleaner structured tool schemas.
200
+ 3. **Secrets:** direct `llm keys` / `BRAVE_SEARCH_API_KEY` integration avoids command-line API keys.
201
+ 4. **Feature coverage:** `bx` may expose brave API features, flags, config, and workflows not implemented here.
202
+ 5. **Errors:** direct calls return structured tool errors; `bx` has mature CLI exit codes and diagnostics.
203
+ 6. **Performance/control:** direct HTTP avoids subprocess and shell quoting overhead, but this plugin owns request/response behavior.
204
+ 7. **API changes:** because `bx` is developed by brave, it is more likely to pick up brave Search API changes quickly.
205
+ 8. **Shared utility:** an installed `bx` CLI is also useful for humans, scripts, and other tools, and is easy to test directly.
@@ -0,0 +1,190 @@
1
+ # llm-tool-brave
2
+
3
+ brave Search API plugin/tool for [Simon Willison's LLM](https://llm.datasette.io/).
4
+
5
+ Default api method: `context`, backed by brave's LLM Context API.
6
+ It returns pre-extracted, token-budgeted grounding content for the model running in `llm`.
7
+ **NB:** This plugin does **not** shell out to `bx`;
8
+ it calls `https://api.search.brave.com/res/v1` directly with the Python standard library.
9
+
10
+ `AGENTS.md` points at this file, so keep operational project knowledge here.
11
+
12
+ ## Quick Start
13
+
14
+ ### Quick in-place usage
15
+
16
+ ```bash
17
+ uv sync # see explanation below
18
+ uv run pytest # optional
19
+ uv run llm plugins # shall show llm-tool-brave
20
+ uv run llm tools list | grep -E '^ +brave'
21
+ ```
22
+
23
+ `uv sync` installs this package into `.venv` because `[tool.uv] package = true`.
24
+ The default `dev` dependency group makes `uv run pytest` work without extra flags.
25
+
26
+ Runtime-only local check:
27
+
28
+ ```bash
29
+ uv run --no-dev llm plugins
30
+ uv run --no-dev llm tools list | grep brave
31
+ ```
32
+
33
+ ### Install into a global `llm` environment from local dir:
34
+
35
+ ```bash
36
+ llm install -e .
37
+ llm plugins
38
+ ```
39
+
40
+ ### Install into a global `llm` after PyPi publishing:
41
+
42
+ ```bash
43
+ llm install llm-tool-brave
44
+ ```
45
+
46
+ ## Configuration (one-time)
47
+
48
+ Set a brave Search API key:
49
+
50
+ ```bash
51
+ llm keys set brave
52
+ ```
53
+
54
+ Lookup order: stored key alias `brave`, `brave-search`, then env BRAVE_SEARCH_API_KEY.
55
+
56
+ Avoid raw API keys in toolbox constructor arguments. LLM may log those; `llm keys`
57
+ or `BRAVE_SEARCH_API_KEY` keeps secrets out of prompts and tool-call logs.
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ # default: only context tool is enabled; --td optional: shows tools calls by llm
63
+ llm -T Brave --td 'Search current web context for the latest llm tool plugin documentation and summarize it.'
64
+
65
+ # selected tools
66
+ llm -T 'Brave("context,web,news")' 'Find current uv release notes and summarize the important changes.'
67
+
68
+ # all implemented endpoints
69
+ llm -T 'Brave("all")' 'Find coffee shops near Warsaw city center and summarize the options.'
70
+ ```
71
+
72
+ ## Tools
73
+
74
+ | Tool | Endpoint | Plan | Use |
75
+ | --- | --- | --- | --- |
76
+ | `context` | `/llm/context` | Search | Main RAG/LLM grounding tool with extracted text chunks, tables, code, and source metadata. |
77
+ | `web` | `/web/search` | Search | Traditional web results with URLs, snippets, and rich result types. |
78
+ | `news` | `/news/search` | Search | Recent news with freshness filters. |
79
+ | `images` | `/images/search` | Search | Image results and thumbnails. |
80
+ | `videos` | `/videos/search` | Search | Video metadata. |
81
+ | `places` | `/local/place_search` | Search | Businesses, landmarks, POIs, cities, and addresses. |
82
+ | `suggest` | `/suggest/search` | Suggest | Query autocomplete and rich entity suggestions. |
83
+ | `spellcheck` | `/spellcheck/search` | Spellcheck | Pre-search query correction. |
84
+ | `answers` | `/chat/completions` | Answers | brave-generated answer. Prefer `context` when your current LLM should synthesize. |
85
+
86
+ Enable selected tools with `Brave("context,web,news")`; enable all with
87
+ `Brave("all")`.
88
+
89
+ ## Context Tool
90
+
91
+ Use `brave_context` first for web research, docs lookup, fact-checking, and RAG.
92
+
93
+ ```bash
94
+ llm -T Brave --td 'Use brave context to find Python 3.13 pathlib changes and explain the practical impact.'
95
+ llm -T Brave --td 'Use brave context, restricted to docs.python.org and peps.python.org, to answer: what changed in Python 3.13 typing?'
96
+ ```
97
+
98
+ | Parameter | brave API field | Default | Range / values |
99
+ | --- | --- | --- | --- |
100
+ | `query` | `q` | required | search query |
101
+ | `max_tokens` | `maximum_number_of_tokens` | `8192` | `1024-32768` |
102
+ | `max_urls` | `maximum_number_of_urls` | `20` | `1-50` |
103
+ | `max_snippets` | `maximum_number_of_snippets` | `50` | `1-100` |
104
+ | `max_tokens_per_url` | `maximum_number_of_tokens_per_url` | `4096` | `512-8192` |
105
+ | `max_snippets_per_url` | `maximum_number_of_snippets_per_url` | `50` | `1-100` |
106
+ | `threshold` | `context_threshold_mode` | `balanced` | `strict`, `balanced`, `lenient`, `disabled` |
107
+ | `freshness` | `freshness` | none | `pd`, `pw`, `pm`, `py`, or `YYYY-MM-DDtoYYYY-MM-DD` |
108
+ | `include_sites`, `exclude_sites`, `goggles` | `goggles` | none | inline brave Goggles rules |
109
+ | `lat`, `long`, `city`, `state`, `loc_country`, `postal_code` | location headers | none | location hints |
110
+
111
+ Additional params: `country` (`US`), `search_lang` (`en`), `count` (`20`, `1-50`), `spellcheck` (`true`), `enable_local` (none).
112
+
113
+ Each call is bounded by `max_tokens` before returning to `llm`. Multiple searches
114
+ consume multiple tool responses plus JSON/source metadata. Invalid documented brave
115
+ ranges are rejected before the API request.
116
+
117
+ ## Untrusted Web Content
118
+
119
+ Search result text is external and untrusted. The plugin wraps snippets and
120
+ descriptions with compact `BRAVE_UNTRUSTED_CONTENT` markers and adds one
121
+ `security_notice` field. URLs, titles, source metadata, and structured fields stay
122
+ intact.
123
+
124
+ This is a prompt-level guardrail, not a security boundary. Be careful combining
125
+ search tools with tools that can read private data, execute commands, send messages,
126
+ or exfiltrate information.
127
+
128
+ ## Python API
129
+
130
+ ```python
131
+ import llm
132
+ from llm_tool_brave import Brave
133
+
134
+ model = llm.get_model("gpt-5.4-mini")
135
+ response = model.chain(
136
+ "Use brave context to find the current LLM tool plugin docs and summarize the API shape.",
137
+ tools=[Brave()],
138
+ )
139
+ print(response.text())
140
+
141
+ tools = [Brave("context,web,news")]
142
+ ```
143
+
144
+ ## Build And Release
145
+
146
+ ```bash
147
+ uv build
148
+ ```
149
+
150
+ Publishing uses `.github/workflows/publish.yml`: full Python test matrix, `uv build`,
151
+ then PyPI Trusted Publishing. Before the first release, configure PyPI for repository
152
+ `rdslw/llm-tool-brave`, workflow `publish.yml`, environment `pypi`.
153
+
154
+ Release:
155
+
156
+ 1. Update `version` in `pyproject.toml`. PyPI versions are immutable.
157
+ 2. Run `uv sync --locked --all-groups`, `uv run pytest`, and `uv build`.
158
+ 3. Commit, tag, push, then create a GitHub release from that tag.
159
+
160
+ ```bash
161
+ git add pyproject.toml
162
+ git commit -m "Release 0.1.0"
163
+ git tag -a 0.1.0 -m "Release 0.1.0"
164
+ git push origin master
165
+ git push origin 0.1.0
166
+ ```
167
+
168
+ The release event triggers `Publish to PyPI`. `workflow_dispatch` is available for
169
+ manual publishing, but normal versions should publish from release tags.
170
+
171
+ Post-publish smoke test:
172
+
173
+ ```bash
174
+ uvx --from llm llm install llm-tool-brave
175
+ llm plugins
176
+ ```
177
+
178
+ ## Direct API Calls vs. `bx`
179
+
180
+ This plugin manually constructs HTTPS requests to the brave Search API instead of
181
+ invoking `bx`. Tradeoffs:
182
+
183
+ 1. **Packaging:** direct API calls avoid requiring a separate `bx` binary.
184
+ 2. **LLM ergonomics:** direct Python methods expose cleaner structured tool schemas.
185
+ 3. **Secrets:** direct `llm keys` / `BRAVE_SEARCH_API_KEY` integration avoids command-line API keys.
186
+ 4. **Feature coverage:** `bx` may expose brave API features, flags, config, and workflows not implemented here.
187
+ 5. **Errors:** direct calls return structured tool errors; `bx` has mature CLI exit codes and diagnostics.
188
+ 6. **Performance/control:** direct HTTP avoids subprocess and shell quoting overhead, but this plugin owns request/response behavior.
189
+ 7. **API changes:** because `bx` is developed by brave, it is more likely to pick up brave Search API changes quickly.
190
+ 8. **Shared utility:** an installed `bx` CLI is also useful for humans, scripts, and other tools, and is easy to test directly.
@@ -0,0 +1,11 @@
1
+ ## List of TODO tasks for llm-tool-brave
2
+
3
+ * Based on llm-tool-brave create llm-tool-bx which will simply use 'bx' CLI tool instead of http api
4
+ - plan to be defined...
5
+
6
+ * Stage two untrusted-content hardening:
7
+ - add broader recursive coverage for all brave response fields that can contain external page text
8
+ - consider a fuller warning mode for fetched/full-page content if that endpoint is added later
9
+ - detect/log suspicious prompt-injection phrases for debugging without blocking legitimate results
10
+ - harden marker sanitization against homoglyph and zero-width spoofing patterns, following the fuller openclaw-style approach
11
+ - add compatibility tests using captured brave response fixtures for context, web, news, images, videos, and places