January 2025
I’ve been building a lot of complex agents at work, and wanted to solidify my understanding — so thought I’d make the simplest barebones agents I could.
I ended up making two, one with built-in function calling, and one without.
Without function calling (99 lines)
What’s happening here pre-call:
- we simply prompt the LLM to output a single word for the tools it wants to use
- we tell it to output "DONE: {final answer}" when finished
- we wrap it in an infinite loop, and tell it to terminate if it’s looping or out of tools
Then we simply check for these values in the text post-call:
- if it starts with "DONE", we extract the remaining portion, print it, and terminate
- if it's a one-word answer, we check if it matches any of our function names, and if so we call and append to results
- after each response, we insert the steps taken (tools used) and all prior results into the next prompt for context
All the magic here is in the prompt and context of steps taken. By providing past steps, results, and instructions to terminate if it’s looping or has a good answer -- it actually does a pretty good job!
The output:
(.venv) shwin@ashwin-m1-wan ai_assistant % python agent_no_func_call.py
What would you like to know? (or 'quit' to exit): what should I know
You've got a meeting at 2pm, and an email from Bob about a project deadline!With built-in function calling (140 lines)
Basically a more formalized version of our non-function-calling example:
- instead of a simple description of tools, we have an official
input_schemaformat for thetoolsparam, one for each tool - we append previous results using the
messagesparameter instead of injecting directly into our prompt (essentially the same thing) - same check for "DONE" to terminate
The output when we run:
(.venv) shwin@ashwin-m1-wan ai_assistant % python agent_func_call.py
What would you like to know? (or 'quit' to exit): what should alice know
Alice should know:
1. She has a meeting with clients at 2pm (from calendar)
2. There's an ongoing email thread about Q4 planning (from email search)Takeaways
Maybe due to confusion with Code Interpreter, I actually didn’t fully grasp that the functions were run locally, and that built-in function calling essentially just provides a more robust, structured version of our function-name-check in the non-function calling example.
I’m also much clearer on termination methods now, there are several:
- check for safe word (if "DONE" in output)
- max number of iterations
- end of a specific function
- a called function may set some state that we check for
- include an explicit Finished() function/tool
Overall this was a worthy exercise.
If you’re interested in more dissections like this here’s my Twitter and LinkedIn
If you want these sent directly to your inbox: