OpenAI, Anthropic, and others have built specialized APIs for tool calling. We've always thought that it's a weird abstraction to be provided by the LLM provider, given that things become much easier if you can treat the LLM as a text-in text-out function.
It's good that structured outputs are a thing, and function calling is trivial to implement if you want to do it yourself and have full control over the control flow. This is especially pertinent when building agentic workflows that need some kind of advanced control flow.
This works, but it's not very robust. For example, if the LLM returns a function name that doesn't exist, it will throw an error. Or, the LLM might return a function name that exists, but with invalid arguments.
This is a bit better, but given that the LLM will be incorrect non-zero percent of the time, our code will fail proportionally. One way out of this is to add a retry mechanism, and help the LLM out by providing some context about what went wrong.