Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Your First LLM Request: The First Answer with Ollama, C# and TypeScript
We get to know Ollama, look at models with ollama list and ollama ps, and send the first request to the /api/chat endpoint with curl. Then we repeat it in C# with Microsoft.Extensions.AI and OllamaSharp and in TypeScript with the ollama package, and use real output to read the token counts, the nanosecond durations and why the first request is slow.
The LLM Context Window: What the Model Remembers and What It Silently Cuts
LLM APIs are stateless; you send the chat history with every request and every turn grows the bill. Even though Gemma 3 supports 128K tokens, Ollama allocates a 4096-token window by default and cuts a 4498-token prompt down to 2051 tokens without any error. We set num_ctx in C# and TypeScript and try managing history with trimming and summarizing, using real output.
LLM Message Roles and the System Prompt: Giving the Bookstore Assistant an Identity
What are the system, user and assistant roles for, and how does Gemma 3's chat template carry them to the model? We write a system prompt for the Bookstore assistant, look at the parts of a good system prompt, and use real output to see how a forged assistant message from the client fools the model and how far separating user text from instructions helps as a first step against prompt injection.
Sampling in LLMs: Temperature, Top-p, Top-k and Seed
Why does the same question get different answers? We look at the model's candidate tokens with logprobs, calculate how temperature, top-k and top-p reshape that distribution, and see the promise and the limits of a seed, num_predict and stop sequences with real output from gemma3:4b.
Streaming LLM Responses: NDJSON, IAsyncEnumerable and Time to First Token
Streaming does not make the answer faster, it makes the wait shorter. We look at Ollama's NDJSON and OpenAI-compatible SSE streams with curl, read the answer with GetStreamingResponseAsync in C# and an async iterator in TypeScript, measure the time to first token, and cancel the stream in two SDKs to see how they differ.
Writing Prompts for LLMs (1): Clear Instructions, Delimiters and Few-shot
A prompt is a specification: context, task, input, format and constraints. We compare a bad and a good prompt on the same review with token counts, separate instructions from data with delimiters, and see with real gemma3:4b output how few-shot examples fix a classifier and how they mislead it.
Writing Prompts for LLMs (2): Step-by-Step Thinking and Reasoning Models
Does saying think step by step guarantee the right answer? We ask gemma3:4b a tricky order calculation in three ways, separate the thinking from the answer with qwen3:4b's think parameter, use ReasoningOptions in C# and measure what thinking costs in tokens and time.
Structured Output with LLMs: JSON Mode, JSON Schema and GetResponseAsync<T>
We see why parsing free text with a regex breaks, why JSON mode is not enough and how a JSON schema pins the format. We use Zod in TypeScript and GetResponseAsync<T> in C#, catch an answer that matches the schema but is wrong, and build two layers of validation.
LLM Tool Calling (1): The Model Doesn't Call Functions, It Asks for Calls
With tool calling the model runs no functions; it produces a call request and running it is up to your code. We send a raw tools request to Ollama's /api/chat endpoint with qwen3:4b, send the result back with the tool role and build the loop in TypeScript and in package-free C#.
LLM Tool Calling (2): Microsoft.Extensions.AI and Safe Tools
We hand the tool loop to UseFunctionInvocation and AIFunctionFactory, then get to the real question: should every call the model asks for run? Taking authorization from the session, approval for tools that change data, argument validation with Zod and arguments the model makes up, with real qwen3:4b output.
Embeddings and Similarity for LLMs: Searching Text by Meaning
What is an embedding and how do you compute cosine similarity? We produce 768-dimensional vectors with nomic-embed-text, write cosine similarity by hand in C# and TypeScript, build a small in-memory semantic search and compare it with keyword search. The negation trap and the limits with Turkish queries, with real scores.
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.