Preview: LLM frameworks can simplify development but they can also become an architecture of their own. Lattice explores a smaller, provider-neutral approach.

Introducing Lattice

Over the past few years, building applications with large language models has become considerably easier. Providers offer increasingly capable APIs, Python SDKs have matured, and an ecosystem of frameworks has emerged to handle everything from prompts and tools to agents, retrieval and complex workflows.

That progress has made a lot possible. It has also made me interested in a somewhat different question:

How little framework do we actually need?

That question is what led me to start building Lattice, a lightweight, provider-neutral Python framework for LLM-powered applications.

A Smaller Abstraction

Frameworks are useful because they give developers common abstractions. Instead of rewriting the same integration logic for every provider, an application can work with a consistent interface.

The tradeoff is that the framework itself can gradually become part of the application's architecture.

Lattice starts from the opposite direction. Its goal is not to provide every component an AI application might eventually need. It is to provide a small interoperability layer around the things many applications already need.

Models. Messages. Prompts. Structured responses. Tools. Streaming.

The underlying idea is that these primitives should remain understandable on their own.

A simple model call should look like a simple model call:

from lattice import Model

model = Model("openai:gpt-5")

response = model.invoke(
    "Explain DNS in two sentences."
)

print(response.text)

Changing providers should primarily mean changing the model:

model = Model("anthropic:claude-sonnet")

The application surrounding it should not need to be redesigned simply because the underlying provider changed.

Provider-Neutral, Not Provider-Blind

One of the principles behind Lattice is that abstraction should not require pretending every AI provider works the same way.

They don't.

Providers expose different models, capabilities, configuration options and response formats. Those differences are sometimes important.

Lattice therefore aims to normalize the parts that genuinely have a useful common representation while leaving provider-specific capabilities accessible.

That means a developer can work with a common response object, for example, while still accessing the original provider response when necessary.

The goal is portability without hiding the underlying technology.

Keeping Python as Python

Another principle is that Lattice should introduce as few framework-specific concepts as possible.

Tools are a good example.

A normal Python function can become available to a model:

from lattice import tool

@tool
def weather(city: str) -> str:
    """Return the current weather for a city."""
    return lookup_weather(city)

But it remains a Python function.

Lattice can describe it to a model, generate the necessary schema and normalize a resulting tool call. It does not need to take ownership of executing the function.

That distinction is intentional.

The first version of Lattice will not contain an automatic agent loop. It will not quietly execute tools, retry requests or decide which model should handle something.

Those decisions remain with the application.

What Lattice Is Not Trying to Be

Defining the boundaries has been one of the more important parts of designing the project.

Lattice 0.1 is not intended to be an all-in-one AI application platform.

There are deliberately no agents, graphs, memory systems, vector databases, retrieval layers or workflow engines in the core framework.

Some of those capabilities could eventually make sense elsewhere in the Lattice ecosystem. Others may never need to exist.

I would rather add an abstraction because real applications demonstrate a need for it than because an AI framework is expected to have one.

That also makes the initial project considerably easier to understand.

Designing Before Building

I have spent a fair amount of the early work on Lattice defining what the API should look like before expanding the implementation.

That included defining the object model and provider contract, writing a technical API specification, developing an implementation blueprint and pressure-testing the proposed API against realistic scenarios.

Those exercises resulted in several changes before they became implementation problems.

For example, tool execution was deliberately removed from the core abstraction. Provider capabilities were separated so integrations would not have to implement methods they cannot actually support. Error handling was refined, serialization rules were clarified and provider-specific escape hatches were preserved.

The result is intentionally less ambitious in scope, but much clearer about what it is supposed to do.

Where Lattice Is Now

The initial Lattice 0.1 core is now taking shape.

The core architecture includes the model interface, normalized messages and responses, prompts, structured output, tools, streaming, provider discovery and a normalized error model. It is designed so provider SDKs remain separate dependencies rather than becoming part of the core package.

The next major milestone is the first real provider integration.

That will be an important test.

A provider-neutral interface looks clean when tested against a simulated provider. The more meaningful question is whether that interface remains clean when it encounters the details and constraints of a real API.

That is where the design starts proving itself.

Building It in the Open

Lattice is still early, and I expect parts of it to evolve as the provider integrations and real-world examples develop.

But the basic philosophy is unlikely to change.

Keep the core small. Normalize what is genuinely common. Preserve access to what is different. Prefer ordinary Python over framework-specific machinery. Add abstractions when they solve demonstrated problems.

There are already excellent frameworks for building sophisticated AI systems. Lattice does not need to reproduce all of them.

There may still be room for something deliberately smaller.

I’ll be documenting that experiment as I build it.

Thanks for reading.

Lattice is still early, and I’ll be sharing more as the project develops, from provider integrations and API decisions to what I learn from building and testing it in the open.

If you’re interested in following along, you can explore the project on GitHub.

Until next time,
Brandon