Spaces:
Running
Running
KaiWu commited on
Commit ·
ab037c6
0
Parent(s):
initial commit
Browse files- .env +61 -0
- AGENTS.md +5 -0
- agent_loop.py +128 -0
- requirements.txt +2 -0
.env
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Key (required)
|
| 2 |
+
# Get yours at: https://console.anthropic.com/
|
| 3 |
+
ANTHROPIC_API_KEY=sk-9wTK68ZO58_s4w5_SUsR7w
|
| 4 |
+
|
| 5 |
+
# Model ID (required)
|
| 6 |
+
MODEL_ID=gemini-3-flash-thinking
|
| 7 |
+
|
| 8 |
+
# Base URL (optional, for Anthropic-compatible providers)
|
| 9 |
+
ANTHROPIC_BASE_URL=https://coding.qunhequnhe.com
|
| 10 |
+
|
| 11 |
+
# =============================================================================
|
| 12 |
+
# Anthropic-compatible providers
|
| 13 |
+
#
|
| 14 |
+
# Provider MODEL_ID SWE-bench TB2 Base URL
|
| 15 |
+
# --------------- -------------------- --------- ------ -------------------
|
| 16 |
+
# Anthropic claude-sonnet-4-6 79.6% 59.1% (default)
|
| 17 |
+
# MiniMax MiniMax-M2.5 80.2% - see below
|
| 18 |
+
# GLM (Zhipu) glm-5 77.8% - see below
|
| 19 |
+
# Kimi (Moonshot) kimi-k2.5 76.8% - see below
|
| 20 |
+
# DeepSeek deepseek-chat 73.0% - see below
|
| 21 |
+
# (V3.2)
|
| 22 |
+
#
|
| 23 |
+
# SWE-bench = SWE-bench Verified (Feb 2026)
|
| 24 |
+
# TB2 = Terminal-Bench 2.0 (Feb 2026)
|
| 25 |
+
# =============================================================================
|
| 26 |
+
|
| 27 |
+
# ---- International ----
|
| 28 |
+
|
| 29 |
+
# MiniMax https://www.minimax.io
|
| 30 |
+
# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic
|
| 31 |
+
# MODEL_ID=MiniMax-M2.5
|
| 32 |
+
|
| 33 |
+
# GLM (Zhipu) https://z.ai
|
| 34 |
+
# ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
|
| 35 |
+
# MODEL_ID=glm-5
|
| 36 |
+
|
| 37 |
+
# Kimi (Moonshot) https://platform.moonshot.ai
|
| 38 |
+
# ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic
|
| 39 |
+
# MODEL_ID=kimi-k2.5
|
| 40 |
+
|
| 41 |
+
# DeepSeek https://platform.deepseek.com
|
| 42 |
+
# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
|
| 43 |
+
# MODEL_ID=deepseek-chat
|
| 44 |
+
|
| 45 |
+
# ---- China mainland ----
|
| 46 |
+
|
| 47 |
+
# MiniMax https://platform.minimax.io
|
| 48 |
+
# ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic
|
| 49 |
+
# MODEL_ID=MiniMax-M2.5
|
| 50 |
+
|
| 51 |
+
# GLM (Zhipu) https://open.bigmodel.cn
|
| 52 |
+
# ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic
|
| 53 |
+
# MODEL_ID=glm-5
|
| 54 |
+
|
| 55 |
+
# Kimi (Moonshot) https://platform.moonshot.cn
|
| 56 |
+
# ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic
|
| 57 |
+
# MODEL_ID=kimi-k2.5
|
| 58 |
+
|
| 59 |
+
# DeepSeek (no regional split, same endpoint globally)
|
| 60 |
+
# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
|
| 61 |
+
# MODEL_ID=deepseek-chat
|
AGENTS.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 本项目致力于搭建一个基于终端交互的agent,用于生成CAD模型或3D模型
|
| 2 |
+
# 这是一个python项目,使用conda管理虚拟环境,虚拟环境名字是:aigc
|
| 3 |
+
# 开发过程中,不要过度设计,少即是多
|
| 4 |
+
# agent迭代方式仍会遵循敏捷迭代,也就是每个版本都独立、可交付
|
| 5 |
+
# 推理、设计、改动等过程中任何一环,出现拿不准的情况,不要擅自主张,可以拆解问题或向用户提问
|
agent_loop.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
from anthropic import Anthropic
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv(override=True)
|
| 9 |
+
|
| 10 |
+
if os.getenv("ANTHROPIC_BASE_URL"):
|
| 11 |
+
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
|
| 12 |
+
|
| 13 |
+
WORKDIR = Path.cwd()
|
| 14 |
+
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
| 15 |
+
MODEL = os.environ["MODEL_ID"]
|
| 16 |
+
|
| 17 |
+
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def safe_path(p: str) -> Path:
|
| 21 |
+
path = (WORKDIR / p).resolve()
|
| 22 |
+
if not path.is_relative_to(WORKDIR):
|
| 23 |
+
raise ValueError(f"Path escapes workspace: {p}")
|
| 24 |
+
return path
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def run_bash(command: str) -> str:
|
| 28 |
+
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
|
| 29 |
+
if any(d in command for d in dangerous):
|
| 30 |
+
return "Error: Dangerous command blocked"
|
| 31 |
+
try:
|
| 32 |
+
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
| 33 |
+
capture_output=True, text=True, timeout=120)
|
| 34 |
+
out = (r.stdout + r.stderr).strip()
|
| 35 |
+
return out[:50000] if out else "(no output)"
|
| 36 |
+
except subprocess.TimeoutExpired:
|
| 37 |
+
return "Error: Timeout (120s)"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def run_read(path: str, limit: int = None) -> str:
|
| 41 |
+
try:
|
| 42 |
+
text = safe_path(path).read_text()
|
| 43 |
+
lines = text.splitlines()
|
| 44 |
+
if limit and limit < len(lines):
|
| 45 |
+
lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
|
| 46 |
+
return "\n".join(lines)[:50000]
|
| 47 |
+
except Exception as e:
|
| 48 |
+
return f"Error: {e}"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def run_write(path: str, content: str) -> str:
|
| 52 |
+
try:
|
| 53 |
+
fp = safe_path(path)
|
| 54 |
+
fp.parent.mkdir(parents=True, exist_ok=True)
|
| 55 |
+
fp.write_text(content)
|
| 56 |
+
return f"Wrote {len(content)} bytes to {path}"
|
| 57 |
+
except Exception as e:
|
| 58 |
+
return f"Error: {e}"
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def run_edit(path: str, old_text: str, new_text: str) -> str:
|
| 62 |
+
try:
|
| 63 |
+
fp = safe_path(path)
|
| 64 |
+
content = fp.read_text()
|
| 65 |
+
if old_text not in content:
|
| 66 |
+
return f"Error: Text not found in {path}"
|
| 67 |
+
fp.write_text(content.replace(old_text, new_text, 1))
|
| 68 |
+
return f"Edited {path}"
|
| 69 |
+
except Exception as e:
|
| 70 |
+
return f"Error: {e}"
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# -- The dispatch map: {tool_name: handler} --
|
| 74 |
+
TOOL_HANDLERS = {
|
| 75 |
+
"bash": lambda **kw: run_bash(kw["command"]),
|
| 76 |
+
"read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
|
| 77 |
+
"write_file": lambda **kw: run_write(kw["path"], kw["content"]),
|
| 78 |
+
"edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
TOOLS = [
|
| 82 |
+
{"name": "bash", "description": "Run a shell command.",
|
| 83 |
+
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
|
| 84 |
+
{"name": "read_file", "description": "Read file contents.",
|
| 85 |
+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
|
| 86 |
+
{"name": "write_file", "description": "Write content to file.",
|
| 87 |
+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
|
| 88 |
+
{"name": "edit_file", "description": "Replace exact text in file.",
|
| 89 |
+
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def agent_loop(messages: list):
|
| 94 |
+
while True:
|
| 95 |
+
response = client.messages.create(
|
| 96 |
+
model=MODEL, system=SYSTEM, messages=messages,
|
| 97 |
+
tools=TOOLS, max_tokens=8000,
|
| 98 |
+
)
|
| 99 |
+
messages.append({"role": "assistant", "content": response.content})
|
| 100 |
+
if response.stop_reason != "tool_use":
|
| 101 |
+
return
|
| 102 |
+
results = []
|
| 103 |
+
for block in response.content:
|
| 104 |
+
if block.type == "tool_use":
|
| 105 |
+
handler = TOOL_HANDLERS.get(block.name)
|
| 106 |
+
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
|
| 107 |
+
print(f"> {block.name}: {output[:200]}")
|
| 108 |
+
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
|
| 109 |
+
messages.append({"role": "user", "content": results})
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
history = []
|
| 114 |
+
while True:
|
| 115 |
+
try:
|
| 116 |
+
query = input("\033[36ms02 >> \033[0m")
|
| 117 |
+
except (EOFError, KeyboardInterrupt):
|
| 118 |
+
break
|
| 119 |
+
if query.strip().lower() in ("q", "exit", ""):
|
| 120 |
+
break
|
| 121 |
+
history.append({"role": "user", "content": query})
|
| 122 |
+
agent_loop(history)
|
| 123 |
+
response_content = history[-1]["content"]
|
| 124 |
+
if isinstance(response_content, list):
|
| 125 |
+
for block in response_content:
|
| 126 |
+
if hasattr(block, "text"):
|
| 127 |
+
print(block.text)
|
| 128 |
+
print()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
anthropic
|
| 2 |
+
python-dotenv
|