LLMSlim + Pydantic AI
A Pydantic AI history processor that fits the message history into a token budget before every model request. Tool calls and returns stay paired.
llmslim_history_processor(max_tokens=...) returns a Pydantic AI history processor. Before every model request it fits the message history into a token budget: older text is compressed by LLMSlim first, and older tool exchanges and turns are removed only when that is not enough. Tool calls and their returns are always kept or removed together, so the provider never sees an unpaired call or return. It runs locally, with no model call. Python 3.10+.
1. Installation and setup
The extra installs pydantic-ai-slim 1.0 or newer and needs Python 3.10 or newer. llmslim[all] includes it.
pip install "llmslim[pydantic-ai]" # pydantic-ai-slim 1.0 or newer, Python 3.10+; needs llmslim 0.9.0 or newerPydantic AI 2.x (and recent 1.x releases) take the processor in the ProcessHistory capability; earlier 1.x releases take it in history_processors, which recent 1.x releases deprecate and 2.x removed:
from pydantic_ai import Agent
from pydantic_ai.capabilities import ProcessHistory
from llmslim.integrations.pydantic_ai import llmslim_history_processor
fit = llmslim_history_processor(max_tokens=8_000)
agent = Agent("openai:gpt-5", capabilities=[ProcessHistory(fit)])
# Pydantic AI 1.x without pydantic_ai.capabilities:
agent = Agent("openai:gpt-5", history_processors=[fit])2. Architecture & execution flow
3. Runnable example and its output
from pydantic_ai import Agent
from pydantic_ai.capabilities import ProcessHistory
from pydantic_ai.messages import ModelResponse, TextPart, ToolCallPart
from pydantic_ai.models.function import AgentInfo, FunctionModel
from llmslim.integrations.pydantic_ai import history_tokens, llmslim_history_processor
def run_tests(suite: str) -> str:
"""Run a test suite and return its output."""
lines = [f"{suite}/test_api.py::test_case_{i} PASSED" for i in range(300)]
lines.insert(200, f"{suite}/test_refund.py::test_rounding FAILED - 10.05 != 10.04")
return "\n".join(lines)
def run(capabilities):
sent = [] # history tokens in each request the model receives
def fake_model(messages, info: AgentInfo) -> ModelResponse:
"""Stands in for an LLM: runs three test suites, then answers."""
sent.append(history_tokens(messages))
if len(sent) <= 3:
return ModelResponse(parts=[ToolCallPart("run_tests", {"suite": f"suite{len(sent)}"})])
return ModelResponse(parts=[TextPart("test_rounding fails in every suite.")])
agent = Agent(
FunctionModel(fake_model),
system_prompt="You are a careful test runner.",
tools=[run_tests],
capabilities=capabilities,
)
result = agent.run_sync("Run the test suites and tell me what fails.")
return result, sent
_, plain = run([])
result, slim = run([ProcessHistory(llmslim_history_processor(max_tokens=1_000))])
print(result.output)
print("history tokens per request without LLMSlim:", plain)
print("history tokens per request with LLMSlim: ", slim)
print()
print(result.all_messages()[2].parts[0].content)Output (Python 3.12 with pydantic-ai-slim 2.55.0, and Python 3.10 with 1.107.7 passing the processor in history_processors; token counts from tiktoken):
test_rounding fails in every suite.
history tokens per request without LLMSlim: [17, 3946, 7875, 11804]
history tokens per request with LLMSlim: [17, 110, 203, 296]
suite1/test_api.py::test_case_0 PASSED
... [198 similar lines]
suite1/test_api.py::test_case_199 PASSED
suite1/test_refund.py::test_rounding FAILED - 10.05 != 10.04
suite1/test_api.py::test_case_200 PASSED
... [98 similar lines]
suite1/test_api.py::test_case_299 PASSEDAn agent runs three test suites, once without LLMSlim and once with it; FunctionModel stands in for the LLM, so the example runs offline. Every tool result stays in the history, paired with its call, and keeps the failure. The history ends far below the 1,000-token budget because runs of near-identical lines collapse into a count; this is a favourable case. Prose is compressed only as far as the budget requires.
4. Options
Keyword arguments, shown as name = default.