{"id":35270,"date":"2026-11-01T23:13:16","date_gmt":"2026-11-01T22:13:16","guid":{"rendered":"https:\/\/www.angulararchitects.io\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/"},"modified":"2026-11-02T14:51:36","modified_gmt":"2026-11-02T13:51:36","slug":"optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/","title":{"rendered":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability"},"content":{"rendered":"<p>Local models are gaining ground. A model running on your own hardware keeps sensitive data on your premises, removes the dependency on a single provider, and makes costs predictable. For many companies, especially in regulated industries, this kind of sovereign AI is a requirement.<\/p>\n<p>However, the models you can host yourself usually trail the frontier models. One reason is that the strongest models are not available as open weights.<\/p>\n<p>In this article, I show patterns that make agentic applications fast and reliable on such models. I developed them in an application that I built to demonstrate a wide range of agentic use cases. The following dashboard shows one part of this application:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/dashboard.png\" alt=\"Dashboard built from a natural-language request: a boarding pass, the booked flights with weather and delay, a flight search, rental cars and hotels in Hamburg, and the weather at the destinations.\" \/><\/p>\n<p>I moved this application to Gemma 4, an open-weight model by Google. While the agents run on Mastra and the frontend uses CopilotKit for Angular, AG-UI, and A2UI, the patterns apply regardless of framework and model. Many of them also make frontier models faster and cheaper.<\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/angular-architects\/flights42\/tree\/AGU-gemma\">Source code<\/a> (branch <code>AGU-gemma<\/code>)<\/p>\n<h2>Model Settings for Local Models<\/h2>\n<p>The first measures concern how the agents call the model: thinking, the temperature, the number of steps, and timeouts. They only touch the configuration of the AI server and leave tools, prompts, and the UI unchanged.<\/p>\n<h3>Turning Thinking Off<\/h3>\n<p>Like many current models, Gemma 4 can think before it answers. In a reasoning step that the user does not see, it weighs the request, the rules of the system prompt, and possible next steps. This often improves complex answers but costs time and computing power.<\/p>\n<p>In my measurements, thinking made Gemma about three times slower. A typical chat turn, such as a question about booked flights, took 2.5 seconds on median without thinking and 7.8 seconds with it. The number of output tokens grew by a factor of three to five. At the same time, my evals showed no gain in reliability that would justify this latency. For an interactive application, such a delay is not an option.<\/p>\n<p>Agentic applications can often do without thinking because much of their work consists of choosing a tool and filling in its parameters. In my application, this worked well because most user requests are close to a tool call: <em>Did I book Paris?<\/em> translates directly into a call of <code>findBookedFlights<\/code> with the destination Paris. For requests that need more planning, such as <em>Maximize my time in Rome<\/em>, I added tools tailored to them, as described below. The remaining reasoning moves into the tools, the code, and the agent loop. When the model makes a mistake, the tool result tells it, and the model corrects itself in the next step. Tasks that require weighing many constraints or planning several steps ahead can still benefit from thinking.<\/p>\n<p>Without thinking, the model decides on its tool call in a single pass. It still processes all instructions, but no explicit reasoning phase weighs the rules of the system prompt or checks the result. In my experiments, the text closest to the decision therefore tended to win. In one case, I had to move a general rule from the system prompt into the description of a tool so that Gemma would follow it.<\/p>\n<p>For reproducible experiments without managing GPU infrastructure, I use Gemma 4 through Cloudflare Workers AI. Since Gemma 4 is an open-weight model, the same architectural patterns also apply when deploying it on your own infrastructure. On Workers AI, every request has to set <code>chat_template_kwargs: { enable_thinking: false }<\/code> to turn thinking off. The application does this in the <a href=\"https:\/\/github.com\/angular-architects\/flights42\/blob\/AGU-gemma\/ai-server\/src\/mastra\/config.ts\">configuration of the AI server<\/a>.<\/p>\n<p><div style=\"\nmargin: 8px 0;\npadding: 22px;\nborder: 1px solid #e5e7eb;\nborder-radius: 14px;\nbackground: #f8fafc;\n\"><\/p>\n<h3 style=\"margin-top:0\">Agentic UI with Angular<\/h3>\n<p>If you don\u2019t just want to connect an agent but embed Agentic UI into a scalable architecture:<br \/>\nIn my book Agentic UI with Angular, I cover the underlying patterns and trade-offs in depth.<\/p>\n<p><a href=\"https:\/\/agentic-angular.com\/\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/04\/cover.png\" width=\"400\" alt=\"Cover of the eBook Agentic UI with Angular\" style=\"cursor:pointer !important\"><\/a><\/p>\n<p><a style=\"cursor:pointer !important\" href=\"https:\/\/agentic-angular.com\/\">More about the eBook \u2192<\/a>\n<\/div>\n<\/p>\n<h3>Allowing More Steps and Cutting Off Hanging Requests<\/h3>\n<p>Without thinking, the model corrects mistakes in the next step instead of in its reasoning. That is why it needs more steps. Mastra stops after five model calls unless configured otherwise. A request that changes a travel plan, for example, needs a search, the change itself, a check of the result, and a closing message. This leaves a single step for corrections. Whenever Gemma needed more, the closing message was cut off: the plan changed, but the user got no answer. The application therefore allows eight steps. For tools that change data, retries require that a failed call has no side effects: the tool validates its input before it changes anything, or a repeated call does no harm.<\/p>\n<p>The second problem is long-running requests. During my measurements, single steps sometimes ran for minutes without producing text or a tool call, and one request even hung for almost an hour before it failed. A cap on the output tokens per step and a timeout per request prevent this.<\/p>\n<p>The <a href=\"https:\/\/github.com\/angular-architects\/flights42\/blob\/AGU-gemma\/ai-server\/src\/mastra\/config.ts\">configuration of the AI server<\/a> takes care of both problems and of thinking. On the one hand, it sets up model options that allow eight steps, cap the output tokens per step, and set the temperature to 0. At this temperature, the model favors the most likely next token, which makes its tool calls more reproducible. On the other hand, it passes a fetch function to the model provider. This function turns thinking off and adds a timeout to every request. This results in the constants <code>model<\/code> and <code>defaultOptions<\/code>, which the agents use as follows:<\/p>\n<pre><code class=\"language-typescript\">\/\/ ai-server\/src\/mastra\/agents\/dashboard-agent.ts\nimport { Agent } from &#039;@mastra\/core\/agent&#039;;\n\nimport { defaultOptions, model } from &#039;..\/config.js&#039;;\nimport { renderDashboard } from &#039;..\/tools\/render-dashboard.js&#039;;\nimport { dashboardAgentPrompt } from &#039;.\/dashboard-agent.prompt.js&#039;;\n\nexport const dashboardAgent = new Agent({\n  id: &#039;dashboardAgent&#039;,\n  name: &#039;Flight42 Dashboard Composer&#039;,\n  instructions: dashboardAgentPrompt,\n  model,\n  tools: { renderDashboard },\n  defaultOptions: { ...defaultOptions, maxSteps: 1 },\n});<\/code><\/pre>\n<p>The dashboard agent overrides <code>maxSteps<\/code> because it makes exactly one tool call per turn.<\/p>\n<h2>Measuring Success with Evals<\/h2>\n<p>I measured the effect of every change in this article right before and right after applying it. Without these numbers, it is hard to tell a real improvement from luck. Several fixes that looked obvious had no effect or even made things worse.<\/p>\n<p>For this, the repository contains an eval that runs as an Angular unit test in Node.js. It creates the real agent stores with their frontend tools and sends typical prompts through CopilotKit to the running agents. This way, the eval uses the same tool descriptions and stores as the application. Its checks compare what the user sees with reference data. For every turn, the eval also reports the duration, the input and output tokens, and failed tool calls.<\/p>\n<p>At temperature 0, three runs per prompt show the direction of an effect rather than exact rates. They suffice for clear differences in latency and tokens. For reliability, they show a trend, not proof. Even at this temperature, the answers are not fully deterministic, because inference servers process requests in batches and tiny rounding differences on the GPU can tip the choice of a token. It also pays off to read the traces before blaming the model, because a failure can come from a bug in the eval itself.<\/p>\n<p>You find the <a href=\"https:\/\/github.com\/angular-architects\/flights42\/tree\/AGU-gemma\/eval\">eval in the repository<\/a>.<\/p>\n<h2>Rendering Tool Results Instead of Copying Data<\/h2>\n<p>Several measures that made the application faster share one idea: the model should not pass data on. The UI takes the data from the tool results or from the agent state.<\/p>\n<h3>Tool Call Renderers Instead of Component Tools<\/h3>\n<p>Agentic UI frameworks such as CopilotKit for Angular let an agent render components in the chat. A common approach registers each component as a tool the model can call. The model first calls a tool that loads the data and then calls the component tool with this data as arguments. In the following figure, the tool <code>findBookedFlights<\/code> loads the booked flights, and the model passes each of them on to a <code>flightWidget<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/component-flight-widget.png\" alt=\"With a component, the model decides: it calls findBookedFlights and then passes each booked flight on to the flightWidget.\" style=\"display:block; margin:0 auto; max-width:600px\"><\/p>\n<p>For a compact model, this has three drawbacks. First, it has to decide to call the component at all. Second, it has to copy every value character by character into the arguments, including ids and full ISO dates. Third, every copied value costs output tokens, the slowest part of a model response.<\/p>\n<p>A tool call renderer reverses the direction. The framework renders the result of a tool call with a component, and the model only writes the accompanying text:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/toolcallrenderer-flight-widget.png\" alt=\"With a tool call renderer, the framework decides: the flightWidget renders the result of findBookedFlights.\" style=\"display:block; margin:0 auto; max-width:600px\"><\/p>\n<p>In the chat, this looks as follows:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/flight-search.png\" alt=\"Chat: for the question &quot;Did I book Paris&quot;, the tool call renderer shows the booked flight from Graz to Paris with a check-in button. The model only adds the sentence &quot;Yes, you have a booked flight from Graz to Paris (Flight #516).&quot;\" \/><\/p>\n<p>The card with the check-in button comes from the tool result, and the model only confirms the booking in one sentence. Since the renderer shows everything the tool returns, the tool takes optional filters for the route and the flight id. The model only has to pick the right filter. The component looks like this:<\/p>\n<pre><code class=\"language-typescript\">\/\/ src\/app\/domains\/ticketing\/ui\/flight-widget.ts\nimport { [...] } from &#039;@angular\/core&#039;;\nimport { RouterLink } from &#039;@angular\/router&#039;;\nimport { type AngularToolCall, type ToolRenderer } from &#039;@copilotkit\/angular&#039;;\nimport { z } from &#039;zod&#039;;\n\nimport { FlightCard } from &#039;.\/flight-card\/flight-card&#039;;\n\nconst flightSchema = z.object({\n  id: z.number(),\n  from: z.string(),\n  to: z.string(),\n  date: z.string(),\n  delay: z.number(),\n});\n\nconst bookedFlightsSchema = z.object({\n  flights: z.array(flightSchema),\n});\n\n[...]\n\n@Component({\n  selector: &#039;app-flight-widget&#039;,\n  imports: [FlightCard, RouterLink],\n  template: `\n    @for (flight of flights(); track flight.id) {\n      &lt;app-flight-card [item]=&quot;flight&quot; [readonly]=&quot;true&quot;&gt;\n        &lt;button\n          class=&quot;btn btn-default&quot;\n          [routerLink]=&quot;[&#039;\/checkin&#039;, { ticketId: flight.id }]&quot;&gt;\n          Check in\n        &lt;\/button&gt;\n      &lt;\/app-flight-card&gt;\n    }\n  `,\n  [...]\n})\nexport class FlightWidget implements ToolRenderer&lt;FindBookedFlightsArgs&gt; {\n  readonly toolCall = input.required&lt;AngularToolCall&lt;FindBookedFlightsArgs&gt;&gt;();\n\n  protected readonly flights = computed(() =&gt;\n    parseFlights(this.toolCall().result),\n  );\n}\n\nfunction parseFlights(result: string | undefined) {\n  [...]\n}<\/code><\/pre>\n<p>The types <code>ToolRenderer<\/code> and <code>AngularToolCall<\/code> come from @copilotkit\/angular, the package of CopilotKit for Angular. The component receives the tool call, including its result, via the input <code>toolCall<\/code>. The function <code>parseFlights<\/code> validates the result with the Zod schema <code>bookedFlightsSchema<\/code>. The function <code>registerRenderToolCall<\/code> provided by CopilotKit for Angular registers the component as a renderer for the tool <code>findBookedFlightsTool<\/code>:<\/p>\n<pre><code class=\"language-typescript\">\/\/ src\/app\/domains\/ticketing\/ai\/ticketing-agent-store.ts\nimport { registerRenderToolCall } from &#039;@copilotkit\/angular&#039;;\nimport { z } from &#039;zod&#039;;\n\nimport { FlightWidget } from &#039;..\/ui\/flight-widget&#039;;\nimport { TICKETING_AGENT_ID } from &#039;.\/agent-ids&#039;;\n\nexport function injectTicketingAgentStore() {\n  registerRenderToolCall({\n    name: &#039;findBookedFlightsTool&#039;,\n    args: z.object({\n      from: z.string().optional(),\n      to: z.string().optional(),\n      flightId: z.number().optional(),\n    }),\n    component: FlightWidget,\n    agentId: TICKETING_AGENT_ID,\n  });\n  [...]\n}<\/code><\/pre>\n<p>Like the other register functions of CopilotKit for Angular, <code>registerRenderToolCall<\/code> needs an injection context. The property <code>agentId<\/code> restricts the renderer to one agent. In the demo, the helper function <a href=\"https:\/\/github.com\/angular-architects\/flights42\/blob\/AGU-gemma\/src\/app\/domains\/shared\/util-copilotkit\/init-agent-store.ts\"><code>initAgentStore<\/code><\/a> abstracts this call.<\/p>\n<p>For the question <em>Did I book Paris?<\/em>, this change made the answer about a third faster and roughly halved the output tokens. More important, the model no longer needs the flight data at all. This enables the minimal tool results I describe below.<\/p>\n<h3>A2UI with a Narrow DSL Instead of Full Markup<\/h3>\n<p>The dashboard shown at the beginning is the most complex generated UI in the application. It uses A2UI, a protocol for user interfaces that an agent generates. Users describe the tiles they want in natural language, and the agent builds the dashboard.<\/p>\n<p>Its migration to Gemma 4 was easy because the dashboard agent already followed the principle of the previous section. As described in my <a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/how-i-made-my-a2ui-dashboard-300-times-faster\/\">article on A2UI with a DSL<\/a>, the model does not write A2UI. Instead, it makes a single <code>renderDashboard<\/code> call with a list of tiles such as <code>{ &quot;type&quot;: &quot;delayShareChart&quot;, &quot;from&quot;: &quot;Graz&quot;, &quot;to&quot;: &quot;Hamburg&quot; }<\/code>. The server translates this spec into A2UI and loads the data for the tiles.<\/p>\n<p>I introduced this DSL for speed and cost. It also helps compact models: a few tiles with flat arguments are much easier to write than component trees and the data they show. The prompt of the dashboard agent is correspondingly short:<\/p>\n<pre><code class=\"language-typescript\">\/\/ ai-server\/src\/mastra\/agents\/dashboard-agent.prompt.ts\nexport const dashboardAgentPrompt = `\nYou produce a compact spec for the Flight42 dashboard.\n\nEach turn ends with **exactly one** renderDashboard tool call. Its\ninput is { tiles: Tile[] }. The server compiles this spec into the\nfinal UI \u2014 you never produce A2UI directly.\n\nTiles render in the order you list them. Use proper city names\n(e.g. &quot;Graz&quot;, &quot;Hamburg&quot;) \u2014 never airport codes.\n\nTile reference:\n\n- { &quot;type&quot;: &quot;flightsTable&quot;, &quot;from&quot;: string, &quot;to&quot;: string,\n       &quot;maxRows&quot;?: number }\n  (default: 30)\n[...]\n- { &quot;type&quot;: &quot;delayShareChart&quot;, &quot;from&quot;: string, &quot;to&quot;: string,\n       &quot;chartType&quot;?: &quot;pie&quot; | &quot;bar&quot; }\n  (default: &quot;pie&quot;)\n[...]\n- { &quot;type&quot;: &quot;bookedFlightsList&quot;,\n       &quot;showCheckInButton&quot;?: boolean,\n       &quot;showWeather&quot;?: boolean,\n       &quot;maxRows&quot;?: number }\n  (defaults: showCheckInButton true, showWeather true, maxRows no limit)\n[...]\n\nHonour user limits (&quot;show only 5 \u2026&quot;, &quot;top 3 \u2026&quot;) by emitting the matching\nmaxRows \/ maxItems \/ count. [...]\n\nButton clicks on the dashboard arrive as a log_a2ui_event tool call\n[...]\n`;<\/code><\/pre>\n<p>The entire prompt has about 2,300 characters, a few hundred tokens. My original approach let the model write A2UI based on detailed examples and ended up with over 43,000 input tokens per dashboard.<\/p>\n<p>On top of that, the server caches the DSL:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/a2ui-optimization.png\" alt=\"The LLM generates the DSL. The server caches it and derives the A2UI structure and the A2UI data from it, which together form the A2UI messages for the client.\" \/><\/p>\n<p>A2UI separates the structure of a surface from its data. The message <code>updateComponents<\/code> describes the components, and <code>updateDataModel<\/code> provides the values they bind to. As long as the request stays the same, the structure stays the same, too. The server therefore uses a hash of the request as the key for the cached DSL. On a cache hit, it derives the structure from the DSL without calling the model and only loads fresh data. In production, the key should also contain a version of the DSL and the tool definitions, as well as everything else the structure depends on, such as the permissions of the user.<\/p>\n<p>However, the cache only saves a few seconds. The big performance boost comes from reducing the task to a narrow DSL. The DSL alone brought the dashboard from about 40 seconds down to a few seconds and cut its tokens by a factor of 30.<\/p>\n<h2>Tool Design for Compact Models<\/h2>\n<p>With a frontier model, a generic set of tools is often enough. With a compact model, the design of each tool decides whether a request succeeds.<\/p>\n<h3>Let the Model Ask for Minimal or Full Results<\/h3>\n<p>Once the UI renders the tool results, the model rarely needs the data. Still, every value in a tool result costs input tokens. Sometimes the model does need the values, though. For <em>Did I book Paris?<\/em>, it only has to know that the user saw a card. For <em>When does my flight to Paris leave?<\/em>, it needs the date.<\/p>\n<p>Hence, <code>findBookedFlights<\/code> has a required parameter <code>detail<\/code>. With <code>&quot;minimal&quot;<\/code>, the model only gets a short note. With <code>&quot;full&quot;<\/code>, it gets all fields. The parameter is required because Gemma tends to omit optional fields. In another project, Gemma left out an optional boolean for the same decision in every run, while a required enum worked in all my test runs.<\/p>\n<p>Mastra supports this with <code>toModelOutput<\/code>. The tool returns the full result, which goes to the UI, and <code>toModelOutput<\/code> decides what the model sees. In the next step of the same run, the model therefore gets the note instead of the flight data:<\/p>\n<pre><code class=\"language-typescript\">\/\/ ai-server\/src\/mastra\/tools\/find-booked-flights.ts\nimport { createTool, isValidationError } from &#039;@mastra\/core\/tools&#039;;\nimport { z } from &#039;zod&#039;;\n\nimport {\n  type BookedFlight,\n  getBookedFlights,\n} from &#039;..\/data\/booked-flights-store.js&#039;;\n[...]\n\nconst detailSchema = z.enum([&#039;minimal&#039;, &#039;full&#039;]).describe(`\n  &quot;minimal&quot; if the user only asks whether a flight is booked or wants to see\n  flights: you get only the id and route of each flight. &quot;full&quot; if you must\n  answer a question about the flights, e.g. their date or delay: you get all\n  fields.\n`);\n\n[...]\n\nexport const findBookedFlightsTool = createTool({\n  id: &#039;findBookedFlights&#039;,\n  description: `\n    Shows the flights that are already booked by the current passenger as cards\n    to the user and returns them. Calling it is what displays the cards, in the\n    current answer only. [...]\n  `,\n  inputSchema: z.object({\n    from: z.string().optional().describe(&#039;Departure city, e.g. &quot;Graz&quot;. [...]&#039;),\n    to: z.string().optional().describe(&#039;Destination city, e.g. &quot;Paris&quot;. [...]&#039;),\n    flightId: z.coerce.number().optional().describe(&#039;Flight id, e.g. 516. [...]&#039;),\n    detail: detailSchema,\n  }),\n  outputSchema,\n  execute: async ({ from, to, flightId, detail }) =&gt; {\n    const flights = await getBookedFlights();\n    return {\n      flights: flights.filter([...]),\n      detail,\n    };\n  },\n  toModelOutput: (output) =&gt; {\n    if (isValidationError(output)) {\n      return { type: &#039;json&#039;, value: output };\n    }\n    if (output.detail === &#039;full&#039;) {\n      return { type: &#039;json&#039;, value: { flights: output.flights } };\n    }\n    return { type: &#039;text&#039;, value: toShownNote(output.flights) };\n  },\n});\n\nfunction toShownNote(flights: BookedFlight[]): string {\n  [...]\n  return (\n    <code>These booked flights were shown to the user as cards in this answer: ${routes}. <\/code> +\n    &#039;Their dates and delays are not part of this result: never mention them. &#039; +\n    &#039;The cards are not visible in later answers: call findBookedFlightsTool again when asked.&#039;\n  );\n}<\/code><\/pre>\n<p>The function <code>toModelOutput<\/code> projects the tool result onto what the model is supposed to see. It only receives the result and not the arguments of the tool call. That is why <code>execute<\/code> passes the parameter <code>detail<\/code> through to the result: the projection needs it to decide between the short note and the full data.<\/p>\n<p>The note tells the model which flights the user saw as cards, that their dates and delays are not part of the result, and that the cards are not visible in later answers. The last sentence and the tool description make clear that calling the tool is what displays the cards. Without such a hint, Gemma answered repeated requests in my other project with text, as the result was already in the history.<\/p>\n<p>Left to decide, Gemma consistently chose <code>&quot;minimal&quot;<\/code> for <em>Did I book \u2026?<\/em> and <code>&quot;full&quot;<\/code> for <em>When \u2026?<\/em> and <em>Is \u2026 delayed?<\/em>. When I forced minimal results, it did not invent a date but pointed to the card.<\/p>\n<h3>Tailoring Tools to Use Cases<\/h3>\n<p>The application also contains a travel planner. It creates a trip with flights and hotels, and users refine it in a chat. Up to now, the travel planner offered generic tools for changing a plan, such as <code>replaceFlightInPlan<\/code>. For a request like <em>Maximize my time in Rome<\/em>, the model had to work out what this means. It had to read the plan, search for an earlier flight into Rome and a later one out of it, pick the alternatives, and replace both flights. A model needs reasoning to get from such a goal to concrete checks of flight availability and a choice among alternatives:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/travel-planner-refinement.png\" alt=\"Travel planner: the user asks for a posh hotel in Paris and to maximize the time in Rome. The agent calls findHotels, maximizeTime, getTravelPlan, addHotelToPlan, and getTravelPlan again and confirms the 5-star Grand Palace Paris and an earlier flight from Paris to Rome.\" \/><\/p>\n<p>Gemma does not handle this well, all the more so with thinking turned off. Hence, the travel planner now provides the tools <code>maximizeTime<\/code> and <code>minimizeTime<\/code>. They take a city and replace the flight into it with the earliest or latest one of the same day. They do the same for the flight out of the city:<\/p>\n<pre><code class=\"language-typescript\">\/\/ ai-server\/src\/mastra\/tools\/plan\/time-in-city.tool.ts\nimport { createTool } from &#039;@mastra\/core\/tools&#039;;\nimport { z } from &#039;zod&#039;;\n\nimport { canonicalCity } from &#039;..\/city-aliases.js&#039;;\nimport { searchFlights } from &#039;..\/search-flights.js&#039;;\nimport type { PlanFlight } from &#039;.\/plan-schemas.js&#039;;\nimport { commitPlan, type PlanToolContext, readPlan } from &#039;.\/plan-store.js&#039;;\n\ntype Goal = &#039;maximize&#039; | &#039;minimize&#039;;\n\nconst inputSchema = z.object({\n  city: z.string().describe(&#039;City of the plan, e.g. &quot;Rome&quot;.&#039;),\n});\n\nexport const maximizeTimeTool = createTool({\n  id: &#039;maximizeTime&#039;,\n  description: `\n    Maximizes the time in a city of the current travel plan and commits the plan:\n    takes the earliest flight into the city and the latest flight out of it, each on\n    its current day. Use it for &quot;maximize my time in Rome&quot;, &quot;mehr Zeit in Paris&quot;.\n  `,\n  inputSchema,\n  execute: (input, context) =&gt; {\n    [...]\n  },\n});<\/code><\/pre>\n<p>The demo keeps the flight selection simple: the tool sorts the flights of the same day by departure time and takes the first or the last one. A production version would also consider arrival times, prices, and the effect on the other stops of the trip. An earlier flight into Rome, for instance, also shortens the stay in the previous city.<\/p>\n<p>The request got much faster: its duration and its input tokens dropped to well under half.<\/p>\n<p>Such tools make the behavior more deterministic and predictable. Code compares the flight times, and the model only maps the request to the tool. The price is flexibility, as the application only covers the cases someone thought of up front. A user who wants to maximize the morning in one city and the afternoon in another will not succeed without a further tool.<\/p>\n<p>This creates a trade-off between control and autonomy:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/tension-field.png\" alt=\"A double arrow between control on the left and autonomy on the right.\" style=\"display:block; margin:0 auto; max-width:600px\"><\/p>\n<p>The weaker the model, the further an application has to move towards control. With a frontier model, you can leave more decisions to the model.<\/p>\n<h3>List Manipulation<\/h3>\n<p>Tailored tools pay off for lists as well. One agent of the application lets users plan several bookings as a list of steps. With <code>getPlan<\/code> and <code>setPlan<\/code> alone, the model would have to write out the whole list again to move a single step, including every flight id and description. Hence, the agent offers tools for specific changes such as <code>updatePlanStep<\/code>, <code>movePlanStep<\/code>, <code>swapPlanSteps<\/code>, and <code>reversePlan<\/code>. For <em>Swap steps 1 and 3<\/em>, the model calls <code>swapPlanSteps<\/code> with the ids of both steps, and the code performs the change.<\/p>\n<h3>App Logic Compensating Errors<\/h3>\n<p>Not every mistake needs a better prompt. If the application can detect and fix a mistake deterministically, a few lines of code are cheaper and more reliable. The travel planner returns a trip as a list of flights and a list of hotels, and the order of the hotels has to follow the route. If the model mixes up this order, the client fixes it before storing the plan.<\/p>\n<h3>Error Messages That Name the Fix<\/h3>\n<p>In my <a href=\"https:\/\/github.com\/manfredsteyer\/agentic-sales\/tree\/gemma4\">agentic-sales<\/a> project, which I also moved to Gemma 4, I show that error messages are a valuable feedback channel. The model reads every tool error, so the message should say what went wrong and what to do instead. A message like <em>Filter value must be a scalar<\/em> names the problem but not the fix.<\/p>\n<p>Gemma passed lists such as <code>[&quot;Europe&quot;, &quot;Americas&quot;]<\/code> as filter values and tried to exclude values with a filter. The server now accepts lists, and for everything else, the message names the expected shape with an example and the alternative for excluding values:<\/p>\n<pre><code class=\"language-typescript\">\/\/ server\/apis\/request-parsing.ts\nimport type { Column, FilterScalar, FilterValue } from &#039;..\/data\/sales-dal.ts&#039;;\n\nexport class BadRequest extends Error {}\n\n[...]\n\nfunction parseFilterValue(value: unknown, column: Column): FilterValue {\n  if (value === null || isFilterScalar(value)) {\n    return value;\n  }\n  if (Array.isArray(value) &amp;&amp; value.length &gt; 0 &amp;&amp; value.every(isFilterScalar)) {\n    return value;\n  }\n  throw new BadRequest(\n    <code>Filter value for &#039;${column.name}&#039; must be a value or a non-empty list of values, <\/code> +\n      &#039;e.g. &quot;Bags&quot; or [&quot;Bags&quot;, &quot;Apparel&quot;]. A filter can\\&#039;t exclude values: &#039; +\n      &#039;load the rows without it and select them afterwards.&#039;,\n  );\n}<\/code><\/pre>\n<p>Failures must also be impossible to misread. After five failed calls of <code>addDashboardItem<\/code>, Gemma answered <em>I've added the bar chart to your dashboard<\/em>. Now these tools fail with <em>The dashboard was not changed: \u2026<\/em>.<\/p>\n<h3>Using the Words of the User<\/h3>\n<p>It helps when the name or the description of a tool contains the words users use. The model then maps a request to the tool more easily. The description of <code>maximizeTime<\/code> shown above, for instance, mentions <em>maximize my time in Rome<\/em> and the German <em>mehr Zeit in Paris<\/em>.<\/p>\n<h3>Accepting Numbers and Booleans Written as Strings<\/h3>\n<p>Gemma sometimes writes numbers and booleans as strings, for example <code>&quot;count&quot;: &quot;2&quot;<\/code> or <code>&quot;showCheckInButton&quot;: &quot;true&quot;<\/code> in the dashboard spec. A strict schema rejects them, and the user gets no dashboard. Where the intent is unambiguous, it makes sense to accept such values.<\/p>\n<p>The function <code>z.coerce.number()<\/code> converts numbers. For booleans, a preprocessor maps only the strings <code>&quot;true&quot;<\/code> and <code>&quot;false&quot;<\/code>. The function <code>z.coerce.boolean()<\/code> would not work here because it turns every non-empty string, including <code>&quot;false&quot;<\/code>, into <code>true<\/code>. The JSON schema the model sees still says integer and boolean:<\/p>\n<pre><code class=\"language-typescript\">\/\/ ai-server\/src\/mastra\/dashboard-dsl\/dashboard-spec.ts\nimport { z } from &#039;zod&#039;;\n\nconst positiveInt = z.coerce.number().int().positive();\n\nconst flag = z.preprocess(\n  (value) =&gt; (value === &#039;true&#039; ? true : value === &#039;false&#039; ? false : value),\n  z.boolean(),\n);\n\nexport const dashboardTileSchema = z.discriminatedUnion(&#039;type&#039;, [\n  [...]\n  z.object({\n    type: z.literal(&#039;boardingPasses&#039;),\n    count: positiveInt\n      .max(8)\n      .nullish()\n      .describe(&#039;Number of upcoming booked flights to show. Defaults to 2.&#039;),\n  }),\n  z.object({\n    type: z.literal(&#039;bookedFlightsList&#039;),\n    showCheckInButton: flag\n      .nullish()\n      .describe(\n        &#039;Whether to render a &quot;Check in&quot; button per booked flight. Defaults to true.&#039;,\n      ),\n    [...]\n  }),\n  [...]\n]);<\/code><\/pre>\n<h2>Prompt Engineering for Compact Models<\/h2>\n<p>With weaker models, the good old knowledge about prompt engineering becomes especially important. Strong models benefit from it too, even though they can work out a lot on their own. In my experiments, examples and negative examples proved particularly effective. Both work better in the description of the tool or parameter the decision is about than in the system prompt.<\/p>\n<h3>Examples Next to the Decision<\/h3>\n<p>In my experiments, Gemma followed examples much more reliably than rules.<\/p>\n<p>A rule in the system prompt says to render a search form when the user asks for flights without naming both cities. After <em>Search Graz \u2192 Madrid<\/em>, a follow-up such as <em>And back<\/em> sometimes led to such a form instead of a search. An example in the description of <code>findFlights<\/code> resolved this in my evals:<\/p>\n<pre><code class=\"language-typescript\">\/\/ src\/app\/domains\/ticketing\/ai\/tools\/find-flights.tool.ts\nimport { z } from &#039;zod&#039;;\n\nimport { createFrontendTool } from &#039;..\/..\/..\/shared\/util-copilotkit\/tool-definition&#039;;\n[...]\n\nexport const findFlightsTool = createFrontendTool({\n  name: &#039;findFlights&#039;,\n  description: `\n    Searches for flights and redirects the user to the result page where the found flights are shown.\n\n    [...]\n\n    Example:\n    - Previous search: Graz \u2192 Madrid\n    - User: &quot;And back&quot;\n    - Call: findFlights({ &quot;from&quot;: &quot;Madrid&quot;, &quot;to&quot;: &quot;Graz&quot; })\n    - WRONG: rendering a search form, as both cities are known\n  `,\n  parameters: z.object({\n    from: z.string().describe(&#039;airport of departure&#039;),\n    to: z.string().describe(&#039;airport of destination&#039;),\n  }),\n  handler: async ({ from, to }) =&gt; {\n    [...]\n  },\n});<\/code><\/pre>\n<p>Keep in mind that Gemma copies examples almost literally. When a prompt contained only a German example reply, Gemma answered English questions in German. One example per language resolved this in my tests.<\/p>\n<h3>Negative Examples for Similar Tools<\/h3>\n<p>Examples show what to do. When two tools look similar, the model also needs to know when not to use one of them. The example in the description of <code>findFlights<\/code> already names a wrong reaction.<\/p>\n<p>The agent that plans bookings as a list of steps shows this. Gemma read a request like <em>Move 1 to 5<\/em> as moving step 1 of the plan to position 5. The user wanted to rebook flight 1 to flight 5 instead. The new tool <code>addRebooking<\/code> alone did not help, as Gemma kept choosing <code>movePlanStep<\/code>. A negative example in the description of <code>movePlanStep<\/code> alone did not help either. Together, they resolved the issue in my evals:<\/p>\n<pre><code class=\"language-typescript\">\/\/ src\/app\/domains\/ticketing\/ai\/tools\/plan\/move-plan-step.tool.ts\nimport { [...] } from &#039;@angular\/core&#039;;\nimport { z } from &#039;zod&#039;;\n\nimport { createFrontendTool } from &#039;..\/..\/..\/..\/shared\/util-copilotkit\/tool-definition&#039;;\nimport { PlanStore } from &#039;..\/..\/plan\/plan-store&#039;;\n\nexport const movePlanStepTool = createFrontendTool({\n  name: &#039;movePlanStep&#039;,\n  description: `\n    Moves a single step to a new 1-based position in the plan, shifting the others.\n    [...]\n\n    Negative example:\n    - User: &quot;Move 1 to 5&quot;\n    - WRONG: movePlanStep, as numbers without the word &quot;step&quot; are flight ids\n    - Call instead: addRebooking({ &quot;cancelFlightId&quot;: 1, &quot;bookFlightId&quot;: 5 })\n    [...]\n  `,\n  parameters: z.object({ [...] }),\n  handler: async ({ id, toPosition }) =&gt; {\n    const store = inject(PlanStore);\n    store.moveStep(id, toPosition);\n    return { moved: id, toPosition };\n  },\n});<\/code><\/pre>\n<p>The place of the guidance matters, too. Examples for this request in the system prompt did not fix it. Instead, they broke other requests of the same agent.<\/p>\n<p>A more precise wording such as <em>Move flight 1 to flight 5<\/em> also works without these changes. However, Gemma then looked up the flights first. It needed more than twice the input tokens and about three times as long.<\/p>\n<h3>Further Prompting Techniques<\/h3>\n<p>The prompts in the application have used further well-known techniques for a long time, and they help compact models, too:<\/p>\n<ul>\n<li><strong>Role and structure:<\/strong> Each prompt starts with the role of the agent and groups its rules under headings.<\/li>\n<li><strong>Explicit steps:<\/strong> Multi-step tasks list their steps in order, for instance deriving a rough travel plan first and calling the workflow second.<\/li>\n<li><strong>Invariants:<\/strong> The prompt describes what a valid result looks like, such as a travel plan in which every overnight stay has a hotel. The model can check its changes against these rules.<\/li>\n<\/ul>\n<p>Frontier models often get by with a short prompt and the tool descriptions. Mid-sized models often need considerably more instructions. In the application, several prompts were already extensive before the migration to Gemma 4. They go back to an earlier optimization for GPT-5.6 Luna. <\/p>\n<p>The good news is that the prompts can grow iteratively and in a targeted way: each failure that the evals reveal shows where an instruction or an example is missing.<\/p>\n<p><div style=\"\nmargin: 8px 0;\npadding: 22px;\nborder: 1px solid #e5e7eb;\nborder-radius: 14px;\nbackground: #f8fafc;\n\"><\/p>\n<h3 style=\"margin-top:0\">Interested in production-ready Agentic UI architectures?<\/h3>\n<p>In my workshop, we dive into AG-UI, A2UI, MCP Apps, HITL patterns, and modern Angular architectures for real-world agentic systems.<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/training\/agentic-ai-with-angular-architecture-patterns\/\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/05\/sujet-workshop-en.png\" width=\"400\" alt=\"Workshop: Agentic AI with Angular \u2013 AG-UI, A2UI, MCP Apps & HITL patterns\" style=\"cursor:pointer !important\"><\/a><\/p>\n<p><a style=\"cursor:pointer !important\" href=\"https:\/\/www.angulararchitects.io\/en\/training\/agentic-ai-with-angular-architecture-patterns\/\">See all details \u2192<\/a>\n<\/div>\n<\/p>\n<h2>Conclusion<\/h2>\n<p>A local model such as Gemma 4 can drive a real Agentic UI if the architecture takes over what the model cannot do reliably. The measures that paid off are turning thinking off, tool call renderers, a narrow DSL, minimal tool results, use-case-specific tools, error messages that name the fix, and examples next to the decision.<\/p>\n<p>This makes the application more deterministic and less autonomous. Where to place it between control and autonomy depends on the model and the use case. Evals show whether a measure helps, and many of these measures pay off with frontier models, too.<\/p>\n<h2>FAQ<\/h2>\n<h3>Can agentic applications run on local models?<\/h3>\n<p>Yes. The sample application in this article runs six agents with tool calling, generative UI via A2UI, and multi-step workflows on Gemma 4 26B A4B, an open-weight model you can host yourself. For reproducible experiments, I used it through Cloudflare Workers AI instead of my own hardware. The patterns apply to self-hosted deployments as well, while the latency figures depend on the hardware. Compared to frontier models, the application needs more deterministic logic: tool call renderers, narrow DSLs, tools tailored to use cases, and helpful error messages.<\/p>\n<h3>Why turn off thinking for Gemma 4?<\/h3>\n<p>In my measurements, thinking made Gemma about three times slower and multiplied the output tokens without a reliability gain that would justify this latency. Agentic applications can move the remaining reasoning into tools and code, and the model corrects mistakes in the next step instead.<\/p>\n<h3>Does CopilotKit for Angular work with local models?<\/h3>\n<p>Yes. CopilotKit for Angular talks to the agents via AG-UI and does not depend on the model. The sample application configures the model in the Mastra backend, so the move from OpenAI to Gemma 4 left the connection between frontend and agents unchanged. Some measures in this article still touch the frontend, such as tool call renderers and the descriptions of frontend tools.<\/p>\n<h3>What is a tool call renderer in CopilotKit for Angular?<\/h3>\n<p>A tool call renderer is an Angular component that CopilotKit uses to display the result of a tool call. Unlike with a component tool, the model does not have to call it and copy data into its arguments. This saves output tokens and avoids copying errors.<\/p>\n<h3>How does A2UI work with compact models?<\/h3>\n<p>Instead of writing A2UI markup, the model produces a compact, application-specific DSL. The server translates it into A2UI and loads the data. This reduces the task to a few flat arguments that compact models handle much better than full markup.<\/p>\n<h3>Do these measures only apply to Gemma 4?<\/h3>\n<p>No. They apply to every compact or self-hosted model. Many of them, such as rendering tool results and narrow DSLs, also make frontier models faster and cheaper.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Local models are gaining ground. A model running on your own hardware keeps sensitive data on your premises, removes the dependency on a single provider, and makes costs predictable. For many companies, especially in regulated industries, this kind of sovereign AI is a requirement. However, the models you can host yourself usually trail the frontier [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":35226,"comment_status":"open","ping_status":"open","sticky":true,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-35270","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"Local models are gaining ground. A model running on your own hardware keeps sensitive data on your premises, removes the dependency on a single provider, and makes costs predictable. For many companies, especially in regulated industries, this kind of sovereign AI is a requirement. However, the models you can host yourself usually trail the frontier [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2026-11-01T22:13:16+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-11-02T13:51:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/social-sujet2-1024x536.png\" \/>\n<meta name=\"author\" content=\"Manfred Steyer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/social-sujet2-1024x536.png\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"26 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"TechArticle\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability\",\"datePublished\":\"2026-11-01T22:13:16+00:00\",\"dateModified\":\"2026-11-02T13:51:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\"},\"wordCount\":3953,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\",\"name\":\"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png\",\"datePublished\":\"2026-11-01T22:13:16+00:00\",\"dateModified\":\"2026-11-02T13:51:36+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png\",\"width\":1920,\"height\":1080},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/","og_locale":"en_US","og_type":"article","og_title":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects","og_description":"Local models are gaining ground. A model running on your own hardware keeps sensitive data on your premises, removes the dependency on a single provider, and makes costs predictable. For many companies, especially in regulated industries, this kind of sovereign AI is a requirement. However, the models you can host yourself usually trail the frontier [&hellip;]","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/","og_site_name":"ANGULARarchitects","article_published_time":"2026-11-01T22:13:16+00:00","article_modified_time":"2026-11-02T13:51:36+00:00","og_image":[{"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/social-sujet2-1024x536.png","type":"","width":"","height":""}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/social-sujet2-1024x536.png","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"26 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability","datePublished":"2026-11-01T22:13:16+00:00","dateModified":"2026-11-02T13:51:36+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/"},"wordCount":3953,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/","name":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png","datePublished":"2026-11-01T22:13:16+00:00","dateModified":"2026-11-02T13:51:36+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/11\/hero2.png","width":1920,"height":1080},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/optimizing-agentic-applications-for-local-models-patterns-for-speed-and-reliability\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Optimizing Agentic Applications for Local Models: Patterns for Speed and Reliability"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/35270","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=35270"}],"version-history":[{"count":7,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/35270\/revisions"}],"predecessor-version":[{"id":35283,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/35270\/revisions\/35283"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/35226"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=35270"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=35270"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=35270"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}