From 38e0ebab4ce438a8e07c8e2a6f2c9a6d519ddc9d Mon Sep 17 00:00:00 2001 From: WangKehan573 <54980782+WangKehan573@users.noreply.github.com> Date: Mon, 25 May 2026 14:10:16 +0800 Subject: [PATCH 1/4] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这里加入了相应的AI输出内容,trace过程可以让初学者更好入门 --- s01_agent_loop/try.py | 150 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 s01_agent_loop/try.py diff --git a/s01_agent_loop/try.py b/s01_agent_loop/try.py new file mode 100644 index 000000000..200fd0bba --- /dev/null +++ b/s01_agent_loop/try.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" +s01_agent_loop.py - The Agent Loop + +The entire secret of an AI coding agent in one pattern: + + while stop_reason == "tool_use": + response = LLM(messages, tools) + execute tools + append results + + +----------+ +-------+ +---------+ + | User | ---> | LLM | ---> | Tool | + | prompt | | | | execute | + +----------+ +---+---+ +----+----+ + ^ | + | tool_result | + +---------------+ + (loop continues) + +This is the core loop: feed tool results back to the model +until the model decides to stop. Production agents layer +policy, hooks, and lifecycle controls on top. + +Usage: + pip install anthropic python-dotenv + ANTHROPIC_API_KEY=... python s01_agent_loop/code.py +""" + +import os +import subprocess +import httpx +http_client = httpx.Client(verify=False) + + +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"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain." + +# ── Tool definition: just bash ──────────────────────────── +TOOLS = [{ + "name": "bash", + "description": "Run a shell command.", + "input_schema": { + "type": "object", + "properties": {"command": {"type": "string"}}, + "required": ["command"], + }, +}] + + +# ── Tool execution ──────────────────────────────────────── +def run_bash(command: str) -> str: + dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"] + if any(d in command for d in dangerous): + return "Error: Dangerous command blocked" + 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 "(no output)" + except subprocess.TimeoutExpired: + return "Error: Timeout (120s)" + except (FileNotFoundError, OSError) as e: + return f"Error: {e}" + + +# ── The core pattern: a while loop that calls tools until the model stops ── +def agent_loop(messages: list): + while True: + response = client.messages.create( + model=MODEL, system=SYSTEM, messages=messages, + tools=TOOLS, max_tokens=8000, + ) + + # Append assistant turn + #每次调用大模型后,都会把模型的回复加入messages(即histoty) + messages.append({"role": "assistant", "content": response.content}) + # ✅ 新增:打印大模型每次的回复内容(文本 + 思考过程) + for block in response.content: + print(f"\033[32m [AI 原始输出]\033[0m {block.model_dump()}") + if block.type == "text": + print(f"\033[32m🤖 [AI 回复]\033[0m {block.text}") + elif block.type == "thinking": + # thinking 是 Claude 3.5/4 等新模型的思维链字段 + print(f"\033[90m🧠 [AI 思考]\033[0m {getattr(block, 'thinking', '')}") + + print("stop_reason:", response.stop_reason) + # If the model didn't call a tool, we're done + if response.stop_reason != "tool_use": + return + + # Execute each tool call, collect results + 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, + }) + + # Feed tool results back, loop continues + messages.append({"role": "user", "content": results}) + + +# ── Entry point ────────────────────────────────────────── +if __name__ == "__main__": + print("s01: Agent Loop") + 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) + # Print the model's final text response + 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() From b8f2c7c0574ca582e66674a4ddcfb2b3a0ae12c6 Mon Sep 17 00:00:00 2001 From: WangKehan573 <54980782+WangKehan573@users.noreply.github.com> Date: Mon, 25 May 2026 14:39:23 +0800 Subject: [PATCH 2/4] =?UTF-8?q?extract=5Fmemories=E5=9C=A8=E4=BD=BF?= =?UTF-8?q?=E7=94=A8ANTHROPIC=E5=85=BC=E5=AE=B9=E7=9A=84qwen3.6-plus?= =?UTF-8?q?=E7=AD=89=E6=97=B6=E4=BC=9A=E6=8A=A5=E9=94=99=EF=BC=9A=E2=80=99?= =?UTF-8?q?ThinkingBlock'=20object=20has=20no=20attribute=20'Text'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extract_memories在使用ANTHROPIC兼容的qwen3.6-plus等时会报错:’ThinkingBlock' object has no attribute 'Text' --- s09_memory/code.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/s09_memory/code.py b/s09_memory/code.py index 7dd39c813..d241e526a 100644 --- a/s09_memory/code.py +++ b/s09_memory/code.py @@ -259,6 +259,9 @@ def extract_memories(messages: list): response = client.messages.create( model=MODEL, messages=[{"role": "user", "content": prompt}], max_tokens=800 ) + for block in response.content: + if block.type == 'text': + text = block.text text = response.content[0].text.strip() # Extract JSON array from response match = re.search(r'\[.*\]', text, re.DOTALL) From d91f144df54196c7cb28821684bf03a51988fcbb Mon Sep 17 00:00:00 2001 From: WangKehan573 <54980782+WangKehan573@users.noreply.github.com> Date: Mon, 25 May 2026 15:50:06 +0800 Subject: [PATCH 3/4] Update code.py --- s09_memory/code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/s09_memory/code.py b/s09_memory/code.py index d241e526a..c7846fe18 100644 --- a/s09_memory/code.py +++ b/s09_memory/code.py @@ -262,7 +262,7 @@ def extract_memories(messages: list): for block in response.content: if block.type == 'text': text = block.text - text = response.content[0].text.strip() + # Extract JSON array from response match = re.search(r'\[.*\]', text, re.DOTALL) if not match: From d2d9c54846e53508447bce1ffa229fcf9de2364d Mon Sep 17 00:00:00 2001 From: WangKehan573 <54980782+WangKehan573@users.noreply.github.com> Date: Mon, 25 May 2026 17:15:35 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:s09=20memory=20pipeline=E4=B8=AD?= =?UTF-8?q?=E7=9A=84extract=5Fmemories=E5=9C=A8=E4=BD=BF=E7=94=A8ANTHROPIC?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E7=9A=84qwen3.6-plus=E7=AD=89=E6=97=B6?= =?UTF-8?q?=E4=BC=9A=E6=8A=A5=E9=94=99=EF=BC=9A=E2=80=99ThinkingBlock'=20o?= =?UTF-8?q?bject=20has=20no=20attribute=20'Text'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix:s09 memory pipeline中的extract_memories在使用ANTHROPIC兼容的qwen3.6-plus等时会报错:’ThinkingBlock' object has no attribute 'Text'