Skip to content

Commit 473eb78

Browse files
committed
optional s3
1 parent b92cac9 commit 473eb78

File tree

4 files changed

+65
-50
lines changed

4 files changed

+65
-50
lines changed

lib/ai.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from browser_use.llm import BaseChatModel
1414
import orjson
1515

16-
__all__ = ["ModelProvider", "ChatFactory", "AGENT_INSTRUCTIONS"]
16+
__all__ = ["ModelProvider", "ChatFactory"]
1717

1818

1919
ModelProvider = t.Literal[
@@ -33,12 +33,3 @@ def config(provider: ModelProvider) -> dict:
3333
"groq": partial(ChatGroq, **config("groq")),
3434
"ollama": partial(ChatOllama, **config("ollama")),
3535
}
36-
37-
38-
AGENT_INSTRUCTIONS = """Remember, you are an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Decompose the user's query into all required sub-requests, and confirm that each is completed. Do not stop after completing only part of the request. Only terminate your turn when you are sure that the problem is solved. You must be prepared to answer multiple queries and only finish the call once the user has confirmed they're done.
39-
40-
You must plan extensively in accordance with the workflow steps before making subsequent function calls, and reflect extensively on the outcomes each function call made, ensuring the user's query, and related sub-requests are completely resolved.
41-
42-
Note that your browser will automatically:
43-
1. Download the PDF file upon viewing it. Just wait for it. You do not need to read the PDF.
44-
2. Solve CAPTCHAs or similar tests. Just wait for it."""

lib/browser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
DOWNLOADS_PATH = anyio.Path(getenv("DOWNLOADS_PATH", "/tmp/downloads"))
1919

2020

21-
async def create_browser(ctx: KernelContext, request: BrowserAgentRequest):
21+
async def create(ctx: KernelContext, request: BrowserAgentRequest):
2222
invocation_id = ctx.invocation_id
2323
headless = request.headless
2424

@@ -49,4 +49,4 @@ async def create_browser(ctx: KernelContext, request: BrowserAgentRequest):
4949
),
5050
)
5151

52-
return session, browser
52+
return session, browser, DOWNLOADS_PATH

lib/storage.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,35 @@
33
from os import environ as env
44
from pathlib import Path
55

6-
from botocore.client import Config
76
import anyio
8-
import boto3
97
import orjson
108

119
from lib.asyncio import asyncify
1210

13-
BUCKET = env.get("S3_BUCKET", "browser-agent")
14-
PRESIGNED_URL_EXPIRES_IN = 24 * 60 * 60 # 12 hours
11+
BUCKET = env.get("S3_BUCKET")
12+
ENABLED = bool(BUCKET)
13+
PRESIGNED_URL_EXPIRES_IN = int(env.get("PRESIGNED_URL_EXPIRES_IN", 24 * 60 * 60))
1514

16-
client = boto3.client(
17-
service_name="s3",
18-
endpoint_url=env["S3_ENDPOINT_URL"],
19-
aws_access_key_id=env["S3_ACCESS_KEY_ID"],
20-
aws_secret_access_key=env["S3_SECRET_ACCESS_KEY"],
21-
region_name="auto",
22-
config=Config(signature_version="s3v4"),
23-
)
15+
client = None
16+
if ENABLED:
17+
import boto3
18+
from botocore.client import Config
19+
20+
client = boto3.client(
21+
service_name="s3",
22+
endpoint_url=env["S3_ENDPOINT_URL"],
23+
aws_access_key_id=env["S3_ACCESS_KEY_ID"],
24+
aws_secret_access_key=env["S3_SECRET_ACCESS_KEY"],
25+
region_name="auto",
26+
config=Config(signature_version="s3v4"),
27+
)
2428

2529

