| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- #!/usr/bin/env python3
- """
- s01_agent_loop.py - Agent 循环
- AI 编码 Agent 的核心秘密可以浓缩成一个模式:
- while stop_reason == "tool_use":
- response = LLM(messages, tools)
- 执行工具
- 追加结果
- +----------+ +-------+ +---------+
- | User | ---> | LLM | ---> | Tool |
- | prompt | | | | execute |
- +----------+ +---+---+ +----+----+
- ^ |
- | tool_result |
- +---------------+
- (循环继续)
- 这就是核心循环:把工具结果喂回给模型,直到模型决定停止。
- 生产级 Agent 会在这个基础上叠加策略、Hooks 和生命周期控制。
- 用法:
- pip install anthropic python-dotenv
- ANTHROPIC_API_KEY=... python s01_agent_loop/code.py
- """
- import os
- import subprocess
- try:
- import readline
- # macOS 的 libedit 在处理中文输入时有退格问题,这四行修复它
- readline.parse_and_bind('set bind-tty-special-chars off')
- readline.parse_and_bind('set input-meta on')
- readline.parse_and_bind('set output-meta on')
- readline.parse_and_bind('set convert-meta off')
- except ImportError:
- pass
- from anthropic import Anthropic
- from dotenv import load_dotenv
- load_dotenv(override=True)
- if os.getenv("ANTHROPIC_BASE_URL"):
- os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
- client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
- MODEL = os.environ["MODEL_ID"]
- SYSTEM = f"你是位于 {os.getcwd()}. 使用 bash 解决任务。直接行动,不要只解释。"
- # ── 工具定义:只有 bash ────────────────────────────
- TOOLS = [{
- "name": "bash",
- "description": "运行一条 shell 命令。",
- "input_schema": {
- "type": "object",
- "properties": {"command": {"type": "string"}},
- "required": ["command"],
- },
- }]
- # ── 工具执行 ────────────────────────────────────────
- def run_bash(command: str) -> str:
- dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
- if any(d in command for d in dangerous):
- return "错误:危险命令已被拦截"
- try:
- r = subprocess.run(command, shell=True, cwd=os.getcwd(),
- capture_output=True, text=True, timeout=120)
- out = (r.stdout + r.stderr).strip()
- return out[:50000] if out else "(无输出)"
- except subprocess.TimeoutExpired:
- return "错误:执行超时(120 秒)"
- except (FileNotFoundError, OSError) as e:
- return f"错误:{e}"
- # ── 核心模式:while 循环持续调用工具,直到模型停止 ──
- def agent_loop(messages: list):
- while True:
- response = client.messages.create(
- model=MODEL, system=SYSTEM, messages=messages,
- tools=TOOLS, max_tokens=8000,
- )
- # 追加 assistant 轮次
- messages.append({"role": "assistant", "content": response.content})
- # 如果模型没有调用工具,就结束
- if response.stop_reason != "tool_use":
- return
- # 执行每个工具调用并收集结果
- results = []
- for block in response.content:
- if block.type == "tool_use":
- print(f"\033[33m$ {block.input['command']}\033[0m")
- output = run_bash(block.input["command"])
- print(output[:200])
- results.append({
- "type": "tool_result",
- "tool_use_id": block.id,
- "content": output,
- })
- # 将工具结果喂回去,循环继续
- messages.append({"role": "user", "content": results})
- # ── 入口 ──────────────────────────────────────────
- if __name__ == "__main__":
- print("s01: Agent 循环")
- print("输入问题,回车发送。输入 q 退出。\n")
- history = []
- while True:
- try:
- query = input("\033[36ms01 >> \033[0m")
- except (EOFError, KeyboardInterrupt):
- break
- if query.strip().lower() in ("q", "exit", ""):
- break
- history.append({"role": "user", "content": query})
- agent_loop(history)
- # 打印模型最终文本回复
- response_content = history[-1]["content"]
- if isinstance(response_content, list):
- for block in response_content:
- if getattr(block, "type", None) == "text":
- print(block.text)
- print()
|