vision-agent 0.2.93__py3-none-any.whl → 0.2.95__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -373,7 +373,7 @@ def write_and_test_code(
373
373
  "code": DefaultImports.prepend_imports(code),
374
374
  "payload": {
375
375
  "test": test,
376
- "result": result.to_json(),
376
+ # "result": result.to_json(),
377
377
  },
378
378
  }
379
379
  )
@@ -426,7 +426,8 @@ def debug_code(
426
426
  ) -> tuple[str, str, Execution]:
427
427
  log_progress(
428
428
  {
429
- "type": "code",
429
+ "type": "log",
430
+ "log_content": ("Debugging code"),
430
431
  "status": "started",
431
432
  }
432
433
  )
@@ -469,10 +470,11 @@ def debug_code(
469
470
  )
470
471
  log_progress(
471
472
  {
472
- "type": "code",
473
+ "type": "log",
474
+ "log_content": ("Running code"),
473
475
  "status": "running",
476
+ "code": DefaultImports.prepend_imports(code),
474
477
  "payload": {
475
- "code": DefaultImports.prepend_imports(code),
476
478
  "test": test,
477
479
  },
478
480
  }
@@ -483,12 +485,15 @@ def debug_code(
483
485
  )
484
486
  log_progress(
485
487
  {
486
- "type": "code",
488
+ "type": "log",
489
+ "log_content": (
490
+ "Code execution succeed" if result.success else "Code execution failed"
491
+ ),
487
492
  "status": "completed" if result.success else "failed",
493
+ "code": DefaultImports.prepend_imports(code),
488
494
  "payload": {
489
- "code": DefaultImports.prepend_imports(code),
490
495
  "test": test,
491
- "result": result.to_json(),
496
+ # "result": result.to_json(),
492
497
  },
493
498
  }
494
499
  )
@@ -525,7 +530,8 @@ def retrieve_tools(
525
530
  ) -> Dict[str, str]:
526
531
  log_progress(
527
532
  {
528
- "type": "tools",
533
+ "type": "log",
534
+ "log_content": ("Retrieving tools for each plan"),
529
535
  "status": "started",
530
536
  }
531
537
  )
@@ -802,17 +808,6 @@ class VisionAgentCoder(Agent):
802
808
  plan.append({"code": code, "test": test, "plan": plan_i})
803
809
 
804
810
  execution_result = cast(Execution, results["test_result"])
805
- self.log_progress(
806
- {
807
- "type": "final_code",
808
- "status": "completed" if success else "failed",
809
- "payload": {
810
- "code": DefaultImports.prepend_imports(code),
811
- "test": test,
812
- "result": execution_result.to_json(),
813
- },
814
- }
815
- )
816
811
 
817
812
  if display_visualization:
818
813
  for res in execution_result.results:
@@ -822,6 +817,7 @@ class VisionAgentCoder(Agent):
822
817
  play_video(res.mp4)
823
818
 
824
819
  return {
820
+ "status": "completed" if success else "failed",
825
821
  "code": DefaultImports.prepend_imports(code),
826
822
  "test": test,
827
823
  "test_result": execution_result,
@@ -9,7 +9,6 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast
9
9
  import cv2
10
10
  import numpy as np
11
11
  import requests
12
- from moviepy.editor import ImageSequenceClip
13
12
  from PIL import Image, ImageDraw, ImageFont
14
13
  from pillow_heif import register_heif_opener # type: ignore
15
14
  from pytube import YouTube # type: ignore
@@ -1044,15 +1043,21 @@ def save_video(
1044
1043
  if fps <= 0:
1045
1044
  _LOGGER.warning(f"Invalid fps value: {fps}. Setting fps to 4 (default value).")
1046
1045
  fps = 4
1047
- with ImageSequenceClip(frames, fps=fps) as video:
1048
- if output_video_path:
1049
- f = open(output_video_path, "wb")
1050
- else:
1051
- f = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) # type: ignore
1052
- video.write_videofile(f.name, codec="libx264")
1053
- f.close()
1054
- _save_video_to_result(f.name)
1055
- return f.name
1046
+
1047
+ if not output_video_path:
1048
+ output_video_path = tempfile.NamedTemporaryFile(
1049
+ suffix=".mp4", delete=False
1050
+ ).name
1051
+
1052
+ height, width, layers = frames[0].shape if frames else (0, 0, 0)
1053
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v") # type: ignore
1054
+ video = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
1055
+ for frame in frames:
1056
+ video.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
1057
+ video.release()
1058
+
1059
+ _save_video_to_result(output_video_path)
1060
+ return output_video_path
1056
1061
 
1057
1062
 
1058
1063
  def _save_video_to_result(video_uri: str) -> None:
@@ -1,6 +1,5 @@
1
1
  import abc
2
2
  import base64
3
- import copy
4
3
  import logging
5
4
  import os
6
5
  import platform
@@ -17,9 +16,14 @@ from typing import Any, Dict, Iterable, List, Optional, Union
17
16
  import nbformat
18
17
  import tenacity
19
18
  from dotenv import load_dotenv
19
+ from e2b.exceptions import SandboxException
20
20
  from e2b_code_interpreter import CodeInterpreter as E2BCodeInterpreterImpl
21
21
  from e2b_code_interpreter import Execution as E2BExecution
22
22
  from e2b_code_interpreter import Result as E2BResult
23
+ from h11._util import LocalProtocolError
24
+ from httpx import ConnectError
25
+ from httpx import RemoteProtocolError as HttpcoreRemoteProtocolError
26
+ from httpx import RemoteProtocolError as HttpxRemoteProtocolError
23
27
  from nbclient import NotebookClient
24
28
  from nbclient import __version__ as nbclient_version
25
29
  from nbclient.exceptions import CellTimeoutError, DeadKernelError
@@ -29,7 +33,6 @@ from pydantic import BaseModel, field_serializer
29
33
  from typing_extensions import Self
30
34
 
31
35
  from vision_agent.utils.exceptions import (
32
- RemoteSandboxClosedError,
33
36
  RemoteSandboxCreationError,
34
37
  RemoteSandboxExecutionError,
35
38
  )
@@ -106,13 +109,8 @@ class Result:
106
109
  is_main_result: bool
107
110
  "Whether this data is the result of the cell. Data can be produced by display calls of which can be multiple in a cell."
108
111
 
109
- raw: Dict[str, str]
110
- "Dictionary that maps MIME types to their corresponding string representations of the data."
111
-
112
112
  def __init__(self, is_main_result: bool, data: Dict[str, Any]):
113
113
  self.is_main_result = is_main_result
114
- self.raw = copy.deepcopy(data)
115
-
116
114
  self.text = data.pop(MimeType.TEXT_PLAIN, None)
117
115
  if self.text and (self.text.startswith("'") and self.text.endswith("'")):
118
116
  # This is a workaround for the issue that str result is wrapped with single quotes by notebook.
@@ -136,13 +134,13 @@ class Result:
136
134
 
137
135
  # Allows to iterate over formats()
138
136
  def __getitem__(self, key: Any) -> Any:
139
- return self.raw[key] if key in self.raw else getattr(self, key)
137
+ return getattr(self, key)
140
138
 
141
139
  def __str__(self) -> str:
142
140
  return repr(self)
143
141
 
144
142
  def __repr__(self) -> str:
145
- return str(self.raw)
143
+ return str(self.text)
146
144
 
147
145
  def _repr_html_(self) -> Optional[str]:
148
146
  """Returns the HTML representation of the data."""
@@ -215,9 +213,16 @@ class Result:
215
213
  """
216
214
  Creates a Result object from an E2BResult object.
217
215
  """
216
+ data = {
217
+ MimeType.TEXT_PLAIN.value: result.text,
218
+ MimeType.IMAGE_PNG.value: result.png,
219
+ MimeType.APPLICATION_JSON.value: result.json,
220
+ }
221
+ for k, v in result.extra.items():
222
+ data[k] = v
218
223
  return Result(
219
224
  is_main_result=result.is_main_result,
220
- data=result.raw,
225
+ data=data,
221
226
  )
222
227
 
223
228
 
@@ -367,7 +372,7 @@ class Execution(BaseModel):
367
372
  value=_remove_escape_and_color_codes(exec.error.value),
368
373
  traceback_raw=[
369
374
  _remove_escape_and_color_codes(line)
370
- for line in exec.error.traceback_raw
375
+ for line in exec.error.traceback.split("\n")
371
376
  ],
372
377
  )
373
378
  if exec.error
@@ -436,11 +441,12 @@ va_version = importlib.metadata.version("vision-agent")
436
441
  print(f"Vision Agent version: {va_version}")"""
437
442
  )
438
443
  sys_versions = "\n".join(result.logs.stdout)
439
- _LOGGER.info(f"E2BCodeInterpreter initialized:\n{sys_versions}")
444
+ _LOGGER.info(
445
+ f"E2BCodeInterpreter (sandbox id: {self.interpreter.sandbox_id}) initialized:\n{sys_versions}"
446
+ )
440
447
 
441
448
  def close(self, *args: Any, **kwargs: Any) -> None:
442
449
  try:
443
- self.interpreter.notebook.close()
444
450
  self.interpreter.kill(request_timeout=2)
445
451
  _LOGGER.info(
446
452
  f"The sandbox {self.interpreter.sandbox_id} is closed successfully."
@@ -451,28 +457,67 @@ print(f"Vision Agent version: {va_version}")"""
451
457
  )
