I’m still exploring how to use LLMs to build agents. In the first post, I described some of the motivation and approach. I started working on two agents:
- web_agent: A basic agent that completes a web-based task using browser automation actions.
- tool_agent: A basic agent that completes a task using a set of supplied tools or methods.
I’ve been focused more on tool_agent, trying to expand the types of tools I can provide. I had a suggestion to add some read/write type tools and scenarios, so I decided to try that out.
Taking Action
an agent is characterized by the environment it operates in and the set of actions it can perform. — Chip Huyen Agents
I want to create a more realistic set of actions for the agent to use. Scheduling appointments seemed like a straightforward scenario with some interesting possible flows. I added a few tool methods and a mocked appointment data store.
get_specialties()
: Retrieves a list of available specialties for scheduling appointments.get_available_appointments(specialty)
: Retrieves available appointments for a given specialty.book_appointment(booking_data)
: Books an appointment based on provided booking data. The agent can pass in an appointment ID or a combination of a date and time.get_my_appointments()
: Retrieves the list of appointments booked by the user.cancel_appointment(appointment_id)
: Cancels a booked appointment based on the provided appointment ID.
Checkout the tool_agent.py
code for more detail on the way the tools work and the way the tools are added to the system prompt.
Example Flows
I was pleased with how well this rudimentary system worked. Without many tweaks (some on that below), I was able to get some useful exchanges.
You: what kind of appts can I book?
Action raw: {"type": "call_function", "tool": "get_specialties"}
Function result: ["dentist", "vision", "hair"]
Action raw: {"type": "output", "value": "You can book appointments for dentist, vision, and hair."}
Agent: You can book appointments for dentist, vision, and hair.
The LLM did a good job of taking some ambiguity (“appts” and “book”) to figure out that it needed to use get_specialties
to answer the question.
You: I want to get a haircut
Action raw: {"type": "call_function", "tool": "get_specialties"}
Function result: ["vision", "dentist", "hair"]
Action raw: {"type": "call_function", "tool": "get_available_appointments", "param": "hair"}
Function result: [... list of appointments ...]
Action raw: {"type": "output", "value": "... output ..."}
Agent: Okay, here are the available hair appointments:
* ID: 10, Date: 2025-03-01, Time: 10:30 AM
* ID: 11, Date: 2025-03-02, Time: 2:00 PM
* ID: 12, Date: 2025-03-08, Time: 11:00 AM
* ID: 13, Date: 2025-03-08, Time: 3:00 PM
In this exchange, the agent was able to figure out that it needed to display “hair” appointments. Interesting: the agent came up with the output display format on its own. When I saw the agent using this format, I added the ability to reference appointments by ID in the tool methods.
You: show me my appointments
Action raw: {"type": "call_function", "tool": "get_my_appointments"}
Function result: [{"id": "3", "date": "2025-03-02", "time": "11:00 AM", "specialty": "dentist"}]
Action raw: {"type": "output", "value": "... output ..."}
Agent: Here are your booked appointments:
* ID: 3, Date: 2025-03-02, Time: 11:00 AM, Specialty: dentist
You: cancel the dentist appointment
Action raw: {"type": "call_function", "tool": "cancel_appointment", "param": "3"}
Function result: {"status": "success", "message": "Appointment canceled successfully."}
Action raw: {"type": "output", "value": "... output ..."}
Agent: Your appointment has been canceled successfully.
I thought I could easily confuse the agent by trying to cancel an appointment by the type (specialty) of the appointment, but the agent used the correct ID for the appointment when cancelling via the tool method.
Learning
At one point, the agent started returning invalid JSON. I assumed I need to be more strict in my system prompt, but I discovered an error. Since I was surrounding my Python strings with double quotes, the inner quotes were single quotes. My JSON examples were using single quotes — which is illegal in JSON. The LLM was relying on those examples, so I was causing the LLM to emit invalid JSON.
As I was looking at the code for similar issues, I noticed I was sending Python formatted object output back into the conversation. The LLM was handling it well enough but I decided to change the output to be legal JSON output:
- response.conversation.prompt(f"Function result: {function_result}")
+ function_result_json = json.dumps(function_result)
+ response = conversation.prompt(f"Function result: {function_result_json}")
What’s Next?
Most of Chip Huyen’s post on Agents talks about “planning”, but I have not really adding any planning specific code to tool_agent
yet. Right now, I am getting by with whatever amount of planning the LLM can create itself.
I want to learn more about planning, and how to add a little code to help the agent deal with even more complicated scenarios.