Hello01 / app.py
Theright07's picture
Update app.py
207c831 verified
Raw
History Blame
6.37 kB
import os, re, json, shutil, subprocess, glob, traceback
from pathlib import Path
import gradio as gr
from huggingface_hub import hf_hub_download, HfApi
from llama_cpp import Llama
import git as gitlib
WORKSPACE = Path("/workspace")
WORKSPACE.mkdir(exist_ok=True)
# ---------- Model load (CPU friendly) ----------
MODEL_REPO = "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF"
MODEL_FILE = "qwen2.5-coder-1.5b-instruct-q4_k_m.gguf"
print("⏬ Downloading model ...")
model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
print("✅ Loading llama.cpp ...")
llm = Llama(
model_path=model_path,
n_ctx=4096,
n_threads=int(os.getenv("LLAMA_THREADS", "2")),
n_batch=256,
verbose=False,
)
# ---------- Tools ----------
def _safe(p: str) -> Path:
path = (WORKSPACE / p).resolve()
if WORKSPACE.resolve() not in path.parents and path != WORKSPACE.resolve():
raise ValueError("Path escapes workspace")
return path
def read_file(path: str) -> str:
return _safe(path).read_text(encoding="utf-8", errors="replace")
def write_file(path: str, content: str) -> str:
p = _safe(path); p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
return f"wrote {len(content)} bytes -> {path}"
def edit_file(path: str, find: str, replace: str) -> str:
p = _safe(path); txt = p.read_text(encoding="utf-8")
if find not in txt: return "pattern not found"
new = txt.replace(find, replace)
p.write_text(new, encoding="utf-8")
return f"replaced {txt.count(find)} occurrence(s) in {path}"
def delete_file(path: str) -> str:
p = _safe(path)
if p.is_dir(): shutil.rmtree(p)
else: p.unlink()
return f"deleted {path}"
def search_files(pattern: str, root: str = ".") -> str:
base = _safe(root)
hits = [str(Path(x).relative_to(WORKSPACE)) for x in glob.glob(f"{base}/**/{pattern}", recursive=True)]
return json.dumps(hits[:200])
def git_clone(url: str, dest: str = "") -> str:
name = dest or url.rstrip("/").split("/")[-1].replace(".git", "")
target = _safe(name)
if target.exists(): return f"{name} already exists"
gitlib.Repo.clone_from(url, target)
return f"cloned -> {name}"
def git_pull(repo: str) -> str:
r = gitlib.Repo(_safe(repo)); r.remotes.origin.pull()
return "pulled"
def git_push(repo: str, message: str = "update", token: str = "") -> str:
r = gitlib.Repo(_safe(repo))
r.git.add(A=True); r.index.commit(message)
if token:
url = r.remotes.origin.url.replace("https://", f"https://x-access-token:{token}@")
r.remotes.origin.set_url(url)
r.remotes.origin.push()
return "pushed"
def _run(cmd, cwd):
proc = subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, text=True, timeout=1800)
return f"[exit {proc.returncode}]\n{proc.stdout[-4000:]}\n{proc.stderr[-2000:]}"
def gradle_build(repo: str, task: str = "assembleDebug") -> str:
cwd = _safe(repo)
wrapper = cwd / "gradlew"
if wrapper.exists():
os.chmod(wrapper, 0o755)
return _run(f"./gradlew {task} --no-daemon", cwd)
return _run(f"gradle {task} --no-daemon", cwd)
def build_apk(repo: str, variant: str = "debug") -> str:
task = f"assemble{variant.capitalize()}"
log = gradle_build(repo, task)
apks = glob.glob(f"{_safe(repo)}/**/*.apk", recursive=True)
return log + "\nAPKs:\n" + "\n".join(apks)
def upload_artifact(path: str, repo_id: str, token: str, path_in_repo: str = "") -> str:
api = HfApi(token=token)
p = _safe(path)
api.upload_file(
path_or_fileobj=str(p),
path_in_repo=path_in_repo or p.name,
repo_id=repo_id,
repo_type="model",
)
return f"uploaded {p.name} -> {repo_id}"
TOOLS = {
"read_file": read_file, "write_file": write_file, "edit_file": edit_file,
"delete_file": delete_file, "search_files": search_files,
"git_clone": git_clone, "git_pull": git_pull, "git_push": git_push,
"gradle_build": gradle_build, "build_apk": build_apk,
"upload_artifact": upload_artifact,
}
TOOL_SPEC = """
Available tools (call by emitting a single JSON block: ```tool\n{"name":"...","args":{...}}\n```):
- read_file(path)
- write_file(path, content)
- edit_file(path, find, replace)
- delete_file(path)
- search_files(pattern, root=".")
- git_clone(url, dest="")
- git_pull(repo)
- git_push(repo, message="update", token="")
- gradle_build(repo, task="assembleDebug")
- build_apk(repo, variant="debug")
- upload_artifact(path, repo_id, token, path_in_repo="")
After tool result, continue. End with plain answer when done.
"""
SYSTEM = "You are an Android build agent. Use tools to clone, edit, build APKs, and upload artifacts. " + TOOL_SPEC
TOOL_RE = re.compile(r"```tool\s*(\{.*?\})\s*```", re.S)
def run_agent(user_msg, history, max_steps=6):
msgs = [{"role": "system", "content": SYSTEM}]
for u, a in history:
msgs.append({"role": "user", "content": u})
msgs.append({"role": "assistant", "content": a})
msgs.append({"role": "user", "content": user_msg})
transcript = ""
for _ in range(max_steps):
out = llm.create_chat_completion(messages=msgs, temperature=0.2, max_tokens=800)
reply = out["choices"][0]["message"]["content"]
transcript += reply + "\n"
m = TOOL_RE.search(reply)
if not m:
return transcript
try:
call = json.loads(m.group(1))
fn = TOOLS[call["name"]]
result = fn(**call.get("args", {}))
except Exception as e:
result = f"ERROR: {e}\n{traceback.format_exc()[-500:]}"
msgs.append({"role": "assistant", "content": reply})
msgs.append({"role": "user", "content": f"tool_result:\n{result}"})
transcript += f"\n[tool result]\n{result}\n"
return transcript
with gr.Blocks(title="Android Build Agent") as demo:
gr.Markdown("## 🤖 Android Build Agent (Qwen2.5-Coder + Gradle + SDK)")
chat = gr.Chatbot(height=500)
box = gr.Textbox(placeholder="e.g. clone https://github.com/.../MyApp and build debug APK")
state = gr.State([])
def respond(msg, hist):
ans = run_agent(msg, hist)
hist = hist + [(msg, ans)]
return "", hist, hist
box.submit(respond, [box, state], [box, chat, state])
demo.queue().launch(server_name="0.0.0.0", server_port=7860)