Theright07 commited on
Commit
2041e32
·
verified ·
1 Parent(s): c83d6aa

Fix: use gr.ChatInterface instead of gr.Blocks+Chatbot to avoid Gradio 4.44 type bug

Browse files
Files changed (1) hide show
  1. app.py +43 -76
app.py CHANGED
@@ -8,13 +8,13 @@ import git as gitlib
8
  WORKSPACE = Path("/workspace")
9
  WORKSPACE.mkdir(exist_ok=True)
10
 
11
- # ---------- Model load (CPU friendly) ----------
12
  MODEL_REPO = "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF"
13
  MODEL_FILE = "qwen2.5-coder-1.5b-instruct-q4_k_m.gguf"
14
 
15
- print("Downloading model ...")
16
  model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
17
- print("Loading llama.cpp ...")
18
  llm = Llama(
19
  model_path=model_path,
20
  n_ctx=4096,
@@ -24,85 +24,74 @@ llm = Llama(
24
  )
25
 
26
  # ---------- Tools ----------
27
- def _safe(p: str) -> Path:
28
  path = (WORKSPACE / p).resolve()
29
  if WORKSPACE.resolve() not in path.parents and path != WORKSPACE.resolve():
30
  raise ValueError("Path escapes workspace")
31
  return path
32
 
33
- def read_file(path: str) -> str:
34
- return _safe(path).read_text(encoding="utf-8", errors="replace")
35
 
36
- def write_file(path: str, content: str) -> str:
37
  p = _safe(path); p.parent.mkdir(parents=True, exist_ok=True)
38
  p.write_text(content, encoding="utf-8")
39
  return f"wrote {len(content)} bytes -> {path}"
40
 
41
- def edit_file(path: str, find: str, replace: str) -> str:
42
  p = _safe(path); txt = p.read_text(encoding="utf-8")
43
  if find not in txt: return "pattern not found"
44
- new = txt.replace(find, replace)
45
- p.write_text(new, encoding="utf-8")
46
  return f"replaced {txt.count(find)} occurrence(s) in {path}"
47
 
48
- def delete_file(path: str) -> str:
49
  p = _safe(path)
50
  if p.is_dir(): shutil.rmtree(p)
51
  else: p.unlink()
52
  return f"deleted {path}"
53
 
54
- def search_files(pattern: str, root: str = ".") -> str:
55
  base = _safe(root)
56
  hits = [str(Path(x).relative_to(WORKSPACE)) for x in glob.glob(f"{base}/**/{pattern}", recursive=True)]
57
  return json.dumps(hits[:200])
58
 
59
- def git_clone(url: str, dest: str = "") -> str:
60
  name = dest or url.rstrip("/").split("/")[-1].replace(".git", "")
61
  target = _safe(name)
62
  if target.exists(): return f"{name} already exists"
63
  gitlib.Repo.clone_from(url, target)
64
  return f"cloned -> {name}"
65
 
66
- def git_pull(repo: str) -> str:
67
- r = gitlib.Repo(_safe(repo)); r.remotes.origin.pull()
68
- return "pulled"
69
 
70
- def git_push(repo: str, message: str = "update", token: str = "") -> str:
71
  r = gitlib.Repo(_safe(repo))
72
  r.git.add(A=True); r.index.commit(message)
73
  if token:
74
  url = r.remotes.origin.url.replace("https://", f"https://x-access-token:{token}@")
75
  r.remotes.origin.set_url(url)
76
- r.remotes.origin.push()
77
- return "pushed"
78
 
79
  def _run(cmd, cwd):
80
  proc = subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, text=True, timeout=1800)
81
  return f"[exit {proc.returncode}]\n{proc.stdout[-4000:]}\n{proc.stderr[-2000:]}"
82
 
83
- def gradle_build(repo: str, task: str = "assembleDebug") -> str:
84
- cwd = _safe(repo)
85
- wrapper = cwd / "gradlew"
86
  if wrapper.exists():
87
- os.chmod(wrapper, 0o755)
88
- return _run(f"./gradlew {task} --no-daemon", cwd)
89
  return _run(f"gradle {task} --no-daemon", cwd)
90
 
91
- def build_apk(repo: str, variant: str = "debug") -> str:
92
  task = f"assemble{variant.capitalize()}"
93
  log = gradle_build(repo, task)
94
  apks = glob.glob(f"{_safe(repo)}/**/*.apk", recursive=True)
95
  return log + "\nAPKs:\n" + "\n".join(apks)
96
 
97
- def upload_artifact(path: str, repo_id: str, token: str, path_in_repo: str = "") -> str:
98
- api = HfApi(token=token)
99
- p = _safe(path)
100
- api.upload_file(
101
- path_or_fileobj=str(p),
102
- path_in_repo=path_in_repo or p.name,
103
- repo_id=repo_id,
104
- repo_type="model",
105
- )
106
  return f"uploaded {p.name} -> {repo_id}"
