Skip to content

Commit 8b8e29d

Browse files
committed
Update browser to allow recording session videos
1 parent 2884eeb commit 8b8e29d

File tree

4 files changed

+85
-14
lines changed

4 files changed

+85
-14
lines changed

llmstack/processors/providers/promptly/static_web_browser.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class StaticWebBrowserConfiguration(ApiProcessorSchema):
8383
description="Capture screenshot",
8484
default=True,
8585
)
86+
capture_session_video: bool = Field(
87+
description="Capture session video",
88+
default=False,
89+
)
8690

8791

8892
class StaticWebBrowserInput(ApiProcessorSchema):
@@ -138,6 +142,10 @@ class StaticWebBrowserOutput(ApiProcessorSchema):
138142
default=[],
139143
description="Steps taken to complete the task",
140144
)
145+
videos: Optional[List[StaticWebBrowserFile]] = Field(
146+
default=None,
147+
description="Videos from the browser session. Make sure to TERMINATE the browser to capture the video",
148+
)
141149

142150

143151
class StaticWebBrowser(
@@ -167,8 +175,20 @@ def provider_slug() -> str:
167175
def get_output_template(cls) -> Optional[OutputTemplate]:
168176
return OutputTemplate(
169177
markdown="""
178+
{% if session %}
179+
### Live Browser Session
170180
<promptly-web-browser-embed wsUrl="{{session.ws_url}}"></promptly-web-browser-embed>
181+
{% endif %}
182+
{% if videos %}
183+
### Videos
184+
{% for video in videos %}
185+
<pa-asset url="{{video.data}}" type="{{video.mime_type}}"></pa-asset>
186+
{% endfor %}
187+
{% endif %}
188+
{% if content.screenshot %}
189+
### Screenshot
171190
<pa-asset url="{{content.screenshot}}" type="image/png"></pa-asset>
191+
{% endif %}
172192
{{text}}
173193
""",
174194
jsonpath="$.text",
@@ -204,7 +224,7 @@ def _web_browser_instruction_to_command(self, instruction: BrowserInstruction) -
204224
def process(self) -> dict:
205225
output_stream = self._output_stream
206226
browser_response = None
207-
227+
session_videos = []
208228
with WebBrowser(
209229
f"{settings.RUNNER_HOST}:{settings.RUNNER_PORT}",
210230
interactive=self._config.stream_video,
@@ -216,6 +236,7 @@ def process(self) -> dict:
216236
if self._config.connection_id
217237
else ""
218238
),
239+
record_video=self._config.capture_session_video,
219240
) as web_browser:
220241
if self._config.stream_video and web_browser.get_wss_url():
221242
async_to_sync(
@@ -237,6 +258,10 @@ def process(self) -> dict:
237258
]
238259
+ list(map(self._web_browser_instruction_to_command, self._input.instructions))
239260
)
261+
262+
if self._config.capture_session_video:
263+
session_videos = web_browser.get_videos()
264+
240265
if browser_response:
241266
output_text = browser_response.text or "\n".join(
242267
list(map(lambda entry: entry.output, browser_response.command_outputs))
@@ -273,6 +298,21 @@ def process(self) -> dict:
273298
browser_content.downloads = swb_downloads
274299
async_to_sync(self._output_stream.write)(StaticWebBrowserOutput(text=output_text, content=browser_content))
275300

301+
if session_videos:
302+
encoded_videos = []
303+
for video in session_videos:
304+
encoded_videos.append(
305+
StaticWebBrowserFile(
306+
name=video.name,
307+
data=self._upload_asset_from_url(
308+
f"data:{video.mime_type};name={video.name};base64,{base64.b64encode(video.data).decode('utf-8')}",
309+
mime_type=video.mime_type,
310+
).objref,
311+
mime_type=video.mime_type,
312+
)
313+
)
314+
async_to_sync(self._output_stream.write)(StaticWebBrowserOutput(videos=encoded_videos))
315+
276316
output = output_stream.finalize()
277317

278318
return output

llmstack/processors/providers/promptly/web_browser.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ class WebBrowserConfiguration(ApiProcessorSchema):
264264
description="Stream video of the browser",
265265
default=True,
266266
)
267-
stream_text: bool = Field(
268-
description="Stream output text from the browser",
267+
record_session_video: bool = Field(
268+
description="Record session video",
269269
default=False,
270270
)
271271
timeout: int = Field(
@@ -289,9 +289,7 @@ class WebBrowserConfiguration(ApiProcessorSchema):
289289
default=None,
290290
description="Seed to use for random number generator",
291291
)
292-
tags_to_extract: List[str] = Field(
293-
description="Tags to extract", default=["a", "button", "input", "textarea", "select"]
294-
)
292+
tags_to_extract: List[str] = Field(description="Tags to extract", default=[])
295293

296294

297295
class WebBrowserOutput(ApiProcessorSchema):
@@ -312,6 +310,10 @@ class WebBrowserOutput(ApiProcessorSchema):
312310
default=[],
313311
description="Steps taken to complete the task",
314312
)
313+
videos: List[StaticWebBrowserFile] = Field(
314+
default=[],
315+
description="Videos from the session",
316+
)
315317

