Skip to content

Commit 7b5bdeb

Browse files
Den 450 move llm logic to sdk (#91)
* started refactor * wip add local cache * working local logic for get_element * format with black * update sync sdk generation * Refactor element retrieval logic to utilize a unified Config class for managing configurations across async and sync APIs. Updated get_element and related functions to support cached selectors and improved error handling. Removed deprecated code and streamlined selector handling in both async and sync contexts. Added new CachedSelectorDTO for better cache management. * Add CachedExtractDTO and update extract logic for caching scripts - Introduced CachedExtractDTO for managing cached script data. - Updated get_script function to accept URL instead of domain. - Modified extract logic to include caching behavior for scripts. - Removed unused cache flags from ExtractDTO and adjusted related logic. * refactor extract agent * add test implemenation of setup_auth * add local storagestate storage * refactor to flatten project structure * update to test multiple cached selectors and scripts * add logger configuration and remove unused cache utility functions * remove old stuff in cli * Update README.md --------- Co-authored-by: Arian Hanifi <[email protected]>
1 parent 27c9dab commit 7b5bdeb

File tree

219 files changed

+8662
-3567
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

219 files changed

+8662
-3567
lines changed

README.md

+177-75
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44
<a href="https://discord.gg/ETPBdXU3kx"><img src="https://img.shields.io/badge/Discord-Join%20Us-7289DA?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"></a>
55
</p>
66

7-
<div align="center">
8-
<h2>🎉We are going open source🎉</h1>
9-
<p>
10-
Let us know if you're interested in contributing! We're working on integrating the core logic for getting elements and extraction into the sdk!
11-
</p>
12-
</div>
7+
> **Notice:** The Dendrite SDK is not under active development anymore. However, the project will remain fully open source so that you and others can learn from it. Feel free to fork, study, or adapt this code for your own projects as you wish – reach out to us on Discord if you have questions! We love chatting about web AI agents. 🤖
138
149
## What is Dendrite?
1510

@@ -24,33 +19,60 @@
2419

2520
#### A simple outlook integration
2621

27-
With Dendrite, it's easy to create web interaction tools for your agent.
22+
With Dendrite it's easy to create web interaction tools for your agent.
23+
24+
Here's how you can send an email:
2825

2926
```python
30-
from dendrite import Dendrite
27+
from dendrite import AsyncDendrite
28+
3129

32-
def send_email():
33-
client = Dendrite(auth="outlook.live.com")
30+
async def send_email(to, subject, message):
31+
client = AsyncDendrite(auth="outlook.live.com")
3432

3533
# Navigate
36-
client.goto(
37-
"https://outlook.live.com/mail/0/",
38-
expected_page="An email inbox"
34+
await client.goto(
35+
"https://outlook.live.com/mail/0/", expected_page="An email inbox"
3936
)
4037

4138
# Create new email and populate fields
42-
client.click("The new email button")
43-
client.fill_fields({
44-
"Recipient": to,
45-
"Subject": subject,
46-
"Message": message
47-
})
39+
await client.click("The new email button")
40+
await client.fill("The recipient field", to)
41+
await client.press("Enter")
42+
await client.fill("The subject field", subject)
43+
await client.fill("The message field", message)
4844

4945
# Send email
50-
client.click("The send button")
46+
await client.press("Enter", hold_cmd=True)
47+
48+
49+
if __name__ == "__main__":
50+
import asyncio
51+
52+
asyncio.run(send_email("[email protected]", "Hello", "This is a test email"))
53+
5154
```
5255

53-
To authenticate you'll need to use our Chrome Extension **Dendrite Vault**, you can download it [here](https://chromewebstore.google.com/detail/dendrite-vault/faflkoombjlhkgieldilpijjnblgabnn). Read more about authentication [in our docs](https://docs.dendrite.systems/examples/authentication-instagram).
56+
You'll need to add your own Anthropic key or [configure which LLMs to use yourself](https://docs.dendrite.systems/concepts/config).
57+
58+
59+
```.env
60+
ANTHROPIC_API_KEY=sk-...
61+
```
62+
63+
To **authenticate** on any web service with Dendrite, follow these steps:
64+
65+
1. Run the authentication command
66+
67+
```bash
68+
dendrite auth --url outlook.live.com
69+
```
70+
71+
2. This command will open a browser that you'll be able to login with.
72+
73+
3. After you've logged in, press enter in your terminal. This will save your cookies locally so that they can be used in your code.
74+
75+
Read more about authentication [in our docs](https://docs.dendrite.systems/examples/authentication).
5476

5577
## Quickstart
5678

@@ -60,81 +82,159 @@ pip install dendrite && dendrite install
6082

6183
#### Simple navigation and interaction
6284

63-
Initialize the Dendrite client and start doing web interactions without boilerplate.
64-
65-
[Get your API key here](https://dendrite.systems/app)
66-
6785
```python
68-
from dendrite import Dendrite
86+
from dendrite import AsyncDendrite
87+
88+
async def main():
89+
client = AsyncDendrite()
6990

70-
client = Dendrite(dendrite_api_key="sk...")
91+
await client.goto("https://google.com")
92+
await client.fill("Search field", "Hello world")
93+
await client.press("Enter")
7194

72-
client.goto("https://google.com")
73-
client.fill("Search field", "Hello world")
74-
client.press("Enter")
95+
if __name__ == "__main__":
96+
import asyncio
97+
asyncio.run(main())
7598
```
7699

77100
In the example above, we simply go to Google, populate the search field with "Hello world" and simulate a keypress on Enter. It's a simple example that starts to explore the endless possibilities with Dendrite. Now you can create tools for your agents that have access to the full web without depending on APIs.
78101

79-
## More powerful examples
102+
## More Examples
80103

81-
Now, let's have some fun. Earlier we showed you a simple send_email example. And sending emails is cool, but if that's all our agent can do it kind of sucks. So let's create two cooler examples.
104+
### Get any page as markdown
82105

83-
### Download Bank Transactions
106+
This is a simple example of how to get any page as markdown, great for feeding to an LLM.
107+
108+
```python
109+
from dendrite import AsyncDendrite
110+
from dotenv import load_dotenv
111+
112+
async def main():
113+
browser = AsyncDendrite()
114+
115+
await browser.goto("https://dendrite.systems")
116+
await browser.wait_for("the page to load")
117+
118+
# Get the entire page as markdown
119+
md = await browser.markdown()
120+
print(md)
121+
print("=" * 200)
122+
123+
# Only get a certain part of the page as markdown
124+
data_extraction_md = await browser.markdown("the part about data extraction")
125+
print(data_extraction_md)
126+
127+
if __name__ == "__main__":
128+
import asyncio
129+
asyncio.run(main())
130+
```
84131

85-
First up, a tool that allows our AI agent to download our bank's monthly transactions so that they can be analyzed and compiled into a report that can be sent to stakeholders with `send_email`.
132+
### Get Company Data from Y Combinator
133+
134+
The classic web data extraction test, made easy:
86135

87136
```python
88-
from dendrite import Dendrite
137+
from dendrite import AsyncDendrite
138+
import pprint
139+
import asyncio
89140

90-
def get_transactions() -> str:
91-
client = Dendrite(auth="mercury.com")
92141

93-
# Navigate and wait for loading
94-
client.goto(
95-
"https://app.mercury.com/transactions",
96-
expected_page="Dashboard with transactions"
97-
)
98-
client.wait_for("The transactions to finish loading")
142+
async def main():
143+
browser = AsyncDendrite()
99144

100-
# Modify filters
101-
client.click("The 'add filter' button")
102-
client.click("The 'show transactions for' dropdown")
103-
client.click("The 'this month' option")
145+
# Navigate
146+
await browser.goto("https://www.ycombinator.com/companies")
147+
148+
# Find and fill the search field with "AI agent"
149+
await browser.fill(
150+
"Search field", value="AI agent"
151+
) # Element selector cached since before
152+
await browser.press("Enter")
153+
154+
# Extract startups with natural language description
155+
# Once created by our agent, the same script will be cached and reused
156+
startups = await browser.extract(
157+
"All companies. Return a list of dicts with name, location, description and url"
158+
)
159+
pprint.pprint(startups, indent=2)
104160

105-
# Download file
106-
client.click("The 'export filtered' button")
107-
transactions = client.get_download()
108161

109-
# Save file locally
110-
path = "files/transactions.xlsx"
111-
transactions.save_as(path)
162+
if __name__ == "__main__":
163+
asyncio.run(main())
112164

113-
return path
165+
```
114166

115-
def analyze_transactions(path: str):
116-
...
167+
returns
117168
```
169+
[ { 'description': 'Book accommodations around the world.',
170+
'location': 'San Francisco, CA, USA',
171+
'name': 'Airbnb',
172+
'url': 'https://www.ycombinator.com/companies/airbnb'},
173+
{ 'description': 'Digital Analytics Platform',
174+
'location': 'San Francisco, CA, USA',
175+
'name': 'Amplitude',
176+
'url': 'https://www.ycombinator.com/companies/amplitude'},
177+
...
178+
] }
179+
```
180+
118181

119-
### Extract Google Analytics
182+
### Extract Data from Google Analytics
120183

121-
Finally, it would be cool if we could add the amount of monthly visitors from Google Analytics to our report. We can do that by using the `extract` function:
184+
Here's how to get the amount of monthly visitors from Google Analytics using the `extract` function:
122185

123186
```python
124-
def get_visitor_count() -> int:
125-
client = Dendrite(auth="analytics.google.com")
187+
async def get_visitor_count() -> int:
188+
client = AsyncDendrite(auth="analytics.google.com")
126189

127-
client.goto(
190+
await client.goto(
128191
"https://analytics.google.com/analytics/web",
129192
expected_page="Google Analytics dashboard"
130193
)
131194

132195
# The Dendrite extract agent will create a web script that is cached
133196
# and reused. It will self-heal when the website updates
134-
visitor_count = client.extract("The amount of visitors this month", int)
197+
visitor_count = await client.extract("The amount of visitors this month", int)
135198
return visitor_count
136199
```
137200

201+
### Download Bank Transactions
202+
203+
Here's tool that allows our AI agent to download our bank's monthly transactions so that they can be analyzed and compiled into a report.
204+
205+
```python
206+
from dendrite import AsyncDendrite
207+
208+
async def get_transactions() -> str:
209+
client = AsyncDendrite(auth="mercury.com")
210+
211+
# Navigate and wait for loading
212+
await client.goto(
213+
"https://app.mercury.com/transactions",
214+
expected_page="Dashboard with transactions"
215+
)
216+
await client.wait_for("The transactions to finish loading")
217+
218+
# Modify filters
219+
await client.click("The 'add filter' button")
220+
await client.click("The 'show transactions for' dropdown")
221+
await client.click("The 'this month' option")
222+
223+
# Download file
224+
await client.click("The 'export filtered' button")
225+
transactions = await client.get_download()
226+
227+
# Save file locally
228+
path = "files/transactions.xlsx"
229+
await transactions.save_as(path)
230+
231+
return path
232+
233+
async def analyze_transactions(path: str):
234+
... # Analyze the transactions with LLM of our choice
235+
```
236+
237+
138238
## Documentation
139239

140240
[Read the full docs here](https://docs.dendrite.systems)
@@ -145,7 +245,7 @@ def get_visitor_count() -> int:
145245

146246
When you want to scale up your AI agents, we support using browsers hosted by Browserbase. This way you can run many agents in parallel without having to worry about the infrastructure.
147247

148-
To start using Browserbase just swap out the `Dendrite` class with `DendriteRemoteBrowser` and add your Browserbase API key and project id, either in the code or in a `.env` file like this:
248+
To start using Browserbase just swap out the `AsyncDendrite` class with `AsyncDendriteRemoteBrowser` and add your Browserbase API key and project id, either in the code or in a `.env` file like this:
149249

150250
```bash
151251
# ... previous keys
@@ -154,17 +254,19 @@ BROWSERBASE_PROJECT_ID=
154254
```
155255

156256
```python
157-
# from dendrite import Dendrite
158-
from dendrite import DendriteRemoteBrowser
159-
160-
...
161-
162-
# client = Dendrite(...)
163-
client = DendriteRemoteBrowser(
164-
# Use interchangeably with the Dendrite class
165-
browserbase_api_key="...", # or specify the browsebase keys in the .env file
166-
browserbase_project_id="..."
167-
)
257+
# from dendrite import AsyncDendrite
258+
from dendrite import AsyncDendriteRemoteBrowser
259+
260+
async def main():
261+
# client = AsyncDendrite(...)
262+
client = AsyncDendriteRemoteBrowser(
263+
# Use interchangeably with the AsyncDendrite class
264+
browserbase_api_key="...", # or specify the browsebase keys in the .env file
265+
browserbase_project_id="..."
266+
)
267+
...
168268

169-
...
269+
if __name__ == "__main__":
270+
import asyncio
271+
asyncio.run(main())
170272
```

dendrite/__init__.py

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,22 @@
11
import sys
2-
from loguru import logger
3-
from dendrite.async_api import (
4-
AsyncDendrite,
5-
AsyncElement,
6-
AsyncPage,
7-
AsyncElementsResponse,
8-
)
92

10-
from dendrite.sync_api import (
3+
from dendrite._loggers.d_logger import logger
4+
from dendrite.browser.async_api import AsyncDendrite, AsyncElement, AsyncPage
5+
from dendrite.logic.config import Config
6+
7+
from dendrite.browser.sync_api import (
118
Dendrite,
129
Element,
1310
Page,
14-
ElementsResponse,
1511
)
1612

17-
logger.remove()
18-
19-
fmt = "<green>{time: HH:mm:ss.SSS}</green> | <level>{level: <8}</level>- <level>{message}</level>"
20-
21-
logger.add(sys.stderr, level="INFO", format=fmt)
22-
2313

2414
__all__ = [
2515
"AsyncDendrite",
2616
"AsyncElement",
2717
"AsyncPage",
28-
"AsyncElementsResponse",
2918
"Dendrite",
3019
"Element",
3120
"Page",
32-
"ElementsResponse",
21+
"Config",
3322
]
File renamed without changes.

0 commit comments

Comments
 (0)