Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Persisting Chat History: Stateless APIs, ConversationId, EF Core and Chat Reducers
With a stateless provider your code keeps the chat history: we turn ChatMessage instances, including tool call and tool result contents, into JSON with AIJsonUtilities and store them in EF Core per user and conversation. We put the ownership check into the query, also support stateful providers through ConversationId, and compare the real output of MessageCountingChatReducer and SummarizingChatReducer.
LLM Streaming in ASP.NET Core: Minimal API, IAsyncEnumerable and Server-Sent Events
The reliable way to stream an LLM answer to the browser is .NET 10's TypedResults.ServerSentEvents: delta, thinking, tool, usage, done and error events, a heartbeat during silent waits and a CancellationToken carried all the way to the model. With real measurements we show the limits of a plain IAsyncEnumerable, that the model really stops when the user closes the tab, and that an endpoint forgetting the token wastes 1024 tokens.
Wiring IChatClient into ASP.NET Core: AddChatClient, Keyed Clients, Options and Secrets
We register IChatClient in ASP.NET Core as a singleton with AddKeyedChatClient, use keyed clients for a "fast" and a "smart" model, and choose provider and model through options validated at startup. With real logs we show the standard resilience handler's 10 s/30 s defaults regenerating an LLM call three times and ending in a 500, then tune timeouts and retries for LLMs.
The IChatClient Pipeline: ChatClientBuilder, Middleware Order and Your Own DelegatingChatClient
In ChatClientBuilder the first middleware added is outermost, and order changes behavior: with the cache outside function invocation the tool never runs, inside it the tool reads fresh data on every request. We write two DelegatingChatClient classes for Bookstore, a token budget and personal data masking, and show with a real run how forgetting the streaming override silently leaks data.
The .NET AI Stack: Where Microsoft.Extensions.AI, VectorData, Evaluation and Agent Framework Fit
An application takes Microsoft.Extensions.AI plus a provider package, while a library depends only on Abstractions; VectorData, DataIngestion, Evaluation and Agent Framework sit on top of or next to that IChatClient layer. We map the packages, run the aichatweb template against a local Ollama and fix three real errors that appear at the layer boundaries.
The Evolution of LLM APIs: From Completions to Tool Calling, Structured Outputs and MCP
The evolution of LLM APIs with verified dates, from the first text-continuing API to the chat format, from function calling to JSON mode and structured outputs, from reasoning parameters to the Model Context Protocol and agents. We see which example in the series each step maps to, and at the end of the series we build a bridge to the next AI series.
LLMs from 2023 to Today: Open-Weight Models, Long Context and Reasoning
A summary of the LLM world from GPT-4 to today with verified dates: the rise of open-weight models with Llama, Mistral, Qwen and Gemma, the context window growing from 2,048 tokens to a million, models that read images, and the reasoning models that came with o1 and DeepSeek-R1. We also discuss the difference between open-weight and open source, and why we give no model ranking.
From the Transformer to ChatGPT: The Birth of LLMs (2017–2022)
The steps that made today's LLMs possible, from the 2017 "Attention Is All You Need" paper to the launch of ChatGPT: the Transformer, pre-training with GPT-1 and BERT, GPT-2's staged release, scaling laws and GPT-3, InstructGPT and RLHF. We see which concept in the series each step is the root of and what it changed for developers.
Provider-Independent LLM Code: IChatClient, the OpenAI-Compatible /v1 and DI
With the IChatClient abstraction we run the same code through Ollama's own API and its OpenAI-compatible /v1 endpoint, switching providers through configuration and environment variables. We wire it into an ASP.NET Core minimal API with DI, keep API keys in user secrets and environment variables, build the same idea in TypeScript with the openai package, and look at the limits of the abstraction.
LLM Cost, Latency and Model Choice: From Tokens to the Bill
We turn token counts into cost per request and per month, and split latency into load, input processing and output generation. We compare gemma3:4b and 12b with real tokens/s measurements, see what Q4_K_M means by looking at tensor types, cache responses with Microsoft.Extensions.AI, and discuss the choice between local and cloud.
LLM Hallucination and Reliability: Cite, Verify, Measure
Asked for the author of a novel that doesn't exist, gemma3:4b confidently invented a name and a year. We look at why hallucinations happen, force the answer to cite sources with structured output, verify those sources in code and compare three approaches with numbers using a ten-question golden set.
LLMs and RAG: Answering with Data the Model Has Never Seen
Asked about the bookstore's return window, gemma3:4b confidently said 30 days; the right answer is 21. We split the policies into chunks, index them with nomic-embed-text, put the closest chunks into the prompt and get the model to say it doesn't know what isn't in the context. A minimal in-memory RAG in C# and its limits.