452
458
 
453
459
  def restart_kernel(self) -> None:
454
- self._check_sandbox_liveness()
455
460
  self.interpreter.notebook.restart_kernel()
456
461
 
457
462
  @tenacity.retry(
458
463
  wait=tenacity.wait_exponential_jitter(),
459
- stop=tenacity.stop_after_attempt(2),
460
- # TODO: change TimeoutError to a more specific exception when e2b team provides more granular retryable exceptions
461
- retry=tenacity.retry_if_exception_type(TimeoutError),
464
+ stop=tenacity.stop_after_attempt(3),
465
+ retry=tenacity.retry_if_exception_type(
466
+ (
467
+ LocalProtocolError,
468
+ HttpxRemoteProtocolError,
469
+ HttpcoreRemoteProtocolError,
470
+ ConnectError,
471
+ SandboxException,
472
+ )
473
+ ),
474
+ before_sleep=tenacity.before_sleep_log(_LOGGER, logging.INFO),
475
+ after=tenacity.after_log(_LOGGER, logging.INFO),
462
476
  )
463
477
  def exec_cell(self, code: str) -> Execution:
464
- self._check_sandbox_liveness()
465
478
  self.interpreter.set_timeout(_SESSION_TIMEOUT) # Extend the life of the sandbox
466
479
  try:
