plasmate-browser-use 0.3.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.
- plasmate_browser_use-0.3.0/.gitignore +22 -0
- plasmate_browser_use-0.3.0/PKG-INFO +168 -0
- plasmate_browser_use-0.3.0/README.md +154 -0
- plasmate_browser_use-0.3.0/examples/basic_agent.py +24 -0
- plasmate_browser_use-0.3.0/plasmate_browser_use/__init__.py +24 -0
- plasmate_browser_use-0.3.0/plasmate_browser_use/extractor.py +145 -0
- plasmate_browser_use-0.3.0/plasmate_browser_use/utils.py +64 -0
- plasmate_browser_use-0.3.0/pyproject.toml +25 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/target
|
|
2
|
+
*.swp
|
|
3
|
+
*.swo
|
|
4
|
+
*~
|
|
5
|
+
.DS_Store
|
|
6
|
+
report.md
|
|
7
|
+
.claude/settings.local.json
|
|
8
|
+
smoke/node_modules/
|
|
9
|
+
sdk/node/node_modules/
|
|
10
|
+
sdk/node/dist/
|
|
11
|
+
sdk/python/*.egg-info/
|
|
12
|
+
sdk/python/dist/
|
|
13
|
+
sdk/python/build/
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.pyc
|
|
16
|
+
|
|
17
|
+
# MCP registry publisher tokens
|
|
18
|
+
.mcpregistry_*
|
|
19
|
+
.vercel
|
|
20
|
+
packages/*/node_modules/
|
|
21
|
+
packages/*/.venv/
|
|
22
|
+
packages/*/dist/
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plasmate-browser-use
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Plasmate SOM integration for Browser Use - 10x fewer tokens for AI web agents
|
|
5
|
+
Project-URL: Homepage, https://plasmate.app
|
|
6
|
+
Project-URL: Documentation, https://plasmate.app/docs/integration-browser-use
|
|
7
|
+
Project-URL: Repository, https://github.com/plasmate-labs/plasmate
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: som-parser>=0.3.0
|
|
11
|
+
Provides-Extra: browser-use
|
|
12
|
+
Requires-Dist: browser-use>=0.12.0; extra == 'browser-use'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# plasmate-browser-use
|
|
16
|
+
|
|
17
|
+
SOM-based content extraction for [Browser Use](https://github.com/browser-use/browser-use). Drop-in alternative to Browser Use's default DOM serializer that uses Plasmate's Semantic Object Model (SOM) to reduce token costs by 10x or more.
|
|
18
|
+
|
|
19
|
+
Instead of sending the full DOM tree to your LLM, Plasmate compresses web pages into a compact semantic representation. Same information, 90% fewer tokens, lower costs, faster responses.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install plasmate-browser-use
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
You need the `plasmate` binary installed:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Via cargo
|
|
33
|
+
cargo install plasmate
|
|
34
|
+
|
|
35
|
+
# Or via install script
|
|
36
|
+
curl -fsSL https://plasmate.app/install.sh | sh
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Verify it works:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
plasmate --version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Basic extraction
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
51
|
+
|
|
52
|
+
extractor = PlasmateExtractor()
|
|
53
|
+
|
|
54
|
+
# Get raw SOM data as a dict
|
|
55
|
+
som = extractor.extract("https://news.ycombinator.com")
|
|
56
|
+
print(f"Elements: {som['meta']['element_count']}")
|
|
57
|
+
print(f"Compression: {som['meta']['html_bytes'] / som['meta']['som_bytes']:.1f}x")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Get page context for an LLM
|
|
61
|
+
|
|
62
|
+
The `get_page_context()` method returns a formatted string optimized for LLM consumption, with interactive elements, links, content, and compression stats:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
context = extractor.get_page_context("https://example.com")
|
|
66
|
+
print(context)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Output:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
# Example Domain
|
|
73
|
+
URL: https://example.com
|
|
74
|
+
Language: en
|
|
75
|
+
|
|
76
|
+
## Interactive Elements (1)
|
|
77
|
+
[e1] link "More information..." (click)
|
|
78
|
+
|
|
79
|
+
## Content
|
|
80
|
+
This domain is for use in illustrative examples in documents...
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
Compression: 15.2x (1256 HTML bytes -> 83 SOM bytes)
|
|
84
|
+
Elements: 5 (1 interactive)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Markdown extraction
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
md = extractor.extract_markdown("https://example.com")
|
|
91
|
+
print(md)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Async support
|
|
95
|
+
|
|
96
|
+
All methods have async variants:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import asyncio
|
|
100
|
+
|
|
101
|
+
async def main():
|
|
102
|
+
extractor = PlasmateExtractor()
|
|
103
|
+
context = await extractor.get_page_context_async("https://example.com")
|
|
104
|
+
som = await extractor.extract_async("https://example.com")
|
|
105
|
+
md = await extractor.extract_markdown_async("https://example.com")
|
|
106
|
+
|
|
107
|
+
asyncio.run(main())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Using with a Browser Use agent
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from browser_use import Agent
|
|
114
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
115
|
+
|
|
116
|
+
extractor = PlasmateExtractor()
|
|
117
|
+
|
|
118
|
+
# Get compact page context instead of full DOM
|
|
119
|
+
context = extractor.get_page_context("https://example.com/products")
|
|
120
|
+
|
|
121
|
+
# Feed to your Browser Use agent with 10x fewer tokens
|
|
122
|
+
agent = Agent(task="Find the cheapest product", page_context=context)
|
|
123
|
+
result = await agent.run()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Token savings comparison
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from plasmate_browser_use import PlasmateExtractor, token_count_comparison
|
|
130
|
+
|
|
131
|
+
extractor = PlasmateExtractor()
|
|
132
|
+
som = extractor.extract("https://news.ycombinator.com")
|
|
133
|
+
stats = token_count_comparison(som)
|
|
134
|
+
|
|
135
|
+
print(f"HTML tokens: ~{stats['html_tokens_est']:,}")
|
|
136
|
+
print(f"SOM tokens: ~{stats['som_tokens_est']:,}")
|
|
137
|
+
print(f"Savings: {stats['token_savings_pct']}%")
|
|
138
|
+
print(f"Ratio: {stats['token_ratio']}x fewer tokens")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Typical token savings
|
|
142
|
+
|
|
143
|
+
| Site | HTML tokens | SOM tokens | Reduction |
|
|
144
|
+
|------|------------|------------|-----------|
|
|
145
|
+
| Hacker News | ~22,000 | ~1,200 | 18x |
|
|
146
|
+
| Wikipedia article | ~85,000 | ~8,500 | 10x |
|
|
147
|
+
| Amazon product page | ~120,000 | ~6,000 | 20x |
|
|
148
|
+
| Google search results | ~45,000 | ~3,500 | 13x |
|
|
149
|
+
|
|
150
|
+
Numbers vary by page. The more complex the page (ads, trackers, layout noise), the bigger the savings.
|
|
151
|
+
|
|
152
|
+
## How it works
|
|
153
|
+
|
|
154
|
+
1. Plasmate fetches the page and parses the HTML
|
|
155
|
+
2. The DOM is compiled into a Semantic Object Model (SOM) that preserves meaning while stripping layout noise
|
|
156
|
+
3. The SOM is serialized into a compact format with tagged interactive elements
|
|
157
|
+
4. Your LLM agent sees the same page information in 10x fewer tokens
|
|
158
|
+
|
|
159
|
+
## Links
|
|
160
|
+
|
|
161
|
+
- [Plasmate](https://plasmate.app) -- the SOM engine
|
|
162
|
+
- [SOM Spec](https://plasmate.app/docs/som-spec) -- Semantic Object Model specification
|
|
163
|
+
- [Browser Use](https://github.com/browser-use/browser-use) -- AI agent browser framework
|
|
164
|
+
- [Token cost analysis](https://plasmate.app/docs/cost-analysis) -- detailed benchmarks
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
Apache-2.0
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# plasmate-browser-use
|
|
2
|
+
|
|
3
|
+
SOM-based content extraction for [Browser Use](https://github.com/browser-use/browser-use). Drop-in alternative to Browser Use's default DOM serializer that uses Plasmate's Semantic Object Model (SOM) to reduce token costs by 10x or more.
|
|
4
|
+
|
|
5
|
+
Instead of sending the full DOM tree to your LLM, Plasmate compresses web pages into a compact semantic representation. Same information, 90% fewer tokens, lower costs, faster responses.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install plasmate-browser-use
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
You need the `plasmate` binary installed:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Via cargo
|
|
19
|
+
cargo install plasmate
|
|
20
|
+
|
|
21
|
+
# Or via install script
|
|
22
|
+
curl -fsSL https://plasmate.app/install.sh | sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Verify it works:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
plasmate --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Basic extraction
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
37
|
+
|
|
38
|
+
extractor = PlasmateExtractor()
|
|
39
|
+
|
|
40
|
+
# Get raw SOM data as a dict
|
|
41
|
+
som = extractor.extract("https://news.ycombinator.com")
|
|
42
|
+
print(f"Elements: {som['meta']['element_count']}")
|
|
43
|
+
print(f"Compression: {som['meta']['html_bytes'] / som['meta']['som_bytes']:.1f}x")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Get page context for an LLM
|
|
47
|
+
|
|
48
|
+
The `get_page_context()` method returns a formatted string optimized for LLM consumption, with interactive elements, links, content, and compression stats:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
context = extractor.get_page_context("https://example.com")
|
|
52
|
+
print(context)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Output:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
# Example Domain
|
|
59
|
+
URL: https://example.com
|
|
60
|
+
Language: en
|
|
61
|
+
|
|
62
|
+
## Interactive Elements (1)
|
|
63
|
+
[e1] link "More information..." (click)
|
|
64
|
+
|
|
65
|
+
## Content
|
|
66
|
+
This domain is for use in illustrative examples in documents...
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
Compression: 15.2x (1256 HTML bytes -> 83 SOM bytes)
|
|
70
|
+
Elements: 5 (1 interactive)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Markdown extraction
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
md = extractor.extract_markdown("https://example.com")
|
|
77
|
+
print(md)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Async support
|
|
81
|
+
|
|
82
|
+
All methods have async variants:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import asyncio
|
|
86
|
+
|
|
87
|
+
async def main():
|
|
88
|
+
extractor = PlasmateExtractor()
|
|
89
|
+
context = await extractor.get_page_context_async("https://example.com")
|
|
90
|
+
som = await extractor.extract_async("https://example.com")
|
|
91
|
+
md = await extractor.extract_markdown_async("https://example.com")
|
|
92
|
+
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Using with a Browser Use agent
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from browser_use import Agent
|
|
100
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
101
|
+
|
|
102
|
+
extractor = PlasmateExtractor()
|
|
103
|
+
|
|
104
|
+
# Get compact page context instead of full DOM
|
|
105
|
+
context = extractor.get_page_context("https://example.com/products")
|
|
106
|
+
|
|
107
|
+
# Feed to your Browser Use agent with 10x fewer tokens
|
|
108
|
+
agent = Agent(task="Find the cheapest product", page_context=context)
|
|
109
|
+
result = await agent.run()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Token savings comparison
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from plasmate_browser_use import PlasmateExtractor, token_count_comparison
|
|
116
|
+
|
|
117
|
+
extractor = PlasmateExtractor()
|
|
118
|
+
som = extractor.extract("https://news.ycombinator.com")
|
|
119
|
+
stats = token_count_comparison(som)
|
|
120
|
+
|
|
121
|
+
print(f"HTML tokens: ~{stats['html_tokens_est']:,}")
|
|
122
|
+
print(f"SOM tokens: ~{stats['som_tokens_est']:,}")
|
|
123
|
+
print(f"Savings: {stats['token_savings_pct']}%")
|
|
124
|
+
print(f"Ratio: {stats['token_ratio']}x fewer tokens")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Typical token savings
|
|
128
|
+
|
|
129
|
+
| Site | HTML tokens | SOM tokens | Reduction |
|
|
130
|
+
|------|------------|------------|-----------|
|
|
131
|
+
| Hacker News | ~22,000 | ~1,200 | 18x |
|
|
132
|
+
| Wikipedia article | ~85,000 | ~8,500 | 10x |
|
|
133
|
+
| Amazon product page | ~120,000 | ~6,000 | 20x |
|
|
134
|
+
| Google search results | ~45,000 | ~3,500 | 13x |
|
|
135
|
+
|
|
136
|
+
Numbers vary by page. The more complex the page (ads, trackers, layout noise), the bigger the savings.
|
|
137
|
+
|
|
138
|
+
## How it works
|
|
139
|
+
|
|
140
|
+
1. Plasmate fetches the page and parses the HTML
|
|
141
|
+
2. The DOM is compiled into a Semantic Object Model (SOM) that preserves meaning while stripping layout noise
|
|
142
|
+
3. The SOM is serialized into a compact format with tagged interactive elements
|
|
143
|
+
4. Your LLM agent sees the same page information in 10x fewer tokens
|
|
144
|
+
|
|
145
|
+
## Links
|
|
146
|
+
|
|
147
|
+
- [Plasmate](https://plasmate.app) -- the SOM engine
|
|
148
|
+
- [SOM Spec](https://plasmate.app/docs/som-spec) -- Semantic Object Model specification
|
|
149
|
+
- [Browser Use](https://github.com/browser-use/browser-use) -- AI agent browser framework
|
|
150
|
+
- [Token cost analysis](https://plasmate.app/docs/cost-analysis) -- detailed benchmarks
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
Apache-2.0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Example: Using Plasmate SOM with Browser Use for cheaper AI browsing."""
|
|
2
|
+
import asyncio
|
|
3
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
extractor = PlasmateExtractor()
|
|
8
|
+
|
|
9
|
+
# Get SOM-formatted page context (10x fewer tokens than raw HTML)
|
|
10
|
+
context = extractor.get_page_context("https://news.ycombinator.com")
|
|
11
|
+
print(context)
|
|
12
|
+
|
|
13
|
+
# Or get structured SOM data
|
|
14
|
+
som = extractor.extract("https://news.ycombinator.com")
|
|
15
|
+
print(f"Compression: {som['meta']['html_bytes'] / som['meta']['som_bytes']:.1f}x")
|
|
16
|
+
print(f"Elements: {som['meta']['element_count']}")
|
|
17
|
+
|
|
18
|
+
# Or just markdown
|
|
19
|
+
md = extractor.extract_markdown("https://news.ycombinator.com")
|
|
20
|
+
print(md[:500])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plasmate SOM integration for Browser Use.
|
|
3
|
+
|
|
4
|
+
Provides SOM-based content extraction that can replace or complement
|
|
5
|
+
Browser Use's default DOM serialization, reducing token costs by 90%+.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from plasmate_browser_use import PlasmateExtractor
|
|
10
|
+
|
|
11
|
+
extractor = PlasmateExtractor()
|
|
12
|
+
context = extractor.get_page_context("https://example.com")
|
|
13
|
+
print(context)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .extractor import PlasmateExtractor
|
|
17
|
+
from .utils import token_count_comparison, estimate_tokens
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"PlasmateExtractor",
|
|
21
|
+
"token_count_comparison",
|
|
22
|
+
"estimate_tokens",
|
|
23
|
+
]
|
|
24
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Plasmate SOM extractor for Browser Use.
|
|
2
|
+
|
|
3
|
+
Provides SOM-based content extraction that can replace or complement
|
|
4
|
+
Browser Use's default DOM serialization, reducing token costs by 90%+.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from som_parser import parse_som, get_links, get_interactive_elements, get_text, to_markdown
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlasmateExtractor:
|
|
16
|
+
"""Extract web page content using Plasmate's SOM format.
|
|
17
|
+
|
|
18
|
+
Use this alongside Browser Use to get structured, token-efficient
|
|
19
|
+
page representations instead of raw DOM serialization.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, plasmate_bin: str = "plasmate"):
|
|
23
|
+
self.plasmate_bin = plasmate_bin
|
|
24
|
+
self._verify_binary()
|
|
25
|
+
|
|
26
|
+
def _verify_binary(self):
|
|
27
|
+
"""Check that plasmate binary is available."""
|
|
28
|
+
try:
|
|
29
|
+
result = subprocess.run(
|
|
30
|
+
[self.plasmate_bin, "--version"],
|
|
31
|
+
capture_output=True, text=True, timeout=5
|
|
32
|
+
)
|
|
33
|
+
if result.returncode != 0:
|
|
34
|
+
raise RuntimeError(f"plasmate binary not working: {result.stderr}")
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
raise RuntimeError(
|
|
37
|
+
"plasmate binary not found. Install with: cargo install plasmate\n"
|
|
38
|
+
"Or: curl -fsSL https://plasmate.app/install.sh | sh"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def extract(self, url: str) -> dict:
|
|
42
|
+
"""Fetch a URL and return parsed SOM output.
|
|
43
|
+
|
|
44
|
+
Returns the full SOM dict with regions, elements, meta, etc.
|
|
45
|
+
"""
|
|
46
|
+
result = subprocess.run(
|
|
47
|
+
[self.plasmate_bin, "fetch", url],
|
|
48
|
+
capture_output=True, text=True, timeout=30
|
|
49
|
+
)
|
|
50
|
+
if result.returncode != 0:
|
|
51
|
+
raise RuntimeError(f"plasmate fetch failed: {result.stderr}")
|
|
52
|
+
return json.loads(result.stdout)
|
|
53
|
+
|
|
54
|
+
async def extract_async(self, url: str) -> dict:
|
|
55
|
+
"""Async version of extract."""
|
|
56
|
+
proc = await asyncio.create_subprocess_exec(
|
|
57
|
+
self.plasmate_bin, "fetch", url,
|
|
58
|
+
stdout=asyncio.subprocess.PIPE,
|
|
59
|
+
stderr=asyncio.subprocess.PIPE
|
|
60
|
+
)
|
|
61
|
+
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
|
|
62
|
+
if proc.returncode != 0:
|
|
63
|
+
raise RuntimeError(f"plasmate fetch failed: {stderr.decode()}")
|
|
64
|
+
return json.loads(stdout.decode())
|
|
65
|
+
|
|
66
|
+
def extract_markdown(self, url: str) -> str:
|
|
67
|
+
"""Fetch a URL and return SOM content as markdown.
|
|
68
|
+
|
|
69
|
+
This is the simplest integration -- just get readable text
|
|
70
|
+
with structure preserved.
|
|
71
|
+
"""
|
|
72
|
+
som_data = self.extract(url)
|
|
73
|
+
som = parse_som(som_data)
|
|
74
|
+
return to_markdown(som)
|
|
75
|
+
|
|
76
|
+
async def extract_markdown_async(self, url: str) -> str:
|
|
77
|
+
"""Async version of extract_markdown."""
|
|
78
|
+
som_data = await self.extract_async(url)
|
|
79
|
+
som = parse_som(som_data)
|
|
80
|
+
return to_markdown(som)
|
|
81
|
+
|
|
82
|
+
def get_page_context(self, url: str) -> str:
|
|
83
|
+
"""Get a token-efficient page context string for LLM consumption.
|
|
84
|
+
|
|
85
|
+
Returns a formatted string with:
|
|
86
|
+
- Page title and URL
|
|
87
|
+
- Interactive elements (what the agent can do)
|
|
88
|
+
- Content summary
|
|
89
|
+
- Compression stats
|
|
90
|
+
"""
|
|
91
|
+
som_data = self.extract(url)
|
|
92
|
+
return self._build_context(som_data)
|
|
93
|
+
|
|
94
|
+
async def get_page_context_async(self, url: str) -> str:
|
|
95
|
+
"""Async version of get_page_context."""
|
|
96
|
+
som_data = await self.extract_async(url)
|
|
97
|
+
return self._build_context(som_data)
|
|
98
|
+
|
|
99
|
+
def _build_context(self, som_data: dict) -> str:
|
|
100
|
+
"""Build the LLM context string from raw SOM data."""
|
|
101
|
+
som = parse_som(som_data)
|
|
102
|
+
|
|
103
|
+
lines = []
|
|
104
|
+
lines.append(f"# {som.title}")
|
|
105
|
+
lines.append(f"URL: {som.url}")
|
|
106
|
+
lines.append(f"Language: {som.lang}")
|
|
107
|
+
lines.append("")
|
|
108
|
+
|
|
109
|
+
# Interactive elements
|
|
110
|
+
interactive = get_interactive_elements(som)
|
|
111
|
+
if interactive:
|
|
112
|
+
lines.append(f"## Interactive Elements ({len(interactive)})")
|
|
113
|
+
for el in interactive:
|
|
114
|
+
actions = ", ".join(el.actions) if el.actions else ""
|
|
115
|
+
label = el.text or el.label or (el.attrs.get("placeholder", "") if el.attrs else "")
|
|
116
|
+
lines.append(f' [{el.id}] {el.role} "{label}" ({actions})')
|
|
117
|
+
lines.append("")
|
|
118
|
+
|
|
119
|
+
# Links
|
|
120
|
+
links = get_links(som)
|
|
121
|
+
if links:
|
|
122
|
+
lines.append(f"## Links ({len(links)})")
|
|
123
|
+
for link in links[:20]: # Cap at 20
|
|
124
|
+
lines.append(f" [{link['id']}] {link['text']} -> {link['href']}")
|
|
125
|
+
if len(links) > 20:
|
|
126
|
+
lines.append(f" ... and {len(links) - 20} more")
|
|
127
|
+
lines.append("")
|
|
128
|
+
|
|
129
|
+
# Content
|
|
130
|
+
text = get_text(som)
|
|
131
|
+
if text:
|
|
132
|
+
lines.append("## Content")
|
|
133
|
+
lines.append(text[:2000]) # Cap content
|
|
134
|
+
if len(text) > 2000:
|
|
135
|
+
lines.append(f"... ({len(text) - 2000} more characters)")
|
|
136
|
+
|
|
137
|
+
# Stats
|
|
138
|
+
meta = som.meta
|
|
139
|
+
ratio = meta.html_bytes / max(meta.som_bytes, 1)
|
|
140
|
+
lines.append("")
|
|
141
|
+
lines.append("---")
|
|
142
|
+
lines.append(f"Compression: {ratio:.1f}x ({meta.html_bytes} HTML bytes -> {meta.som_bytes} SOM bytes)")
|
|
143
|
+
lines.append(f"Elements: {meta.element_count} ({meta.interactive_count} interactive)")
|
|
144
|
+
|
|
145
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Helper functions for the Plasmate Browser Use integration."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def estimate_tokens(text: str) -> int:
|
|
7
|
+
"""Estimate token count for a string (roughly 4 chars per token)."""
|
|
8
|
+
return len(text.encode("utf-8")) // 4
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def token_count_comparison(
|
|
12
|
+
som_data: dict[str, Any],
|
|
13
|
+
som_text: Optional[str] = None,
|
|
14
|
+
) -> dict[str, Any]:
|
|
15
|
+
"""Compare token counts between raw HTML and SOM representation.
|
|
16
|
+
|
|
17
|
+
Useful for benchmarking and demonstrating the token savings Plasmate
|
|
18
|
+
provides over traditional browser backends.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
som_data: A SOM document dict (as returned by Plasmate).
|
|
22
|
+
som_text: Pre-rendered SOM text. If not provided, token estimate
|
|
23
|
+
is based on the SOM bytes from metadata.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict with token counts and savings ratio::
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
"html_bytes": 87234,
|
|
30
|
+
"som_bytes": 4521,
|
|
31
|
+
"html_tokens_est": 21808,
|
|
32
|
+
"som_tokens_est": 1130,
|
|
33
|
+
"byte_ratio": 19.3,
|
|
34
|
+
"token_ratio": 19.3,
|
|
35
|
+
"token_savings_pct": 94.8,
|
|
36
|
+
}
|
|
37
|
+
"""
|
|
38
|
+
meta = som_data.get("meta", {})
|
|
39
|
+
html_bytes = meta.get("html_bytes", 0)
|
|
40
|
+
som_bytes = meta.get("som_bytes", 0)
|
|
41
|
+
|
|
42
|
+
if som_text is not None:
|
|
43
|
+
som_text_bytes = len(som_text.encode("utf-8"))
|
|
44
|
+
else:
|
|
45
|
+
som_text_bytes = som_bytes
|
|
46
|
+
|
|
47
|
+
# Token estimation: ~4 chars per token (standard heuristic)
|
|
48
|
+
html_tokens = html_bytes // 4
|
|
49
|
+
som_tokens = som_text_bytes // 4
|
|
50
|
+
|
|
51
|
+
byte_ratio = html_bytes / som_bytes if som_bytes else 0
|
|
52
|
+
token_ratio = html_tokens / som_tokens if som_tokens else 0
|
|
53
|
+
savings_pct = (1 - som_tokens / html_tokens) * 100 if html_tokens else 0
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
"html_bytes": html_bytes,
|
|
57
|
+
"som_bytes": som_bytes,
|
|
58
|
+
"som_text_bytes": som_text_bytes,
|
|
59
|
+
"html_tokens_est": html_tokens,
|
|
60
|
+
"som_tokens_est": som_tokens,
|
|
61
|
+
"byte_ratio": round(byte_ratio, 1),
|
|
62
|
+
"token_ratio": round(token_ratio, 1),
|
|
63
|
+
"token_savings_pct": round(savings_pct, 1),
|
|
64
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plasmate-browser-use"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Plasmate SOM integration for Browser Use - 10x fewer tokens for AI web agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"som-parser>=0.3.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
browser-use = ["browser-use>=0.12.0"]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://plasmate.app"
|
|
21
|
+
Documentation = "https://plasmate.app/docs/integration-browser-use"
|
|
22
|
+
Repository = "https://github.com/plasmate-labs/plasmate"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["plasmate_browser_use"]
|