2630
@asyncify
2731
def upload_file(file: anyio.Path | Path | str, key: str) -> str:
32+
if not client:
33+
raise ValueError("S3_BUCKET is not set")
34+
2835
client.upload_file(
2936
Bucket=BUCKET,
3037
Filename=str(file),
@@ -39,6 +46,9 @@ def upload_file(file: anyio.Path | Path | str, key: str) -> str:
3946

4047
@asyncify
4148
def upload_json(data: t.Any, key: str) -> str:
49+
if not client:
50+
raise ValueError("S3_BUCKET is not set")
51+
4252
client.put_object(
4353
Bucket=BUCKET,
4454
Key=key,
@@ -55,10 +65,14 @@ async def upload_files(
5565
dir: str,
5666
files: t.AsyncIterator[anyio.Path | Path | str],
5767
) -> dict[str, str]:
58-
files = [Path(f) async for f in files]
59-
names = [f.name for f in files]
68+
paths = [Path(f) async for f in files]
69+
names = [f.name for f in paths]
70+
71+
if not client:
72+
return {n: str(f) for n, f in zip(names, paths)}
73+
6074
object_keys = [f"{dir}/{n}" for n in names]
6175
presigned_urls = await asyncio.gather(
62-
*[upload_file(f, k) for f, k in zip(files, object_keys)]
76+
*[upload_file(f, k) for f, k in zip(paths, object_keys)]
6377
)
6478
return dict(zip(names, presigned_urls))

main.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
from kernel import App, KernelContext
66
from zenbase_llml import llml
77

8-
from lib.ai import AGENT_INSTRUCTIONS, ChatFactory
9-
from lib.browser import DOWNLOADS_PATH, create_browser
10-
from lib.models import BrowserAgentRequest, BrowserAgentResponse
11-
from lib.storage import upload_files, upload_json
8+
from lib import storage, ai, browser, models
129

1310
logger = logging.getLogger(__name__)
1411

@@ -17,40 +14,53 @@
1714

1815
@app.action("perform")
1916
async def perform(ctx: KernelContext, params: dict):
20-
request = BrowserAgentRequest.model_validate(params)
17+
request = models.BrowserAgentRequest.model_validate(params)
2118

22-
llm = ChatFactory[request.provider](
19+
llm = ai.ChatFactory[request.provider](
2320
api_key=request.api_key,
2421
model=request.model,
2522
)
2623

27-
session, browser = await create_browser(ctx, request)
24+
instructions = f"""
25+
You are an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Decompose the user's query into all required sub-requests, and confirm that each is completed. Do not stop after completing only part of the request. Only terminate your turn when you are sure that the problem is solved. You must be prepared to answer multiple queries and only finish the call once the user has confirmed they're done.
2826
29-
prompt = {
30-
"instructions": "\n\n".join(
31-
filter(bool, [request.instructions, AGENT_INSTRUCTIONS])
32-
),
33-
"input": request.input,
34-
}
27+
You must plan extensively in accordance with the workflow steps before making subsequent function calls, and reflect extensively on the outcomes each function call made, ensuring the user's query, and related sub-requests are completely resolved.
3528
29+
{request.instructions}
30+
31+
Remember, you are an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Decompose the user's query into all required sub-requests, and confirm that each is completed. Do not stop after completing only part of the request. Only terminate your turn when you are sure that the problem is solved. You must be prepared to answer multiple queries and only finish the call once the user has confirmed they're done.
32+
33+
You must plan extensively in accordance with the workflow steps before making subsequent function calls, and reflect extensively on the outcomes each function call made, ensuring the user's query, and related sub-requests are completely resolved.
34+
35+
Note that your browser will automatically:
36+
1. Download the PDF file upon viewing it. Just wait for it. You do not need to read the PDF.
37+
2. Solve CAPTCHAs or similar tests. Just wait for it.
38+
"""
39+
40+
session_id, browser_session, downloads_path = await browser.create(ctx, request)
3641
agent = Agent(
37-
task=llml(prompt),
38-
browser=browser,
42+
task=llml({"instructions": instructions, "input": request.input}),
43+
browser=browser_session,
3944
llm=llm,
4045
use_thinking=request.reasoning,
4146
flash_mode=request.flash,
4247
)
43-
4448
trajectory = await agent.run(max_steps=request.max_steps)
4549

46-
uploads = await asyncio.gather(
47-
upload_files(dir=session, files=DOWNLOADS_PATH.glob("*")),
48-
upload_json(trajectory.model_dump(), key=f"{session}/trajectory.json"),
49-
)
50+
if not storage.ENABLED:
51+
downloads = {p.name: str(p) async for p in downloads_path.glob("*")}
52+
else:
53+
(downloads,) = await asyncio.gather(
54+
storage.upload_files(dir=session_id, paths=downloads_path.glob("*")),
55+
storage.upload_json(
56+
trajectory.model_dump(),
57+
key=f"{session_id}/trajectory.json",
58+
),
59+
)
5060

51-
response = BrowserAgentResponse.from_run(
61+
response = models.BrowserAgentResponse.from_run(
5262
trajectory,
53-
session=session,
54-
downloads=uploads[0],
63+
session=session_id,
64+
downloads=downloads,
5565
)
5666
return response.model_dump()

0 commit comments

Comments
 (0)