467
- execution = self.interpreter.notebook.exec_cell(code, timeout=self.timeout)
480
+ _LOGGER.info(
481
+ f"Start code execution in remote sandbox {self.interpreter.sandbox_id}. Timeout: {_SESSION_TIMEOUT}. Code hash: {hash(code)}"
482
+ )
483
+ execution = self.interpreter.notebook.exec_cell(
484
+ code=code,
485
+ on_stdout=lambda msg: _LOGGER.info(msg),
486
+ on_stderr=lambda msg: _LOGGER.info(msg),
487
+ )
488
+ _LOGGER.info(
489
+ f"Finished code execution in remote sandbox {self.interpreter.sandbox_id}. Code hash: {hash(code)}"
490
+ )
468
491
  return Execution.from_e2b_execution(execution)
492
+ except (
493
+ LocalProtocolError,
494
+ HttpxRemoteProtocolError,
495
+ HttpcoreRemoteProtocolError,
496
+ ConnectError,
497
+ SandboxException,
498
+ ) as e:
499
+ raise e
469
500
  except Exception as e:
470
501
  raise RemoteSandboxExecutionError(
471
- f"Failed executing code in remote sandbox due to {e}: {code}"
502
+ f"Failed executing code in remote sandbox ({self.interpreter.sandbox_id}) due to error '{type(e).__name__} {str(e)}', code: {code}"
472
503
  ) from e
473
504
 
505
+ @tenacity.retry(
506
+ wait=tenacity.wait_exponential_jitter(),
507
+ stop=tenacity.stop_after_attempt(3),
508
+ retry=tenacity.retry_if_exception_type(
509
+ (
510
+ LocalProtocolError,
511
+ HttpxRemoteProtocolError,
512
+ HttpcoreRemoteProtocolError,
513
+ ConnectError,
514
+ SandboxException,
515
+ )
516
+ ),
517
+ before_sleep=tenacity.before_sleep_log(_LOGGER, logging.INFO),
518
+ after=tenacity.after_log(_LOGGER, logging.INFO),
519
+ )
474
520
  def upload_file(self, file: Union[str, Path]) -> str:
