Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ huggingface_hub
duckduckgo_search
requests
ddgs
PyMuPDF
PyMuPDF
docker
117 changes: 95 additions & 22 deletions seimei/agents/code_act.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

import asyncio
import os
import re
import subprocess
import uuid
import docker
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple

Expand Down Expand Up @@ -215,30 +218,100 @@ def _run_command(
python_heredoc: Optional[_PythonHeredoc],
cwd: Optional[str],
) -> subprocess.CompletedProcess[str]:
if python_heredoc:
args: List[str] = [python_heredoc.executable]
if python_heredoc.has_dash:
args.append("-")
script_input = python_heredoc.script
if script_input and not script_input.endswith("\n"):
script_input += "\n"
return subprocess.run(
args,
input=script_input,
capture_output=True,
text=True,
timeout=timeout,
cwd=cwd,
try:
client = docker.from_env()
except Exception as e:
return subprocess.CompletedProcess(
args=code,
returncode=1,
stdout="",
stderr=f"Docker is not available or not running: {e}"
)
return subprocess.run(
code,
shell=True,
capture_output=True,
text=True,
timeout=timeout,
cwd=cwd,
)

host_workspace = os.path.abspath(cwd) if cwd and os.path.exists(cwd) else None
volumes = {}
working_dir = None

if host_workspace:
volumes[host_workspace] = {'bind': '/workspace', 'mode': 'rw'}
working_dir = '/workspace'

temp_script_name = None
if python_heredoc and host_workspace:
temp_script_name = f".tmp_agent_{uuid.uuid4().hex}.py"
host_script_path = os.path.join(host_workspace, temp_script_name)

with open(host_script_path, "w", encoding="utf-8") as f:
f.write(python_heredoc.script)

container_cmd = [python_heredoc.executable, f"/workspace/{temp_script_name}"]
else:
container_cmd = ["/bin/sh", "-c", code]

image_name = "python:3.10-slim"
container = None

try:
try:
container = client.containers.run(
image_name,
command=container_cmd,
volumes=volumes,
working_dir=working_dir,
network_mode="none",
mem_limit="256m",
nano_cpus=1000000000,
detach=True,
)
except docker.errors.ImageNotFound:
client.images.pull(image_name)
container = client.containers.run(
image_name,
command=container_cmd,
volumes=volumes,
working_dir=working_dir,
network_mode="none",
mem_limit="256m",
nano_cpus=1000000000,
detach=True,
)

result = container.wait(timeout=timeout)
returncode = result.get("StatusCode", 0)

stdout = container.logs(stdout=True, stderr=False).decode("utf-8", errors="replace")
stderr = container.logs(stdout=False, stderr=True).decode("utf-8", errors="replace")

except Exception as e:
if "ReadTimeout" in type(e).__name__ or "Timeout" in type(e).__name__:
if container:
try:
container.stop(timeout=1)
except Exception:
pass
raise subprocess.TimeoutExpired(cmd=code, timeout=timeout)

returncode = 1
stdout = ""
stderr = f"Execution failed: {e}"
if container:
try:
container.stop(timeout=1)
except Exception:
pass
finally:
if container:
try:
container.remove(force=True)
except Exception:
pass
if temp_script_name and host_workspace:
try:
os.remove(os.path.join(host_workspace, temp_script_name))
except Exception:
pass

return subprocess.CompletedProcess(args=code, returncode=returncode, stdout=stdout, stderr=stderr)

def _normalize_command(command: str) -> str:
cmd = (command or "").strip()
Expand Down
2 changes: 1 addition & 1 deletion seimei/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ async def chat(
else:
payload = {
"model": self.model,
"messages": payload_msgs,
"input": payload_msgs,
}
payload.update(self._filter_payload(extra_params))
if self.using_kyotoai:
Expand Down