107
 
108
  TOOLS = {
@@ -114,66 +103,44 @@ TOOLS = {
114
  }
115
 
116
  TOOL_SPEC = """
117
- Available tools (call by emitting a single JSON block: ```tool\n{"name":"...","args":{...}}\n```):
118
- - read_file(path)
119
- - write_file(path, content)
120
- - edit_file(path, find, replace)
121
- - delete_file(path)
122
- - search_files(pattern, root=".")
123
- - git_clone(url, dest="")
124
- - git_pull(repo)
125
- - git_push(repo, message="update", token="")
126
- - gradle_build(repo, task="assembleDebug")
127
- - build_apk(repo, variant="debug")
128
- - upload_artifact(path, repo_id, token, path_in_repo="")
129
- After tool result, continue. End with plain answer when done.
130
  """
131
 
132
  SYSTEM = "You are an Android build agent. Use tools to clone, edit, build APKs, and upload artifacts. " + TOOL_SPEC
133
-
134
  TOOL_RE = re.compile(r"```tool\s*(\{.*?\})\s*```", re.S)
135
 
136
- def run_agent(user_msg, history, max_steps=6):
137
  msgs = [{"role": "system", "content": SYSTEM}]
138
- for item in history:
139
- if isinstance(item, (list, tuple)) and len(item) == 2:
140
- u, a = item
141
- if u:
142
- msgs.append({"role": "user", "content": str(u)})
143
- if a:
144
- msgs.append({"role": "assistant", "content": str(a)})
145
- msgs.append({"role": "user", "content": user_msg})
146
 
147
  transcript = ""
148
- for _ in range(max_steps):
149
  out = llm.create_chat_completion(messages=msgs, temperature=0.2, max_tokens=800)
150
  reply = out["choices"][0]["message"]["content"]
151
  transcript += reply + "\n"
152
  m = TOOL_RE.search(reply)
153
  if not m:
154
- return transcript
155
  try:
156
  call = json.loads(m.group(1))
157
- fn = TOOLS[call["name"]]
158
- result = fn(**call.get("args", {}))
159
  except Exception as e:
160
- result = f"ERROR: {e}\n{traceback.format_exc()[-500:]}"
161
  msgs.append({"role": "assistant", "content": reply})
162
  msgs.append({"role": "user", "content": f"tool_result:\n{result}"})
163
  transcript += f"\n[tool result]\n{result}\n"
164
- return transcript
165
-
166
- with gr.Blocks(title="Android Build Agent") as demo:
167
- gr.Markdown("## 🤖 Android Build Agent (Qwen2.5-Coder + Gradle + SDK)")
168
- chat = gr.Chatbot(height=500, type="tuples")
169
- box = gr.Textbox(placeholder="e.g. clone https://github.com/.../MyApp and build debug APK")
170
- state = gr.State([])
171
 
172
- def respond(msg, hist):
173
- ans = run_agent(msg, hist)
174
- hist = hist + [(msg, ans)]
175
- return "", hist, hist
176
-
177
- box.submit(respond, [box, state], [box, chat, state])
178
 
179
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=False)
 
8
  WORKSPACE = Path("/workspace")
9
  WORKSPACE.mkdir(exist_ok=True)
10
 
11
+ # ---------- Model load ----------
12
  MODEL_REPO = "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF"
13
  MODEL_FILE = "qwen2.5-coder-1.5b-instruct-q4_k_m.gguf"
14
 
15
+ print("Downloading model ...")
16
  model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
17
+ print("Loading llama.cpp ...")
18
  llm = Llama(
19
  model_path=model_path,
20
  n_ctx=4096,
 
24
  )
25
 
26
  # ---------- Tools ----------
27
+ def _safe(p):
28
  path = (WORKSPACE / p).resolve()
29
  if WORKSPACE.resolve() not in path.parents and path != WORKSPACE.resolve():
30
  raise ValueError("Path escapes workspace")
31
  return path
32
 
33
+ def read_file(path): return _safe(path).read_text(encoding="utf-8", errors="replace")
 
34
 
35
+ def write_file(path, content):
36
  p = _safe(path); p.parent.mkdir(parents=True, exist_ok=True)
37
  p.write_text(content, encoding="utf-8")
38
  return f"wrote {len(content)} bytes -> {path}"
39
 
40
+ def edit_file(path, find, replace):
41
  p = _safe(path); txt = p.read_text(encoding="utf-8")
42
  if find not in txt: return "pattern not found"
43
+ new = txt.replace(find, replace); p.write_text(new, encoding="utf-8")
 
44
  return f"replaced {txt.count(find)} occurrence(s) in {path}"
45
 
46
+ def delete_file(path):
47
  p = _safe(path)
48
  if p.is_dir(): shutil.rmtree(p)
