Sovereign AI Ecosystem

'Complete Guide: Building an AI-Powered Next.js Application with Custom Agent [post] deterministic

A comprehensive guide to building a Next.js application with a custom

Next.jsOllamaCustom Agent FrameworkAI Task AutomationLocal LLMsReal-Time StreamingTask DecompositionReactWeb DevelopmentAI Integration

![Image](/images/ComfyUI_00207_.png)

Building an AI-Powered Next.js Application with Custom Agent Framework, and Ollama

1. Introduction

**What is Ollama?** Ollama allows you to run large language models (LLMs) locally on your machine rather than relying on cloud APIs. This approach provides privacy benefits, reduces costs, and eliminates API latency issues—making it ideal for development and privacy-sensitive applications.

By the end of this tutorial, you'll have created a web application where users can submit goals like "Create a content calendar for social media" or "Analyze quarterly sales data," and watch as an AI agent systematically works through the problem, documenting its reasoning and producing high-quality results.

2. Setting Up the Project

2.1 Prerequisites

Before starting, ensure you have: - Node.js 18+ installed - Basic knowledge of React and Next.js - Ollama installed (we'll cover this in detail)

2.2 Creating a Next.js Application

Let's begin by creating a fresh Next.js project:

```bash

npx create-next-app@latest next-ollama-app

cd next-ollama-app

`

During the setup, select the following options: - Would you like to use TypeScript? → Yes (for type safety) - Would you like to use ESLint? → Yes - Would you like to use Tailwind CSS? → Yes (for styling) - Would you like to use the src/ directory? → Yes (for organization) - Would you like to use App Router? → Yes (for modern routing) - Would you like to customize the default import alias? → No

2.3 Installing Dependencies

Install the necessary packages:

bash npm install dotenv react-markdown

2.4 Setting Up Ollama

1. Visit [Ollama's official website](https://ollama.com/) and download the installer for your operating system. 2. Install Ollama following the on-screen instructions. 3. Open a terminal and pull the Mistral model (a powerful open-source LLM):

bash ollama pull mistral

This will download the model, which may take several minutes depending on your internet connection.

2.5 Creating a Custom Agent Framework

Let's create our own lightweight agent framework:

Create a file at src/lib/agent.ts:

```typescript // src/lib/agent.ts export interface Step { number: number; description: string; reasoning?: string; output?: string; }

export interface AgentResult { goal: string; steps: Step[]; output: string; }

export type StepCallback = (step: Step) => Promise<void> | void;

export class Agent { private goal: string; private maxSteps: number; private onStepComplete?: StepCallback; private steps: Step[] = [];

constructor(options: { goal: string; maxSteps?: number; onStepComplete?: StepCallback; }) { this.goal = options.goal; this.maxSteps = options.maxSteps || 5; this.onStepComplete = options.onStepComplete; }

async execute(): Promise<AgentResult> { // Step 1: Task analysis const taskAnalysis = await this.callOllama( Analyze this task: "${this.goal}". Break it down into ${this.maxSteps} clear steps that would lead to a high-quality result. Return a JSON array of step descriptions only, no additional text. ); let steps: string[] = []; try { const parsed = JSON.parse(this.extractJSON(taskAnalysis)); steps = Array.isArray(parsed) ? parsed : []; } catch (e) { // If parsing fails, try to extract steps using regex const stepRegex = /\d+\.\s*(.*?)(?=\d+\.|$)/gs; const matches = [...taskAnalysis.matchAll(stepRegex)]; steps = matches.map(match => match[1].trim()); } // Ensure we have steps if (steps.length === 0) { steps = ["Analyze the problem", "Generate solution", "Refine the output"]; } // Execute each step for (let i = 0; i < Math.min(steps.length, this.maxSteps); i++) { const stepNumber = i + 1; const stepDescription = steps[i]; // Generate reasoning for this step const reasoning = await this.callOllama( For the task: "${this.goal}", I am on step ${stepNumber}: "${stepDescription}". Explain your reasoning for how you'll approach this step. Keep it clear and concise. ); // Execute the step const stepPrompt = Task: "${this.goal}" Step ${stepNumber}/${Math.min(steps.length, this.maxSteps)}: ${stepDescription} Previous steps: ${this.steps.map(s => Step ${s.number}: ${s.description} -> ${s.output?.substring(0, 100)}...`).join('\n')}

Execute this step and provide the output. Be thorough but focused on just this step. ; const stepOutput = await this.callOllama(stepPrompt); // Record the step const step: Step = { number: stepNumber, description: stepDescription, reasoning, output: stepOutput }; this.steps.push(step); // Notify via callback if provided if (this.onStepComplete) { await this.onStepComplete(step); } } // Generate final comprehensive output const finalPrompt = You've been working on: "${this.goal}"

You've completed the following steps: ${this.steps.map(s => Step ${s.number}: ${s.description}).join('\n')}

Now, compile all of your work into a comprehensive final output that achieves the original goal. Format your response using Markdown for readability. Include headings, bullet points, and other formatting as appropriate. Ensure your response is complete, well-structured, and directly addresses the original goal. `; const finalOutput = await this.callOllama(finalPrompt); return { goal: this.goal, steps: this.steps, output: finalOutput }; }

private async callOllama(prompt: string): Promise<string> { try { const response = await fetch('http://localhost:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'mistral', prompt: prompt, stream: false, }), });

if (!response.ok) { throw new Error(Ollama API error: ${response.statusText}); }

const data = await response.json(); return data.response; } catch (error) { console.error('Error calling Ollama:', error); return Error: ${error instanceof Error ? error.message : 'Unknown error'}; } }

private extractJSON(text: string): string { // Try to extract JSON from the text const jsonRegex = /(\[.*\]|\{.*\})/s; const match = text.match(jsonRegex); return match ? match[0] : '[]'; } } ```

This custom agent implementation provides similar functionality to what we'd expect from Mastra: - Breaking down a task into logical steps - Reasoning about each step before execution - Executing steps sequentially - Providing step-by-step progress updates - Generating a comprehensive final output

3. Understanding the Frontend (React + Next.js)

Now, let's build a responsive, user-friendly interface for our agent application.

3.1 Creating the Home Page Component

Create or replace the file at src/app/page.tsx with:

```tsx "use client"; import { useState, useRef, useEffect } from "react"; import ReactMarkdown from "react-markdown";

export default function Home() { const [goal, setGoal] = useState<string>(""); const [logs, setLogs] = useState<string[]>([]); const [isRunning, setIsRunning] = useState<boolean>(false); const [result, setResult] = useState<string>(""); const logsEndRef = useRef<HTMLDivElement>(null);

// Auto-scroll to the bottom of logs useEffect(() => { if (logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [logs]);

const handleRunAgent = async () => { if (!goal.trim() || isRunning) return; setIsRunning(true); setLogs(["🤖 Initializing Mastra-inspired agent powered by Ollama..."]); setResult(""); try { const response = await

Sources

DanielKliewer.com blog · source

Related (0)

No recorded relationships.

← all Blog