Lesson 02 · Base

The cycle hasn’t changed, it’s just that there are more tools

"The loop hasn't changed at all, I just added things to the TOOLS array." - s02_tool_use.py Original words.

⏱ ~10 min · 📝 3 interactive widgets · 🧑‍💻 Based on shareAI-lab · s02_tool_use.py

How many places do you need to move to add a tool?

S01’s agent only knows bash. How would you change it to make it also work read_file / write_file / edit_file?

Many people’s first reaction is: change loop. wrong. Don't touch a single line ofloop. Only three things are needed:

  1. Write a Python handler function (run_read(path, limit)).
  2. Register it to the TOOL_HANDLERS mapping table ("read_file": lambda **kw: run_read(...)).
  3. Add a JSON schema statement to the TOOLS array (tell the model what the tool is called and what parameters it accepts).

When the loop sees a tool_use block, press block.name to search the function in the dispatch map, execute it, and stuff the output back to tool_result. It follows the exact same path as bash.

# Dispatch map: name → handler lambda
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

See how dispatch is routed

The widget below allows you to click on a tool_use request that may be sent by the model to see how it is routed to a specific Python function. Note: block.name determines which line to take.

safe_path: a line of defense that cannot be omitted

Give the agent file access rights. The most likely safety issue is path escape: the model was supposed to work in /home/user/project/, but it ended up sending a read_file("../../etc/passwd").

There is a small function in s02 that deals with this:

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve() # Normalize, resolve .. and soft links
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

The key lies in the two steps .resolve() + .is_relative_to() - parse to the absolute path and then check whether it is still in the sandbox. Without the former, foo/../../etc can pass through; without the latter, no inspection is even done.

Identify path security

The following 5 paths are all read_file parameters that may be sent by the model. Which ones will be allowed by safe_path and which ones will be rejected? Assume WORKDIR = /home/user/project.

Do not add dangerous tools to the agent

It is easy to add tools, but remember: Every tool you add is a new capability boundary of the model. In s02, bash already has a blacklist (rm -rf /, sudo, shutdown), and write_file has safe_path restrictions. Ask yourself three questions before adding tools to production:

  • Can this tool allow the model to do irreversible things? (rm, send email, git push)
  • What can itleak? (env vars, .ssh, cookies)
  • Will error output be treated as command feedback? (prompt injection via tool output)

Lecture s07 will add a "permission layer" to pull these decisions out of the code and make them declarative.

Interactive

Widget 1 · Tool Dispatch · How tool_use is routed to handler

Click a tool_use request and see how block.name in the loop finds the corresponding function, executes it, and puts the output back into the tool_result block.

模型发来的 tool_use 块
TOOL_HANDLERS 映射表

        
执行结果 · tool_result 块
(点上面任意一条 tool_use 触发路由)
Interactive

Widget 2 · Safe Path · Escape detection for 5 paths

Each path will be safe_path() processed. Assume WORKDIR = /home/user/project, click "Release" or "Reject" - it depends on whether your intuition about path escape is accurate.

答对 0 / 5
Interactive

Widget 3 · Add a Tool · What needs to be changed to add a glob tool to the agent?

Suppose you want to add a new tool glob(pattern) to allow the agent to find files in batches. Fill in the blanks: All three must be changed, none of them can be missing.

第 1 处 · 写一个 handler 函数
def run_glob(pattern: str) -> str:
    import glob
    return "\n".join(glob.glob(pattern))
第 2 处 · 注册到 dispatch map(选正确一行)
第 3 处 · 在 TOOLS 数组里加 schema(选正确一组)