Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions s01_agent_loop/try.py
Original file line number Diff line number Diff line change
@@ -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()
5 changes: 4 additions & 1 deletion s09_memory/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,10 @@ def extract_memories(messages: list):
response = client.messages.create(
model=MODEL, messages=[{"role": "user", "content": prompt}], max_tokens=800
)
text = response.content[0].text.strip()
for block in response.content:
if block.type == 'text':
text = block.text

# Extract JSON array from response
match = re.search(r'\[.*\]', text, re.DOTALL)
if not match:
Expand Down