ä¸ä»£çå½ä»¤è¡çé¢å¼åç³»ç» | AIçæåç¿»è¯
好çï¼æå·²ç»å¯¹ ww é¡¹ç®æäºè¶³å¤çäºè§£ã让æè®¾è®¡è¿ä¸ä¸ª agent ç³»ç»ã
é®é¢ï¼ 为 ww CLI 项ç®è®¾è®¡ä¸ä¸ªä¸ agent ç³»ç»ï¼ç¼ç agentãæµè¯ agentãææ¡£ agentï¼ï¼è¦æ±èè´£æ¸
æ°å离ââæ¯ä¸ª agent æä½ä¸åçæä»¶/å¼ï¼é¿å
å²çªã
çæ¡ï¼
å ³é®æè·¯æ¯æä»¶ç³»ç»çº§å«çå离ââæ¯ä¸ª agent æ¥æä¸åçæä»¶ç±»å«ï¼å æ¤å®ä»¬å¯ä»¥å¹¶è¡è¿è¡ï¼ææé¡ºåºæµæ°´çº¿è¿è¡ï¼èä¸ä¼äº§çåå¹¶å²çªã以䏿¯å®æ´è®¾è®¡ï¼
ww ç Agent ææ
âââââââââââââââââââââââââââââââââââ
â ç¼æå¨ (Orchestrator) â
â (ä½ / ä¸ä¸ªé¡¶å±èæ¬) â
ââââââââââ¬âââââââââ¬âââââââââ¬ââââââââ
â â â
âââââââââââ ââââââ ââââââ
â¼ â¼ â¼
CodingAgent TestingAgent DocAgent
æ¥æ: src/ æ¥æ: tests/ æ¥æ: docs/ README
æä»¶å½å±ï¼æ å²çªï¼
å®ç°ï¼ww ä¸ç agents/ ç®å½
agents/base_agent.py
import anthropic
import subprocess
from pathlib import Path
from abc import ABC, abstractmethod
client = anthropic.Anthropic()
class BaseAgent(ABC):
name: str
system_prompt: str
owned_dirs: list[str] # 该 agent å
许åå
¥çç®å½
read_dirs: list[str] # 该 agent å
许读åçç®å½
def __init__(self, repo_root: str = "."):
self.repo_root = Path(repo_root)
def read_file(self, path: str) -> str:
return (self.repo_root / path).read_text()
def write_file(self, path: str, content: str):
target = self.repo_root / path
# å¼ºå¶æ§è¡æææ
allowed = any(str(target).startswith(str(self.repo_root / d))
for d in self.owned_dirs)
if not allowed:
raise PermissionError(f"{self.name} æ æ³åå
¥ {path}")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
print(f"[{self.name}] å·²åå
¥ {path}")
def run_shell(self, cmd: str) -> str:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True,
cwd=self.repo_root)
return result.stdout + result.stderr
def call_llm(self, user_msg: str) -> str:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
system=self.system_prompt,
messages=[{"role": "user", "content": user_msg}]
)
return response.content[0].text
@abstractmethod
def run(self, task: str) -> str:
...
agents/coding_agent.py
from .base_agent import BaseAgent
import ast, textwrap
class CodingAgent(BaseAgent):
name = "CodingAgent"
owned_dirs = ["ww"] # ä»
åå
¥ ww/ æºç
read_dirs = ["ww", "tests"] # å¯è¯»åæµè¯æä»¶ä»¥è·åä¸ä¸æ
system_prompt = textwrap.dedent("""
ä½ æ¯ `ww` å·¥å
·éç Python CLI ç¼ç agentã
ä½ çå·¥ä½ï¼å¨ ww/**/*.py ä¸å®ç°æ°å½ä»¤æä¿®å¤ bugã
è§åï¼
- ä»
è¾åºææç Python 代ç åï¼æ¯ä¸ªä»£ç ååç¼ä¸ºï¼
FILE: <relative/path/to/file.py>
```python
<code>
```
- ç»ä¸è¦ä¿®æ¹ tests/ãdocs/ æ README.mdã
- éµå¾ªç°æç ww 约å®ï¼Click ç»ãéè¿ openai/anthropic 客æ·ç«¯ç LLM è¾
å©å½æ°ã
- ä¿æå½æ°å°å·§ï¼æ·»å ç±»åæç¤ºåææ¡£å符串ã
""")
def run(self, task: str) -> str:
# æ¶éç¸å
³æºç ä¸ä¸æ
src_files = list((self.repo_root / "ww").rglob("*.py"))
context = ""
for f in src_files[:10]: # éå¶ä¸ä¸æå¤§å°
context += f"\n--- {f.relative_to(self.repo_root)} ---\n"
context += f.read_text()[:500] # æªæå¤§æä»¶
prompt = f"""
å½å代ç åºä¸ä¸æï¼
{context}
ä»»å¡ï¼{task}
è¾åºè¦å建/ä¿®æ¹çæä»¶ã
"""
response = self.call_llm(prompt)
files_written = self._apply_response(response)
return f"CodingAgent å·²åå
¥ï¼{files_written}"
def _apply_response(self, response: str) -> list[str]:
"""è§£æ FILE: åå¹¶åå
¥å®ä»¬ã"""
import re
written = []
pattern = r"FILE:\s*(\S+)\s*```python\n(.*?)```"
for match in re.finditer(pattern, response, re.DOTALL):
path, code = match.group(1), match.group(2)
# åå
¥åéªè¯è¯æ³æ¯å¦æ£ç¡®
try:
ast.parse(code)
except SyntaxError as e:
print(f"[CodingAgent] è·³è¿ {path} ââ è¯æ³é误ï¼{e}")
continue
self.write_file(path, code)
written.append(path)
return written
agents/testing_agent.py
from .base_agent import BaseAgent
import textwrap, re
class TestingAgent(BaseAgent):
name = "TestingAgent"
owned_dirs = ["tests"] # ä»
åå
¥æµè¯æä»¶
read_dirs = ["ww", "tests"] # è¯»åæºç ä»¥çææµè¯
system_prompt = textwrap.dedent("""
ä½ æ¯ `ww` CLI å·¥å
·éç Python æµè¯ agentã
ä½ çå·¥ä½ï¼ä¸º ww å½ä»¤ç¼å pytest æµè¯ã
è§åï¼
- ä»
è¾åºæµè¯ä»£ç åï¼åç¼ä¸ºï¼
FILE: tests/<test_file.py>
```python
<code>
```
- ä½¿ç¨ pytestãpytest-mockãClick ç CliRunner è¿è¡ CLI æµè¯ã
- ç»ä¸è¦ä¿®æ¹ ww/ æºç æä»¶æ READMEã
- æµè¯ï¼å¿«ä¹è·¯å¾ãè¾¹ç¼æ
åµãé误å¤çã
- 模æå¤é¨è°ç¨ï¼LLMãç½ç»ãæä»¶ç³»ç»åå
¥ï¼ã
""")
def run(self, task: str) -> str:
# 读å被æµè¯çæºæä»¶
src_context = ""
for f in (self.repo_root / "ww").rglob("*.py"):
src_context += f"\n--- {f.relative_to(self.repo_root)} ---\n"
src_context += f.read_text()[:800]
# å
è¿è¡ç°ææµè¯ï¼äºè§£å½åç¶æ
existing_results = self.run_shell("python -m pytest tests/ --tb=short -q 2>&1 | head -40")
prompt = f"""
æºä»£ç ï¼
{src_context}
ç°ææµè¯ç»æï¼
{existing_results}
ä»»å¡ï¼{task}
ç¼å pytest æµè¯ãä»
è¾åº FILE: åã
"""
response = self.call_llm(prompt)
written = self._apply_response(response)
# èªå¨è¿è¡æ°æµè¯
run_result = self.run_shell(f"python -m pytest tests/ -v --tb=short 2>&1 | tail -30")
return f"TestingAgent å·²åå
¥ {written}\n\næµè¯ç»æï¼\n{run_result}"
def _apply_response(self, response: str) -> list[str]:
written = []
pattern = r"FILE:\s*(tests/\S+)\s*```python\n(.*?)```"
for match in re.finditer(pattern, response, re.DOTALL):
path, code = match.group(1), match.group(2)
self.write_file(path, code)
written.append(path)
return written
agents/doc_agent.py
from .base_agent import BaseAgent
import textwrap, re
class DocAgent(BaseAgent):
name = "DocAgent"
owned_dirs = ["docs"] # åå
¥ docs/
read_dirs = ["ww", "docs"] # è¯»åæºç åç°æææ¡£
# ç¹æ®æ
åµï¼README.md 卿 ¹ç®å½ï¼æç¡®å¤ç
system_prompt = textwrap.dedent("""
ä½ æ¯ `ww` CLI å·¥å
·éçææ¡£ agentã
ä½ çå·¥ä½ï¼ä¿æ README.md å docs/ ææ°ã
è§åï¼
- è¾åº Markdown åï¼åç¼ä¸ºï¼
FILE: README.md ï¼æ docs/something.mdï¼
```markdown
<content>
```
- ç»ä¸è¦ä¿®æ¹ ww/ æºç æ tests/ã
- 仿ºç ä¸ç Click è£
饰卿åå½ä»¤è¡¨ã
- ä¿æ README ç Commands é¨åæç»åæ¯é¡ºåºæåºã
- ææ¡£é£æ ¼ï¼ç®æ´ï¼ç¤ºä¾ä¼å
ã
""")
def run(self, task: str) -> str:
# è¯»åæºç 以è¿è¡å½ä»¤å
ç
src_context = self._extract_cli_commands()
readme = (self.repo_root / "README.md").read_text()
prompt = f"""
å½å READMEï¼å 2000 å符ï¼ï¼
{readme[:2000]}
仿ºç 䏿åç CLI å½ä»¤ï¼
{src_context}
ä»»å¡ï¼{task}
è¾åºå¸¦ææ´æ°å Markdown ç FILE: åã
"""
response = self.call_llm(prompt)
written = self._apply_response(response)
return f"DocAgent å·²æ´æ°ï¼{written}"
def _extract_cli_commands(self) -> str:
"""å¿«é grep æ¥æ¾ Click @group.command() è£
饰å¨ã"""
result = self.run_shell(
"grep -rn '@.*command\\|@click.group\\|help=' ww/ --include='*.py' | head -60"
)
return result
def _apply_response(self, response: str) -> list[str]:
written = []
pattern = r"FILE:\s*(\S+\.md)\s*```(?:markdown)?\n(.*?)```"
for match in re.finditer(pattern, response, re.DOTALL):
path, content = match.group(1), match.group(2)
if path == "README.md":
# ç¹æ®æ
åµï¼README 卿 ¹ç®å½ï¼ä¸å¨ owned_dirs ä¸
(self.repo_root / "README.md").write_text(content)
written.append("README.md")
else:
self.write_file(path, content)
written.append(path)
return written
agents/orchestrator.py ââ è¿è¡ææä¸ä¸ª agent
#!/usr/bin/env python3
"""
ww å¤ agent ç¼æå¨ã
ç¨æ³ï¼
python -m agents.orchestrator --task "æ·»å ww net ping-sweep å½ä»¤"
python -m agents.orchestrator --agent coding --task "ä¿®å¤ ww git classify ä¸ç bug"
python -m agents.orchestrator --agent testing --task "为 ww note obfuscate æ·»å è¾¹ç¼æ
嵿µè¯"
python -m agents.orchestrator --agent doc --task "æ´æ° READMEï¼å
嫿¬å¨æ°å¢çææå½ä»¤"
"""
import argparse
from .coding_agent import CodingAgent
from .testing_agent import TestingAgent
from .doc_agent import DocAgent
def run_pipeline(task: str, repo_root: str = "."):
"""
é¡ºåºæµæ°´çº¿ï¼ä»£ç â æµè¯ â ææ¡£ã
æ¯ä¸ª agent é½è½çå°åä¸ä¸ª agent åååå
¥çæä»¶ã
"""
print(f"\n{'='*50}")
print(f"æµæ°´çº¿ä»»å¡ï¼{task}")
print('='*50)
coding = CodingAgent(repo_root)
testing = TestingAgent(repo_root)
doc = DocAgent(repo_root)
# 1. ç¼ç agent å®ç°
print("\n[1/3] CodingAgent è¿è¡ä¸...")
code_result = coding.run(task)
print(code_result)
# 2. æµè¯ agent 为ååå®ç°çå
容ç¼åæµè¯
print("\n[2/3] TestingAgent è¿è¡ä¸...")
test_result = testing.run(f"为以ä¸å
容ç¼åæµè¯ï¼{task}\n\nååå®ç°çå
容ï¼\n{code_result}")
print(test_result)
# 3. ææ¡£ agent æ´æ° README/docs
print("\n[3/3] DocAgent è¿è¡ä¸...")
doc_result = doc.run(f"æ´æ°ä»¥ä¸å
å®¹çææ¡£ï¼{task}")
print(doc_result)
print("\nâ
æµæ°´çº¿å®æã")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--task", required=True)
parser.add_argument("--agent", choices=["coding", "testing", "doc", "all"], default="all")
parser.add_argument("--repo", default=".")
args = parser.parse_args()
if args.agent == "all":
run_pipeline(args.task, args.repo)
elif args.agent == "coding":
CodingAgent(args.repo).run(args.task)
elif args.agent == "testing":
TestingAgent(args.repo).run(args.task)
elif args.agent == "doc":
DocAgent(args.repo).run(args.task)
ä½¿ç¨æ¹æ³
# 为æ°åè½è¿è¡å®æ´æµæ°´çº¿
python -m agents.orchestrator --task "æ·»å ww net ping-sweepï¼æ«æä¸ä¸ª CIDR èå´"
# è¿è¡å个 agent
python -m agents.orchestrator --agent coding --task "ä¿®å¤ ww git classify 以å¤çåå¹¶æäº¤"
python -m agents.orchestrator --agent testing --task "为 ww note obfuscate æ·»å è¾¹ç¼æ
嵿µè¯"
python -m agents.orchestrator --agent doc --task "æ´æ° READMEï¼å
嫿¬å¨æ°å¢çææå½ä»¤"
为ä»ä¹è¿æ ·å¯è¡ï¼æ å²çªï¼
è¿ç§åä½ä¹æä»¥å¹²åï¼æ¯å ä¸ºä¸¥æ ¼çåå ¥åé离ï¼
CodingAgentâ ä» ä¿®æ¹ww/*.pyã宿¯ç产è ãTestingAgentâ ä» ä¿®æ¹tests/*.pyãå®è¯»åæºç ä½ä»ä¸åå ¥ãDocAgentâ ä» ä¿®æ¹README.mdådocs/*.mdãå®è¯»åæºç 以å ç CLI å½ä»¤ã
æµæ°´çº¿ä¹æ¯å ææåºçï¼å
æä»£ç â æµè¯å¯ä»¥å¼ç¨çå®å®ç° â ææ¡£åæ æç»ç¶æãå¦æä½ æ³è¦å¹¶è¡ï¼TestingAgent å DocAgent å¯ä»¥å¨ CodingAgent 宿ååæ¶è¿è¡ï¼å®ä»¬åªè¯» ww/ï¼ä¸åå
¥ï¼ã
ä¸ä¸æ¥ï¼æ·»å --dry-run æ å¿ï¼ä½¿æ¯ä¸ª agent å¨åå
¥åæå° diffï¼å¹¶å°å
¶æ¥å
¥ ww agent åå½ä»¤ï¼è¿æ ·ä½ å°±å¯ä»¥ç´æ¥ä» CLI è°ç¨ ww agent run --task "..."ã