475
- self._check_sandbox_liveness()
476
521
  file_name = Path(file).name
477
522
  remote_path = f"/home/user/{file_name}"
478
523
  with open(file, "rb") as f:
@@ -481,28 +526,18 @@ print(f"Vision Agent version: {va_version}")"""
481
526
  return remote_path
482
527
 
483
528
  def download_file(self, file_path: str) -> Path:
484
- self._check_sandbox_liveness()
485
529
  with tempfile.NamedTemporaryFile(mode="w+b", delete=False) as file:
486
530
  file.write(self.interpreter.files.read(path=file_path, format="bytes"))
487
531
  _LOGGER.info(f"File ({file_path}) is downloaded to: {file.name}")
488
532
  return Path(file.name)
489
533
 
490
- def _check_sandbox_liveness(self) -> None:
491
- try:
492
- alive = self.interpreter.is_running(request_timeout=2)
493
- except Exception as e:
494
- _LOGGER.error(
495
- f"Failed to check the health of the remote sandbox ({self.interpreter.sandbox_id}) due to {e}. Consider the sandbox as dead."
496
- )
497
- alive = False
498
- if not alive:
499
- raise RemoteSandboxClosedError(
500
- "Remote sandbox is closed unexpectedly. Please start a new VisionAgent instance."
501
- )
502
-
503
534
  @staticmethod
504
535
  def _new_e2b_interpreter_impl(*args, **kwargs) -> E2BCodeInterpreterImpl: # type: ignore
505
- return E2BCodeInterpreterImpl(template="va-sandbox", *args, **kwargs)
536
+ template_name = os.environ.get("E2B_TEMPLATE_NAME", "nx3fagq7sgdliww9cvm3")
537
+ _LOGGER.info(
538
+ f"Creating a new E2BCodeInterpreter using template: {template_name}"
539
+ )
540
+ return E2BCodeInterpreterImpl(template=template_name, *args, **kwargs)
506
541
 
507
542
 
508
543
  class LocalCodeInterpreter(CodeInterpreter):
vision_agent/utils/sim.py CHANGED
@@ -9,6 +9,7 @@ from openai import AzureOpenAI, Client, OpenAI
9
9
  from scipy.spatial.distance import cosine # type: ignore
10
10
 
11
11
 
12
+ @lru_cache(maxsize=512)
12
13
  def get_embedding(
13
14
  client: Client, text: str, model: str = "text-embedding-3-small"
14
15
  ) -> List[float]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vision-agent
3
- Version: 0.2.93
3
+ Version: 0.2.95
4
4
  Summary: Toolset for Vision Agent
5
5
  Author: Landing AI
6
6
  Author-email: dev@landing.ai
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Requires-Dist: anthropic (>=0.31.0,<0.32.0)
13
13
  Requires-Dist: e2b (>=0.17.1,<0.18.0)
14
- Requires-Dist: e2b-code-interpreter (==0.0.11a2)
14
+ Requires-Dist: e2b-code-interpreter (==0.0.11a17)
15
15
  Requires-Dist: ipykernel (>=6.29.4,<7.0.0)
16
16
  Requires-Dist: langsmith (>=0.1.58,<0.2.0)
17
17
  Requires-Dist: moviepy (>=1.0.0,<2.0.0)
@@ -23,6 +23,7 @@ Requires-Dist: opencv-python (>=4.0.0,<5.0.0)
23
23
  Requires-Dist: pandas (>=2.0.0,<3.0.0)
24
24
  Requires-Dist: pillow (>=10.0.0,<11.0.0)
25
25
  Requires-Dist: pillow-heif (>=0.16.0,<0.17.0)
26
+ Requires-Dist: pydantic (==2.7.4)
26
27
  Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
27
28
  Requires-Dist: pytube (==15.0.0)
28
29
  Requires-Dist: requests (>=2.0.0,<3.0.0)
@@ -3,7 +3,7 @@ vision_agent/agent/__init__.py,sha256=qpduQ9YufJQfMmG6jwKC2xmlbtR2qK8_1eQC1sGA9K
3
3
  vision_agent/agent/agent.py,sha256=Bt8yhjCFXuRdZaHxKEesG40V09nWRt45sZluri1R3AA,575
4
4
  vision_agent/agent/agent_utils.py,sha256=JXdl2xz14LKQAmScY-MIW23AD2WBFCsnI0JS6dAyj3Q,1412
5
5
  vision_agent/agent/vision_agent.py,sha256=i_rNpc7faqHTifp2c9sQE4Js3qYUKuJeiqauTp90OlE,8417
6
- vision_agent/agent/vision_agent_coder.py,sha256=Ouq3ws7w5zq8fw550aBrCjerNUrQ0MNvR45dkqTtReE,30321
6
+ vision_agent/agent/vision_agent_coder.py,sha256=M8J5xE9uX8Nig1WmVmwLMeCSe0E6Bg3Mo5fPPcW_a-c,30246
7
7
  vision_agent/agent/vision_agent_coder_prompts.py,sha256=a3R_vHlT2FW3-DSn4OWgzF9zEAx-uKM4ZaTi9Kn-K54,11116
8
8
  vision_agent/agent/vision_agent_prompts.py,sha256=hjs-m4ZHR7HE1HtOeX_1rOvTQA2FMEAqEkaBbGPBYDo,6072
9
9
  vision_agent/fonts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -15,15 +15,15 @@ vision_agent/tools/__init__.py,sha256=UNiaJAOt1C709gaJ-a9h9BzKnY5JmoEUpgKftsOnyP
15
15
  vision_agent/tools/meta_tools.py,sha256=rmxgVzj-vJKeewHbue3qHru4sYsFLxlSZV-YH-eyH5w,13366
16
16
  vision_agent/tools/prompts.py,sha256=V1z4YJLXZuUl_iZ5rY0M5hHc_2tmMEUKr0WocXKGt4E,1430
17
17
  vision_agent/tools/tool_utils.py,sha256=XoB-iae8hHrBQgJd3fV6-UjZAkClysobUaOM17IcHuE,4597
18
- vision_agent/tools/tools.py,sha256=HT8stRTUmhwm2VbpB3QQNXnL1KIxka-BDCkU2tLaFN4,42326
18
+ vision_agent/tools/tools.py,sha256=CWQY1sD-xtWchPrg_AJNAGH-k7UxrKIkiog8r0sx1Do,42446
19
19
  vision_agent/utils/__init__.py,sha256=CW84HnhqI6XQVuxf2KifkLnSuO7EOhmuL09-gAymAak,219
20
20
  vision_agent/utils/exceptions.py,sha256=isVH-SVL4vHj3q5kK4z7cy5_aOapAqHXWkpibfSNbUs,1659
21
- vision_agent/utils/execute.py,sha256=JPjiXyAHsMSPYFDZ9bmiiX2lgiGroxqDhXhsD8BTv5E,23848
21
+ vision_agent/utils/execute.py,sha256=s43aUtuq7ZNjil2mxrddiz8EvvqlJwttkYlIiZouXqM,25125
22
22
  vision_agent/utils/image_utils.py,sha256=y69wtNla0xHZ1h1x0-vv7nOyKUq69jtjSJBiDCn6EM0,7703
23
- vision_agent/utils/sim.py,sha256=1HTaiVaBiKeyXIy21IYGXlPw0TipOyw9FPOJDfyLI94,4409
23
+ vision_agent/utils/sim.py,sha256=7JvtWGN0Ik5ife3qQYWs7Fm3T8AnAXGFd5HnvDC15mQ,4433
24
24
  vision_agent/utils/type_defs.py,sha256=oVFJcicB-s_09lqvn61u0A5ncZsTqZArZledXWbrrg0,1384
25
25
  vision_agent/utils/video.py,sha256=rNmU9KEIkZB5-EztZNlUiKYN0mm_55A_2VGUM0QpqLA,8779
26
- vision_agent-0.2.93.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
27
- vision_agent-0.2.93.dist-info/METADATA,sha256=wwrgBe5pY0y09470Yb_BrrpBZMbSMDivcZ5wC8Rt4VE,10693
28
- vision_agent-0.2.93.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
29
- vision_agent-0.2.93.dist-info/RECORD,,
26
+ vision_agent-0.2.95.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
27
+ vision_agent-0.2.95.dist-info/METADATA,sha256=-OCOFe_UAKyI5sjDr6nYklJq5jwKZbLjwFkFMO-wrV8,10728
28
+ vision_agent-0.2.95.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
29
+ vision_agent-0.2.95.dist-info/RECORD,,