316318

317319
class WebBrowserInput(ApiProcessorSchema):
@@ -351,8 +353,18 @@ def provider_slug() -> str:
351353
@classmethod
352354
def get_output_template(cls) -> Optional[OutputTemplate]:
353355
return OutputTemplate(
354-
markdown="""<promptly-web-browser-embed wsUrl="{{session.ws_url}}"></promptly-web-browser-embed>
355-
{{text}}""",
356+
markdown="""
357+
{% if session %}
358+
### Live Browser Session
359+
<promptly-web-browser-embed wsUrl="{{session.ws_url}}"></promptly-web-browser-embed>
360+
{% endif %}
361+
{{text}}
362+
{% if videos %}
363+
### Session Videos
364+
{% for video in videos %}
365+
<pa-asset url="{{video.data}}" type="{{video.mime_type}}"></pa-asset>
366+
{% endfor %}
367+
{% endif %}""",
356368
jsonpath="$.text",
357369
)
358370

@@ -428,6 +440,8 @@ def _process_anthropic(self) -> dict:
428440
commands_executed = 0
429441
browser_response = None
430442
browser_downloads = []
443+
session_videos = []
444+
431445
with WebBrowserClient(
432446
f"{settings.RUNNER_HOST}:{settings.RUNNER_PORT}",
433447
interactive=self._config.stream_video,
@@ -439,6 +453,7 @@ def _process_anthropic(self) -> dict:
439453
if self._config.connection_id
440454
else ""
441455
),
456+
record_video=self._config.record_session_video,
442457
) as web_browser:
443458
browser_response = web_browser.run_commands(
444459
commands=[
@@ -583,6 +598,8 @@ def _process_anthropic(self) -> dict:
583598
WebBrowserOutput(text=choice.message.content[0].get("text", "")),
584599
)
585600
break
601+
if self._config.record_session_video:
602+
session_videos = web_browser.get_videos()
586603

587604
if browser_response:
588605
output_text = "\n".join(list(map(lambda entry: entry.output, browser_response.command_outputs)))
@@ -616,6 +633,21 @@ def _process_anthropic(self) -> dict:
616633
browser_content.downloads = swb_downloads
617634
async_to_sync(self._output_stream.write)(WebBrowserOutput(text=output_text, content=browser_content))
618635

636+
if session_videos:
637+
encoded_videos = []
638+
for video in session_videos:
639+
encoded_videos.append(
640+
StaticWebBrowserFile(
641+
name=video.name,
642+
data=self._upload_asset_from_url(
643+
f"data:{video.mime_type};name={video.name};base64,{base64.b64encode(video.data).decode('utf-8')}",
644+
mime_type=video.mime_type,
645+
).objref,
646+
mime_type=video.mime_type,
647+
)
648+
)
649+
async_to_sync(self._output_stream.write)(WebBrowserOutput(videos=encoded_videos))
650+
619651
output = self._output_stream.finalize()
620652
return output
621653

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ python-on-whales = "^0.71.0"
2222
channels-redis = "^4.2.0"
2323
jsonpath-ng = "^1.6.1"
2424
cryptography = "^43.0.1"
25-
langrocks = "^0.1.5"
2625
grpcio = "1.65.1"
2726

2827
[tool.poetry.extras]
@@ -69,7 +68,7 @@ google-auth = {version = "^2.22.0" }
6968
pydantic = "^2.7.4"
7069
daphne = "^4.1.2"
7170
striprtf = "^0.0.26"
72-
langrocks = "0.1.9"
71+
langrocks = "0.2.0"
7372
diff-match-patch = "^20230430"
7473
rq = "^2.0.0"
7574
django-rq = "^3.0.0"

0 commit comments

Comments
 (0)