Five challenges faced when building my first agentic system
In 2025, everyone in the AI space is talking about agents and agentic systems. So as an AI engineer, I was more than happy to build my first agent this summer: an agent for clinical trial managers working at pharmaceutical companies to select the best set of clinical sites for a given clinical trial. This choice is critical to a trial’s success. They must pick about 10 sites from over 100 candidates. This is where my company, Inato, helps, making site selection faster and more inclusive.
I’ll skip the business details and instead use a simpler example here, restaurant selection which will be easier to digest (pun intended).
The overall system was as follows, the user would make a request:
give me the 3 best-rated Indian restaurants, not far from home (2km max), not more than 30 euros a meal.
The agent would then translate the user request to a set of constraints and objectives:
Constraints: 3 restaurants, cuisine=”INDIAN”, max_price=30, distance_max_km=2
Objective: maximize restaurant rating
An optimiser tool would be available to solve optimisation problem against a restaurant database.
The main tools to the agent were:
add_constraint()add_objective()solve_optimization()query_restaurant_dataset()
On paper the plan was clear and straightforward. But in reality building this was a journey full of challenges and surprises:
Challenge 1: Non-determinism
I love writing software tests because they give me confidence and help me move fast. However, writing tests for LLM-based systems is painful because of a core tension: the desire for deterministic tests vs. the inherently non-deterministic nature of LLMs.
LLMs are (sadly) non-deterministic. With one LLM call it is already hard to write robust tests that won’t be flaky because 1 out of 10 times the LLM would answer slightly differently. With agents it’s the same problem but far worse. Since your agent might actually do 10 LLM calls you have even more risk to get unpredictable behavior (some people say “creative output”).
async def test_agent_answer() -> None:
answer = await agent.run(”Find the two best Italian restaurants in Paris”)
assert “Super Pizzeria” in answer
How I handled it:
The first thing I did, was to run the test suite several time (around 30 times) before merging the PR to gain a bit of certainty that I didn’t introduce a flaky test. This assumes a fast test suite, mine ran in one minute using
pytest-xdistfor parallelization.Then on certain tests where I would ask the agent a question, instead of just checking the final answer, I validated the sequence of tool calls. If the agent suddenly used the wrong tools, I wanted to know.
While debugging and iterating, I also relied on observability tools (Langfuse) to inspect agent traces so you know what your agent is doing, and which tools it is calling.
Challenge 2: Response time
As I was building the agent over the summer, GPT-5 came out. Naïvely, I believed Sam Altman when he said GPT-5 was the next revolution, and I immediately used this new model with the settings reasoning = {”effort”: “minimal”} to keep things fast. A few hours later, an internal user gave me the feedback that the agent was taking 5 minutes to answer a simple question. Indeed if the agent is “thinking” between each tool call and that you have 10 tool calls then you will wait a long time to get the answer.
How I dealt with it:
I used a non-reasoning model: GPT-4.1.
Something that doesn’t actually fix agent slowness but can make the perceived duration smaller is streaming partial updates. For example, having the agent share its plan, the tools it’s using, or what it’s doing next.
Challenge 3: Agent failing to input the correct tool arguments
One of my tools, add_constraint(), took a lambda expression as input.
add_constraint(lambda x: x.cuisine_type == “INDIAN”)
Sometimes the agent forgot the lambda keyword and wrote:
add_constraint(x: x.cuisine_type == “INDIAN”)
This would make the tool call fail, which would cause the agent’s answer to fail.
The fix
I added a Pydantic validator to the input expression passed as argument to
add_constraint(), to validate the expression was indeed a valid lambda expression.
Challenge 4: A system prompt that grows out of proportion
As use cases and functionalities grew, the system prompt grew as well, reaching nearly 500 lines. It was complicated to maintain this large unstructured text file, and I feared removing one comma would break the whole thing.
To give some idea: half the prompt was conversation examples, and the rest was business context, data descriptions, and examples of lambda functions for constraints, etc.
The fix:
I stop describing tools in the prompt as their docstrings are passed automatically when tools are registered.
When the system prompt is becoming too big this might be a clue that the agent is doing too many things. So the next steps is to split the work in to several agents with their own reasonably sized system prompt.
Challenge 5: Choosing an agentic Python library
This one is not really a pitfall but more a leap of faith when starting the project, where we had to choose a agentic python lib. The choices were:
OpenAI agents: looked great, but I feared it would become too OpenAI-specific.
LangGraph: it felt too close to Langchain, with which we had negative experience.
SmolAgents: HuggingFace are the cool kids in the AI space, so this was a good option.
PydanticAI: the documentation was clean, and I was trusting the team that built Pydantic which is a key part of the Python ecosystem now.
In the end I went with PydanticAI, and I didn’t regret it: there were no surprise, clear abstractions, good documentation, great support on Slack.
Final thoughts
Building my first agentic system was full of learning, I hope those learnings can help you as well! If you’d like to learn more about what we’re doing at Inato, check out our recent paper on multimodal embedding for matching patients to clinical trials, as well as our work on structuring effective AI tech teams.

