Advanced Self-Made Agent: Building a Smarter, Multi-Step AI Agent

On this page
This post turns a simple, framework-free AI agent into a multi-step agent. It adds three things: an AgentState enum for explicit state management, a loop that lets the agent think and act several times before answering, and a way to ask the user for clarification. The full Python code is at the end.
In our previous blog post on creating AI agents without frameworks, we built an agent by exposing tools like query_database and search_wikipedia directly to the LLM. That agent only needs to:
- Understand available tools
- Decide when and how to use them
- Maintain relevant context
- Arrange the final answer
That first design covered the basic loop of processing user input and optionally calling tools. The multi-step version goes further. It can:
- Decide on its own whether it needs more information from the user
- Run several steps of "thinking" before settling on a final answer
- Exit cleanly when it reaches the maximum number of iterations
The new code relies on the same functions and tool definitions as the last post. What's new is the AgentState enum and the multi-step processing loop, which let the agent refine its answers. If you want to tune the instructions the agent gets in each of these iterations, have a look at DSPy: Build Better AI Systems with Automated Prompt Optimization.
Note: This is a continuation of our previous tutorial on building AI agents from scratch. If you haven't read it yet, start there to learn the basics before moving on to this agent.
What the multi-step agent adds
State management with an enum
The agent's states, THINKING, DONE, ERROR and NEED_MORE_INFO, live in an enum instead of loose local variables. At any point you know exactly where the agent is in the conversation cycle.
An iteration loop for complex reasoning
The agent repeats a think→act→evaluate cycle up to max_iterations times. That lets it handle more elaborate tasks, check partial results and try more than one line of reasoning.
Follow-up questions to the user
If the conversation is missing something essential, the agent returns a "need more info" response. The user can answer and the conversation continues, instead of ending in an error message.
The same tools as before
The agent still uses the tool descriptors from the first tutorial (query_database and search_wikipedia). The agent logic changed, the tools didn't.
How the multi-step agent's code is structured
Here is a conceptual overview before the full code. The agent is built from an enum, a multi-iteration loop and a separate step for each iteration.
-
AgentState definition. The possible states are defined in an enum:
THINKING: the agent is still working out an answer or deciding on next stepsDONE: the agent has a final answerERROR: an error occurred or the agent couldn't continueNEED_MORE_INFO: the agent needs clarification from the user before it can proceed
-
Loop with max iterations. The previous agent ended after a single pass. This one can make repeated attempts at an answer:
The loop keeps going until the agent has an answer, needs the user's input, or runs out of iterations.
-
Asking for clarification. If the LLM realizes mid-conversation that it can't proceed with the data it has, it switches to
NEED_MORE_INFO. When the user responds with more context, the loop resumes. -
Separate "think and act" step. The logic for choosing an action (
TOOL_CALL,ASK_USER,FINAL_ANSWER) sits in its own_think_and_act()method. That keeps the main loop short and easy to debug.
Why multi-step agents are useful
Say a user asks your agent about a complex database topic:
- In the first iteration, the agent checks whether it already has the data it needs. If not, it calls
query_database. - The tool call might return partial or ambiguous data. The agent runs another iteration and decides whether to refine the query or ask the user a clarifying question.
- Only after several steps does the agent produce a final answer, or conclude that it needs more information.
The user doesn't get stuck with a half-finished reply or a single-step error message. The back-and-forth feels closer to talking to a person.
The agent still uses the tools from our previous blog post and keeps the framework-free approach, so debugging and extending it stays simple. What you gain is iterative reasoning. If you need reliable answers rather than one-shot replies, this loop is the next thing to build.
Full Python implementation of the multi-step agent
Below is a complete code example of the multi-step agent. It uses an enum for the possible states, a loop for multiple "thinking" iterations and a simple mechanism for asking the user for more information. The code is heavily commented, so you can copy it into your project and follow along.
Note: In the code example below, we use mock functions for the used
tools (query_database and search_wikipedia). Please have a look at
our previous blog post for the original
tool definitions.
To run the agent, call interact_with_agent(). Then type questions or statements and watch how the agent processes them over several iterations. You can extend the code with more complex tools, additional states or more advanced reasoning logic.
How each part of the agent code works
-
AgentState enum. Defines the states the agent can be in at the end of each iteration:
- THINKING: the agent is still deciding its next step.
- DONE: there is a final answer for the user.
- ERROR: the agent hit a problem that prevents further progress.
- NEED_MORE_INFO: the agent can't proceed without more details from the user.
-
process_with_loop()is the main method and handles the "thinking" iterations. It appends the user input to the conversation, calls_think_and_act()in each iteration and checks the returned state. Then it asks for more info, returns a final answer, or stops once the maximum number of iterations is reached. -
_think_and_act()runs once per iteration. It passes the conversation so far to the LLM, parses the "ACTION:" directive in the reply and acts on it: it calls the necessary tool, asks the user for more data or returns a final answer. -
execute_tool()runs when the LLM requests a tool call with "TOOL_CALL". It parses the arguments and runs the matching Python function (for example, searching Wikipedia or querying a database). It returns a JSON string with the result or an error message. -
interact_with_agent()is a basic read-eval-print loop (REPL) that shows how the agent handles real user input. It feeds each user query intoprocess_with_loop(), then prints the final or partial result and an optional iteration trace.
From here you can add more logic, such as real tool integrations or more advanced planning. The main benefit is control: you decide the logic, error handling and iteration loops, rather than a large framework deciding for you.
If your agent needs external knowledge, How to: RAG with Azure AI Search shows how connecting additional data sources improves its accuracy and context.
Next step: giving the agent memory
This agent gives you a lot of flexibility and is a good starting point for more complex AI agents. The most obvious improvement left is memory, so the agent can remember previous interactions and use them in its answers. We cover that in the next tutorial, Enhancing Self-Made Agent with Memory.
Before you put an agent like this in front of users, read our best practices for building high-quality AI agent systems, which cover architecture, testing and error handling.
Interested in building high-quality AI agent systems?
We prepared a comprehensive guide based on cutting-edge research for how to build robust, reliable AI agent systems that actually work in production. This guide covers:
- Understanding the 14 systematic failure modes in multi-agent systems
- Evidence-based best practices for agent design
- Structured communication protocols and verification mechanisms
Further reading
- How to Build AI Agents Without Frameworks: A Step-by-Step Guide
- DSPy: Build Better AI Systems with Automated Prompt Optimization
- Free and Open Source ChatGPT Alternative: LibreChat
- How to: RAG with Azure AI Search
- Building AI agents with smolagents, a minimalist agent framework
- Crawl4AI: a self-hosted web crawler that gives agents web content
- Creating LLM tools from any Python SDK with langchain-autotools



