How to Build Your First AI, GenAI & Agentic AI Project A developer has published a tutorial on building a first AI, GenAI, and agentic AI project, focusing on a local CLI tool that summarizes server logs and executes cleanup commands. The project uses plain Python with the OpenAI SDK, avoiding heavy abstractions like LangChain, and supports local models via Ollama. The tutorial demonstrates how to create an agent that takes actions based on LLM-generated insights. Every tutorial on the internet right now starts with a ten-paragraph essay defining what an LLM is, as if you haven't been forced to use GitHub Copilot or ChatGPT for the last two years. Let's skip that. The problem isn't understanding what Generative AI is anymore. The problem is that when you sit down to actually build something beyond a wrapper that sends "Hello" to the OpenAI API, the terminology gets murky fast. What makes something "GenAI"? When does an app cross the line into being an "Agent"? We are going to build a small local CLI tool that takes a messy, unstructured text file of raw server logs, generates a summary using an LLM, and then—here's the agentic part—automatically writes and executes a cleanup script based on what it found. No LangChain abstractions that hide what's actually happening. Just Python, the raw openai library pointing to whatever model you have , and standard subprocess calls. When I first started looking at agentic workflows, I tried LangChain first. It was more trouble than it was worth. I spent three hours debugging why some abstracted chain was eating my prompt variables before realizing the documentation I was reading was for a version released three weeks prior that was already deprecated. We are going to write plain Python. Create a new directory and set up a virtual environment. We'll need the OpenAI SDK and python-dotenv for managing API keys. If you don't want to pay OpenAI for messing around, swap out the base URL for a local Ollama instance running llama3 or mistral . The code works the same either way. mkdir log-agent cd log-agent python3 -m venv venv source venv/bin/activate pip install openai python-dotenv Create a .env file in the root: OPENAI API KEY=your key here Or if using Ollama locally: OPENAI BASE URL=http://localhost:11434/v1 OPENAI API KEY=ollama Now let's create a dummy log file to work with. Call it server.log : 2026-03-05 10:12:01 INFO: Server started on port 8080 2026-03-05 10:14:32 ERROR: Connection refused to database at 10.0.0.4:5432 2026-03-05 10:15:00 WARNING: High memory usage detected: 92% 2026-03-05 10:20:12 ERROR: Out of disk space on /var/log. Current free: 0MB 2026-03-05 10:21:00 INFO: Attempting log rotation... failed. Standard Generative AI takes an input and generates text. You give it the log file, it writes a nice summary. That's neat, but it still requires a human to read the summary and fix the problem. Agentic AI closes the loop. It gives the model the ability to take actions based on what it generates. We do this using tool use sometimes called function calling . We will define a Python function that can run shell commands, tell the LLM about it, and let the model decide if it needs to run it. Here is the core script. Save this as agent.py : python import os import json import subprocess from openai import OpenAI from dotenv import load dotenv load dotenv client = OpenAI Define the tool our agent is allowed to use def execute cleanup command command: str - str: """Executes a bash command to clean up disk space or restart services.""" print f"\n AGENT ACTION About to run command: {command}" confirmation = input "Allow this action? y/n : " if confirmation.lower = 'y': return "Action denied by user." try: result = subprocess.run command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True return f"Success:\n{result.stdout}" except subprocess.CalledProcessError as e: return f"Error executing command:\n{e.stderr}" Map the function name to the actual callable available tools = { "execute cleanup command": execute cleanup command } def run log agent log path: str : if not os.path.exists log path : print f"File not found: {log path}" return with open log path, 'r' as f: log content = f.read system prompt = """ You are a DevOps assistant. Analyze the provided server logs. If you find critical issues like disk space errors, you have access to a tool to execute bash commands to fix them. Only use the tool if strictly necessary. """ messages = {"role": "system", "content": system prompt}, {"role": "user", "content": f"Here are the logs to analyze:\n\n{log content}"} Describe the tool to the LLM tools = { "type": "function", "function": { "name": "execute cleanup command", "description": "Run a safe shell command to resolve server issues like clearing logs or restarting services.", "parameters": { "type": "object", "properties": { "command": { "type": "string", "name": "command", "description": "The bash command to execute e.g., 'truncate -s 0 /var/log/ .log' " } }, "required": "command" } } } print "Analyzing logs with LLM..." response = client.chat.completions.create model="gpt-4o-mini", messages=messages, tools=tools, tool choice="auto" response message = response.choices 0 .message messages.append response message Check if the model wants to call a function if response message.tool calls: for tool call in response message.tool calls: function name = tool call.function.name function args = json.loads tool call.arguments if function name in available tools: tool output = available tools function name command=function args.get "command" Send the tool results back to the model messages.append { "role": "tool", "tool call id": tool call.id, "name": function name, "content": tool output } Get the final response from the model after the tool execution second response = client.chat.completions.create model="gpt-4o-mini", messages=messages print "\nFinal Agent Response:" print second response.choices 0 .message.content else: print "\nAgent Response:" print response message.content if name == " main ": run log agent "server.log" Run it with: python agent.py If you run the script above, it will likely spot the Out of disk space on /var/log error and try to run a command to clear space. That's the cool part working. Here is what will trip you up in practice, because it definitely tripped me up: The model hallucinating destructive commands. When I first tested a variation of this agent, I gave it permission to run generic shell commands without strict system prompt guardrails. Instead of safely clearing a log file, it tried to run rm -rf /var/log/ without checking directory paths properly. Another subtle issue is silent parameter malformation. If you use smaller local models via Ollama like Llama 3 8B , they sometimes mess up the JSON schema for tool arguments. You'll get an error like: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 char 0 This happens because the model tried to output markdown code blocks inside the tool arguments field instead of raw JSON. If you're building real agents, you must wrap tool execution in try/except blocks and build a retry loop when the JSON parsing fails. Never trust the LLM to format output correctly 100% of the time. This is also why line 14 of our script has an explicit input confirmation check. Never give an autonomous agent unmonitored shell access. Even if the prompt says "be careful," models drift, especially in long loops. Take this script and add a while loop so the agent can run in a multi-step "plan-execute-evaluate" loop rather than just a single turn. Let it read the output of the tool it just ran, decide if the problem is fixed, and if not, try a different approach.