49
  else: p.unlink()
50
  return f"deleted {path}"
51
 
52
+ def search_files(pattern, root="."):
53
  base = _safe(root)
54
  hits = [str(Path(x).relative_to(WORKSPACE)) for x in glob.glob(f"{base}/**/{pattern}", recursive=True)]
55
  return json.dumps(hits[:200])
56
 
57
+ def git_clone(url, dest=""):
58
  name = dest or url.rstrip("/").split("/")[-1].replace(".git", "")
59
  target = _safe(name)
60
  if target.exists(): return f"{name} already exists"
61
  gitlib.Repo.clone_from(url, target)
62
  return f"cloned -> {name}"
63
 
64
+ def git_pull(repo):
65
+ r = gitlib.Repo(_safe(repo)); r.remotes.origin.pull(); return "pulled"
 
66
 
67
+ def git_push(repo, message="update", token=""):
68
  r = gitlib.Repo(_safe(repo))
69
  r.git.add(A=True); r.index.commit(message)
70
  if token:
71
  url = r.remotes.origin.url.replace("https://", f"https://x-access-token:{token}@")
72
  r.remotes.origin.set_url(url)
73
+ r.remotes.origin.push(); return "pushed"
 
74
 
75
  def _run(cmd, cwd):
76
  proc = subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, text=True, timeout=1800)
77
  return f"[exit {proc.returncode}]\n{proc.stdout[-4000:]}\n{proc.stderr[-2000:]}"
78
 
79
+ def gradle_build(repo, task="assembleDebug"):
80
+ cwd = _safe(repo); wrapper = cwd / "gradlew"
 
81
  if wrapper.exists():
82
+ os.chmod(wrapper, 0o755); return _run(f"./gradlew {task} --no-daemon", cwd)
 
83
  return _run(f"gradle {task} --no-daemon", cwd)
84
 
85
+ def build_apk(repo, variant="debug"):
86
  task = f"assemble{variant.capitalize()}"
87
  log = gradle_build(repo, task)
88
  apks = glob.glob(f"{_safe(repo)}/**/*.apk", recursive=True)
89
  return log + "\nAPKs:\n" + "\n".join(apks)
90
 
91
+ def upload_artifact(path, repo_id, token, path_in_repo=""):
92
+ api = HfApi(token=token); p = _safe(path)
93
+ api.upload_file(path_or_fileobj=str(p), path_in_repo=path_in_repo or p.name,
94
+ repo_id=repo_id, repo_type="model")
 
 
 
 
 
95
  return f"uploaded {p.name} -> {repo_id}"
96
 
97
  TOOLS = {
 
103
  }
104
 
105
  TOOL_SPEC = """
106
+ Tools (emit JSON block: ```tool\n{"name":"...","args":{...}}\n```):
107
+ read_file, write_file, edit_file, delete_file, search_files,
108
+ git_clone, git_pull, git_push, gradle_build, build_apk, upload_artifact
 
 
 
 
 
 
 
 
 
 
109
  """
110
 
111
  SYSTEM = "You are an Android build agent. Use tools to clone, edit, build APKs, and upload artifacts. " + TOOL_SPEC
 
112
  TOOL_RE = re.compile(r"```tool\s*(\{.*?\})\s*```", re.S)
113
 
114
+ def chat_fn(message, history):
115
  msgs = [{"role": "system", "content": SYSTEM}]
116
+ for h in history:
117
+ msgs.append({"role": "user", "content": h[0]})
118
+ msgs.append({"role": "assistant", "content": h[1]})
119
+ msgs.append({"role": "user", "content": message})
 
 
 
 
120
 
121
  transcript = ""
122
+ for _ in range(6):
123
  out = llm.create_chat_completion(messages=msgs, temperature=0.2, max_tokens=800)
124
  reply = out["choices"][0]["message"]["content"]
125
  transcript += reply + "\n"
126
  m = TOOL_RE.search(reply)
127
  if not m:
128
+ return transcript.strip()
129
  try:
130
  call = json.loads(m.group(1))
131
+ result = TOOLS[call["name"]](**call.get("args", {}))
 
132
  except Exception as e:
133
+ result = f"ERROR: {e}\n{traceback.format_exc()[-300:]}"
134
  msgs.append({"role": "assistant", "content": reply})
135
  msgs.append({"role": "user", "content": f"tool_result:\n{result}"})
136
  transcript += f"\n[tool result]\n{result}\n"
137
+ return transcript.strip()
 
 
 
 
 
 
138
 
139
+ demo = gr.ChatInterface(
140
+ fn=chat_fn,
141
+ title="Android Build Agent",
142
+ description="Qwen2.5-Coder + Gradle + Android SDK",
143
+ examples=["Hello! What can you do?"],
144
+ )
145
 
146
+ demo.launch(server_name="0.0.0.0", server_port=7860)