Skip to main content

LLM is Function

The central insight of SimpleLLMFunc: an LLM call should be indistinguishable from a Python function call.

The Problem with Existing Approaches

Most LLM frameworks introduce new abstractions between you and the model:
  • Chain frameworks make you think in terms of linked steps, each passing data to the next through a framework-defined protocol
  • Graph frameworks make you define nodes and edges, routing data through a visual DAG
  • Agent frameworks hide the LLM behind an “agent” abstraction that manages its own state
All of these put a layer of framework-specific concepts between your intent and the model. You learn the framework’s vocabulary instead of expressing your task directly.

The Function Model

In SimpleLLMFunc, an LLM call IS a function call:
This is a complete, runnable program. The function:
  • Has a nameextract_entities
  • Has typed parameterstext: str
  • Has a typed return valuelist[Entity]
  • Has a docstring that describes the behavior — this IS the prompt
  • Is awaitableawait extract_entities("...")
  • Is composable — call it from other functions, pass it around, test it
There is no “chain”, no “node”, no “agent object”. There’s a function.

What You Gain

Type Safety at the Boundary

The return type annotation is a contract. The framework ensures the LLM output is parsed into your declared type — or raises a clear error. No manual JSON parsing, no “sometimes the model returns a string instead of an object”.

Composability

Functions compose naturally. Build complex pipelines with plain Python:
No framework DSL needed. No chain definitions. Just functions calling functions.

Testability

Mock it like any other function:

IDE Support

Your IDE already knows how to work with async functions, type annotations, and docstrings. Autocomplete, jump-to-definition, inline docs — all free.

The Extension: LLM as Agent

@llm_chat extends the function model to multi-turn agents. The agent is still just a function — it takes input, returns output. The difference is that it can use tools and maintain conversation state:
Same function interface. Same composability. But now the function can reason across multiple steps internally (the ReAct loop), use tools, and stream results.

The Key Insight

The framework’s job is to make the gap between “I want to call an LLM” and “I called a Python function” as close to zero as possible. Everything else — context management, tool execution, type parsing — is implementation detail that you can ignore until you need to customize it. When you DO need to customize, the Context Model gives you full control. But the default is: just write a function.