Guaranteed 15% off your current AI inference bill for team spending up to $20000 / month.

Book a call →
Back to Blogs
Engineering

Building Technology Tools with LLMs: A Step-by-Step Guide

Every on-call engineer has stared at a wall of logs at 2 AM. I recently shipped a small CLI tool that tails the last few hundred lines of any log file and...

Building Technology Tools with LLMs: A Step-by-Step Guide

Every on-call engineer has stared at a wall of logs at 2 AM. I recently shipped a small CLI tool that tails the last few hundred lines of any log file and returns a structured diagnosis in under five seconds. It runs entirely against Oxlo.ai, so I do not pay more when an incident dumps a massive stack trace into the buffer. Here is how I built it.

What you'll need

You need Python 3.10 or newer and the OpenAI SDK. Install it with pip.

pip install openai

You also need an Oxlo.ai API key from https://portal.oxlo.ai. I export mine as OXLO_API_KEY. Finally, grab a log file to analyze. I use /var/log/syslog in the examples, but any text log works.

Step 1: Bootstrap the Oxlo.ai client

I instantiate the client once at module level and pull the key from the environment. Oxlo.ai exposes an OpenAI-compatible endpoint, so no adapter code is required.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.oxlo.ai/v1",
    api_key=os.environ["OXLO_API_KEY"]
)

print("Oxlo.ai client ready.")

Step 2: Ingest and window log files

Production logs can grow to gigabytes. I never send the whole file. Instead, I read the last N lines, defaulting to 200. That is usually enough context for triage, and because Oxlo.ai uses flat per-request pricing, I can experiment with larger windows without watching a meter run. See https://oxlo.ai/pricing for plan details.

def tail_logs(path, max_lines=200):
    with open(path, "r", encoding="utf-8", errors="ignore") as f:
        lines = f.readlines()
    return "".join(lines[-max_lines:])

LOG_PATH = "/var/log/syslog"
log_text = tail_logs(LOG_PATH)
print(f"Ingested {len(log_text)} characters from {LOG_PATH}")

Step 3: Define the system prompt

The system prompt is the most important part of the tool. I treat it like a runbook I would hand to a junior SRE. It enforces a strict output schema so the CLI can parse and display results cleanly.

SYSTEM_PROMPT = """You are a senior site reliability engineer.
Analyze the provided log excerpt and return JSON with exactly these keys:
- summary: one sentence describing what happened.
- root_cause: the most likely technical reason.
- fix: a concrete command or configuration change.
- severity: one of low, medium, high, or critical.
Base your answer only on the evidence in the logs."""

Step 4: Build the core diagnosis function

With the client, prompt, and logs in place, the core function is simple. I use llama-3.3-70b because it handles structured instructions reliably and responds quickly. For deeper reasoning on complex distributed traces, I swap the model string to qwen-3-32b or deepseek-v3.2.

def diagnose(log_text):
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Analyze these logs:\n\n{log_text}"},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

print(diagnose(log_text))

Step 5: Enforce structured output with JSON mode

Free-form text is fine for ad-hoc queries, but a tool needs predictable fields. Oxlo.ai supports JSON mode via response_format, so I add that to the completion call and parse the result with the standard library.

import json

def diagnose_structured(log_text):
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Analyze these logs:\n\n{log_text}"},
        ],
        response_format={"type": "json_object"},
        temperature=0.2,
    )
    return json.loads(response.choices[0].message.content)

report = diagnose_structured(log_text)
print(f"Severity: {report['severity']}")
print(f"Root cause: {report['root_cause']}")

Step 6: Wrap the tool in a CLI

I use argparse so the script feels like a native Unix tool. It accepts a file path and an optional line count. I also add a small guard for empty files.

import argparse

def main():
    parser = argparse.ArgumentParser(
        description="Triage logs via Oxlo.ai"
    )
    parser.add_argument("logfile", help="Path to log file")
    parser.add_argument(
        "--lines", type=int, default=200,
        help="Number of trailing lines to analyze"
    )
    args = parser.parse_args()

    raw = tail_logs(args.logfile, args.lines)
    if not raw.strip():
        print("Log file is empty or unreadable.")
        return

    report = diagnose_structured(raw)
    print(f"\nSeverity  : {report['severity'].upper()}")
    print(f"Summary   : {report['summary']}")
    print(f"Root Cause: {report['root_cause']}")
    print(f"Fix       : {report['fix']}")

if __name__ == "__main__":
    main()

Run it

I ran this against a real nginx error log from last week. The output looked like this.

$ python log_doctor.py /var/log/nginx/error.log --lines 150

Severity  : HIGH
Summary   : Upstream timed out repeatedly for /api/v1/export starting at 03:14 UTC.
Root Cause: The worker pool on 10.0.2.15 was exhausted by a slow database query.
Fix       : Restart gunicorn and increase workers from 4 to 8 in /etc/systemd/system/app.service.

Next steps

Two concrete ways to extend this. First, add a --model argument so teammates can override the default with kimi-k2.6 when they need advanced reasoning or vision support for screenshot-based logs. Second, wire the JSON output into a Slack webhook so the bot posts a severity-coded alert automatically.

Ready to build with Oxlo.ai?

Get started building high-performance AI inference applications today.

Get started
Ox Assistant
Online
OxBot
OxBot

Hi there! Try our cost calculator to see what you'd save with Oxlo.ai.