Build Your Own AI Coding Agent

Lesson 2 of 4

Functions

An LLM on its own can only produce text, it can't read a file, run code, or change anything. To build an actual agent (something that takes action, not just talks), you first need real Python functions it can eventually be given access to. This lesson writes three: reading a file, writing a file, and running arbitrary Python code.

Reading and writing files

Pythonshares state with other Python blocks

These are the two most basic actions any coding agent needs: seeing what's currently in a file, and changing it. Nothing fancy yet, just Python's built-in open().

Running arbitrary Python code

An agent that can fix bugs needs to actually execute code to test whether a fix works, not just read and write text:

Pythonshares state with other Python blocks
  • exec(code, {}) runs a string of Python code as if it were a script. Passing {} as its globals dict gives it a fresh, empty namespace each call, so one run_python call can't accidentally see variables left over from a previous one.
  • contextlib.redirect_stdout(output) temporarily redirects anything the code print()s into an in-memory buffer (io.StringIO()) instead of your real terminal, so run_python can capture and return it as a string, exactly what's needed to hand the result back to an LLM as text.
  • The try/except is deliberate: if the executed code raises an error (like the division by zero above), run_python returns a description of the error as its result, instead of crashing your whole program. An agent needs to see its mistakes as feedback ("that failed because...") to correct course, not have the entire session die.

WARNING

exec runs code with the same permissions as your own program, there's no sandboxing here. This is fine for a personal learning project you control, but a production agent that executes model-generated code needs much stronger isolation (a container, a subprocess with a timeout, a restricted execution environment), never run untrusted, model-generated code with a bare exec in anything real.

📝 Functions Quiz

Passing score: 70%
  1. 1.Why does run_python wrap exec() in a try/except instead of letting an error crash the program?

  2. 2.contextlib.redirect_stdout lets run_python capture printed output into a string instead of the real terminal.

  3. 3.exec(code, {}) runs code with a fresh, empty ____ dictionary so previous calls can't leak variables into it.